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