import { Trans, useLingui } from "@lingui/react/macro"; import type { UserFactCategory, UserFactSource, UserProfileState } from "@rakazo/contracts"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Button, } from "@rakazo/ui-web"; import { PencilIcon, XIcon } from "lucide-react"; import type { ReactNode } from "react"; import { useEffect, useId, useState } from "react"; import { getActiveUiLocale } from "../lib/i18n"; import { rpc } from "../lib/rpc"; import type { ProfileField } from "../lib/user-profile-view"; import { CLEARED_PROFILE_FIELD, displayLanguageTag, displayRegionTag, factSpaceNames, filledProfileFields, groupUserFacts, userFactEdit, } from "../lib/user-profile-view"; import { UserFactEditor } from "./UserFactEditor"; function Row({ value, detail, actions, testId, status, }: { value: string; detail: string; actions: (describedBy: string) => ReactNode; testId: string; status?: string; }) { const valueId = useId(); return (
  • {value}

    {detail}

    {actions(valueId)}
  • ); } /** Everything the user's bots know about the user, with edit, consent and forget controls. */ export function UserProfileSettings() { const { t } = useLingui(); const [state, setState] = useState(null); const [spaces, setSpaces] = useState>([]); const [editingId, setEditingId] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [forgetOpen, setForgetOpen] = useState(false); useEffect(() => { let cancelled = false; // Only names a fact's spaces; without them the profile still works. rpc.spaces .list() .then((navigation) => { if (!cancelled) setSpaces(navigation.spaces.map(({ id, name }) => ({ id, name }))); }) .catch(() => undefined); return () => { cancelled = true; }; }, []); useEffect(() => { let cancelled = false; rpc.profile .get() .then((next) => { if (!cancelled) setState(next); }) .catch((err: unknown) => { if (!cancelled) setError(err instanceof Error ? err.message : t`Couldn't load`); }); return () => { cancelled = true; }; }, [t]); async function apply(change: () => Promise): Promise { if (busy) return false; setBusy(true); setError(null); try { setState(await change()); return true; } catch (err) { setError(err instanceof Error ? err.message : t`Couldn't save`); return false; } finally { setBusy(false); } } const categoryLabels: Record = { identity: t`Identity`, work: t`Work`, goals: t`Goals`, people: t`People`, health: t`Health`, values: t`Values`, habits: t`Habits`, preferences: t`Preferences`, tools: t`Tools`, }; const sourceLabels: Record = { onboarding: t`Onboarding`, user: t`You`, conversation: t`Chat`, consolidation: t`Chat history`, connector: t`Connected app`, }; const fieldLabels: Record = { timeZone: t`Time zone`, language: t`Language`, locale: t`Region`, communicationStyle: t`Communication style`, summary: t`Summary`, }; const fields = state ? filledProfileFields(state.profile) : []; const groups = state ? groupUserFacts(state.facts) : []; const uiLocale = getActiveUiLocale(); const deleteButton = (describedBy: string, onClick: () => void) => ( ); if (!error && (!state || (fields.length === 0 && groups.length === 0))) return null; return (

    About you

    {error ? (

    {error}

    ) : null} {fields.length > 0 ? (
      {fields.map(({ field, value }) => ( deleteButton( describedBy, () => void apply(() => rpc.profile.update(CLEARED_PROFILE_FIELD[field])), ) } /> ))}
    ) : null} {groups.map((group) => (

    {categoryLabels[group.category]}

      {group.facts.map((fact) => fact.id === editingId ? ( setEditingId(null)} onSave={(draft) => { const edit = userFactEdit(fact, draft); if (!edit) { setEditingId(null); return; } void apply(() => rpc.profile.update({ editFacts: [edit] })).then((saved) => { if (saved) setEditingId(null); }); }} /> ) : ( ( <> {fact.status === "pending" ? ( ) : null} {deleteButton( describedBy, () => void apply(() => rpc.profile.forget({ factIds: [fact.id] })), )} )} /> ), )}
    ))} {fields.length > 0 || groups.length > 0 ? ( ) : null} Forget everything about you? Cancel { setForgetOpen(false); void apply(() => rpc.profile.forget({ all: true })); }} > Forget
    ); }