this repo has no description
1<script lang="ts">
2 import { login, confirmSignup, resendVerification, getAuthState } from '../lib/auth.svelte'
3 import { navigate } from '../lib/router.svelte'
4 import { ApiError } from '../lib/api'
5
6 let identifier = $state('')
7 let password = $state('')
8 let submitting = $state(false)
9 let error = $state<string | null>(null)
10
11 let pendingVerification = $state<{ did: string } | null>(null)
12 let verificationCode = $state('')
13 let resendingCode = $state(false)
14 let resendMessage = $state<string | null>(null)
15
16 const auth = getAuthState()
17
18 $effect(() => {
19 if (auth.session) {
20 navigate('/dashboard')
21 }
22 })
23
24 async function handleSubmit(e: Event) {
25 e.preventDefault()
26 if (!identifier || !password) return
27
28 submitting = true
29 error = null
30 pendingVerification = null
31
32 try {
33 await login(identifier, password)
34 navigate('/dashboard')
35 } catch (e: any) {
36 if (e instanceof ApiError && e.error === 'AccountNotVerified') {
37 if (e.did) {
38 pendingVerification = { did: e.did }
39 } else {
40 error = 'Account not verified. Please check your verification method for a code.'
41 }
42 } else {
43 error = e.message || 'Login failed'
44 }
45 } finally {
46 submitting = false
47 }
48 }
49
50 async function handleVerification(e: Event) {
51 e.preventDefault()
52
53 if (!pendingVerification || !verificationCode.trim()) return
54
55 submitting = true
56 error = null
57
58 try {
59 await confirmSignup(pendingVerification.did, verificationCode.trim())
60 navigate('/dashboard')
61 } catch (e: any) {
62 error = e.message || 'Verification failed'
63 } finally {
64 submitting = false
65 }
66 }
67
68 async function handleResendCode() {
69 if (!pendingVerification || resendingCode) return
70
71 resendingCode = true
72 resendMessage = null
73 error = null
74
75 try {
76 await resendVerification(pendingVerification.did)
77 resendMessage = 'Verification code resent!'
78 } catch (e: any) {
79 error = e.message || 'Failed to resend code'
80 } finally {
81 resendingCode = false
82 }
83 }
84
85 function backToLogin() {
86 pendingVerification = null
87 verificationCode = ''
88 error = null
89 resendMessage = null
90 }
91</script>
92
93<div class="login-container">
94 {#if error}
95 <div class="error">{error}</div>
96 {/if}
97
98 {#if pendingVerification}
99 <h1>Verify Your Account</h1>
100 <p class="subtitle">
101 Your account needs verification. Enter the code sent to your verification method.
102 </p>
103
104 {#if resendMessage}
105 <div class="success">{resendMessage}</div>
106 {/if}
107
108 <form onsubmit={(e) => { e.preventDefault(); handleVerification(e); }}>
109 <div class="field">
110 <label for="verification-code">Verification Code</label>
111 <input
112 id="verification-code"
113 type="text"
114 bind:value={verificationCode}
115 placeholder="Enter 6-digit code"
116 disabled={submitting}
117 required
118 maxlength="6"
119 pattern="[0-9]{6}"
120 autocomplete="one-time-code"
121 />
122 </div>
123
124 <button type="submit" disabled={submitting || !verificationCode.trim()}>
125 {submitting ? 'Verifying...' : 'Verify Account'}
126 </button>
127
128 <button type="button" class="secondary" onclick={handleResendCode} disabled={resendingCode}>
129 {resendingCode ? 'Resending...' : 'Resend Code'}
130 </button>
131
132 <button type="button" class="tertiary" onclick={backToLogin}>
133 Back to Login
134 </button>
135 </form>
136 {:else}
137 <h1>Sign In</h1>
138 <p class="subtitle">Sign in to manage your PDS account</p>
139
140 <form onsubmit={(e) => { e.preventDefault(); handleSubmit(e); }}>
141 <div class="field">
142 <label for="identifier">Handle or Email</label>
143 <input
144 id="identifier"
145 type="text"
146 bind:value={identifier}
147 placeholder="you.bsky.social or you@example.com"
148 disabled={submitting}
149 required
150 />
151 </div>
152
153 <div class="field">
154 <label for="password">Password</label>
155 <input
156 id="password"
157 type="password"
158 bind:value={password}
159 placeholder="Password"
160 disabled={submitting}
161 required
162 />
163 </div>
164
165 <button type="submit" disabled={submitting || !identifier || !password}>
166 {submitting ? 'Signing in...' : 'Sign In'}
167 </button>
168 </form>
169
170 <p class="register-link">
171 Don't have an account? <a href="#/register">Create one</a>
172 </p>
173 {/if}
174</div>
175
176<style>
177 .login-container {
178 max-width: 400px;
179 margin: 4rem auto;
180 padding: 2rem;
181 }
182
183 h1 {
184 margin: 0 0 0.5rem 0;
185 }
186
187 .subtitle {
188 color: var(--text-secondary);
189 margin: 0 0 2rem 0;
190 }
191
192 form {
193 display: flex;
194 flex-direction: column;
195 gap: 1rem;
196 }
197
198 .field {
199 display: flex;
200 flex-direction: column;
201 gap: 0.25rem;
202 }
203
204 label {
205 font-size: 0.875rem;
206 font-weight: 500;
207 }
208
209 input {
210 padding: 0.75rem;
211 border: 1px solid var(--border-color-light);
212 border-radius: 4px;
213 font-size: 1rem;
214 background: var(--bg-input);
215 color: var(--text-primary);
216 }
217
218 input:focus {
219 outline: none;
220 border-color: var(--accent);
221 }
222
223 button {
224 padding: 0.75rem;
225 background: var(--accent);
226 color: white;
227 border: none;
228 border-radius: 4px;
229 font-size: 1rem;
230 cursor: pointer;
231 margin-top: 0.5rem;
232 }
233
234 button:hover:not(:disabled) {
235 background: var(--accent-hover);
236 }
237
238 button:disabled {
239 opacity: 0.6;
240 cursor: not-allowed;
241 }
242
243 button.secondary {
244 background: transparent;
245 color: var(--accent);
246 border: 1px solid var(--accent);
247 }
248
249 button.secondary:hover:not(:disabled) {
250 background: var(--accent);
251 color: white;
252 }
253
254 button.tertiary {
255 background: transparent;
256 color: var(--text-secondary);
257 border: none;
258 }
259
260 button.tertiary:hover:not(:disabled) {
261 color: var(--text-primary);
262 }
263
264 .error {
265 padding: 0.75rem;
266 background: var(--error-bg);
267 border: 1px solid var(--error-border);
268 border-radius: 4px;
269 color: var(--error-text);
270 }
271
272 .success {
273 padding: 0.75rem;
274 background: var(--success-bg);
275 border: 1px solid var(--success-border);
276 border-radius: 4px;
277 color: var(--success-text);
278 }
279
280 .register-link {
281 text-align: center;
282 margin-top: 1.5rem;
283 color: var(--text-secondary);
284 }
285
286 .register-link a {
287 color: var(--accent);
288 }
289</style>