this repo has no description
1<script lang="ts">
2 import { loginWithOAuth, confirmSignup, resendVerification, getAuthState, switchAccount, forgetAccount } from '../lib/auth.svelte'
3 import { navigate } from '../lib/router.svelte'
4 let submitting = $state(false)
5 let pendingVerification = $state<{ did: string } | null>(null)
6 let verificationCode = $state('')
7 let resendingCode = $state(false)
8 let resendMessage = $state<string | null>(null)
9 let showNewLogin = $state(false)
10 const auth = getAuthState()
11 async function handleSwitchAccount(did: string) {
12 submitting = true
13 try {
14 await switchAccount(did)
15 navigate('/dashboard')
16 } catch {
17 submitting = false
18 }
19 }
20 function handleForgetAccount(did: string, e: Event) {
21 e.stopPropagation()
22 forgetAccount(did)
23 }
24 async function handleOAuthLogin() {
25 submitting = true
26 try {
27 await loginWithOAuth()
28 } catch {
29 submitting = false
30 }
31 }
32 async function handleVerification(e: Event) {
33 e.preventDefault()
34 if (!pendingVerification || !verificationCode.trim()) return
35 submitting = true
36 try {
37 await confirmSignup(pendingVerification.did, verificationCode.trim())
38 navigate('/dashboard')
39 } catch {
40 submitting = false
41 }
42 }
43 async function handleResendCode() {
44 if (!pendingVerification || resendingCode) return
45 resendingCode = true
46 resendMessage = null
47 try {
48 await resendVerification(pendingVerification.did)
49 resendMessage = 'Verification code resent!'
50 } catch {
51 resendMessage = null
52 } finally {
53 resendingCode = false
54 }
55 }
56 function backToLogin() {
57 pendingVerification = null
58 verificationCode = ''
59 resendMessage = null
60 }
61</script>
62<div class="login-container">
63 {#if auth.error}
64 <div class="error">{auth.error}</div>
65 {/if}
66 {#if pendingVerification}
67 <h1>Verify Your Account</h1>
68 <p class="subtitle">
69 Your account needs verification. Enter the code sent to your verification method.
70 </p>
71 {#if resendMessage}
72 <div class="success">{resendMessage}</div>
73 {/if}
74 <form onsubmit={(e) => { e.preventDefault(); handleVerification(e); }}>
75 <div class="field">
76 <label for="verification-code">Verification Code</label>
77 <input
78 id="verification-code"
79 type="text"
80 bind:value={verificationCode}
81 placeholder="Enter 6-digit code"
82 disabled={submitting}
83 required
84 maxlength="6"
85 pattern="[0-9]{6}"
86 autocomplete="one-time-code"
87 />
88 </div>
89 <button type="submit" disabled={submitting || !verificationCode.trim()}>
90 {submitting ? 'Verifying...' : 'Verify Account'}
91 </button>
92 <button type="button" class="secondary" onclick={handleResendCode} disabled={resendingCode}>
93 {resendingCode ? 'Resending...' : 'Resend Code'}
94 </button>
95 <button type="button" class="tertiary" onclick={backToLogin}>
96 Back to Login
97 </button>
98 </form>
99 {:else if auth.savedAccounts.length > 0 && !showNewLogin}
100 <h1>Sign In</h1>
101 <p class="subtitle">Choose an account</p>
102 <div class="saved-accounts">
103 {#each auth.savedAccounts as account}
104 <div
105 class="account-item"
106 class:disabled={submitting}
107 role="button"
108 tabindex="0"
109 onclick={() => !submitting && handleSwitchAccount(account.did)}
110 onkeydown={(e) => e.key === 'Enter' && !submitting && handleSwitchAccount(account.did)}
111 >
112 <div class="account-info">
113 <span class="account-handle">@{account.handle}</span>
114 <span class="account-did">{account.did}</span>
115 </div>
116 <button
117 type="button"
118 class="forget-btn"
119 onclick={(e) => handleForgetAccount(account.did, e)}
120 title="Remove from saved accounts"
121 >
122 ×
123 </button>
124 </div>
125 {/each}
126 </div>
127 <button type="button" class="secondary add-account" onclick={() => showNewLogin = true}>
128 Sign in to another account
129 </button>
130 <p class="register-link">
131 Don't have an account? <a href="#/register">Create one</a>
132 </p>
133 {:else}
134 <h1>Sign In</h1>
135 <p class="subtitle">Sign in to manage your PDS account</p>
136 {#if auth.savedAccounts.length > 0}
137 <button type="button" class="tertiary back-btn" onclick={() => showNewLogin = false}>
138 ← Back to saved accounts
139 </button>
140 {/if}
141 <button type="button" class="oauth-btn" onclick={handleOAuthLogin} disabled={submitting || auth.loading}>
142 {submitting ? 'Redirecting...' : 'Sign In'}
143 </button>
144 <p class="forgot-link">
145 <a href="#/reset-password">Forgot password?</a>
146 </p>
147 <p class="register-link">
148 Don't have an account? <a href="#/register">Create one</a>
149 </p>
150 {/if}
151</div>
152<style>
153 .login-container {
154 max-width: 400px;
155 margin: 4rem auto;
156 padding: 2rem;
157 }
158 h1 {
159 margin: 0 0 0.5rem 0;
160 }
161 .subtitle {
162 color: var(--text-secondary);
163 margin: 0 0 2rem 0;
164 }
165 form {
166 display: flex;
167 flex-direction: column;
168 gap: 1rem;
169 }
170 .field {
171 display: flex;
172 flex-direction: column;
173 gap: 0.25rem;
174 }
175 label {
176 font-size: 0.875rem;
177 font-weight: 500;
178 }
179 input {
180 padding: 0.75rem;
181 border: 1px solid var(--border-color-light);
182 border-radius: 4px;
183 font-size: 1rem;
184 background: var(--bg-input);
185 color: var(--text-primary);
186 }
187 input:focus {
188 outline: none;
189 border-color: var(--accent);
190 }
191 button {
192 padding: 0.75rem;
193 background: var(--accent);
194 color: white;
195 border: none;
196 border-radius: 4px;
197 font-size: 1rem;
198 cursor: pointer;
199 margin-top: 0.5rem;
200 }
201 button:hover:not(:disabled) {
202 background: var(--accent-hover);
203 }
204 button:disabled {
205 opacity: 0.6;
206 cursor: not-allowed;
207 }
208 button.secondary {
209 background: transparent;
210 color: var(--accent);
211 border: 1px solid var(--accent);
212 }
213 button.secondary:hover:not(:disabled) {
214 background: var(--accent);
215 color: white;
216 }
217 button.tertiary {
218 background: transparent;
219 color: var(--text-secondary);
220 border: none;
221 }
222 button.tertiary:hover:not(:disabled) {
223 color: var(--text-primary);
224 }
225 .oauth-btn {
226 width: 100%;
227 padding: 1rem;
228 font-size: 1.125rem;
229 font-weight: 500;
230 }
231 .error {
232 padding: 0.75rem;
233 background: var(--error-bg);
234 border: 1px solid var(--error-border);
235 border-radius: 4px;
236 color: var(--error-text);
237 }
238 .success {
239 padding: 0.75rem;
240 background: var(--success-bg);
241 border: 1px solid var(--success-border);
242 border-radius: 4px;
243 color: var(--success-text);
244 }
245 .forgot-link {
246 text-align: center;
247 margin-top: 1rem;
248 margin-bottom: 0;
249 color: var(--text-secondary);
250 }
251 .forgot-link a {
252 color: var(--accent);
253 }
254 .register-link {
255 text-align: center;
256 margin-top: 0.5rem;
257 color: var(--text-secondary);
258 }
259 .register-link a {
260 color: var(--accent);
261 }
262 .saved-accounts {
263 display: flex;
264 flex-direction: column;
265 gap: 0.5rem;
266 margin-bottom: 1rem;
267 }
268 .account-item {
269 display: flex;
270 align-items: center;
271 justify-content: space-between;
272 padding: 1rem;
273 background: var(--bg-card);
274 border: 1px solid var(--border-color);
275 border-radius: 8px;
276 cursor: pointer;
277 text-align: left;
278 width: 100%;
279 transition: border-color 0.15s, box-shadow 0.15s;
280 }
281 .account-item:hover:not(.disabled) {
282 border-color: var(--accent);
283 box-shadow: 0 2px 8px rgba(77, 166, 255, 0.15);
284 }
285 .account-item.disabled {
286 opacity: 0.6;
287 cursor: not-allowed;
288 }
289 .account-info {
290 display: flex;
291 flex-direction: column;
292 gap: 0.25rem;
293 }
294 .account-handle {
295 font-weight: 500;
296 color: var(--text-primary);
297 }
298 .account-did {
299 font-size: 0.75rem;
300 color: var(--text-muted);
301 font-family: monospace;
302 overflow: hidden;
303 text-overflow: ellipsis;
304 max-width: 250px;
305 }
306 .forget-btn {
307 padding: 0.25rem 0.5rem;
308 background: transparent;
309 border: none;
310 color: var(--text-muted);
311 cursor: pointer;
312 font-size: 1.25rem;
313 line-height: 1;
314 border-radius: 4px;
315 margin: 0;
316 }
317 .forget-btn:hover {
318 background: var(--error-bg);
319 color: var(--error-text);
320 }
321 .add-account {
322 width: 100%;
323 margin-bottom: 1rem;
324 }
325 .back-btn {
326 margin-bottom: 1rem;
327 padding: 0;
328 }
329</style>