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