style: ui fixes

This commit is contained in:
ghost2023
2026-08-02 01:11:04 +03:00
parent aaf0aa3c92
commit e6ce19ad22
5 changed files with 57 additions and 85 deletions

View File

@@ -1,15 +1,23 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState, type ReactNode } from "react";
import { import {
Alert, Alert,
Avatar,
Badge, Badge,
Button, Button,
Card, Card,
Group, Group,
SimpleGrid,
Stack, Stack,
Text, Text,
} from "@mantine/core"; } from "@mantine/core";
import { BadgeCheck, Clock, ShieldCheck, XCircle } from "lucide-react"; import {
BadgeCheck,
Clock,
Mail,
MapPin,
Phone,
ShieldCheck,
XCircle,
} from "lucide-react";
import { import {
verifaydaService, verifaydaService,
@@ -42,10 +50,12 @@ interface FaydaVerifyPanelProps {
pendingReview?: boolean; pendingReview?: boolean;
} }
function formatDate(iso: string | null): string { function getInitials(name: string | null): string {
if (!iso) return ""; if (!name) return "?";
const d = new Date(iso); const parts = name.trim().split(/\s+/);
return Number.isNaN(d.getTime()) ? "" : d.toLocaleDateString(); 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"> <Group gap="sm">
<ShieldCheck size={18} /> <ShieldCheck size={18} />
<Text fw={600} c="edr-text"> <Text fw={600} c="edr-text">
{title} identity {title}
</Text> </Text>
{verified ? ( {verified ? (
<Badge <Badge
@@ -222,13 +232,21 @@ export default function FaydaVerifyPanel({
)} )}
{verified && state && ( {verified && state && (
<SimpleGrid cols={2} spacing="xs"> <Group align="flex-start" gap="sm" wrap="nowrap">
<VerifiedField label="Name" value={state.name} /> <Avatar radius="xl" size={44} color="edr-green" variant="light">
<VerifiedField label="Phone" value={state.phone} /> {getInitials(state.name)}
<VerifiedField label="Email" value={state.email} /> </Avatar>
<VerifiedField label="Address" value={state.address} /> <Stack gap={4} style={{ minWidth: 0, flex: 1 }}>
<VerifiedField label="Verified" value={formatDate(state.verifiedAt)} /> <Text fw={600} size="sm" c="edr-text" truncate>
</SimpleGrid> {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 && ( {error && (
@@ -240,22 +258,16 @@ export default function FaydaVerifyPanel({
); );
} }
function VerifiedField({ function DataRow({ icon, value }: { icon: ReactNode; value: string | null }) {
label,
value,
}: {
label: string;
value: string | null;
}) {
if (!value) return null; if (!value) return null;
return ( return (
<Stack gap={0}> <Group gap={6} wrap="nowrap">
<Text size="xs" c="edr-muted"> <span style={{ color: "var(--mantine-color-edr-muted-6)", display: "flex", flexShrink: 0 }}>
{label} {icon}
</Text> </span>
<Text size="sm" fw={500} c="edr-text"> <Text size="xs" c="edr-muted" truncate>
{value} {value}
</Text> </Text>
</Stack> </Group>
); );
} }

View File

@@ -28,31 +28,6 @@ interface ETradeInfoProps {
const isValidTin = (tin: string) => tin.length === 10; 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({ export default function ETradeInfo({
tin, tin,
register, register,
@@ -86,9 +61,7 @@ export default function ETradeInfo({
}, [tin]); }, [tin]);
const apiError = const apiError =
mutation.isError && mutation.error mutation.isError && mutation.error ? extractApiError(mutation.error) : null;
? extractApiError(mutation.error)
: null;
// A 400 here means eTrade simply has no record for this TIN. // A 400 here means eTrade simply has no record for this TIN.
const notFound = apiError?.statusCode === 400; const notFound = apiError?.statusCode === 400;
const errorMessage = const errorMessage =
@@ -117,18 +90,14 @@ export default function ETradeInfo({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [status]); }, [status]);
const showRetry = isValidTin(tin) && status !== "verified" && status !== "loading"; const showRetry =
isValidTin(tin) && status !== "verified" && status !== "loading";
return ( return (
<Stack gap="md"> <Stack gap="md">
<Group align="flex-start" grow> <div className="max-sm:flex-col! max-sm: grow flex items-start gap-4">
<TextInput <TextInput
label={ aria-label="TIN Number (10 digits), required"
<>
TIN Number (10 digits){" "}
<span style={{ color: "var(--mantine-color-red-6)" }}>*</span>
</>
}
placeholder="0012345678" placeholder="0012345678"
maxLength={10} maxLength={10}
error={error} error={error}
@@ -136,6 +105,7 @@ export default function ETradeInfo({
/> />
{showRetry && ( {showRetry && (
<Button <Button
className="max-w-none"
variant="filled" variant="filled"
color="edr-green" color="edr-green"
onClick={handleFetch} onClick={handleFetch}
@@ -143,23 +113,11 @@ export default function ETradeInfo({
leftSection={ leftSection={
isLoading ? <Loader size={16} /> : <Download size={16} /> isLoading ? <Loader size={16} /> : <Download size={16} />
} }
mt="24px"
> >
{isLoading ? "Getting..." : "Get Data"} {isLoading ? "Getting..." : "Get Data"}
</Button> </Button>
)} )}
{status === "verified" && mutation.data && !mutation.data.tinTaken && ( </div>
<Button
variant="light"
color="edr-green"
onClick={() => downloadTinRecord(tin, mutation.data!)}
leftSection={<Download size={16} />}
mt="24px"
>
Download
</Button>
)}
</Group>
{notFound && ( {notFound && (
<Alert <Alert

View File

@@ -649,7 +649,7 @@ export default function CompanyProfileForm({
} }
> >
<TextInput <TextInput
label="VAT Number" aria-label="VAT Number"
placeholder="0012345678" placeholder="0012345678"
maxLength={10} maxLength={10}
error={errors.vatNumber?.message} error={errors.vatNumber?.message}
@@ -661,9 +661,9 @@ export default function CompanyProfileForm({
index={2} index={2}
title="Owner identity" title="Owner identity"
subtitle={ subtitle={
verifiedIdentity !identity?.owner.verified && !verifiedIdentity
? "Verify the company owner with Fayda — their name, phone, email and address come from the verification." ? "Provide the company owner's passport number."
: "Provide the company owner's passport number." : undefined
} }
status={ status={
verifiedIdentity verifiedIdentity
@@ -683,7 +683,7 @@ export default function CompanyProfileForm({
<> <>
<FaydaVerifyPanel <FaydaVerifyPanel
subject="owner" subject="owner"
title="Company owner" title="Owner"
state={identity.owner} state={identity.owner}
required={identity.faydaRequired} required={identity.faydaRequired}
onVerified={() => onIdentityChange?.()} onVerified={() => onIdentityChange?.()}

View File

@@ -13,7 +13,7 @@ export function ReadOnlyField({
<Text size="xs" c="dimmed"> <Text size="xs" c="dimmed">
{label} {label}
</Text> </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 : "—"} {value && value.trim() ? value : "—"}
</Text> </Text>
</Stack> </Stack>

View File

@@ -1,4 +1,5 @@
import { Badge, Group, Stack, Text } from "@mantine/core"; import { Badge, Group, Stack, Text } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { Check, X } from "lucide-react"; import { Check, X } from "lucide-react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
@@ -32,6 +33,7 @@ export default function StepSection({
children: ReactNode; children: ReactNode;
}) { }) {
const badge = STATUS_BADGE[status]; const badge = STATUS_BADGE[status];
const isMobile = useMediaQuery("(max-width: 48em)");
return ( return (
<Stack gap="sm"> <Stack gap="sm">
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
@@ -75,7 +77,7 @@ export default function StepSection({
</Badge> </Badge>
)} )}
</Group> </Group>
<div style={{ paddingLeft: 34 }}> <div style={{ paddingLeft: isMobile ? 0 : 34 }}>
<Stack gap="sm">{children}</Stack> <Stack gap="sm">{children}</Stack>
</div> </div>
</Stack> </Stack>