1
mirror of https://github.com/jakejarvis/hoot.git synced 2025-10-18 22:34:25 -04:00

Refactor components for improved layout and accessibility: add vertical separator in AppHeader, adjust button sizes in GithubStars and ThemeToggle, and implement Tooltip in DomainReportView for enhanced user guidance.

This commit is contained in:
2025-10-03 21:13:25 -04:00
parent c768fd1460
commit 14fce61f70
12 changed files with 63 additions and 47 deletions

View File

@@ -3,6 +3,7 @@ import { HeaderSearch } from "@/components/domain/header-search";
import { GithubStars } from "@/components/github-stars";
import { Logo } from "@/components/logo";
import { ThemeToggle } from "@/components/theme-toggle";
import { Separator } from "@/components/ui/separator";
export function AppHeader() {
return (
@@ -15,9 +16,10 @@ export function AppHeader() {
<Logo className="h-10 w-10" aria-hidden="true" />
</Link>
<HeaderSearch />
<div className="flex items-center gap-1.5 justify-self-end">
<div className="flex h-full items-center gap-1.5 justify-self-end">
{/* Server-fetched star count with link */}
<GithubStars />
<Separator orientation="vertical" className="!h-4" />
<ThemeToggle />
</div>
</header>

View File

@@ -13,6 +13,11 @@ import { HeadersSection } from "@/components/domain/sections/headers-section";
import { HostingEmailSection } from "@/components/domain/sections/hosting-email-section";
import { RegistrationSection } from "@/components/domain/sections/registration-section";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useDomainHistory } from "@/hooks/use-domain-history";
import { useDomainQueries } from "@/hooks/use-domain-queries";
import { captureClient } from "@/lib/analytics/client";
@@ -69,36 +74,42 @@ export function DomainReportView({ domain }: { domain: string }) {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<ScreenshotTooltip domain={domain}>
<Link
href={`https://${domain}`}
target="_blank"
rel="noopener"
className="flex items-center gap-2"
onClick={() =>
captureClient("external_domain_link_clicked", { domain })
}
>
<Favicon domain={domain} size={20} className="rounded" />
<h2 className="font-semibold text-xl tracking-tight">{domain}</h2>
<ExternalLink
className="size-3.5 text-muted-foreground/60"
aria-hidden="true"
/>
</Link>
</ScreenshotTooltip>
</div>
<div>
<Button
variant="outline"
onClick={handleExportJson}
disabled={areSecondarySectionsLoading}
<ScreenshotTooltip domain={domain}>
<Link
href={`https://${domain}`}
target="_blank"
rel="noopener"
className="flex items-center gap-2"
onClick={() =>
captureClient("external_domain_link_clicked", { domain })
}
>
<Download />
<span className="hidden sm:inline-block">Export</span>
</Button>
</div>
<Favicon domain={domain} size={20} className="rounded" />
<h2 className="font-semibold text-xl tracking-tight">{domain}</h2>
<ExternalLink
className="size-3.5 text-muted-foreground/60"
aria-hidden="true"
/>
</Link>
</ScreenshotTooltip>
<Tooltip delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="outline"
onClick={handleExportJson}
disabled={areSecondarySectionsLoading}
>
<Download />
<span className="hidden sm:inline-block">Export</span>
</Button>
</TooltipTrigger>
<TooltipContent side="left">
<p>
Save this report as a <span className="font-mono">JSON</span>{" "}
file.
</p>
</TooltipContent>
</Tooltip>
</div>
<div className="space-y-4">

View File

@@ -6,7 +6,7 @@ import { ErrorWithRetry } from "./error-with-retry";
describe("ErrorWithRetry", () => {
it("calls onRetry when clicking Retry", async () => {
const onRetry = vi.fn();
render(<ErrorWithRetry message="Failed" onRetry={onRetry} />);
render(<ErrorWithRetry message="Failed" onRetryAction={onRetry} />);
await userEvent.click(screen.getByRole("button", { name: /retry/i }));
expect(onRetry).toHaveBeenCalled();
});

View File

@@ -4,15 +4,15 @@ import { Button } from "@/components/ui/button";
export function ErrorWithRetry({
message,
onRetry,
onRetryAction,
}: {
message: string;
onRetry: () => void;
onRetryAction: () => void;
}) {
return (
<div className="flex items-center gap-2 text-destructive text-sm">
{message}
<Button variant="outline" size="sm" onClick={onRetry}>
<Button variant="outline" size="sm" onClick={onRetryAction}>
Retry
</Button>
</div>

View File

@@ -271,7 +271,7 @@ export function CertificatesSection({
) : isError ? (
<ErrorWithRetry
message="Failed to load certificates."
onRetry={onRetryAction}
onRetryAction={onRetryAction}
/>
) : null}
</Section>

View File

@@ -95,7 +95,10 @@ export function DnsRecordsSection({
</DnsGroup>
</div>
) : isError ? (
<ErrorWithRetry message="Failed to load DNS." onRetry={onRetryAction} />
<ErrorWithRetry
message="Failed to load DNS."
onRetryAction={onRetryAction}
/>
) : null}
</Section>
);

View File

@@ -54,7 +54,7 @@ export function HeadersSection({
) : isError ? (
<ErrorWithRetry
message="Failed to load headers."
onRetry={onRetryAction}
onRetryAction={onRetryAction}
/>
) : null}
</Section>

View File

@@ -110,7 +110,7 @@ export function HostingEmailSection({
) : isError ? (
<ErrorWithRetry
message="Failed to load hosting details."
onRetry={onRetryAction}
onRetryAction={onRetryAction}
/>
) : null}
</Section>

View File

@@ -109,7 +109,7 @@ export function RegistrationSection({
) : isError ? (
<ErrorWithRetry
message="Failed to load WHOIS."
onRetry={onRetryAction}
onRetryAction={onRetryAction}
/>
) : null}
</Section>

View File

@@ -4,8 +4,8 @@ import { Button } from "@/components/ui/button";
async function fetchRepoStars(): Promise<number | null> {
try {
const res = await fetch("https://api.github.com/repos/jakejarvis/hoot", {
// Revalidate at most every 30 minutes to avoid rate limits
next: { revalidate: 1800 },
// Revalidate at most every 30 minutes to avoid rate limits (one day without access token)
next: { revalidate: process.env.GITHUB_TOKEN ? 1800 : 86400 },
headers: {
Accept: "application/vnd.github+json",
...(process.env.GITHUB_TOKEN
@@ -28,26 +28,26 @@ export async function GithubStars() {
const label = stars === null ? "0" : `${stars}`;
return (
<Button variant="ghost" asChild>
<Button variant="ghost" size="sm" asChild>
<Link
href="https://github.com/jakejarvis/hoot"
target="_blank"
rel="noopener"
className="group inline-flex select-none items-center gap-2 transition-colors"
className="group flex select-none items-center gap-2 transition-colors"
aria-label="Open GitHub repository"
>
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="shrink-0 group-hover:text-foreground"
className="flex shrink-0 group-hover:text-foreground"
>
<path
fill="currentColor"
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
<span className="relative top-px inline-flex items-center text-[13px] text-muted-foreground leading-none group-hover:text-foreground">
<span className="relative top-px flex items-center text-[13px] text-muted-foreground leading-none group-hover:text-foreground">
{label}
</span>
</Link>

View File

@@ -17,7 +17,7 @@ export function ThemeToggle() {
<Button
aria-label="Toggle theme"
variant="ghost"
size="icon"
size="sm"
onClick={toggleTheme}
>
<Sun className="dark:-rotate-90 rotate-0 scale-100 transition-all dark:scale-0" />

View File

@@ -56,7 +56,7 @@ function TooltipContent({
>
{children}
{hideArrow ? null : (
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary" />
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[1px] bg-primary fill-primary" />
)}
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>