480 lines
17 KiB
TypeScript
480 lines
17 KiB
TypeScript
import React, { useState } from "react";
|
|
import { View, Pressable, TextInput, StyleSheet } from "react-native";
|
|
import { useSirouRouter } from "@sirou/react-native";
|
|
import { useColorScheme } from "nativewind";
|
|
|
|
import { api } from "@/lib/api";
|
|
import { AppRoutes } from "@/lib/routes";
|
|
import { toast } from "@/lib/toast-store";
|
|
|
|
import { ScreenWrapper } from "@/components/ScreenWrapper";
|
|
import { FormFlow } from "@/components/FormFlow";
|
|
import { Text } from "@/components/ui/text";
|
|
|
|
const S = StyleSheet.create({
|
|
input: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 12,
|
|
fontSize: 12,
|
|
fontWeight: "500",
|
|
borderRadius: 6,
|
|
borderWidth: 1,
|
|
textAlignVertical: "center",
|
|
},
|
|
});
|
|
|
|
function useInputColors() {
|
|
const { colorScheme } = useColorScheme();
|
|
const dark = colorScheme === "dark";
|
|
return {
|
|
bg: dark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)",
|
|
border: dark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)",
|
|
text: dark ? "#f1f5f9" : "#0f172a",
|
|
placeholder: "rgba(100,116,139,0.45)",
|
|
};
|
|
}
|
|
|
|
function Field({
|
|
label,
|
|
value,
|
|
onChangeText,
|
|
placeholder,
|
|
numeric = false,
|
|
flex,
|
|
multiline = false,
|
|
}: {
|
|
label: string;
|
|
value: string;
|
|
onChangeText: (v: string) => void;
|
|
placeholder: string;
|
|
numeric?: boolean;
|
|
flex?: number;
|
|
multiline?: boolean;
|
|
}) {
|
|
const c = useInputColors();
|
|
return (
|
|
<View style={flex != null ? { flex } : undefined}>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
{label}
|
|
</Text>
|
|
<TextInput
|
|
style={[
|
|
S.input,
|
|
{ backgroundColor: c.bg, borderColor: c.border, color: c.text },
|
|
multiline
|
|
? { height: 80, paddingTop: 10, textAlignVertical: "top" }
|
|
: {},
|
|
]}
|
|
placeholder={placeholder}
|
|
placeholderTextColor={c.placeholder}
|
|
value={value}
|
|
onChangeText={onChangeText}
|
|
keyboardType={numeric ? "numeric" : "default"}
|
|
multiline={multiline}
|
|
autoCorrect={false}
|
|
autoCapitalize="none"
|
|
/>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const TYPES = ["INDIVIDUAL", "COMPANY"] as const;
|
|
|
|
const STEPS = [
|
|
{ key: "type", label: "Type" },
|
|
{ key: "details", label: "Details" },
|
|
{ key: "documents", label: "Documents" },
|
|
{ key: "notes", label: "Notes" },
|
|
{ key: "summary", label: "Summary" },
|
|
];
|
|
|
|
export default function CreateCustomerScreen() {
|
|
const nav = useSirouRouter<AppRoutes>();
|
|
|
|
const [step, setStep] = useState(0);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const c = useInputColors();
|
|
|
|
const [type, setType] = useState<"INDIVIDUAL" | "COMPANY">("INDIVIDUAL");
|
|
const [displayName, setDisplayName] = useState("");
|
|
const [firstName, setFirstName] = useState("");
|
|
const [lastName, setLastName] = useState("");
|
|
const [companyName, setCompanyName] = useState("");
|
|
const [email, setEmail] = useState("");
|
|
const [phone, setPhone] = useState("");
|
|
const [tin, setTin] = useState("");
|
|
const [vatReg, setVatReg] = useState("");
|
|
const [businessLicense, setBusinessLicense] = useState("");
|
|
const [address, setAddress] = useState("");
|
|
const [notes, setNotes] = useState("");
|
|
|
|
const handleNext = () => {
|
|
if (step === 0 && !displayName.trim()) {
|
|
toast.error("Validation", "Display name is required");
|
|
return;
|
|
}
|
|
if (step === 1 && type === "INDIVIDUAL" && !firstName.trim()) {
|
|
toast.error("Validation", "First name is required");
|
|
return;
|
|
}
|
|
if (step === 1 && type === "COMPANY" && !companyName.trim()) {
|
|
toast.error("Validation", "Company name is required");
|
|
return;
|
|
}
|
|
setStep(step + 1);
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
const body: Record<string, any> = {
|
|
type,
|
|
displayName,
|
|
email: email || undefined,
|
|
phone: phone ? `+251${phone.replace(/^\+/, "")}` : undefined,
|
|
tin: tin || undefined,
|
|
vatRegistrationNumber: vatReg || undefined,
|
|
businessLicenseNumber: businessLicense || undefined,
|
|
address: address || undefined,
|
|
firstName: firstName || undefined,
|
|
lastName: lastName || undefined,
|
|
companyName: companyName || undefined,
|
|
notes: notes || undefined,
|
|
};
|
|
|
|
Object.keys(body).forEach((k) => body[k] === undefined && delete body[k]);
|
|
|
|
try {
|
|
setSubmitting(true);
|
|
await api.customers.create({ body });
|
|
toast.success("Success", "Customer created successfully!");
|
|
nav.back();
|
|
} catch (err: any) {
|
|
toast.error("Error", err?.message || "Failed to create customer");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<ScreenWrapper className="bg-background">
|
|
<FormFlow
|
|
steps={STEPS}
|
|
currentStep={step}
|
|
onNext={handleNext}
|
|
onBack={() => setStep(step - 1)}
|
|
onComplete={handleSubmit}
|
|
loading={submitting}
|
|
completeLabel="Create Customer"
|
|
>
|
|
{step === 0 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Customer Type
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<View className="flex-row gap-2">
|
|
{TYPES.map((t) => (
|
|
<Pressable
|
|
key={t}
|
|
onPress={() => setType(t)}
|
|
className={`flex-1 py-3 rounded-[6px] items-center border ${
|
|
type === t
|
|
? "bg-primary border-primary"
|
|
: "bg-card border-border"
|
|
}`}
|
|
>
|
|
<Text
|
|
className={`text-[11px] font-sans-bold uppercase tracking-widest ${
|
|
type === t ? "text-white" : "text-foreground"
|
|
}`}
|
|
>
|
|
{t === "INDIVIDUAL" ? "Individual" : "Company"}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
<Field
|
|
label="Display Name"
|
|
value={displayName}
|
|
onChangeText={setDisplayName}
|
|
placeholder="e.g. John Doe or Acme Corp"
|
|
/>
|
|
{type === "INDIVIDUAL" && (
|
|
<Field
|
|
label="Email"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
placeholder="john@example.com"
|
|
/>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 1 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
{type === "INDIVIDUAL" ? "Personal Details" : "Company Details"}
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
{type === "INDIVIDUAL" ? (
|
|
<>
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="First Name"
|
|
value={firstName}
|
|
onChangeText={setFirstName}
|
|
placeholder="John"
|
|
flex={1}
|
|
/>
|
|
<Field
|
|
label="Last Name"
|
|
value={lastName}
|
|
onChangeText={setLastName}
|
|
placeholder="Doe"
|
|
flex={1}
|
|
/>
|
|
</View>
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Phone
|
|
</Text>
|
|
<View className="flex-row items-center h-11 px-3 border border-border rounded-[6px]" style={{ backgroundColor: c.bg, borderColor: c.border }}>
|
|
<Text className="text-foreground font-sans-bold text-xs">+251</Text>
|
|
<TextInput
|
|
className="flex-1 ml-2 text-foreground text-xs font-sans-medium"
|
|
placeholder="912345678"
|
|
placeholderTextColor={c.placeholder}
|
|
value={phone}
|
|
onChangeText={setPhone}
|
|
keyboardType="phone-pad"
|
|
maxLength={9}
|
|
style={{ textAlignVertical: "center" }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Field
|
|
label="Company Name"
|
|
value={companyName}
|
|
onChangeText={setCompanyName}
|
|
placeholder="Acme Corp"
|
|
/>
|
|
<View className="flex-row gap-4">
|
|
<Field
|
|
label="Email"
|
|
value={email}
|
|
onChangeText={setEmail}
|
|
placeholder="info@acme.com"
|
|
flex={1}
|
|
/>
|
|
</View>
|
|
<View>
|
|
<Text className="text-[14px] font-sans-bold mb-1.5 ml-1 text-foreground">
|
|
Phone
|
|
</Text>
|
|
<View className="flex-row items-center h-11 px-3 border border-border rounded-[6px]" style={{ backgroundColor: c.bg, borderColor: c.border }}>
|
|
<Text className="text-foreground font-sans-bold text-xs">+251</Text>
|
|
<TextInput
|
|
className="flex-1 ml-2 text-foreground text-xs font-sans-medium"
|
|
placeholder="912345678"
|
|
placeholderTextColor={c.placeholder}
|
|
value={phone}
|
|
onChangeText={setPhone}
|
|
keyboardType="phone-pad"
|
|
maxLength={9}
|
|
style={{ textAlignVertical: "center" }}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</>
|
|
)}
|
|
<Field
|
|
label="Address"
|
|
value={address}
|
|
onChangeText={setAddress}
|
|
placeholder="e.g. Bole Road, Addis Ababa"
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Documents
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<Field
|
|
label="TIN Number"
|
|
value={tin}
|
|
onChangeText={setTin}
|
|
placeholder="e.g. 1234567890"
|
|
/>
|
|
<Field
|
|
label="VAT Registration"
|
|
value={vatReg}
|
|
onChangeText={setVatReg}
|
|
placeholder="e.g. VAT-12345"
|
|
/>
|
|
<Field
|
|
label="Business License"
|
|
value={businessLicense}
|
|
onChangeText={setBusinessLicense}
|
|
placeholder="e.g. BL-2024-001"
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<View className="gap-5">
|
|
<View className="bg-card rounded-[6px] gap-4">
|
|
<Field
|
|
label="Notes"
|
|
value={notes}
|
|
onChangeText={setNotes}
|
|
placeholder="Any additional notes..."
|
|
multiline
|
|
/>
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{step === 4 && (
|
|
<View className="gap-5">
|
|
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
|
|
Summary
|
|
</Text>
|
|
<View className="bg-card rounded-[6px] p-4 border border-border gap-3">
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Type
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{type === "INDIVIDUAL" ? "Individual" : "Company"}
|
|
</Text>
|
|
</View>
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Display Name
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold text-right flex-1 ml-4">
|
|
{displayName}
|
|
</Text>
|
|
</View>
|
|
{firstName ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
First Name
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{firstName}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{lastName ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Last Name
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{lastName}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{companyName ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Company
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{companyName}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{email ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Email
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{email}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{phone ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Phone
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
+251{phone}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{tin ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
TIN
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{tin}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{vatReg ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
VAT Reg
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{vatReg}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{businessLicense ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Business License
|
|
</Text>
|
|
<Text className="text-[14px] text-foreground font-sans-bold">
|
|
{businessLicense}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{address ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Address
|
|
</Text>
|
|
<Text
|
|
className="text-[14px] text-foreground font-sans-bold text-right flex-1 ml-4"
|
|
numberOfLines={2}
|
|
>
|
|
{address}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
{notes ? (
|
|
<View className="flex-row justify-between">
|
|
<Text className="text-[14px] text-foreground font-sans-medium">
|
|
Notes
|
|
</Text>
|
|
<Text
|
|
className="text-[14px] text-foreground font-sans-bold text-right flex-1 ml-4"
|
|
numberOfLines={2}
|
|
>
|
|
{notes}
|
|
</Text>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
</View>
|
|
)}
|
|
</FormFlow>
|
|
</ScreenWrapper>
|
|
);
|
|
}
|