diff --git a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index dab11c697b..69aa60882f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -1,6 +1,7 @@ package stirling.software.SPDF.config; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -17,6 +18,17 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addInterceptor(endpointInterceptor); } + @Override + public void addCorsMappings(CorsRegistry registry) { + // Allow frontend dev server (Vite on localhost:5173) to access backend + registry.addMapping("/**") + .allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } + // @Override // public void addResourceHandlers(ResourceHandlerRegistry registry) { // // Handler for external static resources - DISABLED in backend-only mode diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 432d52d29d..e53a0e8a5a 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -156,6 +156,13 @@ public class SecurityConfiguration { csrf -> csrf.ignoringRequestMatchers( request -> { + String uri = request.getRequestURI(); + + // Ignore CSRF for auth endpoints + if (uri.startsWith("/api/v1/auth/")) { + return true; + } + String apiKey = request.getHeader("X-API-KEY"); // If there's no API key, don't ignore CSRF // (return false) @@ -254,9 +261,13 @@ public class SecurityConfiguration { || trimmedUri.startsWith("/favicon") || trimmedUri.startsWith( "/api/v1/info/status") - || trimmedUri.startsWith("/api/v1/auth/register") - || trimmedUri.startsWith("/api/v1/auth/login") - || trimmedUri.startsWith("/api/v1/auth/refresh") + || trimmedUri.startsWith( + "/api/v1/auth/register") + || trimmedUri.startsWith( + "/api/v1/auth/login") + || trimmedUri.startsWith( + "/api/v1/auth/refresh") + || trimmedUri.startsWith("/api/v1/auth/me") || trimmedUri.startsWith("/v1/api-docs") || uri.contains("/v1/api-docs"); }) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java index a89352cc3c..ee8dea7a24 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java @@ -26,11 +26,11 @@ import stirling.software.proprietary.security.service.JwtServiceInterface; import stirling.software.proprietary.security.service.UserService; /** - * REST API Controller for authentication operations. - * Replaces Supabase authentication with Spring Security + JWT. + * REST API Controller for authentication operations. Replaces Supabase authentication with Spring + * Security + JWT. * - * This controller provides endpoints matching the Supabase API surface - * to enable seamless frontend integration. + *

This controller provides endpoints matching the Supabase API surface to enable seamless + * frontend integration. */ @RestController @RequestMapping("/api/v1/auth") @@ -52,27 +52,26 @@ public class AuthController { */ @PostMapping("/login") public ResponseEntity login( - @RequestBody LoginRequest request, - HttpServletResponse response) { + @RequestBody LoginRequest request, HttpServletResponse response) { try { - log.debug("Login attempt for user: {}", request.getEmail()); + log.debug("Login attempt for user: {}", request.email()); // Load user - UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail()); + UserDetails userDetails = userDetailsService.loadUserByUsername(request.email()); User user = (User) userDetails; // Validate password - if (!userService.isPasswordCorrect(user, request.getPassword())) { - log.warn("Invalid password for user: {}", request.getEmail()); + if (!userService.isPasswordCorrect(user, request.password())) { + log.warn("Invalid password for user: {}", request.email()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Invalid credentials")); + .body(Map.of("error", "Invalid credentials")); } // Check if user is enabled if (!user.isEnabled()) { - log.warn("Disabled user attempted login: {}", request.getEmail()); + log.warn("Disabled user attempted login: {}", request.email()); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "User account is disabled")); + .body(Map.of("error", "User account is disabled")); } // Generate JWT with claims @@ -85,25 +84,22 @@ public class AuthController { // Set JWT cookie (HttpOnly for security) jwtService.addToken(response, token); - log.info("Login successful for user: {}", request.getEmail()); + log.info("Login successful for user: {}", request.email()); // Return user info (matches Supabase response structure) - return ResponseEntity.ok(Map.of( - "user", buildUserResponse(user), - "session", Map.of( - "access_token", token, - "expires_in", 3600 - ) - )); + return ResponseEntity.ok( + Map.of( + "user", buildUserResponse(user), + "session", Map.of("access_token", token, "expires_in", 3600))); } catch (AuthenticationException e) { - log.error("Authentication failed for user: {}", request.getEmail(), e); + log.error("Authentication failed for user: {}", request.email(), e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Invalid credentials")); + .body(Map.of("error", "Invalid credentials")); } catch (Exception e) { - log.error("Login error for user: {}", request.getEmail(), e); + log.error("Login error for user: {}", request.email(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "Internal server error")); + .body(Map.of("error", "Internal server error")); } } @@ -116,53 +112,57 @@ public class AuthController { @PostMapping("/register") public ResponseEntity register(@RequestBody RegisterRequest request) { try { - log.debug("Registration attempt for user: {}", request.getEmail()); + log.debug("Registration attempt for user: {}", request.email()); // Check if username exists - if (userService.usernameExistsIgnoreCase(request.getEmail())) { - log.warn("Registration failed: username already exists: {}", request.getEmail()); + if (userService.usernameExistsIgnoreCase(request.email())) { + log.warn("Registration failed: username already exists: {}", request.email()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", "User already exists")); + .body(Map.of("error", "User already exists")); } // Validate username format - if (!userService.isUsernameValid(request.getEmail())) { - log.warn("Registration failed: invalid username format: {}", request.getEmail()); + if (!userService.isUsernameValid(request.email())) { + log.warn("Registration failed: invalid username format: {}", request.email()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", "Invalid username format")); + .body(Map.of("error", "Invalid username format")); } // Validate password - if (request.getPassword() == null || request.getPassword().length() < 6) { + if (request.password() == null || request.password().length() < 6) { return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", "Password must be at least 6 characters")); + .body(Map.of("error", "Password must be at least 6 characters")); } // Create user (using default team and USER role) - User user = userService.saveUser( - request.getEmail(), - request.getPassword(), - (Long) null, // team (use default) - Role.USER.getRoleId(), - false // first login not required - ); + User user = + userService.saveUser( + request.email(), + request.password(), + (Long) null, // team (use default) + Role.USER.getRoleId(), + false // first login not required + ); - log.info("User registered successfully: {}", request.getEmail()); + log.info("User registered successfully: {}", request.email()); // Return user info (Note: No session, user must login) - return ResponseEntity.status(HttpStatus.CREATED).body(Map.of( - "user", buildUserResponse(user), - "message", "Account created successfully. Please log in." - )); + return ResponseEntity.status(HttpStatus.CREATED) + .body( + Map.of( + "user", + buildUserResponse(user), + "message", + "Account created successfully. Please log in.")); } catch (IllegalArgumentException e) { log.error("Registration validation error: {}", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", e.getMessage())); + .body(Map.of("error", e.getMessage())); } catch (Exception e) { - log.error("Registration error for user: {}", request.getEmail(), e); + log.error("Registration error for user: {}", request.email(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "Registration failed: " + e.getMessage())); + .body(Map.of("error", "Registration failed: " + e.getMessage())); } } @@ -176,22 +176,22 @@ public class AuthController { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth == null || !auth.isAuthenticated() || auth.getPrincipal().equals("anonymousUser")) { + if (auth == null + || !auth.isAuthenticated() + || auth.getPrincipal().equals("anonymousUser")) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Not authenticated")); + .body(Map.of("error", "Not authenticated")); } UserDetails userDetails = (UserDetails) auth.getPrincipal(); User user = (User) userDetails; - return ResponseEntity.ok(Map.of( - "user", buildUserResponse(user) - )); + return ResponseEntity.ok(Map.of("user", buildUserResponse(user))); } catch (Exception e) { log.error("Get current user error", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "Internal server error")); + .body(Map.of("error", "Internal server error")); } } @@ -217,7 +217,7 @@ public class AuthController { } catch (Exception e) { log.error("Logout error", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(Map.of("error", "Internal server error")); + .body(Map.of("error", "Internal server error")); } } @@ -229,15 +229,13 @@ public class AuthController { * @return New token information */ @PostMapping("/refresh") - public ResponseEntity refresh( - HttpServletRequest request, - HttpServletResponse response) { + public ResponseEntity refresh(HttpServletRequest request, HttpServletResponse response) { try { String token = jwtService.extractToken(request); if (token == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "No token found")); + .body(Map.of("error", "No token found")); } // Validate and extract username @@ -257,15 +255,12 @@ public class AuthController { log.debug("Token refreshed for user: {}", username); - return ResponseEntity.ok(Map.of( - "access_token", newToken, - "expires_in", 3600 - )); + return ResponseEntity.ok(Map.of("access_token", newToken, "expires_in", 3600)); } catch (Exception e) { log.error("Token refresh error", e); return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(Map.of("error", "Token refresh failed")); + .body(Map.of("error", "Token refresh failed")); } } @@ -295,13 +290,9 @@ public class AuthController { // Request/Response DTOs // =========================== - /** - * Login request DTO - */ + /** Login request DTO */ public record LoginRequest(String email, String password) {} - /** - * Registration request DTO - */ + /** Registration request DTO */ public record RegisterRequest(String email, String password, String name) {} -} \ No newline at end of file +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java index faf50832fa..d1e57cc58e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java @@ -75,14 +75,23 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String jwtToken = jwtService.extractToken(request); if (jwtToken == null) { - // Any unauthenticated requests should redirect to /login + // Allow auth endpoints to pass through without JWT String requestURI = request.getRequestURI(); String contextPath = request.getContextPath(); - if (!requestURI.startsWith(contextPath + "/login")) { + // Skip redirect for auth endpoints (they'll handle their own auth checks) + if (!requestURI.startsWith(contextPath + "/login") + && !requestURI.startsWith(contextPath + "/api/v1/auth")) { response.sendRedirect("/login"); return; } + + // For auth endpoints without JWT, continue to the endpoint + // (it will return 401 if needed) + if (requestURI.startsWith(contextPath + "/api/v1/auth")) { + filterChain.doFilter(request, response); + return; + } } try { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index f51a9d5430..5b2b3b2810 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -236,6 +236,9 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { contextPath + "/pdfjs/", contextPath + "/pdfjs-legacy/", contextPath + "/api/v1/info/status", + contextPath + "/api/v1/auth/login", + contextPath + "/api/v1/auth/register", + contextPath + "/api/v1/auth/refresh", contextPath + "/site.webmanifest" }; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java index 8724da9a83..06dd81c951 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java @@ -279,9 +279,12 @@ public class JwtService implements JwtServiceInterface { ResponseCookie.from(JWT_COOKIE_NAME, Newlines.stripAll(token)) .httpOnly(true) .secure(secureCookie) - .sameSite("Strict") + .sameSite("Lax") // Changed from Strict to Lax for cross-port dev + // compatibility .maxAge(EXPIRATION / 1000) .path("/") + .domain("localhost") // Set domain to localhost for cross-port dev + // compatibility .build(); response.addHeader("Set-Cookie", cookie.toString()); @@ -296,6 +299,8 @@ public class JwtService implements JwtServiceInterface { .sameSite("None") .maxAge(0) .path("/") + .domain("localhost") // Set domain to localhost for cross-port dev + // compatibility .build(); response.addHeader("Set-Cookie", cookie.toString()); diff --git a/frontend/public/Login/AddToPDF.png b/frontend/public/Login/AddToPDF.png new file mode 100644 index 0000000000..94e9a0dedf Binary files /dev/null and b/frontend/public/Login/AddToPDF.png differ diff --git a/frontend/public/Login/Firstpage.png b/frontend/public/Login/Firstpage.png new file mode 100644 index 0000000000..f12133f4f7 Binary files /dev/null and b/frontend/public/Login/Firstpage.png differ diff --git a/frontend/public/Login/LoginBackgroundPanel.png b/frontend/public/Login/LoginBackgroundPanel.png new file mode 100644 index 0000000000..4ea0e0ccf1 Binary files /dev/null and b/frontend/public/Login/LoginBackgroundPanel.png differ diff --git a/frontend/public/Login/SecurePDF.png b/frontend/public/Login/SecurePDF.png new file mode 100644 index 0000000000..6184440e92 Binary files /dev/null and b/frontend/public/Login/SecurePDF.png differ diff --git a/frontend/public/Login/apple.svg b/frontend/public/Login/apple.svg new file mode 100644 index 0000000000..b947f4b6bc --- /dev/null +++ b/frontend/public/Login/apple.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/Login/azure.svg b/frontend/public/Login/azure.svg new file mode 100644 index 0000000000..fc1130cbb2 --- /dev/null +++ b/frontend/public/Login/azure.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/public/Login/github.svg b/frontend/public/Login/github.svg new file mode 100644 index 0000000000..651eaac2b8 --- /dev/null +++ b/frontend/public/Login/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/public/Login/google.svg b/frontend/public/Login/google.svg new file mode 100644 index 0000000000..27e4a4ac9c --- /dev/null +++ b/frontend/public/Login/google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/public/Login/microsoft.svg b/frontend/public/Login/microsoft.svg new file mode 100644 index 0000000000..fc1130cbb2 --- /dev/null +++ b/frontend/public/Login/microsoft.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c9736c04de..5b0d061da2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ import { Suspense } from "react"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { Routes, Route } from "react-router-dom"; import { RainbowThemeProvider } from "./components/shared/RainbowThemeProvider"; import { FileContextProvider } from "./contexts/FileContext"; import { NavigationProvider } from "./contexts/NavigationContext"; @@ -52,8 +52,7 @@ const LoadingFallback = () => ( export default function App() { return ( }> - - + @@ -83,16 +82,16 @@ export default function App() { - - - - - - - - - - + + + + + + + + + + } /> @@ -100,7 +99,6 @@ export default function App() { - ); } diff --git a/frontend/src/components/shared/LoginRightCarousel.tsx b/frontend/src/components/shared/LoginRightCarousel.tsx new file mode 100644 index 0000000000..c2ebb29bfa --- /dev/null +++ b/frontend/src/components/shared/LoginRightCarousel.tsx @@ -0,0 +1,159 @@ +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 } + +export default function LoginRightCarousel({ + imageSlides = [], + showBackground = true, + initialSeconds = 5, + slideSeconds = 8, +}: { + imageSlides?: ImageSlide[] + showBackground?: boolean + initialSeconds?: number + slideSeconds?: number +}) { + 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]) + + useEffect(() => { + if (totalSlides <= 1) return + const timeout = setTimeout(() => { + 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) + }, []) + + function TiltImage({ src, alt, enabled, maxDeg = 6 }: { src: string; alt?: string; enabled: boolean; maxDeg?: number }) { + const imgRef = useRef(null) + + useEffect(() => { + const el = imgRef.current + if (!el) return + + 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)` + } else { + el.style.transform = 'translateY(-2rem)' + } + raf = requestAnimationFrame(tick) + } + raf = requestAnimationFrame(tick) + return () => cancelAnimationFrame(raf) + }, [enabled, maxDeg]) + + return ( + {alt + ) + } + + return ( +

+ {showBackground && ( + Background panel + )} + + {/* Image slides */} + {imageSlides.map((s, idx) => ( +
+ {(s.title || s.subtitle) && ( +
+ {s.title && ( +
{s.title}
+ )} + {s.subtitle && ( +
{s.subtitle}
+ )} +
+ )} + + +
+ ))} + + {/* Dot navigation */} +
+ {Array.from({ length: totalSlides }).map((_, i) => ( +
+
+ ) +} diff --git a/frontend/src/components/shared/loginSlides.ts b/frontend/src/components/shared/loginSlides.ts new file mode 100644 index 0000000000..aa3d7f443b --- /dev/null +++ b/frontend/src/components/shared/loginSlides.ts @@ -0,0 +1,43 @@ +import { BASE_PATH } from '../../constants/app'; + +export type LoginCarouselSlide = { + src: string + alt?: string + title?: string + subtitle?: string + cornerModelUrl?: string + followMouseTilt?: boolean + tiltMaxDeg?: number +} + +export const loginSlides: LoginCarouselSlide[] = [ + { + src: `${BASE_PATH}/Login/Firstpage.png`, + alt: 'Stirling PDF overview', + title: 'Your one-stop-shop for all your PDF needs.', + subtitle: + 'A privacy-first cloud suite for PDFs that lets you convert, sign, redact, and manage documents, along with 50+ other powerful tools.', + followMouseTilt: true, + tiltMaxDeg: 5, + }, + { + src: `${BASE_PATH}/Login/AddToPDF.png`, + alt: 'Edit PDFs', + title: 'Edit PDFs to display/secure the information you want', + subtitle: + 'With over a dozen tools to help you redact, sign, read and manipulate PDFs, you will be sure to find what you are looking for.', + followMouseTilt: true, + tiltMaxDeg: 5, + }, + { + src: `${BASE_PATH}/Login/SecurePDF.png`, + alt: 'Secure PDFs', + title: 'Protect sensitive information in your PDFs', + subtitle: + 'Add passwords, redact content, and manage certificates with ease.', + followMouseTilt: true, + tiltMaxDeg: 5, + }, +] + +export default loginSlides diff --git a/frontend/src/routes/Login.tsx b/frontend/src/routes/Login.tsx index f497b8c1d9..df590135d3 100644 --- a/frontend/src/routes/Login.tsx +++ b/frontend/src/routes/Login.tsx @@ -102,8 +102,8 @@ export default function Login() { setError(error.message) } else if (user && session) { console.log('[Login] Email sign in successful') - // Navigate to home page - navigate('/') + // 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) diff --git a/frontend/src/routes/authShared/AuthLayout.tsx b/frontend/src/routes/authShared/AuthLayout.tsx index 28056604d1..e32f629e07 100644 --- a/frontend/src/routes/authShared/AuthLayout.tsx +++ b/frontend/src/routes/authShared/AuthLayout.tsx @@ -1,4 +1,6 @@ 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 { @@ -58,11 +60,7 @@ export default function AuthLayout({ children }: AuthLayoutProps) { {!hideRightPanel && ( -
+ )}
diff --git a/frontend/src/routes/login/EmailPasswordForm.tsx b/frontend/src/routes/login/EmailPasswordForm.tsx index 781cde72eb..c21652535c 100644 --- a/frontend/src/routes/login/EmailPasswordForm.tsx +++ b/frontend/src/routes/login/EmailPasswordForm.tsx @@ -29,8 +29,13 @@ export default function EmailPasswordForm({ }: EmailPasswordFormProps) { const { t } = useTranslation() + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + onSubmit() + } + return ( - <> +
@@ -70,12 +75,12 @@ export default function EmailPasswordForm({
- + ) } diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css index a3b9b0b892..6423728703 100644 --- a/frontend/src/styles/theme.css +++ b/frontend/src/styles/theme.css @@ -262,6 +262,27 @@ --modal-content-bg: #ffffff; --modal-header-border: rgba(0, 0, 0, 0.06); + /* Auth page colors (light mode only - auth pages force light mode) */ + --auth-bg-color-light-only: #f3f4f6; + --auth-card-bg: #ffffff; + --auth-card-bg-light-only: #ffffff; + --auth-label-text-light-only: #374151; + --auth-input-border-light-only: #d1d5db; + --auth-input-bg-light-only: #ffffff; + --auth-input-text-light-only: #111827; + --auth-border-focus-light-only: #3b82f6; + --auth-focus-ring-light-only: rgba(59, 130, 246, 0.1); + --auth-button-bg-light-only: #AF3434; + --auth-button-text-light-only: #ffffff; + --auth-magic-button-bg-light-only: #e5e7eb; + --auth-magic-button-text-light-only: #374151; + --auth-text-primary-light-only: #111827; + --auth-text-secondary-light-only: #6b7280; + --text-divider-rule-rgb-light: 229, 231, 235; + --text-divider-label-rgb-light: 156, 163, 175; + --tool-subcategory-rule-color-light: #e5e7eb; + --tool-subcategory-text-color-light: #9ca3af; + /* PDF Report Colors (always light) */ --pdf-light-header-bg: 239 246 255; --pdf-light-accent: 59 130 246; @@ -480,7 +501,7 @@ /* Tool panel search bar background colors (dark mode) */ --tool-panel-search-bg: #1F2329; --tool-panel-search-border-bottom: #4B525A; - + --information-text-bg: #292e34; --information-text-color: #ececec;