forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
1<script lang="ts">
2 import { onDestroy } from 'svelte'
3 import { api, ApiError } from '../api'
4 import { resendVerification } from '../auth.svelte'
5 import type { RegistrationFlow } from './flow.svelte'
6
7 interface Props {
8 flow: RegistrationFlow
9 }
10
11 let { flow }: Props = $props()
12
13 let verificationCode = $state('')
14 let resending = $state(false)
15 let resendMessage = $state<string | null>(null)
16
17 let pollingInterval: ReturnType<typeof setInterval> | null = null
18
19 $effect(() => {
20 if (flow.state.step === 'verify' && flow.account && !verificationCode.trim()) {
21 pollingInterval = setInterval(async () => {
22 if (verificationCode.trim()) return
23 const advanced = await flow.checkAndAdvanceIfVerified()
24 if (advanced && pollingInterval) {
25 clearInterval(pollingInterval)
26 pollingInterval = null
27 }
28 }, 3000)
29 }
30
31 return () => {
32 if (pollingInterval) {
33 clearInterval(pollingInterval)
34 pollingInterval = null
35 }
36 }
37 })
38
39 onDestroy(() => {
40 if (pollingInterval) {
41 clearInterval(pollingInterval)
42 pollingInterval = null
43 }
44 })
45
46 function channelLabel(ch: string): string {
47 switch (ch) {
48 case 'email': return 'email'
49 case 'discord': return 'Discord'
50 case 'telegram': return 'Telegram'
51 case 'signal': return 'Signal'
52 default: return ch
53 }
54 }
55
56 async function handleSubmit(e: Event) {
57 e.preventDefault()
58 if (!verificationCode.trim()) return
59 resendMessage = null
60 await flow.verifyAccount(verificationCode)
61 }
62
63 async function handleResend() {
64 if (resending || !flow.account) return
65 resending = true
66 resendMessage = null
67 flow.clearError()
68
69 try {
70 await resendVerification(flow.account.did)
71 resendMessage = 'Verification code resent!'
72 } catch (err) {
73 if (err instanceof ApiError) {
74 flow.setError(err.message || 'Failed to resend code')
75 } else if (err instanceof Error) {
76 flow.setError(err.message || 'Failed to resend code')
77 } else {
78 flow.setError('Failed to resend code')
79 }
80 } finally {
81 resending = false
82 }
83 }
84</script>
85
86<div class="verification-step">
87 <p class="info-text">
88 We've sent a verification code to your {channelLabel(flow.info.verificationChannel)}.
89 Enter it below to continue.
90 </p>
91
92 {#if resendMessage}
93 <div class="message success">{resendMessage}</div>
94 {/if}
95
96 <form onsubmit={handleSubmit}>
97 <div class="field">
98 <label for="verification-code">Verification Code</label>
99 <input
100 id="verification-code"
101 type="text"
102 bind:value={verificationCode}
103 placeholder="XXXX-XXXX-XXXX-XXXX"
104 disabled={flow.state.submitting}
105 required
106 autocomplete="one-time-code"
107 class="code-input"
108 />
109 <span class="hint">Copy the entire code from your message, including dashes.</span>
110 </div>
111
112 <button type="submit" disabled={flow.state.submitting || !verificationCode.trim()}>
113 {flow.state.submitting ? 'Verifying...' : 'Verify'}
114 </button>
115
116 <button type="button" class="secondary" onclick={handleResend} disabled={resending}>
117 {resending ? 'Resending...' : 'Resend Code'}
118 </button>
119 </form>
120</div>
121
122<style>
123 .verification-step {
124 display: flex;
125 flex-direction: column;
126 gap: var(--space-4);
127 }
128
129 .info-text {
130 color: var(--text-secondary);
131 margin: 0;
132 }
133
134 .code-input {
135 font-family: var(--font-mono, monospace);
136 font-size: var(--text-base);
137 letter-spacing: 0.05em;
138 }
139
140 .hint {
141 display: block;
142 color: var(--text-secondary);
143 font-size: var(--text-sm);
144 margin-top: var(--space-1);
145 }
146</style>