trptk/components/account/AddressesTab.tsx
2026-02-24 17:14:07 +01:00

277 lines
8.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { CountrySelect } from "@/components/CountrySelect";
type Address = {
id: string;
first_name: string;
last_name: string;
address_1: string;
address_2?: string;
city: string;
province?: string;
postal_code: string;
country_code: string;
phone?: string;
};
const inputClass =
"no-ring w-full rounded-xl border border-lightline px-6 py-3 shadow-lg text-lighttext transition-all duration-200 ease-in-out placeholder:text-lightsec hover:border-lightline-hover focus:border-lightline-focus dark:border-darkline dark:text-darktext dark:placeholder:text-darksec dark:hover:border-darkline-hover dark:focus:border-darkline-focus";
const emptyForm = {
first_name: "",
last_name: "",
address_1: "",
address_2: "",
city: "",
province: "",
postal_code: "",
country_code: "nl",
phone: "",
};
export function AddressesTab() {
const [address, setAddress] = useState<Address | null>(null);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
const [form, setForm] = useState(emptyForm);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchAddress();
}, []);
async function fetchAddress() {
try {
const res = await fetch("/api/account/addresses");
if (res.ok) {
const data = await res.json();
const addresses: Address[] = data.addresses ?? [];
setAddress(addresses[0] ?? null);
}
} catch {
// Silently fail
} finally {
setLoading(false);
}
}
function startEdit() {
if (address) {
setForm({
first_name: address.first_name ?? "",
last_name: address.last_name ?? "",
address_1: address.address_1 ?? "",
address_2: address.address_2 ?? "",
city: address.city ?? "",
province: address.province ?? "",
postal_code: address.postal_code ?? "",
country_code: address.country_code ?? "nl",
phone: address.phone ?? "",
});
} else {
setForm(emptyForm);
}
setEditing(true);
setError(null);
}
function cancelForm() {
setEditing(false);
setForm(emptyForm);
setError(null);
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitting(true);
setError(null);
const url = address ? `/api/account/addresses/${address.id}` : "/api/account/addresses";
try {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (!res.ok) {
const data = await res.json().catch(() => ({}));
throw new Error(data.error ?? "Failed to save address");
}
cancelForm();
await fetchAddress();
} catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong");
} finally {
setSubmitting(false);
}
}
function updateField(field: string, value: string) {
setForm((prev) => ({ ...prev, [field]: value }));
}
if (loading) {
return <p className="py-12 text-center text-lightsec dark:text-darksec">Loading address</p>;
}
// Display mode: show the saved address (or empty state) with an Edit / Add button
if (!editing) {
if (address) {
return (
<div>
<p className="font-silkasb">
{address.first_name} {address.last_name}
</p>
<p className="text-sm text-lightsec dark:text-darksec">
{address.address_1}
{address.address_2 ? `, ${address.address_2}` : ""}
</p>
<p className="text-sm text-lightsec dark:text-darksec">
{address.city}
{address.province ? `, ${address.province}` : ""}, {address.postal_code},{" "}
{address.country_code.toUpperCase()}
</p>
{address.phone && (
<p className="text-sm text-lightsec dark:text-darksec">{address.phone}</p>
)}
<div className="mt-3">
<button
type="button"
onClick={startEdit}
className="text-sm text-trptkblue underline transition-opacity hover:opacity-70 dark:text-white"
>
Edit
</button>
</div>
</div>
);
}
return (
<div>
<p className="py-8 text-center text-lightsec dark:text-darksec">
No address saved. Add one to speed up checkout.
</p>
<button
type="button"
onClick={startEdit}
className="w-full rounded-xl border border-dashed border-lightline px-4 py-4 text-sm text-lightsec transition-all duration-200 hover:border-lightline-hover hover:text-trptkblue dark:border-darkline dark:text-darksec dark:hover:border-darkline-hover dark:hover:text-white"
>
+ Add Address
</button>
</div>
);
}
// Edit / Add form
return (
<form
onSubmit={handleSubmit}
className="rounded-2xl border border-lightline p-6 dark:border-darkline"
>
<h3 className="mb-4 font-silkasb text-sm">{address ? "Edit Address" : "Add Address"}</h3>
<div className="flex flex-col gap-3">
<div className="grid gap-3 sm:grid-cols-2">
<input
type="text"
required
placeholder="First name"
value={form.first_name}
onChange={(e) => updateField("first_name", e.target.value)}
className={inputClass}
/>
<input
type="text"
required
placeholder="Last name"
value={form.last_name}
onChange={(e) => updateField("last_name", e.target.value)}
className={inputClass}
/>
</div>
<input
type="text"
required
placeholder="Address"
value={form.address_1}
onChange={(e) => updateField("address_1", e.target.value)}
className={inputClass}
/>
<input
type="text"
placeholder="Apartment, suite, etc. (optional)"
value={form.address_2}
onChange={(e) => updateField("address_2", e.target.value)}
className={inputClass}
/>
<div className="grid gap-3 sm:grid-cols-3">
<input
type="text"
required
placeholder="City"
value={form.city}
onChange={(e) => updateField("city", e.target.value)}
className={inputClass}
/>
<input
type="text"
placeholder="Province / State"
value={form.province}
onChange={(e) => updateField("province", e.target.value)}
className={inputClass}
/>
<input
type="text"
required
placeholder="Postal code"
value={form.postal_code}
onChange={(e) => updateField("postal_code", e.target.value)}
className={inputClass}
/>
</div>
<CountrySelect
value={form.country_code}
onChange={(code) => updateField("country_code", code)}
className={inputClass}
/>
<input
type="tel"
placeholder="Phone (optional)"
value={form.phone}
onChange={(e) => updateField("phone", e.target.value)}
className={inputClass}
/>
</div>
{error && (
<div className="mt-3 rounded-xl border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
{error}
</div>
)}
<div className="mt-4 flex gap-3">
<button
type="submit"
disabled={submitting}
className="rounded-xl bg-trptkblue px-5 py-2.5 font-silkasb text-sm text-white shadow-lg transition-all duration-200 hover:opacity-90 disabled:pointer-events-none disabled:opacity-50 dark:bg-white dark:text-lighttext"
>
{submitting ? "Saving\u2026" : "Save Address"}
</button>
<button
type="button"
onClick={cancelForm}
className="rounded-xl border border-lightline px-5 py-2.5 text-sm transition-all duration-200 hover:border-lightline-hover hover:text-trptkblue dark:border-darkline dark:hover:border-darkline-hover dark:hover:text-white"
>
Cancel
</button>
</div>
</form>
);
}