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()}
`;
};
- const renderAddressSection = () => {
- const isOpen = openSection === "address";
- let body = "";
- if (isOpen) {
- if (addressesLoading) {
- body = `
`;
- } else {
- const listHTML = addresses.length > 0
- ? `
` : "";
- body = `
-
`;
- }
- }
- 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 {