refactor: apply style guide compliance across auth, dashboard, and tool pages

- Replace hardcoded color classes with design system tokens (error, success, warning, info)
- Replace raw HTML inputs with Input component from @tpmjs/ui
- Replace raw HTML buttons with Button component
- Add eye/eyeOff icons for password visibility toggle
- Add mail icon for verify-email page
- Fix text colors to use semantic tokens (text-error, text-success, text-warning)
- Fix background colors to use opacity tokens (bg-error/10, bg-success/10)
- All pages now properly support dark mode through CSS custom properties
This commit is contained in:
Ajax Davis 2026-01-16 06:49:47 +10:00
parent 066e599293
commit 499a38ce1b
10 changed files with 102 additions and 174 deletions

View file

@ -1,5 +1,7 @@
'use client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Input } from '@tpmjs/ui/Input/Input';
import Link from 'next/link';
import { useState } from 'react';
@ -54,7 +56,7 @@ export default function ForgotPasswordPage() {
</p>
</div>
<div className="bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800 text-green-700 dark:text-green-300 px-4 py-3 rounded-md text-sm">
<div className="bg-success/10 border border-success/20 text-success px-4 py-3 rounded-md text-sm">
Check your inbox and click the link to reset your password. The link will expire in 1
hour.
</div>
@ -93,7 +95,7 @@ export default function ForgotPasswordPage() {
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 px-4 py-3 rounded-md text-sm">
<div className="bg-error/10 border border-error/20 text-error px-4 py-3 rounded-md text-sm">
{error}
</div>
)}
@ -102,24 +104,24 @@ export default function ForgotPasswordPage() {
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
Email
</label>
<input
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="you@example.com"
/>
</div>
<button
<Button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-foreground text-background font-medium rounded-md hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-foreground disabled:opacity-50 disabled:cursor-not-allowed"
loading={loading}
className="w-full"
>
{loading ? 'Sending...' : 'Send Reset Link'}
</button>
</Button>
</form>
<p className="text-center text-sm text-foreground-secondary">

View file

@ -1,47 +1,11 @@
'use client';
import { signIn } from '@/lib/auth-client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import Link from 'next/link';
import { useState } from 'react';
function EyeIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className={className || 'w-5 h-5'}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
/>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
);
}
function EyeOffIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className={className || 'w-5 h-5'}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88"
/>
</svg>
);
}
import { signIn } from '~/lib/auth-client';
export default function SignInPage() {
const [email, setEmail] = useState('');
@ -107,7 +71,7 @@ export default function SignInPage() {
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md text-sm">
<div className="bg-error/10 border border-error/20 text-error px-4 py-3 rounded-md text-sm">
{error}
</div>
)}
@ -116,13 +80,12 @@ export default function SignInPage() {
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
Email
</label>
<input
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="you@example.com"
/>
</div>
@ -140,14 +103,14 @@ export default function SignInPage() {
</Link>
</div>
<div className="relative">
<input
<Input
id="password"
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full px-3 py-2 pr-10 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="Enter your password"
className="pr-10"
/>
<button
type="button"
@ -155,18 +118,19 @@ export default function SignInPage() {
className="absolute right-3 top-1/2 -translate-y-1/2 text-foreground-tertiary hover:text-foreground transition-colors"
aria-label={showPassword ? 'Hide password' : 'Show password'}
>
{showPassword ? <EyeOffIcon className="w-4 h-4" /> : <EyeIcon className="w-4 h-4" />}
<Icon icon={showPassword ? 'eyeOff' : 'eye'} size="sm" />
</button>
</div>
</div>
<button
<Button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-foreground text-background font-medium rounded-md hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-foreground disabled:opacity-50 disabled:cursor-not-allowed"
loading={loading}
className="w-full"
>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</Button>
</form>
<p className="text-center text-sm text-foreground-secondary">

View file

@ -1,9 +1,12 @@
'use client';
import { signUp } from '@/lib/auth-client';
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { suggestUsername } from '@tpmjs/types/user';
import Link from 'next/link';
import { useCallback, useEffect, useState } from 'react';
import { signUp } from '~/lib/auth-client';
interface UsernameCheckResult {
available: boolean;
@ -173,7 +176,7 @@ export default function SignUpPage() {
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md text-sm">
<div className="bg-error/10 border border-error/20 text-error px-4 py-3 rounded-md text-sm">
{error}
</div>
)}
@ -182,13 +185,12 @@ export default function SignUpPage() {
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1">
Name
</label>
<input
<Input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="Your name"
/>
</div>
@ -201,7 +203,7 @@ export default function SignUpPage() {
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-foreground-secondary">
@
</div>
<input
<Input
id="username"
type="text"
value={username}
@ -209,74 +211,31 @@ export default function SignUpPage() {
required
minLength={3}
maxLength={30}
className="w-full pl-7 pr-10 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
className="pl-7 pr-10"
placeholder="username"
/>
{/* Status indicator */}
<div className="absolute inset-y-0 right-0 pr-3 flex items-center">
{checkingUsername && (
<svg
className="animate-spin h-4 w-4 text-foreground-secondary"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<Icon icon="loader" size="sm" className="animate-spin text-foreground-secondary" />
)}
{!checkingUsername && usernameCheck?.available && (
<svg
className="h-4 w-4 text-green-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
<Icon icon="check" size="sm" className="text-success" />
)}
{!checkingUsername &&
usernameCheck &&
!usernameCheck.available &&
username.length >= 3 && (
<svg
className="h-4 w-4 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
<Icon icon="x" size="sm" className="text-error" />
)}
</div>
</div>
{/* Username availability message */}
{username.length >= 3 && usernameCheck && !usernameCheck.available && (
<p className="mt-1 text-xs text-red-500">{usernameCheck.reason}</p>
<p className="mt-1 text-xs text-error">{usernameCheck.reason}</p>
)}
{username.length >= 3 && usernameCheck?.available && (
<p className="mt-1 text-xs text-green-600">Username available</p>
<p className="mt-1 text-xs text-success">Username available</p>
)}
{username && (
<p className="mt-1 text-xs text-foreground-tertiary">
@ -289,13 +248,12 @@ export default function SignUpPage() {
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
Email
</label>
<input
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="you@example.com"
/>
</div>
@ -304,25 +262,25 @@ export default function SignUpPage() {
<label htmlFor="password" className="block text-sm font-medium text-foreground mb-1">
Password
</label>
<input
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-foreground focus:border-transparent"
placeholder="At least 8 characters"
/>
</div>
<button
<Button
type="submit"
disabled={loading || (usernameCheck !== null && !usernameCheck.available)}
className="w-full py-2 px-4 bg-foreground text-background font-medium rounded-md hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-foreground disabled:opacity-50 disabled:cursor-not-allowed"
loading={loading}
className="w-full"
>
{loading ? 'Creating account...' : 'Create Account'}
</button>
</Button>
</form>
<p className="text-center text-sm text-foreground-secondary">

View file

@ -1,22 +1,11 @@
import { Icon } from '@tpmjs/ui/Icon/Icon';
import Link from 'next/link';
export default function VerifyEmailPage() {
return (
<div className="space-y-6 text-center">
<div className="mx-auto w-16 h-16 bg-foreground/10 rounded-full flex items-center justify-center">
<svg
className="w-8 h-8 text-foreground"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
/>
</svg>
<Icon icon="mail" size="lg" className="text-foreground" />
</div>
<div>

View file

@ -2,6 +2,8 @@
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import { Textarea } from '@tpmjs/ui/Textarea/Textarea';
import {
Table,
TableBody,
@ -176,9 +178,9 @@ export default function ApiKeysPage(): React.ReactElement {
}
>
{/* Info banner */}
<div className="bg-blue-500/5 border border-blue-500/20 rounded-lg p-4 mb-6">
<div className="bg-info/10 border border-info/20 rounded-lg p-4 mb-6">
<div className="flex gap-3">
<Icon icon="info" size="sm" className="text-blue-500 flex-shrink-0 mt-0.5" />
<Icon icon="info" size="sm" className="text-info flex-shrink-0 mt-0.5" />
<div className="text-sm">
<p className="text-foreground font-medium mb-1">
These are your AI service credentials
@ -195,14 +197,14 @@ export default function ApiKeysPage(): React.ReactElement {
{showImport && (
<div className="bg-surface border border-border rounded-lg p-6 mb-6">
<h2 className="text-lg font-medium text-foreground mb-4">Import from .env</h2>
<textarea
<Textarea
value={envText}
onChange={(e) => setEnvText(e.target.value)}
placeholder="Paste .env content here...
MY_API_KEY=abc123
ANOTHER_SECRET=xyz789"
rows={5}
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground font-mono text-sm placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none"
className="font-mono text-sm resize-none"
/>
<div className="flex items-center gap-2 mt-4">
<Button onClick={handleImport} disabled={importing || !envText.trim()}>
@ -221,22 +223,22 @@ ANOTHER_SECRET=xyz789"
<h2 className="text-lg font-medium text-foreground mb-4">Add New Key</h2>
{saveError && <p className="text-error text-sm mb-3">{saveError}</p>}
<div className="space-y-3">
<input
<Input
type="text"
value={newKeyName}
onChange={(e) =>
setNewKeyName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, ''))
}
placeholder="KEY_NAME (e.g., OPENAI_API_KEY)"
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground text-sm font-mono placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50"
className="font-mono text-sm"
/>
<input
<Input
type="password"
value={newKeyValue}
onChange={(e) => setNewKeyValue(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAddKey()}
placeholder="Value"
className="w-full px-3 py-2 bg-surface border border-border rounded-lg text-foreground text-sm font-mono placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50"
className="font-mono text-sm"
/>
</div>
<div className="flex items-center gap-2 mt-4">
@ -288,8 +290,8 @@ ANOTHER_SECRET=xyz789"
<TableEmpty
colSpan={4}
icon={
<div className="w-16 h-16 rounded-full bg-blue-500/10 flex items-center justify-center">
<Icon icon="puzzle" size="lg" className="text-blue-500" />
<div className="w-16 h-16 rounded-full bg-info/10 flex items-center justify-center">
<Icon icon="puzzle" size="lg" className="text-info" />
</div>
}
title="No AI provider keys yet"

View file

@ -2,6 +2,7 @@
import { Button } from '@tpmjs/ui/Button/Button';
import { Icon } from '@tpmjs/ui/Icon/Icon';
import { Input } from '@tpmjs/ui/Input/Input';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { Suspense, useCallback, useEffect, useState } from 'react';
@ -192,11 +193,11 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
<form onSubmit={handleSubmit} className="space-y-6">
{/* Setup mode banner */}
{isSetupMode && !profile?.username && (
<div className="bg-amber-50 border border-amber-200 text-amber-800 px-4 py-3 rounded-lg text-sm flex items-start gap-2">
<div className="bg-warning/10 border border-warning/20 text-warning px-4 py-3 rounded-lg text-sm flex items-start gap-2">
<Icon icon="alertCircle" size="sm" className="mt-0.5 shrink-0" />
<div>
<p className="font-medium">Complete your account setup</p>
<p className="text-amber-700 mt-1">
<p className="text-warning/80 mt-1">
Please set your username to complete your account setup. This is required to use
MCP endpoints and public profiles.
</p>
@ -206,7 +207,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
{/* Success message */}
{saveSuccess && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm flex items-center gap-2">
<div className="bg-success/10 border border-success/20 text-success px-4 py-3 rounded-lg text-sm flex items-center gap-2">
<Icon icon="check" size="sm" />
Profile updated successfully!
</div>
@ -214,7 +215,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
{/* Error message */}
{saveError && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm">
<div className="bg-error/10 border border-error/20 text-error px-4 py-3 rounded-lg text-sm">
{saveError}
</div>
)}
@ -224,7 +225,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
<label htmlFor="name" className="block text-sm font-medium text-foreground mb-1">
Display Name
</label>
<input
<Input
id="name"
type="text"
value={name}
@ -233,7 +234,6 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
setSaveSuccess(false);
}}
required
className="w-full px-3 py-2 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
placeholder="Your name"
/>
</div>
@ -244,10 +244,10 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
Username
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-foreground-secondary">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-foreground-secondary z-10">
@
</div>
<input
<Input
id="username"
type="text"
value={username}
@ -255,7 +255,7 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
required
minLength={3}
maxLength={30}
className="w-full pl-7 pr-10 py-2 border border-border rounded-lg bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
className="pl-7 pr-10"
placeholder="username"
/>
{/* Status indicator */}
@ -268,20 +268,20 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
/>
)}
{!checkingUsername && usernameCheck?.available && (
<Icon icon="check" size="sm" className="text-green-500" />
<Icon icon="check" size="sm" className="text-success" />
)}
{!checkingUsername &&
usernameCheck &&
!usernameCheck.available &&
username.length >= 3 && <Icon icon="x" size="sm" className="text-red-500" />}
username.length >= 3 && <Icon icon="x" size="sm" className="text-error" />}
</div>
</div>
{/* Username availability message */}
{username.length >= 3 && usernameCheck && !usernameCheck.available && (
<p className="mt-1 text-xs text-red-500">{usernameCheck.reason}</p>
<p className="mt-1 text-xs text-error">{usernameCheck.reason}</p>
)}
{username.length >= 3 && usernameCheck?.available && (
<p className="mt-1 text-xs text-green-600">
<p className="mt-1 text-xs text-success">
{username === profile.username ? 'Current username' : 'Username available'}
</p>
)}
@ -295,12 +295,12 @@ function ProfileSettingsContent({ isSetupMode }: { isSetupMode: boolean }): Reac
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-1">
Email
</label>
<input
<Input
id="email"
type="email"
value={profile.email}
disabled
className="w-full px-3 py-2 border border-border rounded-lg bg-surface-secondary text-foreground-secondary cursor-not-allowed"
className="bg-surface-secondary text-foreground-secondary cursor-not-allowed"
/>
<p className="mt-1 text-xs text-foreground-tertiary">Email cannot be changed</p>
</div>

View file

@ -269,14 +269,14 @@ export function ToolDetailClient({ tool, slug }: ToolDetailClientProps): React.R
{/* Auto-discovery info banner */}
{tool.toolDiscoverySource === 'auto' && (
<div className="mb-6 p-4 rounded-lg bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-900">
<div className="mb-6 p-4 rounded-lg bg-warning/10 border border-warning/20">
<div className="flex items-start gap-3">
<span className="text-xl mt-0.5">🔍</span>
<div className="flex-1">
<h3 className="text-sm font-semibold text-amber-800 dark:text-amber-300 mb-1">
<h3 className="text-sm font-semibold text-warning mb-1">
Auto-discovered tool
</h3>
<p className="text-sm text-amber-700 dark:text-amber-400">
<p className="text-sm text-warning/80">
This tool was automatically discovered from the package exports. The author did
not explicitly register it in their{' '}
<code className="font-mono">package.json</code>. Schema and description were
@ -289,14 +289,14 @@ export function ToolDetailClient({ tool, slug }: ToolDetailClientProps): React.R
{/* Health warning banner */}
{(tool.importHealth === 'BROKEN' || tool.executionHealth === 'BROKEN') && (
<div className="mb-6 p-4 rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900">
<div className="mb-6 p-4 rounded-lg bg-error/10 border border-error/20">
<div className="flex items-start gap-3">
<span className="text-xl mt-0.5"></span>
<div className="flex-1">
<h3 className="text-sm font-semibold text-red-800 dark:text-red-300 mb-1">
<h3 className="text-sm font-semibold text-error mb-1">
This tool is currently broken
</h3>
<div className="space-y-1 text-sm text-red-700 dark:text-red-400">
<div className="space-y-1 text-sm text-error/80">
{tool.importHealth === 'BROKEN' && (
<div className="flex items-center gap-2">
<Badge variant="error" size="sm">
@ -315,23 +315,24 @@ export function ToolDetailClient({ tool, slug }: ToolDetailClientProps): React.R
)}
</div>
{tool.healthCheckError && (
<pre className="mt-2 p-2 rounded bg-red-100 dark:bg-red-900/30 text-xs font-mono text-red-800 dark:text-red-300 overflow-x-auto whitespace-pre-wrap">
<pre className="mt-2 p-2 rounded bg-error/10 text-xs font-mono text-error overflow-x-auto whitespace-pre-wrap">
{tool.healthCheckError}
</pre>
)}
{tool.lastHealthCheck && (
<p className="text-xs text-red-600 dark:text-red-500 mt-2">
<p className="text-xs text-error/60 mt-2">
Last checked: {new Date(tool.lastHealthCheck).toLocaleString()}
</p>
)}
<button
type="button"
<Button
variant="ghost"
size="sm"
onClick={recheckHealth}
disabled={recheckLoading}
className="mt-3 text-sm font-medium text-red-700 dark:text-red-400 hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
className="mt-3 text-error hover:text-error"
>
{recheckLoading ? 'Rechecking...' : 'Recheck health →'}
</button>
</Button>
</div>
</div>
</div>

View file

@ -97,14 +97,14 @@ export default function BrokenToolsPage(): React.ReactElement {
)}
{/* Error state */}
{error && <div className="text-center py-12 text-red-500">Error: {error}</div>}
{error && <div className="text-center py-12 text-error">Error: {error}</div>}
{/* Empty state - All healthy! */}
{!loading && !error && tools.length === 0 && (
<Card className="text-center py-12">
<CardContent className="space-y-4">
<div className="flex justify-center">
<Icon icon="check" size="lg" className="text-green-600 dark:text-green-400" />
<Icon icon="check" size="lg" className="text-success" />
</div>
<div>
<h2 className="text-2xl font-bold text-foreground mb-2">All Tools are Healthy!</h2>
@ -124,14 +124,14 @@ export default function BrokenToolsPage(): React.ReactElement {
{/* Broken tools grid */}
{!loading && !error && tools.length > 0 && (
<>
<div className="mb-6 p-4 rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-900">
<div className="mb-6 p-4 rounded-lg bg-error/10 border border-error/20">
<div className="flex items-start gap-3">
<span className="text-xl mt-0.5"></span>
<div>
<h3 className="text-sm font-semibold text-red-800 dark:text-red-300 mb-1">
<h3 className="text-sm font-semibold text-error mb-1">
{tools.length} {tools.length === 1 ? 'tool is' : 'tools are'} currently broken
</h3>
<p className="text-sm text-red-700 dark:text-red-400">
<p className="text-sm text-error/80">
These tools are experiencing import or execution failures. Click on a tool to
see details and manually trigger a health recheck.
</p>
@ -148,7 +148,7 @@ export default function BrokenToolsPage(): React.ReactElement {
: null;
return (
<Card key={tool.id} className="flex flex-col border-red-200 dark:border-red-900">
<Card key={tool.id} className="flex flex-col border-error/20">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="flex-1">

View file

@ -352,7 +352,7 @@ export default function ToolSearchPage(): React.ReactElement {
)}
{/* Error state */}
{error && <div className="text-center py-12 text-red-500">Error: {error}</div>}
{error && <div className="text-center py-12 text-error">Error: {error}</div>}
{/* Tool table */}
{!loading && !error && filteredTools.length > 0 && (

View file

@ -204,6 +204,18 @@ export const icons = {
viewBox: '0 0 24 24',
path: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z',
},
eye: {
viewBox: '0 0 24 24',
path: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z',
},
eyeOff: {
viewBox: '0 0 24 24',
path: 'M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z',
},
mail: {
viewBox: '0 0 24 24',
path: 'M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z',
},
} as const;
export type IconName = keyof typeof icons;