chore: heygermany customer project
This commit is contained in:
95
apps/website/views/[lang]/(main)/about/page.tsx
Normal file
95
apps/website/views/[lang]/(main)/about/page.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client'
|
||||
|
||||
import { Container, Stack, Title, Text, Grid, ThemeIcon, List, Box } from '@pikku/mantine/core'
|
||||
import { useI18n } from "@/context/i18n-provider"
|
||||
|
||||
const AboutPage: React.FunctionComponent = () => {
|
||||
const t = useI18n()
|
||||
|
||||
return (
|
||||
<Container size="md" py="xl">
|
||||
<Title order={1} size="h1" mb="xl" ta="center">{t('website.about.title')}</Title>
|
||||
|
||||
<Stack gap="xl">
|
||||
<Text size="lg" c="dimmed">
|
||||
{t('website.about.description')}
|
||||
</Text>
|
||||
|
||||
<Stack gap="md">
|
||||
<Title order={2} size="h2" mt="xl" mb="md">{t('website.about.mission.title')}</Title>
|
||||
<Text c="dimmed">
|
||||
{t('website.about.mission.description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="md">
|
||||
<Title order={2} size="h2" mt="xl" mb="md">{t('website.about.howitworks.title')}</Title>
|
||||
<Grid mt="md">
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Stack align="center" ta="center" gap="md">
|
||||
<ThemeIcon size="xl" radius="xl" variant="light" color="blue">
|
||||
<Text size="xl" fw={700}>{1}</Text>
|
||||
</ThemeIcon>
|
||||
<Title order={3} size="lg" mb="xs">{t('website.about.howitworks.step1.title')}</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('website.about.howitworks.step1.description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Stack align="center" ta="center" gap="md">
|
||||
<ThemeIcon size="xl" radius="xl" variant="light" color="blue">
|
||||
<Text size="xl" fw={700}>{2}</Text>
|
||||
</ThemeIcon>
|
||||
<Title order={3} size="lg" mb="xs">{t('website.about.howitworks.step2.title')}</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('website.about.howitworks.step2.description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Stack align="center" ta="center" gap="md">
|
||||
<ThemeIcon size="xl" radius="xl" variant="light" color="blue">
|
||||
<Text size="xl" fw={700}>{3}</Text>
|
||||
</ThemeIcon>
|
||||
<Title order={3} size="lg" mb="xs">{t('website.about.howitworks.step3.title')}</Title>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('website.about.howitworks.step3.description')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="md">
|
||||
<Title order={2} size="h2" mt="xl" mb="md">{t('website.about.whychoose.title')}</Title>
|
||||
<List spacing="xs" c="dimmed">
|
||||
<List.Item>
|
||||
{t('website.about.whychoose.fast.summary')}
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
{t('website.about.whychoose.free.summary')}
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
{t('website.about.whychoose.secure.summary')}
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
{t('website.about.whychoose.support.summary')}
|
||||
</List.Item>
|
||||
</List>
|
||||
</Stack>
|
||||
|
||||
<Box bg="blue.0" p="xl" style={{ borderRadius: 'var(--mantine-radius-lg)' }} mt="xl">
|
||||
<Title order={3} size="xl" mb="md">{t('website.about.cta.title')}</Title>
|
||||
<Text c="dimmed" mb="md">
|
||||
{t('website.about.cta.description')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default AboutPage
|
||||
100
apps/website/views/[lang]/(main)/auth/magic-link/page.tsx
Normal file
100
apps/website/views/[lang]/(main)/auth/magic-link/page.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
|
||||
import { Container, Stack, Title, Text, Button, Loader, Center } from '@pikku/mantine/core'
|
||||
import { useI18n } from "@/context/i18n-provider"
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSearchParams, useRouter, useParams } from '@/framework/navigation'
|
||||
import { verifyMagicLink } from '@/lib/auth'
|
||||
import { asI18n } from '@pikku/react'
|
||||
|
||||
const MagicLinkPage: React.FunctionComponent = () => {
|
||||
const t = useI18n()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { lang } = useParams<{ lang: string }>()
|
||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading')
|
||||
const [errorMessage, setErrorMessage] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get('token')
|
||||
const redirect = searchParams.get('redirect') || '/jobs/application'
|
||||
|
||||
if (!token) {
|
||||
setStatus('error')
|
||||
setErrorMessage(t('magiclink.error.notoken'))
|
||||
return
|
||||
}
|
||||
|
||||
const completeMagicLinkSignIn = async () => {
|
||||
try {
|
||||
await verifyMagicLink(token)
|
||||
setStatus('success')
|
||||
setTimeout(() => {
|
||||
router.push(redirect)
|
||||
}, 1000)
|
||||
} catch (error: any) {
|
||||
setStatus('error')
|
||||
setErrorMessage(error.message || t('magiclink.error.verificationfailed'))
|
||||
}
|
||||
}
|
||||
|
||||
completeMagicLinkSignIn()
|
||||
}, [searchParams, router, t])
|
||||
|
||||
return (
|
||||
<Container size="sm" py="xl" my='auto'>
|
||||
<Stack gap="xl" align="center">
|
||||
{status === 'loading' && (
|
||||
<>
|
||||
<Center>
|
||||
<Loader size="xl" />
|
||||
</Center>
|
||||
<Title order={1} ta="center">
|
||||
{t('magiclink.loading.title')}
|
||||
</Title>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<Stack gap="md" align="center">
|
||||
<Title order={1} ta="center">
|
||||
{t('magiclink.success.title')}
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
{t('magiclink.success.message')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<>
|
||||
<Stack gap="md" align="center">
|
||||
<Title order={1} ta="center">
|
||||
{t('magiclink.error.title')}
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
{t('magiclink.error.message')}
|
||||
</Text>
|
||||
{errorMessage && (
|
||||
<Text size="sm" c="red" ta="center">
|
||||
{asI18n(errorMessage)}
|
||||
</Text>
|
||||
)}
|
||||
<Button
|
||||
size="lg"
|
||||
mt="md"
|
||||
onClick={() => router.push(`/${lang}`)}
|
||||
>
|
||||
{t('magiclink.error.button')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default MagicLinkPage
|
||||
220
apps/website/views/[lang]/(main)/jobs/application/page.tsx
Normal file
220
apps/website/views/[lang]/(main)/jobs/application/page.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
'use client'
|
||||
|
||||
import React, { startTransition, useState } from 'react'
|
||||
import { FormProvider } from 'react-hook-form'
|
||||
import { Progress, Card, Button, Stepper, Box, Flex, Text, Paper, Container, Portal } from "@pikku/mantine/core"
|
||||
import { BasicInfo } from "@/components/jobs/form/BasicInfo"
|
||||
import { TrainingCertificates } from "@/components/jobs/form/TrainingCertificates"
|
||||
import { License } from "@/components/jobs/form/License"
|
||||
import { GermanLanguage } from "@/components/jobs/form/GermanLanguage"
|
||||
import { ProfessionalExperience } from "@/components/jobs/form/ProfessionalExperience"
|
||||
import { ApplicationCandidateData } from '@heygermany/sdk'
|
||||
import { useCandidateForm } from '@/hooks/candidate/useCandidateForm'
|
||||
import { useGetCandidate } from '@/hooks/candidate/useGetCandidate'
|
||||
import { pikku } from '@/pikku/http'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { Done } from '@/components/jobs/form/Done'
|
||||
import { Invalid } from '@/components/jobs/form/Invalid'
|
||||
import { useI18n } from '@/context/i18n-provider'
|
||||
import { ApplicantStatuses } from '@heygermany/sdk'
|
||||
import { CandidateResult } from '@/components/jobs/CandidateResult'
|
||||
import { useGetCandidateResults } from '@/hooks/useGetCandidateResult'
|
||||
import { IconCheck } from '@tabler/icons-react'
|
||||
import { HelpAffix } from '@/components/jobs/form/HelpAffix'
|
||||
import { useSearchParams } from '@/framework/navigation'
|
||||
import { asI18n } from '@pikku/react'
|
||||
|
||||
const Form: React.FunctionComponent<{ candidate: ApplicationCandidateData }> = ({ candidate }) => {
|
||||
const t = useI18n()
|
||||
|
||||
const steps = [
|
||||
{ title: t('jobs.apply.steps.basicinfo'), component: BasicInfo },
|
||||
{ title: t('jobs.apply.steps.educationdocuments' as any), component: TrainingCertificates },
|
||||
{ title: t('jobs.apply.steps.license'), component: License },
|
||||
{ title: t('jobs.apply.steps.germanlanguage'), component: GermanLanguage },
|
||||
{ title: t('jobs.apply.steps.professionalexperience'), component: ProfessionalExperience },
|
||||
]
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
|
||||
// Single form instance for the entire application
|
||||
const form = useCandidateForm(candidate)
|
||||
|
||||
const progressPercentage = (currentStep / steps.length) * 100
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
// Return a Date (not an ISO string): the pikku fetch client serializes Date
|
||||
// values over the wire, and the /candidate input type expects Date fields.
|
||||
const serializeDate = (value: Date | string | null | undefined) => {
|
||||
if (!value) return null
|
||||
const date = value instanceof Date ? value : new Date(value)
|
||||
return Number.isNaN(date.getTime()) ? null : date
|
||||
}
|
||||
|
||||
const handleNext = async () => {
|
||||
// Validate current form state before proceeding
|
||||
const isValid = await form.trigger()
|
||||
|
||||
if (isValid) {
|
||||
// Perform partial update - save current form data
|
||||
const { education, licenses, languageCertificates, workExperience, ...formData } = form.getValues()
|
||||
const currentData = {
|
||||
...formData,
|
||||
candidateId: candidate.candidateId,
|
||||
dateOfBirth: serializeDate(formData.dateOfBirth),
|
||||
}
|
||||
|
||||
if (currentStep < steps.length - 1) {
|
||||
startTransition(() => {
|
||||
setCurrentStep((step) => step + 1)
|
||||
})
|
||||
void pikku().patch('/candidate/:candidateId', currentData).catch((error: unknown) => {
|
||||
console.error('Error saving application data:', error)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Final submission
|
||||
;(window as any).fbq?.('track', 'Lead', {
|
||||
content_name: 'HeyGermany application',
|
||||
content_category: 'Application'
|
||||
})
|
||||
await pikku().patch('/candidate/:candidateId', {
|
||||
...currentData,
|
||||
submittedAt: new Date(),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error saving application data:', error)
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['candidate'] })
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep > 0) {
|
||||
setCurrentStep(currentStep - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const renderStepContent = () => {
|
||||
const currentStepConfig = steps[currentStep]
|
||||
if (!currentStepConfig) return null
|
||||
const StepComponent = currentStepConfig.component
|
||||
return <StepComponent candidate={candidate} />
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
{/* Mobile Progress Bar */}
|
||||
<Box px="md" py="xs" w="100%" bg="white" pos="sticky" top="4rem" display={{ base: 'block', lg: 'none' }} style={{ borderBottom: '1px solid var(--mantine-color-gray-3)' }}>
|
||||
<Flex align="center" gap="xs" mb="xs">
|
||||
<Text size="sm" fw={500}>{t('jobs.apply.stepprogress', { current: currentStep + 1, total: steps.length })}</Text>
|
||||
</Flex>
|
||||
<Progress value={progressPercentage} h="0.5rem" />
|
||||
</Box>
|
||||
|
||||
<Container size='xl' display={{ base: 'none', lg: 'block' }}>
|
||||
{/* Desktop Stepper */}
|
||||
<Box my="xl">
|
||||
<Stepper
|
||||
active={currentStep}
|
||||
onStepClick={setCurrentStep}
|
||||
allowNextStepsSelect={false}
|
||||
|
||||
wrap={false}
|
||||
>
|
||||
{steps.map((step, index) => <Stepper.Step key={index} label={step.title} completedIcon={<IconCheck color='#eab308' size={18} />} />)}
|
||||
</Stepper>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
<Container size='lg' maw='4xl' w='100%'>
|
||||
<Flex w="100%" direction="column" align="center" justify="center" p="lg">
|
||||
{/* Render the current step once to avoid duplicate form controls across breakpoints */}
|
||||
<Paper w="100%" shadow='xl' withBorder p={{ base: 'md', md: 'xl' }}>
|
||||
{renderStepContent()}
|
||||
</Paper>
|
||||
|
||||
{/* Navigation Buttons */}
|
||||
<Flex justify="center" gap="md" mt="lg">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleBack}
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
{t('jobs.apply.buttons.back')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleNext}
|
||||
disabled={form.formState.isSubmitting}
|
||||
>
|
||||
{currentStep === steps.length - 1 ? t('jobs.apply.buttons.submit') : t('jobs.apply.buttons.next')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Container>
|
||||
|
||||
<HelpAffix />
|
||||
</FormProvider >
|
||||
)
|
||||
}
|
||||
|
||||
const Result: React.FunctionComponent<{ candidateId: string }> = ({ candidateId }) => {
|
||||
const { data: candidateResults, isLoading: resultLoading, error } = useGetCandidateResults(candidateId)
|
||||
|
||||
if (resultLoading) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Container size="lg" py="xl">
|
||||
<Card shadow="sm" p="lg" radius="md" withBorder>
|
||||
<Text size="xl" fw={700} mb="md" c="red">
|
||||
{asI18n('Error Loading Qualification Results')}
|
||||
</Text>
|
||||
<Text size="md" c="dimmed">
|
||||
{asI18n('We encountered an error while checking your qualifications. Please try again later or contact support if the issue persists.')}
|
||||
</Text>
|
||||
</Card>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
if (!candidateResults) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <CandidateResult candidateResults={candidateResults} />
|
||||
}
|
||||
|
||||
const ApplicationPage: React.FunctionComponent = () => {
|
||||
const searchParams = useSearchParams()
|
||||
const candidateIdFromParams = searchParams.get('candidateId')
|
||||
const { data: candidate, isLoading: candidateLoading } = useGetCandidate(candidateIdFromParams || undefined)
|
||||
|
||||
if (candidateLoading || !candidate) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (candidate.status === ApplicantStatuses.PENDING) {
|
||||
return <Done name={candidate.name} />
|
||||
}
|
||||
|
||||
if (candidate.status === ApplicantStatuses.INVALID) {
|
||||
return <Invalid name={candidate.name} candidateId={candidate.candidateId} />
|
||||
}
|
||||
|
||||
if (candidate.status === ApplicantStatuses.COMPLETE) {
|
||||
return <Result candidateId={candidate.candidateId} />
|
||||
}
|
||||
|
||||
return <div style={{ position: 'relative' }}>
|
||||
<Form candidate={candidate} />
|
||||
</div>
|
||||
}
|
||||
|
||||
export default ApplicationPage
|
||||
72
apps/website/views/[lang]/(main)/jobs/apply/page.tsx
Normal file
72
apps/website/views/[lang]/(main)/jobs/apply/page.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client'
|
||||
|
||||
import { StartApplication } from '@/components/jobs/form/StartApplication'
|
||||
import { useI18n } from '@/context/i18n-provider'
|
||||
import { Container, Box, Stack, Title, Text, List, Grid } from "@pikku/mantine/core"
|
||||
|
||||
const ApplyPage: React.FunctionComponent = () => {
|
||||
const t = useI18n()
|
||||
|
||||
return (
|
||||
<Box component="section" py="xl" pos="relative" flex={1}>
|
||||
<Container size="xl" pt="xl">
|
||||
<Grid gutter="xl" align="stretch">
|
||||
|
||||
{/* Who is This For Section */}
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="xl">
|
||||
<Box>
|
||||
<Title order={2} mb="md">{t('jobs.apply.whoisthisfor.title')}</Title>
|
||||
<Text size="sm">
|
||||
{t('jobs.apply.whoisthisfor.description')}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Title order={2} mb="md">{t('jobs.apply.whatyoullget.title')}</Title>
|
||||
<List size="sm" spacing="xs">
|
||||
<List.Item>{t('jobs.apply.whatyoullget.item1')}</List.Item>
|
||||
<List.Item>{t('jobs.apply.whatyoullget.item2')}</List.Item>
|
||||
<List.Item>{t('jobs.apply.whatyoullget.item3')}</List.Item>
|
||||
</List>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Title order={2} mb="md">{t('jobs.apply.whatyoullneed.title')}</Title>
|
||||
<List size="sm" spacing="xs">
|
||||
<List.Item>{t('jobs.apply.whatyoullneed.item1')}</List.Item>
|
||||
<List.Item>{t('jobs.apply.whatyoullneed.item2')}</List.Item>
|
||||
<List.Item>
|
||||
{t('jobs.apply.whatyoullneed.item3.title')}
|
||||
<br />
|
||||
{t('jobs.apply.whatyoullneed.item3.note')}
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
{t('jobs.apply.whatyoullneed.item4.title')}
|
||||
<br />
|
||||
{t('jobs.apply.whatyoullneed.item4.note')}
|
||||
</List.Item>
|
||||
</List>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
{/* Get Your Results Section */}
|
||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||
<Stack gap="lg">
|
||||
<Box>
|
||||
<Title order={2} mb="md">{t('jobs.apply.getyourresults.title')}</Title>
|
||||
<Text size="sm" mt="md">
|
||||
{t('jobs.apply.getyourresults.description')}
|
||||
</Text>
|
||||
</Box>
|
||||
<StartApplication initializing={false} />
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Container>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ApplyPage
|
||||
@@ -0,0 +1,5 @@
|
||||
import { DemoCandidateResultPage } from '@/components/jobs/DemoCandidateResultPage'
|
||||
|
||||
export default function DemoResultPage() {
|
||||
return <DemoCandidateResultPage />
|
||||
}
|
||||
19
apps/website/views/[lang]/(main)/layout.tsx
Normal file
19
apps/website/views/[lang]/(main)/layout.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import TopBar from '@/components/ui/TopBar'
|
||||
import FooterBar from '@/components/ui/FooterBar'
|
||||
import { Flex } from '@pikku/mantine/core'
|
||||
|
||||
interface MainLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function MainLayout({ children }: MainLayoutProps) {
|
||||
return (
|
||||
<Flex direction="column" mih="100vh">
|
||||
<TopBar />
|
||||
<Flex component="main" direction="column" flex="1">
|
||||
{children}
|
||||
</Flex>
|
||||
<FooterBar />
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
5
apps/website/views/[lang]/(main)/page.tsx
Normal file
5
apps/website/views/[lang]/(main)/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { HomePage } from '@/components/home/HomePage'
|
||||
|
||||
export default function LocalizedHomePage() {
|
||||
return <HomePage />
|
||||
}
|
||||
71
apps/website/views/[lang]/(main)/verify-email/page.tsx
Normal file
71
apps/website/views/[lang]/(main)/verify-email/page.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client'
|
||||
|
||||
import { Container, Stack, Title, Text, Button } from '@pikku/mantine/core'
|
||||
import { useI18n } from "@/context/i18n-provider"
|
||||
import { useMemo } from 'react'
|
||||
import { useSearchParams, useRouter, useParams } from '@/framework/navigation'
|
||||
|
||||
const VerifyEmailPage: React.FunctionComponent = () => {
|
||||
const t = useI18n()
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
const { lang } = useParams<{ lang: string }>()
|
||||
const status = useMemo<'success' | 'error'>(() => {
|
||||
const error = searchParams.get('error')
|
||||
const verified = searchParams.get('verified')
|
||||
|
||||
if (error || verified !== '1') {
|
||||
return 'error'
|
||||
}
|
||||
|
||||
return 'success'
|
||||
}, [searchParams])
|
||||
|
||||
return (
|
||||
<Container size="sm" py="xl" my='auto'>
|
||||
<Stack gap="xl" align="center">
|
||||
{status === 'success' && (
|
||||
<>
|
||||
<Stack gap="md" align="center">
|
||||
<Title order={1} ta="center">
|
||||
{t('verifyemail.success.title')}
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
{t('verifyemail.success.message')}
|
||||
</Text>
|
||||
<Button
|
||||
size="lg"
|
||||
mt="md"
|
||||
onClick={() => router.push(`/${lang}`)}
|
||||
>
|
||||
{t('verifyemail.success.button')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<>
|
||||
<Stack gap="md" align="center">
|
||||
<Title order={1} ta="center">
|
||||
{t('verifyemail.error.title')}
|
||||
</Title>
|
||||
<Text c="dimmed" ta="center">
|
||||
{t('verifyemail.error.message')}
|
||||
</Text>
|
||||
<Button
|
||||
size="lg"
|
||||
mt="md"
|
||||
onClick={() => router.push(`/${lang}`)}
|
||||
>
|
||||
{t('verifyemail.error.button')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default VerifyEmailPage
|
||||
Reference in New Issue
Block a user