1
+ import type { SignInResource } from '@clerk/types' ;
2
+
1
3
import * as l from '../../localizations' ;
2
4
import type { Clerk as ClerkType } from '../' ;
3
5
@@ -34,6 +36,7 @@ const AVAILABLE_COMPONENTS = [
34
36
'waitlist' ,
35
37
'pricingTable' ,
36
38
'oauthConsent' ,
39
+ 'signInObservable' ,
37
40
] as const ;
38
41
39
42
const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox' ;
@@ -93,6 +96,7 @@ const componentControls: Record<(typeof AVAILABLE_COMPONENTS)[number], Component
93
96
waitlist : buildComponentControls ( 'waitlist' ) ,
94
97
pricingTable : buildComponentControls ( 'pricingTable' ) ,
95
98
oauthConsent : buildComponentControls ( 'oauthConsent' ) ,
99
+ signInObservable : buildComponentControls ( 'signInObservable' ) ,
96
100
} ;
97
101
98
102
declare global {
@@ -257,6 +261,229 @@ function otherOptions() {
257
261
return { updateOtherOptions } ;
258
262
}
259
263
264
+ function mountSignInObservable ( element : HTMLDivElement ) {
265
+ assertClerkIsLoaded ( Clerk ) ;
266
+
267
+ // Create container for status display
268
+ const statusContainer = document . createElement ( 'div' ) ;
269
+ statusContainer . className = 'p-4 border border-gray-200 rounded-md mb-4' ;
270
+ element . appendChild ( statusContainer ) ;
271
+
272
+ // Create controls container
273
+ const controlsContainer = document . createElement ( 'div' ) ;
274
+ controlsContainer . style . marginBottom = '1rem' ;
275
+ controlsContainer . style . display = 'flex' ;
276
+ controlsContainer . style . flexDirection = 'column' ;
277
+
278
+ // Create store state display
279
+ const storeStateDisplay = document . createElement ( 'div' ) ;
280
+ storeStateDisplay . className = 'p-2 bg-gray-50 rounded text-sm font-mono' ;
281
+
282
+ // Append store state display to controlsContainer
283
+ controlsContainer . appendChild ( storeStateDisplay ) ;
284
+
285
+ element . appendChild ( controlsContainer ) ;
286
+
287
+ // Create sign in form
288
+ const form = document . createElement ( 'form' ) ;
289
+ form . className = 'space-y-4' ;
290
+
291
+ const emailInput = document . createElement ( 'input' ) ;
292
+ emailInput . type = 'email' ;
293
+ emailInput . placeholder = 'Email' ;
294
+ emailInput . className = 'w-full p-2 border rounded' ;
295
+
296
+ const passwordInput = document . createElement ( 'input' ) ;
297
+ passwordInput . type = 'password' ;
298
+ passwordInput . placeholder = 'Password' ;
299
+ passwordInput . className = 'w-full p-2 border rounded' ;
300
+
301
+ const submitButton = document . createElement ( 'button' ) ;
302
+ submitButton . type = 'submit' ;
303
+ submitButton . textContent = 'Sign In' ;
304
+ submitButton . className = 'w-full p-2 bg-blue-500 text-white rounded' ;
305
+
306
+ form . appendChild ( emailInput ) ;
307
+ form . appendChild ( passwordInput ) ;
308
+ form . appendChild ( submitButton ) ;
309
+ element . appendChild ( form ) ;
310
+
311
+ let signIn : SignInResource ;
312
+
313
+ let isInitialized = false ;
314
+
315
+ // Create updateStatus function in the outer scope
316
+ const updateStatus = ( ) => {
317
+ if ( ! signIn ) {
318
+ console . error ( 'SignIn object is not initialized' ) ;
319
+ return ;
320
+ }
321
+ const fetchStatus = signIn . fetchStatus ;
322
+ const error = signIn . signInError . global ;
323
+ const status = signIn . status ;
324
+
325
+ // Update status container with animation
326
+ statusContainer . innerHTML = `
327
+ <div class="space-y-2 transition-all duration-300">
328
+ <div class="flex items-center gap-2">
329
+ <strong>Fetch Status:</strong>
330
+ <span class="px-2 py-0.5 rounded text-sm ${
331
+ fetchStatus === 'fetching'
332
+ ? 'bg-blue-100 text-blue-700'
333
+ : fetchStatus === 'error'
334
+ ? 'bg-red-100 text-red-700'
335
+ : 'bg-green-100 text-green-700'
336
+ } ">${ fetchStatus } </span>
337
+ </div>
338
+ <div class="flex items-center gap-2">
339
+ <strong>Sign In Status:</strong>
340
+ <span class="px-2 py-0.5 rounded text-sm ${
341
+ status === 'needs_first_factor'
342
+ ? 'bg-yellow-100 text-yellow-700'
343
+ : status === 'complete'
344
+ ? 'bg-green-100 text-green-700'
345
+ : 'bg-gray-100 text-gray-700'
346
+ } ">${ status || 'null' } </span>
347
+ </div>
348
+ ${ error ? `<div class="text-red-500"><strong>Error:</strong> ${ error } </div>` : '' }
349
+ </div>
350
+ ` ;
351
+
352
+ // Update store state display
353
+ storeStateDisplay . innerHTML = `
354
+ <div class="space-y-1">
355
+ <div>Store State:</div>
356
+ <pre class="whitespace-pre-wrap">${ JSON . stringify (
357
+ {
358
+ fetchStatus,
359
+ status,
360
+ error : error || null ,
361
+ } ,
362
+ null ,
363
+ 2 ,
364
+ ) } </pre>
365
+ </div>
366
+ ` ;
367
+ } ;
368
+
369
+ // Initialize SignIn instance
370
+ const initializeSignIn = async ( ) => {
371
+ try {
372
+ // Show loading state
373
+ statusContainer . innerHTML = `
374
+ <div class="text-blue-500">
375
+ <strong>Status:</strong> Initializing...
376
+ </div>
377
+ ` ;
378
+
379
+ // Wait for Clerk to be loaded and client to be ready
380
+ const waitForClerk = async ( ) => {
381
+ if ( ! Clerk . loaded ) {
382
+ await new Promise < void > ( resolve => {
383
+ const checkLoaded = ( ) => {
384
+ if ( Clerk . loaded ) {
385
+ resolve ( ) ;
386
+ } else {
387
+ setTimeout ( checkLoaded , 100 ) ;
388
+ }
389
+ } ;
390
+ checkLoaded ( ) ;
391
+ } ) ;
392
+ }
393
+
394
+ // Wait for client to be ready
395
+ if ( ! Clerk . client ) {
396
+ await new Promise < void > ( resolve => {
397
+ const checkClient = ( ) => {
398
+ if ( Clerk . client ) {
399
+ resolve ( ) ;
400
+ } else {
401
+ setTimeout ( checkClient , 100 ) ;
402
+ }
403
+ } ;
404
+ checkClient ( ) ;
405
+ } ) ;
406
+ }
407
+ } ;
408
+
409
+ await waitForClerk ( ) ;
410
+
411
+ // Initial update
412
+ updateStatus ( ) ;
413
+
414
+ // Update status to show initialization complete
415
+ statusContainer . innerHTML = `
416
+ <div class="text-green-500">
417
+ <strong>Status:</strong> Ready to sign in
418
+ </div>
419
+ ` ;
420
+ } catch ( error ) {
421
+ console . error ( 'Failed to initialize:' , error ) ;
422
+ statusContainer . innerHTML = `
423
+ <div class="text-red-500">
424
+ <strong>Error:</strong> ${ error instanceof Error ? error . message : 'Failed to initialize' }
425
+ </div>
426
+ ` ;
427
+ isInitialized = false ;
428
+ }
429
+ } ;
430
+
431
+ // Handle form submission
432
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
433
+ form . addEventListener ( 'submit' , async e => {
434
+ e . preventDefault ( ) ;
435
+
436
+ try {
437
+ if ( ! isInitialized || ! Clerk . client ) {
438
+ throw new Error ( 'System not initialized. Please wait...' ) ;
439
+ }
440
+
441
+ // Show loading state
442
+ statusContainer . innerHTML = `
443
+ <div class="text-blue-500">
444
+ <strong>Status:</strong> Processing sign in...
445
+ </div>
446
+ ` ;
447
+
448
+ // Create SignIn instance with the provided email
449
+ signIn = await Clerk . client . signIn . create ( {
450
+ identifier : emailInput . value ,
451
+ strategy : 'email_code' ,
452
+ } ) ;
453
+
454
+ if ( ! signIn ) {
455
+ throw new Error ( 'Failed to create SignIn instance' ) ;
456
+ }
457
+
458
+ // Initial update using getters
459
+ updateStatus ( ) ;
460
+
461
+ await signIn . prepareFirstFactor ( {
462
+ strategy : 'email_code' ,
463
+ emailAddressId : emailInput . value ,
464
+ } ) ;
465
+
466
+ await signIn . attemptFirstFactor ( {
467
+ strategy : 'email_code' ,
468
+ code : passwordInput . value ,
469
+ } ) ;
470
+
471
+ // Update status after sign-in attempt
472
+ updateStatus ( ) ;
473
+ } catch ( error ) {
474
+ console . error ( 'Sign in error:' , error ) ;
475
+ statusContainer . innerHTML = `
476
+ <div class="text-red-500">
477
+ <strong>Error:</strong> ${ error instanceof Error ? error . message : 'An error occurred' }
478
+ </div>
479
+ ` ;
480
+ }
481
+ } ) ;
482
+
483
+ // Initialize on mount
484
+ void initializeSignIn ( ) ;
485
+ }
486
+
260
487
void ( async ( ) => {
261
488
assertClerkIsLoaded ( Clerk ) ;
262
489
fillLocalizationSelect ( ) ;
@@ -333,6 +560,22 @@ void (async () => {
333
560
'/open-sign-up' : ( ) => {
334
561
mountOpenSignUpButton ( app , componentControls . signUp . getProps ( ) ?? { } ) ;
335
562
} ,
563
+ '/sign-in-observable' : async ( ) => {
564
+ // Wait for Clerk to be fully loaded before mounting the component
565
+ if ( ! Clerk . loaded ) {
566
+ await new Promise < void > ( resolve => {
567
+ const checkLoaded = ( ) => {
568
+ if ( Clerk . loaded ) {
569
+ resolve ( ) ;
570
+ } else {
571
+ setTimeout ( checkLoaded , 100 ) ;
572
+ }
573
+ } ;
574
+ checkLoaded ( ) ;
575
+ } ) ;
576
+ }
577
+ mountSignInObservable ( app ) ;
578
+ } ,
336
579
} ;
337
580
338
581
const route = window . location . pathname as keyof typeof routes ;
@@ -344,7 +587,7 @@ void (async () => {
344
587
signInUrl : '/sign-in' ,
345
588
signUpUrl : '/sign-up' ,
346
589
} ) ;
347
- renderCurrentRoute ( ) ;
590
+ await renderCurrentRoute ( ) ;
348
591
updateVariables ( ) ;
349
592
updateOtherOptions ( ) ;
350
593
} else {
0 commit comments