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 IdentityInputScreen component

Implements identity resolution screen that accepts a handle or DID,
calls resolveIdentity() via IPC, and displays resolved identity info
including DID, handle, PDS URL, and rotation key status. Maps ResolveError
codes to user-friendly messages for HANDLE_NOT_FOUND, DID_NOT_FOUND,
PDS_UNREACHABLE, and NETWORK_ERROR scenarios. Follows established screen
patterns with .screen container, centered layout, identity-card styling
matching HomeScreen conventions, and primary/secondary button styling.

authored by

Malpercio and committed by
Tangled
a5061b05 7f683b09

+254
+254
apps/identity-wallet/src/lib/components/onboarding/IdentityInputScreen.svelte
··· 1 + <script lang="ts"> 2 + import { resolveIdentity, type IdentityInfo, type ResolveError } from '$lib/ipc'; 3 + 4 + let { 5 + value = $bindable(''), 6 + onnext, 7 + onback, 8 + }: { 9 + value: string; 10 + onnext: (info: IdentityInfo) => void; 11 + onback: () => void; 12 + } = $props(); 13 + 14 + let resolving = $state(false); 15 + let resolved = $state<IdentityInfo | null>(null); 16 + let error = $state<string | null>(null); 17 + 18 + async function resolve() { 19 + if (!value.trim()) return; 20 + 21 + resolving = true; 22 + error = null; 23 + resolved = null; 24 + 25 + try { 26 + const info = await resolveIdentity(value.trim()); 27 + resolved = info; 28 + error = null; 29 + } catch (raw: unknown) { 30 + // Map ResolveError codes to user-friendly messages. 31 + if (typeof raw === 'object' && raw !== null && 'code' in raw) { 32 + const err = raw as ResolveError; 33 + switch (err.code) { 34 + case 'HANDLE_NOT_FOUND': 35 + error = 'Handle not found. Check the spelling and try again.'; 36 + break; 37 + case 'DID_NOT_FOUND': 38 + error = 'DID not found on PLC directory.'; 39 + break; 40 + case 'PDS_UNREACHABLE': 41 + error = 'Could not reach the PDS. It may be temporarily offline.'; 42 + break; 43 + case 'NETWORK_ERROR': 44 + error = 'Network error. Check your connection and try again.'; 45 + break; 46 + default: 47 + error = 'An unexpected error occurred. Please try again.'; 48 + } 49 + } else { 50 + error = 'An unexpected error occurred. Please try again.'; 51 + } 52 + resolved = null; 53 + } finally { 54 + resolving = false; 55 + } 56 + } 57 + 58 + function handleInputChange() { 59 + if (resolved || error) { 60 + resolved = null; 61 + error = null; 62 + } 63 + } 64 + 65 + // Truncate the DID for display on narrow mobile screens. 66 + // "did:plc:abcdefghijklmnopqrstuvwx" → "did:plc:abcdefgh…uvwxyz" 67 + let displayDid = $derived.by(() => { 68 + const did = resolved?.did ?? ''; 69 + const prefix = 'did:plc:'; 70 + if (!did.startsWith(prefix)) return did; 71 + const specific = did.slice(prefix.length); 72 + if (specific.length < 14) return did; 73 + return `${prefix}${specific.slice(0, 8)}…${specific.slice(-6)}`; 74 + }); 75 + </script> 76 + 77 + <div class="screen"> 78 + <h2>Import Identity</h2> 79 + <p class="hint">Enter a handle or DID to import an existing identity.</p> 80 + 81 + <input 82 + type="text" 83 + class:error={!!error} 84 + placeholder="alice.example.com or did:plc:..." 85 + autocomplete="off" 86 + autocorrect="off" 87 + autocapitalize="none" 88 + spellcheck={false} 89 + bind:value 90 + onchange={handleInputChange} 91 + /> 92 + 93 + {#if error} 94 + <p class="error-text">{error}</p> 95 + {/if} 96 + 97 + <button 98 + disabled={resolving || !value.trim()} 99 + onclick={resolve} 100 + > 101 + {resolving ? 'Resolving…' : 'Resolve'} 102 + </button> 103 + 104 + {#if resolved} 105 + <div class="identity-card"> 106 + <div class="card-content"> 107 + <p class="card-label">Handle</p> 108 + <p class="card-value">@{resolved.handle}</p> 109 + 110 + <p class="card-label">DID</p> 111 + <p class="card-value did-value">{displayDid}</p> 112 + 113 + <p class="card-label">PDS</p> 114 + <p class="card-value">{resolved.pdsUrl}</p> 115 + 116 + <p class="card-label">Rotation Key Status</p> 117 + <p class="card-value" class:status-root={resolved.deviceKeyIsRoot} class:status-not-root={!resolved.deviceKeyIsRoot}> 118 + {#if resolved.deviceKeyIsRoot} 119 + Your device is the root key 120 + {:else} 121 + Device key is not the root key 122 + {/if} 123 + </p> 124 + </div> 125 + </div> 126 + 127 + <button class="continue-btn" onclick={() => onnext(resolved!)}> 128 + Continue 129 + </button> 130 + {/if} 131 + 132 + <button class="back-btn" onclick={onback}> 133 + Back 134 + </button> 135 + </div> 136 + 137 + <style> 138 + .screen { 139 + display: flex; 140 + flex-direction: column; 141 + align-items: center; 142 + padding: 2rem; 143 + gap: 1rem; 144 + height: 100%; 145 + justify-content: center; 146 + } 147 + 148 + h2 { 149 + font-size: 1.5rem; 150 + font-weight: 700; 151 + margin: 0; 152 + } 153 + 154 + .hint { 155 + font-size: 0.9rem; 156 + color: #6b7280; 157 + text-align: center; 158 + margin: 0; 159 + } 160 + 161 + input { 162 + width: 100%; 163 + max-width: 320px; 164 + padding: 1rem; 165 + font-size: 1rem; 166 + border: 2px solid #d1d5db; 167 + border-radius: 12px; 168 + } 169 + 170 + input.error { 171 + border-color: #ef4444; 172 + } 173 + 174 + .error-text { 175 + color: #ef4444; 176 + font-size: 0.875rem; 177 + margin: 0; 178 + text-align: center; 179 + max-width: 320px; 180 + } 181 + 182 + button { 183 + width: 100%; 184 + max-width: 320px; 185 + padding: 1rem; 186 + background: #007aff; 187 + color: #fff; 188 + border: none; 189 + border-radius: 12px; 190 + font-size: 1rem; 191 + font-weight: 600; 192 + cursor: pointer; 193 + } 194 + 195 + button:disabled { 196 + background: #9ca3af; 197 + cursor: not-allowed; 198 + } 199 + 200 + .identity-card { 201 + background: #f9fafb; 202 + border: 1px solid #d1d5db; 203 + border-radius: 12px; 204 + padding: 1.25rem; 205 + width: 100%; 206 + max-width: 320px; 207 + } 208 + 209 + .card-content { 210 + display: flex; 211 + flex-direction: column; 212 + gap: 0.75rem; 213 + } 214 + 215 + .card-label { 216 + font-size: 0.75rem; 217 + font-weight: 600; 218 + color: #6b7280; 219 + margin: 0; 220 + text-transform: uppercase; 221 + letter-spacing: 0.04em; 222 + } 223 + 224 + .card-value { 225 + font-size: 0.95rem; 226 + color: #111827; 227 + margin: 0; 228 + word-break: break-word; 229 + } 230 + 231 + .did-value { 232 + font-family: monospace; 233 + font-size: 0.85rem; 234 + } 235 + 236 + .status-root { 237 + color: #22c55e; 238 + font-weight: 600; 239 + } 240 + 241 + .status-not-root { 242 + color: #6b7280; 243 + } 244 + 245 + .continue-btn { 246 + margin-top: 0.5rem; 247 + } 248 + 249 + .back-btn { 250 + background: #f3f4f6; 251 + color: #000; 252 + margin-top: auto; 253 + } 254 + </style>