grain.social is a photo sharing platform built on atproto. grain.social
atproto photography appview
57
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add Delete Account action to web settings

Adds a Delete Account row in Settings, Account that calls the new
deleteAccount xrpc, signs the user out, and returns them to the home
page. Styled red to match the Sign Out button. Two confirmation dialogs
guard the action and any server error is surfaced inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+61
+61
app/routes/settings/account/+page.svelte
··· 2 2 import DetailHeader from '$lib/components/molecules/DetailHeader.svelte' 3 3 import { ExternalLink } from 'lucide-svelte' 4 4 import { viewer } from '$lib/stores' 5 + import { callXrpc } from '$hatk/client' 6 + import { logout } from '$lib/auth' 7 + import { goto } from '$app/navigation' 5 8 6 9 const did = $derived($viewer?.did ?? '') 7 10 const handle = $derived($viewer?.handle ?? '') 11 + 12 + let deleting = $state(false) 13 + let deleteError = $state<string | null>(null) 14 + 15 + async function handleDelete() { 16 + const first = confirm( 17 + 'Delete your Grain account?\n\n' + 18 + 'This removes all your Grain galleries, stories, photos, favorites, comments, follows, blocks, and profile. ' + 19 + 'Your atproto identity is separate and is not affected. This cannot be undone.', 20 + ) 21 + if (!first) return 22 + const second = confirm('Are you sure? This cannot be undone.') 23 + if (!second) return 24 + 25 + deleting = true 26 + deleteError = null 27 + try { 28 + await callXrpc('social.grain.unspecced.deleteAccount') 29 + await logout() 30 + goto('/') 31 + } catch (err) { 32 + deleteError = err instanceof Error ? err.message : String(err) 33 + deleting = false 34 + } 35 + } 8 36 </script> 9 37 10 38 <DetailHeader label="Account" /> ··· 28 56 <ExternalLink size={14} class="chevron" /> 29 57 </a> 30 58 </div> 59 + 60 + <div class="settings-group"> 61 + <button type="button" class="settings-row delete" disabled={deleting} onclick={handleDelete}> 62 + <span class="row-label">{deleting ? 'Deleting…' : 'Delete Account'}</span> 63 + </button> 64 + </div> 65 + {#if deleteError} 66 + <p class="error">{deleteError}</p> 67 + {/if} 31 68 {/if} 32 69 </div> 33 70 ··· 79 116 } 80 117 .settings-row :global(.chevron) { 81 118 color: var(--text-muted); 119 + } 120 + button.settings-row { 121 + width: 100%; 122 + background: none; 123 + border: none; 124 + font-family: inherit; 125 + font-size: inherit; 126 + text-align: left; 127 + cursor: pointer; 128 + } 129 + button.settings-row:hover { 130 + background: var(--bg-hover); 131 + } 132 + button.settings-row:disabled { 133 + opacity: 0.6; 134 + cursor: default; 135 + } 136 + .delete .row-label { 137 + color: #f87171; 138 + } 139 + .error { 140 + color: #f87171; 141 + font-size: 13px; 142 + padding: 0 4px; 82 143 } 83 144 </style>