update the registeration

This commit is contained in:
mengstabketemaw
2026-06-16 17:02:36 +03:00
parent e674565845
commit 56bf28b583

View File

@@ -86,19 +86,18 @@ const DOC_SLOTS: DocSlot[] = [
// ---------------------------------------------------------------------------
function StepIndicator({ active, completed }: { active: number; completed: number[] }) {
return (
<Box mb="lg">
<Box mb={32}>
<Group gap={0} align="center" wrap="nowrap">
{STEPS.map((step, i) => {
const isDone = completed.includes(i);
const isCurrent = active === i;
return (
<Group key={i} gap={0} align="center" style={{ flex: i < STEPS.length - 1 ? 1 : 'none' }}>
{/* Circle */}
<Stack gap={4} align="center" style={{ minWidth: rem(40) }}>
<Box
style={{
width: rem(36),
height: rem(36),
width: rem(40),
height: rem(40),
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
@@ -107,15 +106,17 @@ function StepIndicator({ active, completed }: { active: number; completed: numbe
? 'var(--mantine-color-blue-8)'
: isCurrent
? 'var(--mantine-color-blue-7)'
: 'var(--mantine-color-gray-2)',
border: isCurrent ? '2.5px solid var(--mantine-color-blue-5)' : 'none',
: 'var(--mantine-color-gray-1)',
border: isCurrent ? '2.5px solid var(--mantine-color-blue-5)' : '2px solid transparent',
boxShadow: isCurrent || isDone ? '0 2px 8px rgba(34, 139, 230, 0.2)' : 'none',
flexShrink: 0,
transition: 'all 0.2s ease',
}}
>
{isDone ? (
<IconCheck size={18} color="white" stroke={2.5} />
) : (
<Text fw={700} fz="sm" c={isCurrent ? 'white' : 'dimmed'}>
<Text fw={700} fz="sm" c={isCurrent ? 'white' : 'gray.5'}>
{i + 1}
</Text>
)}
@@ -123,14 +124,13 @@ function StepIndicator({ active, completed }: { active: number; completed: numbe
<Text
fz="xs"
fw={isCurrent ? 700 : 400}
c={isCurrent ? 'blue.7' : isDone ? 'dimmed' : 'dimmed'}
c={isCurrent ? 'blue.7' : 'dimmed'}
style={{ whiteSpace: 'nowrap' }}
>
{isDone ? `${step.label}` : step.label}
</Text>
</Stack>
{/* Connector line */}
{i < STEPS.length - 1 && (
<Box
style={{
@@ -138,8 +138,8 @@ function StepIndicator({ active, completed }: { active: number; completed: numbe
height: rem(2),
backgroundColor: isDone
? 'var(--mantine-color-blue-8)'
: 'var(--mantine-color-gray-3)',
marginBottom: rem(20),
: 'var(--mantine-color-gray-2)',
marginBottom: rem(22),
}}
/>
)}
@@ -157,8 +157,8 @@ function StepIndicator({ active, completed }: { active: number; completed: numbe
function SectionHead({ title }: { title: string }) {
return (
<>
<Text fw={600} fz="sm" mt={4} mb={2}>{title}</Text>
<Divider mb={6} />
<Divider mt="md" mb="xs" />
<Text fw={600} fz="sm" tt="uppercase" c="gray.6">{title}</Text>
</>
);
}
@@ -242,7 +242,7 @@ function DocCard({
<FileButton resetRef={resetRef} onChange={onFile} accept="application/pdf,image/jpeg,image/png">
{(props) => (
<Button size="xs" variant="default" {...props}>
📂 Choose File
Choose File
</Button>
)}
</FileButton>
@@ -327,220 +327,223 @@ export function SeafarerRegistrationPage() {
const StepIcon = stepIcons[active];
return (
<Stack gap="sm">
{/* Page header */}
<div>
<Title order={3}>New Seafarer Registration</Title>
<Text fz="sm" c="dimmed">Register a new seafarer profile Step {active + 1} of {STEPS.length}</Text>
</div>
<Stack gap="md">
{/* Page header */}
<div>
<Title order={3}>New Seafarer Registration</Title>
<Text fz="sm" c="dimmed">Register a new seafarer profile Step {active + 1} of {STEPS.length}</Text>
</div>
{/* Step indicator */}
<StepIndicator active={active} completed={completed} />
{/* Step indicator */}
<StepIndicator active={active} completed={completed} />
{/* Card */}
<Paper withBorder radius="lg" p="lg">
{/* Card header */}
<Group justify="space-between" mb="md">
<Group gap="xs">
<StepIcon size={20} stroke={1.6} />
<Text fw={700} fz="lg">{stepLabel}</Text>
{/* Card */}
<Paper withBorder radius="lg" p="xl">
{/* Card header */}
<Group justify="space-between" mb="lg">
<Group gap="xs">
<StepIcon size={20} stroke={1.6} />
<Text fw={700} fz="lg">{stepLabel}</Text>
</Group>
<Badge variant="light" color="blue" radius="md">Step {active + 1} of {STEPS.length}</Badge>
</Group>
<Badge variant="light" color="blue" radius="md">Step {active + 1} of {STEPS.length}</Badge>
</Group>
{/* ── Step 1: Personal Information ───────────────────────────── */}
{active === 0 && (
<Stack gap={8}>
<SectionHead title="Identity Details" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={8}>
<TextInput label="First Name" placeholder="firstname" required value={firstName} onChange={(e) => setFirstName(e.currentTarget.value)} />
<TextInput label="Middle Name" placeholder="Middle name" value={middleName} onChange={(e) => setMiddleName(e.currentTarget.value)} />
<TextInput label="Last Name" placeholder="Last name" required value={lastName} onChange={(e) => setLastName(e.currentTarget.value)} />
<Select label="Gender" placeholder="Select" required data={GENDERS} value={gender} onChange={setGender} />
<TextInput label="Date of Birth" type="date" required value={dob} onChange={(e) => setDob(e.currentTarget.value)} />
<TextInput label="Place of Birth" placeholder="City, Region" required value={placeOfBirth} onChange={(e) => setPlaceOfBirth(e.currentTarget.value)} />
<Select label="Nationality" required data={NATIONALITIES} value={nationality} onChange={setNationality} searchable />
<Select label="Marital Status" placeholder="Select" data={MARITAL_STATUSES} value={maritalStatus} onChange={setMaritalStatus} />
</SimpleGrid>
{/* ── Step 1: Personal Information ───────────────────────────── */}
{active === 0 && (
<Stack gap="md">
<SectionHead title="Identity Details" />
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<TextInput label="First Name" placeholder="First name" required value={firstName} onChange={(e) => setFirstName(e.currentTarget.value)} />
<TextInput label="Middle Name" placeholder="Middle name" value={middleName} onChange={(e) => setMiddleName(e.currentTarget.value)} />
<TextInput label="Last Name" placeholder="Last name" required value={lastName} onChange={(e) => setLastName(e.currentTarget.value)} />
<Select label="Gender" placeholder="Select" required data={GENDERS} value={gender} onChange={setGender} />
<TextInput label="Date of Birth" type="date" required value={dob} onChange={(e) => setDob(e.currentTarget.value)} />
<TextInput label="Place of Birth" placeholder="City, Region" required value={placeOfBirth} onChange={(e) => setPlaceOfBirth(e.currentTarget.value)} />
<Select label="Nationality" required data={NATIONALITIES} value={nationality} onChange={setNationality} searchable />
<Select label="Marital Status" placeholder="Select" data={MARITAL_STATUSES} value={maritalStatus} onChange={setMaritalStatus} />
</SimpleGrid>
<SectionHead title="Identity Documents" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={8}>
<TextInput label="National ID Number" placeholder="ET-000000" required value={nationalIdNumber} onChange={(e) => setNationalIdNumber(e.currentTarget.value)} />
<TextInput label="Passport Number" placeholder="EP000000" value={passportNumber} onChange={(e) => setPassportNumber(e.currentTarget.value)} />
</SimpleGrid>
<TextInput label="Passport Expiry Date" type="date" value={passportExpiry} onChange={(e) => setPassportExpiry(e.currentTarget.value)} style={{ maxWidth: rem(360) }} />
<SectionHead title="Identity Documents" />
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<TextInput label="National ID Number" placeholder="ET-000000" required value={nationalIdNumber} onChange={(e) => setNationalIdNumber(e.currentTarget.value)} />
<TextInput label="Passport Number" placeholder="EP000000" value={passportNumber} onChange={(e) => setPassportNumber(e.currentTarget.value)} />
<TextInput label="Passport Expiry Date" type="date" value={passportExpiry} onChange={(e) => setPassportExpiry(e.currentTarget.value)} />
</SimpleGrid>
<Alert variant="light" color="blue" icon={<IconInfoCircle size={17} />}>
A unique Seafarer ID will be automatically generated upon approval of this registration.
</Alert>
</Stack>
)}
<Alert variant="light" color="blue" icon={<IconInfoCircle size={17} />}>
A unique Seafarer ID will be automatically generated upon approval of this registration.
</Alert>
</Stack>
)}
{/* ── Step 2: Contact Details ─────────────────────────────────── */}
{active === 1 && (
<Stack gap={8}>
<SectionHead title="Contact Information" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={8}>
<TextInput label="Mobile Number" placeholder="+251 9XX XXX XXX" required value={mobile} onChange={(e) => setMobile(e.currentTarget.value)} />
<TextInput label="Email Address" placeholder="email@example.com" type="email" required value={email} onChange={(e) => setEmail(e.currentTarget.value)} />
<Select label="Region" placeholder="Select region" required data={REGIONS} value={region} onChange={setRegion} searchable />
<TextInput label="City" placeholder="City" required value={city} onChange={(e) => setCity(e.currentTarget.value)} />
</SimpleGrid>
<Textarea label="Permanent Address" placeholder="Full permanent address" autosize minRows={2} value={permanentAddress} onChange={(e) => setPermanentAddress(e.currentTarget.value)} />
<Textarea
label={<><Text span fz="sm" fw={500}>Current Address</Text><Text span fz="xs" c="dimmed" ml={6}>(If different from permanent)</Text></>}
placeholder="Full current address"
autosize
minRows={2}
value={currentAddress}
onChange={(e) => setCurrentAddress(e.currentTarget.value)}
/>
{/* ── Step 2: Contact Details ─────────────────────────────────── */}
{active === 1 && (
<Stack gap="md">
<SectionHead title="Contact Information" />
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<TextInput label="Mobile Number" placeholder="+251 9XX XXX XXX" required value={mobile} onChange={(e) => setMobile(e.currentTarget.value)} />
<TextInput label="Email Address" placeholder="email@example.com" type="email" required value={email} onChange={(e) => setEmail(e.currentTarget.value)} />
<Select label="Region" placeholder="Select region" required data={REGIONS} value={region} onChange={setRegion} searchable />
<TextInput label="City" placeholder="City" required value={city} onChange={(e) => setCity(e.currentTarget.value)} />
</SimpleGrid>
<Textarea label="Permanent Address" placeholder="Full permanent address" autosize minRows={2} value={permanentAddress} onChange={(e) => setPermanentAddress(e.currentTarget.value)} />
<Textarea
label={<><Text span fz="sm" fw={500}>Current Address</Text><Text span fz="xs" c="dimmed" ml={6}>(If different from permanent)</Text></>}
placeholder="Full current address"
autosize
minRows={2}
value={currentAddress}
onChange={(e) => setCurrentAddress(e.currentTarget.value)}
/>
<SectionHead title="Emergency Contact" />
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing={8}>
<TextInput label="Contact Name" placeholder="Full name" value={emergencyName} onChange={(e) => setEmergencyName(e.currentTarget.value)} />
<Select label="Relationship" placeholder="Select" data={RELATIONSHIPS} value={emergencyRel} onChange={setEmergencyRel} />
</SimpleGrid>
<TextInput label="Phone Number" placeholder="+251 9XX XXX XXX" value={emergencyPhone} onChange={(e) => setEmergencyPhone(e.currentTarget.value)} style={{ maxWidth: rem(360) }} />
</Stack>
)}
<SectionHead title="Emergency Contact" />
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<TextInput label="Contact Name" placeholder="Full name" value={emergencyName} onChange={(e) => setEmergencyName(e.currentTarget.value)} />
<Select label="Relationship" placeholder="Select" data={RELATIONSHIPS} value={emergencyRel} onChange={setEmergencyRel} />
<TextInput label="Phone Number" placeholder="+251 9XX XXX XXX" value={emergencyPhone} onChange={(e) => setEmergencyPhone(e.currentTarget.value)} />
</SimpleGrid>
</Stack>
)}
{/* ── Step 3: Documents Upload ────────────────────────────────── */}
{active === 2 && (
<Stack gap="sm">
<Alert variant="light" color="yellow" icon={<IconAlertTriangle size={17} />}>
Please upload clear, readable copies of all required documents. Accepted formats: PDF, JPG, PNG (max 5MB each). Items marked with * are mandatory.
</Alert>
{/* ── Step 3: Documents Upload ────────────────────────────────── */}
{active === 2 && (
<Stack gap="md">
<Alert variant="light" color="yellow" icon={<IconAlertTriangle size={17} />}>
Please upload clear, readable copies of all required documents. Accepted formats: PDF, JPG, PNG (max 5MB each). Items marked with * are mandatory.
</Alert>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
{DOC_SLOTS.map((slot) => (
<DocCard
key={slot.key}
slot={slot}
file={files[slot.key]}
onFile={setFile(slot.key)}
/>
))}
</SimpleGrid>
<Paper withBorder radius="md" p="sm">
<Text fw={600} fz="sm" mb={6}>Upload Progress</Text>
<Group gap="lg">
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
{DOC_SLOTS.map((slot) => (
<Group key={slot.key} gap={6} align="center">
{files[slot.key] ? (
<IconCircleCheck size={15} color="var(--mantine-color-teal-6)" />
) : (
<Box style={{ width: 14, height: 14, borderRadius: '50%', border: '1.5px solid var(--mantine-color-gray-4)' }} />
)}
<Text fz="xs" c={files[slot.key] ? 'teal.7' : 'dimmed'}>
{slot.key === 'nationalId' ? 'National ID' : slot.key === 'passport' ? 'Passport' : slot.key === 'graduation' ? 'Certificate' : 'Photo'}
</Text>
</Group>
<DocCard
key={slot.key}
slot={slot}
file={files[slot.key]}
onFile={setFile(slot.key)}
/>
))}
</Group>
</Paper>
</Stack>
)}
{/* ── Step 4: Review & Submit ─────────────────────────────────── */}
{active === 3 && (
<Stack gap="sm">
<Paper withBorder radius="md" p="sm">
<Text fw={700} fz="sm" mb="xs">Personal Information</Text>
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xs">
<ReviewRow label="First Name" value={firstName} />
<ReviewRow label="Middle Name" value={middleName} />
<ReviewRow label="Last Name" value={lastName} />
<ReviewRow label="Gender" value={gender ?? ''} />
<ReviewRow label="Date of Birth" value={dob} />
<ReviewRow label="Place of Birth" value={placeOfBirth} />
<ReviewRow label="Nationality" value={nationality ?? ''} />
<ReviewRow label="Marital Status" value={maritalStatus ?? ''} />
<ReviewRow label="National ID No." value={nationalIdNumber} />
<ReviewRow label="Passport No." value={passportNumber} />
<ReviewRow label="Passport Expiry" value={passportExpiry} />
</SimpleGrid>
</Paper>
<Paper withBorder radius="md" p="sm">
<Text fw={700} fz="sm" mb="xs">Contact Details</Text>
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xs">
<ReviewRow label="Mobile" value={mobile} />
<ReviewRow label="Email" value={email} />
<ReviewRow label="Region" value={region ?? ''} />
<ReviewRow label="City" value={city} />
<ReviewRow label="Permanent Address" value={permanentAddress} />
<ReviewRow label="Current Address" value={currentAddress} />
</SimpleGrid>
{emergencyName && (
<>
<Text fw={600} fz="sm" mt="sm" mb={4}>Emergency Contact</Text>
<SimpleGrid cols={{ base: 1, sm: 3 }} spacing="xs">
<ReviewRow label="Name" value={emergencyName} />
<ReviewRow label="Relationship" value={emergencyRel ?? ''} />
<ReviewRow label="Phone" value={emergencyPhone} />
</SimpleGrid>
</>
<Paper withBorder radius="md" p="md" bg="gray.0">
<Text fw={600} fz="sm" mb="sm">Upload Progress</Text>
<SimpleGrid cols={{ base: 2, sm: 4 }} spacing="sm">
{DOC_SLOTS.map((slot) => (
<Group key={slot.key} gap={6} align="center">
{files[slot.key] ? (
<IconCircleCheck size={16} color="var(--mantine-color-teal-6)" />
) : (
<Box style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid var(--mantine-color-gray-3)' }} />
)}
<Text fz="xs" c={files[slot.key] ? 'teal.7' : 'dimmed'} fw={files[slot.key] ? 600 : 400}>
{slot.key === 'nationalId' ? 'National ID' : slot.key === 'passport' ? 'Passport' : slot.key === 'graduation' ? 'Certificate' : 'Photo'}
</Text>
</Group>
))}
</SimpleGrid>
</Paper>
</Stack>
)}
{/* ── Step 4: Review & Submit ─────────────────────────────────── */}
{active === 3 && (
<Stack gap="md">
<Paper withBorder radius="md" p="md">
<Text fw={700} fz="sm" mb="sm">Personal Information</Text>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<ReviewRow label="First Name" value={firstName} />
<ReviewRow label="Middle Name" value={middleName} />
<ReviewRow label="Last Name" value={lastName} />
<ReviewRow label="Gender" value={gender ?? ''} />
<ReviewRow label="Date of Birth" value={dob} />
<ReviewRow label="Place of Birth" value={placeOfBirth} />
<ReviewRow label="Nationality" value={nationality ?? ''} />
<ReviewRow label="Marital Status" value={maritalStatus ?? ''} />
<ReviewRow label="National ID No." value={nationalIdNumber} />
<ReviewRow label="Passport No." value={passportNumber} />
<ReviewRow label="Passport Expiry" value={passportExpiry} />
</SimpleGrid>
</Paper>
<Paper withBorder radius="md" p="md">
<Text fw={700} fz="sm" mb="sm">Contact Details</Text>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<ReviewRow label="Mobile" value={mobile} />
<ReviewRow label="Email" value={email} />
<ReviewRow label="Region" value={region ?? ''} />
<ReviewRow label="City" value={city} />
<ReviewRow label="Permanent Address" value={permanentAddress} />
<ReviewRow label="Current Address" value={currentAddress} />
</SimpleGrid>
{emergencyName && (
<>
<Divider mt="md" mb="sm" />
<Text fw={600} fz="sm" mb="sm">Emergency Contact</Text>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md">
<ReviewRow label="Name" value={emergencyName} />
<ReviewRow label="Relationship" value={emergencyRel ?? ''} />
<ReviewRow label="Phone" value={emergencyPhone} />
</SimpleGrid>
</>
)}
</Paper>
<Paper withBorder radius="md" p="md">
<Text fw={700} fz="sm" mb="sm">Documents</Text>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="md">
{DOC_SLOTS.map((slot) => (
<Group key={slot.key} gap="xs" align="center">
{files[slot.key] ? (
<IconCircleCheck size={18} color="var(--mantine-color-teal-6)" />
) : (
<Box style={{ width: 18, height: 18, borderRadius: '50%', border: '2px solid var(--mantine-color-gray-3)', flexShrink: 0 }} />
)}
<div>
<Text fz="sm" c={files[slot.key] ? undefined : 'dimmed'}>
{slot.label}
{slot.required && !files[slot.key] && <Text span c="red" ml={3}>*</Text>}
</Text>
{files[slot.key] && (
<Text fz="xs" c="dimmed" truncate maw={160}>{files[slot.key]!.name}</Text>
)}
</div>
</Group>
))}
</SimpleGrid>
</Paper>
</Stack>
)}
{/* Navigation buttons */}
<Group justify="space-between" mt="xl">
<Button variant="default" onClick={() => navigate('/applications')}>
Cancel
</Button>
<Group gap="sm">
{active > 0 && (
<Button variant="default" leftSection={<IconArrowLeft size={16} />} onClick={prev}>
Previous
</Button>
)}
</Paper>
<Paper withBorder radius="md" p="sm">
<Text fw={700} fz="sm" mb="xs">Documents</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="xs">
{DOC_SLOTS.map((slot) => (
<Group key={slot.key} gap="xs" align="center">
{files[slot.key] ? (
<IconCircleCheck size={16} color="var(--mantine-color-teal-6)" />
) : (
<Box style={{ width: 16, height: 16, borderRadius: '50%', border: '1.5px solid var(--mantine-color-gray-4)', flexShrink: 0 }} />
)}
<Text fz="sm" c={files[slot.key] ? undefined : 'dimmed'}>
{slot.label}
{slot.required && !files[slot.key] && <Text span c="red" ml={3}>*</Text>}
</Text>
{files[slot.key] && (
<Text fz="xs" c="dimmed" truncate style={{ flex: 1 }}>({files[slot.key]!.name})</Text>
)}
</Group>
))}
</SimpleGrid>
</Paper>
</Stack>
)}
{/* Navigation buttons */}
<Group justify="space-between" mt="md">
<Button variant="default" onClick={() => navigate('/applications')}>
Cancel
</Button>
<Group gap="sm">
{active > 0 && (
<Button variant="default" leftSection={<IconArrowLeft size={16} />} onClick={prev}>
Previous
</Button>
)}
{active < STEPS.length - 1 ? (
<Button
rightSection={<IconArrowRight size={16} />}
onClick={next}
disabled={!canNext()}
>
Next Step
</Button>
) : (
<Button
color="blue"
leftSection={<IconCircleCheck size={16} />}
onClick={handleSubmit}
loading={submitting}
>
Submit Registration
</Button>
)}
{active < STEPS.length - 1 ? (
<Button
rightSection={<IconArrowRight size={16} />}
onClick={next}
disabled={!canNext()}
>
Next Step
</Button>
) : (
<Button
color="blue"
leftSection={<IconCircleCheck size={16} />}
onClick={handleSubmit}
loading={submitting}
>
Submit Registration
</Button>
)}
</Group>
</Group>
</Group>
</Paper>
</Stack>
</Paper>
</Stack>
);
}