signer-test-sdk-react 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/src/AbstraxnProvider.d.ts +20 -0
- package/dist/src/AbstraxnProvider.js +2213 -0
- package/dist/src/AbstraxnProvider.js.map +1 -0
- package/dist/src/ConnectButton.css +217 -0
- package/dist/src/ConnectButton.d.ts +71 -0
- package/dist/src/ConnectButton.js +102 -0
- package/dist/src/ConnectButton.js.map +1 -0
- package/dist/src/ExternalWalletButtons.css +319 -0
- package/dist/src/ExternalWalletButtons.d.ts +56 -0
- package/dist/src/ExternalWalletButtons.js +245 -0
- package/dist/src/ExternalWalletButtons.js.map +1 -0
- package/dist/src/OnboardingUI.d.ts +63 -0
- package/dist/src/OnboardingUI.js +66 -0
- package/dist/src/OnboardingUI.js.map +1 -0
- package/dist/src/WalletModal.css +549 -0
- package/dist/src/WalletModal.d.ts +6 -0
- package/dist/src/WalletModal.js +89 -0
- package/dist/src/WalletModal.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUI.css +727 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.d.ts +15 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js +65 -0
- package/dist/src/components/OnboardingUI/OnboardingUIReact.js.map +1 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.d.ts +257 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js +3454 -0
- package/dist/src/components/OnboardingUI/OnboardingUIWeb.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.d.ts +16 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js +19 -0
- package/dist/src/components/OnboardingUI/components/EmailForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/Modal.d.ts +15 -0
- package/dist/src/components/OnboardingUI/components/Modal.js +68 -0
- package/dist/src/components/OnboardingUI/components/Modal.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.d.ts +19 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js +58 -0
- package/dist/src/components/OnboardingUI/components/OtpForm.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.d.ts +14 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js +22 -0
- package/dist/src/components/OnboardingUI/components/PasskeyButton.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.d.ts +15 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js +15 -0
- package/dist/src/components/OnboardingUI/components/SocialButtons.js.map +1 -0
- package/dist/src/components/OnboardingUI/components/index.d.ts +13 -0
- package/dist/src/components/OnboardingUI/components/index.js +9 -0
- package/dist/src/components/OnboardingUI/components/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/index.d.ts +7 -0
- package/dist/src/components/OnboardingUI/hooks/index.js +6 -0
- package/dist/src/components/OnboardingUI/hooks/index.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.d.ts +11 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js +146 -0
- package/dist/src/components/OnboardingUI/hooks/useAuthMethods.js.map +1 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.d.ts +21 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js +135 -0
- package/dist/src/components/OnboardingUI/hooks/useOnboarding.js.map +1 -0
- package/dist/src/components/OnboardingUI/index.d.ts +12 -0
- package/dist/src/components/OnboardingUI/index.js +15 -0
- package/dist/src/components/OnboardingUI/index.js.map +1 -0
- package/dist/src/hooks.d.ts +204 -0
- package/dist/src/hooks.js +394 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +181 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wagmiConfig.d.ts +147 -0
- package/dist/src/wagmiConfig.js +81 -0
- package/dist/src/wagmiConfig.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,3454 @@
|
|
|
1
|
+
// CSS styles embedded as string
|
|
2
|
+
const ONBOARDING_UI_STYLES = `
|
|
3
|
+
.onboarding-container {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
min-height: 100vh;
|
|
9
|
+
padding: 20px;
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
11
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
12
|
+
sans-serif;
|
|
13
|
+
-webkit-font-smoothing: antialiased;
|
|
14
|
+
-moz-osx-font-smoothing: grayscale;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Inline mode - no extra height */
|
|
18
|
+
.onboarding-container.onboarding-inline {
|
|
19
|
+
min-height: auto;
|
|
20
|
+
padding: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Modal Styles */
|
|
24
|
+
.onboarding-modal-overlay {
|
|
25
|
+
position: fixed;
|
|
26
|
+
top: 0;
|
|
27
|
+
left: 0;
|
|
28
|
+
right: 0;
|
|
29
|
+
bottom: 0;
|
|
30
|
+
background-color: rgba(0, 0, 0, 0.6);
|
|
31
|
+
backdrop-filter: blur(8px);
|
|
32
|
+
-webkit-backdrop-filter: blur(8px);
|
|
33
|
+
display: none; /* Start hidden by default to prevent flicker */
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
z-index: 9999;
|
|
37
|
+
opacity: 0;
|
|
38
|
+
transition: opacity 0.3s ease-in-out;
|
|
39
|
+
padding: 16px;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
overscroll-behavior: contain;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.onboarding-modal-overlay::-webkit-scrollbar {
|
|
45
|
+
display: none;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.onboarding-modal-overlay {
|
|
49
|
+
-ms-overflow-style: none;
|
|
50
|
+
scrollbar-width: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.onboarding-modal-overlay.onboarding-modal-open {
|
|
54
|
+
display: flex; /* Show when explicitly opened */
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.onboarding-modal-overlay.onboarding-modal-closing {
|
|
59
|
+
opacity: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.onboarding-modal-content {
|
|
63
|
+
position: relative;
|
|
64
|
+
width: 420px;
|
|
65
|
+
max-width: 420px;
|
|
66
|
+
min-width: 420px;
|
|
67
|
+
max-height: calc(100vh - 32px);
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
transform: scale(0.95);
|
|
70
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s ease-in-out;
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
box-sizing: border-box;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (max-width: 480px) {
|
|
77
|
+
.onboarding-modal-content {
|
|
78
|
+
width: 100%;
|
|
79
|
+
max-width: 100%;
|
|
80
|
+
min-width: 100%;
|
|
81
|
+
max-height: 100vh;
|
|
82
|
+
border-radius: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.onboarding-card {
|
|
86
|
+
width: 100%;
|
|
87
|
+
max-width: 100%;
|
|
88
|
+
min-width: 100%;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.onboarding-modal-overlay {
|
|
92
|
+
padding: 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.onboarding-modal-overlay.onboarding-modal-open .onboarding-modal-content {
|
|
97
|
+
transform: scale(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.onboarding-modal-overlay.onboarding-modal-closing .onboarding-modal-content {
|
|
101
|
+
transform: scale(0.95);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.onboarding-modal-close {
|
|
105
|
+
position: absolute;
|
|
106
|
+
top: 24px;
|
|
107
|
+
right: 24px;
|
|
108
|
+
width: 32px;
|
|
109
|
+
height: 32px;
|
|
110
|
+
border: none;
|
|
111
|
+
background-color: #f3f4f6;
|
|
112
|
+
border-radius: 8px;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
font-size: 18px;
|
|
118
|
+
line-height: 1;
|
|
119
|
+
color: #6b7280;
|
|
120
|
+
transition: all 0.2s ease;
|
|
121
|
+
z-index: 10;
|
|
122
|
+
padding: 0;
|
|
123
|
+
font-weight: 400;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.onboarding-modal-close:hover {
|
|
127
|
+
background-color: #e5e7eb;
|
|
128
|
+
color: #374151;
|
|
129
|
+
transform: scale(1.05);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.onboarding-theme-dark .onboarding-modal-close {
|
|
133
|
+
background-color: #374151;
|
|
134
|
+
color: #9ca3af;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.onboarding-theme-dark .onboarding-modal-close:hover {
|
|
138
|
+
background-color: #4b5563;
|
|
139
|
+
color: #ffffff;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* Modal container adjustments */
|
|
143
|
+
.onboarding-modal-content .onboarding-container {
|
|
144
|
+
min-height: auto;
|
|
145
|
+
padding: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Light Theme */
|
|
149
|
+
.onboarding-theme-light {
|
|
150
|
+
color: #000000;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Dark Theme */
|
|
154
|
+
.onboarding-theme-dark {
|
|
155
|
+
color: #ffffff;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.onboarding-card {
|
|
159
|
+
width: 420px;
|
|
160
|
+
max-width: 420px;
|
|
161
|
+
min-width: 420px;
|
|
162
|
+
padding: 40px;
|
|
163
|
+
border-radius: 16px;
|
|
164
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
165
|
+
background-color: inherit;
|
|
166
|
+
position: relative;
|
|
167
|
+
overflow-y: auto;
|
|
168
|
+
overflow-x: hidden;
|
|
169
|
+
max-height: calc(100vh - 32px);
|
|
170
|
+
overscroll-behavior: contain;
|
|
171
|
+
transition: height 0.3s ease-in-out;
|
|
172
|
+
box-sizing: border-box;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.onboarding-card::-webkit-scrollbar {
|
|
176
|
+
width: 6px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.onboarding-card::-webkit-scrollbar-track {
|
|
180
|
+
background: transparent;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.onboarding-card::-webkit-scrollbar-thumb {
|
|
184
|
+
background: rgba(0, 0, 0, 0.2);
|
|
185
|
+
border-radius: 3px;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.onboarding-theme-dark .onboarding-card::-webkit-scrollbar-thumb {
|
|
189
|
+
background: rgba(255, 255, 255, 0.2);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@media (max-width: 480px) {
|
|
193
|
+
.onboarding-card {
|
|
194
|
+
padding: 32px 24px;
|
|
195
|
+
border-radius: 0;
|
|
196
|
+
max-height: 100vh;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.onboarding-theme-light .onboarding-card {
|
|
201
|
+
background-color: #ffffff;
|
|
202
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
203
|
+
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.onboarding-theme-dark .onboarding-card {
|
|
207
|
+
background-color: #1f2937;
|
|
208
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 0 1px rgba(255, 255, 255, 0.1);
|
|
209
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.onboarding-header {
|
|
213
|
+
text-align: center;
|
|
214
|
+
margin-bottom: 32px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.onboarding-logo-section {
|
|
218
|
+
display: flex;
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
align-items: center;
|
|
221
|
+
margin-bottom: 24px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.onboarding-logo-container {
|
|
225
|
+
display: flex;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
align-items: center;
|
|
228
|
+
margin-bottom: 6px;
|
|
229
|
+
gap: 10px;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.onboarding-logo-img {
|
|
233
|
+
max-width: 120px;
|
|
234
|
+
max-height: 60px;
|
|
235
|
+
object-fit: contain;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.onboarding-title {
|
|
239
|
+
font-size: 24px;
|
|
240
|
+
font-weight: 600;
|
|
241
|
+
margin: 0;
|
|
242
|
+
color: inherit;
|
|
243
|
+
text-align: center;
|
|
244
|
+
margin-bottom: 28px;
|
|
245
|
+
letter-spacing: -0.01em;
|
|
246
|
+
line-height: 1.4;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@media (max-width: 480px) {
|
|
250
|
+
.onboarding-title {
|
|
251
|
+
font-size: 24px;
|
|
252
|
+
margin-bottom: 24px;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.onboarding-theme-light .onboarding-title {
|
|
257
|
+
color: #111827;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.onboarding-theme-dark .onboarding-title {
|
|
261
|
+
color: #ffffff;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.onboarding-form {
|
|
265
|
+
width: 100%;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.onboarding-input-group {
|
|
269
|
+
margin-bottom: 20px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.onboarding-input-label {
|
|
273
|
+
display: block;
|
|
274
|
+
font-size: 14px;
|
|
275
|
+
font-weight: 500;
|
|
276
|
+
color: #374151;
|
|
277
|
+
margin-bottom: 8px;
|
|
278
|
+
letter-spacing: -0.01em;
|
|
279
|
+
line-height: 1.5;
|
|
280
|
+
text-align: left;
|
|
281
|
+
width: 100%;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.onboarding-theme-dark .onboarding-input-label {
|
|
285
|
+
color: #e5e7eb;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.onboarding-input-wrapper {
|
|
289
|
+
position: relative;
|
|
290
|
+
display: flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.onboarding-input-icon {
|
|
295
|
+
position: absolute;
|
|
296
|
+
left: 14px;
|
|
297
|
+
color: #9ca3af;
|
|
298
|
+
pointer-events: none;
|
|
299
|
+
z-index: 1;
|
|
300
|
+
width: 18px;
|
|
301
|
+
height: 18px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.onboarding-theme-dark .onboarding-input-icon {
|
|
305
|
+
color: #9ca3af;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.onboarding-input {
|
|
309
|
+
width: 100%;
|
|
310
|
+
padding: 10px 14px 10px 44px;
|
|
311
|
+
font-size: 14px;
|
|
312
|
+
border: 1.5px solid #e5e7eb;
|
|
313
|
+
border-radius: 8px;
|
|
314
|
+
background-color: inherit;
|
|
315
|
+
transition: all 0.15s ease;
|
|
316
|
+
box-sizing: border-box;
|
|
317
|
+
font-weight: 400;
|
|
318
|
+
letter-spacing: 0;
|
|
319
|
+
line-height: 1.5;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.onboarding-theme-light .onboarding-input {
|
|
323
|
+
background-color: #ffffff;
|
|
324
|
+
border-color: #e5e7eb;
|
|
325
|
+
color: #000000;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.onboarding-theme-dark .onboarding-input {
|
|
329
|
+
background-color: #374151;
|
|
330
|
+
color: #ffffff;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.onboarding-input:focus {
|
|
334
|
+
outline: none;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.onboarding-theme-light .onboarding-input:focus {
|
|
338
|
+
border-color: #111827;
|
|
339
|
+
box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
|
|
340
|
+
background-color: #ffffff;
|
|
341
|
+
color: #000000;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.onboarding-theme-dark .onboarding-input {
|
|
345
|
+
border-color: #4b5563;
|
|
346
|
+
background-color: #374151;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.onboarding-theme-dark .onboarding-input:focus {
|
|
350
|
+
border-color: #9ca3af;
|
|
351
|
+
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
|
352
|
+
background-color: #4b5563;
|
|
353
|
+
color: #ffffff;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.onboarding-input:disabled {
|
|
357
|
+
opacity: 0.6;
|
|
358
|
+
cursor: not-allowed;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Arrow button inside email input */
|
|
362
|
+
.onboarding-input-arrow-button {
|
|
363
|
+
position: absolute;
|
|
364
|
+
right: 8px;
|
|
365
|
+
top: 50%;
|
|
366
|
+
transform: translateY(-50%);
|
|
367
|
+
background: none;
|
|
368
|
+
border: none;
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
padding: 4px;
|
|
371
|
+
display: flex;
|
|
372
|
+
align-items: center;
|
|
373
|
+
justify-content: center;
|
|
374
|
+
z-index: 2;
|
|
375
|
+
transition: all 0.2s ease;
|
|
376
|
+
border-radius: 4px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/* Light theme arrow button */
|
|
380
|
+
.onboarding-theme-light .onboarding-input-arrow-button {
|
|
381
|
+
color: #111827;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.onboarding-theme-light .onboarding-input-arrow-button:hover {
|
|
385
|
+
background-color: rgba(17, 24, 39, 0.1);
|
|
386
|
+
color: #1f2937;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.onboarding-theme-light .onboarding-input-arrow-button:active {
|
|
390
|
+
background-color: rgba(17, 24, 39, 0.15);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Dark theme arrow button */
|
|
394
|
+
.onboarding-theme-dark .onboarding-input-arrow-button {
|
|
395
|
+
color: #9ca3af;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.onboarding-theme-dark .onboarding-input-arrow-button:hover {
|
|
399
|
+
background-color: rgba(156, 163, 175, 0.2);
|
|
400
|
+
color: #d1d5db;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.onboarding-theme-dark .onboarding-input-arrow-button:active {
|
|
404
|
+
background-color: rgba(156, 163, 175, 0.25);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.onboarding-input-arrow-button:disabled {
|
|
408
|
+
opacity: 0.5;
|
|
409
|
+
cursor: not-allowed;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.onboarding-input-arrow-icon {
|
|
413
|
+
width: 20px;
|
|
414
|
+
height: 20px;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.onboarding-error {
|
|
418
|
+
padding: 12px;
|
|
419
|
+
margin-bottom: 16px;
|
|
420
|
+
background-color: #fee2e2;
|
|
421
|
+
color: #dc2626;
|
|
422
|
+
border-radius: 8px;
|
|
423
|
+
font-size: 14px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.onboarding-theme-dark .onboarding-error {
|
|
427
|
+
background-color: #7f1d1d;
|
|
428
|
+
color: #fca5a5;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.onboarding-button {
|
|
432
|
+
width: 100%;
|
|
433
|
+
padding: 13px 24px;
|
|
434
|
+
font-size: 15px;
|
|
435
|
+
font-weight: 600;
|
|
436
|
+
border: none;
|
|
437
|
+
border-radius: 12px;
|
|
438
|
+
cursor: pointer;
|
|
439
|
+
transition: all 0.2s ease;
|
|
440
|
+
display: flex;
|
|
441
|
+
align-items: center;
|
|
442
|
+
justify-content: center;
|
|
443
|
+
gap: 8px;
|
|
444
|
+
margin-bottom: 12px;
|
|
445
|
+
letter-spacing: -0.01em;
|
|
446
|
+
line-height: 1.5;
|
|
447
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
448
|
+
min-height: 48px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.onboarding-button:disabled {
|
|
452
|
+
opacity: 0.6;
|
|
453
|
+
cursor: not-allowed;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.onboarding-button-primary {
|
|
457
|
+
font-weight: 600;
|
|
458
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Light theme button */
|
|
462
|
+
.onboarding-theme-light .onboarding-button-primary {
|
|
463
|
+
background-color: #111827;
|
|
464
|
+
color: #ffffff;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.onboarding-theme-light .onboarding-button-primary:hover:not(:disabled) {
|
|
468
|
+
background-color: #1f2937;
|
|
469
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
470
|
+
transform: translateY(-1px);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.onboarding-theme-light .onboarding-button-primary:active:not(:disabled) {
|
|
474
|
+
background-color: #374151;
|
|
475
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
476
|
+
transform: translateY(0);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Dark theme button */
|
|
480
|
+
.onboarding-theme-dark .onboarding-button-primary {
|
|
481
|
+
background-color: #4b5563;
|
|
482
|
+
color: #ffffff;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.onboarding-theme-dark .onboarding-button-primary:hover:not(:disabled) {
|
|
486
|
+
background-color: #6b7280;
|
|
487
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
488
|
+
transform: translateY(-1px);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.onboarding-theme-dark .onboarding-button-primary:active:not(:disabled) {
|
|
492
|
+
background-color: #6b7280;
|
|
493
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
494
|
+
transform: translateY(0);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.onboarding-button-google {
|
|
498
|
+
background-color: #ffffff;
|
|
499
|
+
color: #1f2937;
|
|
500
|
+
border: 1.5px solid #e5e7eb;
|
|
501
|
+
font-weight: 500;
|
|
502
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
503
|
+
font-size: 14px;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.onboarding-button-social {
|
|
507
|
+
background-color: #ffffff;
|
|
508
|
+
color: #1f2937;
|
|
509
|
+
border: 1.5px solid #e5e7eb;
|
|
510
|
+
font-weight: 500;
|
|
511
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
512
|
+
font-size: 14px;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.onboarding-theme-dark .onboarding-button-google {
|
|
516
|
+
background-color: #374151;
|
|
517
|
+
color: #ffffff;
|
|
518
|
+
border-color: #4b5563;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.onboarding-theme-dark .onboarding-button-social {
|
|
522
|
+
background-color: #2f3239;
|
|
523
|
+
color: #ffffff;
|
|
524
|
+
border-color: #4b5563;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.onboarding-button-google:hover:not(:disabled) {
|
|
528
|
+
background-color: #f9fafb;
|
|
529
|
+
border-color: #d1d5db;
|
|
530
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
531
|
+
transform: translateY(-1px);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.onboarding-button-social:hover:not(:disabled) {
|
|
535
|
+
background-color: #f9fafb;
|
|
536
|
+
border-color: #d1d5db;
|
|
537
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
538
|
+
transform: translateY(-1px);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.onboarding-theme-dark .onboarding-button-google:hover:not(:disabled) {
|
|
542
|
+
background-color: #4b5563;
|
|
543
|
+
border-color: #6b7280;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.onboarding-theme-dark .onboarding-button-social:hover:not(:disabled) {
|
|
547
|
+
background-color: #3c404a;
|
|
548
|
+
border-color: #6b7280;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.onboarding-button-social-icon {
|
|
552
|
+
width: 100%;
|
|
553
|
+
height: 48px;
|
|
554
|
+
display: inline-flex;
|
|
555
|
+
align-items: center;
|
|
556
|
+
justify-content: center;
|
|
557
|
+
border: 1.5px solid #e5e7eb;
|
|
558
|
+
border-radius: 12px;
|
|
559
|
+
background-color: #ffffff;
|
|
560
|
+
color: #1f2937;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
transition: all 0.2s ease;
|
|
563
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
564
|
+
flex: 1;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.onboarding-button-social-icon:hover:not(:disabled) {
|
|
568
|
+
background-color: #f9fafb;
|
|
569
|
+
border-color: #d1d5db;
|
|
570
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
571
|
+
transform: translateY(-1px);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.onboarding-theme-dark .onboarding-button-social-icon {
|
|
575
|
+
background-color: #2f3239;
|
|
576
|
+
border-color: #4b5563;
|
|
577
|
+
color: #ffffff;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.onboarding-theme-dark .onboarding-button-social-icon:hover:not(:disabled) {
|
|
581
|
+
background-color: #3c404a;
|
|
582
|
+
border-color: #6b7280;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.onboarding-social-grid {
|
|
586
|
+
display: flex;
|
|
587
|
+
gap: 12px;
|
|
588
|
+
flex-wrap: nowrap;
|
|
589
|
+
margin-top: 8px;
|
|
590
|
+
width: 100%;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.onboarding-social-icon {
|
|
594
|
+
width: 20px;
|
|
595
|
+
height: 20px;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.onboarding-google-icon {
|
|
599
|
+
width: 20px;
|
|
600
|
+
height: 20px;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.onboarding-divider {
|
|
604
|
+
display: flex;
|
|
605
|
+
align-items: center;
|
|
606
|
+
margin: 20px 0;
|
|
607
|
+
position: relative;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.onboarding-divider::before,
|
|
611
|
+
.onboarding-divider::after {
|
|
612
|
+
content: '';
|
|
613
|
+
flex: 1;
|
|
614
|
+
height: 1px;
|
|
615
|
+
background-color: #e5e7eb;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.onboarding-theme-dark .onboarding-divider::before,
|
|
619
|
+
.onboarding-theme-dark .onboarding-divider::after {
|
|
620
|
+
background-color: #4b5563;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.onboarding-divider-text {
|
|
624
|
+
padding: 0 16px;
|
|
625
|
+
font-size: 14px;
|
|
626
|
+
color: #6b7280;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.onboarding-theme-dark .onboarding-divider-text {
|
|
630
|
+
color: #9ca3af;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
.onboarding-footer {
|
|
635
|
+
margin-top: 16px;
|
|
636
|
+
padding: 0;
|
|
637
|
+
text-align: center;
|
|
638
|
+
background: transparent;
|
|
639
|
+
border: none;
|
|
640
|
+
border-radius: 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.onboarding-footer-text {
|
|
644
|
+
font-size: 11px;
|
|
645
|
+
color: #9ca3af;
|
|
646
|
+
margin: 0;
|
|
647
|
+
line-height: 1.5;
|
|
648
|
+
font-weight: 400;
|
|
649
|
+
letter-spacing: 0.01em;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.onboarding-theme-dark .onboarding-footer-text {
|
|
653
|
+
color: #6b7280;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.onboarding-footer-brand {
|
|
657
|
+
font-weight: 700;
|
|
658
|
+
text-decoration: none;
|
|
659
|
+
margin-left: 2px;
|
|
660
|
+
cursor: pointer;
|
|
661
|
+
transition: opacity 0.15s ease;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.onboarding-theme-light .onboarding-footer-brand {
|
|
665
|
+
color: #111827;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.onboarding-theme-dark .onboarding-footer-brand {
|
|
669
|
+
color: #9ca3af;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.onboarding-footer-brand:hover {
|
|
673
|
+
text-decoration: none;
|
|
674
|
+
opacity: 0.8;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/* Modal container adjustments */
|
|
678
|
+
.onboarding-modal-content .onboarding-container {
|
|
679
|
+
min-height: auto;
|
|
680
|
+
padding: 0;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.onboarding-modal-content .onboarding-card {
|
|
684
|
+
margin: 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* OTP Verification Screen */
|
|
688
|
+
.onboarding-otp-verification {
|
|
689
|
+
display: flex;
|
|
690
|
+
flex-direction: column;
|
|
691
|
+
align-items: center;
|
|
692
|
+
text-align: center;
|
|
693
|
+
width: 100%;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.onboarding-otp-icon-container {
|
|
697
|
+
width: 72px;
|
|
698
|
+
height: 72px;
|
|
699
|
+
border-radius: 50%;
|
|
700
|
+
display: flex;
|
|
701
|
+
align-items: center;
|
|
702
|
+
justify-content: center;
|
|
703
|
+
margin-bottom: 20px;
|
|
704
|
+
position: relative;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.onboarding-theme-light .onboarding-otp-icon-container {
|
|
708
|
+
background: linear-gradient(135deg, rgba(17, 24, 39, 0.1) 0%, rgba(17, 24, 39, 0.05) 100%);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.onboarding-theme-dark .onboarding-otp-icon-container {
|
|
712
|
+
background: linear-gradient(135deg, rgba(156, 163, 175, 0.2) 0%, rgba(156, 163, 175, 0.1) 100%);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.onboarding-otp-icon-inner {
|
|
716
|
+
width: 48px;
|
|
717
|
+
height: 48px;
|
|
718
|
+
background-color: #dc2626;
|
|
719
|
+
border-radius: 10px;
|
|
720
|
+
display: flex;
|
|
721
|
+
align-items: center;
|
|
722
|
+
justify-content: center;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.onboarding-otp-icon {
|
|
726
|
+
width: 24px;
|
|
727
|
+
height: 24px;
|
|
728
|
+
color: #ffffff;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.onboarding-otp-title {
|
|
732
|
+
font-size: 22px;
|
|
733
|
+
font-weight: 600;
|
|
734
|
+
margin: 0 0 12px 0;
|
|
735
|
+
color: inherit;
|
|
736
|
+
line-height: 1.4;
|
|
737
|
+
letter-spacing: -0.01em;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.onboarding-theme-light .onboarding-otp-title {
|
|
741
|
+
color: #111827;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.onboarding-theme-dark .onboarding-otp-title {
|
|
745
|
+
color: #ffffff;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
.onboarding-otp-instruction {
|
|
749
|
+
font-size: 14px;
|
|
750
|
+
color: #6b7280;
|
|
751
|
+
margin: 0 0 4px 0;
|
|
752
|
+
line-height: 1.5;
|
|
753
|
+
font-weight: 400;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.onboarding-theme-dark .onboarding-otp-instruction {
|
|
757
|
+
color: #9ca3af;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.onboarding-otp-email {
|
|
761
|
+
font-size: 14px;
|
|
762
|
+
font-weight: 600;
|
|
763
|
+
color: inherit;
|
|
764
|
+
margin: 0 0 24px 0;
|
|
765
|
+
line-height: 1.5;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
.onboarding-theme-light .onboarding-otp-email {
|
|
769
|
+
color: #111827;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.onboarding-theme-dark .onboarding-otp-email {
|
|
773
|
+
color: #ffffff;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.onboarding-otp-inputs-container {
|
|
777
|
+
display: flex;
|
|
778
|
+
gap: 10px;
|
|
779
|
+
justify-content: center;
|
|
780
|
+
margin-bottom: 24px;
|
|
781
|
+
width: 100%;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.onboarding-otp-input {
|
|
785
|
+
width: 44px;
|
|
786
|
+
height: 52px;
|
|
787
|
+
text-align: center;
|
|
788
|
+
font-size: 20px;
|
|
789
|
+
font-weight: 600;
|
|
790
|
+
border: 1.5px solid #e5e7eb;
|
|
791
|
+
border-radius: 8px;
|
|
792
|
+
background-color: inherit;
|
|
793
|
+
transition: all 0.15s ease;
|
|
794
|
+
box-sizing: border-box;
|
|
795
|
+
line-height: 1;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.onboarding-theme-light .onboarding-otp-input {
|
|
799
|
+
background-color: #ffffff;
|
|
800
|
+
border-color: #e5e7eb;
|
|
801
|
+
color: #000000;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.onboarding-otp-input:focus {
|
|
805
|
+
outline: none;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.onboarding-theme-light .onboarding-otp-input:focus {
|
|
809
|
+
border-color: #111827;
|
|
810
|
+
box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.1);
|
|
811
|
+
background-color: #ffffff;
|
|
812
|
+
color: #000000;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.onboarding-theme-dark .onboarding-otp-input {
|
|
816
|
+
border-color: #4b5563;
|
|
817
|
+
background-color: #374151;
|
|
818
|
+
color: #ffffff;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.onboarding-theme-dark .onboarding-otp-input:focus {
|
|
822
|
+
border-color: #9ca3af;
|
|
823
|
+
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
|
824
|
+
background-color: #4b5563;
|
|
825
|
+
color: #ffffff;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.onboarding-otp-resend {
|
|
829
|
+
text-align: center;
|
|
830
|
+
margin-top: 20px;
|
|
831
|
+
line-height: 1.5;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.onboarding-otp-resend-text {
|
|
835
|
+
font-size: 13px;
|
|
836
|
+
color: #6b7280;
|
|
837
|
+
margin: 0;
|
|
838
|
+
line-height: 1.5;
|
|
839
|
+
font-weight: 400;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.onboarding-theme-dark .onboarding-otp-resend-text {
|
|
843
|
+
color: #9ca3af;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.onboarding-otp-resend-link {
|
|
847
|
+
color: #dc2626;
|
|
848
|
+
text-decoration: underline;
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
font-weight: 500;
|
|
851
|
+
transition: color 0.15s ease;
|
|
852
|
+
font-size: 13px;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
.onboarding-otp-resend-link:hover:not(:disabled) {
|
|
856
|
+
color: #b91c1c;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.onboarding-otp-resend-link:disabled {
|
|
860
|
+
color: #9ca3af;
|
|
861
|
+
cursor: not-allowed;
|
|
862
|
+
text-decoration: none;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.onboarding-theme-dark .onboarding-otp-resend-link:disabled {
|
|
866
|
+
color: #6b7280;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/* Passkey Button Styles */
|
|
870
|
+
.onboarding-button-passkey {
|
|
871
|
+
background-color: #374151;
|
|
872
|
+
color: #ffffff;
|
|
873
|
+
border: 1.5px solid #4b5563;
|
|
874
|
+
font-weight: 500;
|
|
875
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
876
|
+
font-size: 14px;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
.onboarding-theme-dark .onboarding-button-passkey {
|
|
880
|
+
background-color: #4b5563;
|
|
881
|
+
color: #ffffff;
|
|
882
|
+
border-color: #6b7280;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.onboarding-button-passkey:hover:not(:disabled) {
|
|
886
|
+
background-color: #4b5563;
|
|
887
|
+
border-color: #6b7280;
|
|
888
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
889
|
+
transform: translateY(-1px);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.onboarding-theme-dark .onboarding-button-passkey:hover:not(:disabled) {
|
|
893
|
+
background-color: #6b7280;
|
|
894
|
+
border-color: #9ca3af;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.onboarding-passkey-icon {
|
|
898
|
+
width: 20px;
|
|
899
|
+
height: 20px;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
.onboarding-passkey-signup-link {
|
|
903
|
+
text-decoration: none;
|
|
904
|
+
cursor: pointer;
|
|
905
|
+
font-size: 14px;
|
|
906
|
+
font-weight: 500;
|
|
907
|
+
margin-top: 12px;
|
|
908
|
+
display: inline-block;
|
|
909
|
+
transition: color 0.15s ease;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.onboarding-theme-light .onboarding-passkey-signup-link {
|
|
913
|
+
color: #111827;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.onboarding-theme-light .onboarding-passkey-signup-link:hover {
|
|
917
|
+
color: #1f2937;
|
|
918
|
+
text-decoration: underline;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
.onboarding-theme-dark .onboarding-passkey-signup-link {
|
|
922
|
+
color: #9ca3af;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
.onboarding-theme-dark .onboarding-passkey-signup-link:hover {
|
|
926
|
+
color: #d1d5db;
|
|
927
|
+
text-decoration: underline;
|
|
928
|
+
}
|
|
929
|
+
`;
|
|
930
|
+
/**
|
|
931
|
+
* OnboardingUI Class
|
|
932
|
+
*
|
|
933
|
+
* A customizable user onboarding component with email-OTP and Google login options.
|
|
934
|
+
* Works with vanilla JavaScript - no framework dependencies required.
|
|
935
|
+
*/
|
|
936
|
+
export class OnboardingUIWeb {
|
|
937
|
+
static stylesInjected = false;
|
|
938
|
+
config;
|
|
939
|
+
container = null;
|
|
940
|
+
rootElement = null;
|
|
941
|
+
modalOverlay = null;
|
|
942
|
+
modalContent = null;
|
|
943
|
+
emailInput = null;
|
|
944
|
+
otpInput = null;
|
|
945
|
+
otpInputs = [];
|
|
946
|
+
emailForm = null;
|
|
947
|
+
continueButton = null;
|
|
948
|
+
emailArrowButton = null;
|
|
949
|
+
googleButton = null;
|
|
950
|
+
twitterButton = null;
|
|
951
|
+
discordButton = null;
|
|
952
|
+
passkeyLoginButton = null;
|
|
953
|
+
passkeySignupLink = null;
|
|
954
|
+
passkeyDivider = null;
|
|
955
|
+
passkeyErrorElement = null;
|
|
956
|
+
errorElement = null;
|
|
957
|
+
otpGroup = null;
|
|
958
|
+
otpVerificationScreen = null;
|
|
959
|
+
closeButton = null;
|
|
960
|
+
resendButton = null;
|
|
961
|
+
socialGrid = null;
|
|
962
|
+
externalWalletContainer = null;
|
|
963
|
+
externalWalletDivider = null;
|
|
964
|
+
divider = null;
|
|
965
|
+
footer = null;
|
|
966
|
+
verifyButton = null;
|
|
967
|
+
email = '';
|
|
968
|
+
otp = '';
|
|
969
|
+
otpSent = false;
|
|
970
|
+
loading = false;
|
|
971
|
+
activeButton = null;
|
|
972
|
+
resendCooldown = 0;
|
|
973
|
+
externalWalletsEnabled = false;
|
|
974
|
+
constructor(config, externalWalletsEnabled = false) {
|
|
975
|
+
this.externalWalletsEnabled = externalWalletsEnabled;
|
|
976
|
+
const defaultTheme = config.theme || 'light';
|
|
977
|
+
this.config = {
|
|
978
|
+
theme: defaultTheme,
|
|
979
|
+
showFooter: config.showFooter !== false,
|
|
980
|
+
className: config.className || '',
|
|
981
|
+
modal: config.modal !== false,
|
|
982
|
+
closeOnBackdropClick: config.closeOnBackdropClick !== false,
|
|
983
|
+
showCloseButton: config.showCloseButton !== false,
|
|
984
|
+
onboardTitle: config.onboardTitle || 'Sign in',
|
|
985
|
+
handleUrlParams: config.handleUrlParams !== false,
|
|
986
|
+
...config,
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Show external wallet container and divider
|
|
991
|
+
* Call this before init() to prevent flicker when external wallets are enabled
|
|
992
|
+
*/
|
|
993
|
+
showExternalWallets() {
|
|
994
|
+
if (this.externalWalletContainer) {
|
|
995
|
+
this.externalWalletContainer.style.display = '';
|
|
996
|
+
}
|
|
997
|
+
if (this.externalWalletDivider) {
|
|
998
|
+
this.externalWalletDivider.style.display = '';
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Initialize and render the component
|
|
1003
|
+
*/
|
|
1004
|
+
init() {
|
|
1005
|
+
this.injectStyles();
|
|
1006
|
+
this.setupContainer();
|
|
1007
|
+
this.render();
|
|
1008
|
+
this.attachEventListeners();
|
|
1009
|
+
this.checkUrlParams();
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Check URL parameters for social login callbacks
|
|
1013
|
+
*/
|
|
1014
|
+
checkUrlParams() {
|
|
1015
|
+
if (typeof window === 'undefined')
|
|
1016
|
+
return;
|
|
1017
|
+
if (!this.config.handleUrlParams)
|
|
1018
|
+
return;
|
|
1019
|
+
const params = new URLSearchParams(window.location.search);
|
|
1020
|
+
const success = params.get('success');
|
|
1021
|
+
const error = params.get('error');
|
|
1022
|
+
// If success=true, always show loading (less restrictive)
|
|
1023
|
+
if (success === 'true') {
|
|
1024
|
+
// Ensure modal is open if in modal mode
|
|
1025
|
+
if (this.config.modal && this.modalOverlay) {
|
|
1026
|
+
this.modalOverlay.classList.remove('onboarding-modal-closing');
|
|
1027
|
+
this.modalOverlay.classList.add('onboarding-modal-open');
|
|
1028
|
+
this.modalOverlay.style.display = 'flex';
|
|
1029
|
+
document.body.style.overflow = 'hidden';
|
|
1030
|
+
}
|
|
1031
|
+
this.showLoadingScreen();
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
// Check login source to decide if we should handle this (for errors)
|
|
1035
|
+
const source = localStorage.getItem('abstraxn_login_source');
|
|
1036
|
+
const isModal = this.config.modal;
|
|
1037
|
+
if (source) {
|
|
1038
|
+
if (source === 'modal' && !isModal)
|
|
1039
|
+
return; // Modal initiated, I am Inline -> Ignore
|
|
1040
|
+
if (source === 'inline' && isModal)
|
|
1041
|
+
return; // Inline initiated, I am Modal -> Ignore
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
// No source (e.g. unknown origin), default to Modal handling it
|
|
1045
|
+
if (!isModal)
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (error) {
|
|
1049
|
+
this.showError(decodeURIComponent(error));
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Show error screen or modal (public method)
|
|
1054
|
+
*/
|
|
1055
|
+
showError(message) {
|
|
1056
|
+
// Hide loading modal if it exists
|
|
1057
|
+
this.hideLoadingModal();
|
|
1058
|
+
if (this.config.modal) {
|
|
1059
|
+
this.showErrorModal(message);
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
this.showErrorScreen(message);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Show error screen
|
|
1067
|
+
*/
|
|
1068
|
+
showErrorScreen(message) {
|
|
1069
|
+
if (!this.rootElement)
|
|
1070
|
+
return;
|
|
1071
|
+
// Hide loading modal if it exists (for inline components)
|
|
1072
|
+
this.hideLoadingModal();
|
|
1073
|
+
const card = this.rootElement.querySelector('.onboarding-card');
|
|
1074
|
+
if (!card)
|
|
1075
|
+
return;
|
|
1076
|
+
// Add error mode class to card
|
|
1077
|
+
card.classList.add('onboarding-mode-error');
|
|
1078
|
+
card.classList.remove('onboarding-mode-loading');
|
|
1079
|
+
// Create or show error container
|
|
1080
|
+
let errorContainer = this.rootElement.querySelector('.onboarding-error-container');
|
|
1081
|
+
if (!errorContainer) {
|
|
1082
|
+
errorContainer = this.createElement('div', {
|
|
1083
|
+
className: 'onboarding-error-container',
|
|
1084
|
+
style: 'display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px 0; text-align: center;',
|
|
1085
|
+
});
|
|
1086
|
+
// Error Icon
|
|
1087
|
+
const iconContainer = this.createElement('div', {
|
|
1088
|
+
style: 'width: 64px; height: 64px; background-color: #fee2e2; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px;',
|
|
1089
|
+
});
|
|
1090
|
+
// Dark mode adjustment for icon container
|
|
1091
|
+
if (this.config.theme === 'dark') {
|
|
1092
|
+
iconContainer.style.backgroundColor = 'rgba(220, 38, 38, 0.2)';
|
|
1093
|
+
}
|
|
1094
|
+
const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1095
|
+
iconSvg.setAttribute('width', '32');
|
|
1096
|
+
iconSvg.setAttribute('height', '32');
|
|
1097
|
+
iconSvg.setAttribute('viewBox', '0 0 24 24');
|
|
1098
|
+
iconSvg.setAttribute('fill', 'none');
|
|
1099
|
+
iconSvg.setAttribute('stroke', '#dc2626');
|
|
1100
|
+
iconSvg.setAttribute('stroke-width', '2');
|
|
1101
|
+
iconSvg.setAttribute('stroke-linecap', 'round');
|
|
1102
|
+
iconSvg.setAttribute('stroke-linejoin', 'round');
|
|
1103
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
1104
|
+
circle.setAttribute('cx', '12');
|
|
1105
|
+
circle.setAttribute('cy', '12');
|
|
1106
|
+
circle.setAttribute('r', '10');
|
|
1107
|
+
iconSvg.appendChild(circle);
|
|
1108
|
+
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
1109
|
+
line1.setAttribute('x1', '12');
|
|
1110
|
+
line1.setAttribute('y1', '8');
|
|
1111
|
+
line1.setAttribute('x2', '12');
|
|
1112
|
+
line1.setAttribute('y2', '12');
|
|
1113
|
+
iconSvg.appendChild(line1);
|
|
1114
|
+
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
1115
|
+
line2.setAttribute('x1', '12');
|
|
1116
|
+
line2.setAttribute('y1', '16');
|
|
1117
|
+
line2.setAttribute('x2', '12.01');
|
|
1118
|
+
line2.setAttribute('y2', '16');
|
|
1119
|
+
iconSvg.appendChild(line2);
|
|
1120
|
+
iconContainer.appendChild(iconSvg);
|
|
1121
|
+
errorContainer.appendChild(iconContainer);
|
|
1122
|
+
// Title
|
|
1123
|
+
const title = this.createElement('h3', {
|
|
1124
|
+
textContent: 'Authentication Failed',
|
|
1125
|
+
style: 'font-size: 20px; font-weight: 600; margin: 0 0 12px 0; color: inherit;',
|
|
1126
|
+
});
|
|
1127
|
+
errorContainer.appendChild(title);
|
|
1128
|
+
// Message
|
|
1129
|
+
const msg = this.createElement('p', {
|
|
1130
|
+
textContent: message,
|
|
1131
|
+
style: 'font-size: 14px; color: #6b7280; margin: 0 0 24px 0; line-height: 1.5; max-width: 100%; word-break: break-word;',
|
|
1132
|
+
});
|
|
1133
|
+
if (this.config.theme === 'dark') {
|
|
1134
|
+
msg.style.color = '#9ca3af';
|
|
1135
|
+
}
|
|
1136
|
+
errorContainer.appendChild(msg);
|
|
1137
|
+
// OK Button
|
|
1138
|
+
const okButton = this.createElement('button', {
|
|
1139
|
+
className: 'onboarding-button onboarding-button-primary',
|
|
1140
|
+
textContent: 'OK',
|
|
1141
|
+
style: 'width: 100%; max-width: 200px;',
|
|
1142
|
+
});
|
|
1143
|
+
okButton.addEventListener('click', () => {
|
|
1144
|
+
if (this.config.modal) {
|
|
1145
|
+
this.close();
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
this.resetToLoginForm();
|
|
1149
|
+
}
|
|
1150
|
+
// Also clear URL params to prevent showing error again on refresh
|
|
1151
|
+
const url = new URL(window.location.href);
|
|
1152
|
+
url.searchParams.delete('error');
|
|
1153
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
1154
|
+
});
|
|
1155
|
+
errorContainer.appendChild(okButton);
|
|
1156
|
+
card.appendChild(errorContainer);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
// Update message if container exists
|
|
1160
|
+
const msgEl = errorContainer.querySelector('p');
|
|
1161
|
+
if (msgEl)
|
|
1162
|
+
msgEl.textContent = message;
|
|
1163
|
+
errorContainer.style.display = 'flex';
|
|
1164
|
+
}
|
|
1165
|
+
// Move footer to the bottom
|
|
1166
|
+
if (this.footer) {
|
|
1167
|
+
card.appendChild(this.footer);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Show loading screen
|
|
1172
|
+
*/
|
|
1173
|
+
showLoadingScreen() {
|
|
1174
|
+
if (!this.rootElement)
|
|
1175
|
+
return;
|
|
1176
|
+
// If inline component, show loading as a modal overlay
|
|
1177
|
+
if (!this.config.modal) {
|
|
1178
|
+
this.showLoadingModal();
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
// Hide the main modal overlay to prevent showing wallet options below loading screen
|
|
1182
|
+
if (this.modalOverlay) {
|
|
1183
|
+
this.modalOverlay.style.display = 'none';
|
|
1184
|
+
}
|
|
1185
|
+
// Show loading modal overlay instead (with higher z-index)
|
|
1186
|
+
this.showLoadingModal();
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Show loading screen as a modal overlay (for inline components)
|
|
1190
|
+
*/
|
|
1191
|
+
showLoadingModal() {
|
|
1192
|
+
// Hide any other modals that might be showing (wallet selection, etc.)
|
|
1193
|
+
const otherModals = document.querySelectorAll('.onboarding-modal-overlay, .external-wallet-modal, .wallet-modal-overlay');
|
|
1194
|
+
otherModals.forEach((modal) => {
|
|
1195
|
+
if (modal.id !== 'onboarding-loading-modal-overlay') {
|
|
1196
|
+
modal.style.display = 'none';
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
// Create modal overlay if it doesn't exist
|
|
1200
|
+
let loadingModalOverlay = document.getElementById('onboarding-loading-modal-overlay');
|
|
1201
|
+
if (!loadingModalOverlay) {
|
|
1202
|
+
loadingModalOverlay = this.createElement('div', {
|
|
1203
|
+
id: 'onboarding-loading-modal-overlay',
|
|
1204
|
+
style: `
|
|
1205
|
+
position: fixed;
|
|
1206
|
+
top: 0;
|
|
1207
|
+
left: 0;
|
|
1208
|
+
right: 0;
|
|
1209
|
+
bottom: 0;
|
|
1210
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
1211
|
+
display: flex;
|
|
1212
|
+
align-items: center;
|
|
1213
|
+
justify-content: center;
|
|
1214
|
+
z-index: 9999999;
|
|
1215
|
+
backdrop-filter: blur(4px);
|
|
1216
|
+
`,
|
|
1217
|
+
});
|
|
1218
|
+
const loadingCard = this.createElement('div', {
|
|
1219
|
+
className: `onboarding-card onboarding-theme-${this.config.theme}`,
|
|
1220
|
+
style: `
|
|
1221
|
+
background: ${this.config.theme === 'dark' ? '#1f2937' : 'white'};
|
|
1222
|
+
border-radius: 16px;
|
|
1223
|
+
padding: 32px 32px 28px;
|
|
1224
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
1225
|
+
display: flex;
|
|
1226
|
+
flex-direction: column;
|
|
1227
|
+
align-items: center;
|
|
1228
|
+
justify-content: center;
|
|
1229
|
+
width: 420px;
|
|
1230
|
+
max-width: 420px;
|
|
1231
|
+
min-width: 420px;
|
|
1232
|
+
gap: 20px;
|
|
1233
|
+
box-sizing: border-box;
|
|
1234
|
+
`,
|
|
1235
|
+
});
|
|
1236
|
+
// Header with logo
|
|
1237
|
+
const header = this.createElement('div', {
|
|
1238
|
+
className: 'onboarding-header',
|
|
1239
|
+
style: 'display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%;',
|
|
1240
|
+
});
|
|
1241
|
+
const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
|
|
1242
|
+
if (this.config.logo) {
|
|
1243
|
+
const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
|
|
1244
|
+
const logoImg = this.createElement('img', {
|
|
1245
|
+
src: this.config.logo,
|
|
1246
|
+
alt: 'Abstraxn',
|
|
1247
|
+
className: 'onboarding-logo-img',
|
|
1248
|
+
});
|
|
1249
|
+
logoContainer.appendChild(logoImg);
|
|
1250
|
+
logoSection.appendChild(logoContainer);
|
|
1251
|
+
}
|
|
1252
|
+
header.appendChild(logoSection);
|
|
1253
|
+
loadingCard.appendChild(header);
|
|
1254
|
+
// Spinner
|
|
1255
|
+
const spinnerColor = this.config.theme === 'dark' ? '#9ca3af' : '#111827';
|
|
1256
|
+
const spinnerBorderColor = this.config.theme === 'dark' ? 'rgba(156, 163, 175, 0.2)' : 'rgba(17, 24, 39, 0.1)';
|
|
1257
|
+
const spinner = this.createElement('div', {
|
|
1258
|
+
className: 'onboarding-loading-spinner',
|
|
1259
|
+
style: `
|
|
1260
|
+
width: 40px;
|
|
1261
|
+
height: 40px;
|
|
1262
|
+
border: 3px solid ${spinnerBorderColor};
|
|
1263
|
+
border-radius: 50%;
|
|
1264
|
+
border-top-color: ${spinnerColor};
|
|
1265
|
+
animation: onboarding-spin 1s ease-in-out infinite;
|
|
1266
|
+
margin-bottom: 16px;
|
|
1267
|
+
`,
|
|
1268
|
+
});
|
|
1269
|
+
// Add keyframes if not exists
|
|
1270
|
+
if (!document.getElementById('onboarding-spin-style')) {
|
|
1271
|
+
const style = document.createElement('style');
|
|
1272
|
+
style.id = 'onboarding-spin-style';
|
|
1273
|
+
style.textContent = `
|
|
1274
|
+
@keyframes onboarding-spin {
|
|
1275
|
+
to { transform: rotate(360deg); }
|
|
1276
|
+
}
|
|
1277
|
+
`;
|
|
1278
|
+
document.head.appendChild(style);
|
|
1279
|
+
}
|
|
1280
|
+
// Text
|
|
1281
|
+
const text = this.createElement('p', {
|
|
1282
|
+
textContent: 'Verifying login...',
|
|
1283
|
+
style: `
|
|
1284
|
+
font-size: 16px;
|
|
1285
|
+
color: ${this.config.theme === 'dark' ? '#e5e7eb' : '#374151'};
|
|
1286
|
+
margin: 0;
|
|
1287
|
+
font-weight: 500;
|
|
1288
|
+
`,
|
|
1289
|
+
});
|
|
1290
|
+
loadingCard.appendChild(spinner);
|
|
1291
|
+
loadingCard.appendChild(text);
|
|
1292
|
+
// Footer "Powered by Abstraxn"
|
|
1293
|
+
if (this.config.showFooter !== false) {
|
|
1294
|
+
const textColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
|
|
1295
|
+
const brandColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
|
|
1296
|
+
const footer = this.createElement('div', { className: 'onboarding-footer' });
|
|
1297
|
+
const footerText = this.createElement('p', {
|
|
1298
|
+
className: 'onboarding-footer-text',
|
|
1299
|
+
style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 11px; margin: 0;`,
|
|
1300
|
+
});
|
|
1301
|
+
const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
|
|
1302
|
+
footerText.appendChild(poweredBySpan);
|
|
1303
|
+
const brandContainer = this.createElement('a', {
|
|
1304
|
+
href: 'https://www.abstraxn.com/',
|
|
1305
|
+
target: '_blank',
|
|
1306
|
+
rel: 'noopener noreferrer',
|
|
1307
|
+
style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
|
|
1308
|
+
'aria-label': 'Visit Abstraxn website',
|
|
1309
|
+
});
|
|
1310
|
+
const logoSvg = this.createElement('span', {
|
|
1311
|
+
innerHTML: `
|
|
1312
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
|
|
1313
|
+
<path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
|
|
1314
|
+
<path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781)"/>
|
|
1315
|
+
<defs>
|
|
1316
|
+
<linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
|
|
1317
|
+
<stop stop-color="#F878D2"/>
|
|
1318
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
1319
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
1320
|
+
</linearGradient>
|
|
1321
|
+
<linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
|
|
1322
|
+
<stop stop-color="#F878D2"/>
|
|
1323
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
1324
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
1325
|
+
</linearGradient>
|
|
1326
|
+
</defs>
|
|
1327
|
+
</svg>
|
|
1328
|
+
`,
|
|
1329
|
+
style: 'display: inline-flex; align-items: center;',
|
|
1330
|
+
});
|
|
1331
|
+
brandContainer.appendChild(logoSvg);
|
|
1332
|
+
const brandText = this.createElement('span', {
|
|
1333
|
+
textContent: 'abstraxn',
|
|
1334
|
+
style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
|
|
1335
|
+
});
|
|
1336
|
+
brandContainer.appendChild(brandText);
|
|
1337
|
+
footerText.appendChild(brandContainer);
|
|
1338
|
+
footer.appendChild(footerText);
|
|
1339
|
+
loadingCard.appendChild(footer);
|
|
1340
|
+
}
|
|
1341
|
+
loadingModalOverlay.appendChild(loadingCard);
|
|
1342
|
+
document.body.appendChild(loadingModalOverlay);
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
loadingModalOverlay.style.display = 'flex';
|
|
1346
|
+
}
|
|
1347
|
+
// Prevent body scroll
|
|
1348
|
+
document.body.style.overflow = 'hidden';
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Hide loading modal overlay
|
|
1352
|
+
*/
|
|
1353
|
+
hideLoadingModal() {
|
|
1354
|
+
const loadingModalOverlay = document.getElementById('onboarding-loading-modal-overlay');
|
|
1355
|
+
if (loadingModalOverlay) {
|
|
1356
|
+
loadingModalOverlay.style.display = 'none';
|
|
1357
|
+
document.body.style.overflow = '';
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Show error screen as a modal overlay (for inline components)
|
|
1362
|
+
*/
|
|
1363
|
+
showErrorModal(message) {
|
|
1364
|
+
// Helper function to close error modal
|
|
1365
|
+
const closeErrorModal = () => {
|
|
1366
|
+
const overlay = document.getElementById('onboarding-error-modal-overlay');
|
|
1367
|
+
if (overlay) {
|
|
1368
|
+
overlay.style.display = 'none';
|
|
1369
|
+
document.body.style.overflow = '';
|
|
1370
|
+
// Clear URL params
|
|
1371
|
+
const url = new URL(window.location.href);
|
|
1372
|
+
url.searchParams.delete('error');
|
|
1373
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
// Create modal overlay if it doesn't exist
|
|
1377
|
+
let errorModalOverlay = document.getElementById('onboarding-error-modal-overlay');
|
|
1378
|
+
if (!errorModalOverlay) {
|
|
1379
|
+
errorModalOverlay = this.createElement('div', {
|
|
1380
|
+
id: 'onboarding-error-modal-overlay',
|
|
1381
|
+
style: `
|
|
1382
|
+
position: fixed;
|
|
1383
|
+
top: 0;
|
|
1384
|
+
left: 0;
|
|
1385
|
+
right: 0;
|
|
1386
|
+
bottom: 0;
|
|
1387
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
1388
|
+
display: flex;
|
|
1389
|
+
align-items: center;
|
|
1390
|
+
justify-content: center;
|
|
1391
|
+
z-index: 999999;
|
|
1392
|
+
backdrop-filter: blur(4px);
|
|
1393
|
+
`,
|
|
1394
|
+
});
|
|
1395
|
+
// Add backdrop click handler to close modal
|
|
1396
|
+
errorModalOverlay.addEventListener('click', (e) => {
|
|
1397
|
+
if (e.target === errorModalOverlay) {
|
|
1398
|
+
closeErrorModal();
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
const errorCard = this.createElement('div', {
|
|
1402
|
+
className: `onboarding-card onboarding-theme-${this.config.theme}`,
|
|
1403
|
+
style: `
|
|
1404
|
+
background: ${this.config.theme === 'dark' ? '#1f2937' : 'white'};
|
|
1405
|
+
border-radius: 16px;
|
|
1406
|
+
padding: 40px;
|
|
1407
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
1408
|
+
display: flex;
|
|
1409
|
+
flex-direction: column;
|
|
1410
|
+
align-items: center;
|
|
1411
|
+
justify-content: center;
|
|
1412
|
+
width: 420px;
|
|
1413
|
+
max-width: 420px;
|
|
1414
|
+
min-width: 420px;
|
|
1415
|
+
text-align: center;
|
|
1416
|
+
position: relative;
|
|
1417
|
+
box-sizing: border-box;
|
|
1418
|
+
`,
|
|
1419
|
+
});
|
|
1420
|
+
// Prevent clicks on card from closing modal
|
|
1421
|
+
errorCard.addEventListener('click', (e) => {
|
|
1422
|
+
e.stopPropagation();
|
|
1423
|
+
});
|
|
1424
|
+
// Header with logo
|
|
1425
|
+
const header = this.createElement('div', {
|
|
1426
|
+
className: 'onboarding-header',
|
|
1427
|
+
style: 'display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; margin-bottom: 24px;',
|
|
1428
|
+
});
|
|
1429
|
+
const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
|
|
1430
|
+
if (this.config.logo) {
|
|
1431
|
+
const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
|
|
1432
|
+
const logoImg = this.createElement('img', {
|
|
1433
|
+
src: this.config.logo,
|
|
1434
|
+
alt: 'Abstraxn',
|
|
1435
|
+
className: 'onboarding-logo-img',
|
|
1436
|
+
});
|
|
1437
|
+
logoContainer.appendChild(logoImg);
|
|
1438
|
+
logoSection.appendChild(logoContainer);
|
|
1439
|
+
}
|
|
1440
|
+
header.appendChild(logoSection);
|
|
1441
|
+
errorCard.appendChild(header);
|
|
1442
|
+
// Error Icon
|
|
1443
|
+
const iconContainer = this.createElement('div', {
|
|
1444
|
+
style: 'width: 64px; height: 64px; background-color: #fee2e2; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 20px;',
|
|
1445
|
+
});
|
|
1446
|
+
if (this.config.theme === 'dark') {
|
|
1447
|
+
iconContainer.style.backgroundColor = 'rgba(220, 38, 38, 0.2)';
|
|
1448
|
+
}
|
|
1449
|
+
const iconSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1450
|
+
iconSvg.setAttribute('width', '32');
|
|
1451
|
+
iconSvg.setAttribute('height', '32');
|
|
1452
|
+
iconSvg.setAttribute('viewBox', '0 0 24 24');
|
|
1453
|
+
iconSvg.setAttribute('fill', 'none');
|
|
1454
|
+
iconSvg.setAttribute('stroke', '#dc2626');
|
|
1455
|
+
iconSvg.setAttribute('stroke-width', '2');
|
|
1456
|
+
iconSvg.setAttribute('stroke-linecap', 'round');
|
|
1457
|
+
iconSvg.setAttribute('stroke-linejoin', 'round');
|
|
1458
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
1459
|
+
circle.setAttribute('cx', '12');
|
|
1460
|
+
circle.setAttribute('cy', '12');
|
|
1461
|
+
circle.setAttribute('r', '10');
|
|
1462
|
+
iconSvg.appendChild(circle);
|
|
1463
|
+
const line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
1464
|
+
line1.setAttribute('x1', '12');
|
|
1465
|
+
line1.setAttribute('y1', '8');
|
|
1466
|
+
line1.setAttribute('x2', '12');
|
|
1467
|
+
line1.setAttribute('y2', '12');
|
|
1468
|
+
iconSvg.appendChild(line1);
|
|
1469
|
+
const line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
1470
|
+
line2.setAttribute('x1', '12');
|
|
1471
|
+
line2.setAttribute('y1', '16');
|
|
1472
|
+
line2.setAttribute('x2', '12.01');
|
|
1473
|
+
line2.setAttribute('y2', '16');
|
|
1474
|
+
iconSvg.appendChild(line2);
|
|
1475
|
+
iconContainer.appendChild(iconSvg);
|
|
1476
|
+
errorCard.appendChild(iconContainer);
|
|
1477
|
+
// Title
|
|
1478
|
+
const title = this.createElement('h3', {
|
|
1479
|
+
textContent: 'Authentication Failed',
|
|
1480
|
+
style: `font-size: 20px; font-weight: 600; margin: 0 0 12px 0; color: ${this.config.theme === 'dark' ? '#ffffff' : '#111827'};`,
|
|
1481
|
+
});
|
|
1482
|
+
errorCard.appendChild(title);
|
|
1483
|
+
// Message
|
|
1484
|
+
const msg = this.createElement('p', {
|
|
1485
|
+
textContent: message,
|
|
1486
|
+
style: `font-size: 14px; color: ${this.config.theme === 'dark' ? '#e5e7eb' : '#374151'}; margin: 0 0 24px 0; line-height: 1.5; max-width: 100%; word-break: break-word;`,
|
|
1487
|
+
});
|
|
1488
|
+
errorCard.appendChild(msg);
|
|
1489
|
+
// OK Button
|
|
1490
|
+
const okButton = this.createElement('button', {
|
|
1491
|
+
className: 'onboarding-button onboarding-button-primary',
|
|
1492
|
+
textContent: 'OK',
|
|
1493
|
+
style: 'width: 100%; max-width: 200px;',
|
|
1494
|
+
});
|
|
1495
|
+
okButton.addEventListener('click', closeErrorModal);
|
|
1496
|
+
errorCard.appendChild(okButton);
|
|
1497
|
+
// Footer "Powered by Abstraxn"
|
|
1498
|
+
if (this.config.showFooter !== false) {
|
|
1499
|
+
const textColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
|
|
1500
|
+
const brandColor = this.config.colors?.text || (this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
|
|
1501
|
+
const footer = this.createElement('div', { className: 'onboarding-footer' });
|
|
1502
|
+
const footerText = this.createElement('p', {
|
|
1503
|
+
className: 'onboarding-footer-text',
|
|
1504
|
+
style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 11px; margin: 0; margin-top: 24px;`,
|
|
1505
|
+
});
|
|
1506
|
+
const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
|
|
1507
|
+
footerText.appendChild(poweredBySpan);
|
|
1508
|
+
const brandContainer = this.createElement('a', {
|
|
1509
|
+
href: 'https://www.abstraxn.com/',
|
|
1510
|
+
target: '_blank',
|
|
1511
|
+
rel: 'noopener noreferrer',
|
|
1512
|
+
style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
|
|
1513
|
+
'aria-label': 'Visit Abstraxn website',
|
|
1514
|
+
});
|
|
1515
|
+
const logoSvg = this.createElement('span', {
|
|
1516
|
+
innerHTML: `
|
|
1517
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
|
|
1518
|
+
<path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_error_modal)"/>
|
|
1519
|
+
<path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_error_modal)"/>
|
|
1520
|
+
<defs>
|
|
1521
|
+
<linearGradient id="paint0_linear_error_modal" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
|
|
1522
|
+
<stop stop-color="#F878D2"/>
|
|
1523
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
1524
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
1525
|
+
</linearGradient>
|
|
1526
|
+
<linearGradient id="paint1_linear_error_modal" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
|
|
1527
|
+
<stop stop-color="#F878D2"/>
|
|
1528
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
1529
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
1530
|
+
</linearGradient>
|
|
1531
|
+
</defs>
|
|
1532
|
+
</svg>
|
|
1533
|
+
`,
|
|
1534
|
+
style: 'display: inline-flex; align-items: center;',
|
|
1535
|
+
});
|
|
1536
|
+
brandContainer.appendChild(logoSvg);
|
|
1537
|
+
const brandText = this.createElement('span', {
|
|
1538
|
+
textContent: 'abstraxn',
|
|
1539
|
+
style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
|
|
1540
|
+
});
|
|
1541
|
+
brandContainer.appendChild(brandText);
|
|
1542
|
+
footerText.appendChild(brandContainer);
|
|
1543
|
+
footer.appendChild(footerText);
|
|
1544
|
+
errorCard.appendChild(footer);
|
|
1545
|
+
}
|
|
1546
|
+
errorModalOverlay.appendChild(errorCard);
|
|
1547
|
+
document.body.appendChild(errorModalOverlay);
|
|
1548
|
+
}
|
|
1549
|
+
else {
|
|
1550
|
+
// Update message if overlay exists
|
|
1551
|
+
const msgEl = errorModalOverlay.querySelector('p');
|
|
1552
|
+
if (msgEl)
|
|
1553
|
+
msgEl.textContent = message;
|
|
1554
|
+
errorModalOverlay.style.display = 'flex';
|
|
1555
|
+
}
|
|
1556
|
+
// Prevent body scroll
|
|
1557
|
+
document.body.style.overflow = 'hidden';
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Inject CSS styles into the document head (only once)
|
|
1561
|
+
*/
|
|
1562
|
+
injectStyles() {
|
|
1563
|
+
if (OnboardingUIWeb.stylesInjected) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
// Check if styles are already injected
|
|
1567
|
+
const existingStyle = document.getElementById('onboarding-ui-styles');
|
|
1568
|
+
if (existingStyle) {
|
|
1569
|
+
OnboardingUIWeb.stylesInjected = true;
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
// Create and inject style element
|
|
1573
|
+
const styleElement = document.createElement('style');
|
|
1574
|
+
styleElement.id = 'onboarding-ui-styles';
|
|
1575
|
+
styleElement.textContent = ONBOARDING_UI_STYLES;
|
|
1576
|
+
document.head.appendChild(styleElement);
|
|
1577
|
+
// Inject custom CSS if provided
|
|
1578
|
+
if (this.config.customCSS) {
|
|
1579
|
+
const customStyleElement = document.createElement('style');
|
|
1580
|
+
customStyleElement.id = 'onboarding-ui-custom-styles';
|
|
1581
|
+
customStyleElement.textContent = this.config.customCSS;
|
|
1582
|
+
document.head.appendChild(customStyleElement);
|
|
1583
|
+
}
|
|
1584
|
+
// Apply custom colors if provided
|
|
1585
|
+
if (this.config.colors) {
|
|
1586
|
+
this.applyCustomColors();
|
|
1587
|
+
}
|
|
1588
|
+
OnboardingUIWeb.stylesInjected = true;
|
|
1589
|
+
// Inject utility styles for loading/error modes
|
|
1590
|
+
const utilityStyle = document.createElement('style');
|
|
1591
|
+
utilityStyle.id = 'onboarding-utility-styles';
|
|
1592
|
+
utilityStyle.textContent = `
|
|
1593
|
+
.onboarding-card.onboarding-mode-loading > *:not(.onboarding-loading-container):not(.onboarding-header):not(.onboarding-footer),
|
|
1594
|
+
.onboarding-card.onboarding-mode-error > *:not(.onboarding-error-container):not(.onboarding-header):not(.onboarding-footer) {
|
|
1595
|
+
display: none !important;
|
|
1596
|
+
}
|
|
1597
|
+
`;
|
|
1598
|
+
document.head.appendChild(utilityStyle);
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Apply custom colors from config
|
|
1602
|
+
*/
|
|
1603
|
+
applyCustomColors() {
|
|
1604
|
+
if (!this.config.colors)
|
|
1605
|
+
return;
|
|
1606
|
+
const root = this.rootElement || document.documentElement;
|
|
1607
|
+
const style = document.createElement('style');
|
|
1608
|
+
style.id = 'onboarding-ui-colors';
|
|
1609
|
+
let css = '';
|
|
1610
|
+
if (this.config.colors.primary) {
|
|
1611
|
+
css += `.onboarding-button-primary { background-color: ${this.config.colors.primary} !important; background: ${this.config.colors.primary} !important; }`;
|
|
1612
|
+
css += `.onboarding-footer-brand { color: ${this.config.colors.primary} !important; }`;
|
|
1613
|
+
css += `.onboarding-passkey-signup-link { color: ${this.config.colors.primary} !important; }`;
|
|
1614
|
+
}
|
|
1615
|
+
if (this.config.colors.primaryHover) {
|
|
1616
|
+
css += `.onboarding-button-primary:hover:not(:disabled) { background-color: ${this.config.colors.primaryHover} !important; background: ${this.config.colors.primaryHover} !important; }`;
|
|
1617
|
+
}
|
|
1618
|
+
if (this.config.colors.background) {
|
|
1619
|
+
css += `.onboarding-card { background-color: ${this.config.colors.background} !important; }`;
|
|
1620
|
+
}
|
|
1621
|
+
if (this.config.colors.text) {
|
|
1622
|
+
css += `.onboarding-card { color: ${this.config.colors.text} !important; }`;
|
|
1623
|
+
}
|
|
1624
|
+
if (this.config.colors.border) {
|
|
1625
|
+
css += `.onboarding-input { border-color: ${this.config.colors.border} !important; }`;
|
|
1626
|
+
}
|
|
1627
|
+
if (this.config.colors.error) {
|
|
1628
|
+
css += `.onboarding-error { background-color: ${this.config.colors.error} !important; }`;
|
|
1629
|
+
}
|
|
1630
|
+
style.textContent = css;
|
|
1631
|
+
document.head.appendChild(style);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Setup the container element
|
|
1635
|
+
*/
|
|
1636
|
+
setupContainer() {
|
|
1637
|
+
// For modal mode, always use body
|
|
1638
|
+
if (this.config.modal) {
|
|
1639
|
+
this.container = document.body;
|
|
1640
|
+
}
|
|
1641
|
+
else {
|
|
1642
|
+
if (this.config.container) {
|
|
1643
|
+
if (typeof this.config.container === 'string') {
|
|
1644
|
+
this.container = document.querySelector(this.config.container);
|
|
1645
|
+
if (!this.container) {
|
|
1646
|
+
throw new Error(`Container element not found: ${this.config.container}`);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
else {
|
|
1650
|
+
this.container = this.config.container;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
else {
|
|
1654
|
+
// Create a default container
|
|
1655
|
+
this.container = document.body;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Render the component
|
|
1661
|
+
*/
|
|
1662
|
+
render() {
|
|
1663
|
+
if (!this.container)
|
|
1664
|
+
return;
|
|
1665
|
+
if (this.config.modal) {
|
|
1666
|
+
this.renderAsModal();
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
this.renderInline();
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Render as modal overlay
|
|
1674
|
+
*/
|
|
1675
|
+
renderAsModal() {
|
|
1676
|
+
// Create modal overlay (backdrop) - start hidden to prevent flicker
|
|
1677
|
+
this.modalOverlay = this.createElement('div', {
|
|
1678
|
+
className: 'onboarding-modal-overlay',
|
|
1679
|
+
style: 'display: none;', // Start hidden by default
|
|
1680
|
+
});
|
|
1681
|
+
// Create modal content wrapper
|
|
1682
|
+
this.modalContent = this.createElement('div', {
|
|
1683
|
+
className: `onboarding-modal-content onboarding-theme-${this.config.theme}`,
|
|
1684
|
+
role: 'dialog',
|
|
1685
|
+
'aria-modal': 'true',
|
|
1686
|
+
'aria-labelledby': 'onboarding-title',
|
|
1687
|
+
tabIndex: -1,
|
|
1688
|
+
});
|
|
1689
|
+
// Create root element for the form
|
|
1690
|
+
this.rootElement = this.createElement('div', {
|
|
1691
|
+
className: `onboarding-container onboarding-theme-${this.config.theme} ${this.config.className}`,
|
|
1692
|
+
style: this.parseStyle(this.config.style),
|
|
1693
|
+
});
|
|
1694
|
+
// Close button
|
|
1695
|
+
if (this.config.showCloseButton) {
|
|
1696
|
+
this.closeButton = this.createElement('button', {
|
|
1697
|
+
className: 'onboarding-modal-close',
|
|
1698
|
+
type: 'button',
|
|
1699
|
+
innerHTML: '×',
|
|
1700
|
+
'aria-label': 'Close modal',
|
|
1701
|
+
'aria-describedby': 'close-button-description',
|
|
1702
|
+
});
|
|
1703
|
+
this.closeButton.addEventListener('click', () => this.close());
|
|
1704
|
+
this.closeButton.addEventListener('keydown', (e) => {
|
|
1705
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1706
|
+
e.preventDefault();
|
|
1707
|
+
this.close();
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
this.modalContent.appendChild(this.closeButton);
|
|
1711
|
+
}
|
|
1712
|
+
// Build the form content
|
|
1713
|
+
this.buildFormContent();
|
|
1714
|
+
// Append form to root, root to modal content, modal content to overlay
|
|
1715
|
+
this.modalContent.appendChild(this.rootElement);
|
|
1716
|
+
this.modalOverlay.appendChild(this.modalContent);
|
|
1717
|
+
// Add click handler for backdrop
|
|
1718
|
+
if (this.config.closeOnBackdropClick) {
|
|
1719
|
+
this.modalOverlay.addEventListener('click', (e) => {
|
|
1720
|
+
if (e.target === this.modalOverlay) {
|
|
1721
|
+
this.close();
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
// Prevent body scroll when modal is open
|
|
1726
|
+
document.body.style.overflow = 'hidden';
|
|
1727
|
+
// Append to body
|
|
1728
|
+
document.body.appendChild(this.modalOverlay);
|
|
1729
|
+
// Add animation class
|
|
1730
|
+
setTimeout(() => {
|
|
1731
|
+
if (this.modalOverlay) {
|
|
1732
|
+
this.modalOverlay.classList.add('onboarding-modal-open');
|
|
1733
|
+
// Focus management for accessibility
|
|
1734
|
+
if (this.emailInput) {
|
|
1735
|
+
this.emailInput.focus();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}, 10);
|
|
1739
|
+
// Add keyboard navigation (ESC to close)
|
|
1740
|
+
this.modalOverlay.addEventListener('keydown', (e) => {
|
|
1741
|
+
if (e.key === 'Escape' && this.config.showCloseButton) {
|
|
1742
|
+
this.close();
|
|
1743
|
+
}
|
|
1744
|
+
// Trap focus within modal
|
|
1745
|
+
if (e.key === 'Tab') {
|
|
1746
|
+
this.trapFocus(e);
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Trap focus within modal for accessibility
|
|
1752
|
+
*/
|
|
1753
|
+
trapFocus(e) {
|
|
1754
|
+
if (!this.modalContent)
|
|
1755
|
+
return;
|
|
1756
|
+
const focusableElements = this.modalContent.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
|
1757
|
+
const firstElement = focusableElements[0];
|
|
1758
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
1759
|
+
if (e.shiftKey && document.activeElement === firstElement) {
|
|
1760
|
+
e.preventDefault();
|
|
1761
|
+
lastElement.focus();
|
|
1762
|
+
}
|
|
1763
|
+
else if (!e.shiftKey && document.activeElement === lastElement) {
|
|
1764
|
+
e.preventDefault();
|
|
1765
|
+
firstElement.focus();
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Render inline (non-modal)
|
|
1770
|
+
*/
|
|
1771
|
+
renderInline() {
|
|
1772
|
+
if (!this.container)
|
|
1773
|
+
return;
|
|
1774
|
+
// Create root element
|
|
1775
|
+
this.rootElement = this.createElement('div', {
|
|
1776
|
+
className: `onboarding-container onboarding-inline onboarding-theme-${this.config.theme} ${this.config.className}`,
|
|
1777
|
+
style: this.parseStyle(this.config.style),
|
|
1778
|
+
});
|
|
1779
|
+
// Build the form content
|
|
1780
|
+
this.buildFormContent();
|
|
1781
|
+
// Append to container
|
|
1782
|
+
if (this.container === document.body) {
|
|
1783
|
+
this.container.appendChild(this.rootElement);
|
|
1784
|
+
}
|
|
1785
|
+
else {
|
|
1786
|
+
this.container.innerHTML = '';
|
|
1787
|
+
this.container.appendChild(this.rootElement);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Build form content (shared between modal and inline)
|
|
1792
|
+
*/
|
|
1793
|
+
buildFormContent() {
|
|
1794
|
+
if (!this.rootElement)
|
|
1795
|
+
return;
|
|
1796
|
+
// Create card
|
|
1797
|
+
const card = this.createElement('div', { className: 'onboarding-card' });
|
|
1798
|
+
// Header with logo at top
|
|
1799
|
+
const header = this.createElement('div', { className: 'onboarding-header' });
|
|
1800
|
+
// Logo section (centered at top)
|
|
1801
|
+
const logoSection = this.createElement('div', { className: 'onboarding-logo-section' });
|
|
1802
|
+
if (this.config.logo) {
|
|
1803
|
+
const logoContainer = this.createElement('div', { className: 'onboarding-logo-container' });
|
|
1804
|
+
const logoImg = this.createElement('img', {
|
|
1805
|
+
src: this.config.logo,
|
|
1806
|
+
alt: 'Logo',
|
|
1807
|
+
className: 'onboarding-logo-img',
|
|
1808
|
+
});
|
|
1809
|
+
logoContainer.appendChild(logoImg);
|
|
1810
|
+
logoSection.appendChild(logoContainer);
|
|
1811
|
+
}
|
|
1812
|
+
header.appendChild(logoSection);
|
|
1813
|
+
// Onboarding title (always shown, default: "Sign in")
|
|
1814
|
+
const title = this.createElement('h1', {
|
|
1815
|
+
className: 'onboarding-title',
|
|
1816
|
+
textContent: this.config.onboardTitle || 'Sign in',
|
|
1817
|
+
id: 'onboarding-title',
|
|
1818
|
+
});
|
|
1819
|
+
header.appendChild(title);
|
|
1820
|
+
// Form
|
|
1821
|
+
this.emailForm = this.createElement('form', {
|
|
1822
|
+
className: 'onboarding-form',
|
|
1823
|
+
'aria-label': 'Sign in form',
|
|
1824
|
+
noValidate: true,
|
|
1825
|
+
});
|
|
1826
|
+
// Email input group
|
|
1827
|
+
const emailGroup = this.createElement('div', { className: 'onboarding-input-group' });
|
|
1828
|
+
// Email label
|
|
1829
|
+
const emailLabel = this.createElement('label', {
|
|
1830
|
+
className: 'onboarding-input-label',
|
|
1831
|
+
textContent: this.config.labels?.emailLabel || 'Email Address',
|
|
1832
|
+
htmlFor: 'onboarding-email-input',
|
|
1833
|
+
});
|
|
1834
|
+
emailGroup.appendChild(emailLabel);
|
|
1835
|
+
const emailWrapper = this.createElement('div', { className: 'onboarding-input-wrapper' });
|
|
1836
|
+
// Email icon SVG
|
|
1837
|
+
const emailIcon = this.createEmailIcon();
|
|
1838
|
+
emailWrapper.appendChild(emailIcon);
|
|
1839
|
+
// Get auth methods to check if all are enabled
|
|
1840
|
+
const authMethods = this.config.authMethods || ['otp', 'google'];
|
|
1841
|
+
const showEmail = authMethods.includes('otp');
|
|
1842
|
+
const showGoogle = authMethods.includes('google');
|
|
1843
|
+
const showTwitter = authMethods.includes('twitter');
|
|
1844
|
+
const showDiscord = authMethods.includes('discord');
|
|
1845
|
+
const showPasskey = authMethods.includes('passkey');
|
|
1846
|
+
const socialMethods = [
|
|
1847
|
+
{ key: 'google', show: showGoogle },
|
|
1848
|
+
{ key: 'twitter', show: showTwitter },
|
|
1849
|
+
{ key: 'discord', show: showDiscord },
|
|
1850
|
+
].filter((m) => m.show);
|
|
1851
|
+
// Treat “all configs” as email + passkey + all socials present
|
|
1852
|
+
const allAuthMethodsEnabled = showEmail && showPasskey && showGoogle && showTwitter && showDiscord;
|
|
1853
|
+
// Email input
|
|
1854
|
+
this.emailInput = this.createElement('input', {
|
|
1855
|
+
type: 'email',
|
|
1856
|
+
id: 'onboarding-email-input',
|
|
1857
|
+
placeholder: this.config.labels?.emailPlaceholder || 'Enter your email',
|
|
1858
|
+
className: 'onboarding-input',
|
|
1859
|
+
required: true,
|
|
1860
|
+
autocomplete: 'email',
|
|
1861
|
+
'aria-label': this.config.labels?.emailLabel || 'Email Address',
|
|
1862
|
+
'aria-required': 'true',
|
|
1863
|
+
style: allAuthMethodsEnabled ? 'padding-right: 48px;' : '',
|
|
1864
|
+
});
|
|
1865
|
+
emailWrapper.appendChild(this.emailInput);
|
|
1866
|
+
// Add right arrow button inside input when all auth methods are enabled
|
|
1867
|
+
if (allAuthMethodsEnabled) {
|
|
1868
|
+
this.emailArrowButton = this.createElement('button', {
|
|
1869
|
+
type: 'button',
|
|
1870
|
+
className: 'onboarding-input-arrow-button',
|
|
1871
|
+
'aria-label': this.config.labels?.emailButton || 'Continue with email',
|
|
1872
|
+
});
|
|
1873
|
+
const arrowIcon = this.createArrowIcon();
|
|
1874
|
+
arrowIcon.setAttribute('aria-hidden', 'true');
|
|
1875
|
+
this.emailArrowButton.appendChild(arrowIcon);
|
|
1876
|
+
// Add click handler to submit form
|
|
1877
|
+
this.emailArrowButton.addEventListener('click', (e) => {
|
|
1878
|
+
e.preventDefault();
|
|
1879
|
+
if (this.emailInput && this.emailInput.value.trim()) {
|
|
1880
|
+
this.emailForm?.requestSubmit();
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
// Also submit on Enter key
|
|
1884
|
+
this.emailInput.addEventListener('keydown', (e) => {
|
|
1885
|
+
if (e.key === 'Enter' && this.emailInput?.value.trim()) {
|
|
1886
|
+
e.preventDefault();
|
|
1887
|
+
this.emailForm?.requestSubmit();
|
|
1888
|
+
}
|
|
1889
|
+
});
|
|
1890
|
+
emailWrapper.appendChild(this.emailArrowButton);
|
|
1891
|
+
}
|
|
1892
|
+
emailGroup.appendChild(emailWrapper);
|
|
1893
|
+
this.emailForm.appendChild(emailGroup);
|
|
1894
|
+
// OTP input group (initially hidden)
|
|
1895
|
+
this.otpGroup = this.createElement('div', {
|
|
1896
|
+
className: 'onboarding-input-group',
|
|
1897
|
+
style: 'display: none;',
|
|
1898
|
+
});
|
|
1899
|
+
// OTP label
|
|
1900
|
+
const otpLabel = this.createElement('label', {
|
|
1901
|
+
className: 'onboarding-input-label',
|
|
1902
|
+
textContent: this.config.labels?.otpLabel || 'Verification code',
|
|
1903
|
+
htmlFor: 'onboarding-otp-input',
|
|
1904
|
+
});
|
|
1905
|
+
this.otpGroup.appendChild(otpLabel);
|
|
1906
|
+
const otpWrapper = this.createElement('div', { className: 'onboarding-input-wrapper' });
|
|
1907
|
+
this.otpInput = this.createElement('input', {
|
|
1908
|
+
type: 'text',
|
|
1909
|
+
id: 'onboarding-otp-input',
|
|
1910
|
+
placeholder: this.config.labels?.otpPlaceholder || 'Enter 6-digit code',
|
|
1911
|
+
className: 'onboarding-input',
|
|
1912
|
+
required: false, // Don't require initially since it's hidden
|
|
1913
|
+
maxLength: 6,
|
|
1914
|
+
inputMode: 'numeric',
|
|
1915
|
+
pattern: '[0-9]*',
|
|
1916
|
+
autocomplete: 'one-time-code',
|
|
1917
|
+
'aria-label': this.config.labels?.otpLabel || 'Verification code',
|
|
1918
|
+
});
|
|
1919
|
+
otpWrapper.appendChild(this.otpInput);
|
|
1920
|
+
this.otpGroup.appendChild(otpWrapper);
|
|
1921
|
+
this.emailForm.appendChild(this.otpGroup);
|
|
1922
|
+
// Error element
|
|
1923
|
+
this.errorElement = this.createElement('div', {
|
|
1924
|
+
className: 'onboarding-error',
|
|
1925
|
+
style: 'display: none;',
|
|
1926
|
+
role: 'alert',
|
|
1927
|
+
'aria-live': 'polite',
|
|
1928
|
+
});
|
|
1929
|
+
this.emailForm.appendChild(this.errorElement);
|
|
1930
|
+
// Continue button (hide if all auth methods are enabled)
|
|
1931
|
+
this.continueButton = this.createElement('button', {
|
|
1932
|
+
type: 'submit',
|
|
1933
|
+
className: 'onboarding-button onboarding-button-primary',
|
|
1934
|
+
textContent: this.config.labels?.emailButton || 'Continue',
|
|
1935
|
+
'aria-label': this.config.labels?.emailButton || 'Continue with email',
|
|
1936
|
+
style: allAuthMethodsEnabled ? 'display: none;' : '',
|
|
1937
|
+
});
|
|
1938
|
+
this.emailForm.appendChild(this.continueButton);
|
|
1939
|
+
card.appendChild(header);
|
|
1940
|
+
// Re-check auth methods for other elements (already checked above)
|
|
1941
|
+
// Only append email form if email auth is enabled
|
|
1942
|
+
if (showEmail) {
|
|
1943
|
+
card.appendChild(this.emailForm);
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
// Still create the form but hide it (needed for OTP flow)
|
|
1947
|
+
this.emailForm.style.display = 'none';
|
|
1948
|
+
card.appendChild(this.emailForm);
|
|
1949
|
+
}
|
|
1950
|
+
// Divider (show if email plus any social method is enabled)
|
|
1951
|
+
if (showEmail && socialMethods.length > 0) {
|
|
1952
|
+
this.divider = this.createElement('div', { className: 'onboarding-divider' });
|
|
1953
|
+
const dividerText = this.createElement('span', {
|
|
1954
|
+
className: 'onboarding-divider-text',
|
|
1955
|
+
textContent: 'or',
|
|
1956
|
+
});
|
|
1957
|
+
this.divider.appendChild(dividerText);
|
|
1958
|
+
card.appendChild(this.divider);
|
|
1959
|
+
}
|
|
1960
|
+
else {
|
|
1961
|
+
// Create divider element but hide it
|
|
1962
|
+
this.divider = this.createElement('div', {
|
|
1963
|
+
className: 'onboarding-divider',
|
|
1964
|
+
style: 'display: none;',
|
|
1965
|
+
});
|
|
1966
|
+
card.appendChild(this.divider);
|
|
1967
|
+
}
|
|
1968
|
+
// Social login buttons (Google, Twitter, Discord)
|
|
1969
|
+
if (socialMethods.length > 0) {
|
|
1970
|
+
if (socialMethods.length === 1) {
|
|
1971
|
+
const method = socialMethods[0].key;
|
|
1972
|
+
const isGoogle = method === 'google';
|
|
1973
|
+
const isTwitter = method === 'twitter';
|
|
1974
|
+
const isDiscord = method === 'discord';
|
|
1975
|
+
const button = this.createElement('button', {
|
|
1976
|
+
type: 'button',
|
|
1977
|
+
className: isGoogle ? 'onboarding-button onboarding-button-google' : 'onboarding-button onboarding-button-social',
|
|
1978
|
+
'aria-label': this.config.labels?.[`${method}Button`] ||
|
|
1979
|
+
(method === 'google'
|
|
1980
|
+
? 'Continue with Google'
|
|
1981
|
+
: method === 'twitter'
|
|
1982
|
+
? 'Continue with X'
|
|
1983
|
+
: `Continue with ${method.charAt(0).toUpperCase() + method.slice(1)}`),
|
|
1984
|
+
});
|
|
1985
|
+
const icon = isGoogle
|
|
1986
|
+
? this.createGoogleIcon()
|
|
1987
|
+
: isTwitter
|
|
1988
|
+
? this.createTwitterIcon()
|
|
1989
|
+
: this.createDiscordIcon();
|
|
1990
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
1991
|
+
button.appendChild(icon);
|
|
1992
|
+
const label = this.config.labels?.[`${method}Button`] ||
|
|
1993
|
+
(method === 'google'
|
|
1994
|
+
? 'Continue with Google'
|
|
1995
|
+
: method === 'twitter'
|
|
1996
|
+
? 'Continue with X'
|
|
1997
|
+
: `Continue with ${method.charAt(0).toUpperCase() + method.slice(1)}`);
|
|
1998
|
+
button.appendChild(document.createTextNode(` ${label}`));
|
|
1999
|
+
if (isGoogle) {
|
|
2000
|
+
this.googleButton = button;
|
|
2001
|
+
}
|
|
2002
|
+
else if (isTwitter) {
|
|
2003
|
+
this.twitterButton = button;
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
this.discordButton = button;
|
|
2007
|
+
}
|
|
2008
|
+
card.appendChild(button);
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
this.socialGrid = this.createElement('div', { className: 'onboarding-social-grid' });
|
|
2012
|
+
socialMethods.forEach(({ key: method }) => {
|
|
2013
|
+
const isGoogle = method === 'google';
|
|
2014
|
+
const isTwitter = method === 'twitter';
|
|
2015
|
+
const isDiscord = method === 'discord';
|
|
2016
|
+
const button = this.createElement('button', {
|
|
2017
|
+
type: 'button',
|
|
2018
|
+
className: 'onboarding-button-social-icon',
|
|
2019
|
+
'aria-label': this.config.labels?.[`${method}Button`] ||
|
|
2020
|
+
(method === 'google'
|
|
2021
|
+
? 'Google'
|
|
2022
|
+
: method === 'twitter'
|
|
2023
|
+
? 'X'
|
|
2024
|
+
: method.charAt(0).toUpperCase() + method.slice(1)),
|
|
2025
|
+
});
|
|
2026
|
+
const icon = isGoogle
|
|
2027
|
+
? this.createGoogleIcon()
|
|
2028
|
+
: isTwitter
|
|
2029
|
+
? this.createTwitterIcon()
|
|
2030
|
+
: this.createDiscordIcon();
|
|
2031
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
2032
|
+
button.appendChild(icon);
|
|
2033
|
+
if (isGoogle) {
|
|
2034
|
+
this.googleButton = button;
|
|
2035
|
+
}
|
|
2036
|
+
else if (isTwitter) {
|
|
2037
|
+
this.twitterButton = button;
|
|
2038
|
+
}
|
|
2039
|
+
else {
|
|
2040
|
+
this.discordButton = button;
|
|
2041
|
+
}
|
|
2042
|
+
if (this.socialGrid) {
|
|
2043
|
+
this.socialGrid.appendChild(button);
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
if (this.socialGrid) {
|
|
2047
|
+
card.appendChild(this.socialGrid);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
// Passkey divider (only show if passkey is enabled and there are other auth methods)
|
|
2052
|
+
if (showPasskey && (showEmail || socialMethods.length > 0)) {
|
|
2053
|
+
this.passkeyDivider = this.createElement('div', { className: 'onboarding-divider' });
|
|
2054
|
+
const passkeyDividerText = this.createElement('span', {
|
|
2055
|
+
className: 'onboarding-divider-text',
|
|
2056
|
+
textContent: 'or',
|
|
2057
|
+
});
|
|
2058
|
+
this.passkeyDivider.appendChild(passkeyDividerText);
|
|
2059
|
+
card.appendChild(this.passkeyDivider);
|
|
2060
|
+
}
|
|
2061
|
+
else {
|
|
2062
|
+
this.passkeyDivider = null;
|
|
2063
|
+
}
|
|
2064
|
+
// Passkey login button (only show if passkey auth is enabled)
|
|
2065
|
+
if (showPasskey) {
|
|
2066
|
+
const passkeyBg = this.config.colors?.primary ||
|
|
2067
|
+
(this.config.theme === 'dark' ? '#4b5563' : '#ffffff');
|
|
2068
|
+
const passkeyBorder = this.config.colors?.border ||
|
|
2069
|
+
(this.config.theme === 'dark' ? '#6b7280' : '#e5e7eb');
|
|
2070
|
+
const passkeyTextColor = this.config.colors?.text ||
|
|
2071
|
+
(this.config.theme === 'dark' ? '#ffffff' : '#1f2937');
|
|
2072
|
+
// Set passkey link color based on theme: white for dark theme, black for light theme
|
|
2073
|
+
const passkeyLinkColor = this.config.theme === 'dark' ? '#ffffff' : '#1f2937';
|
|
2074
|
+
this.passkeyLoginButton = this.createElement('button', {
|
|
2075
|
+
type: 'button',
|
|
2076
|
+
className: 'onboarding-button onboarding-button-passkey',
|
|
2077
|
+
'aria-label': this.config.labels?.passkeyLoginButton || 'Log in with passkey',
|
|
2078
|
+
style: `
|
|
2079
|
+
background-color: ${passkeyBg};
|
|
2080
|
+
color: ${passkeyTextColor};
|
|
2081
|
+
border: 1.5px solid ${passkeyBorder};
|
|
2082
|
+
`,
|
|
2083
|
+
});
|
|
2084
|
+
const passkeyIcon = this.createPasskeyIcon();
|
|
2085
|
+
passkeyIcon.setAttribute('aria-hidden', 'true');
|
|
2086
|
+
this.passkeyLoginButton.appendChild(passkeyIcon);
|
|
2087
|
+
const passkeyText = this.config.labels?.passkeyLoginButton || 'Log in with passkey';
|
|
2088
|
+
this.passkeyLoginButton.appendChild(document.createTextNode(` ${passkeyText}`));
|
|
2089
|
+
card.appendChild(this.passkeyLoginButton);
|
|
2090
|
+
// Passkey signup link
|
|
2091
|
+
const passkeySignupContainer = this.createElement('div', {
|
|
2092
|
+
style: 'text-align: center; margin-top: 12px;',
|
|
2093
|
+
});
|
|
2094
|
+
this.passkeySignupLink = this.createElement('a', {
|
|
2095
|
+
className: 'onboarding-passkey-signup-link',
|
|
2096
|
+
href: '#',
|
|
2097
|
+
textContent: this.config.labels?.passkeySignupButton || 'Sign up with passkey',
|
|
2098
|
+
'aria-label': this.config.labels?.passkeySignupButton || 'Sign up with passkey',
|
|
2099
|
+
style: `color: ${passkeyLinkColor};`,
|
|
2100
|
+
});
|
|
2101
|
+
passkeySignupContainer.appendChild(this.passkeySignupLink);
|
|
2102
|
+
card.appendChild(passkeySignupContainer);
|
|
2103
|
+
// Passkey error element (local to passkey section)
|
|
2104
|
+
this.passkeyErrorElement = this.createElement('div', {
|
|
2105
|
+
className: 'onboarding-error',
|
|
2106
|
+
style: 'display: none; margin-top: 12px;',
|
|
2107
|
+
role: 'alert',
|
|
2108
|
+
'aria-live': 'polite',
|
|
2109
|
+
});
|
|
2110
|
+
card.appendChild(this.passkeyErrorElement);
|
|
2111
|
+
}
|
|
2112
|
+
else {
|
|
2113
|
+
// Create passkey elements but hide them
|
|
2114
|
+
this.passkeyLoginButton = this.createElement('button', {
|
|
2115
|
+
type: 'button',
|
|
2116
|
+
className: 'onboarding-button onboarding-button-passkey',
|
|
2117
|
+
style: 'display: none;',
|
|
2118
|
+
});
|
|
2119
|
+
card.appendChild(this.passkeyLoginButton);
|
|
2120
|
+
this.passkeySignupLink = this.createElement('a', {
|
|
2121
|
+
className: 'onboarding-passkey-signup-link',
|
|
2122
|
+
href: '#',
|
|
2123
|
+
style: 'display: none;',
|
|
2124
|
+
});
|
|
2125
|
+
card.appendChild(this.passkeySignupLink);
|
|
2126
|
+
this.passkeyErrorElement = this.createElement('div', {
|
|
2127
|
+
className: 'onboarding-error',
|
|
2128
|
+
style: 'display: none;',
|
|
2129
|
+
});
|
|
2130
|
+
card.appendChild(this.passkeyErrorElement);
|
|
2131
|
+
}
|
|
2132
|
+
// External wallet divider (shown immediately if external wallets are enabled)
|
|
2133
|
+
this.externalWalletDivider = this.createElement('div', {
|
|
2134
|
+
className: 'onboarding-divider',
|
|
2135
|
+
style: this.externalWalletsEnabled ? '' : 'display: none;',
|
|
2136
|
+
});
|
|
2137
|
+
const externalWalletDividerText = this.createElement('span', {
|
|
2138
|
+
className: 'onboarding-divider-text',
|
|
2139
|
+
textContent: 'or',
|
|
2140
|
+
});
|
|
2141
|
+
this.externalWalletDivider.appendChild(externalWalletDividerText);
|
|
2142
|
+
card.appendChild(this.externalWalletDivider);
|
|
2143
|
+
// External wallet container (shown immediately if external wallets are enabled)
|
|
2144
|
+
this.externalWalletContainer = this.createElement('div', {
|
|
2145
|
+
id: 'onboarding-external-wallet-container',
|
|
2146
|
+
style: this.externalWalletsEnabled ? '' : 'display: none;',
|
|
2147
|
+
});
|
|
2148
|
+
card.appendChild(this.externalWalletContainer);
|
|
2149
|
+
// Footer (inside card, so it appears on initial login screen)
|
|
2150
|
+
if (this.config.showFooter) {
|
|
2151
|
+
const textColor = this.config.colors?.text ||
|
|
2152
|
+
(this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
|
|
2153
|
+
const brandColor = this.config.colors?.text ||
|
|
2154
|
+
(this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
|
|
2155
|
+
this.footer = this.createElement('div', { className: 'onboarding-footer' });
|
|
2156
|
+
const footerText = this.createElement('p', {
|
|
2157
|
+
className: 'onboarding-footer-text',
|
|
2158
|
+
style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center;`,
|
|
2159
|
+
});
|
|
2160
|
+
// "Powered by" text
|
|
2161
|
+
const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
|
|
2162
|
+
footerText.appendChild(poweredBySpan);
|
|
2163
|
+
// Inline brand with logo (clickable link)
|
|
2164
|
+
const brandContainer = this.createElement('a', {
|
|
2165
|
+
href: 'https://www.abstraxn.com/',
|
|
2166
|
+
target: '_blank',
|
|
2167
|
+
rel: 'noopener noreferrer',
|
|
2168
|
+
style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
|
|
2169
|
+
'aria-label': 'Visit Abstraxn website',
|
|
2170
|
+
});
|
|
2171
|
+
// Abstraxn logo SVG
|
|
2172
|
+
const logoSvg = this.createElement('span', {
|
|
2173
|
+
innerHTML: `
|
|
2174
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
|
|
2175
|
+
<path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781)"/>
|
|
2176
|
+
<path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781)"/>
|
|
2177
|
+
<defs>
|
|
2178
|
+
<linearGradient id="paint0_linear_1242_781" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
|
|
2179
|
+
<stop stop-color="#F878D2"/>
|
|
2180
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
2181
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
2182
|
+
</linearGradient>
|
|
2183
|
+
<linearGradient id="paint1_linear_1242_781" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
|
|
2184
|
+
<stop stop-color="#F878D2"/>
|
|
2185
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
2186
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
2187
|
+
</linearGradient>
|
|
2188
|
+
</defs>
|
|
2189
|
+
</svg>
|
|
2190
|
+
`,
|
|
2191
|
+
style: 'display: inline-flex; align-items: center;',
|
|
2192
|
+
});
|
|
2193
|
+
brandContainer.appendChild(logoSvg);
|
|
2194
|
+
const brandText = this.createElement('span', {
|
|
2195
|
+
textContent: 'abstraxn',
|
|
2196
|
+
style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
|
|
2197
|
+
});
|
|
2198
|
+
brandContainer.appendChild(brandText);
|
|
2199
|
+
footerText.appendChild(brandContainer);
|
|
2200
|
+
this.footer.appendChild(footerText);
|
|
2201
|
+
card.appendChild(this.footer);
|
|
2202
|
+
}
|
|
2203
|
+
this.rootElement.appendChild(card);
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* Attach event listeners
|
|
2207
|
+
*/
|
|
2208
|
+
attachEventListeners() {
|
|
2209
|
+
if (!this.emailForm || !this.emailInput || !this.otpInput || !this.continueButton) {
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
// Email input change
|
|
2213
|
+
this.emailInput.addEventListener('input', (e) => {
|
|
2214
|
+
this.email = e.target.value;
|
|
2215
|
+
this.updateButtonState();
|
|
2216
|
+
});
|
|
2217
|
+
// OTP input change
|
|
2218
|
+
this.otpInput.addEventListener('input', (e) => {
|
|
2219
|
+
this.otp = e.target.value;
|
|
2220
|
+
});
|
|
2221
|
+
// Form submit
|
|
2222
|
+
this.emailForm.addEventListener('submit', (e) => {
|
|
2223
|
+
e.preventDefault();
|
|
2224
|
+
this.handleEmailSubmit();
|
|
2225
|
+
});
|
|
2226
|
+
// Google button click
|
|
2227
|
+
if (this.googleButton) {
|
|
2228
|
+
this.googleButton.addEventListener('click', (e) => {
|
|
2229
|
+
e.preventDefault();
|
|
2230
|
+
this.handleGoogleLogin();
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
// Twitter button click
|
|
2234
|
+
if (this.twitterButton) {
|
|
2235
|
+
this.twitterButton.addEventListener('click', (e) => {
|
|
2236
|
+
e.preventDefault();
|
|
2237
|
+
this.handleTwitterLogin();
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
// Discord button click
|
|
2241
|
+
if (this.discordButton) {
|
|
2242
|
+
this.discordButton.addEventListener('click', (e) => {
|
|
2243
|
+
e.preventDefault();
|
|
2244
|
+
this.handleDiscordLogin();
|
|
2245
|
+
});
|
|
2246
|
+
}
|
|
2247
|
+
// Passkey login button click
|
|
2248
|
+
if (this.passkeyLoginButton) {
|
|
2249
|
+
this.passkeyLoginButton.addEventListener('click', (e) => {
|
|
2250
|
+
e.preventDefault();
|
|
2251
|
+
this.handlePasskeyLogin();
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
// Passkey signup link click
|
|
2255
|
+
if (this.passkeySignupLink) {
|
|
2256
|
+
this.passkeySignupLink.addEventListener('click', (e) => {
|
|
2257
|
+
e.preventDefault();
|
|
2258
|
+
this.handlePasskeySignup();
|
|
2259
|
+
});
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Handle email form submission
|
|
2264
|
+
*/
|
|
2265
|
+
async handleEmailSubmit() {
|
|
2266
|
+
if (!this.emailInput || !this.otpInput)
|
|
2267
|
+
return;
|
|
2268
|
+
// Ensure email is captured from input field
|
|
2269
|
+
this.email = this.emailInput.value.trim();
|
|
2270
|
+
if (!this.email || !this.email.includes('@')) {
|
|
2271
|
+
this.setError('Please enter a valid email address');
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
this.setError(null);
|
|
2275
|
+
this.setLoading(true, 'email');
|
|
2276
|
+
try {
|
|
2277
|
+
if (!this.otpSent) {
|
|
2278
|
+
// Initiate OTP
|
|
2279
|
+
if (this.config.onEmailOtpInitiate) {
|
|
2280
|
+
await this.config.onEmailOtpInitiate(this.email);
|
|
2281
|
+
}
|
|
2282
|
+
else if (this.config.emailOtpEndpoint) {
|
|
2283
|
+
const response = await fetch(this.config.emailOtpEndpoint, {
|
|
2284
|
+
method: 'POST',
|
|
2285
|
+
headers: {
|
|
2286
|
+
'Content-Type': 'application/json',
|
|
2287
|
+
},
|
|
2288
|
+
body: JSON.stringify({ email: this.email }),
|
|
2289
|
+
});
|
|
2290
|
+
if (!response.ok) {
|
|
2291
|
+
throw new Error('Failed to send OTP');
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
this.otpSent = true;
|
|
2295
|
+
this.showOtpInput();
|
|
2296
|
+
if (this.continueButton) {
|
|
2297
|
+
this.continueButton.textContent = 'Verify OTP';
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
else {
|
|
2301
|
+
// Verify OTP
|
|
2302
|
+
if (this.config.onEmailOtpVerify) {
|
|
2303
|
+
const result = await this.config.onEmailOtpVerify(this.email, this.otp);
|
|
2304
|
+
if (result.success) {
|
|
2305
|
+
this.config.onLoginSuccess?.({ token: result.token });
|
|
2306
|
+
}
|
|
2307
|
+
else {
|
|
2308
|
+
throw new Error('Invalid OTP');
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
else if (this.config.emailOtpEndpoint) {
|
|
2312
|
+
const response = await fetch(`${this.config.emailOtpEndpoint}/verify`, {
|
|
2313
|
+
method: 'POST',
|
|
2314
|
+
headers: {
|
|
2315
|
+
'Content-Type': 'application/json',
|
|
2316
|
+
},
|
|
2317
|
+
body: JSON.stringify({ email: this.email, otp: this.otp }),
|
|
2318
|
+
});
|
|
2319
|
+
const data = await response.json();
|
|
2320
|
+
if (data.success) {
|
|
2321
|
+
this.config.onLoginSuccess?.({ token: data.token });
|
|
2322
|
+
}
|
|
2323
|
+
else {
|
|
2324
|
+
throw new Error(data.message || 'Invalid OTP');
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
catch (err) {
|
|
2330
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
2331
|
+
this.setError(errorMessage);
|
|
2332
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2333
|
+
}
|
|
2334
|
+
finally {
|
|
2335
|
+
this.setLoading(false);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Handle Google login
|
|
2340
|
+
*/
|
|
2341
|
+
async handleGoogleLogin() {
|
|
2342
|
+
// console.log('🔵 Google login button clicked');
|
|
2343
|
+
// console.log('🔵 Config:', {
|
|
2344
|
+
// hasOnGoogleLogin: !!this.config.onGoogleLogin,
|
|
2345
|
+
// googleAuthEndpoint: this.config.googleAuthEndpoint
|
|
2346
|
+
// });
|
|
2347
|
+
this.setError(null);
|
|
2348
|
+
this.setLoading(true, 'google');
|
|
2349
|
+
try {
|
|
2350
|
+
localStorage.setItem('abstraxn_login_source', this.config.modal ? 'modal' : 'inline');
|
|
2351
|
+
if (this.config.onGoogleLogin) {
|
|
2352
|
+
// console.log('🔄 Calling onGoogleLogin callback');
|
|
2353
|
+
await this.config.onGoogleLogin();
|
|
2354
|
+
}
|
|
2355
|
+
else if (this.config.googleAuthEndpoint) {
|
|
2356
|
+
// console.log('🔄 Redirecting to Google auth endpoint:', this.config.googleAuthEndpoint);
|
|
2357
|
+
window.location.href = this.config.googleAuthEndpoint;
|
|
2358
|
+
}
|
|
2359
|
+
else {
|
|
2360
|
+
throw new Error('Google login endpoint not configured');
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
catch (err) {
|
|
2364
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
2365
|
+
this.setError(errorMessage);
|
|
2366
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2367
|
+
}
|
|
2368
|
+
finally {
|
|
2369
|
+
this.setLoading(false);
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* Handle Twitter login
|
|
2374
|
+
*/
|
|
2375
|
+
async handleTwitterLogin() {
|
|
2376
|
+
this.setError(null);
|
|
2377
|
+
this.setLoading(true, 'twitter');
|
|
2378
|
+
try {
|
|
2379
|
+
if (this.config.onTwitterLogin) {
|
|
2380
|
+
await this.config.onTwitterLogin();
|
|
2381
|
+
}
|
|
2382
|
+
else if (this.config.twitterAuthEndpoint) {
|
|
2383
|
+
window.location.href = this.config.twitterAuthEndpoint;
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
throw new Error('X (Twitter) login not configured');
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
catch (err) {
|
|
2390
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
2391
|
+
this.setError(errorMessage);
|
|
2392
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2393
|
+
}
|
|
2394
|
+
finally {
|
|
2395
|
+
this.setLoading(false);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Handle Discord login
|
|
2400
|
+
*/
|
|
2401
|
+
async handleDiscordLogin() {
|
|
2402
|
+
this.setError(null);
|
|
2403
|
+
this.setLoading(true, 'discord');
|
|
2404
|
+
try {
|
|
2405
|
+
if (this.config.onDiscordLogin) {
|
|
2406
|
+
await this.config.onDiscordLogin();
|
|
2407
|
+
}
|
|
2408
|
+
else if (this.config.discordAuthEndpoint) {
|
|
2409
|
+
window.location.href = this.config.discordAuthEndpoint;
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
throw new Error('Discord login not configured');
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
catch (err) {
|
|
2416
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
2417
|
+
this.setError(errorMessage);
|
|
2418
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2419
|
+
}
|
|
2420
|
+
finally {
|
|
2421
|
+
this.setLoading(false);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Show OTP verification screen
|
|
2426
|
+
*/
|
|
2427
|
+
showOtpInput() {
|
|
2428
|
+
if (!this.rootElement)
|
|
2429
|
+
return;
|
|
2430
|
+
// Hide the email form and all login-related elements
|
|
2431
|
+
if (this.emailForm) {
|
|
2432
|
+
this.emailForm.style.display = 'none';
|
|
2433
|
+
}
|
|
2434
|
+
if (this.divider) {
|
|
2435
|
+
this.divider.style.display = 'none';
|
|
2436
|
+
}
|
|
2437
|
+
if (this.googleButton) {
|
|
2438
|
+
this.googleButton.style.display = 'none';
|
|
2439
|
+
}
|
|
2440
|
+
if (this.twitterButton) {
|
|
2441
|
+
this.twitterButton.style.display = 'none';
|
|
2442
|
+
}
|
|
2443
|
+
if (this.discordButton) {
|
|
2444
|
+
this.discordButton.style.display = 'none';
|
|
2445
|
+
}
|
|
2446
|
+
if (this.socialGrid) {
|
|
2447
|
+
this.socialGrid.style.display = 'none';
|
|
2448
|
+
}
|
|
2449
|
+
if (this.passkeyDivider) {
|
|
2450
|
+
this.passkeyDivider.style.display = 'none';
|
|
2451
|
+
}
|
|
2452
|
+
// Hide passkey buttons on OTP screen
|
|
2453
|
+
if (this.passkeyLoginButton) {
|
|
2454
|
+
this.passkeyLoginButton.style.display = 'none';
|
|
2455
|
+
}
|
|
2456
|
+
if (this.passkeySignupLink) {
|
|
2457
|
+
this.passkeySignupLink.style.display = 'none';
|
|
2458
|
+
}
|
|
2459
|
+
if (this.passkeyErrorElement) {
|
|
2460
|
+
this.passkeyErrorElement.style.display = 'none';
|
|
2461
|
+
}
|
|
2462
|
+
// Hide external wallets on OTP screen
|
|
2463
|
+
if (this.externalWalletContainer) {
|
|
2464
|
+
this.externalWalletContainer.style.display = 'none';
|
|
2465
|
+
}
|
|
2466
|
+
if (this.externalWalletDivider) {
|
|
2467
|
+
this.externalWalletDivider.style.display = 'none';
|
|
2468
|
+
}
|
|
2469
|
+
// Hide footer on login card (OTP screen has its own footer)
|
|
2470
|
+
if (this.footer) {
|
|
2471
|
+
this.footer.style.display = 'none';
|
|
2472
|
+
}
|
|
2473
|
+
// Create OTP verification screen
|
|
2474
|
+
this.createOtpVerificationScreen();
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Create OTP verification screen with 6 input fields
|
|
2478
|
+
*/
|
|
2479
|
+
createOtpVerificationScreen() {
|
|
2480
|
+
if (!this.rootElement)
|
|
2481
|
+
return;
|
|
2482
|
+
// Remove existing OTP screen if any
|
|
2483
|
+
if (this.otpVerificationScreen) {
|
|
2484
|
+
this.otpVerificationScreen.remove();
|
|
2485
|
+
this.verifyButton = null;
|
|
2486
|
+
}
|
|
2487
|
+
// Get the card element
|
|
2488
|
+
const card = this.rootElement.querySelector('.onboarding-card');
|
|
2489
|
+
if (!card)
|
|
2490
|
+
return;
|
|
2491
|
+
// Create OTP verification container
|
|
2492
|
+
this.otpVerificationScreen = this.createElement('div', {
|
|
2493
|
+
className: 'onboarding-otp-verification',
|
|
2494
|
+
});
|
|
2495
|
+
// Email icon container
|
|
2496
|
+
const iconContainer = this.createElement('div', {
|
|
2497
|
+
className: 'onboarding-otp-icon-container',
|
|
2498
|
+
});
|
|
2499
|
+
const iconInner = this.createElement('div', {
|
|
2500
|
+
className: 'onboarding-otp-icon-inner',
|
|
2501
|
+
});
|
|
2502
|
+
const emailIcon = this.createEmailIconSVG();
|
|
2503
|
+
iconInner.appendChild(emailIcon);
|
|
2504
|
+
iconContainer.appendChild(iconInner);
|
|
2505
|
+
this.otpVerificationScreen.appendChild(iconContainer);
|
|
2506
|
+
// Title
|
|
2507
|
+
const title = this.createElement('h1', {
|
|
2508
|
+
className: 'onboarding-otp-title',
|
|
2509
|
+
textContent: 'Enter verification code',
|
|
2510
|
+
});
|
|
2511
|
+
this.otpVerificationScreen.appendChild(title);
|
|
2512
|
+
// Instruction text
|
|
2513
|
+
const instruction = this.createElement('p', {
|
|
2514
|
+
className: 'onboarding-otp-instruction',
|
|
2515
|
+
textContent: 'We sent a verification code to',
|
|
2516
|
+
});
|
|
2517
|
+
this.otpVerificationScreen.appendChild(instruction);
|
|
2518
|
+
// Email address
|
|
2519
|
+
const emailDisplay = this.createElement('p', {
|
|
2520
|
+
className: 'onboarding-otp-email',
|
|
2521
|
+
textContent: this.email,
|
|
2522
|
+
});
|
|
2523
|
+
this.otpVerificationScreen.appendChild(emailDisplay);
|
|
2524
|
+
// OTP inputs container
|
|
2525
|
+
const inputsContainer = this.createElement('div', {
|
|
2526
|
+
className: 'onboarding-otp-inputs-container',
|
|
2527
|
+
});
|
|
2528
|
+
// Create 6 input fields
|
|
2529
|
+
this.otpInputs = [];
|
|
2530
|
+
for (let i = 0; i < 6; i++) {
|
|
2531
|
+
const input = this.createElement('input', {
|
|
2532
|
+
type: 'text',
|
|
2533
|
+
className: 'onboarding-otp-input',
|
|
2534
|
+
maxLength: 1,
|
|
2535
|
+
inputMode: 'numeric',
|
|
2536
|
+
pattern: '[0-9]*',
|
|
2537
|
+
});
|
|
2538
|
+
// Add event listeners for auto-advance
|
|
2539
|
+
input.addEventListener('input', (e) => this.handleOtpInput(e, i));
|
|
2540
|
+
input.addEventListener('keydown', (e) => this.handleOtpKeydown(e, i));
|
|
2541
|
+
input.addEventListener('paste', (e) => this.handleOtpPaste(e));
|
|
2542
|
+
this.otpInputs.push(input);
|
|
2543
|
+
inputsContainer.appendChild(input);
|
|
2544
|
+
}
|
|
2545
|
+
this.otpVerificationScreen.appendChild(inputsContainer);
|
|
2546
|
+
// Error element (for OTP screen)
|
|
2547
|
+
const otpErrorElement = this.createElement('div', {
|
|
2548
|
+
className: 'onboarding-error',
|
|
2549
|
+
style: 'display: none;',
|
|
2550
|
+
});
|
|
2551
|
+
this.otpVerificationScreen.appendChild(otpErrorElement);
|
|
2552
|
+
// Store reference for OTP screen errors
|
|
2553
|
+
this.otpVerificationScreen.errorElement = otpErrorElement;
|
|
2554
|
+
// Verify button
|
|
2555
|
+
this.verifyButton = this.createElement('button', {
|
|
2556
|
+
type: 'button',
|
|
2557
|
+
className: 'onboarding-button onboarding-button-primary',
|
|
2558
|
+
textContent: 'Verify',
|
|
2559
|
+
});
|
|
2560
|
+
this.verifyButton.addEventListener('click', () => this.handleOtpVerify());
|
|
2561
|
+
this.otpVerificationScreen.appendChild(this.verifyButton);
|
|
2562
|
+
// Resend section
|
|
2563
|
+
const resendSection = this.createElement('div', {
|
|
2564
|
+
className: 'onboarding-otp-resend',
|
|
2565
|
+
});
|
|
2566
|
+
const resendText = this.createElement('p', {
|
|
2567
|
+
className: 'onboarding-otp-resend-text',
|
|
2568
|
+
});
|
|
2569
|
+
resendText.innerHTML = 'Didn\'t receive the email? <span class="onboarding-otp-resend-link" id="otp-resend-link">Resend</span>';
|
|
2570
|
+
resendSection.appendChild(resendText);
|
|
2571
|
+
this.otpVerificationScreen.appendChild(resendSection);
|
|
2572
|
+
// Add resend click handler
|
|
2573
|
+
const resendLink = resendSection.querySelector('#otp-resend-link');
|
|
2574
|
+
if (resendLink) {
|
|
2575
|
+
this.resendButton = resendLink;
|
|
2576
|
+
resendLink.addEventListener('click', () => this.handleResendOtp());
|
|
2577
|
+
}
|
|
2578
|
+
// Footer (add to OTP screen as well)
|
|
2579
|
+
if (this.config.showFooter) {
|
|
2580
|
+
const textColor = this.config.colors?.text ||
|
|
2581
|
+
(this.config.theme === 'dark' ? '#9ca3af' : '#6b7280');
|
|
2582
|
+
const brandColor = this.config.colors?.text ||
|
|
2583
|
+
(this.config.theme === 'dark' ? '#e5e7eb' : '#374151');
|
|
2584
|
+
const otpFooter = this.createElement('div', { className: 'onboarding-footer' });
|
|
2585
|
+
const otpFooterText = this.createElement('p', {
|
|
2586
|
+
className: 'onboarding-footer-text',
|
|
2587
|
+
style: `color: ${textColor}; display: flex; align-items: center; gap: 6px; justify-content: center;`,
|
|
2588
|
+
});
|
|
2589
|
+
// "Powered by" text
|
|
2590
|
+
const poweredBySpan = this.createElement('span', { textContent: 'Powered by', style: `color: ${textColor};` });
|
|
2591
|
+
otpFooterText.appendChild(poweredBySpan);
|
|
2592
|
+
// Inline brand with logo (clickable link)
|
|
2593
|
+
const brandContainer = this.createElement('a', {
|
|
2594
|
+
href: 'https://www.abstraxn.com/',
|
|
2595
|
+
target: '_blank',
|
|
2596
|
+
rel: 'noopener noreferrer',
|
|
2597
|
+
style: 'display: inline-flex; align-items: center; gap: 6px; text-decoration: none; cursor: pointer;',
|
|
2598
|
+
'aria-label': 'Visit Abstraxn website',
|
|
2599
|
+
});
|
|
2600
|
+
// Abstraxn logo SVG
|
|
2601
|
+
const logoSvg = this.createElement('span', {
|
|
2602
|
+
innerHTML: `
|
|
2603
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none" style="width: 20px; height: 20px; display: inline-block; vertical-align: middle;">
|
|
2604
|
+
<path d="M47.9082 37.191L23.9541 23.553L0 9.91504L37.7436 -8.13502e-06L47.9082 37.191Z" fill="url(#paint0_linear_1242_781_otp)"/>
|
|
2605
|
+
<path d="M13.5156 46.3594L10.1846 47.2344L8.99219 42.873L12.4365 42.4111L13.5156 46.3594ZM20.4268 44.5566L17.1152 45.4199L16.125 41.8818L19.5566 41.3721L20.4268 44.5566ZM26.9307 42.834L23.6152 43.6982L22.8438 40.9443L26.2744 40.4326L26.9307 42.834ZM33.5078 41.1074L30.1875 41.9727L29.6328 39.9951L33.0635 39.4844L33.5078 41.1074ZM39.998 39.4014L36.6729 40.2686L36.3359 39.0674L39.7656 38.5508L39.998 39.4014ZM10.1406 33.9434L11.6807 39.5762L8.15918 39.7568L6.46094 33.543L10.1406 33.9434ZM17.6582 34.5996L18.8916 39.1113L15.4199 39.3496L14.0078 34.3076L17.6582 34.5996ZM24.8779 35.332L25.8047 38.7227L22.3252 38.959L21.2266 35.0361L24.8779 35.332ZM32.0918 36.0723L32.7119 38.3398L29.2227 38.5723L28.4375 35.7656L32.0918 36.0723ZM43.2373 38.5488L43.1172 38.1172L47.7939 37.3516L43.2373 38.5488ZM39.3047 36.8096L39.6182 37.9561L36.1221 38.1846L35.6484 36.4961L39.3047 36.8096ZM47.7939 37.3516L43.0107 37.7939L42.8516 37.2236L47.7939 37.3516ZM47.8291 37.2988L42.7227 36.7529L42.5547 36.1523L47.8291 37.2988ZM47.8652 37.2412L42.3711 35.3486L42.2031 34.7471L47.8652 37.2412ZM38.748 34.8604L39.0664 36.0254L35.3369 35.4043L34.8594 33.6982L38.748 34.8604ZM31.0576 32.3994L31.6787 34.6709L27.959 34.0566L27.1719 31.2461L31.0576 32.3994ZM38.0811 32.6318L38.4092 33.8301L34.4893 32.3496L34.1543 31.1523C34.1005 30.9591 34.0322 30.7719 33.9531 30.5918L38.0811 32.6318ZM23.3682 29.9385L24.291 33.3164L20.5801 32.71L19.4844 28.7949L23.3682 29.9385ZM15.6641 27.4805L16.8896 31.9629L13.1875 31.3594L11.7812 26.3379L15.6641 27.4805ZM29.9463 28.4678L30.5693 30.748L26.6201 29.2812L25.8633 26.583C25.8534 26.5431 25.8417 26.5028 25.8281 26.4639L29.9463 28.4678ZM7.6875 25.0938L9.21484 30.6816L5.44727 29.959L3.75781 23.7793L7.6875 25.0938ZM21.5312 23.3623L22.4492 26.7227L18.5059 25.2627L17.4141 21.3643L21.5312 23.3623ZM13.6104 20.1602L14.8242 24.6006L10.8887 23.1387L9.49219 18.1553L13.6104 20.1602ZM5.2168 16.0654L6.72559 21.585L2.70703 19.9473L1.06055 13.8701L5.2168 16.0654Z" fill="url(#paint1_linear_1242_781_otp)"/>
|
|
2606
|
+
<defs>
|
|
2607
|
+
<linearGradient id="paint0_linear_1242_781_otp" x1="7.03362" y1="6.09543" x2="47.2003" y2="57.0656" gradientUnits="userSpaceOnUse">
|
|
2608
|
+
<stop stop-color="#F878D2"/>
|
|
2609
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
2610
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
2611
|
+
</linearGradient>
|
|
2612
|
+
<linearGradient id="paint1_linear_1242_781_otp" x1="2.53739" y1="21.5136" x2="48.7969" y2="38.5742" gradientUnits="userSpaceOnUse">
|
|
2613
|
+
<stop stop-color="#F878D2"/>
|
|
2614
|
+
<stop offset="0.393522" stop-color="#FDCC81"/>
|
|
2615
|
+
<stop offset="1" stop-color="#F9A0D9"/>
|
|
2616
|
+
</linearGradient>
|
|
2617
|
+
</defs>
|
|
2618
|
+
</svg>
|
|
2619
|
+
`,
|
|
2620
|
+
style: 'display: inline-flex; align-items: center;',
|
|
2621
|
+
});
|
|
2622
|
+
brandContainer.appendChild(logoSvg);
|
|
2623
|
+
const brandText = this.createElement('span', {
|
|
2624
|
+
textContent: 'abstraxn',
|
|
2625
|
+
style: `color: ${brandColor}; font-weight: 700; letter-spacing: 0.2px;`,
|
|
2626
|
+
});
|
|
2627
|
+
brandContainer.appendChild(brandText);
|
|
2628
|
+
otpFooterText.appendChild(brandContainer);
|
|
2629
|
+
otpFooter.appendChild(otpFooterText);
|
|
2630
|
+
this.otpVerificationScreen.appendChild(otpFooter);
|
|
2631
|
+
}
|
|
2632
|
+
// Append to card
|
|
2633
|
+
card.appendChild(this.otpVerificationScreen);
|
|
2634
|
+
// Focus first input
|
|
2635
|
+
setTimeout(() => {
|
|
2636
|
+
if (this.otpInputs[0]) {
|
|
2637
|
+
this.otpInputs[0].focus();
|
|
2638
|
+
}
|
|
2639
|
+
}, 100);
|
|
2640
|
+
// Start resend cooldown
|
|
2641
|
+
this.startResendCooldown();
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Create email icon SVG
|
|
2645
|
+
*/
|
|
2646
|
+
createEmailIconSVG() {
|
|
2647
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
2648
|
+
svg.setAttribute('class', 'onboarding-otp-icon');
|
|
2649
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
2650
|
+
svg.setAttribute('fill', 'none');
|
|
2651
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
2652
|
+
svg.setAttribute('stroke-width', '2');
|
|
2653
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
2654
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
2655
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
2656
|
+
path.setAttribute('d', 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z');
|
|
2657
|
+
svg.appendChild(path);
|
|
2658
|
+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
2659
|
+
polyline.setAttribute('points', '22,6 12,13 2,6');
|
|
2660
|
+
svg.appendChild(polyline);
|
|
2661
|
+
return svg;
|
|
2662
|
+
}
|
|
2663
|
+
/**
|
|
2664
|
+
* Handle OTP input - auto-advance to next field
|
|
2665
|
+
*/
|
|
2666
|
+
handleOtpInput(e, index) {
|
|
2667
|
+
const input = e.target;
|
|
2668
|
+
const value = input.value.replace(/[^0-9]/g, '');
|
|
2669
|
+
if (value) {
|
|
2670
|
+
input.value = value;
|
|
2671
|
+
// Move to next input if available
|
|
2672
|
+
if (index < 5 && this.otpInputs[index + 1]) {
|
|
2673
|
+
this.otpInputs[index + 1].focus();
|
|
2674
|
+
}
|
|
2675
|
+
// Don't auto-verify, wait for user to click Verify button
|
|
2676
|
+
}
|
|
2677
|
+
else {
|
|
2678
|
+
input.value = '';
|
|
2679
|
+
}
|
|
2680
|
+
this.updateOtpValue();
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Handle OTP keydown - backspace to go to previous field
|
|
2684
|
+
*/
|
|
2685
|
+
handleOtpKeydown(e, index) {
|
|
2686
|
+
const input = e.target;
|
|
2687
|
+
if (e.key === 'Backspace' && !input.value && index > 0) {
|
|
2688
|
+
this.otpInputs[index - 1].focus();
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
/**
|
|
2692
|
+
* Handle OTP paste - fill all fields from clipboard
|
|
2693
|
+
*/
|
|
2694
|
+
handleOtpPaste(e) {
|
|
2695
|
+
e.preventDefault();
|
|
2696
|
+
const pastedData = e.clipboardData?.getData('text') || '';
|
|
2697
|
+
const digits = pastedData.replace(/[^0-9]/g, '').slice(0, 6);
|
|
2698
|
+
for (let i = 0; i < 6; i++) {
|
|
2699
|
+
if (this.otpInputs[i]) {
|
|
2700
|
+
this.otpInputs[i].value = digits[i] || '';
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
// Focus the last filled input or the last input
|
|
2704
|
+
const lastFilledIndex = Math.min(digits.length - 1, 5);
|
|
2705
|
+
if (this.otpInputs[lastFilledIndex]) {
|
|
2706
|
+
this.otpInputs[lastFilledIndex].focus();
|
|
2707
|
+
}
|
|
2708
|
+
this.updateOtpValue();
|
|
2709
|
+
// Don't auto-verify, wait for user to click Verify button
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Update OTP value from input fields
|
|
2713
|
+
*/
|
|
2714
|
+
updateOtpValue() {
|
|
2715
|
+
this.otp = this.otpInputs.map(input => input.value).join('');
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Handle OTP verification
|
|
2719
|
+
*/
|
|
2720
|
+
async handleOtpVerify() {
|
|
2721
|
+
this.updateOtpValue();
|
|
2722
|
+
if (this.otp.length !== 6) {
|
|
2723
|
+
this.setError('Please enter the complete 6-digit code');
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
// Disable inputs during verification
|
|
2727
|
+
this.otpInputs.forEach(input => {
|
|
2728
|
+
input.disabled = true;
|
|
2729
|
+
});
|
|
2730
|
+
this.setLoading(true, 'email');
|
|
2731
|
+
this.setError(null);
|
|
2732
|
+
try {
|
|
2733
|
+
if (this.config.onEmailOtpVerify) {
|
|
2734
|
+
const result = await this.config.onEmailOtpVerify(this.email, this.otp);
|
|
2735
|
+
if (result.success) {
|
|
2736
|
+
this.config.onLoginSuccess?.(result);
|
|
2737
|
+
}
|
|
2738
|
+
else {
|
|
2739
|
+
throw new Error(result.error || 'Invalid verification code');
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
else if (this.config.emailOtpEndpoint) {
|
|
2743
|
+
const response = await fetch(`${this.config.emailOtpEndpoint}/verify`, {
|
|
2744
|
+
method: 'POST',
|
|
2745
|
+
headers: {
|
|
2746
|
+
'Content-Type': 'application/json',
|
|
2747
|
+
},
|
|
2748
|
+
body: JSON.stringify({ email: this.email, otp: this.otp }),
|
|
2749
|
+
});
|
|
2750
|
+
const data = await response.json();
|
|
2751
|
+
if (data.success) {
|
|
2752
|
+
this.config.onLoginSuccess?.({ token: data.token });
|
|
2753
|
+
}
|
|
2754
|
+
else {
|
|
2755
|
+
throw new Error(data.message || 'Invalid verification code');
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
catch (err) {
|
|
2760
|
+
const errorMessage = err instanceof Error ? err.message : 'An error occurred';
|
|
2761
|
+
this.setError(errorMessage);
|
|
2762
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2763
|
+
// Clear OTP inputs on error
|
|
2764
|
+
this.otpInputs.forEach(input => {
|
|
2765
|
+
input.value = '';
|
|
2766
|
+
input.disabled = false;
|
|
2767
|
+
});
|
|
2768
|
+
this.otp = '';
|
|
2769
|
+
if (this.otpInputs[0]) {
|
|
2770
|
+
this.otpInputs[0].focus();
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
finally {
|
|
2774
|
+
this.setLoading(false);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Handle resend OTP
|
|
2779
|
+
*/
|
|
2780
|
+
async handleResendOtp() {
|
|
2781
|
+
if (this.resendCooldown > 0) {
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
this.setError(null);
|
|
2785
|
+
this.setLoading(true, 'email');
|
|
2786
|
+
try {
|
|
2787
|
+
if (this.config.onEmailOtpInitiate) {
|
|
2788
|
+
await this.config.onEmailOtpInitiate(this.email);
|
|
2789
|
+
}
|
|
2790
|
+
else if (this.config.emailOtpEndpoint) {
|
|
2791
|
+
const response = await fetch(this.config.emailOtpEndpoint, {
|
|
2792
|
+
method: 'POST',
|
|
2793
|
+
headers: {
|
|
2794
|
+
'Content-Type': 'application/json',
|
|
2795
|
+
},
|
|
2796
|
+
body: JSON.stringify({ email: this.email }),
|
|
2797
|
+
});
|
|
2798
|
+
if (!response.ok) {
|
|
2799
|
+
throw new Error('Failed to resend OTP');
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
// Clear OTP inputs
|
|
2803
|
+
this.otpInputs.forEach(input => {
|
|
2804
|
+
input.value = '';
|
|
2805
|
+
input.disabled = false;
|
|
2806
|
+
});
|
|
2807
|
+
this.otp = '';
|
|
2808
|
+
if (this.otpInputs[0]) {
|
|
2809
|
+
this.otpInputs[0].focus();
|
|
2810
|
+
}
|
|
2811
|
+
// Start cooldown
|
|
2812
|
+
this.startResendCooldown();
|
|
2813
|
+
}
|
|
2814
|
+
catch (err) {
|
|
2815
|
+
const errorMessage = err instanceof Error ? err.message : 'Failed to resend OTP';
|
|
2816
|
+
this.setError(errorMessage);
|
|
2817
|
+
this.config.onLoginError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
2818
|
+
}
|
|
2819
|
+
finally {
|
|
2820
|
+
this.setLoading(false);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Start resend cooldown timer
|
|
2825
|
+
*/
|
|
2826
|
+
startResendCooldown() {
|
|
2827
|
+
this.resendCooldown = 60; // 60 seconds cooldown
|
|
2828
|
+
const updateCooldown = () => {
|
|
2829
|
+
if (this.resendCooldown > 0 && this.resendButton) {
|
|
2830
|
+
this.resendButton.textContent = `Resend (${this.resendCooldown}s)`;
|
|
2831
|
+
this.resendButton.setAttribute('disabled', 'true');
|
|
2832
|
+
this.resendCooldown--;
|
|
2833
|
+
setTimeout(updateCooldown, 1000);
|
|
2834
|
+
}
|
|
2835
|
+
else if (this.resendButton) {
|
|
2836
|
+
this.resendButton.textContent = 'Resend';
|
|
2837
|
+
this.resendButton.removeAttribute('disabled');
|
|
2838
|
+
}
|
|
2839
|
+
};
|
|
2840
|
+
updateCooldown();
|
|
2841
|
+
}
|
|
2842
|
+
/**
|
|
2843
|
+
* Set error message
|
|
2844
|
+
*/
|
|
2845
|
+
setError(message) {
|
|
2846
|
+
// Show error in OTP screen if it exists
|
|
2847
|
+
if (this.otpVerificationScreen) {
|
|
2848
|
+
const otpErrorElement = this.otpVerificationScreen.errorElement;
|
|
2849
|
+
if (otpErrorElement) {
|
|
2850
|
+
if (message) {
|
|
2851
|
+
otpErrorElement.textContent = message;
|
|
2852
|
+
otpErrorElement.style.display = 'block';
|
|
2853
|
+
}
|
|
2854
|
+
else {
|
|
2855
|
+
otpErrorElement.style.display = 'none';
|
|
2856
|
+
}
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
// Fallback to regular error element
|
|
2861
|
+
if (!this.errorElement)
|
|
2862
|
+
return;
|
|
2863
|
+
if (message) {
|
|
2864
|
+
this.errorElement.textContent = message;
|
|
2865
|
+
this.errorElement.style.display = 'block';
|
|
2866
|
+
}
|
|
2867
|
+
else {
|
|
2868
|
+
this.errorElement.style.display = 'none';
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Set loading state
|
|
2873
|
+
*/
|
|
2874
|
+
setLoading(loading, buttonType) {
|
|
2875
|
+
this.loading = loading;
|
|
2876
|
+
// Track which button is active
|
|
2877
|
+
if (loading && buttonType) {
|
|
2878
|
+
this.activeButton = buttonType;
|
|
2879
|
+
}
|
|
2880
|
+
else if (!loading) {
|
|
2881
|
+
this.activeButton = null;
|
|
2882
|
+
}
|
|
2883
|
+
// Email/Continue button
|
|
2884
|
+
if (this.continueButton) {
|
|
2885
|
+
this.continueButton.disabled = loading || !this.email;
|
|
2886
|
+
// Show loading text only if this is the active button
|
|
2887
|
+
if (loading && this.activeButton === 'email') {
|
|
2888
|
+
this.continueButton.textContent = this.otpSent ? 'Verifying...' : 'Loading...';
|
|
2889
|
+
}
|
|
2890
|
+
else if (!loading) {
|
|
2891
|
+
this.continueButton.textContent = this.otpSent ? 'Verify OTP' : 'Continue';
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
// Email arrow button (when all auth methods are enabled)
|
|
2895
|
+
if (this.emailArrowButton) {
|
|
2896
|
+
this.emailArrowButton.disabled = loading || !this.email;
|
|
2897
|
+
}
|
|
2898
|
+
// Google button
|
|
2899
|
+
if (this.googleButton) {
|
|
2900
|
+
this.googleButton.disabled = loading;
|
|
2901
|
+
// Google button doesn't change text, just gets disabled
|
|
2902
|
+
}
|
|
2903
|
+
// Twitter button
|
|
2904
|
+
if (this.twitterButton) {
|
|
2905
|
+
this.twitterButton.disabled = loading;
|
|
2906
|
+
}
|
|
2907
|
+
// Discord button
|
|
2908
|
+
if (this.discordButton) {
|
|
2909
|
+
this.discordButton.disabled = loading;
|
|
2910
|
+
}
|
|
2911
|
+
// Passkey login button
|
|
2912
|
+
if (this.passkeyLoginButton) {
|
|
2913
|
+
this.passkeyLoginButton.disabled = loading;
|
|
2914
|
+
// Show loading text only if this is the active button
|
|
2915
|
+
if (loading && this.activeButton === 'passkey') {
|
|
2916
|
+
// Update only the text node, preserve the icon
|
|
2917
|
+
const textNodes = Array.from(this.passkeyLoginButton.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
|
|
2918
|
+
if (textNodes.length > 0) {
|
|
2919
|
+
textNodes[0].textContent = ' Authenticating...';
|
|
2920
|
+
}
|
|
2921
|
+
else {
|
|
2922
|
+
// If no text node exists, append one (icon should already be there)
|
|
2923
|
+
this.passkeyLoginButton.appendChild(document.createTextNode(' Authenticating...'));
|
|
2924
|
+
}
|
|
2925
|
+
this.passkeyLoginButton.style.opacity = '0.7';
|
|
2926
|
+
this.passkeyLoginButton.style.cursor = 'not-allowed';
|
|
2927
|
+
}
|
|
2928
|
+
else if (!loading) {
|
|
2929
|
+
// Update only the text node, preserve the icon
|
|
2930
|
+
const textNodes = Array.from(this.passkeyLoginButton.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
|
|
2931
|
+
const passkeyText = ` ${this.config.labels?.passkeyLoginButton || 'Log in with passkey'}`;
|
|
2932
|
+
if (textNodes.length > 0) {
|
|
2933
|
+
textNodes[0].textContent = passkeyText;
|
|
2934
|
+
}
|
|
2935
|
+
else {
|
|
2936
|
+
// If no text node exists, append one (icon should already be there)
|
|
2937
|
+
this.passkeyLoginButton.appendChild(document.createTextNode(passkeyText));
|
|
2938
|
+
}
|
|
2939
|
+
this.passkeyLoginButton.style.opacity = '1';
|
|
2940
|
+
this.passkeyLoginButton.style.cursor = 'pointer';
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
// Passkey signup link
|
|
2944
|
+
if (this.passkeySignupLink) {
|
|
2945
|
+
if (loading) {
|
|
2946
|
+
this.passkeySignupLink.style.opacity = '0.5';
|
|
2947
|
+
this.passkeySignupLink.style.pointerEvents = 'none';
|
|
2948
|
+
this.passkeySignupLink.style.cursor = 'not-allowed';
|
|
2949
|
+
}
|
|
2950
|
+
else {
|
|
2951
|
+
this.passkeySignupLink.style.opacity = '1';
|
|
2952
|
+
this.passkeySignupLink.style.pointerEvents = 'auto';
|
|
2953
|
+
this.passkeySignupLink.style.cursor = 'pointer';
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
// Email input
|
|
2957
|
+
if (this.emailInput) {
|
|
2958
|
+
this.emailInput.disabled = loading || this.otpSent;
|
|
2959
|
+
}
|
|
2960
|
+
// OTP input
|
|
2961
|
+
if (this.otpInput) {
|
|
2962
|
+
this.otpInput.disabled = loading;
|
|
2963
|
+
}
|
|
2964
|
+
// Verify button
|
|
2965
|
+
if (this.verifyButton) {
|
|
2966
|
+
this.verifyButton.disabled = loading;
|
|
2967
|
+
if (loading) {
|
|
2968
|
+
this.verifyButton.textContent = 'Verifying...';
|
|
2969
|
+
this.verifyButton.style.opacity = '0.7';
|
|
2970
|
+
this.verifyButton.style.cursor = 'not-allowed';
|
|
2971
|
+
}
|
|
2972
|
+
else {
|
|
2973
|
+
this.verifyButton.textContent = 'Verify';
|
|
2974
|
+
this.verifyButton.style.opacity = '1';
|
|
2975
|
+
this.verifyButton.style.cursor = 'pointer';
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Update button state
|
|
2981
|
+
*/
|
|
2982
|
+
updateButtonState() {
|
|
2983
|
+
if (this.continueButton) {
|
|
2984
|
+
this.continueButton.disabled = this.loading || !this.email;
|
|
2985
|
+
}
|
|
2986
|
+
// Update arrow button state (when all auth methods are enabled)
|
|
2987
|
+
if (this.emailArrowButton) {
|
|
2988
|
+
this.emailArrowButton.disabled = this.loading || !this.email;
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Update theme
|
|
2993
|
+
*/
|
|
2994
|
+
setTheme(theme) {
|
|
2995
|
+
this.config.theme = theme;
|
|
2996
|
+
if (this.rootElement) {
|
|
2997
|
+
this.rootElement.className = `onboarding-container onboarding-theme-${theme} ${this.config.className}`;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Show/hide component
|
|
3002
|
+
*/
|
|
3003
|
+
setVisible(visible) {
|
|
3004
|
+
if (this.rootElement) {
|
|
3005
|
+
this.rootElement.style.display = visible ? '' : 'none';
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Reset OTP screen and show initial login form
|
|
3010
|
+
*/
|
|
3011
|
+
resetToLoginForm() {
|
|
3012
|
+
// Clear OTP verification screen
|
|
3013
|
+
if (this.otpVerificationScreen) {
|
|
3014
|
+
this.otpVerificationScreen.remove();
|
|
3015
|
+
this.otpVerificationScreen = null;
|
|
3016
|
+
}
|
|
3017
|
+
// Remove loading/error mode classes
|
|
3018
|
+
const card = this.rootElement?.querySelector('.onboarding-card');
|
|
3019
|
+
if (card) {
|
|
3020
|
+
card.classList.remove('onboarding-mode-loading');
|
|
3021
|
+
card.classList.remove('onboarding-mode-error');
|
|
3022
|
+
}
|
|
3023
|
+
// Hide loading modal if exists (for inline components)
|
|
3024
|
+
this.hideLoadingModal();
|
|
3025
|
+
// Remove error container if exists
|
|
3026
|
+
const errorContainer = this.rootElement?.querySelector('.onboarding-error-container');
|
|
3027
|
+
if (errorContainer) {
|
|
3028
|
+
errorContainer.remove();
|
|
3029
|
+
}
|
|
3030
|
+
// Clear OTP inputs
|
|
3031
|
+
this.otpInputs.forEach(input => {
|
|
3032
|
+
input.value = '';
|
|
3033
|
+
input.disabled = false;
|
|
3034
|
+
});
|
|
3035
|
+
this.otpInputs = [];
|
|
3036
|
+
this.otp = '';
|
|
3037
|
+
this.otpSent = false;
|
|
3038
|
+
this.loading = false;
|
|
3039
|
+
// Clear timer
|
|
3040
|
+
this.resendCooldown = 0;
|
|
3041
|
+
if (this.resendButton) {
|
|
3042
|
+
this.resendButton.textContent = 'Resend';
|
|
3043
|
+
this.resendButton.removeAttribute('disabled');
|
|
3044
|
+
}
|
|
3045
|
+
// Get auth methods
|
|
3046
|
+
const authMethods = this.config.authMethods || ['otp', 'google'];
|
|
3047
|
+
const showEmail = authMethods.includes('otp');
|
|
3048
|
+
const showGoogle = authMethods.includes('google');
|
|
3049
|
+
const showTwitter = authMethods.includes('twitter');
|
|
3050
|
+
const showDiscord = authMethods.includes('discord');
|
|
3051
|
+
const showPasskey = authMethods.includes('passkey');
|
|
3052
|
+
const socialMethods = [
|
|
3053
|
+
{ key: 'google', show: showGoogle },
|
|
3054
|
+
{ key: 'twitter', show: showTwitter },
|
|
3055
|
+
{ key: 'discord', show: showDiscord },
|
|
3056
|
+
].filter((m) => m.show);
|
|
3057
|
+
// Show the email form and all login-related elements
|
|
3058
|
+
if (this.emailForm) {
|
|
3059
|
+
this.emailForm.style.display = showEmail ? '' : 'none';
|
|
3060
|
+
}
|
|
3061
|
+
if (this.divider) {
|
|
3062
|
+
this.divider.style.display = (showEmail && socialMethods.length > 0) ? '' : 'none';
|
|
3063
|
+
}
|
|
3064
|
+
if (this.passkeyDivider) {
|
|
3065
|
+
this.passkeyDivider.style.display = (showPasskey && (showEmail || socialMethods.length > 0)) ? '' : 'none';
|
|
3066
|
+
}
|
|
3067
|
+
if (this.googleButton) {
|
|
3068
|
+
this.googleButton.style.display = showGoogle ? '' : 'none';
|
|
3069
|
+
this.googleButton.disabled = false;
|
|
3070
|
+
}
|
|
3071
|
+
if (this.twitterButton) {
|
|
3072
|
+
this.twitterButton.style.display = showTwitter ? '' : 'none';
|
|
3073
|
+
this.twitterButton.disabled = false;
|
|
3074
|
+
}
|
|
3075
|
+
if (this.discordButton) {
|
|
3076
|
+
this.discordButton.style.display = showDiscord ? '' : 'none';
|
|
3077
|
+
this.discordButton.disabled = false;
|
|
3078
|
+
}
|
|
3079
|
+
if (this.socialGrid) {
|
|
3080
|
+
this.socialGrid.style.display = socialMethods.length > 1 ? '' : 'none';
|
|
3081
|
+
}
|
|
3082
|
+
// Show passkey buttons again when returning to login form
|
|
3083
|
+
if (this.passkeyLoginButton) {
|
|
3084
|
+
this.passkeyLoginButton.style.display = showPasskey ? '' : 'none';
|
|
3085
|
+
}
|
|
3086
|
+
if (this.passkeySignupLink) {
|
|
3087
|
+
this.passkeySignupLink.style.display = showPasskey ? '' : 'none';
|
|
3088
|
+
}
|
|
3089
|
+
if (this.passkeyErrorElement) {
|
|
3090
|
+
this.passkeyErrorElement.style.display = 'none'; // Hide error when resetting
|
|
3091
|
+
}
|
|
3092
|
+
// Show external wallets again when returning to login form
|
|
3093
|
+
// Note: The visibility will be managed by AbstraxnProvider, but we ensure they're available
|
|
3094
|
+
// AbstraxnProvider will handle showing them if external wallets are enabled
|
|
3095
|
+
if (this.footer) {
|
|
3096
|
+
this.footer.style.display = '';
|
|
3097
|
+
}
|
|
3098
|
+
// Clear error messages
|
|
3099
|
+
this.setError(null);
|
|
3100
|
+
if (this.errorElement) {
|
|
3101
|
+
this.errorElement.style.display = 'none';
|
|
3102
|
+
}
|
|
3103
|
+
// Reset email input - enable it and clear value
|
|
3104
|
+
if (this.emailInput) {
|
|
3105
|
+
this.emailInput.value = '';
|
|
3106
|
+
this.emailInput.disabled = false;
|
|
3107
|
+
this.email = '';
|
|
3108
|
+
}
|
|
3109
|
+
// Reset OTP group (hidden input)
|
|
3110
|
+
if (this.otpGroup) {
|
|
3111
|
+
this.otpGroup.style.display = 'none';
|
|
3112
|
+
if (this.otpInput) {
|
|
3113
|
+
this.otpInput.value = '';
|
|
3114
|
+
this.otpInput.disabled = false;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
// Reset button state - ensure it shows "Continue" and is properly enabled/disabled
|
|
3118
|
+
if (this.continueButton) {
|
|
3119
|
+
this.continueButton.disabled = !this.email; // Disabled only if no email
|
|
3120
|
+
this.continueButton.textContent = this.config.labels?.emailButton || 'Continue';
|
|
3121
|
+
}
|
|
3122
|
+
// Update button state to ensure everything is in sync
|
|
3123
|
+
this.updateButtonState();
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Close the modal
|
|
3127
|
+
*/
|
|
3128
|
+
close() {
|
|
3129
|
+
// Clear URL params to prevent showing error again on refresh
|
|
3130
|
+
const url = new URL(window.location.href);
|
|
3131
|
+
url.searchParams.delete('error');
|
|
3132
|
+
url.searchParams.delete('success');
|
|
3133
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
3134
|
+
if (this.modalOverlay) {
|
|
3135
|
+
// Add closing animation
|
|
3136
|
+
this.modalOverlay.classList.remove('onboarding-modal-open');
|
|
3137
|
+
this.modalOverlay.classList.add('onboarding-modal-closing');
|
|
3138
|
+
// Remove after animation
|
|
3139
|
+
setTimeout(() => {
|
|
3140
|
+
this.destroy();
|
|
3141
|
+
}, 200);
|
|
3142
|
+
}
|
|
3143
|
+
else {
|
|
3144
|
+
this.destroy();
|
|
3145
|
+
}
|
|
3146
|
+
// Restore body scroll
|
|
3147
|
+
document.body.style.overflow = '';
|
|
3148
|
+
// Call onClose callback
|
|
3149
|
+
if (this.config.onClose) {
|
|
3150
|
+
this.config.onClose();
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* Destroy the component
|
|
3155
|
+
*/
|
|
3156
|
+
destroy() {
|
|
3157
|
+
// Reset to login form before destroying to ensure clean state
|
|
3158
|
+
this.resetToLoginForm();
|
|
3159
|
+
// Clean up modal
|
|
3160
|
+
if (this.modalOverlay && this.modalOverlay.parentNode) {
|
|
3161
|
+
this.modalOverlay.parentNode.removeChild(this.modalOverlay);
|
|
3162
|
+
}
|
|
3163
|
+
// Clean up inline
|
|
3164
|
+
if (this.rootElement && this.rootElement.parentNode) {
|
|
3165
|
+
this.rootElement.parentNode.removeChild(this.rootElement);
|
|
3166
|
+
}
|
|
3167
|
+
// Restore body scroll
|
|
3168
|
+
document.body.style.overflow = '';
|
|
3169
|
+
// Clear references
|
|
3170
|
+
this.rootElement = null;
|
|
3171
|
+
this.modalOverlay = null;
|
|
3172
|
+
this.modalContent = null;
|
|
3173
|
+
this.emailInput = null;
|
|
3174
|
+
this.otpInput = null;
|
|
3175
|
+
this.emailForm = null;
|
|
3176
|
+
this.continueButton = null;
|
|
3177
|
+
this.googleButton = null;
|
|
3178
|
+
this.errorElement = null;
|
|
3179
|
+
this.otpGroup = null;
|
|
3180
|
+
this.closeButton = null;
|
|
3181
|
+
this.otpVerificationScreen = null; // Clear OTP screen reference
|
|
3182
|
+
this.externalWalletContainer = null; // Clear external wallet container reference
|
|
3183
|
+
this.externalWalletDivider = null; // Clear external wallet divider reference
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Create DOM element helper
|
|
3187
|
+
*/
|
|
3188
|
+
createElement(tag, options = {}) {
|
|
3189
|
+
const element = document.createElement(tag);
|
|
3190
|
+
Object.keys(options).forEach((key) => {
|
|
3191
|
+
const value = options[key];
|
|
3192
|
+
if (key === 'className' && typeof value === 'string') {
|
|
3193
|
+
element.className = value;
|
|
3194
|
+
}
|
|
3195
|
+
else if (key === 'textContent' && (typeof value === 'string' || value === null)) {
|
|
3196
|
+
element.textContent = value;
|
|
3197
|
+
}
|
|
3198
|
+
else if (key === 'innerHTML' && typeof value === 'string') {
|
|
3199
|
+
element.innerHTML = value;
|
|
3200
|
+
}
|
|
3201
|
+
else if (key === 'style' && typeof value === 'string') {
|
|
3202
|
+
element.setAttribute('style', value);
|
|
3203
|
+
}
|
|
3204
|
+
else if (key === 'htmlFor' && typeof value === 'string') {
|
|
3205
|
+
// Use setAttribute for htmlFor to ensure it works correctly
|
|
3206
|
+
element.setAttribute('for', value);
|
|
3207
|
+
}
|
|
3208
|
+
else if (key.startsWith('aria-') || key === 'role' || key === 'tabIndex' || key === 'autocomplete' || key === 'noValidate') {
|
|
3209
|
+
// Handle ARIA attributes and other special attributes
|
|
3210
|
+
if (key === 'tabIndex') {
|
|
3211
|
+
element.tabIndex = value;
|
|
3212
|
+
}
|
|
3213
|
+
else if (key === 'noValidate') {
|
|
3214
|
+
element.noValidate = value;
|
|
3215
|
+
}
|
|
3216
|
+
else {
|
|
3217
|
+
element.setAttribute(key, String(value));
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
else if (key !== 'style' && value !== undefined) {
|
|
3221
|
+
element[key] = value;
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
return element;
|
|
3225
|
+
}
|
|
3226
|
+
/**
|
|
3227
|
+
* Parse style object or string
|
|
3228
|
+
*/
|
|
3229
|
+
parseStyle(style) {
|
|
3230
|
+
if (!style)
|
|
3231
|
+
return '';
|
|
3232
|
+
if (typeof style === 'string')
|
|
3233
|
+
return style;
|
|
3234
|
+
return Object.entries(style)
|
|
3235
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
3236
|
+
.map(([key, value]) => {
|
|
3237
|
+
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
3238
|
+
return `${cssKey}: ${value}`;
|
|
3239
|
+
})
|
|
3240
|
+
.join('; ');
|
|
3241
|
+
}
|
|
3242
|
+
/**
|
|
3243
|
+
* Create email icon SVG
|
|
3244
|
+
*/
|
|
3245
|
+
createEmailIcon() {
|
|
3246
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3247
|
+
svg.setAttribute('class', 'onboarding-input-icon');
|
|
3248
|
+
svg.setAttribute('width', '20');
|
|
3249
|
+
svg.setAttribute('height', '20');
|
|
3250
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
3251
|
+
svg.setAttribute('fill', 'none');
|
|
3252
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3253
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3254
|
+
path.setAttribute('d', 'M2.5 6.66667L10 11.6667L17.5 6.66667M3.33333 15H16.6667C17.5871 15 18.3333 14.2538 18.3333 13.3333V6.66667C18.3333 5.74619 17.5871 5 16.6667 5H3.33333C2.41286 5 1.66667 5.74619 1.66667 6.66667V13.3333C1.66667 14.2538 2.41286 15 3.33333 15Z');
|
|
3255
|
+
path.setAttribute('stroke', 'currentColor');
|
|
3256
|
+
path.setAttribute('stroke-width', '1.5');
|
|
3257
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
3258
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
3259
|
+
svg.appendChild(path);
|
|
3260
|
+
return svg;
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Create Google icon SVG
|
|
3264
|
+
*/
|
|
3265
|
+
createGoogleIcon() {
|
|
3266
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3267
|
+
svg.setAttribute('class', 'onboarding-google-icon');
|
|
3268
|
+
svg.setAttribute('width', '20');
|
|
3269
|
+
svg.setAttribute('height', '20');
|
|
3270
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
3271
|
+
svg.setAttribute('fill', 'none');
|
|
3272
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3273
|
+
const paths = [
|
|
3274
|
+
{
|
|
3275
|
+
d: 'M18.1712 8.36792H17.5V8.33333H10V11.6667H14.7096C14.2225 13.1071 13.2408 14.2854 12.0125 15.0125L14.9696 17.3458C16.7792 15.6958 17.9167 13.3958 17.9167 10.8333C17.9167 10.275 17.8708 9.72917 17.7833 9.20417L18.1712 8.36792Z',
|
|
3276
|
+
fill: '#4285F4',
|
|
3277
|
+
},
|
|
3278
|
+
{
|
|
3279
|
+
d: 'M10 18.3333C12.5542 18.3333 14.7208 17.4708 16.3042 16.0125L13.3471 13.6792C12.5042 14.2542 11.4042 14.5833 10 14.5833C7.52083 14.5833 5.39583 12.9042 4.61667 10.6917L1.57083 13.0292C3.13333 16.1 6.32083 18.3333 10 18.3333Z',
|
|
3280
|
+
fill: '#34A853',
|
|
3281
|
+
},
|
|
3282
|
+
{
|
|
3283
|
+
d: 'M4.61667 10.6917C4.39583 10.0708 4.27083 9.40417 4.27083 8.70833C4.27083 8.0125 4.39583 7.34583 4.61667 6.72417L1.57083 4.3875C0.9375 5.66667 0.583336 7.10833 0.583336 8.70833C0.583336 10.3083 0.9375 11.75 1.57083 13.0292L4.61667 10.6917Z',
|
|
3284
|
+
fill: '#FBBC05',
|
|
3285
|
+
},
|
|
3286
|
+
{
|
|
3287
|
+
d: 'M10 2.91667C11.5125 2.91667 12.8708 3.47083 13.9292 4.42917L16.375 2C14.7208 0.483333 12.5542 -0.000165871 10 -0.000165871C6.32083 -0.000165871 3.13333 2.23333 1.57083 5.30417L4.61667 7.64167C5.39583 5.42917 7.52083 3.75 10 3.75V2.91667Z',
|
|
3288
|
+
fill: '#EA4335',
|
|
3289
|
+
},
|
|
3290
|
+
];
|
|
3291
|
+
paths.forEach((pathData) => {
|
|
3292
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3293
|
+
path.setAttribute('d', pathData.d);
|
|
3294
|
+
path.setAttribute('fill', pathData.fill);
|
|
3295
|
+
svg.appendChild(path);
|
|
3296
|
+
});
|
|
3297
|
+
return svg;
|
|
3298
|
+
}
|
|
3299
|
+
/**
|
|
3300
|
+
* Create right arrow icon SVG
|
|
3301
|
+
*/
|
|
3302
|
+
createArrowIcon() {
|
|
3303
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3304
|
+
svg.setAttribute('class', 'onboarding-input-arrow-icon');
|
|
3305
|
+
svg.setAttribute('width', '20');
|
|
3306
|
+
svg.setAttribute('height', '20');
|
|
3307
|
+
svg.setAttribute('viewBox', '0 0 20 20');
|
|
3308
|
+
svg.setAttribute('fill', 'none');
|
|
3309
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3310
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3311
|
+
path.setAttribute('d', 'M7.5 15L12.5 10L7.5 5');
|
|
3312
|
+
path.setAttribute('stroke', 'currentColor');
|
|
3313
|
+
path.setAttribute('stroke-width', '2');
|
|
3314
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
3315
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
3316
|
+
svg.appendChild(path);
|
|
3317
|
+
return svg;
|
|
3318
|
+
}
|
|
3319
|
+
/**
|
|
3320
|
+
* Create passkey icon SVG
|
|
3321
|
+
*/
|
|
3322
|
+
createPasskeyIcon() {
|
|
3323
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3324
|
+
svg.setAttribute('class', 'onboarding-passkey-icon');
|
|
3325
|
+
svg.setAttribute('width', '20');
|
|
3326
|
+
svg.setAttribute('height', '20');
|
|
3327
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
3328
|
+
svg.setAttribute('fill', 'none');
|
|
3329
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3330
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
3331
|
+
// Passkey icon (user with key)
|
|
3332
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3333
|
+
path.setAttribute('d', 'M3.682 19.338v-2.102c0-.476.127-.915.382-1.315.255-.4.603-.708 1.042-.926a14.89 14.89 0 0 1 2.939-1.067c.982-.24 1.966-.36 2.953-.36.352 0 .708.018 1.066.054.358.035.712.084 1.062.147-.015.831.164 1.613.537 2.347a4.882 4.882 0 0 0 1.595 1.822v1.4H3.682Zm15.003 2.713-1.303-1.303v-4.029a2.976 2.976 0 0 1-1.564-1.08 2.944 2.944 0 0 1-.608-1.832c0-.841.297-1.557.891-2.147a2.944 2.944 0 0 1 2.154-.886c.837 0 1.552.295 2.145.886.594.59.89 1.306.89 2.148 0 .656-.182 1.238-.546 1.743a3.018 3.018 0 0 1-1.404 1.084l1.073 1.072-1.294 1.31 1.294 1.301-1.728 1.733ZM11 12.006c-.94 0-1.742-.333-2.405-.999a3.28 3.28 0 0 1-.994-2.4c0-.94.331-1.743.994-2.405A3.276 3.276 0 0 1 11 5.208c.94 0 1.742.331 2.405.994.663.662.994 1.463.994 2.4 0 .939-.331 1.74-.994 2.406a3.27 3.27 0 0 1-2.405.998Zm7.252 2.235a.833.833 0 0 0 .612-.255.842.842 0 0 0 .255-.617.834.834 0 0 0-.253-.614.835.835 0 0 0-.612-.252.85.85 0 0 0-.616.251.823.823 0 0 0-.257.612c0 .24.085.447.257.618a.838.838 0 0 0 .614.257Z');
|
|
3334
|
+
// Set icon color based on theme: white for dark theme, black for light theme
|
|
3335
|
+
const iconColor = this.config.theme === 'dark' ? '#ffffff' : '#000000';
|
|
3336
|
+
path.setAttribute('fill', iconColor);
|
|
3337
|
+
svg.appendChild(path);
|
|
3338
|
+
return svg;
|
|
3339
|
+
}
|
|
3340
|
+
/**
|
|
3341
|
+
* Create X (Twitter) icon SVG
|
|
3342
|
+
*/
|
|
3343
|
+
createTwitterIcon() {
|
|
3344
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3345
|
+
svg.setAttribute('class', 'onboarding-social-icon');
|
|
3346
|
+
svg.setAttribute('width', '20');
|
|
3347
|
+
svg.setAttribute('height', '20');
|
|
3348
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
3349
|
+
svg.setAttribute('fill', 'none');
|
|
3350
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3351
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3352
|
+
path.setAttribute('d', 'M4 3h4.6l3.09 4.1L14.5 3H20l-5.63 7.08L20.4 21H15.8l-3.5-4.65L8.3 21H2.8l6-7.67L4 3Zm2.04 1.2L9.7 9l.1.12L4.97 19h1.79l4.04-5.39L14.78 19h1.8l-5.02-6.55-.05-.06L17.97 4.2h-1.8l-3.52 4.62L9 4.2H6.04Z');
|
|
3353
|
+
path.setAttribute('fill', 'currentColor');
|
|
3354
|
+
svg.appendChild(path);
|
|
3355
|
+
return svg;
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Create Discord icon SVG
|
|
3359
|
+
*/
|
|
3360
|
+
createDiscordIcon() {
|
|
3361
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
3362
|
+
svg.setAttribute('class', 'onboarding-social-icon');
|
|
3363
|
+
svg.setAttribute('width', '20');
|
|
3364
|
+
svg.setAttribute('height', '20');
|
|
3365
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
3366
|
+
svg.setAttribute('fill', 'none');
|
|
3367
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
3368
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
3369
|
+
path.setAttribute('d', 'M20.317 4.37a16.75 16.75 0 0 0-4.091-1.272.06.06 0 0 0-.063.03 11.651 11.651 0 0 0-.512 1.054 15.93 15.93 0 0 0-4.813 0 10.69 10.69 0 0 0-.522-1.054.06.06 0 0 0-.063-.03 16.742 16.742 0 0 0-4.092 1.272.055.055 0 0 0-.025.021C2.84 8.167 2.18 11.875 2.46 15.547a.066.066 0 0 0 .025.045 16.86 16.86 0 0 0 4.98 2.52.06.06 0 0 0 .066-.02c.384-.525.726-1.08 1.02-1.66a.06.06 0 0 0-.033-.083 11.117 11.117 0 0 1-1.593-.765.06.06 0 0 1-.006-.1c.107-.08.214-.162.316-.245a.06.06 0 0 1 .061-.008 11.65 11.65 0 0 0 10.104 0 .06.06 0 0 1 .062.007c.103.083.21.166.316.246a.06.06 0 0 1-.005.1 10.859 10.859 0 0 1-1.594.764.06.06 0 0 0-.033.084 9.255 9.255 0 0 0 1.02 1.659.06.06 0 0 0 .065.02 16.824 16.824 0 0 0 4.982-2.52.06.06 0 0 0 .024-.044c.416-5.09-.698-8.76-2.89-11.156a.048.048 0 0 0-.024-.02ZM9.68 13.348c-.996 0-1.812-.91-1.812-2.03 0-1.12.805-2.031 1.812-2.031 1.016 0 1.822.922 1.812 2.031 0 1.12-.805 2.03-1.812 2.03Zm4.64 0c-.996 0-1.812-.91-1.812-2.03 0-1.12.805-2.031 1.812-2.031 1.016 0 1.822.922 1.812 2.031 0 1.12-.796 2.03-1.812 2.03Z');
|
|
3370
|
+
path.setAttribute('fill', 'currentColor');
|
|
3371
|
+
svg.appendChild(path);
|
|
3372
|
+
return svg;
|
|
3373
|
+
}
|
|
3374
|
+
/**
|
|
3375
|
+
* Handle passkey login
|
|
3376
|
+
*/
|
|
3377
|
+
async handlePasskeyLogin() {
|
|
3378
|
+
if (this.loading)
|
|
3379
|
+
return;
|
|
3380
|
+
try {
|
|
3381
|
+
this.setLoading(true, 'passkey');
|
|
3382
|
+
this.hidePasskeyError();
|
|
3383
|
+
if (this.config.onPasskeyLogin) {
|
|
3384
|
+
await this.config.onPasskeyLogin();
|
|
3385
|
+
// Call onLoginSuccess to close modal and update state
|
|
3386
|
+
if (this.config.onLoginSuccess) {
|
|
3387
|
+
this.config.onLoginSuccess({ user: null });
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
else {
|
|
3391
|
+
throw new Error('Passkey login handler not configured');
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
catch (error) {
|
|
3395
|
+
this.showPasskeyError(error instanceof Error ? error.message : 'Failed to login with passkey');
|
|
3396
|
+
if (this.config.onLoginError) {
|
|
3397
|
+
this.config.onLoginError(error instanceof Error ? error : new Error('Failed to login with passkey'));
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
finally {
|
|
3401
|
+
this.setLoading(false);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Handle passkey signup
|
|
3406
|
+
*/
|
|
3407
|
+
async handlePasskeySignup() {
|
|
3408
|
+
if (this.loading)
|
|
3409
|
+
return;
|
|
3410
|
+
try {
|
|
3411
|
+
this.setLoading(true, 'passkey');
|
|
3412
|
+
this.hidePasskeyError();
|
|
3413
|
+
if (this.config.onPasskeySignup) {
|
|
3414
|
+
await this.config.onPasskeySignup();
|
|
3415
|
+
// Call onLoginSuccess to close modal and update state
|
|
3416
|
+
if (this.config.onLoginSuccess) {
|
|
3417
|
+
this.config.onLoginSuccess({ user: null });
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
else {
|
|
3421
|
+
throw new Error('Passkey signup handler not configured');
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
catch (error) {
|
|
3425
|
+
this.showPasskeyError(error instanceof Error ? error.message : 'Failed to signup with passkey');
|
|
3426
|
+
if (this.config.onLoginError) {
|
|
3427
|
+
this.config.onLoginError(error instanceof Error ? error : new Error('Failed to signup with passkey'));
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
finally {
|
|
3431
|
+
this.setLoading(false);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* Show passkey-specific error near the passkey controls
|
|
3436
|
+
*/
|
|
3437
|
+
showPasskeyError(message) {
|
|
3438
|
+
if (!this.passkeyErrorElement)
|
|
3439
|
+
return;
|
|
3440
|
+
this.passkeyErrorElement.textContent = message;
|
|
3441
|
+
this.passkeyErrorElement.style.display = 'block';
|
|
3442
|
+
}
|
|
3443
|
+
/**
|
|
3444
|
+
* Hide passkey-specific error
|
|
3445
|
+
*/
|
|
3446
|
+
hidePasskeyError() {
|
|
3447
|
+
if (!this.passkeyErrorElement)
|
|
3448
|
+
return;
|
|
3449
|
+
this.passkeyErrorElement.textContent = '';
|
|
3450
|
+
this.passkeyErrorElement.style.display = 'none';
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
export default OnboardingUIWeb;
|
|
3454
|
+
//# sourceMappingURL=OnboardingUIWeb.js.map
|