From 881063eff19a7bb593cf7ddaaa58228c6dcfd374 Mon Sep 17 00:00:00 2001 From: Niels Mokkenstorm Date: Sun, 10 May 2026 14:02:44 +0200 Subject: [PATCH] feat(client): pages refactor - Page wrapper, subdir reorg, router only imports pages --- .../src/features/auth/components/AuthForm.tsx | 2 +- .../src/features/auth/components/index.ts | 6 - apps/client/src/pages/ApplicationFlowPage.tsx | 5 - apps/client/src/pages/ApplicationsPage.tsx | 5 - apps/client/src/pages/CreateEducationPage.tsx | 26 --- .../src/pages/CreateJobExperiencePage.tsx | 25 --- apps/client/src/pages/CreateVacancyPage.tsx | 29 ---- apps/client/src/pages/DashboardPage.tsx | 56 +++--- apps/client/src/pages/OnboardingPage.tsx | 6 +- apps/client/src/pages/OrganizationsPage.tsx | 42 ++--- .../src/pages/ResendVerificationEmailPage.tsx | 5 - apps/client/src/pages/SettingsPage.tsx | 36 ++-- apps/client/src/pages/VerifyEmailPage.tsx | 5 - .../src/pages/{ => admin}/AdminPage.tsx | 14 +- .../pages/{ => admin}/SystemStatusPage.tsx | 29 ++-- .../applications/ApplicationsPage.tsx} | 56 +++--- .../applications/ApplyPage.tsx} | 159 +++++++++--------- .../pages/{ => auth}/ForgotPasswordPage.tsx | 8 +- .../auth/LoginPage.tsx} | 8 +- .../auth/RegisterPage.tsx} | 8 +- .../auth/ResendVerificationEmailPage.tsx} | 4 +- .../auth/VerifyEmailPage.tsx} | 4 +- .../client/src/pages/{ => cvs}/CVEditPage.tsx | 10 +- .../client/src/pages/{ => cvs}/CVViewPage.tsx | 4 +- apps/client/src/pages/{ => cvs}/CVsPage.tsx | 39 +++-- .../src/pages/{ => cvs}/CreateCVPage.tsx | 26 ++- .../pages/education/CreateEducationPage.tsx | 19 +++ .../{ => education}/EditEducationPage.tsx | 40 ++--- .../pages/{ => education}/EducationPage.tsx | 58 +++---- .../CreateJobExperiencePage.tsx | 21 +++ .../EditJobExperiencePage.tsx | 40 ++--- .../JobExperiencePage.tsx | 60 +++---- .../src/pages/{ => profile}/ProfilePage.tsx | 28 ++- .../src/pages/{ => profile}/ProfilesPage.tsx | 20 +-- .../src/pages/vacancies/CreateVacancyPage.tsx | 26 +++ .../pages/{ => vacancies}/VacanciesPage.tsx | 49 +++--- apps/client/src/router/AppRouter.tsx | 85 ++++++---- packages/ui/src/components/Page.tsx | 29 ++++ packages/ui/src/components/index.ts | 1 + packages/ui/src/index.ts | 1 + 40 files changed, 524 insertions(+), 570 deletions(-) delete mode 100644 apps/client/src/features/auth/components/index.ts delete mode 100644 apps/client/src/pages/ApplicationFlowPage.tsx delete mode 100644 apps/client/src/pages/ApplicationsPage.tsx delete mode 100644 apps/client/src/pages/CreateEducationPage.tsx delete mode 100644 apps/client/src/pages/CreateJobExperiencePage.tsx delete mode 100644 apps/client/src/pages/CreateVacancyPage.tsx delete mode 100644 apps/client/src/pages/ResendVerificationEmailPage.tsx delete mode 100644 apps/client/src/pages/VerifyEmailPage.tsx rename apps/client/src/pages/{ => admin}/AdminPage.tsx (89%) rename apps/client/src/pages/{ => admin}/SystemStatusPage.tsx (94%) rename apps/client/src/{features/applications/components/ApplicationsOverview.tsx => pages/applications/ApplicationsPage.tsx} (86%) rename apps/client/src/{features/applications/components/ApplicationFlow.tsx => pages/applications/ApplyPage.tsx} (95%) rename apps/client/src/pages/{ => auth}/ForgotPasswordPage.tsx (94%) rename apps/client/src/{features/auth/components/LoginForm.tsx => pages/auth/LoginPage.tsx} (94%) rename apps/client/src/{features/auth/components/RegisterForm.tsx => pages/auth/RegisterPage.tsx} (96%) rename apps/client/src/{components/ResendVerificationEmailCard.tsx => pages/auth/ResendVerificationEmailPage.tsx} (98%) rename apps/client/src/{components/VerifyEmailCard.tsx => pages/auth/VerifyEmailPage.tsx} (99%) rename apps/client/src/pages/{ => cvs}/CVEditPage.tsx (94%) rename apps/client/src/pages/{ => cvs}/CVViewPage.tsx (99%) rename apps/client/src/pages/{ => cvs}/CVsPage.tsx (87%) rename apps/client/src/pages/{ => cvs}/CreateCVPage.tsx (91%) create mode 100644 apps/client/src/pages/education/CreateEducationPage.tsx rename apps/client/src/pages/{ => education}/EditEducationPage.tsx (67%) rename apps/client/src/pages/{ => education}/EducationPage.tsx (63%) create mode 100644 apps/client/src/pages/job-experience/CreateJobExperiencePage.tsx rename apps/client/src/pages/{ => job-experience}/EditJobExperiencePage.tsx (67%) rename apps/client/src/pages/{ => job-experience}/JobExperiencePage.tsx (64%) rename apps/client/src/pages/{ => profile}/ProfilePage.tsx (77%) rename apps/client/src/pages/{ => profile}/ProfilesPage.tsx (93%) create mode 100644 apps/client/src/pages/vacancies/CreateVacancyPage.tsx rename apps/client/src/pages/{ => vacancies}/VacanciesPage.tsx (74%) create mode 100644 packages/ui/src/components/Page.tsx diff --git a/apps/client/src/features/auth/components/AuthForm.tsx b/apps/client/src/features/auth/components/AuthForm.tsx index 106283d..a9cbe3d 100644 --- a/apps/client/src/features/auth/components/AuthForm.tsx +++ b/apps/client/src/features/auth/components/AuthForm.tsx @@ -7,7 +7,7 @@ type AuthFormProps = { submit: (e: FormEvent) => void | Promise; }; -export default function AuthForm({ title, children, submit }: AuthFormProps) { +export function AuthForm({ title, children, submit }: AuthFormProps) { return (
diff --git a/apps/client/src/features/auth/components/index.ts b/apps/client/src/features/auth/components/index.ts deleted file mode 100644 index 8ecb31f..0000000 --- a/apps/client/src/features/auth/components/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Auth feature components - */ -export { default as AuthForm } from "./AuthForm"; -export { default as LoginForm } from "./LoginForm"; -export { default as RegisterForm } from "./RegisterForm"; diff --git a/apps/client/src/pages/ApplicationFlowPage.tsx b/apps/client/src/pages/ApplicationFlowPage.tsx deleted file mode 100644 index 24d365c..0000000 --- a/apps/client/src/pages/ApplicationFlowPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ApplicationFlow } from "@/features/applications/components/ApplicationFlow"; - -export default function ApplicationFlowPage() { - return ; -} diff --git a/apps/client/src/pages/ApplicationsPage.tsx b/apps/client/src/pages/ApplicationsPage.tsx deleted file mode 100644 index 7c66512..0000000 --- a/apps/client/src/pages/ApplicationsPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ApplicationsOverview } from "@/features/applications/components/ApplicationsOverview"; - -export default function ApplicationsPage() { - return ; -} diff --git a/apps/client/src/pages/CreateEducationPage.tsx b/apps/client/src/pages/CreateEducationPage.tsx deleted file mode 100644 index 3c2aa95..0000000 --- a/apps/client/src/pages/CreateEducationPage.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { PageHeader } from "@cv/ui"; -import { useNavigate } from "react-router-dom"; -import { EducationForm } from "@/features/education/components/EducationForm"; - -export default function CreateEducationPage() { - const navigate = useNavigate(); - - const handleSuccess = () => { - navigate("/education"); - }; - - const handleCancel = () => { - navigate("/education"); - }; - - return ( -
- - - -
- ); -} diff --git a/apps/client/src/pages/CreateJobExperiencePage.tsx b/apps/client/src/pages/CreateJobExperiencePage.tsx deleted file mode 100644 index 7ae3823..0000000 --- a/apps/client/src/pages/CreateJobExperiencePage.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { PageHeader } from "@cv/ui"; -import { useNavigate } from "react-router-dom"; -import { JobExperienceCreationSelector } from "@/features/job-experience/components/JobExperienceCreationSelector"; - -export default function CreateJobExperiencePage() { - const navigate = useNavigate(); - - return ( -
- - - { - navigate("/job-experience"); - }} - onCancel={() => { - navigate("/job-experience"); - }} - /> -
- ); -} diff --git a/apps/client/src/pages/CreateVacancyPage.tsx b/apps/client/src/pages/CreateVacancyPage.tsx deleted file mode 100644 index 834deb4..0000000 --- a/apps/client/src/pages/CreateVacancyPage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Button, PageHeader } from "@cv/ui"; -import { useNavigate } from "react-router-dom"; -import { VacancyCreationSelector } from "@/features/vacancies/components"; - -export default function CreateVacancyPage() { - const navigate = useNavigate(); - - const next = () => { - navigate("/vacancies"); - }; - - return ( -
- - ← Back to Vacancies - - } - /> - -
- -
-
- ); -} diff --git a/apps/client/src/pages/DashboardPage.tsx b/apps/client/src/pages/DashboardPage.tsx index 6d99213..9af8043 100644 --- a/apps/client/src/pages/DashboardPage.tsx +++ b/apps/client/src/pages/DashboardPage.tsx @@ -1,6 +1,7 @@ import { ViewTransitionLink } from "@cv/routing"; -import { Button, FormattedDate, PageHeader, Placeholder } from "@cv/ui"; -import { useActiveProfileId } from "@/contexts/ProfileProvider"; +import { Button, FormattedDate, Page, Placeholder } from "@cv/ui"; +import { Navigate } from "react-router-dom"; +import { useProfile } from "@/contexts/ProfileProvider"; import { OnboardingChecklist } from "@/features/onboarding/components/OnboardingChecklist"; import { useCvTemplatesQuery, @@ -9,33 +10,50 @@ import { useOnboardingStatusQuery, } from "@/generated/graphql"; -export default function DashboardPage() { - const profileId = useActiveProfileId(); +const DashboardPage = () => { + const { + activeProfileId, + hasProfiles, + isLoading: profileLoading, + } = useProfile(); const { data: userData, isLoading: userLoading, error: userError, } = useMeMinimalQuery(); - const { data: cvsData, isLoading: cvsLoading } = useMyCVsQuery({ profileId }); + const { data: cvsData, isLoading: cvsLoading } = useMyCVsQuery( + { profileId: activeProfileId ?? "" }, + { enabled: !!activeProfileId }, + ); const { data: templatesData, isLoading: templatesLoading } = useCvTemplatesQuery({ first: 10 }); const { data: onboardingData } = useOnboardingStatusQuery(); + if (profileLoading) { + return ( + + + + ); + } + + if (!(hasProfiles && activeProfileId)) { + return ; + } + if (userLoading || cvsLoading || templatesLoading) { return ( -
- + -
+ ); } if (userError) { return ( -
- + -
+ ); } @@ -50,12 +68,10 @@ export default function DashboardPage() { const allComplete = onboardingSteps.every((s) => s.status === "COMPLETE"); return ( -
- - + {!allComplete && onboardingSteps.length > 0 && ( )} @@ -145,6 +161,8 @@ export default function DashboardPage() { )}
)} -
+ ); -} +}; + +export default DashboardPage; diff --git a/apps/client/src/pages/OnboardingPage.tsx b/apps/client/src/pages/OnboardingPage.tsx index 0761cb5..dbd0d2d 100644 --- a/apps/client/src/pages/OnboardingPage.tsx +++ b/apps/client/src/pages/OnboardingPage.tsx @@ -44,7 +44,7 @@ const statusToState = ( return "pending" as const; }; -export default function OnboardingPage() { +const OnboardingPage = () => { const { step: stepParam } = useParams<{ step?: string }>(); const navigate = useNavigate(); const { data, isLoading, refetch } = useOnboardingStatusQuery(); @@ -135,4 +135,6 @@ export default function OnboardingPage() { ); -} +}; + +export default OnboardingPage; diff --git a/apps/client/src/pages/OrganizationsPage.tsx b/apps/client/src/pages/OrganizationsPage.tsx index 7790cf7..e77a967 100644 --- a/apps/client/src/pages/OrganizationsPage.tsx +++ b/apps/client/src/pages/OrganizationsPage.tsx @@ -1,31 +1,25 @@ -import { PageHeader, Placeholder } from "@cv/ui"; +import { Page, Placeholder } from "@cv/ui"; import { CollapsibleOrganizationTable } from "@/features/organizations/components"; import { useMeOrganizationsQuery } from "@/generated/graphql"; -export default function OrganizationsPage() { +const OrganizationsPage = () => { const { data, isLoading, error } = useMeOrganizationsQuery(); + const title = "Organizations"; + const description = "Manage your organization memberships"; if (isLoading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } @@ -33,25 +27,17 @@ export default function OrganizationsPage() { if (organizations.length === 0) { return ( -
- + -
+ ); } return ( -
- +
{organizations.map((organization, index) => ( ))}
-
+ ); -} +}; + +export default OrganizationsPage; diff --git a/apps/client/src/pages/ResendVerificationEmailPage.tsx b/apps/client/src/pages/ResendVerificationEmailPage.tsx deleted file mode 100644 index 911bb84..0000000 --- a/apps/client/src/pages/ResendVerificationEmailPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ResendVerificationEmailCard } from "@/components/ResendVerificationEmailCard"; - -export default function ResendVerificationEmailPage() { - return ; -} diff --git a/apps/client/src/pages/SettingsPage.tsx b/apps/client/src/pages/SettingsPage.tsx index dc9b0ee..d41c60a 100644 --- a/apps/client/src/pages/SettingsPage.tsx +++ b/apps/client/src/pages/SettingsPage.tsx @@ -1,6 +1,6 @@ import { Button, - PageHeader, + Page, Placeholder, useConfirmationModal, useToast, @@ -21,7 +21,7 @@ import { } from "@/generated/graphql"; import { extractGraphQLErrorMessage } from "@/utils/graphql-error"; -export default function SettingsPage() { +const SettingsPage = () => { const { data, isLoading, error } = useMeMinimalQuery(); const { logout } = useToken(); const navigate = useNavigate(); @@ -93,39 +93,29 @@ export default function SettingsPage() { } }; + const title = "Settings"; + const description = "Manage your account and preferences"; + if (isLoading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } const user = data?.me; return ( -
- - +

Account

@@ -219,6 +209,8 @@ export default function SettingsPage() { onClose={() => setIsDeleteAccountOpen(false)} onSubmit={handleDeleteAccount} /> -
+ ); -} +}; + +export default SettingsPage; diff --git a/apps/client/src/pages/VerifyEmailPage.tsx b/apps/client/src/pages/VerifyEmailPage.tsx deleted file mode 100644 index 51f9ede..0000000 --- a/apps/client/src/pages/VerifyEmailPage.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { VerifyEmailCard } from "@/components/VerifyEmailCard"; - -export default function VerifyEmailPage() { - return ; -} diff --git a/apps/client/src/pages/AdminPage.tsx b/apps/client/src/pages/admin/AdminPage.tsx similarity index 89% rename from apps/client/src/pages/AdminPage.tsx rename to apps/client/src/pages/admin/AdminPage.tsx index d0ac9f0..2848741 100644 --- a/apps/client/src/pages/AdminPage.tsx +++ b/apps/client/src/pages/admin/AdminPage.tsx @@ -1,4 +1,4 @@ -import { PageHeader } from "@cv/ui"; +import { Page } from "@cv/ui"; import { useState } from "react"; import { AdminLookupTable } from "@/features/admin/components/AdminLookupTable"; @@ -14,7 +14,7 @@ const ENTITY_TABS = [ { type: "ORGANIZATION_ROLE", label: "Organization Roles" }, ] as const; -export default function AdminPage() { +const AdminPage = () => { const [activeTab, setActiveTab] = useState< (typeof ENTITY_TABS)[number]["type"] >(ENTITY_TABS[0].type); @@ -22,9 +22,7 @@ export default function AdminPage() { const activeEntry = ENTITY_TABS.find((t) => t.type === activeTab); return ( -
- - +
{ENTITY_TABS.map((tab) => (
+
); -} +}; + +export default AdminPage; diff --git a/apps/client/src/pages/SystemStatusPage.tsx b/apps/client/src/pages/admin/SystemStatusPage.tsx similarity index 94% rename from apps/client/src/pages/SystemStatusPage.tsx rename to apps/client/src/pages/admin/SystemStatusPage.tsx index 51c3e6f..219e6d8 100644 --- a/apps/client/src/pages/SystemStatusPage.tsx +++ b/apps/client/src/pages/admin/SystemStatusPage.tsx @@ -1,4 +1,4 @@ -import { Badge, Card, PageHeader, Placeholder, StatusDot } from "@cv/ui"; +import { Badge, Card, Page, Placeholder, StatusDot } from "@cv/ui"; import { AiCallLogTable } from "@/features/admin/components/AiCallLogTable"; import { QueueMessagesTable } from "@/features/admin/components/QueueMessagesTable"; import { QueueStatsCard } from "@/features/admin/components/QueueStatsCard"; @@ -215,7 +215,7 @@ const SlotsCard = ({ slots }: { slots: Array> }) => { ); }; -export default function SystemStatusPage() { +const SystemStatusPage = () => { const POLL_INTERVAL = 5_000; const { data, isLoading, error } = useSystemStatusQuery(undefined, { @@ -236,19 +236,17 @@ export default function SystemStatusPage() { if (isLoading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } @@ -256,10 +254,9 @@ export default function SystemStatusPage() { if (!status) { return ( -
- + -
+ ); } @@ -268,9 +265,7 @@ export default function SystemStatusPage() { : null; return ( -
- - +
-
+
); -} +}; + +export default SystemStatusPage; diff --git a/apps/client/src/features/applications/components/ApplicationsOverview.tsx b/apps/client/src/pages/applications/ApplicationsPage.tsx similarity index 86% rename from apps/client/src/features/applications/components/ApplicationsOverview.tsx rename to apps/client/src/pages/applications/ApplicationsPage.tsx index 157ebfd..f9ed911 100644 --- a/apps/client/src/features/applications/components/ApplicationsOverview.tsx +++ b/apps/client/src/pages/applications/ApplicationsPage.tsx @@ -1,4 +1,4 @@ -import { Badge, Button, FormattedDate, PageHeader, Placeholder } from "@cv/ui"; +import { Badge, Button, FormattedDate, Page, Placeholder } from "@cv/ui"; import { useMyApplicationsQuery } from "@/generated/graphql"; import { formatSalary } from "@/utils/salaryFormatter"; @@ -33,44 +33,43 @@ const getStatusColor = (statusName: string): BadgeColor => { return statusColorMap[statusName.toLowerCase()] ?? "ctp-gray"; }; -export const ApplicationsOverview = () => { +const ApplicationsPage = () => { const { data, isLoading, error, refetch } = useMyApplicationsQuery(); const applications = data?.me?.applications?.edges?.map((edge) => edge.node) || []; const totalCount = data?.me?.applications?.totalCount || 0; + const title = "My Applications"; + if (isLoading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } if (applications.length === 0) { return ( -
- + { Apply to Vacancy -
+ ); } return ( -
- refetch()}>Refresh} - /> - + refetch()}>Refresh} + >
{applications.map((application) => (
{ ))}
- {/* Summary Stats */}
@@ -211,6 +207,8 @@ export const ApplicationsOverview = () => {
Accepted
-
+ ); }; + +export default ApplicationsPage; diff --git a/apps/client/src/features/applications/components/ApplicationFlow.tsx b/apps/client/src/pages/applications/ApplyPage.tsx similarity index 95% rename from apps/client/src/features/applications/components/ApplicationFlow.tsx rename to apps/client/src/pages/applications/ApplyPage.tsx index 744a806..b04b893 100644 --- a/apps/client/src/features/applications/components/ApplicationFlow.tsx +++ b/apps/client/src/pages/applications/ApplyPage.tsx @@ -3,86 +3,9 @@ import { useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; +import { CVTemplateSelector } from "@/features/applications/components/CVTemplateSelector"; +import { VacancySelector } from "@/features/applications/components/VacancySelector"; import { useCreateApplicationMutation, useCvQuery } from "@/generated/graphql"; -import { CVTemplateSelector } from "./CVTemplateSelector"; -import { VacancySelector } from "./VacancySelector"; - -export const ApplicationFlow = () => { - const profileId = useActiveProfileId(); - const navigate = useNavigate(); - const queryClient = useQueryClient(); - const [selectedVacancyId, setSelectedVacancyId] = useState( - null, - ); - const [selectedCVId, setSelectedCVId] = useState(null); - const [coverLetter, setCoverLetter] = useState(""); - - const createApplicationMutation = useCreateApplicationMutation(); - - const handleCancel = () => { - navigate("/dashboard"); - }; - - const handleSubmitApplication = async () => { - if (!(selectedVacancyId && selectedCVId)) { - return; - } - - try { - await createApplicationMutation.mutateAsync({ - input: { - profileId, - vacancyId: selectedVacancyId, - cvId: selectedCVId, - coverLetter: coverLetter || null, - }, - }); - - // Invalidate and refetch application queries - await queryClient.invalidateQueries({ - queryKey: ["MyApplications"], - }); - - navigate("/applications", { - state: { message: "Application submitted successfully!" }, - }); - } catch (_error) { - // Error handling is done by React Query's onError callback - } - }; - - return ( -
-
- - - - - - - - - - - -
-
- ); -}; interface ApplicationReviewProps { vacancyId: string | null; @@ -182,3 +105,81 @@ const ApplicationReview = ({
); }; + +const ApplyPage = () => { + const profileId = useActiveProfileId(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [selectedVacancyId, setSelectedVacancyId] = useState( + null, + ); + const [selectedCVId, setSelectedCVId] = useState(null); + const [coverLetter, setCoverLetter] = useState(""); + + const createApplicationMutation = useCreateApplicationMutation(); + + const handleCancel = () => { + navigate("/dashboard"); + }; + + const handleSubmitApplication = async () => { + if (!(selectedVacancyId && selectedCVId)) { + return; + } + + try { + await createApplicationMutation.mutateAsync({ + input: { + profileId, + vacancyId: selectedVacancyId, + cvId: selectedCVId, + coverLetter: coverLetter || null, + }, + }); + + await queryClient.invalidateQueries({ + queryKey: ["MyApplications"], + }); + + navigate("/applications", { + state: { message: "Application submitted successfully!" }, + }); + } catch (_error) { + // handled by React Query onError + } + }; + + return ( +
+
+ + + + + + + + + + + +
+
+ ); +}; + +export default ApplyPage; diff --git a/apps/client/src/pages/ForgotPasswordPage.tsx b/apps/client/src/pages/auth/ForgotPasswordPage.tsx similarity index 94% rename from apps/client/src/pages/ForgotPasswordPage.tsx rename to apps/client/src/pages/auth/ForgotPasswordPage.tsx index 36381a1..97abd82 100644 --- a/apps/client/src/pages/ForgotPasswordPage.tsx +++ b/apps/client/src/pages/auth/ForgotPasswordPage.tsx @@ -2,11 +2,11 @@ import { ViewTransitionLink } from "@cv/routing"; import { Button, TextInput } from "@cv/ui"; import { useState } from "react"; import { PageLoader } from "@/components/PageLoader"; -import AuthForm from "@/features/auth/components/AuthForm"; +import { AuthForm } from "@/features/auth/components/AuthForm"; import { useRequestPasswordResetMutation } from "@/generated/graphql"; import { useAuthRedirect } from "@/hooks/useAuthRedirect"; -export default function ForgotPasswordPage() { +const ForgotPasswordPage = () => { const { isLoading } = useAuthRedirect(); const [email, setEmail] = useState(""); const [isSubmitted, setIsSubmitted] = useState(false); @@ -73,4 +73,6 @@ export default function ForgotPasswordPage() {

); -} +}; + +export default ForgotPasswordPage; diff --git a/apps/client/src/features/auth/components/LoginForm.tsx b/apps/client/src/pages/auth/LoginPage.tsx similarity index 94% rename from apps/client/src/features/auth/components/LoginForm.tsx rename to apps/client/src/pages/auth/LoginPage.tsx index a854855..3d6f531 100644 --- a/apps/client/src/features/auth/components/LoginForm.tsx +++ b/apps/client/src/pages/auth/LoginPage.tsx @@ -3,12 +3,12 @@ import { Button, TextInput } from "@cv/ui"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { PageLoader } from "@/components/PageLoader"; +import { AuthForm } from "@/features/auth/components/AuthForm"; import { useLogin } from "@/hooks/useAuth"; import { useAuthRedirect } from "@/hooks/useAuthRedirect"; import { extractGraphQLErrorCode } from "@/utils/graphql-error"; -import AuthForm from "./AuthForm"; -export default function LoginForm() { +const LoginPage = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const navigate = useNavigate(); @@ -76,4 +76,6 @@ export default function LoginForm() {

); -} +}; + +export default LoginPage; diff --git a/apps/client/src/features/auth/components/RegisterForm.tsx b/apps/client/src/pages/auth/RegisterPage.tsx similarity index 96% rename from apps/client/src/features/auth/components/RegisterForm.tsx rename to apps/client/src/pages/auth/RegisterPage.tsx index 005d6ee..b2b8297 100644 --- a/apps/client/src/features/auth/components/RegisterForm.tsx +++ b/apps/client/src/pages/auth/RegisterPage.tsx @@ -2,11 +2,11 @@ import { ViewTransitionLink } from "@cv/routing"; import { Button, TextInput } from "@cv/ui"; import { useState } from "react"; import { PageLoader } from "@/components/PageLoader"; +import { AuthForm } from "@/features/auth/components/AuthForm"; import { useRegister } from "@/hooks/useAuth"; import { useAuthRedirect } from "@/hooks/useAuthRedirect"; -import AuthForm from "./AuthForm"; -export default function RegisterForm() { +const RegisterPage = () => { const { isLoading } = useAuthRedirect(); const [name, setName] = useState(""); const [email, setEmail] = useState(""); @@ -104,4 +104,6 @@ export default function RegisterForm() {

); -} +}; + +export default RegisterPage; diff --git a/apps/client/src/components/ResendVerificationEmailCard.tsx b/apps/client/src/pages/auth/ResendVerificationEmailPage.tsx similarity index 98% rename from apps/client/src/components/ResendVerificationEmailCard.tsx rename to apps/client/src/pages/auth/ResendVerificationEmailPage.tsx index 6e52be2..29100e5 100644 --- a/apps/client/src/components/ResendVerificationEmailCard.tsx +++ b/apps/client/src/pages/auth/ResendVerificationEmailPage.tsx @@ -12,7 +12,7 @@ const isValidEmail = (email: string): boolean => { return emailRegex.test(email); }; -export const ResendVerificationEmailCard = () => { +const ResendVerificationEmailPage = () => { const location = useLocation(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); @@ -155,3 +155,5 @@ export const ResendVerificationEmailCard = () => {
); }; + +export default ResendVerificationEmailPage; diff --git a/apps/client/src/components/VerifyEmailCard.tsx b/apps/client/src/pages/auth/VerifyEmailPage.tsx similarity index 99% rename from apps/client/src/components/VerifyEmailCard.tsx rename to apps/client/src/pages/auth/VerifyEmailPage.tsx index 7bac4d3..8708c15 100644 --- a/apps/client/src/components/VerifyEmailCard.tsx +++ b/apps/client/src/pages/auth/VerifyEmailPage.tsx @@ -7,7 +7,7 @@ import { extractGraphQLErrorMessage, } from "@/utils/graphql-error"; -export const VerifyEmailCard = () => { +const VerifyEmailPage = () => { const location = useLocation(); const [searchParams] = useSearchParams(); const emailFromState = (location.state as { email?: string })?.email; @@ -191,3 +191,5 @@ export const VerifyEmailCard = () => { ); }; + +export default VerifyEmailPage; diff --git a/apps/client/src/pages/CVEditPage.tsx b/apps/client/src/pages/cvs/CVEditPage.tsx similarity index 94% rename from apps/client/src/pages/CVEditPage.tsx rename to apps/client/src/pages/cvs/CVEditPage.tsx index 81106b5..42fe9f2 100644 --- a/apps/client/src/pages/CVEditPage.tsx +++ b/apps/client/src/pages/cvs/CVEditPage.tsx @@ -21,7 +21,7 @@ const NotFoundScreen = () => ( ); -export const CVEditPage = () => { +const CVEditPage = () => { const { id } = useParams<{ id: string }>(); const cvId = id ?? ""; const data = useCVEditorData(cvId); @@ -29,7 +29,7 @@ export const CVEditPage = () => { if (data.isLoading) { return ; } - if (!data.cv.data?.cv || !data.profile.data?.profile) { + if (!(data.cv.data?.cv && data.profile.data?.profile)) { return ; } @@ -55,7 +55,9 @@ interface CVEditPageBodyProps { NonNullable["cv"]["data"]>["cv"] >; profile: NonNullable< - NonNullable["profile"]["data"]>["profile"] + NonNullable< + ReturnType["profile"]["data"] + >["profile"] >; renderedHtml: string | undefined; templates: Array<{ id: string; name: string }>; @@ -127,3 +129,5 @@ const CVEditPageBody = ({ ); }; + +export default CVEditPage; diff --git a/apps/client/src/pages/CVViewPage.tsx b/apps/client/src/pages/cvs/CVViewPage.tsx similarity index 99% rename from apps/client/src/pages/CVViewPage.tsx rename to apps/client/src/pages/cvs/CVViewPage.tsx index 43fa402..eec3050 100644 --- a/apps/client/src/pages/CVViewPage.tsx +++ b/apps/client/src/pages/cvs/CVViewPage.tsx @@ -90,7 +90,7 @@ const pdfButtonLabel = (state: PdfState): string => { return "Download PDF"; }; -export const CVViewPage = () => { +const CVViewPage = () => { const { id } = useParams<{ id: string }>(); const iframeRef = useRef(null); @@ -203,3 +203,5 @@ export const CVViewPage = () => { ); }; + +export default CVViewPage; diff --git a/apps/client/src/pages/CVsPage.tsx b/apps/client/src/pages/cvs/CVsPage.tsx similarity index 87% rename from apps/client/src/pages/CVsPage.tsx rename to apps/client/src/pages/cvs/CVsPage.tsx index 3f9ce9b..56ab14e 100644 --- a/apps/client/src/pages/CVsPage.tsx +++ b/apps/client/src/pages/cvs/CVsPage.tsx @@ -4,7 +4,7 @@ import { DeleteIcon, EditIcon, IconButton, - PageHeader, + Page, Placeholder, useConfirmationModal, useToast, @@ -13,7 +13,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { useDeleteCvMutation, useMyCVsQuery } from "@/generated/graphql"; -export const CVsPage = () => { +const CVsPage = () => { const profileId = useActiveProfileId(); const { data, isLoading } = useMyCVsQuery({ profileId }); const { mutateAsync: deleteCV } = useDeleteCvMutation(); @@ -48,32 +48,29 @@ export const CVsPage = () => { }); }; + const title = "My CVs"; + const description = "Create and manage your CVs using different templates"; + if (isLoading) { return ( -
- + -
+ ); } const cvs = data?.profile?.cvs?.edges?.map((e) => e.node) ?? []; return ( -
- - - - } - /> - + + + + } + > {cvs.length === 0 ? ( { ))}
)} - + ); }; + +export default CVsPage; diff --git a/apps/client/src/pages/CreateCVPage.tsx b/apps/client/src/pages/cvs/CreateCVPage.tsx similarity index 91% rename from apps/client/src/pages/CreateCVPage.tsx rename to apps/client/src/pages/cvs/CreateCVPage.tsx index f248493..9cf8931 100644 --- a/apps/client/src/pages/CreateCVPage.tsx +++ b/apps/client/src/pages/cvs/CreateCVPage.tsx @@ -1,11 +1,11 @@ -import { Button, PageHeader, Placeholder, TextInput, useToast } from "@cv/ui"; +import { Button, Page, Placeholder, TextInput, useToast } from "@cv/ui"; import { useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { useCreateCvMutation, useCvTemplatesQuery } from "@/generated/graphql"; -export const CreateCVPage = () => { +const CreateCVPage = () => { const profileId = useActiveProfileId(); const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -59,25 +59,19 @@ export const CreateCVPage = () => { } }; + const title = "Create New CV"; + const description = "Choose a template and give your CV a title"; + if (templatesLoading) { return ( -
- + -
+ ); } return ( -
- - +

@@ -169,6 +163,8 @@ export const CreateCVPage = () => {

-
+ ); }; + +export default CreateCVPage; diff --git a/apps/client/src/pages/education/CreateEducationPage.tsx b/apps/client/src/pages/education/CreateEducationPage.tsx new file mode 100644 index 0000000..9c44b81 --- /dev/null +++ b/apps/client/src/pages/education/CreateEducationPage.tsx @@ -0,0 +1,19 @@ +import { Page } from "@cv/ui"; +import { useNavigate } from "react-router-dom"; +import { EducationForm } from "@/features/education/components/EducationForm"; + +const CreateEducationPage = () => { + const navigate = useNavigate(); + const back = () => navigate("/education"); + + return ( + + + + ); +}; + +export default CreateEducationPage; diff --git a/apps/client/src/pages/EditEducationPage.tsx b/apps/client/src/pages/education/EditEducationPage.tsx similarity index 67% rename from apps/client/src/pages/EditEducationPage.tsx rename to apps/client/src/pages/education/EditEducationPage.tsx index d0121f2..65e92f3 100644 --- a/apps/client/src/pages/EditEducationPage.tsx +++ b/apps/client/src/pages/education/EditEducationPage.tsx @@ -1,25 +1,24 @@ -import { PageHeader, Placeholder } from "@cv/ui"; +import { Page, Placeholder } from "@cv/ui"; import { useNavigate, useParams } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { EducationForm } from "@/features/education/components/EducationForm"; import type { EducationFormData } from "@/features/education/components/education.schema"; import { useMeEducationQuery } from "@/generated/graphql"; -export default function EditEducationPage() { +const EditEducationPage = () => { const { id } = useParams<{ id: string }>(); const profileId = useActiveProfileId(); const navigate = useNavigate(); const { data, isPending } = useMeEducationQuery({ profileId }); + const back = () => navigate("/education"); + const title = "Edit Education"; + const description = "Update your education details"; if (isPending) { return ( -
- + -
+ ); } @@ -29,13 +28,9 @@ export default function EditEducationPage() { if (!education) { return ( -
- + -
+ ); } @@ -51,18 +46,15 @@ export default function EditEducationPage() { }; return ( -
- - + navigate("/education")} - onCancel={() => navigate("/education")} + onSuccess={back} + onCancel={back} /> -
+ ); -} +}; + +export default EditEducationPage; diff --git a/apps/client/src/pages/EducationPage.tsx b/apps/client/src/pages/education/EducationPage.tsx similarity index 63% rename from apps/client/src/pages/EducationPage.tsx rename to apps/client/src/pages/education/EducationPage.tsx index c043d53..babef96 100644 --- a/apps/client/src/pages/EducationPage.tsx +++ b/apps/client/src/pages/education/EducationPage.tsx @@ -1,11 +1,11 @@ -import { Button, PageHeader, Placeholder, useToast } from "@cv/ui"; +import { Button, Page, Placeholder, useToast } from "@cv/ui"; import { useNavigate } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { EducationTable } from "@/features/education/components"; import type { MeEducationQuery } from "@/generated/graphql"; import { useMeEducationQuery } from "@/generated/graphql"; -export default function EducationPage() { +const EducationPage = () => { const profileId = useActiveProfileId(); const { data, @@ -35,30 +35,28 @@ export default function EducationPage() { } }; + const title = "Education History"; + const description = "Manage your educational background and qualifications"; + const addAction = ( + + ); + if (loading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } @@ -67,17 +65,7 @@ export default function EducationPage() { if (educations.length === 0) { return ( -
- navigate("/education/create")}> - Add Education - - } - /> - + -
+ ); } return ( -
- navigate("/education/create")}> - Add Education - - } - /> - +

Your Education ({educations.length}) @@ -115,6 +93,8 @@ export default function EducationPage() { onDelete={handleDelete} />

-
+ ); -} +}; + +export default EducationPage; diff --git a/apps/client/src/pages/job-experience/CreateJobExperiencePage.tsx b/apps/client/src/pages/job-experience/CreateJobExperiencePage.tsx new file mode 100644 index 0000000..146d9f9 --- /dev/null +++ b/apps/client/src/pages/job-experience/CreateJobExperiencePage.tsx @@ -0,0 +1,21 @@ +import { Page } from "@cv/ui"; +import { useNavigate } from "react-router-dom"; +import { JobExperienceCreationSelector } from "@/features/job-experience/components/JobExperienceCreationSelector"; + +const CreateJobExperiencePage = () => { + const navigate = useNavigate(); + + return ( + + navigate("/job-experience")} + onCancel={() => navigate("/job-experience")} + /> + + ); +}; + +export default CreateJobExperiencePage; diff --git a/apps/client/src/pages/EditJobExperiencePage.tsx b/apps/client/src/pages/job-experience/EditJobExperiencePage.tsx similarity index 67% rename from apps/client/src/pages/EditJobExperiencePage.tsx rename to apps/client/src/pages/job-experience/EditJobExperiencePage.tsx index f69aefa..51c9f1b 100644 --- a/apps/client/src/pages/EditJobExperiencePage.tsx +++ b/apps/client/src/pages/job-experience/EditJobExperiencePage.tsx @@ -1,25 +1,24 @@ -import { PageHeader, Placeholder } from "@cv/ui"; +import { Page, Placeholder } from "@cv/ui"; import { useNavigate, useParams } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { JobExperienceForm } from "@/features/job-experience/components/JobExperienceForm"; import type { JobExperienceFormData } from "@/features/job-experience/components/jobExperience.schema"; import { useMeJobExperienceQuery } from "@/generated/graphql"; -export default function EditJobExperiencePage() { +const EditJobExperiencePage = () => { const { id } = useParams<{ id: string }>(); const profileId = useActiveProfileId(); const navigate = useNavigate(); const { data, isPending } = useMeJobExperienceQuery({ profileId }); + const back = () => navigate("/job-experience"); + const title = "Edit Job Experience"; + const description = "Update your job experience details"; if (isPending) { return ( -
- + -
+ ); } @@ -29,13 +28,9 @@ export default function EditJobExperiencePage() { if (!experience) { return ( -
- + -
+ ); } @@ -51,18 +46,15 @@ export default function EditJobExperiencePage() { }; return ( -
- - + navigate("/job-experience")} - onCancel={() => navigate("/job-experience")} + onSuccess={back} + onCancel={back} /> -
+ ); -} +}; + +export default EditJobExperiencePage; diff --git a/apps/client/src/pages/JobExperiencePage.tsx b/apps/client/src/pages/job-experience/JobExperiencePage.tsx similarity index 64% rename from apps/client/src/pages/JobExperiencePage.tsx rename to apps/client/src/pages/job-experience/JobExperiencePage.tsx index 6c81969..c9b7da3 100644 --- a/apps/client/src/pages/JobExperiencePage.tsx +++ b/apps/client/src/pages/job-experience/JobExperiencePage.tsx @@ -1,11 +1,11 @@ -import { Button, PageHeader, Placeholder, useToast } from "@cv/ui"; +import { Button, Page, Placeholder, useToast } from "@cv/ui"; import { useNavigate } from "react-router-dom"; import { useActiveProfileId } from "@/contexts/ProfileProvider"; import { JobExperienceTable } from "@/features/job-experience/components"; import type { MeJobExperienceQuery } from "@/generated/graphql"; import { useMeJobExperienceQuery } from "@/generated/graphql"; -export default function JobExperiencePage() { +const JobExperiencePage = () => { const profileId = useActiveProfileId(); const { data, @@ -35,27 +35,27 @@ export default function JobExperiencePage() { } }; + const title = "Job Experience"; + const description = "Manage your work experience and career history"; + const addAction = ( + + ); + if (loading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } @@ -64,17 +64,7 @@ export default function JobExperiencePage() { if (jobExperiences.length === 0) { return ( -
- navigate("/job-experience/create")}> - Create Experience - - } - /> - + -
+ ); } return ( -
- navigate("/job-experience/create")}> - Create Experience - - } - /> - +

Your Experience ({jobExperiences.length}) @@ -112,6 +92,8 @@ export default function JobExperiencePage() { onDelete={handleDelete} />

-
+ ); -} +}; + +export default JobExperiencePage; diff --git a/apps/client/src/pages/ProfilePage.tsx b/apps/client/src/pages/profile/ProfilePage.tsx similarity index 77% rename from apps/client/src/pages/ProfilePage.tsx rename to apps/client/src/pages/profile/ProfilePage.tsx index 3d2e913..6b96743 100644 --- a/apps/client/src/pages/ProfilePage.tsx +++ b/apps/client/src/pages/profile/ProfilePage.tsx @@ -1,21 +1,17 @@ -import { PageHeader, Placeholder } from "@cv/ui"; +import { Page, Placeholder } from "@cv/ui"; import { useActiveProfileId, useProfile } from "@/contexts/ProfileProvider"; import { PersonalProfileForm } from "@/features/profile/components/PersonalProfileForm"; import { ImportedFilesSection } from "@/features/user-settings/components/ImportedFilesSection"; -export default function ProfilePage() { +const ProfilePage = () => { const { activeProfile, isLoading } = useProfile(); const profileId = useActiveProfileId(); if (isLoading) { return ( -
- + -
+ ); } @@ -33,12 +29,10 @@ export default function ProfilePage() { : undefined; return ( -
- - +

Personal Profile @@ -52,6 +46,8 @@ export default function ProfilePage() {

-
+ ); -} +}; + +export default ProfilePage; diff --git a/apps/client/src/pages/ProfilesPage.tsx b/apps/client/src/pages/profile/ProfilesPage.tsx similarity index 93% rename from apps/client/src/pages/ProfilesPage.tsx rename to apps/client/src/pages/profile/ProfilesPage.tsx index 703a4ef..a41f0c2 100644 --- a/apps/client/src/pages/ProfilesPage.tsx +++ b/apps/client/src/pages/profile/ProfilesPage.tsx @@ -1,4 +1,4 @@ -import { Button, PageHeader, useConfirmationModal, useToast } from "@cv/ui"; +import { Button, Page, useConfirmationModal, useToast } from "@cv/ui"; import { useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; import { useProfile } from "@/contexts/ProfileProvider"; @@ -15,7 +15,7 @@ const formatDate = (dateStr: string): string => day: "numeric", }); -export default function ProfilesPage() { +const ProfilesPage = () => { const { profiles, activeProfile, setActiveProfileId, refetch } = useProfile(); const { showSuccess, showError } = useToast(); const { showConfirmation } = useConfirmationModal(); @@ -68,12 +68,10 @@ export default function ProfilesPage() { }; return ( -
- - +
-
+ ); -} +}; + +export default ProfilesPage; diff --git a/apps/client/src/pages/vacancies/CreateVacancyPage.tsx b/apps/client/src/pages/vacancies/CreateVacancyPage.tsx new file mode 100644 index 0000000..2021ebb --- /dev/null +++ b/apps/client/src/pages/vacancies/CreateVacancyPage.tsx @@ -0,0 +1,26 @@ +import { Button, Page } from "@cv/ui"; +import { useNavigate } from "react-router-dom"; +import { VacancyCreationSelector } from "@/features/vacancies/components"; + +const CreateVacancyPage = () => { + const navigate = useNavigate(); + const next = () => navigate("/vacancies"); + + return ( + + ← Back to Vacancies + + } + > +
+ +
+
+ ); +}; + +export default CreateVacancyPage; diff --git a/apps/client/src/pages/VacanciesPage.tsx b/apps/client/src/pages/vacancies/VacanciesPage.tsx similarity index 74% rename from apps/client/src/pages/VacanciesPage.tsx rename to apps/client/src/pages/vacancies/VacanciesPage.tsx index 80898dc..e4d588f 100644 --- a/apps/client/src/pages/VacanciesPage.tsx +++ b/apps/client/src/pages/vacancies/VacanciesPage.tsx @@ -1,9 +1,9 @@ -import { Button, PageHeader, Placeholder, useToast } from "@cv/ui"; +import { Button, Page, Placeholder, useToast } from "@cv/ui"; import { useNavigate } from "react-router-dom"; import { VacancyList } from "@/features/vacancies/components"; import { useMyVacanciesQuery } from "@/generated/graphql"; -export default function VacanciesPage() { +const VacanciesPage = () => { const { data, isLoading, error, refetch } = useMyVacanciesQuery(); const { showError } = useToast(); const navigate = useNavigate(); @@ -19,25 +19,20 @@ export default function VacanciesPage() { } }; + const title = "Job Vacancies"; + const description = "Manage your job vacancies and track applications"; + if (isLoading) { return ( -
- + -
+ ); } if (error) { return ( -
- + -
+ ); } @@ -55,17 +50,15 @@ export default function VacanciesPage() { ).filter((v) => v.company?.id && v.company?.name); return ( -
- navigate("/vacancies/create")}> - Create Vacancy - - } - /> - + navigate("/vacancies/create")}> + Create Vacancy + + } + >

Your Vacancies ({vacancies.length}) @@ -101,6 +94,8 @@ export default function VacanciesPage() { onDelete={handleDelete} />

-
+ ); -} +}; + +export default VacanciesPage; diff --git a/apps/client/src/router/AppRouter.tsx b/apps/client/src/router/AppRouter.tsx index a13f29f..46a6efe 100644 --- a/apps/client/src/router/AppRouter.tsx +++ b/apps/client/src/router/AppRouter.tsx @@ -3,51 +3,56 @@ import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { AdminRoute } from "@/components/AdminRoute"; import { PageLoader } from "@/components/PageLoader"; import { useToken } from "@/contexts/TokenProvider"; -import { LoginForm, RegisterForm } from "@/features/auth/components"; import { AuthenticatedLayout } from "@/layouts/AuthenticatedLayout"; -const VerifyEmailPage = lazy(() => import("@/pages/VerifyEmailPage")); +const LoginPage = lazy(() => import("@/pages/auth/LoginPage")); +const RegisterPage = lazy(() => import("@/pages/auth/RegisterPage")); +const VerifyEmailPage = lazy(() => import("@/pages/auth/VerifyEmailPage")); const ResendVerificationEmailPage = lazy( - () => import("@/pages/ResendVerificationEmailPage"), + () => import("@/pages/auth/ResendVerificationEmailPage"), +); +const ForgotPasswordPage = lazy( + () => import("@/pages/auth/ForgotPasswordPage"), ); -const ForgotPasswordPage = lazy(() => import("@/pages/ForgotPasswordPage")); -const ApplicationFlowPage = lazy(() => import("@/pages/ApplicationFlowPage")); -const ApplicationsPage = lazy(() => import("@/pages/ApplicationsPage")); -const CreateCVPage = lazy(() => - import("@/pages/CreateCVPage").then((m) => ({ default: m.CreateCVPage })), +const ApplyPage = lazy(() => import("@/pages/applications/ApplyPage")); +const ApplicationsPage = lazy( + () => import("@/pages/applications/ApplicationsPage"), ); -const CreateEducationPage = lazy(() => import("@/pages/CreateEducationPage")); -const CreateJobExperiencePage = lazy( - () => import("@/pages/CreateJobExperiencePage"), +const CreateCVPage = lazy(() => import("@/pages/cvs/CreateCVPage")); +const CreateEducationPage = lazy( + () => import("@/pages/education/CreateEducationPage"), ); -const EditEducationPage = lazy(() => import("@/pages/EditEducationPage")); -const EditJobExperiencePage = lazy( - () => import("@/pages/EditJobExperiencePage"), +const CreateJobExperiencePage = lazy( + () => import("@/pages/job-experience/CreateJobExperiencePage"), ); -const CreateVacancyPage = lazy(() => import("@/pages/CreateVacancyPage")); -const CVsPage = lazy(() => - import("@/pages/CVsPage").then((m) => ({ default: m.CVsPage })), +const EditEducationPage = lazy( + () => import("@/pages/education/EditEducationPage"), ); -const CVViewPage = lazy(() => - import("@/pages/CVViewPage").then((m) => ({ default: m.CVViewPage })), +const EditJobExperiencePage = lazy( + () => import("@/pages/job-experience/EditJobExperiencePage"), ); -const CVEditPage = lazy(() => - import("@/pages/CVEditPage").then((m) => ({ default: m.CVEditPage })), +const CreateVacancyPage = lazy( + () => import("@/pages/vacancies/CreateVacancyPage"), ); +const CVsPage = lazy(() => import("@/pages/cvs/CVsPage")); +const CVViewPage = lazy(() => import("@/pages/cvs/CVViewPage")); +const CVEditPage = lazy(() => import("@/pages/cvs/CVEditPage")); const DashboardPage = lazy(() => import("@/pages/DashboardPage")); -const EducationPage = lazy(() => import("@/pages/EducationPage")); -const JobExperiencePage = lazy(() => import("@/pages/JobExperiencePage")); +const EducationPage = lazy(() => import("@/pages/education/EducationPage")); +const JobExperiencePage = lazy( + () => import("@/pages/job-experience/JobExperiencePage"), +); const OnboardingPage = lazy(() => import("@/pages/OnboardingPage")); const OrganizationsPage = lazy(() => import("@/pages/OrganizationsPage")); -const ProfilePage = lazy(() => import("@/pages/ProfilePage")); -const ProfilesPage = lazy(() => import("@/pages/ProfilesPage")); +const ProfilePage = lazy(() => import("@/pages/profile/ProfilePage")); +const ProfilesPage = lazy(() => import("@/pages/profile/ProfilesPage")); const SettingsPage = lazy(() => import("@/pages/SettingsPage")); -const AdminPage = lazy(() => import("@/pages/AdminPage")); -const SystemStatusPage = lazy(() => import("@/pages/SystemStatusPage")); -const VacanciesPage = lazy(() => import("@/pages/VacanciesPage")); +const AdminPage = lazy(() => import("@/pages/admin/AdminPage")); +const SystemStatusPage = lazy(() => import("@/pages/admin/SystemStatusPage")); +const VacanciesPage = lazy(() => import("@/pages/vacancies/VacanciesPage")); -export default function AppRouter() { +const AppRouter = () => { const { isAuthenticated, isLoading } = useToken(); if (isLoading) { @@ -61,13 +66,25 @@ export default function AppRouter() { : + isAuthenticated ? ( + + ) : ( + }> + + + ) } /> : + isAuthenticated ? ( + + ) : ( + }> + + + ) } /> } /> @@ -267,7 +284,7 @@ export default function AppRouter() { path="applications/apply" element={ }> - + } /> @@ -304,4 +321,6 @@ export default function AppRouter() { ); -} +}; + +export default AppRouter; diff --git a/packages/ui/src/components/Page.tsx b/packages/ui/src/components/Page.tsx new file mode 100644 index 0000000..b97eef2 --- /dev/null +++ b/packages/ui/src/components/Page.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from "react"; +import { cn } from "../lib/cn"; +import { PageHeader } from "./PageHeader"; + +interface PageProps { + title: string; + description?: string; + action?: ReactNode; + className?: string; + children: ReactNode; +} + +/** + * Standard page shell: `space-y-6` container + PageHeader. Replaces the + * `
...
` pattern + * that was duplicated across most pages. + */ +export const Page = ({ + title, + description, + action, + className, + children, +}: PageProps) => ( +
+ + {children} +
+); diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 3ef771b..e728393 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -8,6 +8,7 @@ export * from "./ConfirmationModal"; export * from "./IconButton"; // Icons export * from "./icons"; +export * from "./Page"; export * from "./PageHeader"; export * from "./Placeholder"; export * from "./Progress"; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 72f3527..551a021 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -28,6 +28,7 @@ export { ToastIcon, UploadIcon, } from "./components/icons"; +export { Page } from "./components/Page"; export { PageHeader } from "./components/PageHeader"; export { Placeholder } from "./components/Placeholder"; export { -- 2.51.2