'use client'; import { useState } from 'react'; import type { CustomerProfile } from 'brainerce'; import { getClient } from '@/core/lib/brainerce'; import { useTranslations } from '@/core/lib/translations'; import { cn } from '@/core/lib/utils'; import { birthMonthKey, toBirthdayNumber } from '@/core/lib/birthday'; import { BirthdayPicker } from '@/components/shared/birthday-picker'; import { useStoreCapabilities } from '@/core/providers/store-provider'; interface ProfileSectionProps { profile: CustomerProfile; onProfileUpdate?: (updated: CustomerProfile) => void; className?: string; } export function ProfileSection({ profile, onProfileUpdate, className }: ProfileSectionProps) { const t = useTranslations('account'); const tc = useTranslations('common'); const { capabilities } = useStoreCapabilities(); /** Explicit `true` only — see the note on the same gate in `register-form.tsx`. */ const birthdayGiftOn = capabilities?.features.hasBirthdayRewards === true; const [editing, setEditing] = useState(false); const [saving, setSaving] = useState(false); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [form, setForm] = useState({ firstName: profile.firstName || '', lastName: profile.lastName || '', phone: profile.phone || '', // Seeded from the fetched profile so a saved birthday renders back into // the picker instead of the form reopening blank. Held as strings to match // the other fields; '' means no birthday is set. birthMonth: profile.birthMonth ? String(profile.birthMonth) : '', birthDay: profile.birthDay ? String(profile.birthDay) : '', }); const fullName = [profile.firstName, profile.lastName].filter(Boolean).join(' '); const initials = [profile.firstName?.[0], profile.lastName?.[0]].filter(Boolean).join('').toUpperCase() || profile.email[0].toUpperCase(); // Stored birthday for the read-only view. Both halves always arrive together, // and an out-of-range month resolves to null so nothing renders rather than a // raw key path. const storedMonthKey = profile.birthMonth ? birthMonthKey(profile.birthMonth) : null; const storedBirthday = storedMonthKey && profile.birthDay ? tc('birthdayDisplay', { month: tc(storedMonthKey), day: String(profile.birthDay), }) : null; function startEditing() { setForm({ firstName: profile.firstName || '', lastName: profile.lastName || '', phone: profile.phone || '', birthMonth: profile.birthMonth ? String(profile.birthMonth) : '', birthDay: profile.birthDay ? String(profile.birthDay) : '', }); setMessage(null); setEditing(true); } /** * The picker hands back both halves at once, or two nulls when the shopper * clears the field. State stays as strings so the save path below is * unchanged. */ function selectBirthday(month: number | null, day: number | null) { setForm((f) => ({ ...f, birthMonth: month ? String(month) : '', birthDay: day ? String(day) : '', })); setMessage(null); } function cancelEditing() { setEditing(false); setMessage(null); } async function handleSave(e: React.FormEvent) { e.preventDefault(); const birthMonth = toBirthdayNumber(form.birthMonth); const birthDay = toBirthdayNumber(form.birthDay); // Half a birthday is an HTTP 400 at the API, so stop here with something // the shopper can act on instead of showing the generic save failure. if ((birthMonth === null) !== (birthDay === null)) { setMessage({ type: 'error', text: tc('birthdayIncomplete') }); return; } setSaving(true); setMessage(null); try { const client = getClient(); const updated = await client.updateMyProfile({ firstName: form.firstName || undefined, lastName: form.lastName || undefined, phone: form.phone || undefined, // Deliberately NOT the `|| undefined` shape used above: leaving these // two keys out tells the API to KEEP the stored birthday, so a shopper // who cleared the picker could never remove it. Explicit nulls delete // it. Do not "tidy" this back into the pattern of the fields above. birthMonth, birthDay, }); onProfileUpdate?.(updated); setEditing(false); setMessage({ type: 'success', text: t('profileUpdated') }); setTimeout(() => setMessage(null), 3000); } catch { setMessage({ type: 'error', text: t('profileUpdateFailed') }); } finally { setSaving(false); } } return (
{profile.email}
{profile.phone}
)} {storedBirthday && ({tc('birthday')} {storedBirthday}
)} {profile.createdAt && !isNaN(new Date(profile.createdAt).getTime()) && ({t('memberSince')}{' '} {new Date(profile.createdAt).toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric', })}
)} > )} {/* Success/Error message */} {message && ({message.text}
)}