refactor: externalize AppPage component and streamline all tool pages

This commit is contained in:
2026-02-25 18:04:32 +01:00
parent 71c22e465e
commit 7eeb8399b3
13 changed files with 113 additions and 139 deletions

View File

@@ -1,17 +1,13 @@
import { FigletConverter } from '@/components/figlet/FigletConverter'; import { FigletConverter } from '@/components/figlet/FigletConverter';
import { AppPage } from '@/components/layout/AppPage';
export default function FigletPage() { export default function FigletPage() {
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Figlet ASCII"
<div> description="ASCII Art Text Generator with 373 Fonts"
<h1 className="text-4xl font-bold mb-2">Figlet ASCII</h1> >
<p className="text-muted-foreground"> <FigletConverter />
ASCII Art Text Generator with 373 Fonts </AppPage>
</p>
</div>
<FigletConverter />
</div>
</div>
); );
} }

View File

@@ -1,17 +1,13 @@
import { FileConverter } from '@/components/media/FileConverter'; import { FileConverter } from '@/components/media/FileConverter';
import { AppPage } from '@/components/layout/AppPage';
export default function MediaPage() { export default function MediaPage() {
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Media Converter"
<div> description="Professional browser-based media conversion for video, audio, and images"
<h1 className="text-4xl font-bold mb-2">Media Converter</h1> >
<p className="text-muted-foreground"> <FileConverter />
Professional browser-based media conversion for video, audio, and images </AppPage>
</p>
</div>
<FileConverter />
</div>
</div>
); );
} }

View File

@@ -14,6 +14,7 @@ import { Textarea } from '@/components/ui/textarea';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { PaletteGrid } from '@/components/pastel/PaletteGrid'; import { PaletteGrid } from '@/components/pastel/PaletteGrid';
import { ExportMenu } from '@/components/pastel/ExportMenu'; import { ExportMenu } from '@/components/pastel/ExportMenu';
import { AppPage } from '@/components/layout/AppPage';
import { useLighten, useDarken, useSaturate, useDesaturate, useRotate } from '@/lib/pastel/api/queries'; import { useLighten, useDarken, useSaturate, useDesaturate, useRotate } from '@/lib/pastel/api/queries';
import { Loader2, Upload, Download } from 'lucide-react'; import { Loader2, Upload, Download } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -92,16 +93,11 @@ export default function BatchPage() {
rotateMutation.isPending; rotateMutation.isPending;
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Batch Operations"
<div> description="Process multiple colors at once with manipulation operations"
<h1 className="text-4xl font-bold mb-2">Batch Operations</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Process multiple colors at once with manipulation operations
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Input */} {/* Input */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -213,7 +209,6 @@ export default function BatchPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -12,6 +12,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useSimulateColorBlindness } from '@/lib/pastel/api/queries'; import { useSimulateColorBlindness } from '@/lib/pastel/api/queries';
import { Loader2, Eye, Plus, X } from 'lucide-react'; import { Loader2, Eye, Plus, X } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -66,16 +67,11 @@ export default function ColorBlindPage() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Color Blindness Simulator"
<div> description="Simulate how colors appear with different types of color blindness"
<h1 className="text-4xl font-bold mb-2">Color Blindness Simulator</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Simulate how colors appear with different types of color blindness
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Controls */} {/* Controls */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -229,7 +225,6 @@ export default function ColorBlindPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -5,6 +5,7 @@ import { ColorPicker } from '@/components/pastel/ColorPicker';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { getContrastRatio, hexToRgb, checkWCAGCompliance } from '@/lib/pastel/utils/color'; import { getContrastRatio, hexToRgb, checkWCAGCompliance } from '@/lib/pastel/utils/color';
import { ArrowLeftRight, Check, X } from 'lucide-react'; import { ArrowLeftRight, Check, X } from 'lucide-react';
@@ -57,16 +58,11 @@ export default function ContrastPage() {
); );
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Contrast Checker"
<div> description="Test color combinations for WCAG 2.1 compliance"
<h1 className="text-4xl font-bold mb-2">Contrast Checker</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Test color combinations for WCAG 2.1 compliance
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Color Pickers */} {/* Color Pickers */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -183,7 +179,6 @@ export default function ContrastPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -13,6 +13,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useGenerateDistinct } from '@/lib/pastel/api/queries'; import { useGenerateDistinct } from '@/lib/pastel/api/queries';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -38,16 +39,11 @@ export default function DistinctPage() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Distinct Colors Generator"
<div> description="Generate visually distinct colors using simulated annealing"
<h1 className="text-4xl font-bold mb-2">Distinct Colors Generator</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
Generate visually distinct colors using simulated annealing
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Controls */} {/* Controls */}
<div className="lg:col-span-1"> <div className="lg:col-span-1">
<Card> <Card>
@@ -137,7 +133,6 @@ export default function DistinctPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -7,6 +7,7 @@ import { ExportMenu } from '@/components/pastel/ExportMenu';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useGenerateGradient } from '@/lib/pastel/api/queries'; import { useGenerateGradient } from '@/lib/pastel/api/queries';
import { Loader2, Plus, X } from 'lucide-react'; import { Loader2, Plus, X } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -48,16 +49,11 @@ export default function GradientPage() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Gradient Creator"
<div> description="Create smooth color gradients with multiple stops"
<h1 className="text-4xl font-bold mb-2">Gradient Creator</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Create smooth color gradients with multiple stops
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Controls */} {/* Controls */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -169,7 +165,6 @@ export default function GradientPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -13,6 +13,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useGeneratePalette } from '@/lib/pastel/api/queries'; import { useGeneratePalette } from '@/lib/pastel/api/queries';
import { Loader2, Palette } from 'lucide-react'; import { Loader2, Palette } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -59,16 +60,11 @@ export default function HarmonyPage() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Harmony Palette Generator"
<div> description="Create color harmonies based on color theory principles"
<h1 className="text-4xl font-bold mb-2">Harmony Palette Generator</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Create color harmonies based on color theory principles
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Controls */} {/* Controls */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -160,7 +156,6 @@ export default function HarmonyPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -11,6 +11,7 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useNamedColors } from '@/lib/pastel/api/queries'; import { useNamedColors } from '@/lib/pastel/api/queries';
import { Loader2 } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { parse_color } from '@valknarthing/pastel-wasm'; import { parse_color } from '@valknarthing/pastel-wasm';
@@ -44,15 +45,11 @@ export default function NamedColorsPage() {
}, [data, search, sortBy]); }, [data, search, sortBy]);
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Named Colors"
<div> description="Explore 148 CSS/X11 named colors"
<h1 className="text-4xl font-bold mb-2">Named Colors</h1> >
<p className="text-muted-foreground"> <div className="space-y-8">
Explore 148 CSS/X11 named colors
</p>
</div>
{/* Search and Filters */} {/* Search and Filters */}
<div className="flex flex-col sm:flex-row gap-4"> <div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1"> <div className="flex-1">
@@ -120,6 +117,6 @@ export default function NamedColorsPage() {
</CardContent> </CardContent>
</Card> </Card>
</div> </div>
</div> </AppPage>
); );
} }

