150 lines
6.0 KiB
TypeScript
150 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { AppShell } from "../../../components/app-shell";
|
|
import { apiFetch } from "@/lib/api";
|
|
|
|
type ProfileData = {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
fullName?: string | null;
|
|
phone?: string | null;
|
|
companyName?: string | null;
|
|
addressLine1?: string | null;
|
|
addressLine2?: string | null;
|
|
city?: string | null;
|
|
state?: string | null;
|
|
postalCode?: string | null;
|
|
country?: string | null;
|
|
};
|
|
};
|
|
|
|
export default function ProfilePage() {
|
|
const [status, setStatus] = useState("");
|
|
const [isError, setIsError] = useState(false);
|
|
const [fullName, setFullName] = useState("");
|
|
const [phone, setPhone] = useState("");
|
|
const [companyName, setCompanyName] = useState("");
|
|
const [addressLine1, setAddressLine1] = useState("");
|
|
const [addressLine2, setAddressLine2] = useState("");
|
|
const [city, setCity] = useState("");
|
|
const [state, setState] = useState("");
|
|
const [postalCode, setPostalCode] = useState("");
|
|
const [country, setCountry] = useState("");
|
|
|
|
useEffect(() => {
|
|
apiFetch<ProfileData>("/api/auth/me")
|
|
.then((res) => {
|
|
if (!res.error && res.data) {
|
|
const u = (res.data as unknown as ProfileData).user;
|
|
if (u) {
|
|
setFullName(u.fullName ?? "");
|
|
setPhone(u.phone ?? "");
|
|
setCompanyName(u.companyName ?? "");
|
|
setAddressLine1(u.addressLine1 ?? "");
|
|
setAddressLine2(u.addressLine2 ?? "");
|
|
setCity(u.city ?? "");
|
|
setState(u.state ?? "");
|
|
setPostalCode(u.postalCode ?? "");
|
|
setCountry(u.country ?? "");
|
|
}
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
}, []);
|
|
|
|
const onSubmit = async (event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
setStatus("Saving profile...");
|
|
setIsError(false);
|
|
const res = await apiFetch<ProfileData>("/api/auth/profile", {
|
|
method: "PATCH",
|
|
body: JSON.stringify({
|
|
fullName: fullName || undefined,
|
|
phone: phone || undefined,
|
|
companyName: companyName || undefined,
|
|
addressLine1: addressLine1 || undefined,
|
|
addressLine2: addressLine2 || undefined,
|
|
city: city || undefined,
|
|
state: state || undefined,
|
|
postalCode: postalCode || undefined,
|
|
country: country || undefined,
|
|
}),
|
|
});
|
|
if (res.error) {
|
|
setStatus(res.error.message ?? "Profile update failed.");
|
|
setIsError(true);
|
|
return;
|
|
}
|
|
setStatus("Profile saved successfully.");
|
|
};
|
|
|
|
const inputCls = "w-full rounded-lg border border-border bg-background/50 px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-primary transition-all";
|
|
const labelCls = "block text-xs font-medium text-muted-foreground mb-1 uppercase tracking-wide";
|
|
|
|
return (
|
|
<AppShell title="Profile" subtitle="Keep your ledger profile current for exports.">
|
|
<div className="max-w-2xl">
|
|
<div className="glass-panel p-8 rounded-2xl">
|
|
<h2 className="text-xl font-bold text-foreground">Personal & Business Details</h2>
|
|
<p className="mt-1 text-sm text-muted-foreground">
|
|
These details appear on tax exports and CSV reports.
|
|
</p>
|
|
<form className="mt-6 grid gap-5 md:grid-cols-2" onSubmit={onSubmit}>
|
|
<div className="md:col-span-2">
|
|
<label className={labelCls}>Full name</label>
|
|
<input className={inputCls} type="text" value={fullName} onChange={(e) => setFullName(e.target.value)} required />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>Phone</label>
|
|
<input className={inputCls} type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>Company</label>
|
|
<input className={inputCls} type="text" value={companyName} onChange={(e) => setCompanyName(e.target.value)} />
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<label className={labelCls}>Address line 1</label>
|
|
<input className={inputCls} type="text" value={addressLine1} onChange={(e) => setAddressLine1(e.target.value)} />
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<label className={labelCls}>Address line 2</label>
|
|
<input className={inputCls} type="text" value={addressLine2} onChange={(e) => setAddressLine2(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>City</label>
|
|
<input className={inputCls} type="text" value={city} onChange={(e) => setCity(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>State</label>
|
|
<input className={inputCls} type="text" value={state} onChange={(e) => setState(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>Postal code</label>
|
|
<input className={inputCls} type="text" value={postalCode} onChange={(e) => setPostalCode(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className={labelCls}>Country</label>
|
|
<input className={inputCls} type="text" value={country} onChange={(e) => setCountry(e.target.value)} />
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<button
|
|
type="submit"
|
|
className="w-full rounded-lg bg-primary py-2.5 px-4 text-sm font-bold text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 transition-all hover:-translate-y-0.5"
|
|
>
|
|
Save profile
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{status && (
|
|
<div className={`mt-4 rounded-lg p-3 text-sm text-center ${isError ? "bg-red-500/10 border border-red-500/20 text-red-400" : "bg-accent/10 border border-accent/20"}`}>
|
|
{status}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</AppShell>
|
|
);
|
|
}
|