mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 06:35:42 +00:00
fix: improve the setting onboarding flow
This commit is contained in:
@@ -1,31 +1,61 @@
|
|||||||
import { Box, Group, Text } from "@mantine/core";
|
import { Box, Group, Text } from "@mantine/core";
|
||||||
import { ArrowRight, Truck } from "lucide-react";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { ArrowRight, Truck, AlertTriangle } from "lucide-react";
|
||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { api } from "@/services/api";
|
||||||
|
import type { ProfileResponse } from "@/types/profile";
|
||||||
import { cv } from "../constants";
|
import { cv } from "../constants";
|
||||||
|
|
||||||
|
const REQUIRED_FIELDS: (keyof ProfileResponse)[] = [
|
||||||
|
"companyEmail",
|
||||||
|
"companyPhone",
|
||||||
|
"companyAddress",
|
||||||
|
"fanNumber",
|
||||||
|
"contactPersonName",
|
||||||
|
"contactPersonPhone",
|
||||||
|
"generalManagerName",
|
||||||
|
"generalManagerEmail",
|
||||||
|
"generalManagerPhone",
|
||||||
|
];
|
||||||
|
|
||||||
|
function isProfileIncomplete(profile?: ProfileResponse | null): boolean {
|
||||||
|
if (!profile) return true;
|
||||||
|
return REQUIRED_FIELDS.some((field) => !profile[field]);
|
||||||
|
}
|
||||||
|
|
||||||
interface SetupPromptProps {
|
interface SetupPromptProps {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SetupPrompt = memo(function SetupPrompt({ show }: SetupPromptProps) {
|
export const SetupPrompt = memo(function SetupPrompt({ show }: SetupPromptProps) {
|
||||||
if (!show) return null;
|
const profileQuery = useQuery(
|
||||||
|
api.companies.getProfile.queryOptions({ retry: false }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const incomplete = !profileQuery.isPending && isProfileIncomplete(profileQuery.data);
|
||||||
|
|
||||||
|
if (!show && !incomplete) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="rounded-2xl border border-edr-border bg-gradient-to-r from-edr-blue/5 to-edr-green/5 px-7 py-6">
|
<Box className="rounded-2xl border border-edr-border bg-gradient-to-r from-edr-blue/5 to-edr-green/5 px-7 py-6">
|
||||||
<Group justify="space-between" align="center" wrap="nowrap">
|
<Group justify="space-between" align="center" wrap="nowrap">
|
||||||
<Box className="flex-1">
|
<Box className="flex-1">
|
||||||
<Text fz={15} fw={700} c="edr-text" mb={6}>
|
<Group gap={6} align="center" mb={6}>
|
||||||
Setup your Company Profile
|
{incomplete && <AlertTriangle size={16} color={cv("edr-orange")} />}
|
||||||
</Text>
|
<Text fz={15} fw={700} c="edr-text">
|
||||||
|
{incomplete ? "Complete Your Profile" : "Setup your Company Profile"}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
<Text fz={13} c="edr-muted" mb={12}>
|
<Text fz={13} c="edr-muted" mb={12}>
|
||||||
Complete your company information to unlock all features and start
|
{incomplete
|
||||||
booking shipments.
|
? "Your company profile is incomplete. Fill in the missing details to unlock all features."
|
||||||
|
: "Complete your company information to unlock all features and start booking shipments."}
|
||||||
</Text>
|
</Text>
|
||||||
<Link to="/settings" className="no-underline">
|
<Link to="/settings" className="no-underline">
|
||||||
<Group gap={8} align="center" className="w-fit">
|
<Group gap={8} align="center" className="w-fit">
|
||||||
<Text fz={13} fw={600} c="edr-green.7">
|
<Text fz={13} fw={600} c="edr-green.7">
|
||||||
Complete Setup
|
{incomplete ? "Complete Profile" : "Complete Setup"}
|
||||||
</Text>
|
</Text>
|
||||||
<ArrowRight size={16} color={cv("edr-green.7")} />
|
<ArrowRight size={16} color={cv("edr-green.7")} />
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,46 +1,51 @@
|
|||||||
import { useSearchParams } from "react-router-dom";
|
import { api } from "@/services/api";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import type { ProfileResponse } from "@/types/profile";
|
||||||
import {
|
import {
|
||||||
|
Alert,
|
||||||
|
Card,
|
||||||
|
Center,
|
||||||
Container,
|
Container,
|
||||||
Group,
|
Group,
|
||||||
Stack,
|
|
||||||
Title,
|
|
||||||
Text,
|
|
||||||
Tabs,
|
|
||||||
Card,
|
|
||||||
TextInput,
|
|
||||||
Button,
|
|
||||||
Badge,
|
|
||||||
Alert,
|
|
||||||
Center,
|
|
||||||
Loader,
|
Loader,
|
||||||
Grid,
|
Tabs,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
Building2,
|
|
||||||
Briefcase,
|
Briefcase,
|
||||||
CheckCircle2,
|
Building2,
|
||||||
FileCheck,
|
FileCheck,
|
||||||
Save,
|
|
||||||
User,
|
User,
|
||||||
UserCheck,
|
UserCheck,
|
||||||
XCircle,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||||
import { z } from "zod";
|
|
||||||
import { api } from "@/services/api";
|
|
||||||
import type { CreateCompanyPayload } from "@/services/companies.service";
|
|
||||||
import PhoneInput from "@/components/auth/PhoneInput";
|
|
||||||
import TabCompanyProfile from "./settings/TabCompanyProfile";
|
import TabCompanyProfile from "./settings/TabCompanyProfile";
|
||||||
import TabContactPerson from "./settings/TabContactPerson";
|
import TabContactPerson from "./settings/TabContactPerson";
|
||||||
|
import TabDocuments from "./settings/TabDocuments";
|
||||||
import TabGeneralManager from "./settings/TabGeneralManager";
|
import TabGeneralManager from "./settings/TabGeneralManager";
|
||||||
import TabPowerOfAttorney from "./settings/TabPowerOfAttorney";
|
import TabPowerOfAttorney from "./settings/TabPowerOfAttorney";
|
||||||
import TabDocuments from "./settings/TabDocuments";
|
|
||||||
|
|
||||||
type SettingsTab = "company" | "contact" | "gm" | "poa" | "documents";
|
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 }[] = [
|
const TABS: { id: SettingsTab; label: string; icon: React.ReactNode }[] = [
|
||||||
{ id: "company", label: "Company Profile", icon: <Building2 size={16} /> },
|
{ id: "company", label: "Company Profile", icon: <Building2 size={16} /> },
|
||||||
{ id: "contact", label: "Contact Person", icon: <User size={16} /> },
|
{ id: "contact", label: "Contact Person", icon: <User size={16} /> },
|
||||||
@@ -49,72 +54,68 @@ const TABS: { id: SettingsTab; label: string; icon: React.ReactNode }[] = [
|
|||||||
{ id: "documents", label: "Documents", icon: <FileCheck size={16} /> },
|
{ id: "documents", label: "Documents", icon: <FileCheck size={16} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const TAB_ORDER: SettingsTab[] = [
|
||||||
|
"company",
|
||||||
|
"contact",
|
||||||
|
"gm",
|
||||||
|
"poa",
|
||||||
|
"documents",
|
||||||
|
];
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const queryClient = useQueryClient();
|
const navigate = useNavigate();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const tab = (searchParams.get("tab") as SettingsTab) || "company";
|
const tab = (searchParams.get("tab") as SettingsTab) || "company";
|
||||||
const setTab = (t: SettingsTab) => {
|
const setTab = useCallback(
|
||||||
setSearchParams(
|
(t: SettingsTab) => {
|
||||||
(prev) => {
|
setSearchParams(
|
||||||
const next = new URLSearchParams(prev);
|
(prev) => {
|
||||||
next.set("tab", t);
|
const next = new URLSearchParams(prev);
|
||||||
return next;
|
next.set("tab", t);
|
||||||
},
|
return next;
|
||||||
{ replace: true },
|
},
|
||||||
);
|
{ replace: true },
|
||||||
};
|
);
|
||||||
|
},
|
||||||
|
[setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const profileQuery = useQuery(api.companies.getProfile.queryOptions());
|
const profileQuery = useQuery(
|
||||||
|
api.companies.getProfile.queryOptions({
|
||||||
|
retry: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const profile = profileQuery.data;
|
const profile = profileQuery.data;
|
||||||
|
|
||||||
const createCompanyMutation = useMutation({
|
const [isOnboarding, setIsOnboarding] = useState<boolean | null>(null);
|
||||||
mutationFn: (payload: CreateCompanyPayload) =>
|
|
||||||
api.companies.create.call(payload),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: api.companies.getProfile.queryKey(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onboardingSchema = z.object({
|
useEffect(() => {
|
||||||
companyName: z.string().min(1, "Company name is required"),
|
if (profileQuery.isFetched && isOnboarding === null) {
|
||||||
companyEmail: z.string().email("Invalid email address"),
|
setIsOnboarding(!profileQuery.data);
|
||||||
companyPhone: z.string().min(1, "Company phone is required"),
|
}
|
||||||
companyPhoneCountryCode: z.string().min(1, "Country code is required"),
|
}, [profileQuery.isFetched, profileQuery.data, isOnboarding]);
|
||||||
companyLocation: z.string().min(1, "Location is required"),
|
|
||||||
companyAddress: z.string().min(1, "Address is required"),
|
|
||||||
tinNumber: z.string().length(10, "TIN must be exactly 10 digits"),
|
|
||||||
fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
|
|
||||||
});
|
|
||||||
|
|
||||||
type OnboardingFormData = z.infer<typeof onboardingSchema>;
|
const handleOnboardingSuccess = useCallback(() => {
|
||||||
|
setTab("contact");
|
||||||
|
}, [setTab]);
|
||||||
|
|
||||||
const {
|
const handleContactContinue = useCallback(() => {
|
||||||
register,
|
setTab("gm");
|
||||||
handleSubmit,
|
}, [setTab]);
|
||||||
formState: { errors },
|
|
||||||
} = useForm<OnboardingFormData>({
|
|
||||||
resolver: zodResolver(onboardingSchema),
|
|
||||||
defaultValues: {
|
|
||||||
companyPhoneCountryCode: "+251",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmitOnboarding = (data: OnboardingFormData) => {
|
const handleGMContinue = useCallback(() => {
|
||||||
const payload: CreateCompanyPayload = {
|
setTab("poa");
|
||||||
companyType: "customer",
|
}, [setTab]);
|
||||||
companyName: data.companyName,
|
|
||||||
companyEmail: data.companyEmail,
|
const handlePOAContinue = useCallback(() => {
|
||||||
companyPhone: `${data.companyPhoneCountryCode}${data.companyPhone}`,
|
setTab("documents");
|
||||||
companyLocation: data.companyLocation,
|
}, [setTab]);
|
||||||
companyAddress: data.companyAddress,
|
|
||||||
tin: data.tinNumber,
|
const handleDocumentsContinue = useCallback(() => {
|
||||||
fanNumber: data.fanNumber,
|
navigate("/portal");
|
||||||
};
|
}, [navigate]);
|
||||||
createCompanyMutation.mutate(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (profileQuery.isPending) {
|
if (profileQuery.isPending) {
|
||||||
return (
|
return (
|
||||||
@@ -124,25 +125,54 @@ export default function SettingsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Container size="xl" py="xl">
|
<Container size="xl" px="lg">
|
||||||
<Group justify="space-between" mb="xl">
|
<Group justify="space-between" mb="xl">
|
||||||
<div>
|
<div>
|
||||||
<Title order={1} size="h2">
|
<Title order={1} size="h2">
|
||||||
Account Settings
|
{onboarding ? "Complete Your Profile" : "Account Settings"}
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="edr-muted" size="sm" mt={4}>
|
<Text c="edr-muted" size="sm" mt={4}>
|
||||||
Manage your company profile, personnel, and documents
|
{onboarding
|
||||||
|
? "Set up your company profile, personnel, and documents to get started"
|
||||||
|
: "Manage your company profile, personnel, and documents"}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
{profile && <Badge color="edr-green">Verified</Badge>}
|
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (!profile && value !== "company") return;
|
// if (onboarding) return;
|
||||||
setTab(value as SettingsTab);
|
setTab(value as SettingsTab);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -152,7 +182,12 @@ export default function SettingsPage() {
|
|||||||
key={t.id}
|
key={t.id}
|
||||||
value={t.id}
|
value={t.id}
|
||||||
leftSection={t.icon}
|
leftSection={t.icon}
|
||||||
disabled={!profile && t.id !== "company"}
|
disabled={!onboarding && !profile && t.id !== "company"}
|
||||||
|
rightSection={
|
||||||
|
!onboarding && profile && tabIncomplete(t.id, profile) ? (
|
||||||
|
<AlertCircle size={14} color="red" />
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t.label}
|
{t.label}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
@@ -161,201 +196,52 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
<Tabs.Panel value="company">
|
<Tabs.Panel value="company">
|
||||||
{!profile ? (
|
{!profile ? (
|
||||||
<Card padding="lg">
|
<TabCompanyProfile
|
||||||
<Stack gap="md">
|
mode="create"
|
||||||
<Group gap="sm">
|
onCreateSuccess={handleOnboardingSuccess}
|
||||||
<Building2 size={20} />
|
/>
|
||||||
<Title order={3}>Company Profile</Title>
|
|
||||||
</Group>
|
|
||||||
<Text c="edr-muted" size="sm">
|
|
||||||
Enter your company registration details to get started
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmitOnboarding)}>
|
|
||||||
<Stack gap="md" mt="lg">
|
|
||||||
<TextInput
|
|
||||||
label="Company Name"
|
|
||||||
placeholder="Global Logistics Ltd"
|
|
||||||
error={errors.companyName?.message}
|
|
||||||
{...register("companyName")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<TextInput
|
|
||||||
label="Company Email"
|
|
||||||
type="email"
|
|
||||||
placeholder="ops@company.com"
|
|
||||||
error={errors.companyEmail?.message}
|
|
||||||
{...register("companyEmail")}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<PhoneInput
|
|
||||||
countryCode={{ ...register("companyPhoneCountryCode") }}
|
|
||||||
phone={{
|
|
||||||
...register("companyPhone"),
|
|
||||||
placeholder: "912345678",
|
|
||||||
}}
|
|
||||||
countryCodeError={errors.companyPhoneCountryCode}
|
|
||||||
phoneError={errors.companyPhone}
|
|
||||||
label="Company Phone"
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<TextInput
|
|
||||||
label="Location"
|
|
||||||
placeholder="Addis Ababa, Ethiopia"
|
|
||||||
error={errors.companyLocation?.message}
|
|
||||||
{...register("companyLocation")}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<TextInput
|
|
||||||
label="Address"
|
|
||||||
placeholder="Bole Subcity, Woreda 03"
|
|
||||||
error={errors.companyAddress?.message}
|
|
||||||
{...register("companyAddress")}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<TextInput
|
|
||||||
label="TIN Number (10 digits)"
|
|
||||||
placeholder="1234567890"
|
|
||||||
maxLength={10}
|
|
||||||
error={errors.tinNumber?.message}
|
|
||||||
{...register("tinNumber")}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col span={6}>
|
|
||||||
<TextInput
|
|
||||||
label="FAN Number (16 digits)"
|
|
||||||
placeholder="1234567890123456"
|
|
||||||
maxLength={16}
|
|
||||||
error={errors.fanNumber?.message}
|
|
||||||
{...register("fanNumber")}
|
|
||||||
/>
|
|
||||||
</Grid.Col>
|
|
||||||
</Grid>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Group
|
|
||||||
justify="space-between"
|
|
||||||
mt="xl"
|
|
||||||
pt="md"
|
|
||||||
style={{
|
|
||||||
borderTop: "1px solid var(--mantine-color-edr-border-0)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group gap="xs">
|
|
||||||
{createCompanyMutation.isSuccess && (
|
|
||||||
<Group gap={6} c="green">
|
|
||||||
<CheckCircle2 size={16} />
|
|
||||||
<Text size="sm" fw={500}>
|
|
||||||
Profile created successfully
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
{createCompanyMutation.isError && (
|
|
||||||
<Group gap={6} c="red">
|
|
||||||
<XCircle size={16} />
|
|
||||||
<Text size="sm" fw={500}>
|
|
||||||
Failed to create profile
|
|
||||||
</Text>
|
|
||||||
</Group>
|
|
||||||
)}
|
|
||||||
</Group>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
leftSection={<Save size={16} />}
|
|
||||||
loading={createCompanyMutation.isPending}
|
|
||||||
>
|
|
||||||
Create Profile
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
) : (
|
) : (
|
||||||
<TabCompanyProfile profile={profile} />
|
<TabCompanyProfile mode="edit" profile={profile} />
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="contact">
|
<Tabs.Panel value="contact">
|
||||||
{profile ? (
|
{renderProfileContent(
|
||||||
<TabContactPerson profile={profile} />
|
<TabContactPerson
|
||||||
) : (
|
profile={profile!}
|
||||||
<Card padding="xl">
|
mode={onboarding ? "onboarding" : "edit"}
|
||||||
<Center>
|
onContinue={handleContactContinue}
|
||||||
<Alert
|
/>,
|
||||||
icon={<AlertCircle size={24} />}
|
|
||||||
color="gray"
|
|
||||||
variant="light"
|
|
||||||
>
|
|
||||||
Please complete the company profile first.
|
|
||||||
</Alert>
|
|
||||||
</Center>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="gm">
|
<Tabs.Panel value="gm">
|
||||||
{profile ? (
|
{renderProfileContent(
|
||||||
<TabGeneralManager profile={profile} />
|
<TabGeneralManager
|
||||||
) : (
|
profile={profile!}
|
||||||
<Card padding="xl">
|
mode={onboarding ? "onboarding" : "edit"}
|
||||||
<Center>
|
onContinue={handleGMContinue}
|
||||||
<Alert
|
/>,
|
||||||
icon={<AlertCircle size={24} />}
|
|
||||||
color="gray"
|
|
||||||
variant="light"
|
|
||||||
>
|
|
||||||
Please complete the company profile first.
|
|
||||||
</Alert>
|
|
||||||
</Center>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="poa">
|
<Tabs.Panel value="poa">
|
||||||
{profile ? (
|
{renderProfileContent(
|
||||||
<TabPowerOfAttorney profile={profile} />
|
<TabPowerOfAttorney
|
||||||
) : (
|
profile={profile!}
|
||||||
<Card padding="xl">
|
mode={onboarding ? "onboarding" : "edit"}
|
||||||
<Center>
|
onContinue={handlePOAContinue}
|
||||||
<Alert
|
/>,
|
||||||
icon={<AlertCircle size={24} />}
|
|
||||||
color="gray"
|
|
||||||
variant="light"
|
|
||||||
>
|
|
||||||
Please complete the company profile first.
|
|
||||||
</Alert>
|
|
||||||
</Center>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
||||||
<Tabs.Panel value="documents">
|
<Tabs.Panel value="documents">
|
||||||
{profile ? (
|
{renderProfileContent(
|
||||||
<TabDocuments profile={profile} />
|
<TabDocuments
|
||||||
) : (
|
profile={profile!}
|
||||||
<Card padding="xl">
|
mode={onboarding ? "onboarding" : "edit"}
|
||||||
<Center>
|
onContinue={handleDocumentsContinue}
|
||||||
<Alert
|
/>,
|
||||||
icon={<AlertCircle size={24} />}
|
|
||||||
color="gray"
|
|
||||||
variant="light"
|
|
||||||
>
|
|
||||||
Please complete the company profile first.
|
|
||||||
</Alert>
|
|
||||||
</Center>
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ import {
|
|||||||
import { api } from "@/services/api";
|
import { api } from "@/services/api";
|
||||||
import PhoneInput from "@/components/auth/PhoneInput";
|
import PhoneInput from "@/components/auth/PhoneInput";
|
||||||
import type { ProfileResponse } from "@/types/profile";
|
import type { ProfileResponse } from "@/types/profile";
|
||||||
|
import type { CreateCompanyPayload } from "@/services/companies.service";
|
||||||
|
|
||||||
const schema = z.object({
|
export const COMPANY_PROFILE_SCHEMA = z.object({
|
||||||
companyName: z.string().min(1, "Company name is required"),
|
companyName: z.string().min(1, "Company name is required"),
|
||||||
companyEmail: z.string().email("Invalid email address"),
|
companyEmail: z.string().email("Invalid email address"),
|
||||||
companyPhone: z.string().min(1, "Company phone is required"),
|
companyPhone: z.string().min(1, "Company phone is required"),
|
||||||
@@ -29,29 +30,52 @@ const schema = z.object({
|
|||||||
fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
|
fanNumber: z.string().length(16, "FAN must be exactly 16 digits"),
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = z.infer<typeof schema>;
|
export type CompanyProfileFormData = z.infer<typeof COMPANY_PROFILE_SCHEMA>;
|
||||||
|
|
||||||
function splitPhone(fullPhone?: string | null) {
|
export function splitPhone(fullPhone?: string | null) {
|
||||||
if (!fullPhone) return { code: "+251", number: "" };
|
if (!fullPhone) return { code: "+251", number: "" };
|
||||||
const match = fullPhone.match(/^(\+\d{1,3})(.*)$/);
|
const match = fullPhone.match(/^(\+\d{1,3})(.*)$/);
|
||||||
if (match) return { code: match[1], number: match[2] };
|
if (match) return { code: match[1], number: match[2] };
|
||||||
return { code: "+251", number: fullPhone };
|
return { code: "+251", number: fullPhone };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TabCompanyProfile({ profile }: { profile: ProfileResponse }) {
|
interface TabCompanyProfileProps {
|
||||||
const queryClient = useQueryClient();
|
profile?: ProfileResponse;
|
||||||
|
mode?: "edit" | "create";
|
||||||
|
onCreateSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
const defaultValues = useMemo((): FormData => {
|
export default function TabCompanyProfile({
|
||||||
const phone = splitPhone(profile.companyPhone);
|
profile,
|
||||||
|
mode = "edit",
|
||||||
|
onCreateSuccess,
|
||||||
|
}: TabCompanyProfileProps) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const isCreate = mode === "create";
|
||||||
|
|
||||||
|
const defaultValues = useMemo((): CompanyProfileFormData => {
|
||||||
|
if (profile) {
|
||||||
|
const phone = splitPhone(profile.companyPhone);
|
||||||
|
return {
|
||||||
|
companyName: profile.companyName,
|
||||||
|
companyEmail: profile.companyEmail ?? "",
|
||||||
|
companyPhone: phone.number,
|
||||||
|
companyPhoneCountryCode: phone.code,
|
||||||
|
companyLocation: profile.companyLocation,
|
||||||
|
companyAddress: profile.companyAddress ?? "",
|
||||||
|
tinNumber: profile.tinNumber,
|
||||||
|
fanNumber: profile.fanNumber ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
companyName: profile.companyName,
|
companyName: "",
|
||||||
companyEmail: profile.companyEmail ?? "",
|
companyEmail: "",
|
||||||
companyPhone: phone.number,
|
companyPhone: "",
|
||||||
companyPhoneCountryCode: phone.code,
|
companyPhoneCountryCode: "+251",
|
||||||
companyLocation: profile.companyLocation,
|
companyLocation: "",
|
||||||
companyAddress: profile.companyAddress ?? "",
|
companyAddress: "",
|
||||||
tinNumber: profile.tinNumber,
|
tinNumber: "",
|
||||||
fanNumber: profile.fanNumber ?? "",
|
fanNumber: "",
|
||||||
};
|
};
|
||||||
}, [profile]);
|
}, [profile]);
|
||||||
|
|
||||||
@@ -60,14 +84,15 @@ export default function TabCompanyProfile({ profile }: { profile: ProfileRespons
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isDirty },
|
formState: { errors, isDirty },
|
||||||
} = useForm<FormData>({
|
} = useForm<CompanyProfileFormData>({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(COMPANY_PROFILE_SCHEMA),
|
||||||
values: defaultValues,
|
values: defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (data: FormData) =>
|
mutationFn: async (data: CompanyProfileFormData) => {
|
||||||
api.companies.updateProfile.call({
|
const payload: CreateCompanyPayload = {
|
||||||
|
companyType: "customer",
|
||||||
companyName: data.companyName,
|
companyName: data.companyName,
|
||||||
companyEmail: data.companyEmail,
|
companyEmail: data.companyEmail,
|
||||||
companyPhone: `${data.companyPhoneCountryCode}${data.companyPhone}`,
|
companyPhone: `${data.companyPhoneCountryCode}${data.companyPhone}`,
|
||||||
@@ -75,13 +100,25 @@ export default function TabCompanyProfile({ profile }: { profile: ProfileRespons
|
|||||||
companyAddress: data.companyAddress,
|
companyAddress: data.companyAddress,
|
||||||
tin: data.tinNumber,
|
tin: data.tinNumber,
|
||||||
fanNumber: data.fanNumber,
|
fanNumber: data.fanNumber,
|
||||||
}),
|
};
|
||||||
|
|
||||||
|
if (isCreate) {
|
||||||
|
return api.companies.create.call(payload);
|
||||||
|
} else {
|
||||||
|
return api.companies.updateProfile.call(payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: api.companies.getProfile.queryKey(),
|
||||||
|
});
|
||||||
|
if (isCreate) {
|
||||||
|
onCreateSuccess?.();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (data: FormData) => mutation.mutate(data);
|
const onSubmit = (data: CompanyProfileFormData) => mutation.mutate(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card padding="lg">
|
<Card padding="lg">
|
||||||
@@ -90,7 +127,9 @@ export default function TabCompanyProfile({ profile }: { profile: ProfileRespons
|
|||||||
<Title order={3}>Company Profile</Title>
|
<Title order={3}>Company Profile</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Text c="edr-muted" size="sm" mb="lg">
|
<Text c="edr-muted" size="sm" mb="lg">
|
||||||
Edit your company registration details
|
{isCreate
|
||||||
|
? "Enter your company registration details to get started"
|
||||||
|
: "Edit your company registration details"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
@@ -115,7 +154,10 @@ export default function TabCompanyProfile({ profile }: { profile: ProfileRespons
|
|||||||
<Grid.Col span={6}>
|
<Grid.Col span={6}>
|
||||||
<PhoneInput
|
<PhoneInput
|
||||||
countryCode={{ ...register("companyPhoneCountryCode") }}
|
countryCode={{ ...register("companyPhoneCountryCode") }}
|
||||||
phone={{ ...register("companyPhone"), placeholder: "912345678" }}
|
phone={{
|
||||||
|
...register("companyPhone"),
|
||||||
|
placeholder: "912345678",
|
||||||
|
}}
|
||||||
countryCodeError={errors.companyPhoneCountryCode}
|
countryCodeError={errors.companyPhoneCountryCode}
|
||||||
phoneError={errors.companyPhone}
|
phoneError={errors.companyPhone}
|
||||||
label="Company Phone"
|
label="Company Phone"
|
||||||
@@ -171,34 +213,40 @@ export default function TabCompanyProfile({ profile }: { profile: ProfileRespons
|
|||||||
style={{ borderTop: "1px solid var(--mantine-color-edr-border-0)" }}
|
style={{ borderTop: "1px solid var(--mantine-color-edr-border-0)" }}
|
||||||
>
|
>
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
{mutation.isSuccess && (
|
{mutation.isSuccess && !isCreate && (
|
||||||
<Group gap={6} c="green">
|
<Group gap={6} c="green">
|
||||||
<CheckCircle2 size={16} />
|
<CheckCircle2 size={16} />
|
||||||
<Text size="sm" fw={500}>Saved successfully</Text>
|
<Text size="sm" fw={500}>
|
||||||
|
Saved successfully
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{mutation.isError && (
|
{mutation.isError && (
|
||||||
<Group gap={6} c="red">
|
<Group gap={6} c="red">
|
||||||
<XCircle size={16} />
|
<XCircle size={16} />
|
||||||
<Text size="sm" fw={500}>Save failed</Text>
|
<Text size="sm" fw={500}>
|
||||||
|
{isCreate ? "Failed to create profile" : "Save failed"}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<Button
|
{!isCreate && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
disabled={mutation.isPending || !isDirty}
|
variant="outline"
|
||||||
onClick={() => reset()}
|
disabled={mutation.isPending || !isDirty}
|
||||||
>
|
onClick={() => reset()}
|
||||||
Reset
|
>
|
||||||
</Button>
|
Reset
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
leftSection={<Save size={16} />}
|
leftSection={<Save size={16} />}
|
||||||
loading={mutation.isPending}
|
loading={mutation.isPending}
|
||||||
>
|
>
|
||||||
Save Changes
|
{isCreate ? "Continue" : "Save Changes"}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -32,7 +32,13 @@ function splitPhone(fullPhone?: string | null) {
|
|||||||
return { code: "+251", number: fullPhone };
|
return { code: "+251", number: fullPhone };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TabContactPerson({ profile }: { profile: ProfileResponse }) {
|
interface TabContactPersonProps {
|
||||||
|
profile: ProfileResponse;
|
||||||
|
mode?: "edit" | "onboarding";
|
||||||
|
onContinue?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TabContactPerson({ profile, mode = "edit", onContinue }: TabContactPersonProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const defaultValues = useMemo((): FormData => {
|
const defaultValues = useMemo((): FormData => {
|
||||||
@@ -62,6 +68,7 @@ export default function TabContactPerson({ profile }: { profile: ProfileResponse
|
|||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
||||||
|
if (mode === "onboarding") onContinue?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,20 +123,22 @@ export default function TabContactPerson({ profile }: { profile: ProfileResponse
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<Button
|
{mode === "edit" && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
disabled={mutation.isPending || !isDirty}
|
variant="outline"
|
||||||
onClick={() => reset()}
|
disabled={mutation.isPending || !isDirty}
|
||||||
>
|
onClick={() => reset()}
|
||||||
Reset
|
>
|
||||||
</Button>
|
Reset
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
leftSection={<Save size={16} />}
|
leftSection={<Save size={16} />}
|
||||||
loading={mutation.isPending}
|
loading={mutation.isPending}
|
||||||
>
|
>
|
||||||
Save Changes
|
{mode === "onboarding" ? "Continue" : "Save Changes"}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
|
ArrowRight,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
FileCheck,
|
FileCheck,
|
||||||
Loader2,
|
Loader2,
|
||||||
@@ -20,7 +21,13 @@ import { companiesService } from "@/services/companies.service";
|
|||||||
import { SmartFileInput } from "@edr/ui-common";
|
import { SmartFileInput } from "@edr/ui-common";
|
||||||
import type { ProfileResponse } from "@/types/profile";
|
import type { ProfileResponse } from "@/types/profile";
|
||||||
|
|
||||||
export default function TabDocuments({ profile }: { profile: ProfileResponse }) {
|
interface TabDocumentsProps {
|
||||||
|
profile: ProfileResponse;
|
||||||
|
mode?: "edit" | "onboarding";
|
||||||
|
onContinue?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TabDocuments({ profile, mode = "edit", onContinue }: TabDocumentsProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [documentFiles, setDocumentFiles] = useState<Record<string, File | File[] | null>>({});
|
const [documentFiles, setDocumentFiles] = useState<Record<string, File | File[] | null>>({});
|
||||||
|
|
||||||
@@ -75,7 +82,9 @@ export default function TabDocuments({ profile }: { profile: ProfileResponse })
|
|||||||
{docUploadMutation.isSuccess && (
|
{docUploadMutation.isSuccess && (
|
||||||
<Group gap={6} c="green">
|
<Group gap={6} c="green">
|
||||||
<CheckCircle2 size={16} />
|
<CheckCircle2 size={16} />
|
||||||
<Text size="sm" fw={500}>Documents uploaded successfully</Text>
|
<Text size="sm" fw={500}>
|
||||||
|
{mode === "onboarding" ? "Saved successfully" : "Documents uploaded successfully"}
|
||||||
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{docUploadMutation.isError && (
|
{docUploadMutation.isError && (
|
||||||
@@ -85,14 +94,37 @@ export default function TabDocuments({ profile }: { profile: ProfileResponse })
|
|||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Button
|
{mode === "onboarding" ? (
|
||||||
type="button"
|
<Button
|
||||||
leftSection={<UploadCloud size={16} />}
|
type="button"
|
||||||
loading={docUploadMutation.isPending}
|
leftSection={<ArrowRight size={16} />}
|
||||||
onClick={() => docUploadMutation.mutate(documentFiles)}
|
loading={docUploadMutation.isPending}
|
||||||
>
|
onClick={() => {
|
||||||
Upload Documents
|
const hasFiles = Object.values(documentFiles).some((f) => f !== null);
|
||||||
</Button>
|
if (hasFiles) {
|
||||||
|
docUploadMutation.mutate(documentFiles, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
||||||
|
onContinue?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
onContinue?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
leftSection={<UploadCloud size={16} />}
|
||||||
|
loading={docUploadMutation.isPending}
|
||||||
|
onClick={() => docUploadMutation.mutate(documentFiles)}
|
||||||
|
>
|
||||||
|
Upload Documents
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ function splitPhone(fullPhone?: string | null) {
|
|||||||
return { code: "+251", number: fullPhone };
|
return { code: "+251", number: fullPhone };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TabGeneralManager({ profile }: { profile: ProfileResponse }) {
|
interface TabGeneralManagerProps {
|
||||||
|
profile: ProfileResponse;
|
||||||
|
mode?: "edit" | "onboarding";
|
||||||
|
onContinue?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TabGeneralManager({ profile, mode = "edit", onContinue }: TabGeneralManagerProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const defaultValues = useMemo((): FormData => {
|
const defaultValues = useMemo((): FormData => {
|
||||||
@@ -66,6 +72,7 @@ export default function TabGeneralManager({ profile }: { profile: ProfileRespons
|
|||||||
}),
|
}),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
queryClient.invalidateQueries({ queryKey: api.companies.getProfile.queryKey() });
|
||||||
|
if (mode === "onboarding") onContinue?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,20 +140,22 @@ export default function TabGeneralManager({ profile }: { profile: ProfileRespons
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<Button
|
{mode === "edit" && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
disabled={mutation.isPending || !isDirty}
|
variant="outline"
|
||||||
onClick={() => reset()}
|
disabled={mutation.isPending || !isDirty}
|
||||||
>
|
onClick={() => reset()}
|
||||||
Reset
|
>
|
||||||
</Button>
|
Reset
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
leftSection={<Save size={16} />}
|
leftSection={<Save size={16} />}
|
||||||
loading={mutation.isPending}
|
loading={mutation.isPending}
|
||||||
>
|
>
|
||||||
Save Changes
|
{mode === "onboarding" ? "Continue" : "Save Changes"}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
@@ -36,11 +36,17 @@ function splitPhone(fullPhone?: string | null) {
|
|||||||
return { code: "+251", number: fullPhone };
|
return { code: "+251", number: fullPhone };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TabPowerOfAttorneyProps {
|
||||||
|
profile: ProfileResponse;
|
||||||
|
mode?: "edit" | "onboarding";
|
||||||
|
onContinue?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default function TabPowerOfAttorney({
|
export default function TabPowerOfAttorney({
|
||||||
profile,
|
profile,
|
||||||
}: {
|
mode = "edit",
|
||||||
profile: ProfileResponse;
|
onContinue,
|
||||||
}) {
|
}: TabPowerOfAttorneyProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const defaultValues = useMemo((): FormData => {
|
const defaultValues = useMemo((): FormData => {
|
||||||
@@ -49,7 +55,7 @@ export default function TabPowerOfAttorney({
|
|||||||
poaName: profile.poaName ?? "",
|
poaName: profile.poaName ?? "",
|
||||||
poaEmail: profile.poaEmail ?? "",
|
poaEmail: profile.poaEmail ?? "",
|
||||||
poaPhone: phone.number,
|
poaPhone: phone.number,
|
||||||
poaPhoneCountryCode: profile.poaPhone ? phone.code : "",
|
poaPhoneCountryCode: profile.poaPhone ? phone.code : "+251",
|
||||||
poaLocation: profile.poaLocation ?? "",
|
poaLocation: profile.poaLocation ?? "",
|
||||||
poaAddress: profile.poaAddress ?? "",
|
poaAddress: profile.poaAddress ?? "",
|
||||||
};
|
};
|
||||||
@@ -81,6 +87,7 @@ export default function TabPowerOfAttorney({
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: api.companies.getProfile.queryKey(),
|
queryKey: api.companies.getProfile.queryKey(),
|
||||||
});
|
});
|
||||||
|
if (mode === "onboarding") onContinue?.();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,11 +106,6 @@ export default function TabPowerOfAttorney({
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text c="edr-muted" size="sm">
|
|
||||||
Power of Attorney details are optional. Fill them in if you have an
|
|
||||||
authorized representative, or leave blank.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
label="PoA Full Name"
|
label="PoA Full Name"
|
||||||
placeholder="Authorized Representative Name"
|
placeholder="Authorized Representative Name"
|
||||||
@@ -177,20 +179,22 @@ export default function TabPowerOfAttorney({
|
|||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
<Button
|
{mode === "edit" && (
|
||||||
type="button"
|
<Button
|
||||||
variant="outline"
|
type="button"
|
||||||
disabled={mutation.isPending || !isDirty}
|
variant="outline"
|
||||||
onClick={() => reset()}
|
disabled={mutation.isPending || !isDirty}
|
||||||
>
|
onClick={() => reset()}
|
||||||
Reset
|
>
|
||||||
</Button>
|
Reset
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
leftSection={<Save size={16} />}
|
leftSection={<Save size={16} />}
|
||||||
loading={mutation.isPending}
|
loading={mutation.isPending}
|
||||||
>
|
>
|
||||||
Save Changes
|
{mode === "onboarding" ? "Continue" : "Save Changes"}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
Reference in New Issue
Block a user