mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
style: profile page ui improvment
This commit is contained in:
@@ -1,17 +1,110 @@
|
||||
import { User, Building2, Phone, Mail, MapPin, ShieldCheck, Briefcase, UserCheck, Fingerprint, FileCheck, Globe, Building } from "lucide-react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/services/api";
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, Badge, Separator } from "@edr/ui-common";
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
Center,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
Group,
|
||||
Loader,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
Briefcase,
|
||||
Building,
|
||||
Building2,
|
||||
FileCheck,
|
||||
Globe,
|
||||
Mail,
|
||||
MapPin,
|
||||
Phone,
|
||||
ShieldCheck,
|
||||
User,
|
||||
UserCheck,
|
||||
} from "lucide-react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function InfoItem({ icon, label, value }: { icon?: React.ReactNode; label: string; value?: string | null }) {
|
||||
function InfoItem({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
icon?: React.ReactNode;
|
||||
label: string;
|
||||
value?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start gap-3">
|
||||
{icon && <div className="mt-1 text-muted-foreground [&_svg]:size-4">{icon}</div>}
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<p className="text-[10px] font-bold uppercase tracking-tight text-muted-foreground">{label}</p>
|
||||
<p className="text-sm font-bold text-foreground">{value || "—"}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Group gap="sm" align="flex-start" wrap="nowrap">
|
||||
{icon && (
|
||||
<ThemeIcon variant="light" color="edr-green" size="md" radius="md">
|
||||
{icon}
|
||||
</ThemeIcon>
|
||||
)}
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" fw={700} tt="uppercase" c="edr-muted">
|
||||
{label}
|
||||
</Text>
|
||||
<Text size="sm" fw={600} c="edr-text">
|
||||
{value || "—"}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
function CardHeading({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<Stack gap={2} mb="md">
|
||||
<Group gap="sm">
|
||||
{icon}
|
||||
<Title order={4} size="h5">
|
||||
{title}
|
||||
</Title>
|
||||
</Group>
|
||||
<Text size="sm" c="edr-muted">
|
||||
{description}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function PersonnelGroup({
|
||||
color,
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
color: string;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs">
|
||||
<Box w={4} h={16} bg={color} style={{ borderRadius: 2 }} />
|
||||
<Text size="sm" fw={700} tt="uppercase" c="edr-text">
|
||||
{title}
|
||||
</Text>
|
||||
</Group>
|
||||
<Stack gap="sm" pl="lg">
|
||||
{children}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,163 +115,142 @@ export default function ProfilePage() {
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="h-8 w-8 animate-spin rounded-full border-b-2 border-primary" />
|
||||
</div>
|
||||
<Center h="100%">
|
||||
<Loader color="edr-green" size="lg" />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
if (!profile) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<p className="text-muted-foreground">No company profile found.</p>
|
||||
</div>
|
||||
<Center h="100%">
|
||||
<Text c="edr-muted">No company profile found.</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-4 py-8">
|
||||
<div className="mx-auto max-w-7xl">
|
||||
<div className="flex flex-col gap-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex size-24 items-center justify-center rounded-2xl bg-primary/10 text-primary shadow-inner">
|
||||
<User className="size-12" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-black tracking-tight text-foreground">
|
||||
{profile.companyName}
|
||||
</h1>
|
||||
<Badge variant="secondary" className="font-bold uppercase tracking-wider">
|
||||
Verified
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="flex items-center gap-2 font-medium text-muted-foreground">
|
||||
<Building className="size-4" />
|
||||
{profile.companyName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Container size="xl" px="lg" py="xl">
|
||||
{/* Header */}
|
||||
<Group gap="lg" align="center" mb="lg">
|
||||
<ThemeIcon variant="light" color="edr-green" size={88} radius="lg">
|
||||
<User size={44} />
|
||||
</ThemeIcon>
|
||||
<Stack gap={6}>
|
||||
<Group gap="sm" align="center">
|
||||
<Title order={1} size="h2">
|
||||
{profile.companyName}
|
||||
</Title>
|
||||
<Badge color="edr-green" variant="light">
|
||||
Verified
|
||||
</Badge>
|
||||
</Group>
|
||||
<Group gap={6} c="edr-muted">
|
||||
<Building size={16} />
|
||||
<Text c="edr-muted" fw={500}>
|
||||
{profile.companyType}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Group>
|
||||
|
||||
<Separator />
|
||||
<Divider mb="lg" />
|
||||
|
||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-3">
|
||||
{/* Left Column */}
|
||||
<div className="flex flex-col gap-8 lg:col-span-2">
|
||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
{/* Company Details */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Building2 className="size-5 text-primary" />
|
||||
Company Details
|
||||
</CardTitle>
|
||||
<CardDescription>Business registration information</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<InfoItem icon={<Globe />} label="Location" value={profile.companyLocation} />
|
||||
<InfoItem icon={<MapPin />} label="Address" value={profile.companyAddress} />
|
||||
<InfoItem icon={<FileCheck />} label="TIN Number" value={profile.tinNumber} />
|
||||
<InfoItem icon={<ShieldCheck />} label="FAN Number" value={profile.fanNumber} />
|
||||
<InfoItem icon={<Mail />} label="Email" value={profile.companyEmail} />
|
||||
<InfoItem icon={<Phone />} label="Phone" value={profile.companyPhone} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Grid gap="lg">
|
||||
{/* Left Column */}
|
||||
<Grid.Col span={{ base: 12, lg: 8 }}>
|
||||
<Stack gap="lg">
|
||||
{/* Company Details */}
|
||||
<Card>
|
||||
<CardHeading
|
||||
icon={<Building2 size={20} color="var(--mantine-color-edr-green-6)" />}
|
||||
title="Company Details"
|
||||
description="Business registration information"
|
||||
/>
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||
<InfoItem icon={<Globe size={16} />} label="Location" value={profile.companyLocation} />
|
||||
<InfoItem icon={<MapPin size={16} />} label="Address" value={profile.companyAddress} />
|
||||
<InfoItem icon={<FileCheck size={16} />} label="TIN Number" value={profile.tinNumber} />
|
||||
<InfoItem icon={<ShieldCheck size={16} />} label="FAN Number" value={profile.fanNumber} />
|
||||
<InfoItem icon={<Mail size={16} />} label="Email" value={profile.companyEmail} />
|
||||
<InfoItem icon={<Phone size={16} />} label="Phone" value={profile.companyPhone} />
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
|
||||
{/* Personal Details (from ExternalProfile) */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Fingerprint className="size-5 text-primary" />
|
||||
Profile Details
|
||||
</CardTitle>
|
||||
<CardDescription>Your linked user profile</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<InfoItem icon={<User />} label="Profile" value="Primary Contact" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Key Personnel */}
|
||||
<Card>
|
||||
<CardHeading
|
||||
icon={<Briefcase size={20} color="var(--mantine-color-edr-green-6)" />}
|
||||
title="Key Personnel"
|
||||
description="Management and contact persons"
|
||||
/>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="lg">
|
||||
<PersonnelGroup color="edr-green" title="Contact Person">
|
||||
<InfoItem label="Name" value={profile.contactPersonName} />
|
||||
<InfoItem label="Phone" value={profile.contactPersonPhone} />
|
||||
</PersonnelGroup>
|
||||
<PersonnelGroup color="edr-accent" title="General Manager">
|
||||
<InfoItem label="Name" value={profile.generalManagerName} />
|
||||
<InfoItem label="Email" value={profile.generalManagerEmail} />
|
||||
<InfoItem label="Phone" value={profile.generalManagerPhone} />
|
||||
</PersonnelGroup>
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
|
||||
{/* Personnel Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Briefcase className="size-5 text-primary" />
|
||||
Key Personnel
|
||||
</CardTitle>
|
||||
<CardDescription>Management and contact persons</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="border-l-4 border-primary pl-3 text-sm font-bold uppercase tracking-wide text-foreground">
|
||||
Contact Person
|
||||
</h3>
|
||||
<div className="flex flex-col gap-3 pl-4">
|
||||
<InfoItem label="Name" value={profile.contactPersonName} />
|
||||
<InfoItem label="Phone" value={profile.contactPersonPhone} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="border-l-4 border-accent pl-3 text-sm font-bold uppercase tracking-wide text-foreground">
|
||||
General Manager
|
||||
</h3>
|
||||
<div className="flex flex-col gap-3 pl-4">
|
||||
<InfoItem label="Name" value={profile.generalManagerName} />
|
||||
<InfoItem label="Email" value={profile.generalManagerEmail} />
|
||||
<InfoItem label="Phone" value={profile.generalManagerPhone} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
{/* Power of Attorney */}
|
||||
{profile.poaName && (
|
||||
<Card style={{ borderStyle: "dashed" }}>
|
||||
<CardHeading
|
||||
icon={<UserCheck size={20} color="var(--mantine-color-edr-accent-6)" />}
|
||||
title="Power of Attorney"
|
||||
description="Authorized representative details"
|
||||
/>
|
||||
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="md">
|
||||
<InfoItem label="PoA Name" value={profile.poaName} />
|
||||
<InfoItem label="PoA Email" value={profile.poaEmail} />
|
||||
<InfoItem label="PoA Phone" value={profile.poaPhone} />
|
||||
<InfoItem label="PoA Location" value={profile.poaLocation} />
|
||||
</SimpleGrid>
|
||||
</Card>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
|
||||
{/* Power of Attorney */}
|
||||
{profile.poaName && (
|
||||
<Card className="border-dashed">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<UserCheck className="size-5 text-accent" />
|
||||
Power of Attorney
|
||||
</CardTitle>
|
||||
<CardDescription>Authorized representative details</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<InfoItem label="PoA Name" value={profile.poaName} />
|
||||
<InfoItem label="PoA Email" value={profile.poaEmail} />
|
||||
<InfoItem label="PoA Phone" value={profile.poaPhone} />
|
||||
<InfoItem label="PoA Location" value={profile.poaLocation} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="flex flex-col gap-8">
|
||||
<Card className="relative overflow-hidden border-none bg-foreground text-background shadow-xl">
|
||||
<div className="absolute right-0 top-0 p-4 opacity-10">
|
||||
<ShieldCheck className="size-32" />
|
||||
</div>
|
||||
<CardContent className="relative z-10 flex flex-col gap-4 px-6 py-8">
|
||||
<h3 className="text-xl font-black">Secure Account</h3>
|
||||
<p className="text-sm leading-relaxed text-muted-foreground/80">
|
||||
Your information is protected by enterprise-grade security.
|
||||
Contact support for verified information updates.
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<a
|
||||
href="/settings"
|
||||
className="inline-flex h-9 items-center justify-center rounded-md bg-background px-4 text-sm font-medium text-foreground hover:bg-background/90"
|
||||
>
|
||||
Edit Settings
|
||||
</a>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Right Column */}
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<Card
|
||||
padding="xl"
|
||||
style={{
|
||||
background: "var(--mantine-color-edr-ink-6)",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Box style={{ position: "absolute", top: 16, right: 16, opacity: 0.1 }}>
|
||||
<ShieldCheck size={128} color="white" />
|
||||
</Box>
|
||||
<Stack gap="md" style={{ position: "relative", zIndex: 1 }}>
|
||||
<Title order={3} size="h4" c="white">
|
||||
Secure Account
|
||||
</Title>
|
||||
<Text size="sm" c="gray.4">
|
||||
Your information is protected by enterprise-grade security. Contact
|
||||
support for verified information updates.
|
||||
</Text>
|
||||
<Button
|
||||
component={Link}
|
||||
to="/settings"
|
||||
variant="white"
|
||||
color="dark"
|
||||
mt="xs"
|
||||
w="fit-content"
|
||||
>
|
||||
Edit Settings
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user