Implement dark-first design system with Geist typography (#28)
Co-authored-by: Julian Tabel <juliantabel.jt@gmail.com> Co-committed-by: Julian Tabel <juliantabel.jt@gmail.com>
This commit was merged in pull request #28.
This commit is contained in:
@@ -83,19 +83,19 @@ export function AdminEvolutions() {
|
||||
const data = await exportEvolutions()
|
||||
downloadJson(data, 'evolutions.json')
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowBulkImport(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Bulk Import
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Evolution
|
||||
</button>
|
||||
@@ -111,7 +111,7 @@ export function AdminEvolutions() {
|
||||
setPage(0)
|
||||
}}
|
||||
placeholder="Search by pokemon name, trigger, or item..."
|
||||
className="w-full max-w-sm px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="w-full max-w-sm px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
/>
|
||||
<select
|
||||
value={triggerFilter}
|
||||
@@ -119,7 +119,7 @@ export function AdminEvolutions() {
|
||||
setTriggerFilter(e.target.value)
|
||||
setPage(0)
|
||||
}}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All triggers</option>
|
||||
{EVOLUTION_TRIGGERS.map((t) => (
|
||||
@@ -135,14 +135,12 @@ export function AdminEvolutions() {
|
||||
setTriggerFilter('')
|
||||
setPage(0)
|
||||
}}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className="text-sm text-text-tertiary hover:text-text-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
{total} evolutions
|
||||
</span>
|
||||
<span className="text-sm text-text-tertiary whitespace-nowrap">{total} evolutions</span>
|
||||
</div>
|
||||
|
||||
<AdminTable
|
||||
@@ -156,38 +154,38 @@ export function AdminEvolutions() {
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
<div className="text-sm text-text-tertiary">
|
||||
Showing {offset + 1}-{Math.min(offset + PAGE_SIZE, total)} of {total}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setPage(0)}
|
||||
disabled={page === 0}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
First
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||
disabled={page === 0}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300 px-2">
|
||||
<span className="text-sm text-text-secondary px-2">
|
||||
Page {page + 1} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
disabled={page >= totalPages - 1}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage(totalPages - 1)}
|
||||
disabled={page >= totalPages - 1}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Last
|
||||
</button>
|
||||
|
||||
@@ -95,18 +95,15 @@ function SortableRouteGroup({
|
||||
<tbody
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
className={`${isDragging ? 'opacity-50 bg-blue-50 dark:bg-blue-900/20' : ''} divide-y divide-gray-200 dark:divide-gray-700`}
|
||||
className={`${isDragging ? 'opacity-50 bg-blue-50 dark:bg-blue-900/20' : ''} divide-y divide-border-default`}
|
||||
>
|
||||
<tr
|
||||
className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"
|
||||
onClick={() => onClick(group)}
|
||||
>
|
||||
<tr className="hover:bg-surface-2 cursor-pointer" onClick={() => onClick(group)}>
|
||||
<td className="px-4 py-3 text-sm w-12">
|
||||
<button
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 touch-none"
|
||||
className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-text-secondary touch-none"
|
||||
title="Drag to reorder"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
@@ -128,7 +125,7 @@ function SortableRouteGroup({
|
||||
<Link
|
||||
to={`/admin/games/${gameId}/routes/${group.id}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
className="text-text-link hover:underline"
|
||||
>
|
||||
Encounters
|
||||
</Link>
|
||||
@@ -137,15 +134,15 @@ function SortableRouteGroup({
|
||||
{group.children.map((child) => (
|
||||
<tr
|
||||
key={child.id}
|
||||
className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"
|
||||
className="hover:bg-surface-2 cursor-pointer"
|
||||
onClick={() => onClick(child)}
|
||||
>
|
||||
<td className="px-4 py-3 text-sm w-12" />
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap w-16 text-gray-400 dark:text-gray-500">
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap w-16 text-text-muted">
|
||||
{child.order}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap pl-8 text-gray-600 dark:text-gray-400">
|
||||
<span className="text-gray-300 dark:text-gray-600 mr-1.5">{'\u2514'}</span>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap pl-8 text-text-tertiary">
|
||||
<span className="text-text-muted mr-1.5">{'\u2514'}</span>
|
||||
{child.name}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap text-center">
|
||||
@@ -155,7 +152,7 @@ function SortableRouteGroup({
|
||||
<Link
|
||||
to={`/admin/games/${gameId}/routes/${child.id}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
className="text-text-link hover:underline"
|
||||
>
|
||||
Encounters
|
||||
</Link>
|
||||
@@ -192,7 +189,7 @@ function SortableBossRow({
|
||||
<tr
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
className={`${isDragging ? 'opacity-50 bg-blue-50 dark:bg-blue-900/20' : ''} hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer`}
|
||||
className={`${isDragging ? 'opacity-50 bg-blue-50 dark:bg-blue-900/20' : ''} hover:bg-surface-2 cursor-pointer`}
|
||||
onClick={() => onClick(boss)}
|
||||
>
|
||||
<td className="px-4 py-3 text-sm w-12">
|
||||
@@ -200,7 +197,7 @@ function SortableBossRow({
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 touch-none"
|
||||
className="cursor-grab active:cursor-grabbing text-gray-400 hover:text-text-secondary touch-none"
|
||||
title="Drag to reorder"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
@@ -220,7 +217,7 @@ function SortableBossRow({
|
||||
(() => {
|
||||
const g = games.find((g) => g.id === boss.gameId)
|
||||
return g ? (
|
||||
<span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400">
|
||||
<span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-surface-2 text-text-tertiary">
|
||||
{g.name}
|
||||
</span>
|
||||
) : null
|
||||
@@ -242,7 +239,7 @@ function SortableBossRow({
|
||||
const value = e.target.value === '' ? null : Number(e.target.value)
|
||||
onPositionChange(boss.id, value)
|
||||
}}
|
||||
className="text-sm border border-gray-300 dark:border-gray-600 rounded px-1.5 py-0.5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 max-w-[180px]"
|
||||
className="text-sm border border-border-default rounded px-1.5 py-0.5 bg-surface-1 text-text-primary max-w-[180px]"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{routes.map((r) => (
|
||||
@@ -343,30 +340,30 @@ export function AdminGameDetail() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className="text-sm mb-4 text-gray-500 dark:text-gray-400">
|
||||
<nav className="text-sm mb-4 text-text-tertiary">
|
||||
<Link to="/admin/games" className="hover:underline">
|
||||
Games
|
||||
</Link>
|
||||
{' / '}
|
||||
<span className="text-gray-900 dark:text-gray-100">{game.name}</span>
|
||||
<span className="text-text-primary">{game.name}</span>
|
||||
</nav>
|
||||
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold">{game.name}</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
<p className="text-sm text-text-tertiary">
|
||||
{game.region.charAt(0).toUpperCase() + game.region.slice(1)} · Gen{' '}
|
||||
{game.generation}
|
||||
{game.releaseYear ? ` \u00b7 ${game.releaseYear}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700 mb-4">
|
||||
<div className="flex border-b border-border-default mb-4">
|
||||
<button
|
||||
onClick={() => setTab('routes')}
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${
|
||||
tab === 'routes'
|
||||
? 'border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400'
|
||||
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
: 'border-transparent text-text-tertiary hover:text-text-secondary'
|
||||
}`}
|
||||
>
|
||||
Routes ({routes.length})
|
||||
@@ -376,7 +373,7 @@ export function AdminGameDetail() {
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 -mb-px ${
|
||||
tab === 'bosses'
|
||||
? 'border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400'
|
||||
: 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'
|
||||
: 'border-transparent text-text-tertiary hover:text-text-secondary'
|
||||
}`}
|
||||
>
|
||||
Boss Battles ({bosses?.length ?? 0})
|
||||
@@ -391,19 +388,19 @@ export function AdminGameDetail() {
|
||||
const result = await exportGameRoutes(id)
|
||||
downloadJson(result.data, result.filename)
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowBulkImportRoutes(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Bulk Import
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Route
|
||||
</button>
|
||||
@@ -421,26 +418,26 @@ export function AdminGameDetail() {
|
||||
)}
|
||||
|
||||
{routes.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="text-center py-8 text-text-tertiary border border-border-default rounded-lg">
|
||||
No routes yet. Add one to get started.
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div className="border border-border-default rounded-lg overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<table className="min-w-full divide-y divide-border-default">
|
||||
<thead className="bg-surface-1">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-12" />
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-16">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-12" />
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-16">
|
||||
Order
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-24">
|
||||
<th className="px-4 py-3 text-center text-xs font-medium text-text-tertiary uppercase tracking-wider w-24">
|
||||
Pinwheel
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-28">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-28">
|
||||
Links
|
||||
</th>
|
||||
</tr>
|
||||
@@ -513,19 +510,19 @@ export function AdminGameDetail() {
|
||||
const result = await exportGameBosses(id)
|
||||
downloadJson(result.data, result.filename)
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowBulkImportBosses(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Bulk Import
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreateBoss(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Boss Battle
|
||||
</button>
|
||||
@@ -543,41 +540,41 @@ export function AdminGameDetail() {
|
||||
)}
|
||||
|
||||
{!bosses || bosses.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="text-center py-8 text-text-tertiary border border-border-default rounded-lg">
|
||||
No boss battles yet. Add one to get started.
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div className="border border-border-default rounded-lg overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<table className="min-w-full divide-y divide-border-default">
|
||||
<thead className="bg-surface-1">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-12" />
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-16">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-12" />
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-16">
|
||||
Order
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Type
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Specialty
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Section
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Location
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Position
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-20">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-20">
|
||||
Lv Cap
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-16">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-16">
|
||||
Team
|
||||
</th>
|
||||
</tr>
|
||||
@@ -591,7 +588,7 @@ export function AdminGameDetail() {
|
||||
items={bosses.map((b) => b.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tbody className="bg-surface-0 divide-y divide-border-default">
|
||||
{bosses.map((boss) => (
|
||||
<SortableBossRow
|
||||
key={boss.id}
|
||||
|
||||
@@ -57,13 +57,13 @@ export function AdminGames() {
|
||||
const data = await exportGames()
|
||||
downloadJson(data, 'games.json')
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Game
|
||||
</button>
|
||||
@@ -74,7 +74,7 @@ export function AdminGames() {
|
||||
<select
|
||||
value={regionFilter}
|
||||
onChange={(e) => setRegionFilter(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All regions</option>
|
||||
{regions.map((r) => (
|
||||
@@ -86,7 +86,7 @@ export function AdminGames() {
|
||||
<select
|
||||
value={genFilter}
|
||||
onChange={(e) => setGenFilter(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All generations</option>
|
||||
{generations.map((g) => (
|
||||
@@ -101,12 +101,12 @@ export function AdminGames() {
|
||||
setRegionFilter('')
|
||||
setGenFilter('')
|
||||
}}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className="text-sm text-text-tertiary hover:text-text-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
<span className="text-sm text-text-tertiary whitespace-nowrap">
|
||||
{filteredGames.length} games
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -67,36 +67,32 @@ export function AdminGenlockeDetail() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className="text-sm mb-4 text-gray-500 dark:text-gray-400">
|
||||
<nav className="text-sm mb-4 text-text-tertiary">
|
||||
<Link to="/admin/genlockes" className="hover:underline">
|
||||
Genlockes
|
||||
</Link>
|
||||
{' / '}
|
||||
<span className="text-gray-900 dark:text-gray-100">{genlocke.name}</span>
|
||||
<span className="text-text-primary">{genlocke.name}</span>
|
||||
</nav>
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-6 space-y-4">
|
||||
<div className="flex flex-wrap items-end gap-4">
|
||||
<div className="flex-1 min-w-[200px]">
|
||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
Name
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-text-tertiary mb-1">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
value={editName}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="w-full px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||||
Status
|
||||
</label>
|
||||
<label className="block text-xs font-medium text-text-tertiary mb-1">Status</label>
|
||||
<select
|
||||
value={editStatus}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="active">Active</option>
|
||||
<option value="completed">Completed</option>
|
||||
@@ -106,35 +102,35 @@ export function AdminGenlockeDetail() {
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={!hasChanges || updateGenlocke.isPending}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500 disabled:opacity-50"
|
||||
>
|
||||
{updateGenlocke.isPending ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowDelete(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-red-300 dark:border-red-600 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-red-300 dark:border-red-600 text-status-failed hover:bg-red-50 dark:hover:bg-red-900/20"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
<p className="text-sm text-text-tertiary">
|
||||
Created {new Date(genlocke.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Rules (read-only) */}
|
||||
<div className="mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<h3 className="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">Rules</h3>
|
||||
<div className="mb-6 p-4 bg-surface-1 rounded-lg">
|
||||
<h3 className="text-sm font-semibold mb-2 text-text-secondary">Rules</h3>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Genlocke rules:</span>
|
||||
<pre className="mt-1 text-xs bg-white dark:bg-gray-900 p-2 rounded border dark:border-gray-700 overflow-x-auto">
|
||||
<span className="text-text-tertiary">Genlocke rules:</span>
|
||||
<pre className="mt-1 text-xs bg-surface-0 p-2 rounded border border-border-default overflow-x-auto">
|
||||
{JSON.stringify(genlocke.genlockeRules, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Nuzlocke rules:</span>
|
||||
<pre className="mt-1 text-xs bg-white dark:bg-gray-900 p-2 rounded border dark:border-gray-700 overflow-x-auto">
|
||||
<span className="text-text-tertiary">Nuzlocke rules:</span>
|
||||
<pre className="mt-1 text-xs bg-surface-0 p-2 rounded border border-border-default overflow-x-auto">
|
||||
{JSON.stringify(genlocke.nuzlockeRules, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
@@ -147,18 +143,18 @@ export function AdminGenlockeDetail() {
|
||||
<h3 className="text-lg font-semibold">Legs ({genlocke.legs.length})</h3>
|
||||
<button
|
||||
onClick={() => setAddingLeg(!addingLeg)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Leg
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{addingLeg && (
|
||||
<div className="mb-4 flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="mb-4 flex items-center gap-3 p-3 bg-surface-1 rounded-lg">
|
||||
<select
|
||||
value={selectedGameId}
|
||||
onChange={(e) => setSelectedGameId(e.target.value ? Number(e.target.value) : '')}
|
||||
className="flex-1 px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="flex-1 px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">Select a game...</option>
|
||||
{games.map((g) => (
|
||||
@@ -179,7 +175,7 @@ export function AdminGenlockeDetail() {
|
||||
setAddingLeg(false)
|
||||
setSelectedGameId('')
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default hover:bg-surface-2"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -187,39 +183,39 @@ export function AdminGenlockeDetail() {
|
||||
)}
|
||||
|
||||
{genlocke.legs.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400 border border-gray-200 dark:border-gray-700 rounded-lg">
|
||||
<div className="text-center py-8 text-text-tertiary border border-border-default rounded-lg">
|
||||
No legs yet. Add one to get started.
|
||||
</div>
|
||||
) : (
|
||||
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div className="border border-border-default rounded-lg overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<table className="min-w-full divide-y divide-border-default">
|
||||
<thead className="bg-surface-1">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-16">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-16">
|
||||
Order
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Game
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Run
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-20">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-20">
|
||||
Enc.
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-20">
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-text-tertiary uppercase tracking-wider w-20">
|
||||
Deaths
|
||||
</th>
|
||||
<th className="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-20">
|
||||
<th className="px-4 py-3 text-right text-xs font-medium text-text-tertiary uppercase tracking-wider w-20">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<tbody className="bg-surface-0 divide-y divide-border-default">
|
||||
{genlocke.legs.map((leg) => (
|
||||
<tr key={leg.id}>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{leg.legOrder}</td>
|
||||
@@ -228,7 +224,7 @@ export function AdminGenlockeDetail() {
|
||||
{leg.runId ? (
|
||||
<Link
|
||||
to={`/runs/${leg.runId}`}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
className="text-text-link hover:underline"
|
||||
>
|
||||
Run #{leg.runId}
|
||||
</Link>
|
||||
@@ -241,10 +237,10 @@ export function AdminGenlockeDetail() {
|
||||
<span
|
||||
className={
|
||||
leg.runStatus === 'active'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
? 'text-status-active'
|
||||
: leg.runStatus === 'completed'
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
? 'text-text-link'
|
||||
: 'text-status-failed'
|
||||
}
|
||||
>
|
||||
{leg.runStatus}
|
||||
@@ -264,7 +260,7 @@ export function AdminGenlockeDetail() {
|
||||
? 'Cannot remove a leg with a linked run'
|
||||
: 'Remove leg'
|
||||
}
|
||||
className="text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
className="text-status-failed hover:text-red-800 dark:hover:text-red-300 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
@@ -279,25 +275,25 @@ export function AdminGenlockeDetail() {
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="mb-6 p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<h3 className="text-sm font-semibold mb-2 text-gray-700 dark:text-gray-300">Stats</h3>
|
||||
<div className="mb-6 p-4 bg-surface-1 rounded-lg">
|
||||
<h3 className="text-sm font-semibold mb-2 text-text-secondary">Stats</h3>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Legs</span>
|
||||
<span className="text-text-tertiary">Legs</span>
|
||||
<p className="text-lg font-semibold">
|
||||
{genlocke.stats.legsCompleted} / {genlocke.stats.totalLegs}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Encounters</span>
|
||||
<span className="text-text-tertiary">Encounters</span>
|
||||
<p className="text-lg font-semibold">{genlocke.stats.totalEncounters}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Deaths</span>
|
||||
<span className="text-text-tertiary">Deaths</span>
|
||||
<p className="text-lg font-semibold">{genlocke.stats.totalDeaths}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-500 dark:text-gray-400">Survival Rate</span>
|
||||
<span className="text-text-tertiary">Survival Rate</span>
|
||||
<p className="text-lg font-semibold">
|
||||
{genlocke.stats.totalEncounters > 0
|
||||
? `${Math.round(((genlocke.stats.totalEncounters - genlocke.stats.totalDeaths) / genlocke.stats.totalEncounters) * 100)}%`
|
||||
|
||||
@@ -27,10 +27,10 @@ export function AdminGenlockes() {
|
||||
<span
|
||||
className={
|
||||
g.status === 'active'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
? 'text-status-active'
|
||||
: g.status === 'completed'
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
? 'text-text-link'
|
||||
: 'text-status-failed'
|
||||
}
|
||||
>
|
||||
{g.status}
|
||||
@@ -60,7 +60,7 @@ export function AdminGenlockes() {
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All statuses</option>
|
||||
<option value="active">Active</option>
|
||||
@@ -70,12 +70,12 @@ export function AdminGenlockes() {
|
||||
{statusFilter && (
|
||||
<button
|
||||
onClick={() => setStatusFilter('')}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className="text-sm text-text-tertiary hover:text-text-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
<span className="text-sm text-text-tertiary whitespace-nowrap">
|
||||
{filtered.length} genlockes
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ export function AdminPokemon() {
|
||||
p.spriteUrl ? (
|
||||
<img src={p.spriteUrl} alt={p.name} className="w-8 h-8" />
|
||||
) : (
|
||||
<div className="w-8 h-8 bg-gray-200 dark:bg-gray-700 rounded" />
|
||||
<div className="w-8 h-8 bg-surface-3 rounded" />
|
||||
),
|
||||
},
|
||||
{ header: 'Name', accessor: (p) => p.name },
|
||||
@@ -85,19 +85,19 @@ export function AdminPokemon() {
|
||||
const data = await exportPokemon()
|
||||
downloadJson(data, 'pokemon.json')
|
||||
}}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default text-text-secondary hover:bg-surface-2"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowBulkImport(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md border border-border-default hover:bg-surface-2"
|
||||
>
|
||||
Bulk Import
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Pokemon
|
||||
</button>
|
||||
@@ -113,7 +113,7 @@ export function AdminPokemon() {
|
||||
setPage(0)
|
||||
}}
|
||||
placeholder="Search by name..."
|
||||
className="w-full max-w-sm px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="w-full max-w-sm px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
/>
|
||||
<select
|
||||
value={typeFilter}
|
||||
@@ -121,7 +121,7 @@ export function AdminPokemon() {
|
||||
setTypeFilter(e.target.value)
|
||||
setPage(0)
|
||||
}}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All types</option>
|
||||
{POKEMON_TYPES.map((t) => (
|
||||
@@ -137,14 +137,12 @@ export function AdminPokemon() {
|
||||
setTypeFilter('')
|
||||
setPage(0)
|
||||
}}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className="text-sm text-text-tertiary hover:text-text-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
{total} pokemon
|
||||
</span>
|
||||
<span className="text-sm text-text-tertiary whitespace-nowrap">{total} pokemon</span>
|
||||
</div>
|
||||
|
||||
<AdminTable
|
||||
@@ -159,38 +157,38 @@ export function AdminPokemon() {
|
||||
{/* Pagination */}
|
||||
{totalPages > 1 && (
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
<div className="text-sm text-text-tertiary">
|
||||
Showing {offset + 1}-{Math.min(offset + PAGE_SIZE, total)} of {total}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setPage(0)}
|
||||
disabled={page === 0}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
First
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||
disabled={page === 0}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Prev
|
||||
</button>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-300 px-2">
|
||||
<span className="text-sm text-text-secondary px-2">
|
||||
Page {page + 1} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||
disabled={page >= totalPages - 1}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage(totalPages - 1)}
|
||||
disabled={page >= totalPages - 1}
|
||||
className="px-3 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
className="px-3 py-1 text-sm rounded border border-border-default disabled:opacity-50 disabled:cursor-not-allowed hover:bg-surface-2"
|
||||
>
|
||||
Last
|
||||
</button>
|
||||
|
||||
@@ -98,7 +98,7 @@ export function AdminRouteDetail() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<nav className="text-sm mb-4 text-gray-500 dark:text-gray-400">
|
||||
<nav className="text-sm mb-4 text-text-tertiary">
|
||||
<Link to="/admin/games" className="hover:underline">
|
||||
Games
|
||||
</Link>
|
||||
@@ -108,7 +108,7 @@ export function AdminRouteDetail() {
|
||||
</Link>
|
||||
{' / '}
|
||||
<select
|
||||
className="text-gray-900 dark:text-gray-100 bg-transparent font-medium cursor-pointer hover:underline border-none p-0 text-sm"
|
||||
className="text-text-primary bg-transparent font-medium cursor-pointer hover:underline border-none p-0 text-sm"
|
||||
value={rId}
|
||||
onChange={(e) => navigate(`/admin/games/${gId}/routes/${e.target.value}`)}
|
||||
>
|
||||
@@ -129,26 +129,26 @@ export function AdminRouteDetail() {
|
||||
{prevRoute ? (
|
||||
<Link
|
||||
to={`/admin/games/${gId}/routes/${prevRoute.id}`}
|
||||
className="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
className="px-2 py-1 text-sm rounded border border-border-default hover:bg-surface-2"
|
||||
title={prevRoute.name}
|
||||
>
|
||||
← Prev
|
||||
</Link>
|
||||
) : (
|
||||
<span className="px-2 py-1 text-sm rounded border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
|
||||
<span className="px-2 py-1 text-sm rounded border border-border-default text-text-muted cursor-not-allowed">
|
||||
← Prev
|
||||
</span>
|
||||
)}
|
||||
{nextRoute ? (
|
||||
<Link
|
||||
to={`/admin/games/${gId}/routes/${nextRoute.id}`}
|
||||
className="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
className="px-2 py-1 text-sm rounded border border-border-default hover:bg-surface-2"
|
||||
title={nextRoute.name}
|
||||
>
|
||||
Next →
|
||||
</Link>
|
||||
) : (
|
||||
<span className="px-2 py-1 text-sm rounded border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
|
||||
<span className="px-2 py-1 text-sm rounded border border-border-default text-text-muted cursor-not-allowed">
|
||||
Next →
|
||||
</span>
|
||||
)}
|
||||
@@ -156,7 +156,7 @@ export function AdminRouteDetail() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Pokemon
|
||||
</button>
|
||||
@@ -212,26 +212,26 @@ export function AdminRouteDetail() {
|
||||
<h3 className="text-lg font-semibold">Sub-areas ({childRoutes.length})</h3>
|
||||
<button
|
||||
onClick={() => setShowCreateChild(true)}
|
||||
className="px-3 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
className="px-3 py-1.5 text-sm font-medium rounded-md bg-accent-600 text-white hover:bg-accent-500"
|
||||
>
|
||||
Add Sub-area
|
||||
</button>
|
||||
</div>
|
||||
{childRoutes.length === 0 ? (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">No sub-areas for this route.</p>
|
||||
<p className="text-sm text-text-tertiary">No sub-areas for this route.</p>
|
||||
) : (
|
||||
<div className="border rounded-md dark:border-gray-700 divide-y dark:divide-gray-700">
|
||||
<div className="border border-border-default rounded-md divide-y divide-border-default">
|
||||
{childRoutes.map((child) => (
|
||||
<div key={child.id} className="flex items-center justify-between px-4 py-2">
|
||||
<Link
|
||||
to={`/admin/games/${gId}/routes/${child.id}`}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
className="text-text-link hover:underline"
|
||||
>
|
||||
{child.name}
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => setDeletingChild(child)}
|
||||
className="text-sm text-red-600 dark:text-red-400 hover:underline"
|
||||
className="text-sm text-status-failed hover:underline"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
@@ -46,10 +46,10 @@ export function AdminRuns() {
|
||||
<span
|
||||
className={
|
||||
r.status === 'active'
|
||||
? 'text-green-600 dark:text-green-400'
|
||||
? 'text-status-active'
|
||||
: r.status === 'completed'
|
||||
? 'text-blue-600 dark:text-blue-400'
|
||||
: 'text-red-600 dark:text-red-400'
|
||||
? 'text-text-link'
|
||||
: 'text-status-failed'
|
||||
}
|
||||
>
|
||||
{r.status}
|
||||
@@ -74,7 +74,7 @@ export function AdminRuns() {
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All statuses</option>
|
||||
<option value="active">Active</option>
|
||||
@@ -84,7 +84,7 @@ export function AdminRuns() {
|
||||
<select
|
||||
value={gameFilter}
|
||||
onChange={(e) => setGameFilter(e.target.value)}
|
||||
className="px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
className="px-3 py-2 border rounded-md bg-surface-2 border-border-default"
|
||||
>
|
||||
<option value="">All games</option>
|
||||
{runGames.map(([id, name]) => (
|
||||
@@ -99,12 +99,12 @@ export function AdminRuns() {
|
||||
setStatusFilter('')
|
||||
setGameFilter('')
|
||||
}}
|
||||
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
className="text-sm text-text-tertiary hover:text-text-primary"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
|
||||
<span className="text-sm text-text-tertiary whitespace-nowrap">
|
||||
{filteredRuns.length} runs
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user