From 03d0878f7158c27de942f2f7469cfbdaaf883c75 Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 16:35:19 -0600 Subject: [PATCH 1/6] improve auth accessibility and consistency --- .../web/src/components/auth/AuthButtons.jsx | 10 +++- .../web/src/components/auth/AuthLayout.jsx | 4 +- .../web/src/components/auth/CheckEmail.jsx | 7 ++- .../src/components/auth/CompleteProfile.jsx | 14 +++-- .../web/src/components/auth/ErrorMessage.jsx | 14 ++++- .../web/src/components/auth/MagicLinkForm.jsx | 5 +- .../src/components/auth/ProtectedGuard.jsx | 6 +- .../web/src/components/auth/ResetPassword.jsx | 56 +++++++++++-------- .../web/src/components/auth/RoleSelector.jsx | 10 +++- packages/web/src/components/auth/SignIn.jsx | 11 +++- packages/web/src/components/auth/SignUp.jsx | 2 +- .../src/components/auth/SocialAuthButtons.jsx | 38 ++++++++++--- .../src/components/auth/TwoFactorVerify.jsx | 3 +- 13 files changed, 125 insertions(+), 55 deletions(-) diff --git a/packages/web/src/components/auth/AuthButtons.jsx b/packages/web/src/components/auth/AuthButtons.jsx index 6b950739d..82def9475 100644 --- a/packages/web/src/components/auth/AuthButtons.jsx +++ b/packages/web/src/components/auth/AuthButtons.jsx @@ -1,5 +1,4 @@ import { AnimatedShow } from '../AnimatedShow.jsx'; -import { AiOutlineLoading3Quarters } from 'solid-icons/ai'; /** * Primary button for auth forms @@ -18,11 +17,16 @@ export function PrimaryButton(props) { class='flex w-full items-center justify-center rounded-lg bg-blue-600 py-2 text-sm font-bold text-white shadow transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50 sm:rounded-xl sm:py-3 sm:text-base' disabled={props.disabled || props.loading} onClick={() => props.onClick?.()} + aria-busy={props.loading} >
- - {props.loadingText || 'Loading...'} +
+ {props.loadingText || 'Loading...'}
diff --git a/packages/web/src/components/auth/AuthLayout.jsx b/packages/web/src/components/auth/AuthLayout.jsx index 1c630ea4e..5d177d624 100644 --- a/packages/web/src/components/auth/AuthLayout.jsx +++ b/packages/web/src/components/auth/AuthLayout.jsx @@ -60,9 +60,7 @@ export default function AuthLayout(props) {
- -
{props.children}
-
+ {props.children} ); } diff --git a/packages/web/src/components/auth/CheckEmail.jsx b/packages/web/src/components/auth/CheckEmail.jsx index 3588ac873..a4f3c8a2a 100644 --- a/packages/web/src/components/auth/CheckEmail.jsx +++ b/packages/web/src/components/auth/CheckEmail.jsx @@ -131,7 +131,12 @@ export default function CheckEmail() { {loading() ? <>
- +

Email Verified!

Redirecting you to the dashboard...

diff --git a/packages/web/src/components/auth/CompleteProfile.jsx b/packages/web/src/components/auth/CompleteProfile.jsx index ba1d5da11..01340c8f4 100644 --- a/packages/web/src/components/auth/CompleteProfile.jsx +++ b/packages/web/src/components/auth/CompleteProfile.jsx @@ -270,7 +270,11 @@ export default function CompleteProfile() { +
} >
@@ -341,6 +345,7 @@ export default function CompleteProfile() { required id='first-name-input' placeholder='First' + aria-describedby={displayError() ? 'profile-step1-error' : undefined} />
@@ -364,6 +369,7 @@ export default function CompleteProfile() { required id='last-name-input' placeholder='Last' + aria-describedby={displayError() ? 'profile-step1-error' : undefined} />
@@ -409,7 +415,7 @@ export default function CompleteProfile() {
- + Next @@ -466,7 +472,7 @@ export default function CompleteProfile() { /> - +
- +
-
+ +
{props.displayError()}
diff --git a/packages/web/src/components/auth/MagicLinkForm.jsx b/packages/web/src/components/auth/MagicLinkForm.jsx index 78916868c..ea086fb5b 100644 --- a/packages/web/src/components/auth/MagicLinkForm.jsx +++ b/packages/web/src/components/auth/MagicLinkForm.jsx @@ -100,7 +100,7 @@ export default function MagicLinkForm(props) { Click the link in the email to sign in. The link expires in 10 minutes.

- +
@@ -159,10 +159,11 @@ export default function MagicLinkForm(props) { id='magic-link-email' placeholder='you@example.com' disabled={loading()} + aria-describedby={displayError() ? 'magic-link-error' : undefined} />
- + {props.buttonText || 'Send Sign-In Link'} diff --git a/packages/web/src/components/auth/ProtectedGuard.jsx b/packages/web/src/components/auth/ProtectedGuard.jsx index 385ac58a7..d8f8f74bf 100644 --- a/packages/web/src/components/auth/ProtectedGuard.jsx +++ b/packages/web/src/components/auth/ProtectedGuard.jsx @@ -5,16 +5,16 @@ import { PageLoader } from '@/components/ui/spinner'; /** * ProtectedGuard - For authenticated pages (profile, settings, admin, etc.) - * Redirects guests to dashboard + * Redirects guests to signin */ export default function ProtectedGuard(props) { const { isLoggedIn, authLoading } = useBetterAuth(); const navigate = useNavigate(); - // Redirect non-logged-in users to dashboard + // Redirect non-logged-in users to signin createEffect(() => { if (!authLoading() && !isLoggedIn()) { - navigate('/dashboard', { replace: true }); + navigate('/signin', { replace: true }); } }); diff --git a/packages/web/src/components/auth/ResetPassword.jsx b/packages/web/src/components/auth/ResetPassword.jsx index 9325c2f42..cb9ed751e 100644 --- a/packages/web/src/components/auth/ResetPassword.jsx +++ b/packages/web/src/components/auth/ResetPassword.jsx @@ -5,6 +5,12 @@ import { AnimatedShow } from '../AnimatedShow.jsx'; import ErrorMessage from './ErrorMessage.jsx'; import { PrimaryButton, AuthLink } from './AuthButtons.jsx'; import StrengthIndicator from './StrengthIndicator.jsx'; +import { + PasswordInput, + PasswordInputControl, + PasswordInputField, + PasswordInputVisibilityTrigger, +} from '@/components/ui/password-input'; import { handleError } from '@/lib/error-utils.js'; const REDIRECT_DELAY_MS = 3000; @@ -203,21 +209,22 @@ function SetNewPasswordForm(props) {
- setPassword(e.target.value)} - class='w-full rounded-lg border border-gray-300 py-2 pr-3 pl-3 text-xs transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none sm:pr-4 sm:pl-4 sm:text-sm' - required - id='password-input' - placeholder='Enter new password' - disabled={loading()} - /> + + + setPassword(e.target.value)} + placeholder='Enter new password' + aria-describedby={displayError() ? 'reset-password-error' : undefined} + /> + + +
@@ -228,20 +235,21 @@ function SetNewPasswordForm(props) { > Confirm Password - setConfirmPassword(e.target.value)} - class='w-full rounded-lg border border-gray-300 py-2 pr-3 pl-3 text-xs transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none sm:pr-4 sm:pl-4 sm:text-sm' - required - id='confirm-password-input' - placeholder='Confirm new password' - disabled={loading()} - /> + + + setConfirmPassword(e.target.value)} + placeholder='Confirm new password' + aria-describedby={displayError() ? 'reset-password-error' : undefined} + /> + + +
- + Set Password diff --git a/packages/web/src/components/auth/RoleSelector.jsx b/packages/web/src/components/auth/RoleSelector.jsx index 1de134d92..eb331be4e 100644 --- a/packages/web/src/components/auth/RoleSelector.jsx +++ b/packages/web/src/components/auth/RoleSelector.jsx @@ -46,25 +46,31 @@ export function getRoleLabel(roleId) { /** * Role selection grid for sign up flow + * Uses radiogroup pattern for single-select accessibility * @param {Object} props * @param {string} props.selectedRole - Currently selected role ID * @param {Function} props.onSelect - Callback when role is selected */ export default function RoleSelector(props) { return ( -
+
{roleOption => ( diff --git a/packages/web/src/components/auth/SignIn.jsx b/packages/web/src/components/auth/SignIn.jsx index 20582475b..1b9ad7c71 100644 --- a/packages/web/src/components/auth/SignIn.jsx +++ b/packages/web/src/components/auth/SignIn.jsx @@ -229,23 +229,32 @@ export default function SignIn() { id='email-input' placeholder='you@example.com' disabled={loading()} + aria-describedby={displayError() ? 'signin-error' : undefined} />
+ setPassword(e.target.value)} placeholder='Password' + aria-describedby={displayError() ? 'signin-error' : undefined} />
- + Sign In diff --git a/packages/web/src/components/auth/SignUp.jsx b/packages/web/src/components/auth/SignUp.jsx index 851dd5c94..ef95c36c6 100644 --- a/packages/web/src/components/auth/SignUp.jsx +++ b/packages/web/src/components/auth/SignUp.jsx @@ -138,7 +138,7 @@ export default function SignUp() { - + {/* Magic Link Form - simple email signup */} } + fallback={} > -
+
Continue with Google @@ -37,12 +41,19 @@ export function GoogleButton(props) { disabled={props.loading} class={`${baseClass} p-3 sm:p-3.5`} title='Continue with Google' + aria-label='Continue with Google' > } + fallback={ + + } > -
+
@@ -72,9 +83,13 @@ export function OrcidButton(props) { > } + fallback={} > -
+
Continue with ORCID @@ -86,12 +101,19 @@ export function OrcidButton(props) { disabled={props.loading} class={`${baseClass} p-3 sm:p-3.5`} title='Continue with ORCID' + aria-label='Continue with ORCID' > } + fallback={ + + } > -
+
diff --git a/packages/web/src/components/auth/TwoFactorVerify.jsx b/packages/web/src/components/auth/TwoFactorVerify.jsx index 3cded284a..a61c67e48 100644 --- a/packages/web/src/components/auth/TwoFactorVerify.jsx +++ b/packages/web/src/components/auth/TwoFactorVerify.jsx @@ -92,10 +92,11 @@ export default function TwoFactorVerify(props) { disabled={loading()} id='2fa-code' autoFocus + aria-describedby={displayError() ? '2fa-error' : undefined} />
- + Verify From 3d3e84309e332ad6ad231366d6bf04068beb5af0 Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 16:51:55 -0600 Subject: [PATCH 2/6] fix container styling --- .../web/src/components/auth/CheckEmail.jsx | 4 +-- .../src/components/auth/CompleteProfile.jsx | 28 +++++++++---------- .../web/src/components/auth/ResetPassword.jsx | 20 ++++++------- packages/web/src/components/auth/SignIn.jsx | 4 +-- packages/web/src/components/auth/SignUp.jsx | 4 +-- .../settings/pages/AcademicInfoSection.jsx | 10 +++---- .../settings/pages/PersonaSection.jsx | 6 ++-- .../settings/pages/ProfileSettings.jsx | 28 +++++++++---------- 8 files changed, 47 insertions(+), 57 deletions(-) diff --git a/packages/web/src/components/auth/CheckEmail.jsx b/packages/web/src/components/auth/CheckEmail.jsx index a4f3c8a2a..cca0cbee9 100644 --- a/packages/web/src/components/auth/CheckEmail.jsx +++ b/packages/web/src/components/auth/CheckEmail.jsx @@ -121,8 +121,7 @@ export default function CheckEmail() { }; return ( - ); } diff --git a/packages/web/src/components/auth/CompleteProfile.jsx b/packages/web/src/components/auth/CompleteProfile.jsx index 01340c8f4..cbfcdaa03 100644 --- a/packages/web/src/components/auth/CompleteProfile.jsx +++ b/packages/web/src/components/auth/CompleteProfile.jsx @@ -266,18 +266,17 @@ export default function CompleteProfile() { const displayError = () => error(); return ( - +
+
); } diff --git a/packages/web/src/components/auth/ResetPassword.jsx b/packages/web/src/components/auth/ResetPassword.jsx index cb9ed751e..821e13a60 100644 --- a/packages/web/src/components/auth/ResetPassword.jsx +++ b/packages/web/src/components/auth/ResetPassword.jsx @@ -20,17 +20,15 @@ export default function ResetPassword() { const token = () => searchParams.token; return ( -
- +
+ {/* Logo */} + + CoRATES + + + }> + +
); } diff --git a/packages/web/src/components/auth/SignIn.jsx b/packages/web/src/components/auth/SignIn.jsx index 1b9ad7c71..0cc74ab7e 100644 --- a/packages/web/src/components/auth/SignIn.jsx +++ b/packages/web/src/components/auth/SignIn.jsx @@ -149,8 +149,7 @@ export default function SignIn() { } return ( - ); } diff --git a/packages/web/src/components/auth/SignUp.jsx b/packages/web/src/components/auth/SignUp.jsx index ef95c36c6..1c318a651 100644 --- a/packages/web/src/components/auth/SignUp.jsx +++ b/packages/web/src/components/auth/SignUp.jsx @@ -108,8 +108,7 @@ export default function SignUp() { } return ( - ); } diff --git a/packages/web/src/components/settings/pages/AcademicInfoSection.jsx b/packages/web/src/components/settings/pages/AcademicInfoSection.jsx index 861fb6fad..5978684c4 100644 --- a/packages/web/src/components/settings/pages/AcademicInfoSection.jsx +++ b/packages/web/src/components/settings/pages/AcademicInfoSection.jsx @@ -100,7 +100,7 @@ export default function AcademicInfoSection() { }; return ( -
+
Title: - + {user()?.title || Not set}
Institution: - + {user()?.institution || Not set}
Department: - + {user()?.department || Not set}
diff --git a/packages/web/src/components/settings/pages/PersonaSection.jsx b/packages/web/src/components/settings/pages/PersonaSection.jsx index 3a9559b01..f01740483 100644 --- a/packages/web/src/components/settings/pages/PersonaSection.jsx +++ b/packages/web/src/components/settings/pages/PersonaSection.jsx @@ -70,7 +70,7 @@ export default function PersonaSection() { }; return ( -
+
+ {user()?.persona ? getRoleLabel(user()?.persona) : Not set} diff --git a/packages/web/src/components/settings/pages/ProfileSettings.jsx b/packages/web/src/components/settings/pages/ProfileSettings.jsx index 5f2384b4c..162fbbdca 100644 --- a/packages/web/src/components/settings/pages/ProfileSettings.jsx +++ b/packages/web/src/components/settings/pages/ProfileSettings.jsx @@ -11,23 +11,23 @@ import DeleteAccountSection from '@/components/settings/pages/DeleteAccountSecti export default function ProfileSettings() { return ( -
+
-

Profile

-

Manage your personal information

+

Profile

+

Manage your personal information

{/* Profile Information Card */} -
-
+
+
-
- +
+
-

Personal Information

-

+

Personal Information

+

Your profile information visible to project collaborators

@@ -41,13 +41,13 @@ export default function ProfileSettings() {
{/* Danger Zone Card */} -
-
+
+
-
- +
+
-

Danger Zone

+

Danger Zone

From 244e530dc11bb7e8654afb5b6b920ae47805be84 Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 17:08:04 -0600 Subject: [PATCH 3/6] add animations to sign in --- packages/web/src/components/auth/SignIn.jsx | 378 ++++++++++++-------- 1 file changed, 229 insertions(+), 149 deletions(-) diff --git a/packages/web/src/components/auth/SignIn.jsx b/packages/web/src/components/auth/SignIn.jsx index 0cc74ab7e..17c8e1bb1 100644 --- a/packages/web/src/components/auth/SignIn.jsx +++ b/packages/web/src/components/auth/SignIn.jsx @@ -1,4 +1,4 @@ -import { createSignal, onCleanup, onMount, Show } from 'solid-js'; +import { createEffect, createSignal, onCleanup, onMount, Show } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { useBetterAuth } from '@api/better-auth-store.js'; import { @@ -29,6 +29,9 @@ export default function SignIn() { const [orcidLoading, setOrcidLoading] = createSignal(false); const [useMagicLink, setUseMagicLink] = createSignal(false); const [showTwoFactor, setShowTwoFactor] = createSignal(false); + const [formHeight, setFormHeight] = createSignal('auto'); + let passwordFormRef; + let magicLinkFormRef; const navigate = useNavigate(); const { signin, signinWithGoogle, signinWithOrcid, authError, clearAuthError } = useBetterAuth(); @@ -63,6 +66,28 @@ export default function SignIn() { }); }); + // Update form height based on which form is active + const updateFormHeight = () => { + const activeRef = useMagicLink() ? magicLinkFormRef : passwordFormRef; + if (activeRef) { + setFormHeight(`${activeRef.offsetHeight}px`); + } + }; + + // Set initial height after mount when refs are ready + onMount(() => { + // Small delay to ensure refs are measured after initial render + requestAnimationFrame(updateFormHeight); + }); + + // Update height when switching forms + createEffect(() => { + // Track the signal + useMagicLink(); + // Update after a frame to ensure layout is complete + requestAnimationFrame(updateFormHeight); + }); + // Watch for auth errors from the store const displayError = () => error() || authError(); @@ -150,158 +175,213 @@ export default function SignIn() { return (
- {/* Logo */} - - CoRATES - - - {/* Two-Factor Verification */} - - - - - {/* Normal Sign In */} - -
-

+ CoRATES + + + {/* Two-Factor Verification */} + + + + + {/* Normal Sign In */} + +
+

+ Welcome Back +

+

Sign in to your account.

+
+ + + + {/* Toggle between password and magic link */} +
{ + if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { + e.preventDefault(); + const goToMagicLink = e.key === 'ArrowRight'; + setUseMagicLink(goToMagicLink); + document.getElementById(goToMagicLink ? 'tab-magic-link' : 'tab-password')?.focus(); + } + }} + > + {/* Sliding indicator */} + + + {/* Sliding form container */} +
+
+ {/* Password Form */} +
- Welcome Back -

-

Sign in to your account.

-
- - - - {/* Toggle between password and magic link */} -
- - -
- - {/* Magic Link Form */} - - - - - {/* Password Form */} - -
-
-
- - setEmail(e.target.value)} - class='w-full rounded-lg border border-gray-300 py-2 pr-3 pl-3 text-xs transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none sm:pr-4 sm:pl-4 sm:text-sm' - required - id='email-input' - placeholder='you@example.com' - disabled={loading()} - aria-describedby={displayError() ? 'signin-error' : undefined} - /> -
- -
- - - - setPassword(e.target.value)} - placeholder='Password' - aria-describedby={displayError() ? 'signin-error' : undefined} - /> - - - -
- - - - - Sign In - - -
- { - e.preventDefault(); - navigate('/reset-password'); - }} - > - Forgot password? - + +
+
+ + setEmail(e.target.value)} + class='w-full rounded-lg border border-gray-300 py-2 pr-3 pl-3 text-xs transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none sm:pr-4 sm:pl-4 sm:text-sm' + required + id='email-input' + placeholder='you@example.com' + disabled={loading()} + aria-describedby={displayError() ? 'signin-error' : undefined} + /> +
+ +
+ + + + setPassword(e.target.value)} + placeholder='Password' + aria-describedby={displayError() ? 'signin-error' : undefined} + /> + + + +
+ + + + + Sign In + + +
+ { + e.preventDefault(); + navigate('/reset-password'); + }} + > + Forgot password? + +
-
- - - - - - - 1} - /> - 1} - /> - - -
- Don't have an account?{' '} - { - e.preventDefault(); - navigate('/signup'); - }} + +
+ + {/* Magic Link Form */} +
-
+
+ + + + + 1} + /> + 1} + /> + + +
+ Don't have an account?{' '} + { + e.preventDefault(); + navigate('/signup'); + }} + > + Sign Up + +
+
); } From aad7833a3ae763d9fe1b89d9fbe9adc70c36a91d Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 18 Jan 2026 23:08:51 +0000 Subject: [PATCH 4/6] Apply Prettier formatting --- .../web/src/components/auth/CheckEmail.jsx | 118 ++--- .../src/components/auth/CompleteProfile.jsx | 470 +++++++++--------- packages/web/src/components/auth/SignUp.jsx | 138 +++-- 3 files changed, 357 insertions(+), 369 deletions(-) diff --git a/packages/web/src/components/auth/CheckEmail.jsx b/packages/web/src/components/auth/CheckEmail.jsx index cca0cbee9..91048549d 100644 --- a/packages/web/src/components/auth/CheckEmail.jsx +++ b/packages/web/src/components/auth/CheckEmail.jsx @@ -122,70 +122,70 @@ export default function CheckEmail() { return (
- {/* Logo */} - - CoRATES - - - {loading() ? - <> -
- -
-

Email Verified!

-

Redirecting you to the dashboard...

- - : <> -
-
- -
-
- -
-

Check Your Email

-

We've sent a verification email to:

-

{email()}

+ {/* Logo */} + + CoRATES + + + {loading() ? + <> +
+ +
+

Email Verified!

+

Redirecting you to the dashboard...

+ + : <> +
+
+
+
-
-

- Click the verification link in your email to activate your account. Once verified, - you'll automatically be redirected to the dashboard. -

+
+

Check Your Email

+

We've sent a verification email to:

+

{email()}

+
- +
+

+ Click the verification link in your email to activate your account. Once verified, + you'll automatically be redirected to the dashboard. +

- {resent() && ( -
- Verification email sent successfully! -
- )} -
+ -
- - Resend Email - - - Back to Sign In -
- -
-

Didn't receive the email? Check your spam folder or try resending.

-
- - } + {resent() && ( +
+ Verification email sent successfully! +
+ )} +
+ +
+ + Resend Email + + + Back to Sign In +
+ +
+

Didn't receive the email? Check your spam folder or try resending.

+
+ + }
); } diff --git a/packages/web/src/components/auth/CompleteProfile.jsx b/packages/web/src/components/auth/CompleteProfile.jsx index cbfcdaa03..5dcda09bd 100644 --- a/packages/web/src/components/auth/CompleteProfile.jsx +++ b/packages/web/src/components/auth/CompleteProfile.jsx @@ -277,279 +277,269 @@ export default function CompleteProfile() { } >
- {/* Logo */} - - CoRATES - - - - {/* Step Indicator */} - - - {(stepInfo, index) => ( - - currentStep() + 1} - > - - - - - - - - - - - - )} - - - - {/* Step 1: Name and Title */} - -
-

