mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
style: ui fixes
This commit is contained in:
@@ -1,15 +1,23 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Avatar,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { BadgeCheck, Clock, ShieldCheck, XCircle } from "lucide-react";
|
||||
import {
|
||||
BadgeCheck,
|
||||
Clock,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
ShieldCheck,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
|
||||
import {
|
||||
verifaydaService,
|
||||
@@ -42,10 +50,12 @@ interface FaydaVerifyPanelProps {
|
||||
pendingReview?: boolean;
|
||||
}
|
||||
|
||||
function formatDate(iso: string | null): string {
|
||||
if (!iso) return "";
|
||||
const d = new Date(iso);
|
||||
return Number.isNaN(d.getTime()) ? "" : d.toLocaleDateString();
|
||||
function getInitials(name: string | null): string {
|
||||
if (!name) return "?";
|
||||
const parts = name.trim().split(/\s+/);
|
||||
const first = parts[0]?.[0] ?? "";
|
||||
const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? "") : "";
|
||||
return (first + last).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +182,7 @@ export default function FaydaVerifyPanel({
|
||||
<Group gap="sm">
|
||||
<ShieldCheck size={18} />
|
||||
<Text fw={600} c="edr-text">
|
||||
{title} identity
|
||||
{title}
|
||||
</Text>
|
||||
{verified ? (
|
||||
<Badge
|
||||
@@ -222,13 +232,21 @@ export default function FaydaVerifyPanel({
|
||||
)}
|
||||
|
||||
{verified && state && (
|
||||
<SimpleGrid cols={2} spacing="xs">
|
||||
<VerifiedField label="Name" value={state.name} />
|
||||
<VerifiedField label="Phone" value={state.phone} />
|
||||
<VerifiedField label="Email" value={state.email} />
|
||||
<VerifiedField label="Address" value={state.address} />
|
||||
<VerifiedField label="Verified" value={formatDate(state.verifiedAt)} />
|
||||
</SimpleGrid>
|
||||
<Group align="flex-start" gap="sm" wrap="nowrap">
|
||||
<Avatar radius="xl" size={44} color="edr-green" variant="light">
|
||||
{getInitials(state.name)}
|
||||
</Avatar>
|
||||
<Stack gap={4} style={{ minWidth: 0, flex: 1 }}>
|
||||
<Text fw={600} size="sm" c="edr-text" truncate>
|
||||
{state.name}
|
||||
</Text>
|
||||
<Group gap="md" wrap="wrap">
|
||||
<DataRow icon={<Phone size={13} />} value={state.phone} />
|
||||
<DataRow icon={<Mail size={13} />} value={state.email} />
|
||||
</Group>
|
||||
<DataRow icon={<MapPin size={13} />} value={state.address} />
|
||||
</Stack>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
@@ -240,22 +258,16 @@ export default function FaydaVerifyPanel({
|
||||
);
|
||||
}
|
||||
|
||||
function VerifiedField({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: string | null;
|
||||
}) {
|
||||
function DataRow({ icon, value }: { icon: ReactNode; value: string | null }) {
|
||||
if (!value) return null;
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<Text size="xs" c="edr-muted">
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm" fw={500} c="edr-text">
|
||||
<Group gap={6} wrap="nowrap">
|
||||
<span style={{ color: "var(--mantine-color-edr-muted-6)", display: "flex", flexShrink: 0 }}>
|
||||
{icon}
|
||||
</span>
|
||||
<Text size="xs" c="edr-muted" truncate>
|
||||
{value}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,31 +28,6 @@ interface ETradeInfoProps {
|
||||
|
||||
const isValidTin = (tin: string) => tin.length === 10;
|
||||
|
||||
/** Plain-text summary of the fetched eTrade record, downloaded client-side (eTrade returns data, not a document). */
|
||||
function downloadTinRecord(tin: string, data: CompanyRegistrationData) {
|
||||
const lines = [
|
||||
`TIN: ${tin}`,
|
||||
`Company name: ${data.companyName}`,
|
||||
`Licence number: ${data.licenceNumber}`,
|
||||
`Status: ${data.statusDescription}`,
|
||||
`Date registered: ${data.dateRegistered}`,
|
||||
`Renewed from: ${data.renewedFrom}`,
|
||||
`Renewal date: ${data.renewalDate}`,
|
||||
`Renewed to: ${data.renewedTo}`,
|
||||
`Address: ${[data.region, data.zone, data.woreda, data.kebele, data.houseNo].filter(Boolean).join(", ")}`,
|
||||
`Manager: ${data.managerName}`,
|
||||
];
|
||||
const blob = new Blob([lines.join("\n")], { type: "text/plain;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `tin-${tin}.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
}
|
||||
|
||||
export default function ETradeInfo({
|
||||
tin,
|
||||
register,
|
||||
@@ -86,9 +61,7 @@ export default function ETradeInfo({
|
||||
}, [tin]);
|
||||
|
||||
const apiError =
|
||||
mutation.isError && mutation.error
|
||||
? extractApiError(mutation.error)
|
||||
: null;
|
||||
mutation.isError && mutation.error ? extractApiError(mutation.error) : null;
|
||||
// A 400 here means eTrade simply has no record for this TIN.
|
||||
const notFound = apiError?.statusCode === 400;
|
||||
const errorMessage =
|
||||
@@ -117,18 +90,14 @@ export default function ETradeInfo({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [status]);
|
||||
|
||||
const showRetry = isValidTin(tin) && status !== "verified" && status !== "loading";
|
||||
const showRetry =
|
||||
isValidTin(tin) && status !== "verified" && status !== "loading";
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Group align="flex-start" grow>
|
||||
<div className="max-sm:flex-col! max-sm: grow flex items-start gap-4">
|
||||
<TextInput
|
||||
label={
|
||||
<>
|
||||
TIN Number (10 digits){" "}
|
||||
<span style={{ color: "var(--mantine-color-red-6)" }}>*</span>
|
||||
</>
|
||||
}
|
||||
aria-label="TIN Number (10 digits), required"
|
||||
placeholder="0012345678"
|
||||
maxLength={10}
|
||||
error={error}
|
||||
@@ -136,6 +105,7 @@ export default function ETradeInfo({
|
||||
/>
|
||||
{showRetry && (
|
||||
<Button
|
||||
className="max-w-none"
|
||||
variant="filled"
|
||||
color="edr-green"
|
||||
onClick={handleFetch}
|
||||
@@ -143,23 +113,11 @@ export default function ETradeInfo({
|
||||
leftSection={
|
||||
isLoading ? <Loader size={16} /> : <Download size={16} />
|
||||
}
|
||||
mt="24px"
|
||||
>
|
||||
{isLoading ? "Getting..." : "Get Data"}
|
||||
</Button>
|
||||
)}
|
||||
{status === "verified" && mutation.data && !mutation.data.tinTaken && (
|
||||
<Button
|
||||
variant="light"
|
||||
color="edr-green"
|
||||
onClick={() => downloadTinRecord(tin, mutation.data!)}
|
||||
leftSection={<Download size={16} />}
|
||||
mt="24px"
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</div>
|
||||
|
||||
{notFound && (
|
||||
<Alert
|
||||
|
||||
@@ -649,7 +649,7 @@ export default function CompanyProfileForm({
|
||||
}
|
||||
>
|
||||
<TextInput
|
||||
label="VAT Number"
|
||||
aria-label="VAT Number"
|
||||
placeholder="0012345678"
|
||||
maxLength={10}
|
||||
error={errors.vatNumber?.message}
|
||||
@@ -661,9 +661,9 @@ export default function CompanyProfileForm({
|
||||
index={2}
|
||||
title="Owner identity"
|
||||
subtitle={
|
||||
verifiedIdentity
|
||||
? "Verify the company owner with Fayda — their name, phone, email and address come from the verification."
|
||||
: "Provide the company owner's passport number."
|
||||
!identity?.owner.verified && !verifiedIdentity
|
||||
? "Provide the company owner's passport number."
|
||||
: undefined
|
||||
}
|
||||
status={
|
||||
verifiedIdentity
|
||||
@@ -683,7 +683,7 @@ export default function CompanyProfileForm({
|
||||
<>
|
||||
<FaydaVerifyPanel
|
||||
subject="owner"
|
||||
title="Company owner"
|
||||
title="Owner"
|
||||
state={identity.owner}
|
||||
required={identity.faydaRequired}
|
||||
onVerified={() => onIdentityChange?.()}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function ReadOnlyField({
|
||||
<Text size="xs" c="dimmed">
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm" c="edr-text" fw={500}>
|
||||
<Text className="wrap-break-word" size="sm" c="edr-text" fw={500}>
|
||||
{value && value.trim() ? value : "—"}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Badge, Group, Stack, Text } from "@mantine/core";
|
||||
import { useMediaQuery } from "@mantine/hooks";
|
||||
import { Check, X } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
@@ -32,6 +33,7 @@ export default function StepSection({
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const badge = STATUS_BADGE[status];
|
||||
const isMobile = useMediaQuery("(max-width: 48em)");
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Group justify="space-between" align="center">
|
||||
@@ -75,7 +77,7 @@ export default function StepSection({
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
<div style={{ paddingLeft: 34 }}>
|
||||
<div style={{ paddingLeft: isMobile ? 0 : 34 }}>
|
||||
<Stack gap="sm">{children}</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user