From ec9f05fbb85f0e87d6bbcff8dd34ddff99a63ba1 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Sun, 29 Mar 2026 20:08:16 -0400 Subject: [PATCH 1/7] chore: remove loading Spinner --- package-lock.json | 6 +++--- src/AuthProvider.tsx | 12 +++--------- src/components/LoadingSpinner.tsx | 14 -------------- tests/authProvider.test.tsx | 12 ------------ tests/loadingSpinner.test.tsx | 18 ------------------ 5 files changed, 6 insertions(+), 56 deletions(-) delete mode 100644 src/components/LoadingSpinner.tsx delete mode 100644 tests/loadingSpinner.test.tsx diff --git a/package-lock.json b/package-lock.json index 9289441..dd71e5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5784,9 +5784,9 @@ } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, diff --git a/src/AuthProvider.tsx b/src/AuthProvider.tsx index 651bce6..53fac7b 100644 --- a/src/AuthProvider.tsx +++ b/src/AuthProvider.tsx @@ -9,7 +9,6 @@ import React, { } from 'react'; import { AuthMode, createFetchWithAuth } from './fetchWithAuth'; -import LoadingSpinner from './components/LoadingSpinner'; import { usePreviousSignIn } from './hooks/usePreviousSignIn'; import { AuthenticatorTransportFuture, @@ -42,6 +41,7 @@ export interface AuthContextType { credentials: Credential[]; updateCredential: (credential: Credential) => Promise; deleteCredential: (credentialId: string) => Promise; + loading: boolean; } export interface Credential { @@ -136,6 +136,7 @@ export const AuthProvider: React.FC = ({ const hasRole = (role: string) => user?.roles?.includes(role); const validateToken = async () => { + setLoading(true); try { const response = await fetchWithAuth(`users/me`, { method: 'GET', @@ -197,19 +198,12 @@ export const AuthProvider: React.FC = ({ } }, [user, isAuthenticated, markSignedIn]); - if (loading) { - return ( -
- ; -
- ); - } - return ( { - return ( -
-
-
- ); -}; - -export default LoadingSpinner; diff --git a/tests/authProvider.test.tsx b/tests/authProvider.test.tsx index 3285abf..79f9110 100644 --- a/tests/authProvider.test.tsx +++ b/tests/authProvider.test.tsx @@ -3,7 +3,6 @@ import { AuthProvider, useAuth } from '../src/AuthProvider'; import { createFetchWithAuth } from '../src/fetchWithAuth'; jest.mock('../src/fetchWithAuth'); -jest.mock('../src/components/LoadingSpinner', () => () =>
Loading...
); jest.mock('@/context/InternalAuthContext', () => ({ InternalAuthProvider: ({ children }: any) =>
{children}
, })); @@ -31,17 +30,6 @@ describe('AuthProvider', () => { jest.clearAllMocks(); }); - it('renders loading spinner initially', async () => { - await act(async () => { - render( - -
Child
-
- ); - }); - expect(screen.getByText('Child')).toBeInTheDocument(); - }); - it('loads user and token successfully', async () => { mockFetchWithAuthImpl.mockResolvedValueOnce({ ok: true, diff --git a/tests/loadingSpinner.test.tsx b/tests/loadingSpinner.test.tsx deleted file mode 100644 index 0f06a67..0000000 --- a/tests/loadingSpinner.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import '@testing-library/jest-dom'; - -import { render, screen } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; - -import LoadingSpinner from '../src/components/LoadingSpinner'; - -describe('Login Component', () => { - it('renders the login form correctly', () => { - render( - - - - ); - - expect(screen.getByTestId('loading')).toBeInTheDocument(); - }); -}); From 55fe2b41bfe65079dc8a3e5594b827e466457435 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Tue, 31 Mar 2026 14:49:53 -0400 Subject: [PATCH 2/7] feat: add check for bootstrap tokens --- src/views/Login.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/views/Login.tsx b/src/views/Login.tsx index 489dad1..1f4a0d8 100644 --- a/src/views/Login.tsx +++ b/src/views/Login.tsx @@ -24,6 +24,7 @@ const Login: React.FC = () => { const [identifierError, setIdentifierError] = useState(''); const [passkeyAvailable, setPasskeyAvailable] = useState(false); const [showFallbackOptions, setShowFallbackOptions] = useState(false); + const [bootstrapToken, setBootstrapToken] = useState(null); const fetchWithAuth = createFetchWithAuth({ authMode, @@ -41,6 +42,14 @@ const Login: React.FC = () => { if (hasSignedInBefore) { setMode('login'); } + + const params = new URLSearchParams(window.location.search); + const token = params.get('bootstrapToken'); + + if (token && token.length > 10) { + setBootstrapToken(token); + console.log('Bootstrap token detected in URL'); + } }, [hasSignedInBefore]); const handleEmailChange = (e: React.ChangeEvent) => { @@ -134,7 +143,11 @@ const Login: React.FC = () => { try { const response = await fetchWithAuth(`/registration/register`, { method: 'POST', - body: JSON.stringify({ email, phone }), + body: JSON.stringify({ + email, + phone, + ...(bootstrapToken ? { bootstrapToken } : {}), + }), }); if (!response.ok) { From 1524637cbcaf9ee576741fb23ad4a69801d4a914 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Tue, 31 Mar 2026 20:09:11 -0400 Subject: [PATCH 3/7] feat: email otp input --- src/components/OtpInput.tsx | 18 ++++++++++--- src/views/EmailRegistration.tsx | 14 ++++------- tests/EmailRegistration.test.tsx | 25 ++++++++++++------- .../{OtpInuput.test.tsx => OtpInput.test.tsx} | 0 4 files changed, 36 insertions(+), 21 deletions(-) rename tests/{OtpInuput.test.tsx => OtpInput.test.tsx} (100%) diff --git a/src/components/OtpInput.tsx b/src/components/OtpInput.tsx index 038c41d..1d17591 100644 --- a/src/components/OtpInput.tsx +++ b/src/components/OtpInput.tsx @@ -4,16 +4,28 @@ import styles from '@/styles/otpInput.module.css'; interface Props { length?: number; value: string; + inputMode?: 'numeric' | 'text'; onChange: (value: string) => void; } -const OtpInput: React.FC = ({ length = 6, value, onChange }) => { +const OtpInput: React.FC = ({ + length = 6, + value, + inputMode = 'numeric', + onChange, +}) => { const inputs = useRef>([]); const values = value.split('').concat(Array(length).fill('')).slice(0, length); const handleChange = (index: number, char: string) => { - if (!/^\d?$/.test(char)) return; + if (inputMode === 'numeric') { + if (!/^\d?$/.test(char)) return; + } + + if (inputMode === 'text') { + if (!/[a-z]/i.test(char)) return; + } const newValue = value.substring(0, index) + char + value.substring(index + 1); @@ -48,7 +60,7 @@ const OtpInput: React.FC = ({ length = 6, value, onChange }) => { key={i} ref={el => (inputs.current[i] = el)} type="text" - inputMode="numeric" + inputMode={inputMode} maxLength={1} value={digit || ''} className={styles.otpInput} diff --git a/src/views/EmailRegistration.tsx b/src/views/EmailRegistration.tsx index ba04668..75a9acd 100644 --- a/src/views/EmailRegistration.tsx +++ b/src/views/EmailRegistration.tsx @@ -6,6 +6,7 @@ import styles from '@/styles/verifyOTP.module.css'; import { createFetchWithAuth } from '@/fetchWithAuth'; import { isPasskeySupported } from '@/utils'; import { useInternalAuth } from '@/context/InternalAuthContext'; +import OtpInput from '@/components/OtpInput'; const EmailRegistration: React.FC = () => { const navigate = useNavigate(); @@ -129,16 +130,11 @@ const EmailRegistration: React.FC = () => { — Code expires in {formatTime(emailTimeLeft)} - - setEmailOtp(e.target.value)} - className={styles.input} - required + onChange={setEmailOtp} + inputMode="text" />