feat: initialize Prisma schema with Config and Button models

This commit is contained in:
Michael committed 2026-04-10 10:57:42 +02:00
1 parent ca99378348
commit 485aba96fd
10 files changed
+408 -241

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
'use client';
import React, { useState } from 'react';
import { addButton } from '@/app/actions/admin';
import { EmojiPicker } from '@/components/EmojiPicker';
import { PlusCircle } from 'lucide-react';
export function AddButtonForm() {
const [icon, setIcon] = useState('🔗');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const formData = new FormData(e.currentTarget);
formData.set('icon', icon);
try {
await addButton(formData);
(e.target as HTMLFormElement).reset();
setIcon('🔗');
} finally {
setLoading(false);
}
};
return (
<div style={{
background: 'rgba(255,255,255,0.07)',
backdropFilter: 'blur(24px)',
WebkitBackdropFilter: 'blur(24px)',
border: '1px solid rgba(255,255,255,0.12)',
borderRadius: '20px',
padding: '1.75rem',
marginBottom: '2rem',
}}>
<h2 style={{
fontSize: '0.85rem',
fontWeight: 700,
color: 'rgba(255,255,255,0.5)',
marginBottom: '1.25rem',
letterSpacing: '0.08em',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
}}>
<PlusCircle size={15} /> AJOUTER UNE APPLICATION
</h2>
<form onSubmit={handleSubmit} style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap', alignItems: 'flex-end' }}>
{/* Emoji picker */}
<div>
<label style={{ display: 'block', fontSize: '0.75rem', fontWeight: 600, color: 'rgba(255,255,255,0.45)', marginBottom: '0.45rem' }}>
ICÔNE
</label>
<EmojiPicker value={icon} onChange={setIcon} />
</div>
{/* Name */}
<div style={{ flex: '1 1 140px' }}>
<label style={{ display: 'block', fontSize: '0.75rem', fontWeight: 600, color: 'rgba(255,255,255,0.45)', marginBottom: '0.45rem' }}>
NOM
</label>
<input name="name" type="text" placeholder="Ex: Slack, Notion…" required />
</div>
{/* URL */}
<div style={{ flex: '2 1 240px' }}>
<label style={{ display: 'block', fontSize: '0.75rem', fontWeight: 600, color: 'rgba(255,255,255,0.45)', marginBottom: '0.45rem' }}>
URL
</label>
<input name="url" type="url" placeholder="https://…" required />
</div>
{/* Submit */}
<button
type="submit"
disabled={loading}
style={{
background: 'rgba(255,255,255,0.16)',
backdropFilter: 'blur(12px)',
border: '1px solid rgba(255,255,255,0.22)',
color: '#fff',
padding: '0.75rem 1.25rem',
borderRadius: '12px',
fontWeight: 600,
fontSize: '0.88rem',
cursor: 'pointer',
whiteSpace: 'nowrap',
flexShrink: 0,
transition: 'all 200ms ease',
opacity: loading ? 0.6 : 1,
}}
>
{loading ? '…' : 'Ajouter →'}
</button>
</form>
</div>
);
}
+117
View File
@@ -0,0 +1,117 @@
/* ──────────────────────────────────────────────────────────────
EmojiPicker — composant client pour sélectionner un emoji
utilisé dans le formulaire admin
────────────────────────────────────────────────────────────── */
'use client';
import React, { useState } from 'react';
const QUICK_EMOJIS = [
'🚀','📊','💼','🔗','📁','📧','📅','🧠',
'💻','🖥️','📱','🔑','⚙️','🛠️','📈','🌐',
'💬','🤝','📝','🔒','☁️','🗂️','🎯','📌',
'🏠','🏢','👥','🔔','✅','❌','💡','🎨',
];
interface EmojiPickerProps {
value: string;
onChange: (emoji: string) => void;
}
export function EmojiPicker({ value, onChange }: EmojiPickerProps) {
const [open, setOpen] = useState(false);
return (
<div style={{ position: 'relative' }}>
{/* Trigger button */}
<button
type="button"
onClick={() => setOpen(!open)}
style={{
width: '52px',
height: '52px',
borderRadius: '14px',
background: 'rgba(255,255,255,0.08)',
border: '1px solid rgba(255,255,255,0.15)',
fontSize: '1.6rem',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
transition: 'background 200ms ease',
}}
title="Choisir une icône"
>
{value || '🔗'}
</button>
{/* Dropdown picker */}
{open && (
<div style={{
position: 'absolute',
top: '60px',
left: 0,
zIndex: 100,
background: 'rgba(30, 10, 35, 0.92)',
backdropFilter: 'blur(30px)',
WebkitBackdropFilter: 'blur(30px)',
border: '1px solid rgba(255,255,255,0.12)',
borderRadius: '16px',
padding: '1rem',
width: '268px',
boxShadow: '0 12px 40px rgba(0,0,0,0.5)',
}}>
<p style={{ fontSize: '0.75rem', color: 'rgba(255,255,255,0.4)', marginBottom: '0.75rem', fontWeight: 600, letterSpacing: '0.05em' }}>
SÉLECTIONNER UNE ICÔNE
</p>
{/* Quick emoji grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(8, 1fr)', gap: '4px', marginBottom: '0.75rem' }}>
{QUICK_EMOJIS.map((emoji) => (
<button
key={emoji}
type="button"
onClick={() => { onChange(emoji); setOpen(false); }}
style={{
background: value === emoji ? 'rgba(255,255,255,0.18)' : 'transparent',
border: 'none',
borderRadius: '8px',
fontSize: '1.2rem',
padding: '4px',
cursor: 'pointer',
transition: 'background 150ms',
lineHeight: 1,
}}
onMouseEnter={(e) => (e.currentTarget.style.background = 'rgba(255,255,255,0.12)')}
onMouseLeave={(e) => (e.currentTarget.style.background = value === emoji ? 'rgba(255,255,255,0.18)' : 'transparent')}
>
{emoji}
</button>
))}
</div>
{/* Manual input */}
<div style={{ borderTop: '1px solid rgba(255,255,255,0.08)', paddingTop: '0.75rem' }}>
<input
type="text"
placeholder="Coller un emoji…"
value={value}
onChange={(e) => onChange(e.target.value)}
style={{
width: '100%',
background: 'rgba(255,255,255,0.06)',
border: '1px solid rgba(255,255,255,0.12)',
borderRadius: '8px',
padding: '0.4rem 0.6rem',
color: '#fff',
fontSize: '1rem',
}}
maxLength={4}
/>
</div>
</div>
)}
</div>
);
}
+11 -7
View File
@@ -9,17 +9,17 @@ import styles from './MenuButton.module.css';
interface MenuButtonProps {
name: string;
url: string;
icon?: string | null;
}
function getDomain(url: string): string {
try {
return new URL(url).hostname;
} catch {
return url;
}
try { return new URL(url).hostname; } catch { return url; }
}
export const MenuButton: React.FC<MenuButtonProps> = ({ name, url }) => {
/**
* MenuButton — displays emoji icon if set, falls back to Google favicon.
*/
export const MenuButton: React.FC<MenuButtonProps> = ({ name, url, icon }) => {
const domain = getDomain(url);
const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
const [faviconError, setFaviconError] = useState(false);
@@ -27,7 +27,11 @@ export const MenuButton: React.FC<MenuButtonProps> = ({ name, url }) => {
return (
<Link href={url} target="_blank" rel="noopener noreferrer" className={styles.menuButton}>
<div className={styles.faviconWrapper}>
{!faviconError ? (
{icon ? (
// Custom emoji icon
<span style={{ fontSize: '2.2rem', lineHeight: 1 }}>{icon}</span>
) : !faviconError ? (
// Auto favicon from Google CDN
<Image
src={faviconUrl}
alt={`${name} favicon`}