198 lines
7.5 KiB
TypeScript
198 lines
7.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { AppShell } from "../../components/app-shell";
|
|
|
|
type ApiResponse<T> = {
|
|
data: T;
|
|
meta: { timestamp: string; version: "v1" };
|
|
error: null | { message: string; code?: string };
|
|
};
|
|
|
|
type ProfileData = {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
fullName: string;
|
|
phone?: string | null;
|
|
companyName?: string | null;
|
|
addressLine1i: string | null;
|
|
addressLine2i: string | null;
|
|
city?: string | null;
|
|
state?: string | null;
|
|
postalCode?: string | null;
|
|
country?: string | null;
|
|
};
|
|
};
|
|
|
|
export default function ProfilePage() {
|
|
const [token, setToken] = useState("");
|
|
const [status, setStatus] = useState("");
|
|
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(() => {
|
|
const stored = localStorage.getItem("ledgerone_token") ?? "";
|
|
setToken(stored);
|
|
}, []);
|
|
|
|
const onSubmit = async (event: React.FormEvent) => {
|
|
event.preventDefault();
|
|
if (!token) {
|
|
setStatus("Please sign in to update your profile.");
|
|
return;
|
|
}
|
|
setStatus("Saving profile...");
|
|
try {
|
|
const res = await fetch("/api/auth/profile", {
|
|
method: "PATCH",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({
|
|
fullName,
|
|
phone: phone || undefined,
|
|
companyName: companyName || undefined,
|
|
addressLine1: addressLine1 || undefined,
|
|
addressLine2: addressLine2 || undefined,
|
|
city: city || undefined,
|
|
state: state || undefined,
|
|
postalCode: postalCode || undefined,
|
|
country: country || undefined
|
|
})
|
|
});
|
|
const payload = (await res.json()) as ApiResponse<ProfileData>;
|
|
if (!res.ok || payload.error) {
|
|
setStatus(payload.error?.message ?? "Profile update failed.");
|
|
return;
|
|
}
|
|
setStatus("Profile updated.");
|
|
} catch {
|
|
setStatus("Profile update failed.");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AppShell title="Profile" subtitle="Keep your ledger profile current for exports.">
|
|
<div className="app-card p-10">
|
|
<p className="text-xs uppercase tracking-[0.3em] text-muted">LedgerOne</p>
|
|
<h2 className="mt-4 text-2xl font-semibold">Complete your profile</h2>
|
|
<p className="mt-2 text-sm text-muted">
|
|
Add the details we need to personalize your ledger workspace.
|
|
</p>
|
|
<form className="mt-8 grid gap-5 md:grid-cols-2" onSubmit={onSubmit}>
|
|
<div className="space-y-2 md:col-span-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">
|
|
Full name
|
|
</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={fullName}
|
|
onChange={(event) => setFullName(event.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">Phone</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="tel"
|
|
value={phone}
|
|
onChange={(event) => setPhone(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">
|
|
Company
|
|
</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={companyName}
|
|
onChange={(event) => setCompanyName(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2 md:col-span-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">
|
|
Address line 1
|
|
</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={addressLine1}
|
|
onChange={(event) => setAddressLine1(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2 md:col-span-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">
|
|
Address line 2
|
|
</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={addressLine2}
|
|
onChange={(event) => setAddressLine2(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">City</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={city}
|
|
onChange={(event) => setCity(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">State</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={state}
|
|
onChange={(event) => setState(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">
|
|
Postal code
|
|
</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={postalCode}
|
|
onChange={(event) => setPostalCode(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<label className="text-xs uppercase tracking-[0.2em] text-muted">Country</label>
|
|
<input
|
|
className="w-full rounded-2xl border border-ink/10 bg-white px-4 py-3 text-sm"
|
|
type="text"
|
|
value={country}
|
|
onChange={(event) => setCountry(event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<button
|
|
type="submit"
|
|
className="w-full rounded-2xl bg-ink px-4 py-3 text-sm font-semibold text-haze"
|
|
>
|
|
Save profile
|
|
</button>
|
|
</div>
|
|
</form>
|
|
{status ? <p className="mt-4 text-xs text-muted">{status}</p> : null}
|
|
</div>
|
|
</AppShell>
|
|
);
|
|
}
|