Files
edr-platform/apps/edr-freight-web/portal/src/pages/SettingsPage.tsx
2026-06-17 13:34:23 +03:00

260 lines
7.0 KiB
TypeScript

import { api } from "@/services/api";
import type { ProfileResponse } from "@/types/profile";
import {
Alert,
Card,
Center,
Container,
Group,
Loader,
Tabs,
Text,
Title,
} from "@mantine/core";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
AlertCircle,
Briefcase,
Building2,
FileCheck,
User,
UserCheck,
} from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import TabCompanyProfile from "./settings/TabCompanyProfile";
import TabContactPerson from "./settings/TabContactPerson";
import TabDocuments from "./settings/TabDocuments";
import TabGeneralManager from "./settings/TabGeneralManager";
import TabPowerOfAttorney from "./settings/TabPowerOfAttorney";
type SettingsTab = "company" | "contact" | "gm" | "poa" | "documents";
function tabIncomplete(tabId: SettingsTab, profile?: ProfileResponse | null): boolean {
if (!profile) return false;
switch (tabId) {
case "company":
return !profile.companyEmail || !profile.companyPhone || !profile.companyAddress || !profile.fanNumber;
case "contact":
return !profile.contactPersonName || !profile.contactPersonPhone;
case "gm":
return !profile.generalManagerName || !profile.generalManagerEmail || !profile.generalManagerPhone;
case "poa":
return false;
case "documents":
return false;
}
}
const TABS: { id: SettingsTab; label: string; icon: React.ReactNode }[] = [
{ id: "company", label: "Company Profile", icon: <Building2 size={16} /> },
{ id: "contact", label: "Contact Person", icon: <User size={16} /> },
{ id: "gm", label: "General Manager", icon: <Briefcase size={16} /> },
{ id: "poa", label: "Power of Attorney", icon: <UserCheck size={16} /> },
{ id: "documents", label: "Documents", icon: <FileCheck size={16} /> },
];
const TAB_ORDER: SettingsTab[] = [
"company",
"contact",
"gm",
"poa",
"documents",
];
export default function SettingsPage() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [searchParams, setSearchParams] = useSearchParams();
const tab = (searchParams.get("tab") as SettingsTab) || "company";
const setTab = useCallback(
(t: SettingsTab) => {
setSearchParams(
(prev) => {
const next = new URLSearchParams(prev);
next.set("tab", t);
return next;
},
{ replace: true },
);
},
[setSearchParams],
);
const profileQuery = useQuery(
api.companies.getProfile.queryOptions({
retry: false,
refetchOnWindowFocus: false,
}),
);
const profile = profileQuery.data;
useEffect(() => {
if (profileQuery.dataUpdatedAt > 0) {
queryClient.invalidateQueries({
queryKey: api.companies.getInfo.queryKey(),
});
}
}, [profileQuery.dataUpdatedAt, queryClient]);
const [isOnboarding, setIsOnboarding] = useState<boolean | null>(null);
useEffect(() => {
if (profileQuery.isFetched && isOnboarding === null) {
setIsOnboarding(!profileQuery.data);
}
}, [profileQuery.isFetched, profileQuery.data, isOnboarding]);
const handleOnboardingSuccess = useCallback(() => {
setTab("contact");
}, [setTab]);
const handleContactContinue = useCallback(() => {
setTab("gm");
}, [setTab]);
const handleGMContinue = useCallback(() => {
setTab("poa");
}, [setTab]);
const handlePOAContinue = useCallback(() => {
setTab("documents");
}, [setTab]);
const handleDocumentsContinue = useCallback(() => {
navigate("/portal");
}, [navigate]);
if (profileQuery.isPending) {
return (
<Center h="100%">
<Loader color="edr-green" size="lg" />
</Center>
);
}
const onboarding = isOnboarding === true;
const renderProfileContent = (children: React.ReactNode) => {
if (onboarding && tab !== "company" && !profile) {
return (
<Center h={200}>
<Loader color="edr-green" />
</Center>
);
}
if (!profile) {
return (
<Card padding="xl">
<Center>
<Alert
icon={<AlertCircle size={24} />}
color="gray"
variant="light"
>
Please complete the company profile first.
</Alert>
</Center>
</Card>
);
}
return children;
};
return (
<Container size="xl" px="lg">
<Group justify="space-between" mb="xl">
<div>
<Title order={1} size="h2">
{onboarding ? "Complete Your Profile" : "Account Settings"}
</Title>
<Text c="edr-muted" size="sm" mt={4}>
{onboarding
? "Set up your company profile, personnel, and documents to get started"
: "Manage your company profile, personnel, and documents"}
</Text>
</div>
</Group>
<Tabs
value={tab}
onChange={(value) => {
if (!value) return;
// if (onboarding) return;
setTab(value as SettingsTab);
}}
>
<Tabs.List mb="md">
{TABS.map((t) => (
<Tabs.Tab
key={t.id}
value={t.id}
leftSection={t.icon}
disabled={!onboarding && !profile && t.id !== "company"}
rightSection={
!onboarding && profile && tabIncomplete(t.id, profile) ? (
<AlertCircle size={14} color="red" />
) : undefined
}
>
{t.label}
</Tabs.Tab>
))}
</Tabs.List>
<Tabs.Panel value="company">
{!profile ? (
<TabCompanyProfile
mode="create"
onCreateSuccess={handleOnboardingSuccess}
/>
) : (
<TabCompanyProfile mode="edit" profile={profile} />
)}
</Tabs.Panel>
<Tabs.Panel value="contact">
{renderProfileContent(
<TabContactPerson
profile={profile!}
mode={onboarding ? "onboarding" : "edit"}
onContinue={handleContactContinue}
/>,
)}
</Tabs.Panel>
<Tabs.Panel value="gm">
{renderProfileContent(
<TabGeneralManager
profile={profile!}
mode={onboarding ? "onboarding" : "edit"}
onContinue={handleGMContinue}
/>,
)}
</Tabs.Panel>
<Tabs.Panel value="poa">
{renderProfileContent(
<TabPowerOfAttorney
profile={profile!}
mode={onboarding ? "onboarding" : "edit"}
onContinue={handlePOAContinue}
/>,
)}
</Tabs.Panel>
<Tabs.Panel value="documents">
{renderProfileContent(
<TabDocuments
profile={profile!}
mode={onboarding ? "onboarding" : "edit"}
onContinue={handleDocumentsContinue}
/>,
)}
</Tabs.Panel>
</Tabs>
</Container>
);
}