View File

@@ -7,6 +7,7 @@ import { ColorDisplay } from '@/components/pastel/ColorDisplay';
import { ColorInfo } from '@/components/pastel/ColorInfo'; import { ColorInfo } from '@/components/pastel/ColorInfo';
import { ManipulationPanel } from '@/components/pastel/ManipulationPanel'; import { ManipulationPanel } from '@/components/pastel/ManipulationPanel';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useColorInfo } from '@/lib/pastel/api/queries'; import { useColorInfo } from '@/lib/pastel/api/queries';
import { useColorHistory } from '@/lib/pastel/stores/historyStore'; import { useColorHistory } from '@/lib/pastel/stores/historyStore';
import { Loader2, Share2, History, X } from 'lucide-react'; import { Loader2, Share2, History, X } from 'lucide-react';
@@ -73,16 +74,11 @@ function PlaygroundContent() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Pastel"
<div> description="Interactive color manipulation and analysis tool"
<h1 className="text-4xl font-bold mb-2">Pastel</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Interactive color manipulation and analysis tool
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Left Column: Color Picker and Display */} {/* Left Column: Color Picker and Display */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -197,8 +193,7 @@ function PlaygroundContent() {
</Card> </Card>
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -5,6 +5,7 @@ import { ColorPicker } from '@/components/pastel/ColorPicker';
import { ColorDisplay } from '@/components/pastel/ColorDisplay'; import { ColorDisplay } from '@/components/pastel/ColorDisplay';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { AppPage } from '@/components/layout/AppPage';
import { useTextColor } from '@/lib/pastel/api/queries'; import { useTextColor } from '@/lib/pastel/api/queries';
import { Loader2, Palette, Plus, X, CheckCircle2, XCircle } from 'lucide-react'; import { Loader2, Palette, Plus, X, CheckCircle2, XCircle } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@@ -55,16 +56,11 @@ export default function TextColorPage() {
}; };
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Text Color Optimizer"
<div> description="Automatically find the best text color (black or white) for any background color"
<h1 className="text-4xl font-bold mb-2">Text Color Optimizer</h1> >
<p className="text-muted-foreground"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
Automatically find the best text color (black or white) for any background color
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Input */} {/* Input */}
<div className="space-y-6"> <div className="space-y-6">
<Card> <Card>
@@ -226,7 +222,6 @@ export default function TextColorPage() {
)} )}
</div> </div>
</div> </div>
</div> </AppPage>
</div>
); );
} }

View File

@@ -1,17 +1,13 @@
import MainConverter from '@/components/units/MainConverter'; import MainConverter from '@/components/units/MainConverter';
import { AppPage } from '@/components/layout/AppPage';
export default function UnitsPage() { export default function UnitsPage() {
return ( return (
<div className="min-h-screen py-12"> <AppPage
<div className="max-w-7xl mx-auto px-8 space-y-8"> title="Units Converter"
<div> description="Smart unit converter with 187 units across 23 categories"
<h1 className="text-4xl font-bold mb-2">Units Converter</h1> >
<p className="text-muted-foreground"> <MainConverter />
Smart unit converter with 187 units across 23 categories </AppPage>
</p>
</div>
<MainConverter />
</div>
</div>
); );
} }

View File

@@ -0,0 +1,29 @@
'use client';
import * as React from 'react';
import { cn } from '@/lib/utils';
interface AppPageProps {
title: string;
description?: string;
children: React.ReactNode;
className?: string;
}
export function AppPage({ title, description, children, className }: AppPageProps) {
return (
<div className={cn("min-h-screen py-12", className)}>
<div className="max-w-7xl mx-auto px-8 space-y-8">
<div>
<h1 className="text-4xl font-bold mb-2">{title}</h1>
{description && (
<p className="text-muted-foreground">
{description}
</p>
)}
</div>
{children}
</div>
</div>
);
}