- Complete Your Profile -

-

- Just a few details to get you started -

-
- -
- {/* Name fields - side by side */} -
-
- - { - setHasEditedName(true); - setFirstName(e.target.value); - }} - class='w-full rounded-lg border border-gray-300 px-3 py-2 text-sm transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none' - required - id='first-name-input' - placeholder='First' - aria-describedby={displayError() ? 'profile-step1-error' : undefined} - /> -
-
- - { - setHasEditedName(true); - setLastName(e.target.value); - }} - class='w-full rounded-lg border border-gray-300 px-3 py-2 text-sm transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none' - required - id='last-name-input' - placeholder='Last' - aria-describedby={displayError() ? 'profile-step1-error' : undefined} - /> -
-
- - {/* Title dropdown */} -
- - - - setCustomTitle(e.target.value)} - class='w-full rounded-lg border border-gray-300 px-3 py-2 text-sm transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none' - placeholder='Enter your title' - maxLength={50} - /> + + + + + + + + + -
- - - - Next - -
- - {/* Step 2: Institution Details */} - -
-

- Institution Details -

-

- Optional - helps us understand your background -

-
- -
- {/* Institution */} + + )} + + + + {/* Step 1: Name and Title */} + +
+

+ Complete Your Profile +

+

Just a few details to get you started

