style: show the profile and the review ont the settings

This commit is contained in:
Nathnael
2026-07-21 12:13:18 +00:00
parent 649316070d
commit d95af0ea72
4 changed files with 275 additions and 38 deletions

View File

@@ -25,6 +25,7 @@ import {
Menu as MenuIcon,
Moon,
Plus,
RefreshCw,
Search,
Settings,
Sun,
@@ -59,7 +60,13 @@ export interface AppLayoutProps {
userName?: string;
userEmail?: string;
/** Operational profiles for the company — surfaced as reference chips in the account menu. */
companyProfiles?: { type: string; reference: string; status?: string }[];
companyProfiles?: {
id?: string;
type: string;
reference: string;
status?: string;
reviewNote?: string | null;
}[];
/** Company type (e.g. "customer", "forwarder") — gates the "Add service" control. */
companyType?: string | null;
/** Create a new service profile of the given type (with business license). */
@@ -67,6 +74,11 @@ export interface AppLayoutProps {
type: ServiceType,
licenseFiles: File[],
) => Promise<SwitchResult> | void;
/** Resubmit a rejected service for approval, optionally replacing its license. */
onReapplyProfile?: (
profileId: string,
licenseFiles: File[],
) => Promise<SwitchResult> | void;
children: ReactNode;
}
@@ -145,6 +157,7 @@ export function AppLayout({
companyProfiles = [],
companyType,
onCreateProfile,
onReapplyProfile,
children,
}: AppLayoutProps) {
const [mobileOpen, { toggle: toggleMobile }] = useDisclosure();
@@ -178,36 +191,59 @@ export function AppLayout({
const profileExists = (type: ServiceType) =>
companyProfiles.some((p) => p.type === type);
const addableServices = CUSTOMER_SERVICES.filter((t) => !profileExists(t));
const canAddService = isCustomer && addableServices.length > 0;
// Rejected services can't be re-added (they exist), so they'd otherwise be
// invisible here — surface them for resubmission alongside addable ones.
const rejectedServices = isCustomer
? companyProfiles.filter(
(p) =>
p.status === "rejected" &&
p.id &&
CUSTOMER_SERVICES.includes(p.type as ServiceType),
)
: [];
const canManageServices =
isCustomer && (addableServices.length > 0 || rejectedServices.length > 0);
const [switching, setSwitching] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [createTarget, setCreateTarget] = useState<ServiceType>("importer");
// Non-null while resubmitting a rejected service; null while creating a new one.
const [reapplyId, setReapplyId] = useState<string | null>(null);
const [licenseFiles, setLicenseFiles] = useState<File[]>([]);
const [createError, setCreateError] = useState<string | null>(null);
const handleAddService = (type: ServiceType) => {
// Collect a business license, then create the profile.
const openServiceModal = (
type: ServiceType,
profileId: string | null,
) => {
setCreateTarget(type);
setReapplyId(profileId);
setLicenseFiles([]);
setCreateError(null);
setCreateOpen(true);
};
const handleAddService = (type: ServiceType) => openServiceModal(type, null);
const handleCreateConfirm = async () => {
if (licenseFiles.length === 0) {
const isReapply = reapplyId !== null;
// A new profile needs its license up front; a resubmit may reuse the old one.
if (!isReapply && licenseFiles.length === 0) {
setCreateError("Please upload at least one business license file.");
return;
}
setSwitching(true);
setCreateError(null);
try {
const res = await onCreateProfile?.(createTarget, licenseFiles);
const res = isReapply
? await onReapplyProfile?.(reapplyId, licenseFiles)
: await onCreateProfile?.(createTarget, licenseFiles);
if (res && !res.success) {
setCreateError(res.error?.message ?? "Failed to create profile");
setCreateError(res.error?.message ?? "Failed to submit service");
return;
}
setCreateOpen(false);
setReapplyId(null);
} finally {
setSwitching(false);
}
@@ -287,10 +323,10 @@ export function AppLayout({
{/* Right: switch + search + bell + avatar */}
<Group gap={10} wrap="nowrap" align="center">
{/* Add a service (customer companies that don't yet have all three) */}
{canAddService && (
{/* Add a service, or resubmit a rejected one (customer companies) */}
{canManageServices && (
<Menu
width={220}
width={240}
position="bottom-end"
withinPortal
shadow="md"
@@ -313,16 +349,38 @@ export function AppLayout({
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Add a service</Menu.Label>
{addableServices.map((type) => (
<Menu.Item
key={type}
onClick={() => handleAddService(type)}
leftSection={<Plus size={15} strokeWidth={1.8} />}
>
{serviceLabel(type)}
</Menu.Item>
))}
{addableServices.length > 0 && (
<>
<Menu.Label>Add a service</Menu.Label>
{addableServices.map((type) => (
<Menu.Item
key={type}
onClick={() => handleAddService(type)}
leftSection={<Plus size={15} strokeWidth={1.8} />}
>
{serviceLabel(type)}
</Menu.Item>
))}
</>
)}
{rejectedServices.length > 0 && (
<>
{addableServices.length > 0 && <Menu.Divider />}
<Menu.Label>Rejected resubmit</Menu.Label>
{rejectedServices.map((p) => (
<Menu.Item
key={p.id}
color="red"
onClick={() =>
openServiceModal(p.type as ServiceType, p.id!)
}
leftSection={<RefreshCw size={15} strokeWidth={1.8} />}
>
{serviceLabel(p.type as ServiceType)}
</Menu.Item>
))}
</>
)}
</Menu.Dropdown>
</Menu>
)}
@@ -790,18 +848,28 @@ export function AppLayout({
<Modal
opened={createOpen}
onClose={() => (switching ? undefined : setCreateOpen(false))}
title={`Set up your ${serviceLabel(createTarget)} profile`}
title={
reapplyId
? `Resubmit your ${serviceLabel(createTarget)} service`
: `Set up your ${serviceLabel(createTarget)} profile`
}
centered
radius="lg"
>
<Stack gap="md">
<Text size="sm" c="dimmed">
You don't have a {serviceLabel(createTarget).toLowerCase()} profile
yet. Add your business license to create one and switch to{" "}
{serviceLabel(createTarget).toLowerCase()}.
{reapplyId
? `Your ${serviceLabel(
createTarget,
).toLowerCase()} service was rejected. Replace the business license if needed, then resubmit for approval.`
: `You don't have a ${serviceLabel(
createTarget,
).toLowerCase()} profile yet. Add your business license to create one and switch to ${serviceLabel(
createTarget,
).toLowerCase()}.`}
</Text>
<FileInput
label="Business license"
label={reapplyId ? "Business license (optional)" : "Business license"}
multiple
clearable
accept="application/pdf,image/png,image/jpeg"
@@ -824,7 +892,7 @@ export function AppLayout({
onClick={handleCreateConfirm}
loading={switching}
>
Create &amp; switch
{reapplyId ? "Resubmit" : "Create & switch"}
</Button>
</Group>
</Stack>