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:
parent
066e599293
commit
499a38ce1b
10 changed files with 102 additions and 174 deletions
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue