diff --git a/shared/components/rstack-identity.ts b/shared/components/rstack-identity.ts index b64ac89e..bc9320db 100644 --- a/shared/components/rstack-identity.ts +++ b/shared/components/rstack-identity.ts @@ -470,9 +470,10 @@ export class RStackIdentity extends HTMLElement { const cached = localStorage.getItem(CACHE_KEY); if (cached) { try { - const { ts, ok } = JSON.parse(cached); - if (Date.now() - ts < 30 * 60 * 1000) { - if (!ok) this.#showRecoveryDot(); + const c = JSON.parse(cached); + if (Date.now() - c.ts < 30 * 60 * 1000) { + if (!c.socialRecovery) this.#showRecoveryDot(); + if (!c.email || !c.multiDevice || !c.socialRecovery) this.#showAccountDot(); return; } } catch { /* stale cache */ } @@ -484,9 +485,9 @@ export class RStackIdentity extends HTMLElement { }); if (!res.ok) return; const status = await res.json(); - const recoveryOk = status.socialRecovery === true; - localStorage.setItem(CACHE_KEY, JSON.stringify({ ts: Date.now(), ok: recoveryOk })); - if (!recoveryOk) this.#showRecoveryDot(); + localStorage.setItem(CACHE_KEY, JSON.stringify({ ts: Date.now(), email: !!status.email, multiDevice: !!status.multiDevice, socialRecovery: !!status.socialRecovery })); + if (!status.socialRecovery) this.#showRecoveryDot(); + if (!status.email || !status.multiDevice || !status.socialRecovery) this.#showAccountDot(); } catch { /* offline */ } } @@ -499,6 +500,14 @@ export class RStackIdentity extends HTMLElement { wrap.appendChild(dot); } + #showAccountDot() { + const btn = this.#shadow.querySelector('[data-action="my-account"]'); + if (!btn || btn.querySelector(".acct-alert-dot")) return; + const dot = document.createElement("span"); + dot.className = "acct-alert-dot"; + btn.appendChild(dot); + } + async #checkDeviceNudge() { const session = getSession(); if (!session?.accessToken) return; @@ -1360,10 +1369,6 @@ export class RStackIdentity extends HTMLElement { let devicesLoaded = false; let devicesLoading = false; - let addresses: { id: string; street: string; city: string; state: string; zip: string; country: string }[] = []; - let addressesLoaded = false; - let addressesLoading = false; - // Connections data let connectionsLoaded = false; let connectionsLoading = false; @@ -1418,7 +1423,6 @@ export class RStackIdentity extends HTMLElement { ${renderEmailSection()} ${renderDeviceSection()} ${renderRecoverySection()} - ${renderAddressSection()}
@@ -1714,52 +1718,7 @@ export class RStackIdentity extends HTMLElement {
`; }; - const renderAddressSection = () => { - const isOpen = openSection === "address"; - let body = ""; - if (isOpen) { - if (addressesLoading) { - body = `
Loading addresses...
`; - } else { - const listHTML = addresses.length > 0 - ? `
${addresses.map(a => ` -
-
- ${a.street.replace(/ - ${a.city.replace(/ -
- -
- `).join("")}
` : ""; - body = ` -
-
- -
- - -
-
- - -
- -
- ${listHTML} -
-
`; - } - } - return ` -
- - ${body} -
`; - }; const renderShortcutsSection = () => { const isOpen = openSection === "shortcuts"; @@ -1944,31 +1903,6 @@ export class RStackIdentity extends HTMLElement { render(); }; - const loadAddresses = async () => { - if (addressesLoaded || addressesLoading) return; - addressesLoading = true; - render(); - try { - const res = await fetch(`${ENCRYPTID_URL}/api/user/addresses`, { - headers: { Authorization: `Bearer ${getAccessToken()}` }, - }); - if (res.ok) { - const data = await res.json(); - addresses = (data.addresses || []).map((a: any) => { - try { - const decoded = JSON.parse(atob(a.ciphertext)); - return { id: a.id, ...decoded }; - } catch { - return { id: a.id, street: "", city: "", state: "", zip: "", country: "" }; - } - }); - } - } catch { /* offline */ } - addressesLoaded = true; - addressesLoading = false; - render(); - }; - const loadDevices = async () => { if (devicesLoaded || devicesLoading) return; devicesLoading = true; @@ -2003,7 +1937,6 @@ export class RStackIdentity extends HTMLElement { const section = (el as HTMLElement).dataset.section!; openSection = openSection === section ? null : section; if (openSection === "recovery") loadGuardians(); - if (openSection === "address") loadAddresses(); if (openSection === "device") loadDevices(); if (openSection === "connections") loadConnections(); render(); @@ -2261,58 +2194,6 @@ export class RStackIdentity extends HTMLElement { showWalkthrough = false; walkthroughStep = 0; render(); }); - // Address: save - overlay.querySelector('[data-action="save-address"]')?.addEventListener("click", async () => { - const street = (overlay.querySelector("#acct-street") as HTMLInputElement)?.value.trim() || ""; - const city = (overlay.querySelector("#acct-city") as HTMLInputElement)?.value.trim() || ""; - const state = (overlay.querySelector("#acct-state") as HTMLInputElement)?.value.trim() || ""; - const zip = (overlay.querySelector("#acct-zip") as HTMLInputElement)?.value.trim() || ""; - const country = (overlay.querySelector("#acct-country") as HTMLInputElement)?.value.trim() || ""; - const err = overlay.querySelector("#address-error") as HTMLElement; - const btn = overlay.querySelector('[data-action="save-address"]') as HTMLButtonElement; - - if (!street || !city) { err.textContent = "Street and city are required."; return; } - err.textContent = ""; - btn.disabled = true; btn.innerHTML = ' Saving...'; - - const payload = { street, city, state, zip, country }; - const ciphertext = btoa(JSON.stringify(payload)); - - try { - const res = await fetch(`${ENCRYPTID_URL}/api/user/addresses`, { - method: "POST", - headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAccessToken()}` }, - body: JSON.stringify({ ciphertext }), - }); - const data = await res.json(); - if (!res.ok) throw new Error(data.error || "Failed to save address"); - addresses.push({ id: data.id || data.address?.id || String(Date.now()), ...payload }); - render(); - } catch (e: any) { - btn.disabled = false; btn.innerHTML = "Save Address"; - err.textContent = e.message; - } - }); - - // Address: remove - overlay.querySelectorAll("[data-remove-address]").forEach(el => { - el.addEventListener("click", async () => { - const id = (el as HTMLElement).dataset.removeAddress!; - const err = overlay.querySelector("#address-error") as HTMLElement; - try { - const res = await fetch(`${ENCRYPTID_URL}/api/user/addresses/${id}`, { - method: "DELETE", - headers: { Authorization: `Bearer ${getAccessToken()}` }, - }); - if (!res.ok) throw new Error("Failed to remove address"); - addresses = addresses.filter(a => a.id !== id); - render(); - } catch (e: any) { - if (err) err.textContent = e.message; - } - }); - }); - // Device: rename credential overlay.querySelectorAll("[data-rename-credential]").forEach(el => { el.addEventListener("click", async () => { @@ -2820,6 +2701,13 @@ const STYLES = ` 0%, 100% { box-shadow: 0 0 4px rgba(248,113,113,0.4); } 50% { box-shadow: 0 0 10px rgba(248,113,113,0.8); } } +/* Account alert dot on "My Account" dropdown item */ +.acct-alert-dot { + width: 8px; height: 8px; border-radius: 50%; + background: #f87171; margin-left: auto; flex-shrink: 0; + box-shadow: 0 0 6px rgba(248,113,113,0.6); + animation: recovery-pulse 2s ease-in-out infinite; +} /* Persona switcher in dropdown */ .dropdown-label {