agent: @U0AJM7X8FBR Admin Codebase - Hide Sensitive Info Toggle • actual: email#17
Conversation
Adds a global Eye/EyeOff toggle button (fixed top-right) that masks all email fields across the admin dashboard when activated. - New HideProvider context + useHide hook - New maskEmail utility (e.g. "jo***@ex***.com") - HideToggle component wired into global Providers - privyLoginsColumns, sandboxesColumns, AccountReposList all respect isHidden Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR introduces a new client-side hide/masking feature with context-based infrastructure, creates dedicated UI components for email display with optional masking, refactors table columns to use component-based cells, updates the app layout with a header component, and reorganizes import paths for better module structure. Changes
Sequence DiagramsequenceDiagram
participant User
participant HideToggle
participant HideContext
participant Component as Email Component<br/>(e.g., EmailCell)
participant useDisplayEmail
participant maskEmail
User->>HideToggle: Click toggle
HideToggle->>HideContext: Call toggle()<br/>(flip isHidden)
HideContext-->>HideToggle: Update isHidden state
Component->>useDisplayEmail: Call useDisplayEmail(email)
useDisplayEmail->>HideContext: Check isHidden state
HideContext-->>useDisplayEmail: Return isHidden boolean
alt isHidden = true
useDisplayEmail->>maskEmail: Call maskEmail(email)
maskEmail-->>useDisplayEmail: Return masked email
useDisplayEmail-->>Component: Return masked email
else isHidden = false
useDisplayEmail-->>Component: Return original email
end
Component-->>User: Render email<br/>(masked or original)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
providers/HideProvider.tsx (1)
27-29: Consider adding a development-time warning for missing provider.The current implementation silently falls back to default values when
useHideis called outside the provider boundary. This is fine for this feature, but could make debugging harder if a component is accidentally rendered outside the provider.♻️ Optional: Add development warning
+const HideContext = createContext<HideContextValue | null>(null); + export function useHide() { - return useContext(HideContext); + const context = useContext(HideContext); + if (!context) { + if (process.env.NODE_ENV === "development") { + console.warn("useHide called outside of HideProvider"); + } + return { isHidden: false, toggle: () => {} }; + } + return context; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@providers/HideProvider.tsx` around lines 27 - 29, The useHide hook should warn in development when it's used outside the HideContext provider: retrieve the context with useContext(HideContext) inside useHide, and if process.env.NODE_ENV !== 'production' and the returned value is undefined (or equals your default sentinel), call console.warn with a clear message mentioning useHide and HideContext to help debugging, then return the context value as before; update the useHide function to perform this check so production behavior is unchanged but developers get a visible warning when the provider is missing.components/PrivyLogins/privyLoginsColumns.tsx (1)
21-23: Consider simplifying the cell definition.The wrapper
getValue={() => getValue<string | null>()}is functional but verbose. You could passgetValuedirectly with a type assertion, or refactorEmailCellto accept the value directly (similar toAccountEmailCellinsandboxesColumns.tsx).♻️ Optional: Simplify by passing value directly
-function EmailCell({ getValue }: { getValue: () => string | null }) { +function EmailCell({ email }: { email: string | null }) { const { isHidden } = useHide(); - const email = getValue(); if (!email) return <span className="text-gray-400 italic">No email</span>; return <span>{isHidden ? maskEmail(email) : email}</span>; }Then in the column definition:
- cell: ({ getValue }) => ( - <EmailCell getValue={() => getValue<string | null>()} /> - ), + cell: ({ getValue }) => <EmailCell email={getValue<string | null>()} />,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/PrivyLogins/privyLoginsColumns.tsx` around lines 21 - 23, The cell wrapper is unnecessarily verbose: instead of passing getValue via a lambda in the column cell, simplify by either (A) passing getValue directly with a type assertion (e.g., cell: ({ getValue }) => <EmailCell getValue={getValue as () => string | null} />) or (B) refactor EmailCell to accept the resolved value like AccountEmailCell (change EmailCell's prop from getValue to value: string | null and update its usages, then use cell: ({ getValue }) => <EmailCell value={getValue<string | null>()} />); update all references to EmailCell accordingly so types remain correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/Sandboxes/sandboxesColumns.tsx`:
- Line 26: The title attribute currently exposes the raw email via title={`View
task runs for ${email ?? accountId}`}, so update the logic to respect the
isHidden flag: compute a display value (e.g., use accountId when isHidden is
true, otherwise use email ?? accountId) and use that for the title; reference
the title prop, email, accountId, and isHidden in
sandboxesColumns/SandboxesColumns rendering so the tooltip never reveals the
unmasked email when isHidden is true.
In `@components/SandboxOrgs/AccountReposList.tsx`:
- Line 31: The title attribute on the link in AccountReposList exposes the raw
email (`title={`View task runs for ${email ?? account_id}`}`) even when masking
is enabled; change the display logic to compute a single displayIdentifier
(e.g., use masked value when isHidden is true, otherwise email or account_id)
and use that variable for both the visible label and the title (replace
interpolation in title and any displayLabel usage with displayIdentifier) so the
tooltip no longer reveals the unmasked email.
---
Nitpick comments:
In `@components/PrivyLogins/privyLoginsColumns.tsx`:
- Around line 21-23: The cell wrapper is unnecessarily verbose: instead of
passing getValue via a lambda in the column cell, simplify by either (A) passing
getValue directly with a type assertion (e.g., cell: ({ getValue }) =>
<EmailCell getValue={getValue as () => string | null} />) or (B) refactor
EmailCell to accept the resolved value like AccountEmailCell (change EmailCell's
prop from getValue to value: string | null and update its usages, then use cell:
({ getValue }) => <EmailCell value={getValue<string | null>()} />); update all
references to EmailCell accordingly so types remain correct.
In `@providers/HideProvider.tsx`:
- Around line 27-29: The useHide hook should warn in development when it's used
outside the HideContext provider: retrieve the context with
useContext(HideContext) inside useHide, and if process.env.NODE_ENV !==
'production' and the returned value is undefined (or equals your default
sentinel), call console.warn with a clear message mentioning useHide and
HideContext to help debugging, then return the context value as before; update
the useHide function to perform this check so production behavior is unchanged
but developers get a visible warning when the provider is missing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ee8a7a81-7eea-4ed8-8b93-9e0b4b4f8822
📒 Files selected for processing (7)
components/HideToggle.tsxcomponents/PrivyLogins/privyLoginsColumns.tsxcomponents/SandboxOrgs/AccountReposList.tsxcomponents/Sandboxes/sandboxesColumns.tsxlib/maskEmail.tsproviders/HideProvider.tsxproviders/Providers.tsx
| <Link | ||
| href={`/accounts/${accountId}`} | ||
| className="text-[#345A5D] hover:underline font-medium" | ||
| title={`View task runs for ${email ?? accountId}`} |
There was a problem hiding this comment.
Title attribute exposes unmasked email when sensitive info is hidden.
Same issue as in AccountReposList.tsx: the tooltip reveals the raw email even when isHidden is true.
🛡️ Proposed fix
- title={`View task runs for ${email ?? accountId}`}
+ title={`View task runs for ${displayEmail ?? accountId}`}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| title={`View task runs for ${email ?? accountId}`} | |
| title={`View task runs for ${displayEmail ?? accountId}`} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/Sandboxes/sandboxesColumns.tsx` at line 26, The title attribute
currently exposes the raw email via title={`View task runs for ${email ??
accountId}`}, so update the logic to respect the isHidden flag: compute a
display value (e.g., use accountId when isHidden is true, otherwise use email ??
accountId) and use that for the title; reference the title prop, email,
accountId, and isHidden in sandboxesColumns/SandboxesColumns rendering so the
tooltip never reveals the unmasked email when isHidden is true.
Prevents the toggle from overlapping the sign-in button on the login page. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- HideToggle is now icon-only (no button background, no label) - Moved from fixed position in Providers to LoginButton header (next to email and sign out) - Extracted EmailCell to its own component file (SRP) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| import { maskEmail } from "@/lib/maskEmail"; | ||
| import type { PrivyUser } from "@/types/privy"; | ||
|
|
||
| function EmailCell({ getValue }: { getValue: () => string | null }) { |
There was a problem hiding this comment.
SRP - new component file for this definition.
| import { maskEmail } from "@/lib/maskEmail"; | ||
| import type { AccountSandboxRow } from "@/types/sandbox"; | ||
|
|
||
| function AccountEmailCell({ |
There was a problem hiding this comment.
SRP - new component file for this definition.
- AppHeader with LoginButton + HideToggle in root layout (all pages) - Removed duplicate header from HomePage - Fixed title tooltips to use masked email when hidden - Extracted AccountEmailCell from sandboxesColumns (SRP) - Extracted LastSeenCell from privyLoginsColumns (SRP) - Moved maskEmail to lib/hide/maskEmail.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/HideToggle.tsx`:
- Around line 10-16: The icon-only toggle button in HideToggle.tsx uses title
only; update the <button> (the element using onClick={toggle}) to include an
accessible name and state by adding a dynamic aria-label based on isHidden
(e.g., "Show sensitive info" when hidden, "Hide sensitive info" when visible)
and an aria-pressed attribute bound to the boolean isHidden (or its inverse
depending on intended semantics); keep the existing title if desired but ensure
aria-label and aria-pressed are present so screen readers announce the control
and its state.
In `@components/Login/LoginButton.tsx`:
- Around line 18-21: The header currently renders the raw email via
user?.email?.address ignoring the hide-mode toggled by HideToggle; update the
LoginButton rendering to read the same hide-state used by HideToggle (e.g., the
hide boolean from the hide context or hook that HideToggle uses) and, when hide
is active, render a masked value (for example "••••••" or a partially masked
address like first char + "...@..." ) instead of the raw email; ensure you
reference the existing symbols user?.email?.address and HideToggle so the check
is co-located with the span that displays the address and use the same
hide-state source (context/hook/prop) as HideToggle to keep behavior consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5f5b9d92-3788-40b2-aa7a-c85c1c89d614
📒 Files selected for processing (5)
components/HideToggle.tsxcomponents/Login/LoginButton.tsxcomponents/PrivyLogins/EmailCell.tsxcomponents/PrivyLogins/privyLoginsColumns.tsxproviders/Providers.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- providers/Providers.tsx
- components/PrivyLogins/privyLoginsColumns.tsx
components/Login/LoginButton.tsx
Outdated
| <span className="text-sm text-muted-foreground"> | ||
| {user?.email?.address ?? "Signed in"} | ||
| </span> | ||
| <HideToggle /> |
There was a problem hiding this comment.
SRP - move the hideToggle out of this button into the AppHeader component.
There was a problem hiding this comment.
How can we DRY the email display text changes in both this file and components/PrivyLogins/EmailCell.tsx?
There was a problem hiding this comment.
Also components/SandboxOrgs/AccountReposList.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/PrivyLogins/LastSeenCell.tsx`:
- Around line 7-8: The component LastSeenCell currently uses a falsy check on ts
which treats 0 as "Never"; change the guard to explicitly check for null or
undefined (e.g., ts === null || ts === undefined) so that a timestamp of 0 is
rendered as a date; update the return logic around the ts variable in
LastSeenCell to only show the "Never" span when ts is null/undefined and
otherwise render new Date(ts * 1000).toLocaleString().
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d086738e-2334-4b72-a2c3-ac477e069cd5
📒 Files selected for processing (10)
app/layout.tsxcomponents/AppHeader.tsxcomponents/Home/HomePage.tsxcomponents/PrivyLogins/EmailCell.tsxcomponents/PrivyLogins/LastSeenCell.tsxcomponents/PrivyLogins/privyLoginsColumns.tsxcomponents/SandboxOrgs/AccountReposList.tsxcomponents/Sandboxes/AccountEmailCell.tsxcomponents/Sandboxes/sandboxesColumns.tsxlib/hide/maskEmail.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- components/PrivyLogins/EmailCell.tsx
- HideToggle: added aria-label, aria-pressed, type=button - LoginButton: mask header email when hidden - DRY: created useDisplayEmail hook, used by EmailCell, AccountEmailCell, AccountReposList, LoginButton - LastSeenCell: use == null instead of falsy check for timestamp 0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LoginButton should only handle login/logout. The visibility toggle is a header-level concern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| repos: AccountRepo[]; | ||
| } | ||
|
|
||
| function AccountRepoLink({ account_id, email }: AccountRepo) { |
There was a problem hiding this comment.
SRP - new component file for AccountRepoLink
There was a problem hiding this comment.
Do not store components in the root of components. Store them in component sub-directories.
- actual: components/AppHeader.tsx
- required: components/Header/AppHeader.tsx
If there's any other components outside of a components/[subDirectory] file please fix those too.
- AppHeader → components/Header/AppHeader.tsx - HideToggle → components/Header/HideToggle.tsx - ApiDocsLink → components/ApiDocs/ApiDocsLink.tsx - Extracted AccountRepoLink from AccountReposList (SRP) - Updated all imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| <HomeContent /> | ||
| </main> | ||
| <div className="flex flex-1 items-center justify-center"> | ||
| <HomeContent /> |
There was a problem hiding this comment.
After you moved the header, this content was moved up to the top of the page.
- required: HomeContent is centered vertically on the page.
components/Header/HideToggle.tsx
Outdated
| title={isHidden ? "Show sensitive info" : "Hide sensitive info"} | ||
| className="text-muted-foreground hover:text-foreground transition-colors" | ||
| > | ||
| {isHidden ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} |
There was a problem hiding this comment.
Can you make the transition between these 2 icons appear as though the eye is blinking?
onClick
- animate closed
- animate open to new icon state
On click: eye closes (scaleY 0), icon swaps, eye opens (scaleY 1). 150ms transition for a natural blink effect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Made <main> a flex column container so child flex-1 items-center can properly fill the remaining height. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep scaleY(0) when icon swaps, then animate to scaleY(1) on the next frame so the opening is a smooth transition instead of a flash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/layout.tsx`:
- Around line 39-41: RootLayout wraps {children} in a <main> creating nested
main landmarks; remove or replace that wrapper so pages that already render
<main> (e.g., SandboxesPage, PrivyLoginsPage, SandboxOrgsPage) aren’t nested.
Edit the RootLayout (app/layout.tsx) to return children directly or wrap them in
a neutral element (e.g., <div> or fragment) instead of the <main> element so
only route pages supply the primary landmark.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a28c3326-c494-4621-966c-1cc38885e068
📒 Files selected for processing (15)
app/layout.tsxcomponents/ApiDocs/ApiDocsLink.tsxcomponents/Header/AppHeader.tsxcomponents/Header/HideToggle.tsxcomponents/Home/AdminDashboard.tsxcomponents/Login/LoginButton.tsxcomponents/PrivyLogins/EmailCell.tsxcomponents/PrivyLogins/LastSeenCell.tsxcomponents/PrivyLogins/PrivyLoginsPage.tsxcomponents/SandboxOrgs/AccountRepoLink.tsxcomponents/SandboxOrgs/AccountReposList.tsxcomponents/SandboxOrgs/SandboxOrgsPage.tsxcomponents/Sandboxes/AccountEmailCell.tsxcomponents/Sandboxes/SandboxesPage.tsxlib/hide/useDisplayEmail.ts
✅ Files skipped from review due to trivial changes (1)
- components/Home/AdminDashboard.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- components/Login/LoginButton.tsx
- components/PrivyLogins/LastSeenCell.tsx
- components/PrivyLogins/EmailCell.tsx
app/layout.tsx
Outdated
| <main className="flex-1"> | ||
| {children} | ||
| </main> |
There was a problem hiding this comment.
Avoid nested <main> landmarks in RootLayout.
Wrapping children in <main> here causes nested main landmarks because route pages already render <main> (e.g., components/Sandboxes/SandboxesPage.tsx Line 7, components/PrivyLogins/PrivyLoginsPage.tsx Line 20, components/SandboxOrgs/SandboxOrgsPage.tsx Line 7).
Suggested fix
- <main className="flex-1">
- {children}
- </main>
+ <div className="flex-1">
+ {children}
+ </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <main className="flex-1"> | |
| {children} | |
| </main> | |
| <div className="flex-1"> | |
| {children} | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/layout.tsx` around lines 39 - 41, RootLayout wraps {children} in a <main>
creating nested main landmarks; remove or replace that wrapper so pages that
already render <main> (e.g., SandboxesPage, PrivyLoginsPage, SandboxOrgsPage)
aren’t nested. Edit the RootLayout (app/layout.tsx) to return children directly
or wrap them in a neutral element (e.g., <div> or fragment) instead of the
<main> element so only route pages supply the primary landmark.
Automated PR from coding agent.
Summary by CodeRabbit
New Features
UI Improvements