+
+ + + {/* Name fields - side by side */} +
setInstitution(e.target.value)} + autoComplete='given-name' + autocapitalize='words' + spellcheck='false' + value={firstName()} + onInput={e => { + setHasEditedName(true); + setFirstName(e.target.value); + }} class='w-full rounded-lg border border-gray-300 px-3 py-2 text-sm transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none' - placeholder='e.g., University of Oxford' - maxLength={200} + required + id='first-name-input' + placeholder='First' + aria-describedby={displayError() ? 'profile-step1-error' : undefined} />
- - {/* Department */}
setDepartment(e.target.value)} + autoComplete='family-name' + autocapitalize='words' + spellcheck='false' + value={lastName()} + onInput={e => { + setHasEditedName(true); + setLastName(e.target.value); + }} class='w-full rounded-lg border border-gray-300 px-3 py-2 text-sm transition focus:border-transparent focus:ring-2 focus:ring-blue-500 focus:outline-none' - placeholder='e.g., Department of Medicine' - maxLength={200} + required + id='last-name-input' + placeholder='Last' + aria-describedby={displayError() ? 'profile-step1-error' : undefined} />
+
- - -
- - Back - - -
- - - -
- - {/* Completed Content (shows after all steps) */} - -
-
- -
-

All Done!

