diff --git a/docs/guides/FEATURES.md b/docs/guides/FEATURES.md index 4dd93d85ce..df79c1d0ef 100644 --- a/docs/guides/FEATURES.md +++ b/docs/guides/FEATURES.md @@ -185,7 +185,7 @@ Comprehensive proxy configuration enforcement across the entire request pipeline ## ๐Ÿ“ง Email Privacy Masking _(v3.5.6+)_ -OAuth account emails are now masked in the provider dashboard (e.g. `di*****@g****.com`) to prevent accidental exposure when sharing screenshots or recording demos. The full email address remains accessible via hover tooltip (`title` attribute). +OAuth account emails are masked by default (e.g. `di*****@g****.com`) to prevent accidental exposure when sharing screenshots or recording demos. Use Settings โ†’ Appearance โ†’ Account email visibility to reveal or mask full account emails globally across providers, combos, logs, quota, and playground screens. --- diff --git a/src/app/(dashboard)/dashboard/combos/page.tsx b/src/app/(dashboard)/dashboard/combos/page.tsx index 27af619bd1..eea7fbc6d4 100644 --- a/src/app/(dashboard)/dashboard/combos/page.tsx +++ b/src/app/(dashboard)/dashboard/combos/page.tsx @@ -12,7 +12,6 @@ import Input from "@/shared/components/Input"; import Modal from "@/shared/components/Modal"; import Toggle from "@/shared/components/Toggle"; import Tooltip from "@/shared/components/Tooltip"; -import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"; import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; import { pickDisplayValue } from "@/shared/utils/maskEmail"; import useEmailPrivacyStore from "@/store/emailPrivacyStore"; @@ -976,32 +975,6 @@ export default function CombosPage() {
-
- - {getI18nOrFallback( - t, - "emailVisibilityHint", - "Account emails here follow the global privacy toggle." - )} - - - - - - - - {emailsVisible - ? getI18nOrFallback(t, "emailVisibilityStateOn", "Emails visible globally") - : getI18nOrFallback(t, "emailVisibilityStateOff", "Emails masked globally")} - -
{!showUsageGuide && (
- @@ -549,8 +572,7 @@ export default function QuotaSharePageClient() { ) : ( groupsToRender.map((g) => { const groupPools = pools.filter( - (p) => - ((p as unknown as { groupId?: string }).groupId ?? "group-demo") === g.id + (p) => ((p as unknown as { groupId?: string }).groupId ?? "group-demo") === g.id ); return (
@@ -643,7 +665,9 @@ export default function QuotaSharePageClient() { connections={connections} apiKeys={apiKeys} plans={plans} - existingPoolConnectionIds={new Set(pools.flatMap((p) => p.connectionIds ?? [p.connectionId]))} + existingPoolConnectionIds={ + new Set(pools.flatMap((p) => p.connectionIds ?? [p.connectionId])) + } connectionPoolName={connectionPoolName} groups={groups} selectedGroupId={selectedGroupId} diff --git a/src/app/(dashboard)/dashboard/logs/page.tsx b/src/app/(dashboard)/dashboard/logs/page.tsx index c99aafd955..df1790bd3c 100644 --- a/src/app/(dashboard)/dashboard/logs/page.tsx +++ b/src/app/(dashboard)/dashboard/logs/page.tsx @@ -2,7 +2,6 @@ import { useState, useRef, useEffect } from "react"; import { ConfirmModal, RequestLoggerV2 } from "@/shared/components"; -import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"; import { useTranslations } from "next-intl"; const TIME_RANGES = [ @@ -124,8 +123,6 @@ export default function LogsPage() {

{t("requestLogs")}

- - -
+

+ {showEmail ? connection.email : maskEmail(connection.email)} +

)} {isOAuth && ( diff --git a/src/app/(dashboard)/dashboard/settings/components/AccountEmailVisibilitySetting.tsx b/src/app/(dashboard)/dashboard/settings/components/AccountEmailVisibilitySetting.tsx new file mode 100644 index 0000000000..59caaf4cb2 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/AccountEmailVisibilitySetting.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import { useShallow } from "zustand/react/shallow"; +import { Toggle } from "@/shared/components"; +import useEmailPrivacyStore from "@/store/emailPrivacyStore"; + +export default function AccountEmailVisibilitySetting() { + const t = useTranslations("settings"); + const { emailsVisible, setEmailsVisible } = useEmailPrivacyStore( + useShallow((state) => ({ + emailsVisible: state.emailsVisible, + setEmailsVisible: state.setEmailsVisible, + })) + ); + + const label = (key: string, fallback: string) => + typeof t.has === "function" && t.has(key) ? t(key) : fallback; + + return ( +
+
+
+

+ {label("accountEmailVisibility", "Account email visibility")} +

+

+ {label( + "accountEmailVisibilityDesc", + "Show full account emails across providers, combos, logs, quota, and playground screens. Turn this off to mask them by default." + )} +

+
+ +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx index f099c6333d..32e030363d 100644 --- a/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx @@ -1,7 +1,6 @@ "use client"; import { useState, useEffect } from "react"; -import Link from "next/link"; import { Button, Card, Toggle } from "@/shared/components"; import { useTheme } from "@/shared/hooks/useTheme"; import useThemeStore, { COLOR_THEMES } from "@/store/themeStore"; @@ -17,6 +16,8 @@ import { PIN_PROVIDER_QUOTA_TO_HOME_KEY, SHOW_TOKEN_SAVER_ON_ENDPOINT_KEY, } from "@/shared/constants/homeWidgets"; +import AccountEmailVisibilitySetting from "./AccountEmailVisibilitySetting"; +import SidebarVisibilitySetting from "./SidebarVisibilitySetting"; export default function AppearanceTab() { const { theme, setTheme, isDark } = useTheme(); @@ -533,26 +534,9 @@ export default function AppearanceTab() {
-
-
-
-

{t("sidebarVisibilityToggle")}

-

- {getSettingsLabel( - "sidebarCustomizeLink", - "Customize which items appear in the sidebar, their order, and apply role presets." - )} -

-
- - view_sidebar - {getSettingsLabel("sidebarCustomizeLinkBtn", "Customize")} - -
-
+ + +
diff --git a/src/app/(dashboard)/dashboard/settings/components/SidebarVisibilitySetting.tsx b/src/app/(dashboard)/dashboard/settings/components/SidebarVisibilitySetting.tsx new file mode 100644 index 0000000000..aa0c168c01 --- /dev/null +++ b/src/app/(dashboard)/dashboard/settings/components/SidebarVisibilitySetting.tsx @@ -0,0 +1,33 @@ +"use client"; + +import Link from "next/link"; +import { useTranslations } from "next-intl"; + +export default function SidebarVisibilitySetting() { + const t = useTranslations("settings"); + const label = (key: string, fallback: string) => + typeof t.has === "function" && t.has(key) ? t(key) : fallback; + + return ( +
+
+
+

{t("sidebarVisibilityToggle")}

+

+ {label( + "sidebarCustomizeLink", + "Customize which items appear in the sidebar, their order, and apply role presets." + )} +

+
+ + view_sidebar + {label("sidebarCustomizeLinkBtn", "Customize")} + +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx index fc13c6578d..3b83abb03c 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx @@ -17,7 +17,6 @@ import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; import { pickDisplayValue } from "@/shared/utils/maskEmail"; import useEmailPrivacyStore from "@/store/emailPrivacyStore"; import { useNotificationStore } from "@/store/notificationStore"; -import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle"; import QuotaCutoffModal from "./QuotaCutoffModal"; import QuotaCardGrid from "./QuotaCardGrid"; import { translateUsageOrFallback, type UsageTranslationValues } from "./i18nFallback"; @@ -819,7 +818,6 @@ export default function ProviderLimits({ {visibleConnections.length !== sortedConnections.length && ` ${t("filteredFromCount", { count: sortedConnections.length })}`} -
- ); -} diff --git a/src/store/emailPrivacyStore.ts b/src/store/emailPrivacyStore.ts index a8856e8224..f46b8757b4 100644 --- a/src/store/emailPrivacyStore.ts +++ b/src/store/emailPrivacyStore.ts @@ -6,15 +6,15 @@ import { persist } from "zustand/middleware"; interface EmailPrivacyState { /** When true, all email addresses are shown in full (unmasked). Default: false (masked). */ emailsVisible: boolean; - /** Toggle the global email visibility state. */ - toggleEmailVisibility: () => void; + /** Set the global email visibility state. */ + setEmailsVisible: (visible: boolean) => void; } const useEmailPrivacyStore = create()( persist( - (set, get) => ({ + (set) => ({ emailsVisible: false, - toggleEmailVisibility: () => set({ emailsVisible: !get().emailsVisible }), + setEmailsVisible: (visible) => set({ emailsVisible: visible }), }), { name: "omniroute-email-privacy",