-
Notifications
You must be signed in to change notification settings - Fork 0
agent: @U0AJM7X8FBR Admin Codebase - Hide Sensitive Info Toggle • actual: email #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b7ec88
14d7afd
f141e99
e1bc6d9
a9dd3be
d542828
c625d59
2d5f743
a1b28be
9ae9eb3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import LoginButton from "@/components/Login/LoginButton"; | ||
| import { HideToggle } from "@/components/Header/HideToggle"; | ||
|
|
||
| export default function AppHeader() { | ||
| return ( | ||
| <header className="flex items-center justify-between border-b px-6 py-4"> | ||
| <h1 className="text-lg font-semibold">Recoup Admin</h1> | ||
| <div className="flex items-center gap-3"> | ||
| <LoginButton /> | ||
| <HideToggle /> | ||
| </div> | ||
| </header> | ||
| ); | ||
| } |
sweetmantech marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| "use client"; | ||
|
|
||
| import { useState, useCallback, useRef } from "react"; | ||
| import { Eye, EyeOff } from "lucide-react"; | ||
| import { useHide } from "@/providers/HideProvider"; | ||
|
|
||
| const BLINK_MS = 150; | ||
|
|
||
| export function HideToggle() { | ||
| const { isHidden, toggle } = useHide(); | ||
| const [phase, setPhase] = useState<"idle" | "closing" | "opening">("idle"); | ||
| const iconRef = useRef<SVGSVGElement>(null); | ||
|
|
||
| const handleClick = useCallback(() => { | ||
| if (phase !== "idle") return; | ||
|
|
||
| // Phase 1: close the eye | ||
| setPhase("closing"); | ||
|
|
||
| setTimeout(() => { | ||
| // Phase 2: swap icon while still closed, then open | ||
| toggle(); | ||
| setPhase("opening"); | ||
|
|
||
| // Wait for next frame so the browser registers scaleY(0) before animating to scaleY(1) | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| setPhase("idle"); | ||
| }); | ||
| }); | ||
| }, BLINK_MS); | ||
| }, [toggle, phase]); | ||
|
|
||
| const Icon = isHidden ? EyeOff : Eye; | ||
|
|
||
| const scaleY = phase === "closing" || phase === "opening" ? 0 : 1; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={handleClick} | ||
| aria-label={isHidden ? "Show sensitive info" : "Hide sensitive info"} | ||
| aria-pressed={isHidden} | ||
| title={isHidden ? "Show sensitive info" : "Hide sensitive info"} | ||
| className="text-muted-foreground hover:text-foreground transition-colors" | ||
| > | ||
| <Icon | ||
| ref={iconRef} | ||
| className="h-4 w-4" | ||
| style={{ | ||
| transform: `scaleY(${scaleY})`, | ||
| transition: `transform ${BLINK_MS}ms ease-in-out`, | ||
| }} | ||
| /> | ||
| </button> | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,9 @@ | ||
| import LoginButton from "@/components/Login/LoginButton"; | ||
| import HomeContent from "@/components/Home/HomeContent"; | ||
|
|
||
| export default function HomePage() { | ||
| return ( | ||
| <div className="flex min-h-screen flex-col"> | ||
| <header className="flex items-center justify-between border-b px-6 py-4"> | ||
| <h1 className="text-lg font-semibold">Recoup Admin</h1> | ||
| <LoginButton /> | ||
| </header> | ||
| <main className="flex flex-1 items-center justify-center"> | ||
| <HomeContent /> | ||
| </main> | ||
| <div className="flex flex-1 items-center justify-center"> | ||
| <HomeContent /> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After you moved the header, this content was moved up to the top of the page.
|
||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| "use client"; | ||
|
|
||
| import { useDisplayEmail } from "@/lib/hide/useDisplayEmail"; | ||
|
|
||
| interface EmailCellProps { | ||
| getValue: () => string | null; | ||
| } | ||
|
|
||
| export default function EmailCell({ getValue }: EmailCellProps) { | ||
| const displayEmail = useDisplayEmail(getValue()); | ||
| if (!displayEmail) return <span className="text-gray-400 italic">No email</span>; | ||
| return <span>{displayEmail}</span>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| interface LastSeenCellProps { | ||
| getValue: () => number | null; | ||
| } | ||
|
|
||
| export default function LastSeenCell({ getValue }: LastSeenCellProps) { | ||
| const ts = getValue(); | ||
| if (ts == null) return <span className="text-gray-400 italic">Never</span>; | ||
| return <span>{new Date(ts * 1000).toLocaleString()}</span>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { useDisplayEmail } from "@/lib/hide/useDisplayEmail"; | ||
| import type { AccountRepo } from "@/types/sandbox"; | ||
|
|
||
| export default function AccountRepoLink({ account_id, email }: AccountRepo) { | ||
| const displayEmail = useDisplayEmail(email ?? null); | ||
| const displayLabel = displayEmail ?? account_id; | ||
| return ( | ||
| <li> | ||
| <Link | ||
| href={`/accounts/${account_id}`} | ||
| className="text-[#345A5D] hover:underline font-medium truncate block max-w-xs" | ||
| title={`View task runs for ${displayLabel}`} | ||
| > | ||
| {displayLabel} | ||
| </Link> | ||
| </li> | ||
| ); | ||
| } |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How can we DRY the email display text changes in both this file and components/PrivyLogins/EmailCell.tsx?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also components/SandboxOrgs/AccountReposList.tsx |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { useDisplayEmail } from "@/lib/hide/useDisplayEmail"; | ||
|
|
||
| interface AccountEmailCellProps { | ||
| email: string | null; | ||
| accountId: string; | ||
| } | ||
|
|
||
| export default function AccountEmailCell({ email, accountId }: AccountEmailCellProps) { | ||
| const displayEmail = useDisplayEmail(email); | ||
| return ( | ||
| <Link | ||
| href={`/accounts/${accountId}`} | ||
| className="text-[#345A5D] hover:underline font-medium" | ||
| title={`View task runs for ${displayEmail ?? accountId}`} | ||
| > | ||
| {displayEmail ?? ( | ||
| <span className="font-mono text-xs text-gray-500">{accountId}</span> | ||
| )} | ||
| </Link> | ||
| ); | ||
| } |
sweetmantech marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| /** | ||
| * Masks an email address so sensitive info is not exposed. | ||
| * e.g. "john.doe@example.com" → "jo***@ex***.com" | ||
| */ | ||
| export function maskEmail(email: string): string { | ||
| const atIndex = email.indexOf("@"); | ||
| if (atIndex === -1) return "***"; | ||
|
|
||
| const local = email.slice(0, atIndex); | ||
| const domain = email.slice(atIndex + 1); | ||
|
|
||
| const maskedLocal = local.slice(0, 2).padEnd(2, "*") + "***"; | ||
|
|
||
| const dotIndex = domain.lastIndexOf("."); | ||
| if (dotIndex === -1) return `${maskedLocal}@***`; | ||
|
|
||
| const domainName = domain.slice(0, dotIndex); | ||
| const tld = domain.slice(dotIndex); | ||
| const maskedDomain = domainName.slice(0, 2).padEnd(2, "*") + "***"; | ||
|
|
||
| return `${maskedLocal}@${maskedDomain}${tld}`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| "use client"; | ||
|
|
||
| import { useHide } from "@/providers/HideProvider"; | ||
| import { maskEmail } from "@/lib/hide/maskEmail"; | ||
|
|
||
| /** | ||
| * Returns the email formatted for display — masked when hide mode is active. | ||
| */ | ||
| export function useDisplayEmail(email: string | null): string | null { | ||
| const { isHidden } = useHide(); | ||
| if (!email) return null; | ||
| return isHidden ? maskEmail(email) : email; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not store components in the root of components. Store them in component sub-directories.
If there's any other components outside of a components/[subDirectory] file please fix those too.