- {t("pool")}: {pool.name}
+ {t("pool")}: {emailsVisible ? pool.name : maskEmailLikeValue(pool.name)}
{drafts.length === 0 ? (
diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx
index 8e8680c95e..083efd7e27 100644
--- a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx
+++ b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolCard.tsx
@@ -3,6 +3,8 @@
import { useTranslations } from "next-intl";
import Card from "@/shared/components/Card";
import ProviderIcon from "@/shared/components/ProviderIcon";
+import useEmailPrivacyStore from "@/store/emailPrivacyStore";
+import { maskEmailLikeValue } from "@/shared/utils/maskEmail";
import type { QuotaPool } from "@/lib/quota/dimensions";
import type { PoolUsageSnapshot } from "@/lib/quota/types";
import DimensionBar from "./DimensionBar";
@@ -59,9 +61,13 @@ export default function PoolCard({
onRemove,
}: PoolCardProps) {
const t = useTranslations("quotaShare");
+ const emailsVisible = useEmailPrivacyStore((s) => s.emailsVisible);
const status = computeStatus(usage);
const { icon: statusIcon, cls: statusCls } = STATUS_ICONS[status];
+ const displayName = emailsVisible ? pool.name : maskEmailLikeValue(pool.name);
+ const displayConnectionLabel = emailsVisible ? connectionLabel : maskEmailLikeValue(connectionLabel);
+
// Check for plan dimensions from usage
const hasDimensions = !!usage?.dimensions?.length;
@@ -98,7 +104,7 @@ export default function PoolCard({
{statusIcon}
- {pool.name} · {connectionLabel}
+ {displayName} · {displayConnectionLabel}
diff --git a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx
index f782d6ea82..11be86b712 100644
--- a/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx
+++ b/src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx
@@ -20,6 +20,8 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { Button, Modal } from "@/shared/components";
+import useEmailPrivacyStore from "@/store/emailPrivacyStore";
+import { maskEmailLikeValue } from "@/shared/utils/maskEmail";
import { getKnownPlan } from "@/lib/quota/planRegistry";
import { quotaModelName } from "@/lib/quota/quotaModelNaming";
import type { Policy, PoolAllocation, QuotaDimension, QuotaUnit, QuotaWindow } from "@/lib/quota/dimensions";
@@ -160,6 +162,7 @@ export default function PoolWizard({
}: PoolWizardProps) {
const t = useTranslations("quotaShare");
const tPlans = useTranslations("quotaPlans");
+ const emailsVisible = useEmailPrivacyStore((s) => s.emailsVisible);
// ── Wizard step ───────────────────────────────────────────────────────────
const [step, setStep] = useState<1 | 2 | 3>(1);
@@ -188,8 +191,11 @@ export default function PoolWizard({
// ── Helpers ───────────────────────────────────────────────────────────────
- const connLabel = (c: Connection) =>
- `${c.provider} / ${c.name || c.email || c.displayName || c.id.slice(0, 12)}`;
+ const connLabel = (c: Connection) => {
+ const detail = c.name || c.email || c.displayName || c.id.slice(0, 12);
+ const maskedDetail = emailsVisible ? detail : maskEmailLikeValue(detail);
+ return `${c.provider} / ${maskedDetail}`;
+ };
const selectedConn = useMemo(
() => connections.find((c) => c.id === primaryConnectionId),
diff --git a/tests/unit/quota-email-privacy.test.ts b/tests/unit/quota-email-privacy.test.ts
new file mode 100644
index 0000000000..fddebbe416
--- /dev/null
+++ b/tests/unit/quota-email-privacy.test.ts
@@ -0,0 +1,241 @@
+/**
+ * tests/unit/quota-email-privacy.test.ts
+ *
+ * Source-scan assertions for the email-privacy feature on the Quota Share screen.
+ * Mirrors the pattern from mask-email.test.ts and providers-page-utils.test.ts.
+ *
+ * Checks that every display component that can show an email:
+ * 1. Imports `useEmailPrivacyStore` from the store
+ * 2. Imports `maskEmailLikeValue` (or `pickDisplayValue`) from the mask utility
+ * 3. References `emailsVisible` in its body
+ *
+ * Also verifies that QuotaSharePageClient renders {
+ assert.ok(
+ pageClientSrc.includes('import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"'),
+ "Expected EmailPrivacyToggle import in QuotaSharePageClient"
+ );
+});
+
+test("QuotaSharePageClient imports useEmailPrivacyStore", () => {
+ assert.ok(
+ pageClientSrc.includes('import useEmailPrivacyStore from "@/store/emailPrivacyStore"'),
+ "Expected useEmailPrivacyStore import in QuotaSharePageClient"
+ );
+});
+
+test("QuotaSharePageClient imports maskEmailLikeValue", () => {
+ assert.ok(
+ pageClientSrc.includes("maskEmailLikeValue"),
+ "Expected maskEmailLikeValue import/usage in QuotaSharePageClient"
+ );
+});
+
+test("QuotaSharePageClient consumes emailsVisible from store", () => {
+ assert.ok(
+ pageClientSrc.includes("emailsVisible"),
+ "Expected emailsVisible consumption in QuotaSharePageClient"
+ );
+});
+
+test("QuotaSharePageClient renders EmailPrivacyToggle in JSX", () => {
+ assert.ok(
+ pageClientSrc.includes(" {
+ // connLabel must call maskEmailLikeValue and guard with emailsVisible
+ assert.ok(
+ pageClientSrc.includes("emailsVisible ? raw : maskEmailLikeValue(raw)") ||
+ pageClientSrc.includes("emailsVisible") && pageClientSrc.includes("maskEmailLikeValue(raw)"),
+ "Expected connLabel to mask raw value when emailsVisible is false"
+ );
+});
+
+// ── PoolCard ──────────────────────────────────────────────────────────────────
+
+test("PoolCard imports useEmailPrivacyStore", () => {
+ assert.ok(
+ poolCardSrc.includes('import useEmailPrivacyStore from "@/store/emailPrivacyStore"'),
+ "Expected useEmailPrivacyStore import in PoolCard"
+ );
+});
+
+test("PoolCard imports maskEmailLikeValue", () => {
+ assert.ok(
+ poolCardSrc.includes("maskEmailLikeValue"),
+ "Expected maskEmailLikeValue import/usage in PoolCard"
+ );
+});
+
+test("PoolCard consumes emailsVisible from store", () => {
+ assert.ok(
+ poolCardSrc.includes("emailsVisible"),
+ "Expected emailsVisible usage in PoolCard"
+ );
+});
+
+test("PoolCard uses displayName instead of raw pool.name in header", () => {
+ // The masked variable must be rendered, not the raw pool.name directly
+ assert.ok(
+ poolCardSrc.includes("displayName"),
+ "Expected displayName masking variable in PoolCard"
+ );
+ assert.ok(
+ poolCardSrc.includes("displayConnectionLabel"),
+ "Expected displayConnectionLabel masking variable in PoolCard"
+ );
+});
+
+test("PoolCard header renders displayName and displayConnectionLabel (not raw values)", () => {
+ // The raw `{pool.name} · {connectionLabel}` must NOT appear unmasked
+ assert.ok(
+ !poolCardSrc.includes("{pool.name} · {connectionLabel}"),
+ "PoolCard must not render raw {pool.name} · {connectionLabel} — use masked variables"
+ );
+});
+
+// ── AccountQuotaRow ───────────────────────────────────────────────────────────
+
+test("AccountQuotaRow imports useEmailPrivacyStore", () => {
+ assert.ok(
+ accountQuotaRowSrc.includes('import useEmailPrivacyStore from "@/store/emailPrivacyStore"'),
+ "Expected useEmailPrivacyStore import in AccountQuotaRow"
+ );
+});
+
+test("AccountQuotaRow imports maskEmailLikeValue", () => {
+ assert.ok(
+ accountQuotaRowSrc.includes("maskEmailLikeValue"),
+ "Expected maskEmailLikeValue import/usage in AccountQuotaRow"
+ );
+});
+
+test("AccountQuotaRow consumes emailsVisible from store", () => {
+ assert.ok(
+ accountQuotaRowSrc.includes("emailsVisible"),
+ "Expected emailsVisible usage in AccountQuotaRow"
+ );
+});
+
+// ── PoolWizard ────────────────────────────────────────────────────────────────
+
+test("PoolWizard imports useEmailPrivacyStore", () => {
+ assert.ok(
+ poolWizardSrc.includes('import useEmailPrivacyStore from "@/store/emailPrivacyStore"'),
+ "Expected useEmailPrivacyStore import in PoolWizard"
+ );
+});
+
+test("PoolWizard imports maskEmailLikeValue", () => {
+ assert.ok(
+ poolWizardSrc.includes("maskEmailLikeValue"),
+ "Expected maskEmailLikeValue import/usage in PoolWizard"
+ );
+});
+
+test("PoolWizard consumes emailsVisible from store", () => {
+ assert.ok(
+ poolWizardSrc.includes("emailsVisible"),
+ "Expected emailsVisible usage in PoolWizard"
+ );
+});
+
+test("PoolWizard connLabel masks detail with emailsVisible gate", () => {
+ assert.ok(
+ poolWizardSrc.includes("maskedDetail"),
+ "Expected maskedDetail variable in PoolWizard connLabel"
+ );
+});
+
+// ── EditAllocationsModal ──────────────────────────────────────────────────────
+
+test("EditAllocationsModal imports useEmailPrivacyStore", () => {
+ assert.ok(
+ editAllocationsModalSrc.includes('import useEmailPrivacyStore from "@/store/emailPrivacyStore"'),
+ "Expected useEmailPrivacyStore import in EditAllocationsModal"
+ );
+});
+
+test("EditAllocationsModal imports maskEmailLikeValue", () => {
+ assert.ok(
+ editAllocationsModalSrc.includes("maskEmailLikeValue"),
+ "Expected maskEmailLikeValue import/usage in EditAllocationsModal"
+ );
+});
+
+test("EditAllocationsModal consumes emailsVisible from store", () => {
+ assert.ok(
+ editAllocationsModalSrc.includes("emailsVisible"),
+ "Expected emailsVisible usage in EditAllocationsModal"
+ );
+});
+
+test("EditAllocationsModal masks pool.name display", () => {
+ // pool.name can contain an email (e.g. "codex / gael.martins@domain.com")
+ assert.ok(
+ editAllocationsModalSrc.includes("maskEmailLikeValue(pool.name)"),
+ "Expected maskEmailLikeValue(pool.name) in EditAllocationsModal"
+ );
+});
+
+// ── maskEmailLikeValue helper behaviour (regression) ─────────────────────────
+
+const { maskEmailLikeValue, pickDisplayValue } = await import(
+ "../../src/shared/utils/maskEmail.ts"
+);
+
+test("maskEmailLikeValue masks email embedded in a pool name", () => {
+ const poolName = "codex / gael.martins@example.com";
+ // The full string contains @ so the whole thing gets treated as email — however
+ // maskEmailLikeValue only masks when the *trimmed* value contains @.
+ // The label is NOT just the email here, it contains a slash prefix.
+ // Verify: values with @ get masked, plain names stay plain.
+ assert.ok(maskEmailLikeValue("gael.martins@example.com").includes("***"), "email gets masked");
+ assert.equal(maskEmailLikeValue("Work Account"), "Work Account", "plain name is unchanged");
+ assert.equal(maskEmailLikeValue(null), "", "null returns empty string");
+ assert.equal(maskEmailLikeValue(undefined), "", "undefined returns empty string");
+});
+
+test("pickDisplayValue respects emailsVisible toggle for quota labels", () => {
+ const email = "gael.martins@example.com";
+ assert.equal(
+ pickDisplayValue([email], false, ""),
+ maskEmailLikeValue(email),
+ "when hidden: returns masked value"
+ );
+ assert.equal(
+ pickDisplayValue([email], true, ""),
+ email,
+ "when visible: returns raw value"
+ );
+});