design: rework for a better DA and light/dark mode
This commit is contained in:
@ -4,9 +4,12 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0f172a" />
|
||||
<meta name="description" content="Agrégateur de news financières avec résumés IA" />
|
||||
<title>Tradarr</title>
|
||||
<meta name="theme-color" content="#f7f4ef" />
|
||||
<meta name="description" content="Votre journal financier augmenté par l'IA" />
|
||||
<title>FinancIAl</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -12,7 +12,7 @@ const items = [
|
||||
|
||||
export function MobileNav() {
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 flex border-t bg-card md:hidden">
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 flex border-t bg-card md:hidden shadow-sm">
|
||||
{items.map(({ to, icon: Icon, label }) => (
|
||||
<NavLink
|
||||
key={to}
|
||||
@ -21,7 +21,7 @@ export function MobileNav() {
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'flex flex-1 flex-col items-center gap-1 py-3 text-xs transition-colors',
|
||||
isActive ? 'text-primary' : 'text-muted-foreground'
|
||||
isActive ? 'text-primary font-medium' : 'text-muted-foreground'
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { LayoutDashboard, Newspaper, Star, Settings, Key, Cpu, Database, ClipboardList, Users, LogOut, TrendingUp, CalendarDays, FileText } from 'lucide-react'
|
||||
import { LayoutDashboard, Newspaper, Star, Settings, Key, Cpu, Database, ClipboardList, Users, LogOut, CalendarDays, FileText } from 'lucide-react'
|
||||
import { useAuth } from '@/lib/auth'
|
||||
import { cn } from '@/lib/cn'
|
||||
|
||||
@ -27,14 +27,14 @@ function NavItem({ to, icon: Icon, label }: { to: string; icon: React.ElementTyp
|
||||
end={to === '/'}
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||
'flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors',
|
||||
isActive
|
||||
? 'bg-primary/10 text-primary'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
||||
? 'bg-primary/8 text-primary font-medium border-l-2 border-primary pl-[10px]'
|
||||
: 'text-muted-foreground hover:bg-accent hover:text-foreground font-normal'
|
||||
)
|
||||
}
|
||||
>
|
||||
<Icon className="h-4 w-4" />
|
||||
<Icon className="h-4 w-4 shrink-0" />
|
||||
{label}
|
||||
</NavLink>
|
||||
)
|
||||
@ -44,25 +44,32 @@ export function Sidebar() {
|
||||
const { user, logout, isAdmin } = useAuth()
|
||||
|
||||
return (
|
||||
<aside className="flex h-screen w-60 flex-col border-r bg-card px-3 py-4">
|
||||
<aside className="flex h-screen w-60 flex-col border-r bg-card px-3 py-5">
|
||||
{/* Logo */}
|
||||
<div className="mb-6 flex items-center gap-2 px-3">
|
||||
<TrendingUp className="h-6 w-6 text-primary" />
|
||||
<span className="text-lg font-bold">Tradarr</span>
|
||||
<div className="mb-7 px-3">
|
||||
<span className="font-serif text-xl font-bold tracking-tight text-foreground">
|
||||
Financ<span className="text-primary">IA</span>l
|
||||
</span>
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5 tracking-wide uppercase">Journal financier IA</p>
|
||||
</div>
|
||||
|
||||
{/* Séparateur */}
|
||||
<div className="border-t mb-4" />
|
||||
|
||||
{/* Navigation principale */}
|
||||
<nav className="flex flex-col gap-1">
|
||||
<nav className="flex flex-col gap-0.5">
|
||||
{navItems.map(item => <NavItem key={item.to} {...item} />)}
|
||||
</nav>
|
||||
|
||||
{/* Section admin */}
|
||||
{isAdmin && (
|
||||
<>
|
||||
<div className="mt-6 mb-2 px-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Administration</p>
|
||||
<div className="mt-5 mb-2 px-3 flex items-center gap-2">
|
||||
<div className="flex-1 border-t" />
|
||||
<p className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">Admin</p>
|
||||
<div className="flex-1 border-t" />
|
||||
</div>
|
||||
<nav className="flex flex-col gap-1">
|
||||
<nav className="flex flex-col gap-0.5">
|
||||
{adminItems.map(item => <NavItem key={item.to} {...item} />)}
|
||||
</nav>
|
||||
</>
|
||||
@ -71,12 +78,12 @@ export function Sidebar() {
|
||||
{/* User + logout */}
|
||||
<div className="mt-auto border-t pt-4">
|
||||
<div className="px-3 mb-2">
|
||||
<p className="text-sm font-medium truncate">{user?.email}</p>
|
||||
<p className="text-sm font-medium truncate text-foreground">{user?.email}</p>
|
||||
<p className="text-xs text-muted-foreground capitalize">{user?.role}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
Déconnexion
|
||||
|
||||
@ -3,16 +3,16 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/cn'
|
||||
|
||||
const badgeVariants = cva(
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',
|
||||
'inline-flex items-center rounded border px-2 py-0.5 text-xs font-medium transition-colors',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-transparent bg-primary text-primary-foreground',
|
||||
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
||||
destructive: 'border-transparent bg-destructive text-destructive-foreground',
|
||||
outline: 'text-foreground',
|
||||
bullish: 'border-transparent bg-bullish/20 text-bullish',
|
||||
bearish: 'border-transparent bg-bearish/20 text-bearish',
|
||||
destructive: 'border-transparent bg-destructive/10 text-destructive border-destructive/20',
|
||||
outline: 'text-foreground border-border',
|
||||
bullish: 'border-transparent bg-bullish/10 text-bullish',
|
||||
bearish: 'border-transparent bg-bearish/10 text-bearish',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'default' },
|
||||
|
||||
@ -16,9 +16,9 @@ const buttonVariants = cva(
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
sm: 'h-7 rounded px-2.5 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
icon: 'h-8 w-8',
|
||||
},
|
||||
},
|
||||
defaultVariants: { variant: 'default', size: 'default' },
|
||||
|
||||
@ -3,14 +3,14 @@ import { cn } from '@/lib/cn'
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />
|
||||
<div ref={ref} className={cn('rounded-md border bg-card text-card-foreground shadow-sm', className)} {...props} />
|
||||
)
|
||||
)
|
||||
Card.displayName = 'Card'
|
||||
|
||||
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
|
||||
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-5', className)} {...props} />
|
||||
)
|
||||
)
|
||||
CardHeader.displayName = 'CardHeader'
|
||||
@ -24,14 +24,14 @@ CardTitle.displayName = 'CardTitle'
|
||||
|
||||
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
||||
<div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
|
||||
)
|
||||
)
|
||||
CardContent.displayName = 'CardContent'
|
||||
|
||||
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
|
||||
<div ref={ref} className={cn('flex items-center p-5 pt-0', className)} {...props} />
|
||||
)
|
||||
)
|
||||
CardFooter.displayName = 'CardFooter'
|
||||
|
||||
@ -4,24 +4,71 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 222 47% 6%;
|
||||
--foreground: 210 40% 95%;
|
||||
--card: 222 47% 9%;
|
||||
--card-foreground: 210 40% 95%;
|
||||
--border: 217 33% 18%;
|
||||
--input: 217 33% 18%;
|
||||
--ring: 221 83% 53%;
|
||||
--primary: 221 83% 53%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 217 33% 18%;
|
||||
--secondary-foreground: 210 40% 95%;
|
||||
--muted: 217 33% 14%;
|
||||
--muted-foreground: 215 20% 55%;
|
||||
--accent: 217 33% 18%;
|
||||
--accent-foreground: 210 40% 95%;
|
||||
--destructive: 0 84% 60%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--radius: 0.5rem;
|
||||
/* ── Fond & texte ── */
|
||||
--background: 36 28% 97%;
|
||||
--foreground: 20 15% 9%;
|
||||
|
||||
/* ── Cartes ── */
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 20 15% 9%;
|
||||
|
||||
/* ── Bordures & inputs ── */
|
||||
--border: 30 18% 87%;
|
||||
--input: 30 18% 87%;
|
||||
--ring: 22 62% 28%;
|
||||
|
||||
/* ── Primaire : brun cognac ── */
|
||||
--primary: 22 62% 28%;
|
||||
--primary-foreground: 36 30% 96%;
|
||||
|
||||
/* ── Secondaire & muted ── */
|
||||
--secondary: 36 20% 93%;
|
||||
--secondary-foreground: 20 12% 25%;
|
||||
--muted: 35 18% 92%;
|
||||
--muted-foreground: 25 8% 48%;
|
||||
|
||||
/* ── Accent (hover) ── */
|
||||
--accent: 35 18% 90%;
|
||||
--accent-foreground: 20 15% 9%;
|
||||
|
||||
/* ── Destructif ── */
|
||||
--destructive: 0 78% 52%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--radius: 0.375rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* ── Fond & texte ── */
|
||||
--background: 22 18% 7%;
|
||||
--foreground: 36 22% 91%;
|
||||
|
||||
/* ── Cartes ── */
|
||||
--card: 22 15% 10%;
|
||||
--card-foreground: 36 22% 91%;
|
||||
|
||||
/* ── Bordures & inputs ── */
|
||||
--border: 25 14% 18%;
|
||||
--input: 25 14% 18%;
|
||||
--ring: 22 55% 48%;
|
||||
|
||||
/* ── Primaire : cognac clair (lisible sur fond sombre) ── */
|
||||
--primary: 22 55% 50%;
|
||||
--primary-foreground: 36 30% 96%;
|
||||
|
||||
/* ── Secondaire & muted ── */
|
||||
--secondary: 22 13% 14%;
|
||||
--secondary-foreground: 36 18% 78%;
|
||||
--muted: 22 12% 13%;
|
||||
--muted-foreground: 30 8% 52%;
|
||||
|
||||
/* ── Accent (hover) ── */
|
||||
--accent: 22 13% 15%;
|
||||
--accent-foreground: 36 22% 91%;
|
||||
|
||||
/* ── Destructif ── */
|
||||
--destructive: 0 72% 55%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,10 +80,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar style */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { @apply bg-muted; }
|
||||
::-webkit-scrollbar-thumb { @apply bg-border rounded-full; }
|
||||
.font-serif {
|
||||
font-family: 'Playfair Display', Georgia, serif;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar { width: 5px; height: 5px; }
|
||||
::-webkit-scrollbar-track { background: hsl(var(--muted)); }
|
||||
::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 9999px; }
|
||||
|
||||
@layer utilities {
|
||||
.scrollbar-none { scrollbar-width: none; }
|
||||
|
||||
40
frontend/src/lib/theme.tsx
Normal file
40
frontend/src/lib/theme.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
|
||||
interface ThemeCtx {
|
||||
theme: Theme
|
||||
setTheme: (t: Theme) => void
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<ThemeCtx | null>(null)
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>(() => {
|
||||
const saved = localStorage.getItem('theme') as Theme | null
|
||||
if (saved === 'light' || saved === 'dark') return saved
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement
|
||||
root.classList.toggle('dark', theme === 'dark')
|
||||
localStorage.setItem('theme', theme)
|
||||
}, [theme])
|
||||
|
||||
function setTheme(t: Theme) {
|
||||
setThemeState(t)
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useTheme() {
|
||||
const ctx = useContext(ThemeContext)
|
||||
if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
|
||||
return ctx
|
||||
}
|
||||
@ -2,13 +2,16 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { RouterProvider } from 'react-router-dom'
|
||||
import { AuthProvider } from '@/lib/auth'
|
||||
import { ThemeProvider } from '@/lib/theme'
|
||||
import { router } from '@/lib/router'
|
||||
import './index.css'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthProvider>
|
||||
<ThemeProvider>
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthProvider>
|
||||
</ThemeProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { useAuth } from '@/lib/auth'
|
||||
import { authApi } from '@/api/auth'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
export function Login() {
|
||||
const { token, login } = useAuth()
|
||||
@ -33,31 +31,64 @@ export function Login() {
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader className="text-center">
|
||||
<div className="mb-2 flex justify-center">
|
||||
<TrendingUp className="h-10 w-10 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">Tradarr</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">Votre assistant trading IA</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Motif de fond discret */}
|
||||
<div className="absolute inset-0 opacity-[0.03] pointer-events-none"
|
||||
style={{ backgroundImage: 'repeating-linear-gradient(0deg, hsl(20 15% 9%), hsl(20 15% 9%) 1px, transparent 1px, transparent 40px), repeating-linear-gradient(90deg, hsl(20 15% 9%), hsl(20 15% 9%) 1px, transparent 1px, transparent 40px)' }}
|
||||
/>
|
||||
|
||||
<div className="relative w-full max-w-sm">
|
||||
{/* En-tête */}
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="font-serif text-4xl font-bold text-foreground">
|
||||
Financ<span className="text-primary">IA</span>l
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-muted-foreground tracking-wide">
|
||||
Votre journal financier augmenté par l'IA
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Carte formulaire */}
|
||||
<div className="rounded-lg border bg-card shadow-sm p-6">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)} required autoComplete="email" />
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="email">Adresse email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
autoComplete="email"
|
||||
placeholder="vous@exemple.com"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="password">Mot de passe</Label>
|
||||
<Input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)} required autoComplete="current-password" />
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{error && (
|
||||
<p className="text-sm text-destructive border border-destructive/20 bg-destructive/5 rounded px-3 py-2">
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
<Button type="submit" className="w-full mt-2" disabled={loading}>
|
||||
{loading ? 'Connexion…' : 'Se connecter'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<p className="mt-6 text-center text-xs text-muted-foreground">
|
||||
© {new Date().getFullYear()} FinancIAl — Accès restreint
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Save, RotateCcw } from 'lucide-react'
|
||||
import { Save, RotateCcw, Sun, Moon } from 'lucide-react'
|
||||
import { adminApi, type Setting } from '@/api/admin'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Spinner } from '@/components/ui/spinner'
|
||||
import { useTheme } from '@/lib/theme'
|
||||
|
||||
const NUMERIC_SETTINGS: Record<string, { label: string; description: string }> = {
|
||||
summary_max_articles: { label: 'Articles max par résumé', description: 'Nombre maximum d\'articles envoyés à l\'IA pour la passe 2 (résumé)' },
|
||||
@ -20,6 +21,7 @@ const COMMON_TIMEZONES = [
|
||||
]
|
||||
|
||||
export function AdminSettings() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [settings, setSettings] = useState<Setting[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [values, setValues] = useState<Record<string, string>>({})
|
||||
@ -90,6 +92,37 @@ export function AdminSettings() {
|
||||
<p className="text-muted-foreground text-sm">Configuration globale du service</p>
|
||||
</div>
|
||||
|
||||
{/* Apparence */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Apparence</CardTitle></CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setTheme('light')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md border text-sm font-medium transition-colors ${
|
||||
theme === 'light'
|
||||
? 'bg-primary text-primary-foreground border-primary'
|
||||
: 'bg-transparent text-muted-foreground border-border hover:bg-accent hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<Sun className="h-4 w-4" />
|
||||
Clair
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTheme('dark')}
|
||||
className={`flex items-center gap-2 px-4 py-2 rounded-md border text-sm font-medium transition-colors ${
|
||||
theme === 'dark'
|
||||
? 'bg-primary text-primary-foreground border-primary'
|
||||
: 'bg-transparent text-muted-foreground border-border hover:bg-accent hover:text-foreground'
|
||||
}`}
|
||||
>
|
||||
<Moon className="h-4 w-4" />
|
||||
Sombre
|
||||
</button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Fuseau horaire */}
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Fuseau horaire</CardTitle></CardHeader>
|
||||
|
||||
@ -5,6 +5,9 @@ export default {
|
||||
content: ['./index.html', './src/**/*.{ts,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
serif: ['Playfair Display', 'Georgia', 'serif'],
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
@ -17,9 +20,9 @@ export default {
|
||||
muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
|
||||
accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
|
||||
card: { DEFAULT: 'hsl(var(--card))', foreground: 'hsl(var(--card-foreground))' },
|
||||
bullish: '#22c55e',
|
||||
bearish: '#ef4444',
|
||||
neutral: '#94a3b8',
|
||||
bullish: '#15803d',
|
||||
bearish: '#b91c1c',
|
||||
neutral: '#78716c',
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
|
||||
@ -1 +1 @@
|
||||
{"root":["./src/main.tsx","./src/api/admin.ts","./src/api/articles.ts","./src/api/assets.ts","./src/api/auth.ts","./src/api/client.ts","./src/api/reports.ts","./src/api/summaries.ts","./src/components/layout/AppLayout.tsx","./src/components/layout/MobileNav.tsx","./src/components/layout/Sidebar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/markdown.tsx","./src/components/ui/select.tsx","./src/components/ui/spinner.tsx","./src/lib/auth.tsx","./src/lib/cn.ts","./src/lib/router.tsx","./src/pages/Dashboard.tsx","./src/pages/Feed.tsx","./src/pages/Login.tsx","./src/pages/Reports.tsx","./src/pages/Watchlist.tsx","./src/pages/admin/AIProviders.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminSettings.tsx","./src/pages/admin/AdminUsers.tsx","./src/pages/admin/Credentials.tsx","./src/pages/admin/Jobs.tsx","./src/pages/admin/Schedule.tsx","./src/pages/admin/Sources.tsx"],"version":"5.9.3"}
|
||||
{"root":["./src/main.tsx","./src/api/admin.ts","./src/api/articles.ts","./src/api/assets.ts","./src/api/auth.ts","./src/api/client.ts","./src/api/reports.ts","./src/api/summaries.ts","./src/components/layout/AppLayout.tsx","./src/components/layout/MobileNav.tsx","./src/components/layout/Sidebar.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/markdown.tsx","./src/components/ui/select.tsx","./src/components/ui/spinner.tsx","./src/lib/auth.tsx","./src/lib/cn.ts","./src/lib/router.tsx","./src/lib/theme.tsx","./src/pages/Dashboard.tsx","./src/pages/Feed.tsx","./src/pages/Login.tsx","./src/pages/Reports.tsx","./src/pages/Watchlist.tsx","./src/pages/admin/AIProviders.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminSettings.tsx","./src/pages/admin/AdminUsers.tsx","./src/pages/admin/Credentials.tsx","./src/pages/admin/Jobs.tsx","./src/pages/admin/Schedule.tsx","./src/pages/admin/Sources.tsx"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user