a simple pds frontend for listing accounts and generating invite codes
1
fork

Configure Feed

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

fix pds.ls link

Winter 72d973c9 f7bd53ed

+55 -54
+55 -54
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 - import '../app.css'; 3 - import type { PageData } from './$types'; 2 + import "../app.css"; 3 + import type { PageData } from "./$types"; 4 4 5 5 export let data: PageData; 6 6 7 - let inviteState: 'idle' | 'loading' | 'done' | 'error' = 'idle'; 8 - let inviteCode = ''; 9 - let inviteError = ''; 7 + let inviteState: "idle" | "loading" | "done" | "error" = "idle"; 8 + let inviteCode = ""; 9 + let inviteError = ""; 10 10 let copied = false; 11 11 12 12 async function generateInvite() { 13 - inviteState = 'loading'; 14 - inviteError = ''; 13 + inviteState = "loading"; 14 + inviteError = ""; 15 15 try { 16 - const res = await fetch('/api/invite', { method: 'POST' }); 16 + const res = await fetch("/api/invite", { method: "POST" }); 17 17 const body = await res.json().catch(() => ({})); 18 18 if (res.ok) { 19 19 inviteCode = body.code; 20 - inviteState = 'done'; 20 + inviteState = "done"; 21 21 copied = false; 22 - navigator.clipboard.writeText(body.code).then(() => { 23 - copied = true; 24 - }).catch(() => {}); 22 + navigator.clipboard 23 + .writeText(body.code) 24 + .then(() => { 25 + copied = true; 26 + }) 27 + .catch(() => {}); 25 28 } else if (res.status === 429) { 26 - const retryAfter = res.headers.get('Retry-After'); 29 + const retryAfter = res.headers.get("Retry-After"); 27 30 inviteError = retryAfter 28 31 ? `rate limited — try again in ${Math.ceil(parseInt(retryAfter) / 3600)}h` 29 - : 'rate limited'; 30 - inviteState = 'error'; 32 + : "rate limited"; 33 + inviteState = "error"; 31 34 } else { 32 35 inviteError = body.error ?? `server error (${res.status})`; 33 - inviteState = 'error'; 36 + inviteState = "error"; 34 37 } 35 38 } catch { 36 - inviteError = 'network error — are you online?'; 37 - inviteState = 'error'; 39 + inviteError = "network error — are you online?"; 40 + inviteState = "error"; 38 41 } 39 42 } 40 43 </script> ··· 62 65 width="48" 63 66 height="48" 64 67 loading="lazy" 65 - onerror={(e) => ((e.currentTarget as HTMLImageElement).style.visibility = 'hidden')} 68 + onerror={(e) => ((e.currentTarget as HTMLImageElement).style.visibility = "hidden")} 66 69 /> 67 70 <div class="account-info"> 68 71 {#if account.displayName} ··· 86 89 class="action-btn" 87 90 title="Open in Bluesky" 88 91 > 89 - <svg width="16" height="16" viewBox="0 0 360 320" fill="currentColor" aria-hidden="true"> 90 - <path d="M180 142c-16.3-31.7-60.7-90.8-102-120C38.5-5.9 23.4-1 13.5 3.4 2.1 8.6 0 26.2 0 36.5c0 10.4 5.7 84.8 9.4 97.2 12.2 41 55.7 55 95.7 50.5-58.7 8.6-110.8 30-53.2 105.8C131.6 380.1 143.8 295.3 180 256c36.2 39.3 48.4 124.1 128.1 33.9 57.6-75.8 5.5-97.2-53.2-105.8 40 4.4 83.5-9.5 95.7-50.5 3.7-12.4 9.4-86.8 9.4-97.2 0-10.3-2.1-27.9-13.5-33.1C336.6-1 321.5-5.9 282 22c-41.3 29.2-85.7 88.3-102 120Z"/> 92 + <svg 93 + width="16" 94 + height="16" 95 + viewBox="0 0 360 320" 96 + fill="currentColor" 97 + aria-hidden="true" 98 + > 99 + <path 100 + d="M180 142c-16.3-31.7-60.7-90.8-102-120C38.5-5.9 23.4-1 13.5 3.4 2.1 8.6 0 26.2 0 36.5c0 10.4 5.7 84.8 9.4 97.2 12.2 41 55.7 55 95.7 50.5-58.7 8.6-110.8 30-53.2 105.8C131.6 380.1 143.8 295.3 180 256c36.2 39.3 48.4 124.1 128.1 33.9 57.6-75.8 5.5-97.2-53.2-105.8 40 4.4 83.5-9.5 95.7-50.5 3.7-12.4 9.4-86.8 9.4-97.2 0-10.3-2.1-27.9-13.5-33.1C336.6-1 321.5-5.9 282 22c-41.3 29.2-85.7 88.3-102 120Z" 101 + /> 91 102 </svg> 92 103 </a> 93 104 <a 94 - href="https://pds.ls/{account.did}" 105 + href="https://pds.ls/at://{account.did}" 95 106 target="_blank" 96 107 rel="noopener noreferrer" 97 108 class="action-btn" ··· 107 118 <hr class="dashed" /> 108 119 109 120 <div class="footer-actions"> 110 - {#if inviteState === 'idle' || inviteState === 'loading'} 111 - <button 112 - class="primary-btn" 113 - onclick={generateInvite} 114 - disabled={inviteState === 'loading'} 115 - > 116 - {inviteState === 'loading' ? 'generating...' : 'generate invite code'} 117 - </button> 118 - {:else if inviteState === 'done'} 119 - <div class="invite-result"> 120 - <code class="invite-code">{inviteCode}</code> 121 - <span class="copied-hint">{copied ? 'copied!' : 'click to copy'}</span> 122 - <button 123 - class="primary-btn" 124 - onclick={generateInvite} 125 - >generate another</button> 126 - </div> 127 - {:else if inviteState === 'error'} 128 - <button 129 - class="primary-btn" 130 - onclick={generateInvite} 131 - >generate invite code</button> 132 - <span class="invite-error">{inviteError}</span> 133 - {/if} 134 - <a 135 - href="https://pdsmoover.com/moover" 136 - target="_blank" 137 - rel="noopener noreferrer" 138 - class="moover-link" 139 - >migrate</a> 140 - </div> 121 + {#if inviteState === "idle" || inviteState === "loading"} 122 + <button class="primary-btn" onclick={generateInvite} disabled={inviteState === "loading"}> 123 + {inviteState === "loading" ? "generating..." : "generate invite code"} 124 + </button> 125 + {:else if inviteState === "done"} 126 + <div class="invite-result"> 127 + <code class="invite-code">{inviteCode}</code> 128 + <span class="copied-hint">{copied ? "copied!" : "click to copy"}</span> 129 + <button class="primary-btn" onclick={generateInvite}>generate another</button> 130 + </div> 131 + {:else if inviteState === "error"} 132 + <button class="primary-btn" onclick={generateInvite}>generate invite code</button> 133 + <span class="invite-error">{inviteError}</span> 134 + {/if} 135 + <a 136 + href="https://pdsmoover.com/moover" 137 + target="_blank" 138 + rel="noopener noreferrer" 139 + class="moover-link">migrate</a 140 + > 141 + </div> 141 142 </div> 142 143 </main> 143 144 ··· 254 255 width: 22px; 255 256 height: 22px; 256 257 background-color: currentColor; 257 - mask-image: url('/pdsls-icon.svg'); 258 + mask-image: url("/pdsls-icon.svg"); 258 259 mask-size: 160%; 259 260 mask-repeat: no-repeat; 260 261 mask-position: center;