diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index a446de1a2ba..d5fc38c0d51 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,9 +1,9 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "605kB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "69.2KB" }, - { "path": "./dist/clerk.legacy.browser.js", "maxSize": "113KB" }, - { "path": "./dist/clerk.headless*.js", "maxSize": "53KB" }, + { "path": "./dist/clerk.js", "maxSize": "608kB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "73KB" }, + { "path": "./dist/clerk.legacy.browser.js", "maxSize": "114KB" }, + { "path": "./dist/clerk.headless*.js", "maxSize": "55KB" }, { "path": "./dist/ui-common*.js", "maxSize": "106.3KB" }, { "path": "./dist/vendors*.js", "maxSize": "40.2KB" }, { "path": "./dist/coinbase*.js", "maxSize": "38KB" }, diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 36c01bbcb9c..fd78b4e4f64 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -78,7 +78,8 @@ "dequal": "2.0.3", "qrcode.react": "4.2.0", "regenerator-runtime": "0.14.1", - "swr": "2.3.3" + "swr": "2.3.3", + "zustand": "5.0.5" }, "devDependencies": { "@emotion/jest": "^11.13.0", diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 707b52b1674..e4c34e3f416 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -1,3 +1,5 @@ +import type { SignInResource } from '@clerk/types'; + import * as l from '../../localizations'; import type { Clerk as ClerkType } from '../'; @@ -34,6 +36,7 @@ const AVAILABLE_COMPONENTS = [ 'waitlist', 'pricingTable', 'oauthConsent', + 'signInObservable', ] as const; const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox'; @@ -93,6 +96,7 @@ const componentControls: Record<(typeof AVAILABLE_COMPONENTS)[number], Component waitlist: buildComponentControls('waitlist'), pricingTable: buildComponentControls('pricingTable'), oauthConsent: buildComponentControls('oauthConsent'), + signInObservable: buildComponentControls('signInObservable'), }; declare global { @@ -257,6 +261,844 @@ function otherOptions() { return { updateOtherOptions }; } +function mountSignInObservable(element: HTMLDivElement) { + assertClerkIsLoaded(Clerk); + + // Add global error handler to catch store-related errors + const originalError = console.error; + console.error = function (...args) { + if (args.some(arg => typeof arg === 'string' && arg.includes('dispatch is not a function'))) { + console.log('=== Store Dispatch Error Detected ==='); + console.log('Arguments:', args); + console.log('Current signIn:', signIn); + if (signIn?.store) { + console.log('SignIn store state:', signIn.store.getState()); + } + } + originalError.apply(console, args); + }; + + // Create main container + const mainContainer = document.createElement('div'); + mainContainer.className = 'space-y-6'; + element.appendChild(mainContainer); + + // Create title + const title = document.createElement('h2'); + title.textContent = 'SignIn Observable Store Demo'; + title.className = 'text-2xl font-bold mb-4'; + mainContainer.appendChild(title); + + // Create container for status display + const statusContainer = document.createElement('div'); + statusContainer.className = 'p-4 border border-gray-200 rounded-md mb-4'; + mainContainer.appendChild(statusContainer); + + // 1. SIGN IN FORM (First) + const form = document.createElement('form'); + form.className = 'space-y-4 p-4 border rounded-lg bg-blue-50'; + + const formTitle = document.createElement('h3'); + formTitle.textContent = '1. Test Sign In Flow'; + formTitle.className = 'font-semibold mb-2 text-blue-800'; + form.appendChild(formTitle); + + const formDescription = document.createElement('p'); + formDescription.textContent = 'Create a SignIn instance and observe store state changes in real-time'; + formDescription.className = 'text-sm text-blue-600 mb-3'; + form.appendChild(formDescription); + + const emailInput = document.createElement('input'); + emailInput.type = 'email'; + emailInput.placeholder = 'Email (must exist in your Clerk app)'; + emailInput.value = ''; + emailInput.className = 'w-full p-2 border rounded'; + + const passwordInput = document.createElement('input'); + passwordInput.type = 'password'; + passwordInput.placeholder = 'Password or Code'; + passwordInput.value = '123456'; + passwordInput.className = 'w-full p-2 border rounded'; + + const submitButton = document.createElement('button'); + submitButton.type = 'submit'; + submitButton.textContent = 'Create SignIn & Observe Store'; + submitButton.className = 'w-full p-2 bg-blue-500 text-white rounded hover:bg-blue-600'; + + form.appendChild(emailInput); + form.appendChild(passwordInput); + form.appendChild(submitButton); + mainContainer.appendChild(form); + + // 2. SIMULATE BUTTONS (Second) + const buttonsContainer = document.createElement('div'); + buttonsContainer.className = 'space-y-3 p-4 bg-gray-50 rounded-lg'; + + const buttonsTitle = document.createElement('h3'); + buttonsTitle.textContent = '2. Store State Simulation Controls'; + buttonsTitle.className = 'font-semibold mb-2 text-gray-800'; + buttonsContainer.appendChild(buttonsTitle); + + const buttonsDescription = document.createElement('p'); + buttonsDescription.textContent = 'Manually trigger store actions to observe state changes'; + buttonsDescription.className = 'text-sm text-gray-600 mb-3'; + buttonsContainer.appendChild(buttonsDescription); + + const buttonsRow = document.createElement('div'); + buttonsRow.className = 'flex flex-wrap gap-2'; + + const createTestButton = (text: string, onClick: () => void, colorClass = 'bg-gray-500 hover:bg-gray-600') => { + const button = document.createElement('button'); + button.textContent = text; + button.className = `px-3 py-1 ${colorClass} text-white rounded text-sm`; + button.onclick = onClick; + return button; + }; + + buttonsContainer.appendChild(buttonsRow); + mainContainer.appendChild(buttonsContainer); + + // 3. STORE SLICES SECTIONS (Third) + const combinedStoreDisplay = document.createElement('div'); + combinedStoreDisplay.className = 'space-y-4'; + + const storeTitle = document.createElement('h3'); + storeTitle.textContent = '3. Live Store State Inspection'; + storeTitle.className = 'font-semibold mb-2 text-purple-800'; + combinedStoreDisplay.appendChild(storeTitle); + + const storeDescription = document.createElement('p'); + storeDescription.textContent = 'Real-time view of the observable store structure and state changes'; + storeDescription.className = 'text-sm text-purple-600 mb-4'; + combinedStoreDisplay.appendChild(storeDescription); + + // Combined store display section + const combinedStoreSection = document.createElement('div'); + combinedStoreSection.className = 'p-4 bg-purple-50 rounded-lg'; + combinedStoreSection.innerHTML = + '
Resource Slice: Generic, inherited from BaseResource
+store.getState().resource.{ status, data, error, dispatch }
+Handles: API calls, loading states, error handling
+SignIn Slice: Domain-specific, added by SignInResource
+store.getState().signin.{ status, setStatus }
+Handles: SignIn flow logic, authentication steps
+🌐 Framework-Agnostic Reactive Integration
+The .store
property is a vanilla JS Zustand store that any framework can integrate with:
const status = useStore(signIn.store, (state) => state.resource.status)
const status = computed(() => signIn.store.getState().resource.status)
$: status = $signInStore.resource.status
signIn.store.subscribe(state => this.status = state.resource.status)
🔄 Non-Breaking Progressive Enhancement
+New .store
property enables gradual adoption without version coupling:
if (signIn.store) { /* use reactive features */ }
🔍 Internal Architecture Observability
+Observable store powers resource internals regardless of UI usage:
+${JSON.stringify( + Object.fromEntries( + Object.entries(fullStoreState).map(([key, value]) => [ + key, + typeof value === 'function' ? `[Function: ${key}]` : value, + ]), + ), + null, + 2, + )}+
${JSON.stringify(resourceSliceProps, null, 2)}+
${JSON.stringify(signInSliceProps, null, 2)}+