Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037) | 1 | import { useState } from "react"; |
| 2 | import { Form, Link, useNavigation } from "react-router"; | |
| 3 | ||
| 4 | import { ACCOUNT_RESTORE_DAYS, type AccountDeletion } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import { DangerAction } from "./danger-zone"; | |
| 7 | import { Button, ErrorText } from "./ui"; | |
| 8 | import { | |
| 9 | AlertDialog, | |
| 10 | AlertDialogCancel, | |
| 11 | AlertDialogContent, | |
| 12 | AlertDialogDescription, | |
| 13 | AlertDialogFooter, | |
| 14 | AlertDialogHeader, | |
| 15 | AlertDialogTitle, | |
| 16 | } from "./ui/alert-dialog"; | |
| 17 | import { FieldDescription, FieldLabel, Field as FormField } from "./ui/field"; | |
| 18 | import { Input as TextInput } from "./ui/input"; | |
| 19 | import { accountDeletionRefusal, confirmsUsername, whatAccountDeletionTakes } from "../lib/account-deletion"; | |
| 20 | ||
| 21 | /** | |
| 22 | * Deleting your account, in one step, typed out to confirm, with your | |
| 23 | * password unless you signed in within the last few minutes. Kept for | |
| 24 | * `ACCOUNT_RESTORE_DAYS`, when support can restore it. Workspaces you own | |
| 25 | * alone are listed instead of the button until they have another owner or | |
| 26 | * are deleted. | |
| 27 | */ | |
| 28 | export function DeleteAccountAction({ | |
| 29 | username, | |
| 30 | deletion, | |
| 31 | hasPassword, | |
| 32 | error, | |
| 33 | defaultOpen = false, | |
| 34 | }: { | |
| 35 | username: string; | |
| 36 | deletion: AccountDeletion | null; | |
| 37 | hasPassword: boolean; | |
| 38 | error?: string; | |
| 39 | /** Open on first render: for previews of the dialog. */ | |
| 40 | defaultOpen?: boolean; | |
| 41 | }) { | |
| 42 | const [open, setOpen] = useState(Boolean(error) || defaultOpen); | |
| 43 | const [confirm, setConfirm] = useState(""); | |
| 44 | const navigation = useNavigation(); | |
| 45 | const deleting = navigation.state !== "idle" && navigation.formData?.get("intent") === "delete-account"; | |
| 46 | const refusal = accountDeletionRefusal(deletion); | |
| 47 | const goes = deletion ? whatAccountDeletionTakes(deletion) : []; | |
| 48 | if (deletion?.protected) { | |
| 49 | return ( | |
| 50 | <DangerAction title="Delete your account" action={null}> | |
| 51 | <span role="status">{refusal}</span> | |
| 52 | </DangerAction> | |
| 53 | ); | |
| 54 | } | |
| 55 | return ( | |
| 56 | <DangerAction | |
| 57 | title="Delete your account" | |
| 58 | action={ | |
| 59 | <Button type="button" variant="danger" disabled={Boolean(refusal)} onClick={() => setOpen(true)}> | |
| 60 | Delete account | |
| 61 | </Button> | |
| 62 | } | |
| 63 | > | |
| 64 | {refusal ? ( | |
| 65 | <> | |
| 66 | <span role="status">{refusal}</span> | |
| 67 | <ul className="mt-3 space-y-2"> | |
| 68 | {deletion?.sole_owner_of.map((workspace) => ( | |
| 69 | <li key={workspace.slug} className="rounded-lg border border-line bg-surface px-3 py-2"> | |
| 70 | <p className="text-fg"> | |
| 71 | <span className="font-medium">{workspace.name}</span>{" "} | |
| 72 | <span className="font-mono text-xs text-faint">{workspace.slug}</span> | |
| 73 | </p> | |
| 74 | <p className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs"> | |
| 75 | {workspace.members > 1 ? ( | |
| People and teams are front and centre: one directory of people and agents with presence, local time, titles, teams and what each owns; profiles with manager and reports and the agents they work with; an org chart with each team's agents beside the person who leads it; and teams of any mix, with a lead, a channel, a budget agents keep to and the agents on them. Every agent is told its teams each turn (who leads, who owns what, who's around and who to page), and the team page shows exactly what. Member management is Members and invites; the people and teams guide says how. | 76 | <Link to={`/${workspace.slug}/-/members`} className="text-accent underline-offset-4 hover:underline"> |
| Merge account deletion: soft delete for 30 days, staff restore and purge, ghost for what remains (identity 0037) | 77 | Make someone else an owner |
| 78 | </Link> | |
| 79 | ) : null} | |
| 80 | <Link to={`/${workspace.slug}/-/settings`} className="text-accent underline-offset-4 hover:underline"> | |
| 81 | Delete the workspace | |
| 82 | </Link> | |
| 83 | </p> | |
| 84 | {workspace.billing ? <p className="mt-1 text-xs text-warn">{workspace.billing}</p> : null} | |
| 85 | </li> | |
| 86 | ))} | |
| 87 | </ul> | |
| 88 | </> | |
| 89 | ) : ( | |
| 90 | <> | |
| 91 | You sign out everywhere and leave every workspace at once. Your account is kept for {ACCOUNT_RESTORE_DAYS}{" "} | |
| 92 | days, and support can restore it until then. | |
| 93 | </> | |
| 94 | )} | |
| 95 | <AlertDialog | |
| 96 | open={open} | |
| 97 | onOpenChange={(next) => { | |
| 98 | setOpen(next); | |
| 99 | setConfirm(""); | |
| 100 | }} | |
| 101 | > | |
| 102 | <AlertDialogContent> | |
| 103 | <Form method="post" className="grid gap-4"> | |
| 104 | <input type="hidden" name="intent" value="delete-account" /> | |
| 105 | <AlertDialogHeader> | |
| 106 | <AlertDialogTitle>Delete your account?</AlertDialogTitle> | |
| 107 | <AlertDialogDescription> | |
| 108 | Everything you sign in with stops working now. For {ACCOUNT_RESTORE_DAYS} days, support can restore | |
| 109 | your account; after that it is gone for good. | |
| 110 | </AlertDialogDescription> | |
| 111 | </AlertDialogHeader> | |
| 112 | {goes.length > 0 ? ( | |
| 113 | <div className="grid gap-1.5"> | |
| 114 | <p className="text-sm font-medium">What goes with it</p> | |
| 115 | <ul className="list-disc space-y-1 pl-5 text-sm text-muted"> | |
| 116 | {goes.map((line) => ( | |
| 117 | <li key={line}>{line}</li> | |
| 118 | ))} | |
| 119 | </ul> | |
| 120 | </div> | |
| 121 | ) : null} | |
| 122 | <ul className="list-disc space-y-1.5 pl-5 text-sm text-muted"> | |
| 123 | <li>You are signed out everywhere. Your access tokens, SSH keys, deploy keys you added and applications stop working.</li> | |
| 124 | <li>You leave every workspace, team and repository. Workspaces you share keep everything in them.</li> | |
| 125 | <li>Your profile is no longer found, and nobody can add you to anything.</li> | |
| 126 | <li> | |
| 127 | To get it back within {ACCOUNT_RESTORE_DAYS} days, write to support from one of its addresses. After | |
| 128 | that your addresses, keys, profile and settings are removed for good. | |
| 129 | </li> | |
| 130 | <li> | |
| 131 | What you wrote stays where it is: issues, pull requests, comments and reviews show as{" "} | |
| 132 | <span className="font-mono text-fg">ghost</span> once it is removed. | |
| 133 | </li> | |
| 134 | <li> | |
| 135 | The username <span className="font-mono text-fg">{username}</span> is never given to anyone else. | |
| 136 | </li> | |
| 137 | </ul> | |
| 138 | <FormField> | |
| 139 | <FieldLabel htmlFor="confirm-account"> | |
| 140 | Type <span className="font-mono text-fg">{username}</span> to confirm | |
| 141 | </FieldLabel> | |
| 142 | <TextInput | |
| 143 | id="confirm-account" | |
| 144 | name="confirm" | |
| 145 | value={confirm} | |
| 146 | spellCheck={false} | |
| 147 | autoCapitalize="off" | |
| 148 | autoComplete="off" | |
| 149 | onChange={(event) => setConfirm(event.target.value)} | |
| 150 | className="font-mono" | |
| 151 | /> | |
| 152 | </FormField> | |
| 153 | {hasPassword ? ( | |
| 154 | <FormField> | |
| 155 | <FieldLabel htmlFor="confirm-password">Your password</FieldLabel> | |
| 156 | <TextInput id="confirm-password" name="password" type="password" autoComplete="current-password" /> | |
| 157 | <FieldDescription>Not needed if you signed in within the last 10 minutes.</FieldDescription> | |
| 158 | </FormField> | |
| 159 | ) : ( | |
| 160 | <p className="text-sm text-muted"> | |
| 161 | Your account signs in with GitHub only: sign out, sign in with GitHub again, and delete it within 10 | |
| 162 | minutes. | |
| 163 | </p> | |
| 164 | )} | |
| 165 | <ErrorText>{error}</ErrorText> | |
| 166 | <AlertDialogFooter> | |
| 167 | <AlertDialogCancel type="button">Cancel</AlertDialogCancel> | |
| 168 | <Button type="submit" variant="danger" disabled={!confirmsUsername(username, confirm) || deleting}> | |
| 169 | {deleting ? "Deleting…" : "Delete account"} | |
| 170 | </Button> | |
| 171 | </AlertDialogFooter> | |
| 172 | </Form> | |
| 173 | </AlertDialogContent> | |
| 174 | </AlertDialog> | |
| 175 | </DangerAction> | |
| 176 | ); | |
| 177 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.