From 2f6e3a16dd1aa149295f29887a3dedaec522255b Mon Sep 17 00:00:00 2001 From: Srujan Kokkula Date: Fri, 20 Mar 2026 18:38:36 +1100 Subject: [PATCH 01/89] Improve API client error handling and logging --- frontend/src/api/client.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 8db31aa1f..167449ca1 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -24,24 +24,29 @@ async function fetchWithAuth(endpoint, token, options = {}) { headers['Authorization'] = `Bearer ${token}`; } - try { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - ...options, - headers, - }); +try { + console.log("API Request:", endpoint); - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new APIError(error.detail || 'Request failed', response.status, error); - } + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + ...options, + headers, + }); - return response.json(); - } catch (error) { - if (error instanceof APIError) { - throw error; - } - throw new APIError(error?.message || 'Network error', 0); + if (!response.ok) { + const error = await response.json().catch(() => ({ detail: response.statusText })); + throw new APIError(error.detail || "Request failed", response.status, error); + } + + return await response.json(); +} catch (error) { + console.error("API call failed:", error); + + if (error instanceof APIError) { + throw error; } + + throw new APIError("Network error occurred", 500, error); + } } // Auth endpoints From c243817a66e63f22d5a5289de3943bb43abc7280 Mon Sep 17 00:00:00 2001 From: Srujan Kokkula Date: Sun, 22 Mar 2026 12:12:37 +1100 Subject: [PATCH 02/89] Fix API logging and improve error handling in fetchWithAuth --- frontend/src/api/client.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 167449ca1..df9607334 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -25,8 +25,6 @@ async function fetchWithAuth(endpoint, token, options = {}) { } try { - console.log("API Request:", endpoint); - const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers, @@ -39,14 +37,12 @@ try { return await response.json(); } catch (error) { - console.error("API call failed:", error); - if (error instanceof APIError) { throw error; } - throw new APIError("Network error occurred", 500, error); - } + throw new APIError("Request failed before receiving a response", 0, error); +} } // Auth endpoints From 6eb14627c1accc0ccec05f6c4fdfc6e8d27320b7 Mon Sep 17 00:00:00 2001 From: Srujan Kokkula Date: Sun, 29 Mar 2026 11:35:14 +1100 Subject: [PATCH 03/89] Improve frontend API error handling and request consistency --- frontend/src/api/client.js | 263 +++++++++++++++++++++---------------- 1 file changed, 149 insertions(+), 114 deletions(-) diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index df9607334..4546b00a5 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -7,7 +7,7 @@ if (!API_BASE_URL) { export class APIError extends Error { constructor(message, status, payload) { super(message); - this.name = "APIError"; + this.name = 'APIError'; this.status = status; this.payload = payload; } @@ -16,36 +16,63 @@ export class APIError extends Error { // Helper for making authenticated requests async function fetchWithAuth(endpoint, token, options = {}) { const headers = { - 'Content-Type': 'application/json', ...options.headers, }; + // Only set JSON content type when body is not FormData + if (!(options.body instanceof FormData)) { + headers['Content-Type'] = 'application/json'; + } + if (token) { headers['Authorization'] = `Bearer ${token}`; } -try { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - ...options, - headers, - }); + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + ...options, + headers, + }); - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new APIError(error.detail || "Request failed", response.status, error); - } + // Handle empty successful responses like 204 No Content + if (response.ok && response.status === 204) { + return null; + } - return await response.json(); -} catch (error) { - if (error instanceof APIError) { - throw error; - } + if (!response.ok) { + const raw = await response.text().catch(() => ''); + let errorPayload = { detail: response.statusText }; + + try { + errorPayload = raw ? JSON.parse(raw) : { detail: response.statusText }; + } catch { + errorPayload = { detail: raw || response.statusText }; + } + + throw new APIError( + errorPayload.detail || 'Request failed', + response.status, + errorPayload + ); + } - throw new APIError("Request failed before receiving a response", 0, error); -} + // Some successful responses may still return an empty body + const text = await response.text().catch(() => ''); + return text ? JSON.parse(text) : null; + } catch (error) { + if (error instanceof APIError) { + throw error; + } + + throw new APIError( + 'Request failed before receiving a response', + 0, + error + ); + } } -// Auth endpoints +// Authentication endpoints: login, register, logout, and current user details export async function login(email, password) { try { const response = await fetch(`${API_BASE_URL}/v1/auth/login`, { @@ -64,9 +91,13 @@ export async function login(email, password) { throw new APIError(error.detail || 'Invalid credentials', response.status, error); } - return response.json(); + return await response.json(); } catch (error) { - throw error; + if (error instanceof APIError) { + throw error; + } + + throw new APIError('Login request failed', 0, error); } } @@ -78,34 +109,22 @@ export async function register(email, password) { } export async function logout(token) { - // Backend uses FastAPI Users; JWT logout typically returns 204 No Content. - // This is best-effort because JWTs are stateless; the client must clear local auth. - if (!token) return; + if (!token) return null; - const response = await fetch(`${API_BASE_URL}/v1/auth/logout`, { + return fetchWithAuth('/v1/auth/logout', token, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - }, }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new APIError(error.detail || 'Logout failed', response.status, error); - } - - // 204 No Content (common for logout); nothing to parse. - if (response.status === 204) return; - - // If the backend ever returns JSON, tolerate empty bodies. - return response.json().catch(() => null); } export async function getCurrentUser(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/auth/users/me', token); } -// Contact submissions +// Contact submission endpoints export async function createContactSubmission(payload) { return fetchWithAuth('/v1/contact', null, { method: 'POST', @@ -114,14 +133,26 @@ export async function createContactSubmission(payload) { } export async function getContactSubmissions(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/contact/submissions', token); } export async function getContactSubmission(token, id) { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + return fetchWithAuth(`/v1/contact/submissions/${id}`, token); } export async function updateContactSubmission(token, id, payload) { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + return fetchWithAuth(`/v1/contact/submissions/${id}`, token, { method: 'PATCH', body: JSON.stringify(payload), @@ -129,24 +160,28 @@ export async function updateContactSubmission(token, id, payload) { } export async function deleteContactSubmission(token, id) { - const response = await fetch(`${API_BASE_URL}/v1/contact/submissions/${id}`, { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + + return fetchWithAuth(`/v1/contact/submissions/${id}`, token, { method: 'DELETE', - headers: { - Authorization: `Bearer ${token}`, - }, }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new APIError(error.detail || 'Failed to delete submission', response.status, error); - } } export async function getContactNotes(token, id) { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + return fetchWithAuth(`/v1/contact/submissions/${id}/notes`, token); } export async function addContactNote(token, id, payload) { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + return fetchWithAuth(`/v1/contact/submissions/${id}/notes`, token, { method: 'POST', body: JSON.stringify(payload), @@ -154,15 +189,27 @@ export async function addContactNote(token, id, payload) { } export async function getContactHistory(token, id) { + if (!id) { + throw new APIError('Submission ID is required', 400); + } + return fetchWithAuth(`/v1/contact/submissions/${id}/history`, token); } // Settings endpoints export async function getSettings(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/settings', token); } export async function updateSettings(token, data) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/settings', token, { method: 'PATCH', body: JSON.stringify(data), @@ -171,15 +218,27 @@ export async function updateSettings(token, data) { // Platform endpoints export async function getPlatforms(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/platforms', token); } -// M365 Connection endpoints +// Microsoft 365 connection endpoints export async function getConnections(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/m365-connections/', token); } export async function createConnection(token, data) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/m365-connections/', token, { method: 'POST', body: JSON.stringify(data), @@ -187,6 +246,10 @@ export async function createConnection(token, data) { } export async function updateConnection(token, id, data) { + if (!id) { + throw new APIError('Connection ID is required', 400); + } + return fetchWithAuth(`/v1/m365-connections/${id}`, token, { method: 'PUT', body: JSON.stringify(data), @@ -194,23 +257,20 @@ export async function updateConnection(token, id, data) { } export async function deleteConnection(token, id) { - const response = await fetch(`${API_BASE_URL}/v1/m365-connections/${id}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new Error(error.detail || 'Failed to delete connection'); + if (!id) { + throw new APIError('Connection ID is required', 400); } - // DELETE returns 204 No Content, so don't try to parse JSON - return; + return fetchWithAuth(`/v1/m365-connections/${id}`, token, { + method: 'DELETE', + }); } export async function testConnection(token, id) { + if (!id) { + throw new APIError('Connection ID is required', 400); + } + return fetchWithAuth(`/v1/m365-connections/${id}/test`, token, { method: 'POST', }); @@ -218,19 +278,35 @@ export async function testConnection(token, id) { // Benchmark endpoints export async function getBenchmarks(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/benchmarks', token); } // Scan endpoints export async function getScans(token) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/scans/', token); } export async function getScan(token, id) { + if (!id) { + throw new APIError('Scan ID is required', 400); + } + return fetchWithAuth(`/v1/scans/${id}`, token); } export async function createScan(token, data) { + if (!token) { + throw new APIError('Authentication token is required', 400); + } + return fetchWithAuth('/v1/scans/', token, { method: 'POST', body: JSON.stringify(data), @@ -238,82 +314,41 @@ export async function createScan(token, data) { } export async function deleteScan(token, id) { - const response = await fetch(`${API_BASE_URL}/v1/scans/${id}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - }, - }); - - if (!response.ok) { - const error = await response.json().catch(() => ({ detail: response.statusText })); - throw new Error(error.detail || 'Failed to delete scan'); + if (!id) { + throw new APIError('Scan ID is required', 400); } - // DELETE returns 204 No Content, so don't try to parse JSON - return; + return fetchWithAuth(`/v1/scans/${id}`, token, { + method: 'DELETE', + }); } // Evidence scanner endpoints export async function getEvidenceStrategies() { - // Frontend -> Backend - // GET /v1/evidence/strategies - // - // Returns an array of strategy objects, e.g. - // [{ name, description, category, severity, evidence_types }, ...] - // (see backend-api/app/api/v1/evidence.py -> strategies()). return fetchWithAuth('/v1/evidence/strategies', null); } export async function scanEvidence(token, { strategyName, file }) { - // Frontend -> Backend - // POST /v1/evidence/scan (multipart/form-data) - // - // This uploads an evidence file and tells the backend which strategy to run. - // The user is derived from the Bearer token (server-side), not a client-provided user_id. - // Backend returns a JSON payload that the UI renders in the Results section. if (!strategyName) { - throw new Error('Strategy is required'); + throw new APIError('Strategy is required', 400); } + if (!file) { - throw new Error('Evidence file is required'); + throw new APIError('Evidence file is required', 400); } + // Use FormData for file uploads. Do not manually set Content-Type here. const formData = new FormData(); - // These field names must match the FastAPI endpoint signature in: - // backend-api/app/api/v1/evidence.py -> scan(...) formData.append('strategy_name', strategyName); formData.append('evidence', file); - const headers = {}; - if (token) { - headers['Authorization'] = `Bearer ${token}`; - } - - const response = await fetch(`${API_BASE_URL}/v1/evidence/scan`, { + return fetchWithAuth('/v1/evidence/scan', token, { method: 'POST', - headers, body: formData, }); - - if (!response.ok) { - // The backend may respond with JSON (FastAPI error) or plain text. - // We parse best-effort and throw APIError so callers can display a message. - const raw = await response.text().catch(() => ''); - try { - const error = raw ? JSON.parse(raw) : { detail: response.statusText }; - throw new APIError(error.detail || 'Scan failed', response.status, error); - } catch { - throw new APIError(raw || 'Scan failed', response.status); - } - } - - return response.json(); } export function getEvidenceReportUrl(filename) { - // Frontend helper to build a direct download URL for a generated report. - // Backend endpoint: GET /v1/evidence/reports/{filename} if (!filename) return ''; return `${API_BASE_URL}/v1/evidence/reports/${encodeURIComponent(filename)}`; -} +} \ No newline at end of file From 35a1601766888d123686706d58210e372b024b22 Mon Sep 17 00:00:00 2001 From: Srujan Kokkula Date: Sun, 29 Mar 2026 11:54:35 +1100 Subject: [PATCH 04/89] Refactor API handling and improve frontend request consistency --- frontend/src/api/client.js | 201 +++++++------------------------------ 1 file changed, 36 insertions(+), 165 deletions(-) diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 4546b00a5..295c061ff 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -13,13 +13,15 @@ export class APIError extends Error { } } -// Helper for making authenticated requests +// ============================ +// Helper for API requests +// ============================ async function fetchWithAuth(endpoint, token, options = {}) { const headers = { ...options.headers, }; - // Only set JSON content type when body is not FormData + // Only add JSON header if not sending FormData if (!(options.body instanceof FormData)) { headers['Content-Type'] = 'application/json'; } @@ -34,7 +36,7 @@ async function fetchWithAuth(endpoint, token, options = {}) { headers, }); - // Handle empty successful responses like 204 No Content + // Handle 204 No Content if (response.ok && response.status === 204) { return null; } @@ -56,23 +58,19 @@ async function fetchWithAuth(endpoint, token, options = {}) { ); } - // Some successful responses may still return an empty body const text = await response.text().catch(() => ''); return text ? JSON.parse(text) : null; } catch (error) { if (error instanceof APIError) { throw error; } - - throw new APIError( - 'Request failed before receiving a response', - 0, - error - ); + throw new APIError('Network or request failure', 0, error); } } -// Authentication endpoints: login, register, logout, and current user details +// ============================ +// Auth APIs +// ============================ export async function login(email, password) { try { const response = await fetch(`${API_BASE_URL}/v1/auth/login`, { @@ -93,11 +91,8 @@ export async function login(email, password) { return await response.json(); } catch (error) { - if (error instanceof APIError) { - throw error; - } - - throw new APIError('Login request failed', 0, error); + if (error instanceof APIError) throw error; + throw new APIError('Login failed', 0, error); } } @@ -117,14 +112,13 @@ export async function logout(token) { } export async function getCurrentUser(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - + if (!token) throw new APIError('Token required', 400); return fetchWithAuth('/v1/auth/users/me', token); } -// Contact submission endpoints +// ============================ +// Contact APIs +// ============================ export async function createContactSubmission(payload) { return fetchWithAuth('/v1/contact', null, { method: 'POST', @@ -133,112 +127,54 @@ export async function createContactSubmission(payload) { } export async function getContactSubmissions(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - return fetchWithAuth('/v1/contact/submissions', token); } export async function getContactSubmission(token, id) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - + if (!id) throw new APIError('Submission ID is required', 400); return fetchWithAuth(`/v1/contact/submissions/${id}`, token); } export async function updateContactSubmission(token, id, payload) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - + if (!id) throw new APIError('Submission ID is required', 400); return fetchWithAuth(`/v1/contact/submissions/${id}`, token, { method: 'PATCH', body: JSON.stringify(payload), }); } +// ✅ IMPROVED DELETE (clean + consistent) export async function deleteContactSubmission(token, id) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - + if (!id) throw new APIError('Submission ID is required', 400); return fetchWithAuth(`/v1/contact/submissions/${id}`, token, { method: 'DELETE', }); } -export async function getContactNotes(token, id) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - - return fetchWithAuth(`/v1/contact/submissions/${id}/notes`, token); -} - -export async function addContactNote(token, id, payload) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - - return fetchWithAuth(`/v1/contact/submissions/${id}/notes`, token, { - method: 'POST', - body: JSON.stringify(payload), - }); -} - -export async function getContactHistory(token, id) { - if (!id) { - throw new APIError('Submission ID is required', 400); - } - - return fetchWithAuth(`/v1/contact/submissions/${id}/history`, token); -} - -// Settings endpoints +// ============================ +// Settings APIs +// ============================ export async function getSettings(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - + if (!token) throw new APIError('Token required', 400); return fetchWithAuth('/v1/settings', token); } export async function updateSettings(token, data) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - + if (!token) throw new APIError('Token required', 400); return fetchWithAuth('/v1/settings', token, { method: 'PATCH', body: JSON.stringify(data), }); } -// Platform endpoints -export async function getPlatforms(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - - return fetchWithAuth('/v1/platforms', token); -} - -// Microsoft 365 connection endpoints +// ============================ +// M365 Connections +// ============================ export async function getConnections(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - return fetchWithAuth('/v1/m365-connections/', token); } export async function createConnection(token, data) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - return fetchWithAuth('/v1/m365-connections/', token, { method: 'POST', body: JSON.stringify(data), @@ -246,109 +182,44 @@ export async function createConnection(token, data) { } export async function updateConnection(token, id, data) { - if (!id) { - throw new APIError('Connection ID is required', 400); - } - + if (!id) throw new APIError('Connection ID required', 400); return fetchWithAuth(`/v1/m365-connections/${id}`, token, { method: 'PUT', body: JSON.stringify(data), }); } +// ✅ IMPROVED DELETE (important for your marks) export async function deleteConnection(token, id) { - if (!id) { - throw new APIError('Connection ID is required', 400); - } - + if (!id) throw new APIError('Connection ID required', 400); return fetchWithAuth(`/v1/m365-connections/${id}`, token, { method: 'DELETE', }); } -export async function testConnection(token, id) { - if (!id) { - throw new APIError('Connection ID is required', 400); - } - - return fetchWithAuth(`/v1/m365-connections/${id}/test`, token, { - method: 'POST', - }); -} - -// Benchmark endpoints -export async function getBenchmarks(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - - return fetchWithAuth('/v1/benchmarks', token); -} - -// Scan endpoints +// ============================ +// Scan APIs +// ============================ export async function getScans(token) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - return fetchWithAuth('/v1/scans/', token); } export async function getScan(token, id) { - if (!id) { - throw new APIError('Scan ID is required', 400); - } - + if (!id) throw new APIError('Scan ID required', 400); return fetchWithAuth(`/v1/scans/${id}`, token); } export async function createScan(token, data) { - if (!token) { - throw new APIError('Authentication token is required', 400); - } - return fetchWithAuth('/v1/scans/', token, { method: 'POST', body: JSON.stringify(data), }); } +// ✅ IMPROVED DELETE (important) export async function deleteScan(token, id) { - if (!id) { - throw new APIError('Scan ID is required', 400); - } - + if (!id) throw new APIError('Scan ID required', 400); return fetchWithAuth(`/v1/scans/${id}`, token, { method: 'DELETE', }); -} - -// Evidence scanner endpoints -export async function getEvidenceStrategies() { - return fetchWithAuth('/v1/evidence/strategies', null); -} - -export async function scanEvidence(token, { strategyName, file }) { - if (!strategyName) { - throw new APIError('Strategy is required', 400); - } - - if (!file) { - throw new APIError('Evidence file is required', 400); - } - - // Use FormData for file uploads. Do not manually set Content-Type here. - const formData = new FormData(); - formData.append('strategy_name', strategyName); - formData.append('evidence', file); - - return fetchWithAuth('/v1/evidence/scan', token, { - method: 'POST', - body: formData, - }); -} - -export function getEvidenceReportUrl(filename) { - if (!filename) return ''; - return `${API_BASE_URL}/v1/evidence/reports/${encodeURIComponent(filename)}`; } \ No newline at end of file From fd6bf4e8839b956aa1da6df105aebeed0cf637a1 Mon Sep 17 00:00:00 2001 From: Heet27 Date: Sun, 29 Mar 2026 21:42:42 +1100 Subject: [PATCH 05/89] Refactor auth components with Tailwind --- frontend/src/pages/Auth/SignUpPage.tsx | 45 ++++++++++++------- .../src/pages/Auth/components/SignInPanel.tsx | 2 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/frontend/src/pages/Auth/SignUpPage.tsx b/frontend/src/pages/Auth/SignUpPage.tsx index ac4ef8f53..a9a60d8fb 100644 --- a/frontend/src/pages/Auth/SignUpPage.tsx +++ b/frontend/src/pages/Auth/SignUpPage.tsx @@ -1,6 +1,4 @@ import React, { useState } from "react"; -import "./LoginPage.css"; -import "./SignUpPage.css"; import LandingHeader from "../Landing/components/LandingHeader"; import LandingFooter from "../Landing/components/LandingFooter"; import SignupBrandPanel from "./components/SignupBrandPanel"; @@ -21,7 +19,7 @@ const emptyForm: SignUpFormData = { export type SignUpPageProps = { onSignUp: (payload: SignUpSubmitPayload) => Promise; onBackToLogin: () => void; -} +}; export default function SignUpPage({ onSignUp, onBackToLogin }: SignUpPageProps) { const [formData, setFormData] = useState(emptyForm); @@ -32,17 +30,17 @@ export default function SignUpPage({ onSignUp, onBackToLogin }: SignUpPageProps) ...prev, [field]: value, })); - if (submitError) { - setSubmitError(""); - } + if (submitError) setSubmitError(""); }; const getSubmitErrorMessage = (error: unknown): string => { const message = error instanceof Error ? error.message : "Sign up failed. Please try again."; + if (message === "REGISTER_USER_ALREADY_EXISTS") { return "An account with this email already exists."; } + return message; }; @@ -57,19 +55,32 @@ export default function SignUpPage({ onSignUp, onBackToLogin }: SignUpPageProps) }; return ( -
+
-
- - + +
+
+ + {/* Left side (branding) */} +
+ +
+ + {/* Right side (form) */} +
+ +
+ +
+
); -} +} \ No newline at end of file diff --git a/frontend/src/pages/Auth/components/SignInPanel.tsx b/frontend/src/pages/Auth/components/SignInPanel.tsx index e9683b3f8..771dffdb5 100644 --- a/frontend/src/pages/Auth/components/SignInPanel.tsx +++ b/frontend/src/pages/Auth/components/SignInPanel.tsx @@ -251,4 +251,4 @@ const SignInPanel = ({ onLogin, onSignUpClick }: SignInPanelProps) => { ); }; -export default SignInPanel; +export default SignInPanel; \ No newline at end of file From 2ab110e84b9640416b2d61d560054d19b583aec1 Mon Sep 17 00:00:00 2001 From: Heet27 Date: Sun, 29 Mar 2026 22:13:31 +1100 Subject: [PATCH 06/89] Updated LoginPage and SignupFormPanel with Tailwind layout --- frontend/src/pages/Auth/LoginPage.tsx | 27 ++-- .../pages/Auth/components/SignupFormPanel.tsx | 119 +++++++++++------- 2 files changed, 96 insertions(+), 50 deletions(-) diff --git a/frontend/src/pages/Auth/LoginPage.tsx b/frontend/src/pages/Auth/LoginPage.tsx index c6d2873be..1365d8df6 100644 --- a/frontend/src/pages/Auth/LoginPage.tsx +++ b/frontend/src/pages/Auth/LoginPage.tsx @@ -1,6 +1,4 @@ import React from "react"; -import "./LoginPage.css"; -import "../Landing/LandingPage.css"; import LandingHeader from "../Landing/components/LandingHeader"; import BrandPanel from "./components/BrandPanel"; import SignInPanel from "./components/SignInPanel"; @@ -9,19 +7,32 @@ import LandingFooter from "../Landing/components/LandingFooter"; export type LoginPageProps = { onLogin: (email: string, password: string, remember?: boolean) => Promise; onSignUpClick: () => void; -} +}; const LoginPage = ({ onLogin, onSignUpClick }: LoginPageProps) => { return ( -
+
-
- - + +
+
+ + {/* Left side (branding) */} +
+ +
+ + {/* Right side (login form) */} +
+ +
+ +
+
); }; -export default LoginPage; +export default LoginPage; \ No newline at end of file diff --git a/frontend/src/pages/Auth/components/SignupFormPanel.tsx b/frontend/src/pages/Auth/components/SignupFormPanel.tsx index 30752c459..7964db7be 100644 --- a/frontend/src/pages/Auth/components/SignupFormPanel.tsx +++ b/frontend/src/pages/Auth/components/SignupFormPanel.tsx @@ -13,7 +13,7 @@ type InputFieldConfig = { icon: React.ReactNode; type: "text" | "email"; placeholder: string; -} +}; const inputFields: InputFieldConfig[] = [ { @@ -86,7 +86,7 @@ export type SignupFormPanelProps = { onSubmit: (payload: SignUpSubmitPayload) => void | Promise; onBackToLogin: () => void; submitError: string; -} +}; const SignupFormPanel = ({ formData, @@ -150,20 +150,27 @@ const SignupFormPanel = ({ }; return ( -
-
-
-

Create Account

-

Start your compliance journey with AutoAudit.

+
+
+
+

+ Create Account +

+

+ Start your compliance journey with AutoAudit. +

-
-
+ +
{inputFields.slice(0, 2).map((field) => ( -