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 {
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>
);
}

View File

@@ -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

View File

@@ -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?.()}

View File

@@ -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>

View File

@@ -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>