-

Redirecting to your dashboard...

-
-
+ + + + + + {/* Step 3: Persona Selection */} + +
+

+ What best describes you? +

+

This helps us tailor your experience

+
+ +
+ + + + +
+ + Back + + + Finish Setup + +
+ + + +
+ + {/* Completed Content (shows after all steps) */} + +
+
+ +
+

All Done!

+

Redirecting to your dashboard...

+
+
+
); diff --git a/packages/web/src/components/auth/SignUp.jsx b/packages/web/src/components/auth/SignUp.jsx index 1c318a651..ef63d422d 100644 --- a/packages/web/src/components/auth/SignUp.jsx +++ b/packages/web/src/components/auth/SignUp.jsx @@ -109,77 +109,75 @@ export default function SignUp() { return (
- {/* Logo */} - - CoRATES - - -
-

- Create an Account -

-

Get started with CoRATES

-
- - {/* Social providers */} - - 1} - /> - 1} - /> - - - - - - - {/* Magic Link Form - simple email signup */} - + CoRATES + + +
+

Create an Account

+

Get started with CoRATES

+
+ + {/* Social providers */} + + 1} /> - -

- By continuing, you agree to our{' '} - - Terms of Service - {' '} - and{' '} - - Privacy Policy - - . -

- -
- Already have an account?{' '} - { - e.preventDefault(); - navigate('/signin'); - }} - > - Sign In - -
+ 1} + /> +
+ + + + + + {/* Magic Link Form - simple email signup */} + + +

