feat: update dashboard and API keys pages to use DashboardLayout
- Convert dashboard overview to use DashboardLayout with sidebar - Update API keys page with DashboardLayout and table layout - Add upload icon to icon library - Consistent sidebar navigation across all dashboard pages
This commit is contained in:
parent
1efb9b0003
commit
d0e304cdab
3 changed files with 246 additions and 188 deletions
|
|
@ -1,108 +1,91 @@
|
|||
import { AppHeader } from '@/components/AppHeader';
|
||||
import { SignOutButton } from '@/components/auth/SignOutButton';
|
||||
import { auth } from '@/lib/auth';
|
||||
'use client';
|
||||
|
||||
import { useSession } from '@/lib/auth-client';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import { headers } from 'next/headers';
|
||||
import Link from 'next/link';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { DashboardLayout } from '~/components/dashboard/DashboardLayout';
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers(),
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
redirect('/sign-in');
|
||||
}
|
||||
export default function DashboardPage(): React.ReactElement {
|
||||
const { data: session } = useSession();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppHeader />
|
||||
<div className="max-w-4xl mx-auto py-12 px-4">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
|
||||
<SignOutButton />
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 mb-8">
|
||||
<Link href="/dashboard/agents" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="terminal" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">My Agents</h2>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Create and manage AI agents with tools
|
||||
</p>
|
||||
</div>
|
||||
<DashboardLayout title="Overview">
|
||||
{/* Quick Actions */}
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 mb-8">
|
||||
<Link href="/dashboard/agents" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="terminal" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">My Agents</h2>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Create and manage AI agents with tools
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/dashboard/collections" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="folder" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">My Collections</h2>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Organize and share your favorite tools
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/dashboard/collections" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="folder" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">My Collections</h2>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Organize and share your favorite tools
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/dashboard/settings/api-keys" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="key" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">API Keys</h2>
|
||||
<p className="text-sm text-foreground-secondary">
|
||||
Manage AI provider credentials
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/dashboard/settings/api-keys" className="block">
|
||||
<div className="bg-background border border-border rounded-lg p-6 hover:border-foreground/20 transition-colors group">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-lg bg-primary/10 flex items-center justify-center group-hover:bg-primary/20 transition-colors">
|
||||
<Icon icon="key" size="md" className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-medium text-foreground">API Keys</h2>
|
||||
<p className="text-sm text-foreground-secondary">Manage AI provider credentials</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Profile Section */}
|
||||
<div className="bg-background border border-border rounded-lg p-6">
|
||||
<h2 className="text-lg font-medium text-foreground mb-4">Profile</h2>
|
||||
{/* Profile Section */}
|
||||
<div className="bg-background border border-border rounded-lg p-6">
|
||||
<h2 className="text-lg font-medium text-foreground mb-4">Profile</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Name</span>
|
||||
<p className="text-foreground font-medium">{session.user.name}</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Name</span>
|
||||
<p className="text-foreground font-medium">{session?.user?.name || 'User'}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Email</span>
|
||||
<p className="text-foreground font-medium">{session.user.email}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Email</span>
|
||||
<p className="text-foreground font-medium">{session?.user?.email}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Email Verified</span>
|
||||
<p className="text-foreground font-medium">
|
||||
{session.user.emailVerified ? (
|
||||
<span className="text-green-600">Verified</span>
|
||||
) : (
|
||||
<span className="text-amber-600">Not verified</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="block text-sm text-foreground-secondary">Email Verified</span>
|
||||
<p className="text-foreground font-medium">
|
||||
{session?.user?.emailVerified ? (
|
||||
<span className="text-success">Verified</span>
|
||||
) : (
|
||||
<span className="text-warning">Not verified</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,18 @@
|
|||
|
||||
import { Button } from '@tpmjs/ui/Button/Button';
|
||||
import { Icon } from '@tpmjs/ui/Icon/Icon';
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableEmpty,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@tpmjs/ui/Table/Table';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { AppHeader } from '~/components/AppHeader';
|
||||
import { DashboardLayout } from '~/components/dashboard/DashboardLayout';
|
||||
|
||||
interface ApiKeyInfo {
|
||||
id: string;
|
||||
|
|
@ -15,6 +23,15 @@ interface ApiKeyInfo {
|
|||
updatedAt: string;
|
||||
}
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
export default function ApiKeysPage(): React.ReactElement {
|
||||
const router = useRouter();
|
||||
const [keys, setKeys] = useState<ApiKeyInfo[]>([]);
|
||||
|
|
@ -22,6 +39,7 @@ export default function ApiKeysPage(): React.ReactElement {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Add new key form
|
||||
const [showAddForm, setShowAddForm] = useState(false);
|
||||
const [newKeyName, setNewKeyName] = useState('');
|
||||
const [newKeyValue, setNewKeyValue] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
|
@ -80,6 +98,7 @@ export default function ApiKeysPage(): React.ReactElement {
|
|||
if (result.success) {
|
||||
setNewKeyName('');
|
||||
setNewKeyValue('');
|
||||
setShowAddForm(false);
|
||||
fetchKeys();
|
||||
} else {
|
||||
setSaveError(result.error || 'Failed to save');
|
||||
|
|
@ -88,7 +107,8 @@ export default function ApiKeysPage(): React.ReactElement {
|
|||
setSaving(false);
|
||||
}, [newKeyName, newKeyValue, saveKey, fetchKeys]);
|
||||
|
||||
const handleDelete = useCallback(async (keyName: string, id: string) => {
|
||||
const handleDelete = useCallback(async (keyName: string, id: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (!confirm(`Delete ${keyName}?`)) return;
|
||||
|
||||
setDeletingId(id);
|
||||
|
|
@ -135,137 +155,188 @@ export default function ApiKeysPage(): React.ReactElement {
|
|||
fetchKeys();
|
||||
}, [envText, saveKey, fetchKeys]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppHeader />
|
||||
<div className="max-w-2xl mx-auto py-12 px-4">
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-8 bg-surface-secondary rounded w-32" />
|
||||
<div className="h-16 bg-surface-secondary rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppHeader />
|
||||
<div className="max-w-2xl mx-auto py-12 px-4 text-center">
|
||||
<p className="text-error mb-4">{error}</p>
|
||||
<Button onClick={fetchKeys}>Retry</Button>
|
||||
<DashboardLayout title="API Keys">
|
||||
<div className="text-center py-16">
|
||||
<Icon icon="alertCircle" size="lg" className="mx-auto text-error mb-4" />
|
||||
<h2 className="text-lg font-medium text-foreground mb-2">Error</h2>
|
||||
<p className="text-foreground-secondary mb-4">{error}</p>
|
||||
<Button onClick={fetchKeys}>Try Again</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<AppHeader />
|
||||
<div className="max-w-2xl mx-auto py-12 px-4">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<Link href="/dashboard" className="text-foreground-secondary hover:text-foreground">
|
||||
<Icon icon="arrowLeft" size="sm" />
|
||||
</Link>
|
||||
<h1 className="text-2xl font-bold text-foreground">Environment Variables</h1>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowImport(!showImport)}
|
||||
className="text-sm text-primary hover:underline"
|
||||
>
|
||||
{showImport ? 'Hide' : 'Import .env'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Import from .env */}
|
||||
{showImport && (
|
||||
<div className="mb-6 p-4 bg-surface-secondary border border-border rounded-lg">
|
||||
<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-background border border-border rounded text-foreground font-mono text-sm placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleImport}
|
||||
disabled={importing || !envText.trim()}
|
||||
className="mt-2"
|
||||
>
|
||||
{importing ? 'Importing...' : 'Import'}
|
||||
<DashboardLayout
|
||||
title="API Keys"
|
||||
subtitle={keys.length > 0 ? `${keys.length} key${keys.length !== 1 ? 's' : ''}` : undefined}
|
||||
actions={
|
||||
!showAddForm &&
|
||||
!showImport && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setShowImport(true)}>
|
||||
<Icon icon="upload" size="sm" className="mr-2" />
|
||||
Import .env
|
||||
</Button>
|
||||
<Button onClick={() => setShowAddForm(true)}>
|
||||
<Icon icon="plus" size="sm" className="mr-2" />
|
||||
Add Key
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
}
|
||||
>
|
||||
{/* Import from .env */}
|
||||
{showImport && (
|
||||
<div className="bg-background border border-border rounded-lg p-6 mb-6">
|
||||
<h2 className="text-lg font-medium text-foreground mb-4">Import from .env</h2>
|
||||
<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"
|
||||
/>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Button onClick={handleImport} disabled={importing || !envText.trim()}>
|
||||
{importing ? 'Importing...' : 'Import Keys'}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => setShowImport(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add new key */}
|
||||
<div className="mb-6 p-4 bg-surface-secondary border border-border rounded-lg">
|
||||
<h2 className="text-sm font-medium text-foreground mb-3">Add New Key</h2>
|
||||
{saveError && <p className="text-red-500 text-sm mb-2">{saveError}</p>}
|
||||
<div className="flex gap-2 mb-2">
|
||||
{/* Add new key form */}
|
||||
{showAddForm && (
|
||||
<div className="bg-background border border-border rounded-lg p-6 mb-6">
|
||||
<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
|
||||
type="text"
|
||||
value={newKeyName}
|
||||
onChange={(e) =>
|
||||
setNewKeyName(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, ''))
|
||||
}
|
||||
placeholder="KEY_NAME"
|
||||
className="flex-1 px-3 py-2 bg-background border border-border rounded text-foreground text-sm font-mono placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="password"
|
||||
value={newKeyValue}
|
||||
onChange={(e) => setNewKeyValue(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleAddKey()}
|
||||
placeholder="Value"
|
||||
className="flex-1 px-3 py-2 bg-background border border-border rounded text-foreground text-sm font-mono placeholder:text-foreground-tertiary focus:outline-none focus:ring-2 focus:ring-primary/50"
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Button
|
||||
onClick={handleAddKey}
|
||||
disabled={saving || !newKeyName.trim() || !newKeyValue.trim()}
|
||||
>
|
||||
{saving ? 'Saving...' : 'Add'}
|
||||
{saving ? 'Saving...' : 'Save Key'}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => setShowAddForm(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Keys list */}
|
||||
{keys.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{keys.map((key) => (
|
||||
<div
|
||||
key={key.id}
|
||||
className="flex items-center justify-between px-4 py-3 bg-surface-secondary border border-border rounded-lg"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="font-mono text-sm text-foreground">{key.keyName}</span>
|
||||
<span className="text-xs text-foreground-tertiary">...{key.keyHint}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleDelete(key.keyName, key.id)}
|
||||
disabled={deletingId === key.id}
|
||||
className="text-foreground-tertiary hover:text-red-500 disabled:opacity-50"
|
||||
>
|
||||
<Icon icon="trash" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-center text-foreground-tertiary py-8">
|
||||
No environment variables stored yet.
|
||||
</p>
|
||||
)}
|
||||
{/* Keys Table */}
|
||||
<div className="bg-background border border-border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[300px]">Key Name</TableHead>
|
||||
<TableHead>Value</TableHead>
|
||||
<TableHead>Added</TableHead>
|
||||
<TableHead className="w-[80px] text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<>
|
||||
{[0, 1, 2].map((idx) => (
|
||||
<TableRow key={`key-skeleton-${idx}`}>
|
||||
<TableCell>
|
||||
<div className="h-4 w-40 bg-surface-secondary rounded animate-pulse" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="h-4 w-20 bg-surface-secondary rounded animate-pulse" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="h-4 w-24 bg-surface-secondary rounded animate-pulse" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="h-8 w-8 bg-surface-secondary rounded animate-pulse ml-auto" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
) : keys.length === 0 ? (
|
||||
<TableEmpty
|
||||
colSpan={4}
|
||||
icon={
|
||||
<div className="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<Icon icon="key" size="lg" className="text-primary" />
|
||||
</div>
|
||||
}
|
||||
title="No API keys yet"
|
||||
description="Add your AI provider API keys to enable agent functionality. Keys are encrypted and stored securely."
|
||||
action={
|
||||
<Button onClick={() => setShowAddForm(true)}>
|
||||
<Icon icon="plus" size="sm" className="mr-2" />
|
||||
Add Your First Key
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
keys.map((key) => (
|
||||
<TableRow key={key.id}>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
|
||||
<Icon icon="key" size="sm" className="text-primary" />
|
||||
</div>
|
||||
<span className="font-mono text-sm text-foreground">{key.keyName}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-foreground-tertiary font-mono text-sm">
|
||||
•••••••{key.keyHint}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-foreground-secondary text-sm">
|
||||
{formatDate(key.createdAt)}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center justify-end">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={(e) => handleDelete(key.keyName, key.id, e)}
|
||||
disabled={deletingId === key.id}
|
||||
>
|
||||
<Icon icon="trash" size="xs" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@ export const icons = {
|
|||
viewBox: '0 0 24 24',
|
||||
path: 'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z',
|
||||
},
|
||||
upload: {
|
||||
viewBox: '0 0 24 24',
|
||||
path: 'M9 16h6v-6h4l-7-7-7 7h4v6zm-4 2h14v2H5v-2z',
|
||||
},
|
||||
alertCircle: {
|
||||
viewBox: '0 0 24 24',
|
||||
path: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue