An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

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

feat(identity-wallet): add PdsAuthScreen component

- Create PdsAuthScreen component that follows AuthenticatingScreen pattern
- Accepts pdsUrl, onnext, onback props
- Shows initial state with PDS connection prompt and button
- On button press, sets authenticating=true and calls startPdsAuth(pdsUrl)
- Shows spinner while authenticating
- Maps ClaimError codes to user-friendly messages:
- UNAUTHORIZED: 'Authentication was denied. Please try again.'
- NETWORK_ERROR: 'Network error. Check your connection and try again.'
- Other: 'Authentication failed. Please try again.'
- On error, shows error message with 'Try Again' and 'Back' buttons
- On success, calls onnext() to navigate to next step
- Styling follows existing screen patterns with centered layout
- Type checked successfully with pnpm check

authored by

Malpercio and committed by
Tangled
e7bd4d6d 0a104c9a

+200
+200
apps/identity-wallet/src/lib/components/onboarding/PdsAuthScreen.svelte
··· 1 + <script lang="ts"> 2 + import { startPdsAuth, type ClaimError } from '$lib/ipc'; 3 + 4 + let { 5 + pdsUrl, 6 + onnext, 7 + onback, 8 + }: { 9 + pdsUrl: string; 10 + onnext: () => void; 11 + onback: () => void; 12 + } = $props(); 13 + 14 + let authenticating = $state(false); 15 + let error = $state<string | null>(null); 16 + 17 + async function authenticate() { 18 + authenticating = true; 19 + error = null; 20 + 21 + try { 22 + await startPdsAuth(pdsUrl); 23 + onnext(); 24 + } catch (raw: unknown) { 25 + authenticating = false; 26 + 27 + // Guard against non-ClaimError shapes 28 + if ( 29 + typeof raw === 'object' && 30 + raw !== null && 31 + 'code' in raw && 32 + typeof (raw as ClaimError).code === 'string' 33 + ) { 34 + const err = raw as ClaimError; 35 + switch (err.code) { 36 + case 'UNAUTHORIZED': 37 + error = 'Authentication was denied. Please try again.'; 38 + break; 39 + case 'NETWORK_ERROR': 40 + error = 'Network error. Check your connection and try again.'; 41 + break; 42 + default: 43 + error = 'Authentication failed. Please try again.'; 44 + } 45 + } else { 46 + error = 'Authentication failed. Please try again.'; 47 + } 48 + } 49 + } 50 + </script> 51 + 52 + <div class="screen"> 53 + {#if authenticating} 54 + <div class="spinner" aria-label="Loading"></div> 55 + <p class="status">Opening browser for PDS authentication…</p> 56 + {:else if error} 57 + <div class="content"> 58 + <h2>Connect to Your PDS</h2> 59 + <p class="hint">Connect to your PDS at <code>{pdsUrl}</code> to verify your identity.</p> 60 + <p class="error-text">{error}</p> 61 + <div class="button-group"> 62 + <button class="primary" onclick={authenticate}> 63 + Try Again 64 + </button> 65 + <button class="secondary" onclick={onback}> 66 + Back 67 + </button> 68 + </div> 69 + </div> 70 + {:else} 71 + <div class="content"> 72 + <h2>Connect to Your PDS</h2> 73 + <p class="hint">Connect to your PDS at <code>{pdsUrl}</code> to verify your identity.</p> 74 + <div class="button-group"> 75 + <button class="primary" onclick={authenticate}> 76 + Authenticate with PDS 77 + </button> 78 + <button class="secondary" onclick={onback}> 79 + Back 80 + </button> 81 + </div> 82 + </div> 83 + {/if} 84 + </div> 85 + 86 + <style> 87 + .screen { 88 + display: flex; 89 + flex-direction: column; 90 + align-items: center; 91 + justify-content: center; 92 + height: 100%; 93 + gap: 24px; 94 + padding: 32px; 95 + } 96 + 97 + .content { 98 + display: flex; 99 + flex-direction: column; 100 + align-items: center; 101 + gap: 1.5rem; 102 + max-width: 320px; 103 + width: 100%; 104 + } 105 + 106 + h2 { 107 + font-size: 1.5rem; 108 + font-weight: 700; 109 + margin: 0; 110 + text-align: center; 111 + } 112 + 113 + .hint { 114 + font-size: 0.95rem; 115 + color: #6b7280; 116 + text-align: center; 117 + margin: 0; 118 + line-height: 1.5; 119 + } 120 + 121 + code { 122 + background: #f3f4f6; 123 + padding: 0.25rem 0.5rem; 124 + border-radius: 4px; 125 + font-family: monospace; 126 + font-size: 0.85rem; 127 + word-break: break-all; 128 + } 129 + 130 + .error-text { 131 + color: #ef4444; 132 + font-size: 0.875rem; 133 + margin: 0; 134 + text-align: center; 135 + } 136 + 137 + .button-group { 138 + display: flex; 139 + flex-direction: column; 140 + gap: 1rem; 141 + width: 100%; 142 + } 143 + 144 + .spinner { 145 + width: 40px; 146 + height: 40px; 147 + border: 4px solid #e5e7eb; 148 + border-top-color: #007aff; 149 + border-radius: 50%; 150 + animation: spin 0.8s linear infinite; 151 + } 152 + 153 + @keyframes spin { 154 + to { 155 + transform: rotate(360deg); 156 + } 157 + } 158 + 159 + .status { 160 + text-align: center; 161 + color: #6b7280; 162 + font-size: 1rem; 163 + margin: 0; 164 + } 165 + 166 + button { 167 + width: 100%; 168 + padding: 1rem; 169 + border: none; 170 + border-radius: 12px; 171 + font-size: 1rem; 172 + font-weight: 600; 173 + cursor: pointer; 174 + transition: background-color 0.2s; 175 + } 176 + 177 + .primary { 178 + background: #007aff; 179 + color: #fff; 180 + } 181 + 182 + .primary:active { 183 + background: #0051d5; 184 + } 185 + 186 + .secondary { 187 + background: #f3f4f6; 188 + color: #374151; 189 + } 190 + 191 + .secondary:active { 192 + background: #e5e7eb; 193 + } 194 + 195 + button:disabled { 196 + background: #9ca3af; 197 + cursor: not-allowed; 198 + color: #fff; 199 + } 200 + </style>