+ By continuing, you agree to our{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + + . +

+ +
+ Already have an account?{' '} + { + e.preventDefault(); + navigate('/signin'); + }} + > + Sign In + +
); } From 347a1211c30401898732fc57835ed9df6b27ea1b Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 17:09:40 -0600 Subject: [PATCH 5/6] add new styles for use later --- packages/docs/guides/style-guide.md | 244 +++++++++++++++++----------- packages/web/src/global.css | 92 ++++++++++- 2 files changed, 241 insertions(+), 95 deletions(-) diff --git a/packages/docs/guides/style-guide.md b/packages/docs/guides/style-guide.md index 57ae39285..9b811e7b5 100644 --- a/packages/docs/guides/style-guide.md +++ b/packages/docs/guides/style-guide.md @@ -9,47 +9,98 @@ ### Logo/Brand Icon - Circular checkmark icon -- Primary color: `blue-600` +- Primary color: `primary` - White checkmark symbol inside -## Color Palette +## Design Tokens + +The application uses a **shadcn-style semantic token system** for theming. Tokens are defined in `global.css` and support light/dark modes. + +### Token Reference + +| Token | Light Value | Usage | +| ------------------------ | ----------- | ------------------------- | +| `background` | slate-50 | Page backgrounds | +| `foreground` | slate-900 | Primary text | +| `card` | white | Card surfaces | +| `card-foreground` | slate-900 | Card text | +| `popover` | white | Dropdown/popover surfaces | +| `popover-foreground` | slate-900 | Popover text | +| `primary` | blue-600 | Primary actions, links | +| `primary-foreground` | white | Text on primary | +| `secondary` | slate-100 | Secondary buttons | +| `secondary-foreground` | slate-700 | Secondary button text | +| `muted` | slate-100 | Subtle backgrounds | +| `muted-foreground` | slate-500 | Muted/secondary text | +| `accent` | slate-100 | Hover highlights | +| `accent-foreground` | slate-900 | Accent text | +| `destructive` | red-600 | Danger/delete actions | +| `destructive-foreground` | white | Text on destructive | +| `success` | emerald-600 | Success states | +| `success-foreground` | white | Text on success | +| `warning` | amber-500 | Warning states | +| `warning-foreground` | white | Text on warning | +| `border` | slate-200 | Default borders | +| `border-subtle` | slate-100 | Subtle dividers | +| `input` | slate-200 | Input borders | +| `ring` | blue-600 | Focus rings | + +### Using Tokens -### Primary Colors +```jsx +// Backgrounds +
// Page background +
// Card surface +
// Subtle background +
// Primary action background + +// Text +

