refactor: create shared AppHeader component for consistent navigation across all pages

**Problem:**
- Each page had different header implementations with varying navigation links
- Inconsistent user experience across homepage, tools, playground, and publish pages
- Duplicate header code throughout the application

**Solution:**
- Created `AppHeader` component (apps/web/src/components/AppHeader.tsx) with consistent navigation:
  - TPMJS logo linking to homepage
  - Tools, Playground, and Publish Tool links
  - GitHub icon link
  - Sticky header with medium size
- Updated all pages to use the shared component:
  - apps/web/src/app/page.tsx (homepage)
  - apps/web/src/app/tool/tool-search/page.tsx (tools search)
  - apps/web/src/app/playground/page.tsx (component playground)
  - apps/web/src/app/publish/page.tsx (publish guide)
  - apps/web/src/app/tool/[...slug]/page.tsx (tool detail pages)

**Benefits:**
- Consistent header across all pages
- Single source of truth for navigation
- Easier to maintain and update navigation links
- Improved user experience with predictable navigation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-30 21:03:33 +10:00
parent 0b4a314bd1
commit c8ccab5cb8
6 changed files with 67 additions and 192 deletions

View file

@ -2,9 +2,8 @@ import { prisma } from '@tpmjs/db';
import { Badge } from '@tpmjs/ui/Badge/Badge';
import { Button } from '@tpmjs/ui/Button/Button';
import { Container } from '@tpmjs/ui/Container/Container';
import { Header } from '@tpmjs/ui/Header/Header';
import Link from 'next/link';
import { ThemeToggle } from '../components/ThemeToggle';
import { AppHeader } from '../components/AppHeader';
import { HeroSection } from '../components/home/HeroSection';
async function getHomePageData() {
@ -93,43 +92,7 @@ export default async function HomePage(): Promise<React.ReactElement> {
const data = await getHomePageData();
return (
<div className="min-h-screen flex flex-col">
{/* Header */}
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
size="md"
sticky={true}
actions={
<div className="flex items-center gap-4">
<Link href="/tool/tool-search">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Tools
</Button>
</Link>
<Link href="/playground">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Playground
</Button>
</Link>
<Link href="/publish">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Publish
</Button>
</Link>
<Button variant="secondary" size="sm">
Sign In
</Button>
<Button size="sm">Sign Up</Button>
<ThemeToggle />
</div>
}
/>
<AppHeader />
<main className="flex-1">
{/* Hero Section - Dithered Design */}

View file

@ -14,7 +14,6 @@ import { Checkbox } from '@tpmjs/ui/Checkbox/Checkbox';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Container } from '@tpmjs/ui/Container/Container';
import { FormField } from '@tpmjs/ui/FormField/FormField';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { Label } from '@tpmjs/ui/Label/Label';
@ -26,9 +25,8 @@ import { Slider } from '@tpmjs/ui/Slider/Slider';
import { Switch } from '@tpmjs/ui/Switch/Switch';
import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
import Link from 'next/link';
import { useState } from 'react';
import { ThemeToggle } from '../../components/ThemeToggle';
import { AppHeader } from '~/components/AppHeader';
// Disable static generation for this page due to context provider requirements
export const dynamic = 'force-dynamic';
@ -50,26 +48,7 @@ export default function PlaygroundPage() {
return (
<div className="min-h-screen flex flex-col dotted-grid-background">
{/* Header */}
<Header
title={
<Link href="/" className="text-foreground hover:text-foreground">
TPMJS Playground
</Link>
}
size="md"
sticky={true}
actions={
<div className="flex items-center gap-4">
<Link href="/">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Home
</Button>
</Link>
<ThemeToggle />
</div>
}
/>
<AppHeader />
<main className="flex-1 py-12 relative">
<div className="absolute inset-0 bg-background/95 -z-10" />

View file

@ -1,9 +1,8 @@
import { Button } from '@tpmjs/ui/Button/Button';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Container } from '@tpmjs/ui/Container/Container';
import { Header } from '@tpmjs/ui/Header/Header';
import Link from 'next/link';
import { ThemeToggle } from '../../components/ThemeToggle';
import { AppHeader } from '~/components/AppHeader';
export const metadata = {
title: 'Publish a Tool | TPMJS',
@ -13,39 +12,7 @@ export const metadata = {
export default function PublishPage(): React.ReactElement {
return (
<div className="min-h-screen flex flex-col bg-background">
{/* Header */}
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
size="md"
sticky={true}
actions={
<div className="flex items-center gap-4">
<Link href="/tool/tool-search">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Tools
</Button>
</Link>
<Link href="/playground">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Playground
</Button>
</Link>
<Link href="/publish">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Publish
</Button>
</Link>
<ThemeToggle />
</div>
}
/>
<AppHeader />
<main className="flex-1 py-16">
<Container size="lg" padding="lg">

View file

@ -5,14 +5,13 @@ import { Button } from '@tpmjs/ui/Button/Button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Container } from '@tpmjs/ui/Container/Container';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { Markdown } from '../../../components/Markdown';
import { ThemeToggle } from '../../../components/ThemeToggle';
import { ToolPlayground } from '../../../components/ToolPlayground';
import { AppHeader } from '~/components/AppHeader';
import { Markdown } from '~/components/Markdown';
import { ToolPlayground } from '~/components/ToolPlayground';
interface Tool {
id: string;
@ -112,19 +111,7 @@ export default function ToolDetailPage({
if (loading) {
return (
<div className="min-h-screen bg-background">
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
actions={<ThemeToggle />}
sticky={true}
size="md"
/>
<AppHeader />
<Container size="xl" padding="md" className="py-12">
<div className="text-center text-foreground-secondary">Loading tool...</div>
</Container>
@ -135,19 +122,7 @@ export default function ToolDetailPage({
if (error || !tool) {
return (
<div className="min-h-screen bg-background">
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
actions={<ThemeToggle />}
sticky={true}
size="md"
/>
<AppHeader />
<Container size="xl" padding="md" className="py-12">
<div className="text-center">
<p className="text-red-500 text-lg mb-4">{error || 'Tool not found'}</p>
@ -164,39 +139,7 @@ export default function ToolDetailPage({
return (
<div className="min-h-screen bg-background">
{/* Header */}
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
actions={
<div className="flex items-center gap-3">
<Link href="/tool/tool-search">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Browse Tools
</Button>
</Link>
{tool.npmRepository && (
<a
href={tool.npmRepository.url.replace('git+', '').replace('.git', '')}
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
<Icon icon="github" size="md" />
</a>
)}
<ThemeToggle />
</div>
}
sticky={true}
size="md"
/>
<AppHeader />
{/* Main content */}
<Container size="xl" padding="md" className="py-8">

View file

@ -12,7 +12,6 @@ import {
} from '@tpmjs/ui/Card/Card';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import { Container } from '@tpmjs/ui/Container/Container';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { ProgressBar } from '@tpmjs/ui/ProgressBar/ProgressBar';
@ -20,6 +19,7 @@ import { Select } from '@tpmjs/ui/Select/Select';
import { Tabs } from '@tpmjs/ui/Tabs/Tabs';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { AppHeader } from '~/components/AppHeader';
interface Tool {
id: string;
@ -113,37 +113,7 @@ export default function ToolSearchPage(): React.ReactElement {
return (
<div className="min-h-screen bg-background">
{/* Header */}
<Header
title={
<div className="flex items-center gap-2">
<span className="text-2xl font-bold">TPMJS</span>
<Badge variant="outline" size="sm">
Beta
</Badge>
</div>
}
actions={
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm">
Docs
</Button>
<a
href="https://github.com/tpmjs/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground-secondary hover:text-foreground transition-colors"
>
<Icon icon="github" size="md" />
</a>
<Button variant="default" size="sm">
Publish Tool
</Button>
</div>
}
sticky={true}
size="md"
/>
<AppHeader />
{/* Main content */}
<Container size="xl" padding="md" className="py-8">

View file

@ -0,0 +1,53 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Header } from '@tpmjs/ui/Header/Header';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import Link from 'next/link';
/**
* Shared application header used across all pages
*/
export function AppHeader(): React.ReactElement {
return (
<Header
title={
<Link
href="/"
className="text-foreground hover:text-foreground text-xl md:text-2xl font-bold uppercase tracking-tight"
>
TPMJS
</Link>
}
size="md"
sticky={true}
actions={
<div className="flex items-center gap-4">
<Link href="/tool/tool-search">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Tools
</Button>
</Link>
<Link href="/playground">
<Button variant="ghost" size="sm" className="text-foreground hover:text-foreground">
Playground
</Button>
</Link>
<a
href="https://github.com/tpmjs/tpmjs"
target="_blank"
rel="noopener noreferrer"
className="text-foreground hover:text-foreground transition-colors"
>
<Icon icon="github" size="md" />
</a>
<Link href="/publish">
<Button variant="default" size="sm">
Publish Tool
</Button>
</Link>
</div>
}
/>
);
}