Sube Webclient/src
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
import '@fontsource/roboto/300.css';
|
||||
import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
import '@fontsource/oswald/500.css';
|
||||
import '@fontsource/oswald/600.css';
|
||||
import '@fontsource/oswald/700.css';
|
||||
import { lazy, ReactElement, Suspense } from 'react';
|
||||
import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import routes from './modules/core/constants/routes';
|
||||
import { APP_ROUTES } from './modules/core/constants/appRoutes';
|
||||
import { useAuth } from './modules/auth/hook/auth.hook';
|
||||
import SidebarLayout from './views/core/components/SidebarLayout';
|
||||
import PublicLayout from './views/core/components/PublicLayout';
|
||||
import { UserRolesType } from './modules/core/enum/user/userRolesType';
|
||||
import InvalidToken from './views/core/errors/invalidToken';
|
||||
import Forbidden from './views/core/errors/forbidden';
|
||||
import NotFound from './views/core/errors/NotFound';
|
||||
import PrivateRoute from './views/core/privateRoute';
|
||||
import ScrollToTop from './views/core/components/ScrollToTop';
|
||||
import GlobalLoadingOverlay from './views/core/components/GlobalLoadingOverlay';
|
||||
import BlockingOverlay from './views/core/components/BlockingOverlay';
|
||||
|
||||
// Every route-level page is loaded on demand instead of shipped in the one
|
||||
// main bundle every visitor downloads on first paint — the whole admin
|
||||
// panel (Jugadores, Sanciones, the tournament wizard, …) was landing in a
|
||||
// public visitor's browser just to render the home page. `NotFound`,
|
||||
// `Forbidden` and `InvalidToken` stay eager: `App()` can return them
|
||||
// directly from an early check below, outside the <Suspense> boundary the
|
||||
// <Routes> tree sits in, and they're tiny enough that splitting them buys
|
||||
// nothing.
|
||||
const Home = lazy(() => import('./views/home/home'));
|
||||
const PublicTeamPage = lazy(() => import('./views/home/teams/PublicTeamPage'));
|
||||
const PublicSanctionsPage = lazy(() => import('./views/home/sanctions/PublicSanctionsPage'));
|
||||
const PublicChampionsPage = lazy(() => import('./views/home/champions/PublicChampionsPage'));
|
||||
const PublicMatchPage = lazy(() => import('./views/home/matches/PublicMatchPage'));
|
||||
const PublicTournamentPage = lazy(() => import('./views/home/tournaments/PublicTournamentPage'));
|
||||
const PublicSeasonsPage = lazy(() => import('./views/home/seasons/PublicSeasonsPage'));
|
||||
const PublicSeasonPage = lazy(() => import('./views/home/seasons/PublicSeasonPage'));
|
||||
const BlogPostDetailPage = lazy(() => import('./views/blogPost/BlogPostDetailPage'));
|
||||
const BlogListPage = lazy(() => import('./views/blogPost/BlogListPage'));
|
||||
const AddBlogPostForm = lazy(() => import('./views/blogPost/addBlogPostForm'));
|
||||
const BlogPostsPage = lazy(() => import('./views/blogPost/BlogPostsPage'));
|
||||
const BlogPostEditPage = lazy(() => import('./views/blogPost/BlogPostEditPage'));
|
||||
const Login = lazy(() => import('./views/auth/login'));
|
||||
const HowWeAre = lazy(() => import('./views/home/howWeAre/howWeAre'));
|
||||
const MedicalRecord = lazy(() => import('./views/home/information/medicalRecord'));
|
||||
const Regulation = lazy(() => import('./views/home/information/regulation'));
|
||||
const PlayersPage = lazy(() => import('./views/player/PlayersPage'));
|
||||
const PlayerPage = lazy(() => import('./views/player/PlayerPage'));
|
||||
const TeamPage = lazy(() => import('./views/team/TeamPage'));
|
||||
const TournamentPage = lazy(() => import('./views/tournament/TournamentPage'));
|
||||
const TournamentEditPage = lazy(() => import('./views/tournament/TournamentEditPage'));
|
||||
const TournamentWizardPage = lazy(() => import('./views/tournament/wizard/TournamentWizardPage'));
|
||||
const DivisionPage = lazy(() => import('./views/division/divisionPage'));
|
||||
const DivisionCreatePage = lazy(() => import('./views/division/divisionCreatePage'));
|
||||
const DivisionEditPage = lazy(() => import('./views/division/divisionEditPage'));
|
||||
const MatchPage = lazy(() => import('./views/match/matchPage'));
|
||||
const UsersPage = lazy(() => import('./views/panel/UsersPage'));
|
||||
const UserDetails = lazy(() => import('./views/user/userDetails'));
|
||||
const CreateUser = lazy(() => import('./views/user/createUser'));
|
||||
const InviteUser = lazy(() => import('./views/user/inviteUser'));
|
||||
const EditUser = lazy(() => import('./views/user/editUser'));
|
||||
const ChangePasswordPage = lazy(() => import('./views/panel/ChangePasswordPage'));
|
||||
const StatisticsPage = lazy(() => import('./views/panel/StatisticsPage'));
|
||||
const AuditLogsPage = lazy(() => import('./views/panel/AuditLogsPage'));
|
||||
const DataAdministrationPage = lazy(() => import('./views/panel/DataAdministrationPage'));
|
||||
const PasswordReset = lazy(() => import('./views/auth/passwordReset'));
|
||||
const ForgotPassword = lazy(() => import('./views/auth/forgotPassword'));
|
||||
const ActivateAccount = lazy(() => import('./views/auth/activateAccount'));
|
||||
const ClubsPage = lazy(() => import('./views/club/ClubsPage'));
|
||||
const ClubHistoryPage = lazy(() => import('./views/club/ClubHistoryPage'));
|
||||
const PlayerSanctionsPage = lazy(() => import('./views/playerSanction/PlayerSanctionsPage'));
|
||||
const PlayerSanctionPage = lazy(() => import('./views/playerSanction/PlayerSanctionPage'));
|
||||
const PlayerSanctionEditPage = lazy(() => import('./views/playerSanction/playerSanctionEditPage'));
|
||||
const VenuesPage = lazy(() => import('./views/venue/VenuesPage'));
|
||||
const VenuePage = lazy(() => import('./views/venue/venuePage'));
|
||||
const SeasonsPage = lazy(() => import('./views/season/SeasonsPage'));
|
||||
const AdminSeasonDetailPage = lazy(() => import('./views/season/AdminSeasonDetailPage'));
|
||||
|
||||
const FIRST_TAB_BY_ROLE: Partial<Record<UserRolesType, string>> = {
|
||||
[UserRolesType.Owner]: APP_ROUTES.panelSeasons,
|
||||
[UserRolesType.Admin]: APP_ROUTES.panelSeasons,
|
||||
};
|
||||
|
||||
interface AdminRouteConfig {
|
||||
path: string;
|
||||
element: ReactElement;
|
||||
allowedRoles?: UserRolesType[];
|
||||
}
|
||||
|
||||
const ADMIN_ROUTES: AdminRouteConfig[] = [
|
||||
{ path: APP_ROUTES.passwordReset, element: <PasswordReset /> },
|
||||
{
|
||||
path: APP_ROUTES.panelPlayers,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <PlayersPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelPlayer.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <PlayerPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelTeamDetail.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <TeamPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelTournamentDetail.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <TournamentPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelTournamentEdit.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <TournamentEditPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelTeams,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <ClubsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelClub.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <ClubHistoryPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSanctions,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <PlayerSanctionsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSanction.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <PlayerSanctionPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSanctionEdit.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <PlayerSanctionEditPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelVenues,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <VenuesPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelVenue.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <VenuePage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSeasons,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <SeasonsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSeason.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <AdminSeasonDetailPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelTournamentWizard,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <TournamentWizardPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelDivisionCreate,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <DivisionCreatePage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelDivisionEdit.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <DivisionEditPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelDivision.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <DivisionPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelMatch.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <MatchPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelBlog,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <BlogPostsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelBlogCreate,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <AddBlogPostForm />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelBlogEdit.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <BlogPostEditPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelUsers,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <UsersPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelUserCreate,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <CreateUser />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelUserInvite,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <InviteUser />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelUserEdit.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <EditUser />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelUser.pattern,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <UserDetails />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelSettings,
|
||||
element: <Navigate to={APP_ROUTES.panelChangePassword} replace />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelChangePassword,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <ChangePasswordPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelEditProfile,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <EditUser />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelStatistics,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <StatisticsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelAuditLogs,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <AuditLogsPage />,
|
||||
},
|
||||
{
|
||||
path: APP_ROUTES.panelDataAdministration,
|
||||
allowedRoles: [UserRolesType.Admin, UserRolesType.Owner],
|
||||
element: <DataAdministrationPage />,
|
||||
},
|
||||
{ path: '*', element: <NotFound /> },
|
||||
];
|
||||
|
||||
interface PublicRouteConfig {
|
||||
path: string;
|
||||
element: ReactElement;
|
||||
}
|
||||
|
||||
const PUBLIC_ROUTES: PublicRouteConfig[] = [
|
||||
{ path: APP_ROUTES.passwordReset, element: <PasswordReset /> },
|
||||
{ path: APP_ROUTES.home, element: <Home /> },
|
||||
{ path: APP_ROUTES.quienesSomos, element: <HowWeAre /> },
|
||||
{ path: APP_ROUTES.fichaMedica, element: <MedicalRecord /> },
|
||||
{ path: APP_ROUTES.reglamento, element: <Regulation /> },
|
||||
{ path: APP_ROUTES.publicTeam.pattern, element: <PublicTeamPage /> },
|
||||
{ path: APP_ROUTES.publicSanctions, element: <PublicSanctionsPage /> },
|
||||
{ path: APP_ROUTES.publicChampions, element: <PublicChampionsPage /> },
|
||||
{ path: APP_ROUTES.publicMatch.pattern, element: <PublicMatchPage /> },
|
||||
{ path: APP_ROUTES.publicSeasons, element: <PublicSeasonsPage /> },
|
||||
{ path: APP_ROUTES.publicSeason.pattern, element: <PublicSeasonPage /> },
|
||||
// No `/torneos` flat listing route: it was never linked from any nav
|
||||
// (dead/unreachable, HU orphan-route audit) — every tournament is reached
|
||||
// via Temporadas -> season -> tournament instead.
|
||||
{
|
||||
path: APP_ROUTES.publicTournament.pattern,
|
||||
element: <PublicTournamentPage />,
|
||||
},
|
||||
{ path: APP_ROUTES.publicBlog, element: <BlogListPage /> },
|
||||
{ path: APP_ROUTES.blogPost.pattern, element: <BlogPostDetailPage /> },
|
||||
];
|
||||
|
||||
function App() {
|
||||
const { isAuthenticated, role } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
if (location.pathname === APP_ROUTES.forbidden) return <Forbidden />;
|
||||
if (location.pathname === routes.tokenInvalido) return <InvalidToken />;
|
||||
|
||||
const defaultTab = FIRST_TAB_BY_ROLE[role] ?? APP_ROUTES.panelUsers;
|
||||
|
||||
// Public pages render for EVERYONE — authenticated or not — so shareable
|
||||
// slug links (public tournament HU-14, blog post HU-13, team/match, HU-15)
|
||||
// keep working even while an admin is logged in. Previously the whole public
|
||||
// route tree was omitted for authenticated users, so any public slug URL fell
|
||||
// through to the panel catch-all and 404'd without ever hitting the API.
|
||||
//
|
||||
// Login (HU-02) and the 404/NotFound catch-all (HU-04) render without the
|
||||
// public header/footer, so they sit outside the PublicLayout chrome. The
|
||||
// admin panel is only mounted when authenticated, under one persistent
|
||||
// SidebarLayout (via <Outlet />) so the sidebar survives panel navigation.
|
||||
return (
|
||||
<>
|
||||
<ScrollToTop />
|
||||
<GlobalLoadingOverlay />
|
||||
<Suspense fallback={<BlockingOverlay open />}>
|
||||
<Routes>
|
||||
<Route element={<PublicLayout />}>
|
||||
{PUBLIC_ROUTES.map(({ path, element }) => (
|
||||
<Route key={path} path={path} element={element} />
|
||||
))}
|
||||
</Route>
|
||||
<Route path={APP_ROUTES.login} element={<Login />} />
|
||||
<Route path={APP_ROUTES.forgotPassword} element={<ForgotPassword />} />
|
||||
<Route path={APP_ROUTES.activate} element={<ActivateAccount />} />
|
||||
|
||||
{isAuthenticated && (
|
||||
<Route
|
||||
element={
|
||||
<SidebarLayout>
|
||||
<Outlet />
|
||||
</SidebarLayout>
|
||||
}
|
||||
>
|
||||
{ADMIN_ROUTES.filter(({ path }) => path !== '*').map(
|
||||
({ path, element, allowedRoles }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
element={
|
||||
allowedRoles ? (
|
||||
<PrivateRoute allowedRoles={allowedRoles}>
|
||||
{element}
|
||||
</PrivateRoute>
|
||||
) : (
|
||||
element
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Route
|
||||
path={APP_ROUTES.panel}
|
||||
element={<Navigate to={defaultTab} replace />}
|
||||
/>
|
||||
</Route>
|
||||
)}
|
||||
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Reference in New Issue
Block a user