mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
style(frontend): enforce semicolons across auth & shared components (#4737)
# Description of Changes This pull request primarily focuses on code style improvements across several frontend files, standardizing the use of semicolons and ensuring consistent formatting. No functional or logic changes are introduced; the updates are purely syntactic to improve code readability and maintainability. **Code Style and Formatting Improvements:** * Added missing semicolons and standardized import statements in multiple files, including `DividerWithText.tsx`, `LoginRightCarousel.tsx`, `loginSlides.ts`, `AuthCallback.tsx`, `Landing.tsx`, `Login.tsx`, and `Signup.tsx`. [[1]](diffhunk://#diff-5de1b22e63fe3b6c9781c2a476db7440818f18d2aeb5c6c1ddeb446517cf001fL1-R1) [[2]](diffhunk://#diff-7cc961105816564bebd8656fe59119970d5859b4557f48c37fe920d344a948c3L1-R1) [[3]](diffhunk://#diff-1fc806abd10f8882945f54b56828db4c4b9a8b986743250b26dd9bdf0ec49bdbL41-R43) [[4]](diffhunk://#diff-540ce2405611334ce0bdff1f48d187218be99ce64fb92f054b9cf5a71cb1ed8cL1-R3) [[5]](diffhunk://#diff-d55dde4f28998eb9b30f332a1c96a4c79ec6a70b568bb51eea81d11a3715c35cL1-R5) [[6]](diffhunk://#diff-183a38f7c78b7c2950c4bed87ff2843de146d960e28591865d91c3cd86c3fadbL1-R39) [[7]](diffhunk://#diff-0a98c2e661e58f226f98c90b2e82198090b9fd986bbd98c2af6574d19f2ee37aL1-R25) * Updated function bodies and return statements to use consistent semicolon placement and code formatting throughout the affected files. [[1]](diffhunk://#diff-5de1b22e63fe3b6c9781c2a476db7440818f18d2aeb5c6c1ddeb446517cf001fL13-R15) [[2]](diffhunk://#diff-5de1b22e63fe3b6c9781c2a476db7440818f18d2aeb5c6c1ddeb446517cf001fL27-R35) [[3]](diffhunk://#diff-7cc961105816564bebd8656fe59119970d5859b4557f48c37fe920d344a948c3L17-R63) [[4]](diffhunk://#diff-7cc961105816564bebd8656fe59119970d5859b4557f48c37fe920d344a948c3L82-R82) [[5]](diffhunk://#diff-7cc961105816564bebd8656fe59119970d5859b4557f48c37fe920d344a948c3L158-R158) [[6]](diffhunk://#diff-540ce2405611334ce0bdff1f48d187218be99ce64fb92f054b9cf5a71cb1ed8cL13-R56) [[7]](diffhunk://#diff-540ce2405611334ce0bdff1f48d187218be99ce64fb92f054b9cf5a71cb1ed8cL72-R72) [[8]](diffhunk://#diff-d55dde4f28998eb9b30f332a1c96a4c79ec6a70b568bb51eea81d11a3715c35cL15-R26) [[9]](diffhunk://#diff-d55dde4f28998eb9b30f332a1c96a4c79ec6a70b568bb51eea81d11a3715c35cL39-R61) [[10]](diffhunk://#diff-183a38f7c78b7c2950c4bed87ff2843de146d960e28591865d91c3cd86c3fadbL51-R118) [[11]](diffhunk://#diff-183a38f7c78b7c2950c4bed87ff2843de146d960e28591865d91c3cd86c3fadbL188-R188) [[12]](diffhunk://#diff-0a98c2e661e58f226f98c90b2e82198090b9fd986bbd98c2af6574d19f2ee37aL1-R25) No business logic, UI, or feature behavior has been changed as part of this update. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
This commit is contained in:
parent
c67859a1ff
commit
3e9c55243e
@ -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 =
|
||||
<span className="text-divider__label">{text}</span>
|
||||
<div className="text-divider__rule" />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -32,5 +32,5 @@ export default function DividerWithText({ text, className = '', style, variant =
|
||||
className={`h-px my-2.5 ${themeClass} ${className}`}
|
||||
style={styleWithOpacity}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<HTMLImageElement | null>(null)
|
||||
const imgRef = useRef<HTMLImageElement | null>(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 (
|
||||
<img
|
||||
@ -79,7 +79,7 @@ export default function LoginRightCarousel({
|
||||
transformOrigin: '50% 50%',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -155,5 +155,5 @@ export default function LoginRightCarousel({
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,6 +38,6 @@ export const loginSlides: LoginCarouselSlide[] = [
|
||||
followMouseTilt: true,
|
||||
tiltMaxDeg: 5,
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
export default loginSlides
|
||||
export default loginSlides;
|
||||
|
||||
@ -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 (
|
||||
<div style={{
|
||||
@ -69,5 +69,5 @@ export default function AuthCallback() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If login is disabled, show app directly (anonymous mode)
|
||||
if (config?.enableLogin === false) {
|
||||
console.debug('[Landing] Login disabled - showing app in anonymous mode')
|
||||
return <HomePage />
|
||||
console.debug('[Landing] Login disabled - showing app in anonymous mode');
|
||||
return <HomePage />;
|
||||
}
|
||||
|
||||
// If we have a session, show the main app
|
||||
if (session) {
|
||||
return <HomePage />
|
||||
return <HomePage />;
|
||||
}
|
||||
|
||||
// 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 <Login />
|
||||
return <Login />;
|
||||
}
|
||||
|
||||
// For non-home routes without auth, navigate to login (preserves from location)
|
||||
return <Navigate to="/login" replace state={{ from: location }} />
|
||||
return <Navigate to="/login" replace state={{ from: location }} />;
|
||||
}
|
||||
|
||||
@ -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<string | null>(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<string | null>(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 <LoggedInState />
|
||||
return <LoggedInState />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<AuthLayout>
|
||||
@ -185,5 +185,5 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
</AuthLayout>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<string | null>(null)
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const [fieldErrors, setFieldErrors] = useState<SignupFieldErrors>({})
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [isSigningUp, setIsSigningUp] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [fieldErrors, setFieldErrors] = useState<SignupFieldErrors>({});
|
||||
|
||||
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 (
|
||||
<AuthLayout>
|
||||
@ -101,5 +101,5 @@ export default function Signup() {
|
||||
</button>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<HTMLDivElement | null>(null)
|
||||
const [hideRightPanel, setHideRightPanel] = useState(false)
|
||||
const cardRef = useRef<HTMLDivElement | null>(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 (
|
||||
<div className={styles.authContainer}>
|
||||
@ -64,5 +64,5 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<form onSubmit={handleSubmit}>
|
||||
@ -82,5 +82,5 @@ export default function EmailPasswordForm({
|
||||
{submitButtonText}
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@ interface ErrorMessageProps {
|
||||
}
|
||||
|
||||
export default function ErrorMessage({ error }: ErrorMessageProps) {
|
||||
if (!error) return null
|
||||
if (!error) return null;
|
||||
|
||||
return (
|
||||
<div className="error-message">
|
||||
<p className="error-message-text">{error}</p>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div style={{
|
||||
@ -50,5 +50,5 @@ export default function LoggedInState() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -18,5 +18,5 @@ export default function LoginHeader({ title, subtitle }: LoginHeaderProps) {
|
||||
<p className="login-subtitle">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -15,5 +15,5 @@ export default function NavigationLink({ onClick, text, isDisabled = false }: Na
|
||||
{text}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 = '
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (layout === 'grid') {
|
||||
@ -56,7 +56,7 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -74,5 +74,5 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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')}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -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');
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user