From 068142ed619085f0e435dc34ecbd5388d0037229 Mon Sep 17 00:00:00 2001 From: debu-2 Date: Wed, 7 Oct 2026 03:36:56 +0530 Subject: [PATCH] setting, session and profile fix --- frontend/src/App.jsx | 36 +++++++-- frontend/src/auth.js | 23 +++++- frontend/src/components/AccountProfile.jsx | 86 +++++++++++++--------- frontend/src/components/Brand.jsx | 4 +- frontend/src/components/DashboardPage.jsx | 6 +- frontend/src/components/LoginPage.jsx | 10 +-- frontend/src/styles.css | 35 +++++---- 7 files changed, 134 insertions(+), 66 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f2681a5..9dca954 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,3 +1,4 @@ +import { useEffect, useLayoutEffect, useState } from "react"; import DashboardPage from "./components/DashboardPage"; import ForgotPasswordPage from "./components/ForgotPasswordPage"; import InviteAcceptPage from "./components/InviteAcceptPage"; @@ -6,24 +7,47 @@ import MobileLanding from "./components/MobileLanding"; import SignupPage from "./components/SignupPage"; import SmtpCredentialsPage from "./components/SmtpCredentialsPage"; import WebLanding from "./components/WebLanding"; +import { APP_HOME_PATH, currentPath, enterApp, isLoggedIn, SESSION_GATED_PATHS } from "./auth"; + +function useLocationPath() { + const [path, setPath] = useState(currentPath); + useEffect(() => { + function sync() { + setPath(currentPath()); + } + window.addEventListener("popstate", sync); + return () => window.removeEventListener("popstate", sync); + }, []); + return path; +} export default function App() { - if (window.location.pathname === "/app") { + const path = useLocationPath(); + const authed = isLoggedIn(); + const showApp = path === APP_HOME_PATH || (authed && SESSION_GATED_PATHS.has(path)); + + useLayoutEffect(() => { + if (authed && SESSION_GATED_PATHS.has(currentPath())) { + enterApp(); + } + }, [authed, path]); + + if (showApp) { return ; } - if (window.location.pathname === "/login") { + if (path === "/login") { return ; } - if (window.location.pathname === "/forgot-password") { + if (path === "/forgot-password") { return ; } - if (window.location.pathname.startsWith("/invite/")) { + if (path.startsWith("/invite/")) { return ; } - if (window.location.pathname === "/signup") { + if (path === "/signup") { return ; } - if (window.location.pathname === "/smtp-credentials") { + if (path === "/smtp-credentials") { return ; } return ( diff --git a/frontend/src/auth.js b/frontend/src/auth.js index 1fa85ca..da5dcf4 100644 --- a/frontend/src/auth.js +++ b/frontend/src/auth.js @@ -1,6 +1,9 @@ export const AUTH_TOKEN_KEY = "agmail_access_token"; export const AUTH_ACCOUNT_KEY = "agmail_account"; +export const APP_HOME_PATH = "/app"; +export const SESSION_GATED_PATHS = new Set(["/", "/login", "/signup", "/forgot-password"]); + let sessionRedirecting = false; export function getAccessToken() { @@ -16,8 +19,25 @@ export function clearSession() { sessionStorage.removeItem(AUTH_ACCOUNT_KEY); } +export function currentPath() { + const path = window.location.pathname; + if (!path || path === "/") return "/"; + return path.replace(/\/+$/, "") || "/"; +} + +export function enterApp(mode = "replace") { + if (currentPath() === APP_HOME_PATH && !window.location.search && !window.location.hash) { + return; + } + if (mode === "assign") { + window.location.assign(APP_HOME_PATH); + return; + } + window.history.replaceState(null, "", APP_HOME_PATH); +} + export function getBrandHomeHref() { - return isLoggedIn() ? "/app" : "/"; + return isLoggedIn() ? APP_HOME_PATH : "/"; } export function redirectSessionExpired() { @@ -26,3 +46,4 @@ export function redirectSessionExpired() { clearSession(); window.location.replace("/?session=expired"); } + diff --git a/frontend/src/components/AccountProfile.jsx b/frontend/src/components/AccountProfile.jsx index 763c5af..2f02818 100644 --- a/frontend/src/components/AccountProfile.jsx +++ b/frontend/src/components/AccountProfile.jsx @@ -1,4 +1,3 @@ -import { Camera, UserRound } from "lucide-react"; import { useRef, useState } from "react"; import { updateAccountProfile } from "../api"; @@ -9,10 +8,18 @@ function accountInitials(account) { return `${account?.first_name?.[0] || ""}${account?.last_name?.[0] || ""}`.toUpperCase() || "AG"; } +function displayPhone(phone) { + if (!phone) return "Not set"; + return phone.replace(/^\+/, ""); +} + export default function AccountProfile({ account, onUpdated }) { const token = sessionStorage.getItem("agmail_access_token"); const inputRef = useRef(null); const [status, setStatus] = useState({ loading: false, error: "", notice: "" }); + const fullName = `${account?.first_name || ""} ${account?.last_name || ""}`.trim(); + const isCompany = account?.account_type === "company"; + const hasPhoto = Boolean(account?.profile_picture_url); async function handleFileChange(event) { const file = event.target.files?.[0]; @@ -39,7 +46,7 @@ export default function AccountProfile({ account, onUpdated }) { const updated = await updateAccountProfile({ profile_picture_url: dataUrl }, token); sessionStorage.setItem("agmail_account", JSON.stringify(updated)); onUpdated?.(updated); - setStatus({ loading: false, error: "", notice: "Profile picture updated." }); + setStatus({ loading: false, error: "", notice: "Saved" }); } catch (error) { setStatus({ loading: false, error: error.message, notice: "" }); } @@ -51,7 +58,7 @@ export default function AccountProfile({ account, onUpdated }) { const updated = await updateAccountProfile({ profile_picture_url: null }, token); sessionStorage.setItem("agmail_account", JSON.stringify(updated)); onUpdated?.(updated); - setStatus({ loading: false, error: "", notice: "Profile picture removed." }); + setStatus({ loading: false, error: "", notice: "Saved" }); } catch (error) { setStatus({ loading: false, error: error.message, notice: "" }); } @@ -60,42 +67,48 @@ export default function AccountProfile({ account, onUpdated }) { return (
-
-

Account

-

Profile

-

Update how you appear across Gentic.

-
+

{isCompany ? "Company workspace" : "Personal workspace"}

+

Profile

+

Manage your personal information and how you appear across Gentic.

+

+ {isCompany ? "Company account" : "Personal account"} · {isCompany ? "Company" : "Individual"} +

-
-
+
+
- {account?.profile_picture_url ? ( + {hasPhoto ? ( ) : ( {accountInitials(account)} )}
-
- - {account?.profile_picture_url && ( +
+ {fullName} +
- )} + {hasPhoto && ( + <> + + + + )} +
- {status.notice &&

{status.notice}

} - {status.error &&

{status.error}

} +
+ {status.error &&

{status.error}

} + {status.notice && !status.error && ( +

{status.notice}

+ )} + {status.loading && !status.notice && ( +

Saving…

+ )} +
); diff --git a/frontend/src/components/Brand.jsx b/frontend/src/components/Brand.jsx index 32b8314..05f7b5d 100644 --- a/frontend/src/components/Brand.jsx +++ b/frontend/src/components/Brand.jsx @@ -4,9 +4,9 @@ export default function Brand({ compact = false, forceHome = false }) { const href = forceHome ? "/app" : getBrandHomeHref(); function handleClick(event) { - if (!forceHome) return; + if (!forceHome && getBrandHomeHref() !== "/app") return; event.preventDefault(); - window.location.assign("/app"); + window.location.replace("/app"); } return ( diff --git a/frontend/src/components/DashboardPage.jsx b/frontend/src/components/DashboardPage.jsx index d1b98bf..372932b 100644 --- a/frontend/src/components/DashboardPage.jsx +++ b/frontend/src/components/DashboardPage.jsx @@ -178,7 +178,7 @@ export default function DashboardPage() { function logout() { clearSession(); - window.location.assign("/login"); + window.location.replace("/login"); } function closeMobileMenu() { @@ -506,7 +506,7 @@ export default function DashboardPage() { {workspaceLabel} )} - {!companyActive && !["Inbox", "Outbox", "Trash"].includes(active) && ( + {!companyActive && !["Inbox", "Outbox", "Trash", "Profile", "Settings"].includes(active) && (

{workspaceTitle}

{active}

{workspaceLabel} @@ -609,7 +609,7 @@ export default function DashboardPage() { {(active === "Settings" || companyActive === "Settings") && ( )} - {!companyActive && !["Inbox", "Outbox", "Trash", "Settings"].includes(active) && ( + {!companyActive && !["Inbox", "Outbox", "Trash", "Settings", "Profile"].includes(active) && (

{active} is ready for you

diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx index 11d5c6b..1e18add 100644 --- a/frontend/src/components/LoginPage.jsx +++ b/frontend/src/components/LoginPage.jsx @@ -13,12 +13,6 @@ export default function LoginPage() { const [challenge, setChallenge] = useState(null); const [otpCooldown, setOtpCooldown] = useState(0); - useEffect(() => { - if (isLoggedIn()) { - window.location.replace("/app"); - } - }, []); - useEffect(() => { if (!otpCooldown) return undefined; const timer = window.setInterval(() => { @@ -34,6 +28,10 @@ export default function LoginPage() { return true; }, [status.loading, email, password, challenge, otpValid]); + if (isLoggedIn()) { + return null; + } + async function handleSubmit(event) { event.preventDefault(); setStatus({ loading: true, error: "" }); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 4491b80..24ef4b9 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1421,24 +1421,31 @@ body.compose-blank-open .mailbox-empty-cta { opacity:.42; filter:saturate(.55); .workspace-account-menu button:hover { background:#f7f5fa; } .workspace-account-menu button.danger { color:#b54f3d; } .workspace-account-menu button.danger:hover { background:#fdeeed; } -.account-profile-page { max-width:720px; } +.account-profile-page { width:min(100%,800px); } +.account-profile-head { margin-bottom:22px; padding-bottom:18px; border-bottom:1px solid #ece9ee; } .account-profile-head h1 { margin:4px 0 0; font:700 clamp(28px,3vw,34px)/1.15 "Manrope"; letter-spacing:-.04em; } -.account-profile-head p { margin:8px 0 0; color:#77758a; font-size:13px; } -.account-profile-eyebrow { margin:0; color:#7357f6; font-size:10px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; } -.account-profile-card { margin-top:18px; padding:20px; border:1px solid #ece9ee; border-radius:16px; background:#fff; box-shadow:0 1px 2px rgba(20,18,30,.04); } -.account-profile-avatar-block { display:flex; align-items:center; gap:16px; padding-bottom:18px; border-bottom:1px solid #f0eef2; } -.account-profile-avatar { width:72px; height:72px; display:grid; place-items:center; border-radius:50%; overflow:hidden; background:linear-gradient(180deg,#ff9a82,#ff6f57); color:#2a2038; font-size:22px; font-weight:700; } +.account-profile-head > p { margin:8px 0 0; color:#77758a; font-size:13px; line-height:1.45; } +.account-profile-eyebrow { margin:0; color:#8a84a0; font-size:10px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; } +.account-profile-meta { margin:10px 0 0; color:#6f6b76; font-size:12px; font-weight:600; } +.account-profile-section { padding:2px 0 8px; } +.account-profile-identity { display:flex; align-items:center; gap:14px; padding-bottom:16px; margin-bottom:4px; border-bottom:1px solid #f0eef2; } +.account-profile-avatar { width:64px; height:64px; flex:0 0 64px; display:grid; place-items:center; border-radius:50%; overflow:hidden; background:linear-gradient(180deg,#ff9a82,#ff6f57); color:#2a2038; font-size:18px; font-weight:700; } .account-profile-avatar img { width:100%; height:100%; object-fit:cover; } -.account-profile-avatar-actions { display:flex; flex-wrap:wrap; gap:8px; } -.account-profile-btn { height:36px; padding:0 12px; display:inline-flex; align-items:center; gap:8px; border:1px solid #e4e1e8; border-radius:10px; background:#fff; color:#3d3948; font-size:12px; font-weight:600; } -.account-profile-btn.subtle { color:#77758a; } -.account-profile-btn:hover:not(:disabled) { background:#f7f5fa; } -.account-profile-btn:disabled { opacity:.55; cursor:wait; } -.account-profile-fields { margin-top:18px; display:grid; gap:14px; } +.account-profile-identity-copy { min-width:0; display:grid; gap:4px; } +.account-profile-identity-copy strong { font-size:15px; font-weight:700; color:#171424; } +.account-profile-identity-actions { display:flex; align-items:center; gap:8px; } +.account-profile-link { padding:0; border:0; background:transparent; color:#c95945; font-size:12px; font-weight:700; } +.account-profile-link.subtle { color:#7a7580; font-weight:600; } +.account-profile-link:hover:not(:disabled) { text-decoration:underline; } +.account-profile-link:disabled { opacity:.55; cursor:wait; } +.account-profile-fields { margin-top:16px; display:grid; gap:16px; } .account-profile-fields label { display:grid; gap:6px; } .account-profile-fields label>span { color:#77758a; font-size:11px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; } -.account-profile-readonly { min-height:42px; padding:0 12px; display:flex; align-items:center; gap:8px; border:1px solid #ece9ee; border-radius:10px; background:#faf9fc; color:#3d3948; font-size:13px; } -.account-profile-notice { margin:14px 0 0; color:#3d8a5d; font-size:12px; } +.account-profile-fields input { width:100%; min-height:42px; padding:0 12px; border:1px solid #ece9ee; border-radius:10px; background:#f6f5f8; color:#3d3948; font-size:13px; } +.account-profile-fields input:disabled { color:#4a4652; cursor:default; opacity:1; } +.account-profile-fields small { color:#8c8790; font-size:11px; font-weight:500; } +.account-profile-footer { min-height:20px; margin-top:14px; display:flex; justify-content:flex-end; } +.account-profile-saved { margin:0; color:#3d8a5d; font-size:12px; font-weight:600; } .workspace-account div { min-width:0; display:grid; text-align:left; } .workspace-account strong,.workspace-account small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .workspace-account strong { color:#241f2b; font-size:12px; font-weight:600; }