mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
- Implement ShippingAgentLicenseRenewalPage for submitting renewal documents with file uploads. - Create WaiverApplicationPage for multi-step waiver application process, including document uploads and review. - Add WaiverPage to display application status and details, including waiver letter download functionality. - Introduce document handling components and validation for required fields in both applications.
147 lines
5.6 KiB
TypeScript
147 lines
5.6 KiB
TypeScript
import { useRef, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useApiMutation } from '@ema-platform/api';
|
|
import {
|
|
Alert,
|
|
Box,
|
|
Button,
|
|
Card,
|
|
Group,
|
|
Paper,
|
|
Stack,
|
|
Text,
|
|
ThemeIcon,
|
|
Title,
|
|
rem,
|
|
} from '@mantine/core';
|
|
import {
|
|
IconArrowLeft,
|
|
IconCheck,
|
|
IconCircleCheck,
|
|
IconFileDescription,
|
|
IconInfoCircle,
|
|
IconShip,
|
|
} from '@tabler/icons-react';
|
|
import { FileButton } from '@mantine/core';
|
|
import { notify } from '@ema-platform/ui';
|
|
|
|
interface DocSlot {
|
|
key: string;
|
|
label: string;
|
|
description: string;
|
|
required: boolean;
|
|
}
|
|
|
|
const RENEWAL_DOCS: DocSlot[] = [
|
|
{ key: 'prevCertificate', label: 'Previous Shipping Agent Certificate', description: 'Your current/expiring certificate', required: true },
|
|
{ key: 'taxClearance', label: 'Tax Clearance', description: 'Current tax clearance certificate', required: true },
|
|
{ key: 'threeMonthClearance', label: 'Three-Month Clearance Evidence', description: 'Clearance evidence for the last three months', required: true },
|
|
{ key: 'vehicleRenewal', label: 'Updated Vehicle Rental Agreement', description: 'Required only if the previous agreement has expired', required: false },
|
|
{ key: 'officeRenewal', label: 'Updated Office Rental Agreement', description: 'Required only if the previous agreement has expired', required: false },
|
|
{ key: 'paymentReceipt', label: 'Renewal Service Payment Receipt (600 ETB)', description: 'Proof of payment for the renewal service fee', required: true },
|
|
{ key: 'renewalDeclaration', label: 'Applicant Renewal Declaration', description: 'Signed declaration confirming renewal details', required: true },
|
|
];
|
|
|
|
function DocCard({ slot, file, onFile }: { slot: DocSlot; file: File | null; onFile: (f: File | null) => void }) {
|
|
const resetRef = useRef<() => void>(null);
|
|
return (
|
|
<Card withBorder radius="md" p="md" style={{
|
|
borderStyle: 'dashed',
|
|
borderColor: file ? 'var(--mantine-color-teal-5)' : 'var(--mantine-color-default-border)',
|
|
}}>
|
|
<Group gap="sm" mb="sm" wrap="nowrap">
|
|
<Box style={{
|
|
width: rem(44), height: rem(44), borderRadius: rem(8),
|
|
background: 'var(--mantine-color-blue-light)', display: 'flex',
|
|
alignItems: 'center', justifyContent: 'center', flexShrink: 0,
|
|
}}>
|
|
<IconFileDescription size={22} color="var(--mantine-color-blue-6)" stroke={1.5} />
|
|
</Box>
|
|
<div>
|
|
<Text fw={600} fz="sm">{slot.label}{slot.required && <Text span c="red" ml={3}>*</Text>}</Text>
|
|
<Text fz="xs" c="dimmed">{slot.description}</Text>
|
|
</div>
|
|
</Group>
|
|
{file ? (
|
|
<Group gap="xs" align="center">
|
|
<IconCircleCheck size={16} color="var(--mantine-color-teal-6)" />
|
|
<Text fz="xs" c="teal.7" style={{ flex: 1 }} truncate>{file.name}</Text>
|
|
<Button size="xs" variant="subtle" color="red" onClick={() => { onFile(null); resetRef.current?.(); }}>Remove</Button>
|
|
</Group>
|
|
) : (
|
|
<FileButton resetRef={resetRef} onChange={onFile} accept="application/pdf,image/jpeg,image/png">
|
|
{(props) => <Button size="xs" variant="default" {...props}>Choose File</Button>}
|
|
</FileButton>
|
|
)}
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export function ShippingAgentLicenseRenewalPage() {
|
|
const navigate = useNavigate();
|
|
const [submitTrigger] = useApiMutation<{ id: string }>();
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [files, setFiles] = useState<Record<string, File | null>>(
|
|
Object.fromEntries(RENEWAL_DOCS.map((s) => [s.key, null]))
|
|
);
|
|
const setFile = (key: string) => (f: File | null) => setFiles((prev) => ({ ...prev, [key]: f }));
|
|
|
|
const canSubmit = RENEWAL_DOCS.every((s) => !s.required || !!files[s.key]);
|
|
|
|
const handleSubmit = async () => {
|
|
setSubmitting(true);
|
|
try {
|
|
await submitTrigger({ url: '/logistics-licenses/shipping-agent/renew', method: 'POST', body: {} }).unwrap();
|
|
notify.success('Renewal request submitted successfully!');
|
|
navigate('/shipping-agent-license');
|
|
} catch {
|
|
notify.error('Renewal submission failed. Please try again.');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Stack gap="md">
|
|
<Group gap="xs">
|
|
<Button variant="subtle" size="xs" leftSection={<IconArrowLeft size={14} />} onClick={() => navigate('/shipping-agent-license')}>
|
|
Back
|
|
</Button>
|
|
</Group>
|
|
|
|
<Group gap="sm">
|
|
<ThemeIcon size={44} radius="md" color="blue" variant="light"><IconShip size={24} /></ThemeIcon>
|
|
<div>
|
|
<Title order={3}>Renew Shipping Agent License</Title>
|
|
<Text fz="sm" c="dimmed">Submit renewal documents to extend your license by one year</Text>
|
|
</div>
|
|
</Group>
|
|
|
|
<Alert variant="light" color="blue" icon={<IconInfoCircle size={16} />}>
|
|
A renewal service payment of 600 ETB is required. If your vehicle or office rental agreement has expired since your last submission, an updated agreement is required.
|
|
</Alert>
|
|
|
|
<Paper withBorder radius="lg" p="xl">
|
|
<Text fw={700} fz="lg" mb="lg">Renewal Documents</Text>
|
|
<Stack gap="md">
|
|
{RENEWAL_DOCS.map((slot) => (
|
|
<DocCard key={slot.key} slot={slot} file={files[slot.key]} onFile={setFile(slot.key)} />
|
|
))}
|
|
</Stack>
|
|
|
|
<Group justify="flex-end" mt="xl">
|
|
<Button
|
|
color="teal"
|
|
leftSection={<IconCheck size={16} />}
|
|
loading={submitting}
|
|
disabled={!canSubmit}
|
|
onClick={handleSubmit}
|
|
>
|
|
Submit Renewal Request
|
|
</Button>
|
|
</Group>
|
|
</Paper>
|
|
</Stack>
|
|
);
|
|
}
|