From 229b3525d3e2221e3651b2980f45dcb565844f8e Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Fri, 10 Apr 2026 11:28:26 +0200 Subject: [PATCH] feat: implement admin dashboard and main menu interface with global theme support --- app/admin/page.tsx | 65 ++----------- app/globals.css | 145 ++++++++++++++++++--------- app/page.tsx | 30 +++--- components/AdminButtonRow.tsx | 162 +++++++++++++++++++++++++++++++ components/MenuButton.module.css | 54 +++++------ 5 files changed, 309 insertions(+), 147 deletions(-) create mode 100644 components/AdminButtonRow.tsx diff --git a/app/admin/page.tsx b/app/admin/page.tsx index d6624a1..3d838c2 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -5,8 +5,7 @@ import { ThemeToggle } from '@/components/ThemeToggle'; import { LogOut, ArrowLeft, Trash2 } from 'lucide-react'; import Link from 'next/link'; import { AddButtonForm } from '@/components/AddButtonForm'; -import { ICON_MAP, parseIcon } from '@/lib/lucideIcons'; -import { Globe } from 'lucide-react'; +import { AdminButtonRow } from '@/components/AdminButtonRow'; export const dynamic = 'force-dynamic'; @@ -69,66 +68,14 @@ export default async function AdminPage() { {/* Button list */} {buttons.length === 0 ? ( -
+
Aucun bouton configuré.
) : ( -
- {(buttons as any[]).map((btn) => { - const { name: iconName, color } = parseIcon(btn.icon); - const IconComponent = ICON_MAP[iconName] || Globe; - - return ( -
-
-
- -
-
-
- {btn.name} -
-
- {btn.url} -
-
-
- -
{ - 'use server'; - await deleteButton(btn.id); - }}> - -
-
- ); - })} +
+ {(buttons as any[]).map((btn) => ( + + ))}
)}
diff --git a/app/globals.css b/app/globals.css index 0a4f291..f6bc708 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,88 +1,139 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); :root { --font: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - --ease: cubic-bezier(0.4, 0, 0.2, 1); + --ease-elastic: cubic-bezier(0.4, 0, 0.2, 1); - /* Dark (default) */ - --bg-from: #2c0a30; - --bg-mid: #5a0f28; - --bg-to: #8c1525; + /* DARK THEME (Obsidian with subtle violet glow) */ + --bg-color: #050505; + --fg: #ffffff; + --fg-secondary: rgba(255, 255, 255, 0.6); + --fg-muted: rgba(255, 255, 255, 0.4); - --fg: #ffffff; - --fg-secondary: rgba(255, 255, 255, 0.5); + --card-bg: rgba(255, 255, 255, 0.04); + --card-border: rgba(255, 255, 255, 0.08); + --card-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + --card-hover-bg: rgba(255, 255, 255, 0.08); + --card-hover-border: rgba(255, 255, 255, 0.15); - --card-bg: rgba(255, 255, 255, 0.09); - --card-border: rgba(255, 255, 255, 0.13); - --card-shadow: 0 2px 12px rgba(0,0,0,0.3), 0 1px 0 rgba(255,255,255,0.06) inset; - --card-hover-bg: rgba(255, 255, 255, 0.14); - --card-hover-shadow: 0 16px 48px rgba(0,0,0,0.5), 0 1px 0 rgba(255,255,255,0.10) inset; + --accent-btn-bg: rgba(255, 255, 255, 0.12); + --accent-btn-hover: rgba(255, 255, 255, 0.2); + + --error: #ff453a; + --success: #32d74b; + + --glow-1: rgba(140, 50, 255, 0.15); + --glow-2: rgba(50, 140, 255, 0.15); } [data-theme='light'] { - --bg-from: #e0c4ef; - --bg-mid: #e8b8cc; - --bg-to: #f0b8be; + /* LIGHT THEME (Apple Pearl) */ + --bg-color: #f5f5f7; + --fg: #1d1d1f; + --fg-secondary: rgba(29, 29, 31, 0.65); + --fg-muted: rgba(29, 29, 31, 0.45); - --fg: #1c0a22; - --fg-secondary: rgba(28, 10, 34, 0.5); + --card-bg: rgba(255, 255, 255, 0.6); + --card-border: rgba(0, 0, 0, 0.05); + --card-shadow: 0 8px 30px rgba(0, 0, 0, 0.04); + --card-hover-bg: rgba(255, 255, 255, 0.9); + --card-hover-border: rgba(0, 0, 0, 0.1); - --card-bg: rgba(255, 255, 255, 0.50); - --card-border: rgba(255, 255, 255, 0.85); - --card-shadow: 0 2px 12px rgba(120,30,60,0.08), 0 1px 0 rgba(255,255,255,0.9) inset; - --card-hover-bg: rgba(255, 255, 255, 0.70); - --card-hover-shadow: 0 16px 48px rgba(120,30,60,0.15), 0 1px 0 rgba(255,255,255,0.9) inset; + --accent-btn-bg: rgba(0, 0, 0, 0.06); + --accent-btn-hover: rgba(0, 0, 0, 0.12); + + --error: #ff3b30; + --success: #34c759; + + --glow-1: rgba(140, 50, 255, 0.05); + --glow-2: rgba(50, 140, 255, 0.05); } -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } -html, body { height: 100%; } +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body { + height: 100%; +} body { min-height: 100vh; font-family: var(--font); color: var(--fg); + background-color: var(--bg-color); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-x: hidden; - /* Multi-stop radial mesh gradient pour plus de profondeur */ - background: - radial-gradient(ellipse at 10% 10%, rgba(120, 30, 180, 0.35) 0%, transparent 55%), - radial-gradient(ellipse at 90% 85%, rgba(160, 20, 40, 0.4) 0%, transparent 50%), - radial-gradient(ellipse at 50% 50%, rgba(80, 10, 50, 0.6) 0%, transparent 80%), - linear-gradient(160deg, var(--bg-from) 0%, var(--bg-mid) 50%, var(--bg-to) 100%); - background-attachment: fixed; - transition: background var(--ease) 260ms; + transition: background-color 300ms ease, color 300ms ease; + position: relative; +} + +/* Premium Ambient Glow Orbs */ +body::before, body::after { + content: ''; + position: fixed; + width: 60vw; + height: 60vw; + border-radius: 50%; + filter: blur(140px); + z-index: -1; + pointer-events: none; + opacity: 0.8; + transition: background 300ms ease; +} + +body::before { + top: -20vw; + left: -10vw; + background: var(--glow-1); +} + +body::after { + bottom: -20vw; + right: -10vw; + background: var(--glow-2); } a { text-decoration: none; color: inherit; } button { cursor: pointer; border: none; outline: none; font-family: var(--font); } /* Scrollbar */ -::-webkit-scrollbar { width: 6px; } +::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: transparent; } -::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.18); border-radius: 10px; } +::-webkit-scrollbar-thumb { background: var(--card-border); border-radius: 10px; } +::-webkit-scrollbar-thumb:hover { background: var(--fg-muted); } -/* Inputs */ +/* Premium Input Styles */ input[type="text"], input[type="password"], input[type="url"], input[type="email"] { width: 100%; - padding: 0.82rem 1rem; - background: rgba(255,255,255,0.07); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - border: 1px solid rgba(255,255,255,0.13); + padding: 0.85rem 1.1rem; + background: var(--card-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--card-border); border-radius: 12px; color: var(--fg); font-family: var(--font); - font-size: 0.92rem; + font-size: 0.95rem; + font-weight: 500; outline: none; - transition: all 240ms var(--ease); + transition: all 250ms ease; + box-shadow: inset 0 2px 4px rgba(0,0,0,0.02); } + input:focus { - border-color: rgba(255,255,255,0.45); - box-shadow: 0 0 0 3px rgba(255,255,255,0.08); - background: rgba(255,255,255,0.11); + border-color: var(--fg-muted); + background: var(--card-hover-bg); + box-shadow: 0 0 0 3px rgba(140, 50, 255, 0.15), inset 0 2px 4px rgba(0,0,0,0.02); +} + +input::placeholder { + color: var(--fg-muted); + font-weight: 400; } -input::placeholder { color: var(--fg-secondary); } diff --git a/app/page.tsx b/app/page.tsx index 357fc6f..5a15048 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -27,17 +27,24 @@ export default async function Home() { padding: '1.5rem 2rem', gap: '0.6rem', }}> - - + + @@ -57,13 +64,12 @@ export default async function Home() { fontSize: 'clamp(1.8rem, 4vw, 2.8rem)', fontWeight: 800, letterSpacing: '-0.04em', - color: '#fff', - textShadow: '0 2px 20px rgba(0,0,0,0.3)', + color: 'var(--fg)', }}> Menu Application {buttons.length > 0 && ( -

+

{buttons.length} application{buttons.length > 1 ? 's' : ''} disponible{buttons.length > 1 ? 's' : ''}

)} @@ -71,9 +77,9 @@ export default async function Home() { {/* Grid */} {buttons.length === 0 ? ( -
+
🔗
-

+

Aucune application

@@ -81,10 +87,10 @@ export default async function Home() {

{ + if (!name || !url) return; + setLoading(true); + const formData = new FormData(); + formData.set('id', button.id.toString()); + formData.set('name', name); + formData.set('url', url); + formData.set('icon', serializeIcon(iconName, iconColor)); + + try { + await updateButton(formData); + setIsEditing(false); + } finally { + setLoading(false); + } + }; + + const handleCancel = () => { + setName(button.name); + setUrl(button.url); + setIconName(parsed.name); + setIconColor(parsed.color); + setIsEditing(false); + }; + + if (isEditing) { + return ( +
+ + +
+ setName(e.target.value)} placeholder="Nom" required style={{ padding: '0.6rem 0.8rem' }} /> +
+ +
+ setUrl(e.target.value)} placeholder="https://..." required style={{ padding: '0.6rem 0.8rem' }} /> +
+ +
+ + +
+
+ ); + } + + return ( +
{ e.currentTarget.style.background = 'var(--card-hover-bg)'; e.currentTarget.style.borderColor = 'var(--card-hover-border)'; }} + onMouseLeave={(e) => { e.currentTarget.style.background = 'var(--card-bg)'; e.currentTarget.style.borderColor = 'var(--card-border)'; }} + > +
+
+ +
+
+
+ {button.name} +
+
+ {button.url} +
+
+
+ +
+ + +
{ await deleteButton(button.id); }}> + +
+
+
+ ); +} diff --git a/components/MenuButton.module.css b/components/MenuButton.module.css index 172dabf..4213832 100644 --- a/components/MenuButton.module.css +++ b/components/MenuButton.module.css @@ -3,68 +3,64 @@ flex-direction: column; align-items: center; justify-content: center; - gap: 1rem; - padding: 2rem 1.25rem 1.75rem; + gap: 1.1rem; + padding: 1.5rem 1rem; text-decoration: none; color: var(--fg); - width: 160px; - aspect-ratio: 1 / 1; + width: 150px; + height: 150px; cursor: pointer; background: var(--card-bg); - backdrop-filter: blur(24px) saturate(1.4); - -webkit-backdrop-filter: blur(24px) saturate(1.4); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); border: 1px solid var(--card-border); box-shadow: var(--card-shadow); - border-radius: 22px; - transition: all 240ms cubic-bezier(0.4, 0, 0.2, 1); + border-radius: 28px; /* Apple squircle-like */ + transition: all 300ms cubic-bezier(0.25, 1, 0.5, 1); position: relative; overflow: hidden; } .menuButton:hover { background: var(--card-hover-bg); - box-shadow: var(--card-hover-shadow); - transform: translateY(-5px) scale(1.02); + border-color: var(--card-hover-border); + transform: translateY(-4px) scale(1.02); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); } .menuButton:active { - transform: scale(0.97); - transition-duration: 80ms; + transform: scale(0.96); + transition-duration: 100ms; } -/* Favicon */ +/* Icon Container */ .faviconWrapper { - width: 56px; - height: 56px; + width: 54px; + height: 54px; border-radius: 14px; display: flex; align-items: center; justify-content: center; - overflow: hidden; + background: var(--accent-btn-bg); + box-shadow: inset 0 2px 4px rgba(255,255,255,0.05); + transition: transform 300ms cubic-bezier(0.25, 1, 0.5, 1); } -.favicon { - width: 48px; - height: 48px; - object-fit: contain; -} - -.faviconFallback { - color: rgba(255,255,255,0.7); - display: flex; - align-items: center; - justify-content: center; +.menuButton:hover .faviconWrapper { + transform: scale(1.08); + background: var(--accent-btn-hover); } /* Label */ .label { - font-size: 0.82rem; + font-size: 0.85rem; font-weight: 600; text-align: center; letter-spacing: -0.01em; color: var(--fg); - max-width: 130px; + max-width: 125px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-shadow: 0 1px 2px rgba(0,0,0,0.1); }