'use client' import { useState } from 'react' import { useRouter } from 'next/navigation' import { toast } from 'sonner' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { ChipInput } from '@/components/chip-input' import { WILLING_TO_SUGGESTIONS } from '@/lib/profile-presets' import { updateMyProfile, discloseAffiliations, requestEmailChange } from '@/lib/actions' export type ProfileInitial = { email: string display_name: string handle: string phone: string bio: string skills: string[] interests: string[] willing_to: string[] affiliations: string[] // Conflict-of-interest disclosure (ported from the former /profile page): // privileged roles are nudged to disclose; the last-disclosed date is shown // when set. isPrivileged: boolean coiLastDisclosed: string | null } const sameList = (a: string[], b: string[]) => a.length === b.length && a.every((x, i) => x === b[i]) const fieldLabel = 'font-mono text-[10px] tracking-widest text-muted-foreground uppercase' export function ProfileForm({ initial }: { initial: ProfileInitial }) { const router = useRouter() const [busy, setBusy] = useState(false) const [emailBusy, setEmailBusy] = useState(false) const [newEmail, setNewEmail] = useState('') const [displayName, setDisplayName] = useState(initial.display_name) const [handle, setHandle] = useState(initial.handle) const [phone, setPhone] = useState(initial.phone) const [bio, setBio] = useState(initial.bio) const [skills, setSkills] = useState(initial.skills) const [interests, setInterests] = useState(initial.interests) const [willingTo, setWillingTo] = useState(initial.willing_to) const [affiliations, setAffiliations] = useState(initial.affiliations) async function save() { if (!displayName.trim()) return toast.error('Display name is required.') setBusy(true) const res = await updateMyProfile({ display_name: displayName.trim(), handle: handle.trim() || null, phone: phone.trim() || null, bio: bio.trim() || null, skills, interests, willing_to: willingTo, }) if ('error' in res && res.error) { setBusy(false) return toast.error(res.error) } // Only re-disclose affiliations when they actually changed, so the // conflict-of-interest disclosure timestamp is not reset on every save. if (!sameList(affiliations, initial.affiliations)) { const aff = await discloseAffiliations({ affiliations }) if ('error' in aff && aff.error) { setBusy(false) return toast.error(aff.error) } } setBusy(false) toast.success('Profile saved.') router.refresh() } async function changeEmail() { const e = newEmail.trim() if (!e) return toast.error('Enter the new email address.') setEmailBusy(true) const res = await requestEmailChange({ email: e }) setEmailBusy(false) if ('error' in res && res.error) return toast.error(res.error) setNewEmail('') toast.success( 'Confirmation sent. Check BOTH your current and new email to finish the change.', ) } return (
setDisplayName(e.target.value)} maxLength={100} />
setHandle(e.target.value)} maxLength={50} placeholder="optional" />
setPhone(e.target.value)} maxLength={20} placeholder="optional" />