// Primary text +

// Secondary/muted text +

// Link/accent text + +// Borders +

// Default border +
// Subtle divider + +// Buttons + ``` @@ -118,7 +169,7 @@ Section labels use uppercase tracking: #### Secondary Button ```jsx - ``` @@ -126,13 +177,21 @@ Section labels use uppercase tracking: #### Text Button (Links) ```jsx - + +``` + +#### Destructive Button + +```jsx + ``` #### Icon Button ```jsx - ``` @@ -140,7 +199,7 @@ Section labels use uppercase tracking: #### Disabled State (Quota Limit) ```jsx - + Invite @@ -151,7 +210,7 @@ Section labels use uppercase tracking: #### Input Field ```jsx - + ``` #### Select (Ark UI) @@ -161,7 +220,13 @@ Uses the `@/components/ui/select` component with `createListCollection`. #### Label ```jsx - + +``` + +#### Muted Label (Uppercase) + +```jsx + ``` ### Cards & Containers @@ -169,8 +234,8 @@ Uses the `@/components/ui/select` component with `createListCollection`. #### Standard Card (Project View) ```jsx -
-

Section Title

+
+

Section Title

{/* Content */}
``` @@ -178,16 +243,16 @@ Uses the `@/components/ui/select` component with `createListCollection`. #### Settings Card (with Header) ```jsx -
+
{/* Header */} -
+
-
- +
+
-

