import React, { useMemo, useState } from "react"; import { html, partnerGroupChipLabel } from "./constants.js"; import { api, authHeaders } from "./api.js"; import { RoleChip, StatusText, ConfirmDialog } from "./components.js"; export function SettingsView({ auth, user, status, selectedRoles, roleSearchInput, onRoleSearch, roleSearchItems, saveRoleMutation, onPhotoPreference, logout, onDeleted, setStatus, }) { const [busy, setBusy] = useState(""); const [confirmDelete, setConfirmDelete] = useState(false); const sharingSummary = useMemo(() => { const groups = user?.partner_groups || []; return groups.map((group) => { const others = (group.participant_ids || []).filter((id) => id && id !== auth.userId); const visibleTo = group.my_share_full_list ? others : []; const visibleFrom = (group.sharing_member_ids || []).filter((id) => id && id !== auth.userId); return { group, visibleTo, visibleFrom }; }); }, [user?.partner_groups, auth.userId]); async function handleExport() { try { setBusy("export"); const payload = await api(`/users/${auth.userId}/export`, { headers: authHeaders(auth.token) }); const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${auth.userId}-export.json`; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); setStatus("Export downloaded."); } catch (error) { setStatus(error.message); } finally { setBusy(""); } } async function handleDelete() { try { setBusy("delete"); await api(`/users/${auth.userId}`, { method: "DELETE", headers: authHeaders(auth.token), }); setStatus("Account deleted."); onDeleted?.(); } catch (error) { setStatus(error.message); } finally { setBusy(""); setConfirmDelete(false); } } return html` <${ConfirmDialog} open=${confirmDelete} title="Delete your account?" body="This deletes your profile, all plays, and all partner connections. This cannot be undone." confirmLabel=${busy === "delete" ? "Deleting…" : "Delete forever"} cancelLabel="Keep account" destructive=${true} onConfirm=${handleDelete} onCancel=${() => setConfirmDelete(false)} />
Settings
Privacy, roles, and account controls
Profile ID
${auth.userId}
Roles
Optional. These help with framing, not matching.
onRoleSearch(e.target.value)} /> ${selectedRoles.length ? html`
${selectedRoles.map((item) => html` <${RoleChip} key=${item.id} kink=${item} selected=${true} onToggle=${(id, selected) => saveRoleMutation.mutate({ kinkId: id, selected })} /> `)}
` : html`
No roles selected.
`} ${roleSearchItems.length ? html`
${roleSearchItems.map((item) => html` <${RoleChip} key=${item.kink.id} kink=${item.kink} selected=${false} onToggle=${(id, selected) => saveRoleMutation.mutate({ kinkId: id, selected })} /> `)}
` : null}
Sharing
Private by default. Full-list sharing is group-specific.
${sharingSummary.length ? html`
${sharingSummary.map(({ group, visibleTo, visibleFrom }) => html`
${partnerGroupChipLabel(group, auth.userId)}
${visibleTo.length ? `They can view my list: ${visibleTo.join(", ")}` : "They cannot view my full list"}
${visibleFrom.length ? `I can view: ${visibleFrom.join(", ")}` : "I cannot view anyone else's full list"}
`)}
` : html`
No partner groups yet.
`}
Data
Export your data or remove the account entirely.
<${StatusText} text=${status} />
`; }