From 205964841b122400568e54cc3e9129860af6498d Mon Sep 17 00:00:00 2001 From: debu-2 Date: Wed, 7 Oct 2026 06:39:07 +0530 Subject: [PATCH] setting, session and profile fix --- frontend/src/App.jsx | 4 +- frontend/src/auth.js | 94 +++++++++++++++--------- frontend/src/components/ComposeModal.jsx | 77 ++++++++++++++++++- frontend/src/styles.css | 1 + 4 files changed, 137 insertions(+), 39 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index bdfabf2..f3b5840 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,7 +7,7 @@ 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, onSessionChange, SESSION_GATED_PATHS } from "./auth"; +import { APP_HOME_PATH, currentPath, enterApp, followSharedSession, isLoggedIn, onSessionChange, SESSION_GATED_PATHS } from "./auth"; function useLocationPath() { const [path, setPath] = useState(currentPath); @@ -33,7 +33,7 @@ export default function App() { }, [authed, path]); useEffect(() => onSessionChange(() => { - window.location.reload(); + followSharedSession(); }), []); if (showApp) { diff --git a/frontend/src/auth.js b/frontend/src/auth.js index 6082de0..5ab52a6 100644 --- a/frontend/src/auth.js +++ b/frontend/src/auth.js @@ -2,48 +2,48 @@ export const AUTH_TOKEN_KEY = "agmail_access_token"; export const AUTH_ACCOUNT_KEY = "agmail_account"; export const APP_HOME_PATH = "/app"; +export const LOGIN_PATH = "/login"; export const SESSION_GATED_PATHS = new Set(["/", "/login", "/signup", "/forgot-password"]); +const AUTH_CHANNEL = "agmail-auth"; let sessionRedirecting = false; +let authChannel = null; + +function sharedStore() { + return localStorage; +} function readShared(key) { try { - return localStorage.getItem(key); + return sharedStore().getItem(key); } catch { return null; } } -function writeShared(key, value) { - localStorage.setItem(key, value); - sessionStorage.setItem(key, value); +function dropTabCopies() { + sessionStorage.removeItem(AUTH_TOKEN_KEY); + sessionStorage.removeItem(AUTH_ACCOUNT_KEY); } -function removeShared(key) { - localStorage.removeItem(key); - sessionStorage.removeItem(key); +dropTabCopies(); + +function channel() { + if (authChannel || typeof BroadcastChannel === "undefined") return authChannel; + authChannel = new BroadcastChannel(AUTH_CHANNEL); + return authChannel; } -function syncSharedSession() { - const sharedToken = readShared(AUTH_TOKEN_KEY); - if (sharedToken) { - sessionStorage.setItem(AUTH_TOKEN_KEY, sharedToken); - const sharedAccount = readShared(AUTH_ACCOUNT_KEY); - if (sharedAccount) sessionStorage.setItem(AUTH_ACCOUNT_KEY, sharedAccount); - return; +function publish(type) { + try { + channel()?.postMessage({ type }); + } catch { + /* private browsing can reject BroadcastChannel */ } - - const legacyToken = sessionStorage.getItem(AUTH_TOKEN_KEY); - if (!legacyToken) return; - localStorage.setItem(AUTH_TOKEN_KEY, legacyToken); - const legacyAccount = sessionStorage.getItem(AUTH_ACCOUNT_KEY); - if (legacyAccount) localStorage.setItem(AUTH_ACCOUNT_KEY, legacyAccount); } -syncSharedSession(); - export function getAccessToken() { - return readShared(AUTH_TOKEN_KEY) || sessionStorage.getItem(AUTH_TOKEN_KEY); + return readShared(AUTH_TOKEN_KEY); } export function isLoggedIn() { @@ -51,27 +51,55 @@ export function isLoggedIn() { } export function saveSession(token, account) { - writeShared(AUTH_TOKEN_KEY, token); - if (account) writeShared(AUTH_ACCOUNT_KEY, JSON.stringify(account)); + sharedStore().setItem(AUTH_TOKEN_KEY, token); + if (account) sharedStore().setItem(AUTH_ACCOUNT_KEY, JSON.stringify(account)); + dropTabCopies(); + publish("login"); } export function saveStoredAccount(account) { - writeShared(AUTH_ACCOUNT_KEY, JSON.stringify(account)); + sharedStore().setItem(AUTH_ACCOUNT_KEY, JSON.stringify(account)); } export function clearSession() { - removeShared(AUTH_TOKEN_KEY); - removeShared(AUTH_ACCOUNT_KEY); + sharedStore().removeItem(AUTH_TOKEN_KEY); + sharedStore().removeItem(AUTH_ACCOUNT_KEY); + dropTabCopies(); + publish("logout"); } export function onSessionChange(listener) { - function handle(event) { + function handleStorage(event) { if (event.storageArea !== localStorage) return; - if (event.key !== AUTH_TOKEN_KEY && event.key !== AUTH_ACCOUNT_KEY && event.key !== null) return; - listener(event); + if (event.key !== AUTH_TOKEN_KEY && event.key !== null) return; + listener(event.newValue || null); } - window.addEventListener("storage", handle); - return () => window.removeEventListener("storage", handle); + function handleBroadcast(event) { + if (event.data?.type === "logout") listener(null); + if (event.data?.type === "login") listener(getAccessToken()); + } + window.addEventListener("storage", handleStorage); + channel()?.addEventListener("message", handleBroadcast); + return () => { + window.removeEventListener("storage", handleStorage); + channel()?.removeEventListener("message", handleBroadcast); + }; +} + +export function followSharedSession() { + if (sessionRedirecting) return; + if (isLoggedIn()) { + if (currentPath() !== APP_HOME_PATH) { + sessionRedirecting = true; + window.location.replace(APP_HOME_PATH); + } else { + window.location.reload(); + } + return; + } + if (currentPath() === LOGIN_PATH) return; + sessionRedirecting = true; + window.location.replace(LOGIN_PATH); } export function currentPath() { diff --git a/frontend/src/components/ComposeModal.jsx b/frontend/src/components/ComposeModal.jsx index 140626b..6bed8ed 100644 --- a/frontend/src/components/ComposeModal.jsx +++ b/frontend/src/components/ComposeModal.jsx @@ -80,6 +80,42 @@ function detectWarnings({ bodyHtml, to, cc, bcc, attachments, orgDomain }) { return warnings; } +function unwrapElement(element) { + const parent = element.parentNode; + if (!parent) return; + while (element.firstChild) parent.insertBefore(element.firstChild, element); + parent.removeChild(element); +} + +function unlinkSelection(editor) { + if (!editor) return; + const current = selectionInside(editor); + if (!current) { + editor.querySelectorAll("a").forEach(unwrapElement); + return; + } + const { selection, range } = current; + if (range.collapsed) { + const node = range.startContainer; + const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement; + const anchor = element?.closest?.("a"); + if (anchor && editor.contains(anchor)) unwrapElement(anchor); + return; + } + const anchors = [...editor.querySelectorAll("a")].filter((anchor) => { + try { + return range.intersectsNode(anchor); + } catch { + return false; + } + }); + if (anchors.length) { + anchors.forEach(unwrapElement); + return; + } + document.execCommand("unlink", false, null); +} + function MenuDropdown({ label, items, @@ -87,6 +123,7 @@ function MenuDropdown({ className = "", showCaret = true, iconStroke = 1.7, + onOpen, }) { const [open, setOpen] = useState(false); const ref = useRef(null); @@ -101,7 +138,18 @@ function MenuDropdown({ return (
- @@ -301,6 +349,7 @@ export default function ComposeModal({ const draftHydratedRef = useRef(false); const savedRangeRef = useRef(null); const linkPopoverRef = useRef(null); + const emojiWrapRef = useRef(null); const [showCc, setShowCc] = useState(false); const [showBcc, setShowBcc] = useState(false); @@ -415,6 +464,17 @@ export default function ComposeModal({ }); }, [account.email]); + useEffect(() => { + if (!showEmoji) return undefined; + function closeEmoji(event) { + if (!emojiWrapRef.current?.contains(event.target)) { + setShowEmoji(false); + } + } + document.addEventListener("mousedown", closeEmoji); + return () => document.removeEventListener("mousedown", closeEmoji); + }, [showEmoji]); + useEffect(() => { if (draftTimerRef.current) window.clearTimeout(draftTimerRef.current); draftTimerRef.current = window.setTimeout(saveDraft, 1400); @@ -511,6 +571,10 @@ export default function ComposeModal({ if (next && next !== editor) next.style.textAlign = align; normalizeEditorAlignment(editor); } + } else if (command === "removeFormat") { + unlinkSelection(editorRef.current); + document.execCommand("unlink", false, null); + document.execCommand("removeFormat", false, value); } else { document.execCommand(command, false, value); } @@ -765,6 +829,7 @@ export default function ComposeModal({ className="compose-from-picker" showCaret iconStroke={iconStroke} + onOpen={() => setShowEmoji(false)} label={ {fromAddress.email} @@ -1027,13 +1092,14 @@ export default function ComposeModal({ > -
+
@@ -1049,6 +1115,7 @@ export default function ComposeModal({ className="compose-icon-menu" showCaret={false} iconStroke={iconStroke} + onOpen={() => setShowEmoji(false)} label={} items={signatures.map((signature) => ({ id: signature.id, @@ -1060,6 +1127,7 @@ export default function ComposeModal({ className="compose-icon-menu" showCaret={false} iconStroke={iconStroke} + onOpen={() => setShowEmoji(false)} label={} items={[ { id: "standard", label: "Standard", active: true, onClick: () => flashNotice("Standard security") }, @@ -1071,6 +1139,7 @@ export default function ComposeModal({ className="compose-icon-menu" showCaret={false} iconStroke={iconStroke} + onOpen={() => setShowEmoji(false)} label={} items={[ { id: "receipt", label: "Request read receipt", onClick: () => flashNotice("Read receipt — coming soon") }, diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 8b4a35c..4f7436f 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1198,6 +1198,7 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; } .compose-menu-trigger { min-height:34px; padding:0 12px; display:inline-flex; align-items:center; gap:6px; border:0; border-radius:8px; background:var(--coral); color:var(--ink); font-size:12px; font-weight:700; } .compose-icon-menu .compose-menu-trigger,.compose-icon-btn { width:32px; height:32px; padding:0; display:grid; place-items:center; border:0; border-radius:6px; background:transparent; color:#6f6a73; box-shadow:none; } .compose-icon-menu .compose-menu-trigger:hover,.compose-icon-btn:hover { background:#f3efec; color:var(--ink); } +.compose-icon-btn.active { background:#ece7e1; color:#241f2b; } .compose-menu-panel { position:absolute; bottom:calc(100% + 6px); left:0; z-index:20; min-width:190px; padding:6px; display:grid; gap:2px; border:1px solid #ECE9EE; border-radius:8px; background:#fff; box-shadow:0 8px 24px rgba(20,18,30,.12); } .compose-menu-panel.align-right { left:auto; right:0; } .compose-menu-panel button { width:100%; min-height:34px; padding:0 10px; display:flex; align-items:center; gap:8px; border:0; border-radius:6px; background:transparent; color:var(--ink); font-size:11px; font-weight:600; text-align:left; }