Section Title

-

Description text

+

Section Title

+

Description text

@@ -199,12 +264,12 @@ Uses the `@/components/ui/select` component with `createListCollection`. #### Stat Card ```jsx -
+
- +
-

42

-

Label

+

42

+

Label

``` @@ -212,26 +277,26 @@ Uses the `@/components/ui/select` component with `createListCollection`. ```jsx // Success variant -
-

12

-

Ready

+
+

12

+

Ready

-// Info variant -
-

8

-

Completed

+// Primary/Info variant +
+

8

+

Completed

``` #### Collapsible Section ```jsx -
+
- -

Section Title

-
+ +

Section Title

+
Click to expand @@ -239,7 +304,7 @@ Uses the `@/components/ui/select` component with `createListCollection`.
-
{/* Content */}
+
{/* Content */}
@@ -250,7 +315,7 @@ Uses the `@/components/ui/select` component with `createListCollection`. #### Page Header (Sticky) ```jsx -
+
{/* Header content */}
``` @@ -262,18 +327,18 @@ Uses `@/components/ui/tabs` components: ```jsx {icon} Tab Label - + ``` #### Tab Badge ```jsx - + {count} ``` @@ -283,7 +348,7 @@ Uses `@/components/ui/tabs` components: #### Role Badge ```jsx - + owner ``` @@ -291,7 +356,7 @@ Uses `@/components/ui/tabs` components: #### Status Badge (Verified) ```jsx - + Verified @@ -300,7 +365,7 @@ Uses `@/components/ui/tabs` components: #### Count Badge (Project Card) ```jsx - + {role} ``` @@ -312,7 +377,7 @@ Uses `@/components/ui/avatar` component: ```jsx - {getInitials(name)} + {getInitials(name)} ``` @@ -320,9 +385,9 @@ Uses `@/components/ui/avatar` component: ```jsx
- -
``` @@ -330,7 +395,7 @@ Uses `@/components/ui/avatar` component: #### Avatar Fallback (Initials) ```jsx -
+
{initials}
``` @@ -340,10 +405,7 @@ Uses `@/components/ui/avatar` component: #### Alternating Row ```jsx -
+
{/* Row content */}
``` @@ -351,7 +413,7 @@ Uses `@/components/ui/avatar` component: #### Divider Between Sections ```jsx -
+
``` ### Modals & Overlays @@ -403,13 +465,13 @@ See `ChartSection.jsx` for chart implementations. ### Hover Effects - **Shadow**: `hover:shadow-md` for cards -- **Background**: `hover:bg-slate-50` for interactive rows -- **Text Color**: `hover:text-blue-700` for links +- **Background**: `hover:bg-muted` for interactive rows +- **Text Color**: `hover:text-primary/80` for links ### Focus States -- **Ring**: `focus:ring-2 focus:ring-blue-500/20` -- **Border**: `focus:border-blue-500` +- **Ring**: `focus:ring-2 focus:ring-ring/20` +- **Border**: `focus:border-ring` - **Outline**: `focus:outline-none` ## Page Layouts @@ -417,12 +479,12 @@ See `ChartSection.jsx` for chart implementations. ### Settings Page ```jsx -
+
{/* Page header */}
-

Page Title

-

Page description

+

Page Title

+

Page description

