diff --git a/frontend/src/components/shared/DividerWithText.tsx b/frontend/src/components/shared/DividerWithText.tsx
index 9b82240a1..86984969e 100644
--- a/frontend/src/components/shared/DividerWithText.tsx
+++ b/frontend/src/components/shared/DividerWithText.tsx
@@ -1,4 +1,4 @@
-import './dividerWithText/DividerWithText.css'
+import './dividerWithText/DividerWithText.css';
interface TextDividerProps {
text?: string
@@ -10,9 +10,9 @@ interface TextDividerProps {
}
export default function DividerWithText({ text, className = '', style, variant = 'default', respondsToDarkMode = true, opacity }: TextDividerProps) {
- const variantClass = variant === 'subcategory' ? 'subcategory' : ''
- const themeClass = respondsToDarkMode ? '' : 'force-light'
- const styleWithOpacity = opacity !== undefined ? { ...(style || {}), ['--text-divider-opacity' as any]: opacity } : style
+ const variantClass = variant === 'subcategory' ? 'subcategory' : '';
+ const themeClass = respondsToDarkMode ? '' : 'force-light';
+ const styleWithOpacity = opacity !== undefined ? { ...(style || {}), ['--text-divider-opacity' as any]: opacity } : style;
if (text) {
return (
@@ -24,7 +24,7 @@ export default function DividerWithText({ text, className = '', style, variant =
{text}
- )
+ );
}
return (
@@ -32,5 +32,5 @@ export default function DividerWithText({ text, className = '', style, variant =
className={`h-px my-2.5 ${themeClass} ${className}`}
style={styleWithOpacity}
/>
- )
+ );
}
diff --git a/frontend/src/components/shared/LoginRightCarousel.tsx b/frontend/src/components/shared/LoginRightCarousel.tsx
index c2ebb29bf..eb7e7ab73 100644
--- a/frontend/src/components/shared/LoginRightCarousel.tsx
+++ b/frontend/src/components/shared/LoginRightCarousel.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from 'react'
+import { useEffect, useMemo, useRef, useState } from 'react';
import { BASE_PATH } from '../../constants/app';
type ImageSlide = { src: string; alt?: string; cornerModelUrl?: string; title?: string; subtitle?: string; followMouseTilt?: boolean; tiltMaxDeg?: number }
@@ -14,53 +14,53 @@ export default function LoginRightCarousel({
initialSeconds?: number
slideSeconds?: number
}) {
- const totalSlides = imageSlides.length
- const [index, setIndex] = useState(0)
- const mouse = useRef({ x: 0, y: 0 })
+ const totalSlides = imageSlides.length;
+ const [index, setIndex] = useState(0);
+ const mouse = useRef({ x: 0, y: 0 });
const durationsMs = useMemo(() => {
- if (imageSlides.length === 0) return []
- return imageSlides.map((_, i) => (i === 0 ? (initialSeconds ?? slideSeconds) : slideSeconds) * 1000)
- }, [imageSlides, initialSeconds, slideSeconds])
+ if (imageSlides.length === 0) return [];
+ return imageSlides.map((_, i) => (i === 0 ? (initialSeconds ?? slideSeconds) : slideSeconds) * 1000);
+ }, [imageSlides, initialSeconds, slideSeconds]);
useEffect(() => {
- if (totalSlides <= 1) return
+ if (totalSlides <= 1) return;
const timeout = setTimeout(() => {
- setIndex((i) => (i + 1) % totalSlides)
- }, durationsMs[index] ?? slideSeconds * 1000)
- return () => clearTimeout(timeout)
- }, [index, totalSlides, durationsMs, slideSeconds])
+ setIndex((i) => (i + 1) % totalSlides);
+ }, durationsMs[index] ?? slideSeconds * 1000);
+ return () => clearTimeout(timeout);
+ }, [index, totalSlides, durationsMs, slideSeconds]);
useEffect(() => {
const onMove = (e: MouseEvent) => {
- mouse.current.x = (e.clientX / window.innerWidth) * 2 - 1
- mouse.current.y = (e.clientY / window.innerHeight) * 2 - 1
- }
- window.addEventListener('mousemove', onMove)
- return () => window.removeEventListener('mousemove', onMove)
- }, [])
+ mouse.current.x = (e.clientX / window.innerWidth) * 2 - 1;
+ mouse.current.y = (e.clientY / window.innerHeight) * 2 - 1;
+ };
+ window.addEventListener('mousemove', onMove);
+ return () => window.removeEventListener('mousemove', onMove);
+ }, []);
function TiltImage({ src, alt, enabled, maxDeg = 6 }: { src: string; alt?: string; enabled: boolean; maxDeg?: number }) {
- const imgRef = useRef(null)
+ const imgRef = useRef(null);
useEffect(() => {
- const el = imgRef.current
- if (!el) return
+ const el = imgRef.current;
+ if (!el) return;
- let raf = 0
+ let raf = 0;
const tick = () => {
if (enabled) {
- const rotY = (mouse.current.x || 0) * maxDeg
- const rotX = -(mouse.current.y || 0) * maxDeg
- el.style.transform = `translateY(-2rem) rotateX(${rotX.toFixed(2)}deg) rotateY(${rotY.toFixed(2)}deg)`
+ const rotY = (mouse.current.x || 0) * maxDeg;
+ const rotX = -(mouse.current.y || 0) * maxDeg;
+ el.style.transform = `translateY(-2rem) rotateX(${rotX.toFixed(2)}deg) rotateY(${rotY.toFixed(2)}deg)`;
} else {
- el.style.transform = 'translateY(-2rem)'
+ el.style.transform = 'translateY(-2rem)';
}
- raf = requestAnimationFrame(tick)
- }
- raf = requestAnimationFrame(tick)
- return () => cancelAnimationFrame(raf)
- }, [enabled, maxDeg])
+ raf = requestAnimationFrame(tick);
+ };
+ raf = requestAnimationFrame(tick);
+ return () => cancelAnimationFrame(raf);
+ }, [enabled, maxDeg]);
return (
- )
+ );
}
return (
@@ -155,5 +155,5 @@ export default function LoginRightCarousel({
))}
- )
+ );
}
diff --git a/frontend/src/components/shared/loginSlides.ts b/frontend/src/components/shared/loginSlides.ts
index aa3d7f443..690f84f6c 100644
--- a/frontend/src/components/shared/loginSlides.ts
+++ b/frontend/src/components/shared/loginSlides.ts
@@ -38,6 +38,6 @@ export const loginSlides: LoginCarouselSlide[] = [
followMouseTilt: true,
tiltMaxDeg: 5,
},
-]
+];
-export default loginSlides
+export default loginSlides;
diff --git a/frontend/src/routes/AuthCallback.tsx b/frontend/src/routes/AuthCallback.tsx
index 285d58251..be4cd5424 100644
--- a/frontend/src/routes/AuthCallback.tsx
+++ b/frontend/src/routes/AuthCallback.tsx
@@ -1,6 +1,6 @@
-import { useEffect } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { useAuth } from '../auth/UseSession'
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../auth/UseSession';
/**
* OAuth Callback Handler
@@ -10,50 +10,50 @@ import { useAuth } from '../auth/UseSession'
* We extract it, store in localStorage, and redirect to the home page.
*/
export default function AuthCallback() {
- const navigate = useNavigate()
- const { refreshSession } = useAuth()
+ const navigate = useNavigate();
+ const { refreshSession } = useAuth();
useEffect(() => {
const handleCallback = async () => {
try {
- console.log('[AuthCallback] Handling OAuth callback...')
+ console.log('[AuthCallback] Handling OAuth callback...');
// Extract JWT from URL fragment (#access_token=...)
- const hash = window.location.hash.substring(1) // Remove '#'
- const params = new URLSearchParams(hash)
- const token = params.get('access_token')
+ const hash = window.location.hash.substring(1); // Remove '#'
+ const params = new URLSearchParams(hash);
+ const token = params.get('access_token');
if (!token) {
- console.error('[AuthCallback] No access_token in URL fragment')
+ console.error('[AuthCallback] No access_token in URL fragment');
navigate('/login', {
replace: true,
state: { error: 'OAuth login failed - no token received.' }
- })
- return
+ });
+ return;
}
// Store JWT in localStorage
- localStorage.setItem('stirling_jwt', token)
- console.log('[AuthCallback] JWT stored in localStorage')
+ localStorage.setItem('stirling_jwt', token);
+ console.log('[AuthCallback] JWT stored in localStorage');
// Refresh session to load user info into state
- await refreshSession()
+ await refreshSession();
- console.log('[AuthCallback] Session refreshed, redirecting to home')
+ console.log('[AuthCallback] Session refreshed, redirecting to home');
// Clear the hash from URL and redirect to home page
- navigate('/', { replace: true })
+ navigate('/', { replace: true });
} catch (error) {
- console.error('[AuthCallback] Error:', error)
+ console.error('[AuthCallback] Error:', error);
navigate('/login', {
replace: true,
state: { error: 'OAuth login failed. Please try again.' }
- })
+ });
}
- }
+ };
- handleCallback()
- }, [navigate, refreshSession])
+ handleCallback();
+ }, [navigate, refreshSession]);
return (
- )
+ );
}
diff --git a/frontend/src/routes/Landing.tsx b/frontend/src/routes/Landing.tsx
index 0eb2ba091..6f473f6f1 100644
--- a/frontend/src/routes/Landing.tsx
+++ b/frontend/src/routes/Landing.tsx
@@ -1,8 +1,8 @@
-import { Navigate, useLocation } from 'react-router-dom'
-import { useAuth } from '../auth/UseSession'
-import { useAppConfig } from '../hooks/useAppConfig'
-import HomePage from '../pages/HomePage'
-import Login from './Login'
+import { Navigate, useLocation } from 'react-router-dom';
+import { useAuth } from '../auth/UseSession';
+import { useAppConfig } from '../hooks/useAppConfig';
+import HomePage from '../pages/HomePage';
+import Login from './Login';
/**
* Landing component - Smart router based on authentication status
@@ -12,18 +12,18 @@ import Login from './Login'
* If user is not authenticated: Show Login or redirect to /login
*/
export default function Landing() {
- const { session, loading: authLoading } = useAuth()
- const { config, loading: configLoading } = useAppConfig()
- const location = useLocation()
+ const { session, loading: authLoading } = useAuth();
+ const { config, loading: configLoading } = useAppConfig();
+ const location = useLocation();
- const loading = authLoading || configLoading
+ const loading = authLoading || configLoading;
console.log('[Landing] State:', {
pathname: location.pathname,
loading,
hasSession: !!session,
loginEnabled: config?.enableLogin,
- })
+ });
// Show loading while checking auth and config
if (loading) {
@@ -36,27 +36,27 @@ export default function Landing() {
- )
+ );
}
// If login is disabled, show app directly (anonymous mode)
if (config?.enableLogin === false) {
- console.debug('[Landing] Login disabled - showing app in anonymous mode')
- return
+ console.debug('[Landing] Login disabled - showing app in anonymous mode');
+ return ;
}
// If we have a session, show the main app
if (session) {
- return
+ return ;
}
// If we're at home route ("/"), show login directly (marketing/landing page)
// Otherwise navigate to login (fixes URL mismatch for tool routes)
- const isHome = location.pathname === '/' || location.pathname === ''
+ const isHome = location.pathname === '/' || location.pathname === '';
if (isHome) {
- return
+ return ;
}
// For non-home routes without auth, navigate to login (preserves from location)
- return
+ return ;
}
diff --git a/frontend/src/routes/Login.tsx b/frontend/src/routes/Login.tsx
index 61efb0c74..04c345936 100644
--- a/frontend/src/routes/Login.tsx
+++ b/frontend/src/routes/Login.tsx
@@ -1,42 +1,42 @@
-import { useEffect, useState } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { springAuth } from '../auth/springAuthClient'
-import { useAuth } from '../auth/UseSession'
-import { useTranslation } from 'react-i18next'
-import { useDocumentMeta } from '../hooks/useDocumentMeta'
-import AuthLayout from './authShared/AuthLayout'
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { springAuth } from '../auth/springAuthClient';
+import { useAuth } from '../auth/UseSession';
+import { useTranslation } from 'react-i18next';
+import { useDocumentMeta } from '../hooks/useDocumentMeta';
+import AuthLayout from './authShared/AuthLayout';
// Import login components
-import LoginHeader from './login/LoginHeader'
-import ErrorMessage from './login/ErrorMessage'
-import EmailPasswordForm from './login/EmailPasswordForm'
-import OAuthButtons from './login/OAuthButtons'
-import DividerWithText from '../components/shared/DividerWithText'
-import LoggedInState from './login/LoggedInState'
-import { BASE_PATH } from '../constants/app'
+import LoginHeader from './login/LoginHeader';
+import ErrorMessage from './login/ErrorMessage';
+import EmailPasswordForm from './login/EmailPasswordForm';
+import OAuthButtons from './login/OAuthButtons';
+import DividerWithText from '../components/shared/DividerWithText';
+import LoggedInState from './login/LoggedInState';
+import { BASE_PATH } from '../constants/app';
export default function Login() {
- const navigate = useNavigate()
- const { session, loading } = useAuth()
- const { t } = useTranslation()
- const [isSigningIn, setIsSigningIn] = useState(false)
- const [error, setError] = useState(null)
- const [showEmailForm, setShowEmailForm] = useState(false)
- const [email, setEmail] = useState('')
- const [password, setPassword] = useState('')
+ const navigate = useNavigate();
+ const { session, loading } = useAuth();
+ const { t } = useTranslation();
+ const [isSigningIn, setIsSigningIn] = useState(false);
+ const [error, setError] = useState(null);
+ const [showEmailForm, setShowEmailForm] = useState(false);
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
// Prefill email from query param (e.g. after password reset)
useEffect(() => {
try {
- const url = new URL(window.location.href)
- const emailFromQuery = url.searchParams.get('email')
+ const url = new URL(window.location.href);
+ const emailFromQuery = url.searchParams.get('email');
if (emailFromQuery) {
- setEmail(emailFromQuery)
+ setEmail(emailFromQuery);
}
} catch (_) {
// ignore
}
- }, [])
+ }, []);
const baseUrl = window.location.origin + BASE_PATH;
@@ -48,74 +48,74 @@ export default function Login() {
ogDescription: t('app.description', 'The Free Adobe Acrobat alternative (10M+ Downloads)'),
ogImage: `${baseUrl}/og_images/home.png`,
ogUrl: `${window.location.origin}${window.location.pathname}`
- })
+ });
// Show logged in state if authenticated
if (session && !loading) {
- return
+ return ;
}
const signInWithProvider = async (provider: 'github' | 'google' | 'apple' | 'azure') => {
try {
- setIsSigningIn(true)
- setError(null)
+ setIsSigningIn(true);
+ setError(null);
- console.log(`[Login] Signing in with ${provider}`)
+ console.log(`[Login] Signing in with ${provider}`);
// Redirect to Spring OAuth2 endpoint
const { error } = await springAuth.signInWithOAuth({
provider,
options: { redirectTo: `${BASE_PATH}/auth/callback` }
- })
+ });
if (error) {
- console.error(`[Login] ${provider} error:`, error)
- setError(t('login.failedToSignIn', { provider, message: error.message }) || `Failed to sign in with ${provider}`)
+ console.error(`[Login] ${provider} error:`, error);
+ setError(t('login.failedToSignIn', { provider, message: error.message }) || `Failed to sign in with ${provider}`);
}
} catch (err) {
- console.error(`[Login] Unexpected error:`, err)
- setError(t('login.unexpectedError', { message: err instanceof Error ? err.message : 'Unknown error' }) || 'An unexpected error occurred')
+ console.error(`[Login] Unexpected error:`, err);
+ setError(t('login.unexpectedError', { message: err instanceof Error ? err.message : 'Unknown error' }) || 'An unexpected error occurred');
} finally {
- setIsSigningIn(false)
+ setIsSigningIn(false);
}
- }
+ };
const signInWithEmail = async () => {
if (!email || !password) {
- setError(t('login.pleaseEnterBoth') || 'Please enter both email and password')
- return
+ setError(t('login.pleaseEnterBoth') || 'Please enter both email and password');
+ return;
}
try {
- setIsSigningIn(true)
- setError(null)
+ setIsSigningIn(true);
+ setError(null);
- console.log('[Login] Signing in with email:', email)
+ console.log('[Login] Signing in with email:', email);
const { user, session, error } = await springAuth.signInWithPassword({
email: email.trim(),
password: password
- })
+ });
if (error) {
- console.error('[Login] Email sign in error:', error)
- setError(error.message)
+ console.error('[Login] Email sign in error:', error);
+ setError(error.message);
} else if (user && session) {
- console.log('[Login] Email sign in successful')
+ console.log('[Login] Email sign in successful');
// Auth state will update automatically and Landing will redirect to home
// No need to navigate manually here
}
} catch (err) {
- console.error('[Login] Unexpected error:', err)
- setError(t('login.unexpectedError', { message: err instanceof Error ? err.message : 'Unknown error' }) || 'An unexpected error occurred')
+ console.error('[Login] Unexpected error:', err);
+ setError(t('login.unexpectedError', { message: err instanceof Error ? err.message : 'Unknown error' }) || 'An unexpected error occurred');
} finally {
- setIsSigningIn(false)
+ setIsSigningIn(false);
}
- }
+ };
const handleForgotPassword = () => {
- navigate('/auth/reset')
- }
+ navigate('/auth/reset');
+ };
return (
@@ -185,5 +185,5 @@ export default function Login() {
- )
+ );
}
diff --git a/frontend/src/routes/Signup.tsx b/frontend/src/routes/Signup.tsx
index 0a5ee32ac..465ff338b 100644
--- a/frontend/src/routes/Signup.tsx
+++ b/frontend/src/routes/Signup.tsx
@@ -1,28 +1,28 @@
-import { useState } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { useTranslation } from 'react-i18next'
-import { useDocumentMeta } from '../hooks/useDocumentMeta'
-import AuthLayout from './authShared/AuthLayout'
-import './authShared/auth.css'
-import { BASE_PATH } from '../constants/app'
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { useDocumentMeta } from '../hooks/useDocumentMeta';
+import AuthLayout from './authShared/AuthLayout';
+import './authShared/auth.css';
+import { BASE_PATH } from '../constants/app';
// Import signup components
-import LoginHeader from './login/LoginHeader'
-import ErrorMessage from './login/ErrorMessage'
-import DividerWithText from '../components/shared/DividerWithText'
-import SignupForm from './signup/SignupForm'
-import { useSignupFormValidation, SignupFieldErrors } from './signup/SignupFormValidation'
-import { useAuthService } from './signup/AuthService'
+import LoginHeader from './login/LoginHeader';
+import ErrorMessage from './login/ErrorMessage';
+import DividerWithText from '../components/shared/DividerWithText';
+import SignupForm from './signup/SignupForm';
+import { useSignupFormValidation, SignupFieldErrors } from './signup/SignupFormValidation';
+import { useAuthService } from './signup/AuthService';
export default function Signup() {
- const navigate = useNavigate()
- const { t } = useTranslation()
- const [isSigningUp, setIsSigningUp] = useState(false)
- const [error, setError] = useState(null)
- const [email, setEmail] = useState('')
- const [password, setPassword] = useState('')
- const [confirmPassword, setConfirmPassword] = useState('')
- const [fieldErrors, setFieldErrors] = useState({})
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+ const [isSigningUp, setIsSigningUp] = useState(false);
+ const [error, setError] = useState(null);
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [fieldErrors, setFieldErrors] = useState({});
const baseUrl = window.location.origin + BASE_PATH;
@@ -34,38 +34,38 @@ export default function Signup() {
ogDescription: t('app.description', 'The Free Adobe Acrobat alternative (10M+ Downloads)'),
ogImage: `${baseUrl}/og_images/home.png`,
ogUrl: `${window.location.origin}${window.location.pathname}`
- })
+ });
- const { validateSignupForm } = useSignupFormValidation()
- const { signUp } = useAuthService()
+ const { validateSignupForm } = useSignupFormValidation();
+ const { signUp } = useAuthService();
const handleSignUp = async () => {
- const validation = validateSignupForm(email, password, confirmPassword)
+ const validation = validateSignupForm(email, password, confirmPassword);
if (!validation.isValid) {
- setError(validation.error)
- setFieldErrors(validation.fieldErrors || {})
- return
+ setError(validation.error);
+ setFieldErrors(validation.fieldErrors || {});
+ return;
}
try {
- setIsSigningUp(true)
- setError(null)
- setFieldErrors({})
+ setIsSigningUp(true);
+ setError(null);
+ setFieldErrors({});
- const result = await signUp(email, password, '')
+ const result = await signUp(email, password, '');
if (result.user) {
// Show success message and redirect to login
- setError(null)
- setTimeout(() => navigate('/login'), 2000)
+ setError(null);
+ setTimeout(() => navigate('/login'), 2000);
}
} catch (err) {
- console.error('[Signup] Unexpected error:', err)
- setError(err instanceof Error ? err.message : t('signup.unexpectedError', { message: 'Unknown error' }))
+ console.error('[Signup] Unexpected error:', err);
+ setError(err instanceof Error ? err.message : t('signup.unexpectedError', { message: 'Unknown error' }));
} finally {
- setIsSigningUp(false)
+ setIsSigningUp(false);
}
- }
+ };
return (
@@ -101,5 +101,5 @@ export default function Signup() {
- )
+ );
}
diff --git a/frontend/src/routes/authShared/AuthLayout.tsx b/frontend/src/routes/authShared/AuthLayout.tsx
index e32f629e0..99c4b69de 100644
--- a/frontend/src/routes/authShared/AuthLayout.tsx
+++ b/frontend/src/routes/authShared/AuthLayout.tsx
@@ -1,51 +1,51 @@
-import React, { useEffect, useRef, useState } from 'react'
-import LoginRightCarousel from '../../components/shared/LoginRightCarousel'
-import loginSlides from '../../components/shared/loginSlides'
-import styles from './AuthLayout.module.css'
+import React, { useEffect, useRef, useState } from 'react';
+import LoginRightCarousel from '../../components/shared/LoginRightCarousel';
+import loginSlides from '../../components/shared/loginSlides';
+import styles from './AuthLayout.module.css';
interface AuthLayoutProps {
children: React.ReactNode
}
export default function AuthLayout({ children }: AuthLayoutProps) {
- const cardRef = useRef(null)
- const [hideRightPanel, setHideRightPanel] = useState(false)
+ const cardRef = useRef(null);
+ const [hideRightPanel, setHideRightPanel] = useState(false);
// Force light mode on auth pages
useEffect(() => {
- const htmlElement = document.documentElement
- const previousColorScheme = htmlElement.getAttribute('data-mantine-color-scheme')
+ const htmlElement = document.documentElement;
+ const previousColorScheme = htmlElement.getAttribute('data-mantine-color-scheme');
// Set light mode
- htmlElement.setAttribute('data-mantine-color-scheme', 'light')
+ htmlElement.setAttribute('data-mantine-color-scheme', 'light');
// Cleanup: restore previous theme when leaving auth pages
return () => {
if (previousColorScheme) {
- htmlElement.setAttribute('data-mantine-color-scheme', previousColorScheme)
+ htmlElement.setAttribute('data-mantine-color-scheme', previousColorScheme);
}
- }
- }, [])
+ };
+ }, []);
useEffect(() => {
const update = () => {
// Use viewport to avoid hysteresis when the card is already in single-column mode
- const viewportWidth = window.innerWidth
- const viewportHeight = window.innerHeight
- const cardWidthIfTwoCols = Math.min(1180, viewportWidth * 0.96) // matches min(73.75rem, 96vw)
- const columnWidth = cardWidthIfTwoCols / 2
- const tooNarrow = columnWidth < 470
- const tooShort = viewportHeight < 740
- setHideRightPanel(tooNarrow || tooShort)
- }
- update()
- window.addEventListener('resize', update)
- window.addEventListener('orientationchange', update)
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+ const cardWidthIfTwoCols = Math.min(1180, viewportWidth * 0.96); // matches min(73.75rem, 96vw)
+ const columnWidth = cardWidthIfTwoCols / 2;
+ const tooNarrow = columnWidth < 470;
+ const tooShort = viewportHeight < 740;
+ setHideRightPanel(tooNarrow || tooShort);
+ };
+ update();
+ window.addEventListener('resize', update);
+ window.addEventListener('orientationchange', update);
return () => {
- window.removeEventListener('resize', update)
- window.removeEventListener('orientationchange', update)
- }
- }, [])
+ window.removeEventListener('resize', update);
+ window.removeEventListener('orientationchange', update);
+ };
+ }, []);
return (
@@ -64,5 +64,5 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
)}
- )
+ );
}
diff --git a/frontend/src/routes/login/EmailPasswordForm.tsx b/frontend/src/routes/login/EmailPasswordForm.tsx
index 7036e24cf..dff38026f 100644
--- a/frontend/src/routes/login/EmailPasswordForm.tsx
+++ b/frontend/src/routes/login/EmailPasswordForm.tsx
@@ -1,5 +1,5 @@
-import { useTranslation } from 'react-i18next'
-import '../authShared/auth.css'
+import { useTranslation } from 'react-i18next';
+import '../authShared/auth.css';
interface EmailPasswordFormProps {
email: string
@@ -27,12 +27,12 @@ export default function EmailPasswordForm({
showPasswordField = true,
fieldErrors = {}
}: EmailPasswordFormProps) {
- const { t } = useTranslation()
+ const { t } = useTranslation();
const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault()
- onSubmit()
- }
+ e.preventDefault();
+ onSubmit();
+ };
return (
- )
+ );
}
diff --git a/frontend/src/routes/login/ErrorMessage.tsx b/frontend/src/routes/login/ErrorMessage.tsx
index 4f237b77c..4563a2450 100644
--- a/frontend/src/routes/login/ErrorMessage.tsx
+++ b/frontend/src/routes/login/ErrorMessage.tsx
@@ -3,11 +3,11 @@ interface ErrorMessageProps {
}
export default function ErrorMessage({ error }: ErrorMessageProps) {
- if (!error) return null
+ if (!error) return null;
return (
- )
+ );
}
diff --git a/frontend/src/routes/login/LoggedInState.tsx b/frontend/src/routes/login/LoggedInState.tsx
index 19483b8bc..7caf92bcf 100644
--- a/frontend/src/routes/login/LoggedInState.tsx
+++ b/frontend/src/routes/login/LoggedInState.tsx
@@ -1,20 +1,20 @@
-import { useEffect } from 'react'
-import { useNavigate } from 'react-router-dom'
-import { useAuth } from '../../auth/UseSession'
-import { useTranslation } from 'react-i18next'
+import { useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '../../auth/UseSession';
+import { useTranslation } from 'react-i18next';
export default function LoggedInState() {
- const navigate = useNavigate()
- const { user } = useAuth()
- const { t } = useTranslation()
+ const navigate = useNavigate();
+ const { user } = useAuth();
+ const { t } = useTranslation();
useEffect(() => {
const timer = setTimeout(() => {
- navigate('/')
- }, 2000)
+ navigate('/');
+ }, 2000);
- return () => clearTimeout(timer)
- }, [navigate])
+ return () => clearTimeout(timer);
+ }, [navigate]);
return (
- )
+ );
}
diff --git a/frontend/src/routes/login/LoginHeader.tsx b/frontend/src/routes/login/LoginHeader.tsx
index 093100574..dde6867ab 100644
--- a/frontend/src/routes/login/LoginHeader.tsx
+++ b/frontend/src/routes/login/LoginHeader.tsx
@@ -18,5 +18,5 @@ export default function LoginHeader({ title, subtitle }: LoginHeaderProps) {
{subtitle}
)}
- )
+ );
}
diff --git a/frontend/src/routes/login/NavigationLink.tsx b/frontend/src/routes/login/NavigationLink.tsx
index 965a659b9..935654381 100644
--- a/frontend/src/routes/login/NavigationLink.tsx
+++ b/frontend/src/routes/login/NavigationLink.tsx
@@ -15,5 +15,5 @@ export default function NavigationLink({ onClick, text, isDisabled = false }: Na
{text}
- )
+ );
}
diff --git a/frontend/src/routes/login/OAuthButtons.tsx b/frontend/src/routes/login/OAuthButtons.tsx
index 0de8f3179..b5d5ec54c 100644
--- a/frontend/src/routes/login/OAuthButtons.tsx
+++ b/frontend/src/routes/login/OAuthButtons.tsx
@@ -1,5 +1,5 @@
-import { useTranslation } from 'react-i18next'
-import { BASE_PATH } from '../../constants/app'
+import { useTranslation } from 'react-i18next';
+import { BASE_PATH } from '../../constants/app';
// OAuth provider configuration
const oauthProviders = [
@@ -7,7 +7,7 @@ const oauthProviders = [
{ id: 'github', label: 'GitHub', file: 'github.svg', isDisabled: false },
{ id: 'apple', label: 'Apple', file: 'apple.svg', isDisabled: true },
{ id: 'azure', label: 'Microsoft', file: 'microsoft.svg', isDisabled: true }
-]
+];
interface OAuthButtonsProps {
onProviderClick: (provider: 'github' | 'google' | 'apple' | 'azure') => void
@@ -16,10 +16,10 @@ interface OAuthButtonsProps {
}
export default function OAuthButtons({ onProviderClick, isSubmitting, layout = 'vertical' }: OAuthButtonsProps) {
- const { t } = useTranslation()
+ const { t } = useTranslation();
// Filter out disabled providers - don't show them at all
- const enabledProviders = oauthProviders.filter(p => !p.isDisabled)
+ const enabledProviders = oauthProviders.filter(p => !p.isDisabled);
if (layout === 'icons') {
return (
@@ -37,7 +37,7 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
))}
- )
+ );
}
if (layout === 'grid') {
@@ -56,7 +56,7 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
))}
- )
+ );
}
return (
@@ -74,5 +74,5 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
))}
- )
+ );
}
diff --git a/frontend/src/routes/signup/AuthService.ts b/frontend/src/routes/signup/AuthService.ts
index ce2426fa2..a68e72d24 100644
--- a/frontend/src/routes/signup/AuthService.ts
+++ b/frontend/src/routes/signup/AuthService.ts
@@ -1,5 +1,5 @@
-import { springAuth } from '../../auth/springAuthClient'
-import { BASE_PATH } from '../../constants/app'
+import { springAuth } from '../../auth/springAuthClient';
+import { BASE_PATH } from '../../constants/app';
export const useAuthService = () => {
@@ -8,7 +8,7 @@ export const useAuthService = () => {
password: string,
name: string
) => {
- console.log('[Signup] Creating account for:', email)
+ console.log('[Signup] Creating account for:', email);
const { user, session, error } = await springAuth.signUp({
email: email.trim(),
@@ -17,38 +17,38 @@ export const useAuthService = () => {
data: { full_name: name },
emailRedirectTo: `${BASE_PATH}/auth/callback`
}
- })
+ });
if (error) {
- console.error('[Signup] Sign up error:', error)
- throw new Error(error.message)
+ console.error('[Signup] Sign up error:', error);
+ throw new Error(error.message);
}
if (user) {
- console.log('[Signup] Sign up successful:', user)
+ console.log('[Signup] Sign up successful:', user);
return {
user: user,
session: session,
requiresEmailConfirmation: user && !session
- }
+ };
}
- throw new Error('Unknown error occurred during signup')
- }
+ throw new Error('Unknown error occurred during signup');
+ };
const signInWithProvider = async (provider: 'github' | 'google' | 'apple' | 'azure') => {
const { error } = await springAuth.signInWithOAuth({
provider,
options: { redirectTo: `${BASE_PATH}/auth/callback` }
- })
+ });
if (error) {
- throw new Error(error.message)
+ throw new Error(error.message);
}
- }
+ };
return {
signUp,
signInWithProvider
- }
-}
\ No newline at end of file
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/routes/signup/SignupForm.tsx b/frontend/src/routes/signup/SignupForm.tsx
index 2ae809467..9ff7bc7f4 100644
--- a/frontend/src/routes/signup/SignupForm.tsx
+++ b/frontend/src/routes/signup/SignupForm.tsx
@@ -1,7 +1,7 @@
-import { useEffect } from 'react'
-import '../authShared/auth.css'
-import { useTranslation } from 'react-i18next'
-import { SignupFieldErrors } from './SignupFormValidation'
+import { useEffect } from 'react';
+import '../authShared/auth.css';
+import { useTranslation } from 'react-i18next';
+import { SignupFieldErrors } from './SignupFormValidation';
interface SignupFormProps {
name?: string
@@ -38,14 +38,14 @@ export default function SignupForm({
showName = false,
showTerms = false
}: SignupFormProps) {
- const { t } = useTranslation()
- const showConfirm = password.length >= 4
+ const { t } = useTranslation();
+ const showConfirm = password.length >= 4;
useEffect(() => {
if (!showConfirm && confirmPassword) {
- setConfirmPassword('')
+ setConfirmPassword('');
}
- }, [showConfirm, confirmPassword, setConfirmPassword])
+ }, [showConfirm, confirmPassword, setConfirmPassword]);
return (
<>
@@ -158,5 +158,5 @@ export default function SignupForm({
{isSubmitting ? t('signup.creatingAccount') : t('signup.signUp')}
>
- )
+ );
}
\ No newline at end of file
diff --git a/frontend/src/routes/signup/SignupFormValidation.ts b/frontend/src/routes/signup/SignupFormValidation.ts
index 7abe498a0..40c1ad7e7 100644
--- a/frontend/src/routes/signup/SignupFormValidation.ts
+++ b/frontend/src/routes/signup/SignupFormValidation.ts
@@ -1,4 +1,4 @@
-import { useTranslation } from 'react-i18next'
+import { useTranslation } from 'react-i18next';
export interface SignupFieldErrors {
name?: string
@@ -14,7 +14,7 @@ export interface SignupValidationResult {
}
export const useSignupFormValidation = () => {
- const { t } = useTranslation()
+ const { t } = useTranslation();
const validateSignupForm = (
email: string,
@@ -22,45 +22,45 @@ export const useSignupFormValidation = () => {
confirmPassword: string,
name?: string
): SignupValidationResult => {
- const fieldErrors: SignupFieldErrors = {}
+ const fieldErrors: SignupFieldErrors = {};
// Validate name
if (name !== undefined && name !== null && !name.trim()) {
- fieldErrors.name = t('signup.nameRequired', 'Name is required')
+ fieldErrors.name = t('signup.nameRequired', 'Name is required');
}
// Validate email
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!email) {
- fieldErrors.email = t('signup.emailRequired', 'Email is required')
+ fieldErrors.email = t('signup.emailRequired', 'Email is required');
} else if (!emailRegex.test(email)) {
- fieldErrors.email = t('signup.invalidEmail')
+ fieldErrors.email = t('signup.invalidEmail');
}
// Validate password
if (!password) {
- fieldErrors.password = t('signup.passwordRequired', 'Password is required')
+ fieldErrors.password = t('signup.passwordRequired', 'Password is required');
} else if (password.length < 6) {
- fieldErrors.password = t('signup.passwordTooShort')
+ fieldErrors.password = t('signup.passwordTooShort');
}
// Validate confirm password
if (!confirmPassword) {
- fieldErrors.confirmPassword = t('signup.confirmPasswordRequired', 'Please confirm your password')
+ fieldErrors.confirmPassword = t('signup.confirmPasswordRequired', 'Please confirm your password');
} else if (password !== confirmPassword) {
- fieldErrors.confirmPassword = t('signup.passwordsDoNotMatch')
+ fieldErrors.confirmPassword = t('signup.passwordsDoNotMatch');
}
- const hasErrors = Object.keys(fieldErrors).length > 0
+ const hasErrors = Object.keys(fieldErrors).length > 0;
return {
isValid: !hasErrors,
error: null, // Don't show generic error, field errors are more specific
fieldErrors: hasErrors ? fieldErrors : undefined
- }
- }
+ };
+ };
return {
validateSignupForm
- }
-}
\ No newline at end of file
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/tests/missingTranslations.test.ts b/frontend/src/tests/missingTranslations.test.ts
index 908ab1504..53d635e16 100644
--- a/frontend/src/tests/missingTranslations.test.ts
+++ b/frontend/src/tests/missingTranslations.test.ts
@@ -3,7 +3,7 @@ import path from 'path';
import ts from 'typescript';
import { describe, expect, test } from 'vitest';
-const REPO_ROOT = path.join(__dirname, '../../../')
+const REPO_ROOT = path.join(__dirname, '../../../');
const SRC_ROOT = path.join(__dirname, '..');
const EN_GB_FILE = path.join(__dirname, '../../public/locales/en-GB/translation.json');