{/* Cards */} @@ -434,9 +496,9 @@ See `ChartSection.jsx` for chart implementations. ### Project View ```jsx -
+
{/* Sticky header with tabs */} -
+
{/* Header + Tabs */}
@@ -452,14 +514,14 @@ See `ChartSection.jsx` for chart implementations. All interactive elements use: ``` -focus:ring-2 focus:ring-blue-500/20 focus:outline-none +focus:ring-2 focus:ring-ring/20 focus:outline-none ``` ### Color Contrast -- Primary text (`slate-900`) on white backgrounds: 15.4:1 -- Secondary text (`slate-500`) on white backgrounds: 5.6:1 -- Blue links (`blue-600`) on white backgrounds: 4.5:1 +- `foreground` on `card` backgrounds: 15.4:1 +- `muted-foreground` on `card` backgrounds: 5.6:1 +- `primary` on `card` backgrounds: 4.5:1 ### Interactive Elements diff --git a/packages/web/src/global.css b/packages/web/src/global.css index 416529b23..54229eea9 100644 --- a/packages/web/src/global.css +++ b/packages/web/src/global.css @@ -13,17 +13,101 @@ --breakpoint-xs: 30rem; /* 480px */ - /* Brand color scale */ - --color-brand-50: #f4f7ff; + /* Border radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + + /* --color-brand-50: #f4f7ff; --color-brand-100: #e6ecff; --color-brand-200: #cdd9ff; --color-brand-300: #aabaff; --color-brand-400: #7f93ff; - --color-brand-500: #5a6bff; /* brand anchor */ + --color-brand-500: #5a6bff; --color-brand-600: #4756e6; --color-brand-700: #3b46bf; --color-brand-800: #313a99; - --color-brand-900: #2a327a; + --color-brand-900: #2a327a; */ +} + +/* Tailwind theme extension - semantic color tokens using OKLCH */ +@theme { + /* Light theme colors (default) */ + --color-background: oklch(0.984 0.003 247.858); /* slate-50 */ + --color-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-card: oklch(1 0 0); /* white */ + --color-card-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-popover: oklch(1 0 0); /* white */ + --color-popover-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-primary: oklch(0.546 0.245 262.881); /* blue-600 */ + --color-primary-foreground: oklch(1 0 0); /* white */ + + --color-secondary: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-secondary-foreground: oklch(0.372 0.044 257.287); /* slate-700 */ + + --color-muted: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-muted-foreground: oklch(0.554 0.046 257.417); /* slate-500 */ + + --color-accent: oklch(0.968 0.007 247.896); /* slate-100 */ + --color-accent-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-destructive: oklch(0.577 0.245 27.325); /* red-600 */ + --color-destructive-foreground: oklch(1 0 0); /* white */ + + --color-success: oklch(0.596 0.145 163.225); /* emerald-600 */ + --color-success-foreground: oklch(1 0 0); /* white */ + + --color-warning: oklch(0.769 0.188 70.08); /* amber-500 */ + --color-warning-foreground: oklch(1 0 0); /* white */ + + --color-border: oklch(0.929 0.013 255.508); /* slate-200 */ + --color-border-subtle: oklch(0.968 0.007 247.896); /* slate-100 */ + + --color-input: oklch(0.929 0.013 255.508); /* slate-200 */ + --color-ring: oklch(0.546 0.245 262.881); /* blue-600 */ +} + +/* Dark theme overrides */ +.dark { + --color-background: oklch(0.208 0.042 265.755); /* slate-900 */ + --color-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-card: oklch(0.279 0.041 260.031); /* slate-800 */ + --color-card-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-popover: oklch(0.279 0.041 260.031); /* slate-800 */ + --color-popover-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-primary: oklch(0.707 0.165 254.624); /* blue-400 */ + --color-primary-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-secondary: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-secondary-foreground: oklch(0.929 0.013 255.508); /* slate-200 */ + + --color-muted: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-muted-foreground: oklch(0.704 0.04 256.788); /* slate-400 */ + + --color-accent: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-accent-foreground: oklch(0.984 0.003 247.858); /* slate-50 */ + + --color-destructive: oklch(0.637 0.237 25.331); /* red-500 */ + --color-destructive-foreground: oklch(1 0 0); /* white */ + + --color-success: oklch(0.765 0.177 163.223); /* emerald-400 */ + --color-success-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-warning: oklch(0.828 0.189 84.429); /* amber-400 */ + --color-warning-foreground: oklch(0.208 0.042 265.755); /* slate-900 */ + + --color-border: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-border-subtle: oklch(0.279 0.041 260.031); /* slate-800 */ + + --color-input: oklch(0.372 0.044 257.287); /* slate-700 */ + --color-ring: oklch(0.707 0.165 254.624); /* blue-400 */ } /* scrollbar for sidebar */ From d7d829bc526065d9aedfb49aab9c4befab2793e1 Mon Sep 17 00:00:00 2001 From: Jacob Maynard Date: Sun, 18 Jan 2026 21:09:18 -0600 Subject: [PATCH 6/6] add inert --- packages/web/src/components/auth/SignIn.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/web/src/components/auth/SignIn.jsx b/packages/web/src/components/auth/SignIn.jsx index 17c8e1bb1..6f73686cd 100644 --- a/packages/web/src/components/auth/SignIn.jsx +++ b/packages/web/src/components/auth/SignIn.jsx @@ -271,6 +271,7 @@ export default function SignIn() { role='tabpanel' aria-labelledby='tab-password' aria-hidden={useMagicLink()} + inert={useMagicLink()} class='w-1/2 shrink-0 bg-white pr-4' >
@@ -347,6 +348,7 @@ export default function SignIn() { role='tabpanel' aria-labelledby='tab-magic-link' aria-hidden={!useMagicLink()} + inert={!useMagicLink()} class='w-1/2 shrink-0 bg-white pl-4' >