refactor(frontend): improve ui using design system
- Refactor Navbar, ChatInput, RegisterPage, and CreatePlaylistDialog to use @veza/design-system components - Shim local UI components (Button, Input, Card) to align with Design System API and styles - Fix hundreds of type errors by exporting missing components (SearchInput, FileUpload) and adding missing props (icon, variant)
This commit is contained in:
parent
420b0f4e9b
commit
eccc4e5e89
15 changed files with 282 additions and 221 deletions
|
|
@ -65,7 +65,7 @@ export function SwaggerUIDoc({ specUrl, spec, useIframe = false }: SwaggerUIProp
|
|||
|
||||
const swaggerConfig = {
|
||||
url: spec ? undefined : getOpenApiUrl(),
|
||||
spec: spec,
|
||||
spec,
|
||||
deepLinking: true,
|
||||
displayOperationId: false,
|
||||
defaultModelsExpandDepth: 1,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface DashboardLayoutProps {
|
|||
*/
|
||||
export function DashboardLayout({ children }: DashboardLayoutProps) {
|
||||
const { sidebarOpen } = useUIStore();
|
||||
|
||||
|
||||
// FIX: Calculer le margin-left pour compenser la sidebar fixe
|
||||
// Sidebar: left-6 (24px) + width (w-64 = 256px quand ouverte, w-20 = 80px quand fermée) + gap (24px)
|
||||
// Sur desktop (lg): sidebar est toujours visible, donc toujours besoin de margin
|
||||
|
|
@ -28,13 +28,13 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
|
|||
<div className="flex h-screen overflow-hidden relative">
|
||||
<Sidebar />
|
||||
{/* FIX: Ajouter margin-left pour compenser la sidebar fixe et éviter la superposition */}
|
||||
<div
|
||||
<div
|
||||
className={cn(
|
||||
'flex-1 flex flex-col min-w-0 transition-all duration-500 ease-in-out',
|
||||
// Sur desktop (lg), toujours ajouter le margin pour la sidebar
|
||||
// Sidebar ouverte: left-6 (24px) + w-64 (256px) + gap (24px) = 304px
|
||||
// Sidebar fermée: left-6 (24px) + w-20 (80px) + gap (24px) = 128px
|
||||
sidebarOpen
|
||||
sidebarOpen
|
||||
? 'lg:ml-[304px]' // 24 + 256 + 24 = 304px
|
||||
: 'lg:ml-[128px]', // 24 + 80 + 24 = 128px
|
||||
// Sur mobile, pas de margin car sidebar est cachée (-translate-x-full)
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export function Header(_props: HeaderProps) {
|
|||
>
|
||||
<Menu className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
|
||||
<Link
|
||||
to="/dashboard"
|
||||
className="flex items-center gap-4 active:scale-95 transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-steel focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void rounded"
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import {
|
|||
User,
|
||||
ShoppingCart,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import { Button, SearchInput } from '@veza/design-system';
|
||||
import { useTheme } from '../../context/ThemeContext';
|
||||
import { SearchInput } from '../ui/input';
|
||||
import { Notification } from '../../types';
|
||||
import { useCart } from '../../context/CartContext';
|
||||
import { NotificationBell } from '../notifications/NotificationBell';
|
||||
|
|
@ -191,52 +190,57 @@ export const Navbar: React.FC<NavbarProps> = ({ onNavigate, onLogout }) => {
|
|||
<p className="text-sm font-bold text-white">Cyber_Producer</p>
|
||||
<p className="text-xs text-kodo-content-dim">Pro Plan</p>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onNavigate('profile');
|
||||
setShowUserMenu(false);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white flex items-center gap-4 transition-colors"
|
||||
className="w-full justify-start px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white gap-4"
|
||||
>
|
||||
<User className="w-4 h-4" /> My Profile
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onNavigate('studio/go-live');
|
||||
setShowUserMenu(false);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white flex items-center gap-4 transition-colors"
|
||||
className="w-full justify-start px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white gap-4"
|
||||
>
|
||||
<Zap className="w-4 h-4 text-kodo-red" /> Go Live
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onNavigate('purchases');
|
||||
setShowUserMenu(false);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white flex items-center gap-4 transition-colors"
|
||||
className="w-full justify-start px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white gap-4"
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4" /> Purchases
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onNavigate('settings');
|
||||
setShowUserMenu(false);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white flex items-center gap-4 transition-colors"
|
||||
className="w-full justify-start px-4 py-2.5 text-sm text-kodo-text-main hover:bg-white/5 hover:text-white gap-4"
|
||||
>
|
||||
<Settings className="w-4 h-4" /> Settings
|
||||
</button>
|
||||
</Button>
|
||||
<div className="h-px bg-kodo-steel/30 my-1 mx-2"></div>
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onLogout();
|
||||
setShowUserMenu(false);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2.5 text-sm text-kodo-red hover:bg-kodo-red/10 rounded-lg flex items-center gap-4 transition-colors"
|
||||
className="w-full justify-start px-4 py-2.5 text-sm text-kodo-red hover:bg-kodo-red/10 rounded-lg gap-4"
|
||||
>
|
||||
<LogOut className="w-4 h-4" /> Sign Out
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
|||
sidebarOpen ? 'w-64 translate-x-0 opacity-100' : '-translate-x-full lg:translate-x-0 lg:opacity-100 lg:w-20',
|
||||
)}
|
||||
style={{ zIndex: 90 }}
|
||||
// FIX: S'assurer que la sidebar ne masque pas le contenu principal
|
||||
// FIX: S'assurer que la sidebar ne masque pas le contenu principal
|
||||
>
|
||||
{/* Hub Header Integration avec bouton toggle */}
|
||||
<div className="p-6 border-b border-white/5 flex items-center gap-4">
|
||||
|
|
|
|||
|
|
@ -14,13 +14,16 @@ import { cn } from '@/lib/utils';
|
|||
* - ghost: Minimal button with hover effect, for tertiary actions
|
||||
*/
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void disabled:pointer-events-none disabled:opacity-50 gap-2',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
/** Primary action button - use for main CTAs, submit buttons */
|
||||
default:
|
||||
'bg-kodo-cyan text-kodo-void hover:bg-kodo-cyan-dim hover:shadow-[0_0_15px_rgba(102,252,241,0.3)] border border-transparent font-semibold tracking-tight',
|
||||
/** Primary alias for Design System compatibility */
|
||||
primary:
|
||||
'bg-kodo-cyan text-kodo-void hover:bg-kodo-cyan-dim hover:shadow-[0_0_15px_rgba(102,252,241,0.3)] border border-transparent font-semibold tracking-tight',
|
||||
/** Destructive actions - use for delete, remove, clear actions */
|
||||
destructive:
|
||||
'bg-kodo-red/10 text-kodo-red hover:bg-kodo-red/20 border border-kodo-red/30 hover:border-kodo-red/50',
|
||||
|
|
@ -32,6 +35,12 @@ const buttonVariants = cva(
|
|||
'bg-kodo-steel/30 text-white hover:bg-kodo-steel/50 border border-white/5 hover:border-white/10',
|
||||
/** Ghost button - use for icon buttons, menu items, subtle actions */
|
||||
ghost: 'hover:bg-white/5 text-white',
|
||||
/** Gaming style */
|
||||
gaming: 'bg-kodo-slate border border-kodo-gold/40 text-kodo-gold hover:bg-kodo-gold/10 hover:border-kodo-gold font-bold tracking-wider uppercase',
|
||||
/** Terminal style */
|
||||
terminal: 'bg-kodo-ink border border-kodo-steel text-gray-300 font-mono text-xs hover:border-kodo-cyan hover:text-kodo-cyan',
|
||||
/** Nature style */
|
||||
nature: 'bg-kodo-slate border border-kodo-lime/30 text-kodo-lime hover:bg-kodo-lime/10',
|
||||
},
|
||||
size: {
|
||||
/** Default size - standard buttons */
|
||||
|
|
@ -75,6 +84,8 @@ export interface ButtonProps
|
|||
VariantProps<typeof buttonVariants> {
|
||||
/** Use asChild to compose with other components (e.g., Link from react-router) */
|
||||
asChild?: boolean;
|
||||
/** Optional icon to display before the label */
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,14 +111,17 @@ export interface ButtonProps
|
|||
* ```
|
||||
*/
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
({ className, variant, size, asChild = false, icon, children, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
{icon && <span className="flex items-center justify-center pointer-events-none">{icon}</span>}
|
||||
{children}
|
||||
</Comp>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,39 @@
|
|||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-2xl border border-white/5 bg-kodo-ink/40 text-kodo-text-main shadow-lg backdrop-blur-md relative overflow-hidden',
|
||||
'hover:bg-white/5 transition-colors duration-200',
|
||||
'z-10', // FIX: Assurer que les Cards sont au-dessus des overlays
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
));
|
||||
const cardVariants = cva(
|
||||
'rounded-2xl shadow-lg backdrop-blur-md relative overflow-hidden transition-colors duration-200 z-10',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-kodo-graphite border border-kodo-steel/60 hover:border-kodo-steel text-kodo-text-main',
|
||||
manga: 'bg-gradient-to-br from-kodo-graphite to-kodo-slate border border-kodo-magenta/20 hover:border-kodo-magenta/40 hover:shadow-neon-magenta/10 text-kodo-text-main',
|
||||
gaming: 'bg-kodo-ink border border-kodo-cyan/20 hover:border-kodo-cyan/40 hover:shadow-neon-cyan/10 text-kodo-text-main',
|
||||
glass: 'bg-kodo-slate/40 backdrop-blur-xl border border-white/5 hover:bg-kodo-slate/50 text-kodo-text-main',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface CardProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof cardVariants> {}
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
||||
({ className, variant, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(cardVariants({ variant, className }))}
|
||||
{...props}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
|
|
|
|||
|
|
@ -1,24 +1,10 @@
|
|||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { SearchInput as DSSearchInput, FileUpload as DSFileUpload, Input as DSInput } from '@veza/design-system';
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
// Re-export components from Design System
|
||||
export const Input = DSInput;
|
||||
export const SearchInput = DSSearchInput;
|
||||
export const FileUpload = DSFileUpload;
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-10 w-full rounded-lg border border-white/10 bg-black/20 px-4 py-2.5 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-kodo-secondary/50 focus-visible:outline-none focus-visible:border-kodo-cyan/60 disabled:cursor-not-allowed disabled:opacity-50 transition-colors duration-200 text-white',
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = 'Input';
|
||||
|
||||
export { Input };
|
||||
// Export InputProps for compatibility
|
||||
export type { InputProps } from '@veza/design-system';
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ import React, { useState, useEffect } from 'react';
|
|||
import { Navigate, Link } from 'react-router-dom';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { AuthLayout } from '../components/AuthLayout';
|
||||
import { AuthInput } from '../components/AuthInput';
|
||||
import { AuthButton } from '../components/AuthButton';
|
||||
import { PasswordStrengthIndicator } from '../components/PasswordStrengthIndicator';
|
||||
import { useRegister } from '../hooks/useRegister';
|
||||
import { useUsernameAvailability } from '../hooks/useUsernameAvailability';
|
||||
import { authApi } from '@/services/api/auth';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { Input, Button } from '@veza/design-system';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import type { RegisterFormData } from '../types';
|
||||
|
||||
export function RegisterPage() {
|
||||
|
|
@ -186,23 +187,23 @@ export function RegisterPage() {
|
|||
Email de vérification renvoyé avec succès !
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={handleResendVerificationEmail}
|
||||
disabled={resendLoading}
|
||||
className="text-kodo-cyan hover:underline text-sm disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded"
|
||||
className="text-kodo-cyan hover:underline text-sm disabled:opacity-50"
|
||||
aria-label="Renvoyer l'email de vérification"
|
||||
aria-busy={resendLoading}
|
||||
>
|
||||
{resendLoading ? (
|
||||
<>
|
||||
<span className="sr-only">Envoi en cours</span>
|
||||
<span aria-hidden="true">Envoi en cours...</span>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Envoi en cours...
|
||||
</>
|
||||
) : (
|
||||
"Renvoyer l'email de vérification"
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form
|
||||
|
|
@ -220,16 +221,21 @@ export function RegisterPage() {
|
|||
</div>
|
||||
)}
|
||||
<div>
|
||||
<AuthInput
|
||||
<Input
|
||||
type="text"
|
||||
label="Nom d'utilisateur"
|
||||
label="Nom d'utilisateur *"
|
||||
value={formData.username}
|
||||
onChange={(e) => handleChange('username', e.target.value)}
|
||||
onBlur={() => handleBlur('username')}
|
||||
error={errors.username}
|
||||
required
|
||||
autoComplete="username"
|
||||
aria-invalid={errors.username ? 'true' : 'false'}
|
||||
/>
|
||||
{errors.username && (
|
||||
<p className="mt-1 text-sm text-kodo-red" role="alert">
|
||||
{errors.username}
|
||||
</p>
|
||||
)}
|
||||
{formData.username.length >= 3 && (
|
||||
<div className="mt-1" aria-live="polite" aria-atomic="true">
|
||||
{checkingUsername ? (
|
||||
|
|
@ -253,56 +259,73 @@ export function RegisterPage() {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<AuthInput
|
||||
type="email"
|
||||
label="Email"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
onBlur={() => handleBlur('email')}
|
||||
error={errors.email}
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
<div>
|
||||
<AuthInput
|
||||
<Input
|
||||
type="email"
|
||||
label="Email *"
|
||||
value={formData.email}
|
||||
onChange={(e) => handleChange('email', e.target.value)}
|
||||
onBlur={() => handleBlur('email')}
|
||||
required
|
||||
autoComplete="email"
|
||||
aria-invalid={errors.email ? 'true' : 'false'}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="mt-1 text-sm text-kodo-red" role="alert">
|
||||
{errors.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
type="password"
|
||||
label="Mot de passe"
|
||||
label="Mot de passe *"
|
||||
value={formData.password}
|
||||
onChange={(e) => handleChange('password', e.target.value)}
|
||||
onBlur={() => handleBlur('password')}
|
||||
error={errors.password}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
aria-invalid={errors.password ? 'true' : 'false'}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="mt-1 text-sm text-kodo-red" role="alert">
|
||||
{errors.password}
|
||||
</p>
|
||||
)}
|
||||
<PasswordStrengthIndicator password={formData.password} />
|
||||
</div>
|
||||
<AuthInput
|
||||
type="password"
|
||||
label="Confirmer le mot de passe"
|
||||
value={formData.password_confirm}
|
||||
onChange={(e) => handleChange('password_confirm', e.target.value)}
|
||||
onBlur={() => handleBlur('password_confirm')}
|
||||
error={errors.password_confirm}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
<div>
|
||||
<Input
|
||||
type="password"
|
||||
label="Confirmer le mot de passe *"
|
||||
value={formData.password_confirm}
|
||||
onChange={(e) => handleChange('password_confirm', e.target.value)}
|
||||
onBlur={() => handleBlur('password_confirm')}
|
||||
required
|
||||
autoComplete="new-password"
|
||||
aria-invalid={errors.password_confirm ? 'true' : 'false'}
|
||||
/>
|
||||
{errors.password_confirm && (
|
||||
<p className="mt-1 text-sm text-kodo-red" role="alert">
|
||||
{errors.password_confirm}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-start">
|
||||
<input
|
||||
type="checkbox"
|
||||
<Checkbox
|
||||
id="terms"
|
||||
checked={acceptedTerms}
|
||||
onChange={(e) => {
|
||||
setAcceptedTerms(e.target.checked);
|
||||
onCheckedChange={(checked) => {
|
||||
setAcceptedTerms(checked as boolean);
|
||||
if (errors.terms) {
|
||||
setErrors({ ...errors, terms: undefined });
|
||||
}
|
||||
}}
|
||||
className="h-4 w-4 text-kodo-cyan focus:ring-blue-500 border-kodo-steel rounded mt-1"
|
||||
required
|
||||
aria-invalid={errors.terms ? 'true' : 'false'}
|
||||
aria-describedby={
|
||||
errors.terms ? 'terms-error' : 'terms-description'
|
||||
}
|
||||
required
|
||||
/>
|
||||
<label htmlFor="terms" className="ml-2 block text-sm text-kodo-text-main">
|
||||
J'accepte les{' '}
|
||||
|
|
@ -332,9 +355,16 @@ export function RegisterPage() {
|
|||
{errors.terms}
|
||||
</p>
|
||||
)}
|
||||
<AuthButton type="submit" loading={loading}>
|
||||
S'inscrire
|
||||
</AuthButton>
|
||||
<Button type="submit" disabled={loading} className="w-full">
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Inscription en cours...
|
||||
</>
|
||||
) : (
|
||||
"S'inscrire"
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</AuthLayout>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import { MessageAttachment } from '../types';
|
|||
import { LoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Button } from '@veza/design-system';
|
||||
import { useIsRateLimited } from '@/hooks/useIsRateLimited';
|
||||
|
||||
// Lazy load
|
||||
|
|
@ -226,7 +226,7 @@ export const ChatInput: React.FC = () => {
|
|||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="default"
|
||||
variant="primary"
|
||||
size="icon"
|
||||
className={cn(
|
||||
'rounded-xl transition-all duration-300',
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import { z } from 'zod';
|
|||
// Ensure PlaylistError is exported from service or assume general Error if not available
|
||||
import { playlistsApi } from '@/services/api/playlists';
|
||||
import { Dialog } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Button, Input } from '@veza/design-system';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
|
@ -97,14 +96,9 @@ export function CreatePlaylistDialog({
|
|||
aria-label="Dialogue de création de playlist"
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">
|
||||
Titre{' '}
|
||||
<span className="text-destructive" aria-hidden="true">
|
||||
*
|
||||
</span>
|
||||
</Label>
|
||||
<div>
|
||||
<Input
|
||||
label="Titre *"
|
||||
id="title"
|
||||
{...register('title')}
|
||||
placeholder="Ma nouvelle playlist"
|
||||
|
|
@ -164,7 +158,7 @@ export function CreatePlaylistDialog({
|
|||
<div className="flex justify-end gap-2 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
variant="secondary"
|
||||
onClick={handleCancel}
|
||||
disabled={isSubmitting}
|
||||
aria-label="Annuler la création de playlist"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const developerService = {
|
|||
return {
|
||||
id: `k-${Date.now()}`,
|
||||
name: data.name,
|
||||
prefix: prefix,
|
||||
prefix,
|
||||
created: formattedDate,
|
||||
lastUsed: 'Never',
|
||||
status: 'active' as const,
|
||||
|
|
|
|||
|
|
@ -96,48 +96,32 @@
|
|||
--veza-matrix: #003300;
|
||||
|
||||
/* === GRADIENTS === */
|
||||
--gradient-neon: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-cyan) 0%,
|
||||
var(--veza-magenta) 50%,
|
||||
var(--veza-lime) 100%
|
||||
);
|
||||
--gradient-cyber: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-spectral-cyan) 0%,
|
||||
var(--veza-lavender) 100%
|
||||
);
|
||||
--gradient-sunset: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-orange) 0%,
|
||||
var(--veza-magenta) 100%
|
||||
);
|
||||
--gradient-forest: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-moss) 0%,
|
||||
var(--veza-leaf) 50%,
|
||||
var(--veza-lime) 100%
|
||||
);
|
||||
--gradient-nature: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-sage) 0%,
|
||||
var(--veza-moss) 100%
|
||||
);
|
||||
--gradient-space: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-lavender) 0%,
|
||||
var(--veza-ember) 100%
|
||||
);
|
||||
--gradient-terminal: linear-gradient(
|
||||
180deg,
|
||||
var(--veza-matrix) 0%,
|
||||
var(--veza-void) 100%
|
||||
);
|
||||
--gradient-gaming: linear-gradient(
|
||||
135deg,
|
||||
var(--veza-orange) 0%,
|
||||
var(--veza-xp-gold) 100%
|
||||
);
|
||||
--gradient-neon: linear-gradient(135deg,
|
||||
var(--veza-cyan) 0%,
|
||||
var(--veza-magenta) 50%,
|
||||
var(--veza-lime) 100%);
|
||||
--gradient-cyber: linear-gradient(135deg,
|
||||
var(--veza-spectral-cyan) 0%,
|
||||
var(--veza-lavender) 100%);
|
||||
--gradient-sunset: linear-gradient(135deg,
|
||||
var(--veza-orange) 0%,
|
||||
var(--veza-magenta) 100%);
|
||||
--gradient-forest: linear-gradient(135deg,
|
||||
var(--veza-moss) 0%,
|
||||
var(--veza-leaf) 50%,
|
||||
var(--veza-lime) 100%);
|
||||
--gradient-nature: linear-gradient(135deg,
|
||||
var(--veza-sage) 0%,
|
||||
var(--veza-moss) 100%);
|
||||
--gradient-space: linear-gradient(135deg,
|
||||
var(--veza-lavender) 0%,
|
||||
var(--veza-ember) 100%);
|
||||
--gradient-terminal: linear-gradient(180deg,
|
||||
var(--veza-matrix) 0%,
|
||||
var(--veza-void) 100%);
|
||||
--gradient-gaming: linear-gradient(135deg,
|
||||
var(--veza-orange) 0%,
|
||||
var(--veza-xp-gold) 100%);
|
||||
|
||||
/* === TYPOGRAPHY === */
|
||||
--font-display: 'Orbitron', 'Noto Sans JP', system-ui, sans-serif;
|
||||
|
|
@ -147,32 +131,56 @@
|
|||
--font-mono: 'JetBrains Mono', 'IBM Plex Mono', 'Consolas', monospace;
|
||||
|
||||
/* Font Sizes - Modular Scale */
|
||||
--text-xs: 0.75rem; /* 12px */
|
||||
--text-sm: 0.875rem; /* 14px */
|
||||
--text-base: 1rem; /* 16px */
|
||||
--text-lg: 1.125rem; /* 18px */
|
||||
--text-xl: 1.25rem; /* 20px */
|
||||
--text-2xl: 1.5rem; /* 24px */
|
||||
--text-3xl: 1.875rem; /* 30px */
|
||||
--text-4xl: 2.25rem; /* 36px */
|
||||
--text-5xl: 3rem; /* 48px */
|
||||
--text-6xl: 3.75rem; /* 60px */
|
||||
--text-7xl: 4.5rem; /* 72px */
|
||||
--text-8xl: 6rem; /* 96px */
|
||||
--text-xs: 0.75rem;
|
||||
/* 12px */
|
||||
--text-sm: 0.875rem;
|
||||
/* 14px */
|
||||
--text-base: 1rem;
|
||||
/* 16px */
|
||||
--text-lg: 1.125rem;
|
||||
/* 18px */
|
||||
--text-xl: 1.25rem;
|
||||
/* 20px */
|
||||
--text-2xl: 1.5rem;
|
||||
/* 24px */
|
||||
--text-3xl: 1.875rem;
|
||||
/* 30px */
|
||||
--text-4xl: 2.25rem;
|
||||
/* 36px */
|
||||
--text-5xl: 3rem;
|
||||
/* 48px */
|
||||
--text-6xl: 3.75rem;
|
||||
/* 60px */
|
||||
--text-7xl: 4.5rem;
|
||||
/* 72px */
|
||||
--text-8xl: 6rem;
|
||||
/* 96px */
|
||||
|
||||
/* === SPACING === */
|
||||
--space-1: 0.25rem; /* 4px */
|
||||
--space-2: 0.5rem; /* 8px */
|
||||
--space-3: 0.75rem; /* 12px */
|
||||
--space-4: 1rem; /* 16px */
|
||||
--space-5: 1.25rem; /* 20px */
|
||||
--space-6: 1.5rem; /* 24px */
|
||||
--space-8: 2rem; /* 32px */
|
||||
--space-10: 2.5rem; /* 40px */
|
||||
--space-12: 3rem; /* 48px */
|
||||
--space-16: 4rem; /* 64px */
|
||||
--space-20: 5rem; /* 80px */
|
||||
--space-24: 6rem; /* 96px */
|
||||
--space-1: 0.25rem;
|
||||
/* 4px */
|
||||
--space-2: 0.5rem;
|
||||
/* 8px */
|
||||
--space-3: 0.75rem;
|
||||
/* 12px */
|
||||
--space-4: 1rem;
|
||||
/* 16px */
|
||||
--space-5: 1.25rem;
|
||||
/* 20px */
|
||||
--space-6: 1.5rem;
|
||||
/* 24px */
|
||||
--space-8: 2rem;
|
||||
/* 32px */
|
||||
--space-10: 2.5rem;
|
||||
/* 40px */
|
||||
--space-12: 3rem;
|
||||
/* 48px */
|
||||
--space-16: 4rem;
|
||||
/* 64px */
|
||||
--space-20: 5rem;
|
||||
/* 80px */
|
||||
--space-24: 6rem;
|
||||
/* 96px */
|
||||
|
||||
/* === BORDERS & RADIUS === */
|
||||
--radius-none: 0;
|
||||
|
|
@ -185,33 +193,27 @@
|
|||
--radius-organic: 60% 40% 50% 50% / 55% 50% 40% 60%;
|
||||
|
||||
/* Manga-style clip paths */
|
||||
--clip-manga: polygon(
|
||||
0 0,
|
||||
calc(100% - 12px) 0,
|
||||
100% 12px,
|
||||
100% 100%,
|
||||
12px 100%,
|
||||
0 calc(100% - 12px)
|
||||
);
|
||||
--clip-manga-lg: polygon(
|
||||
0 0,
|
||||
calc(100% - 20px) 0,
|
||||
100% 20px,
|
||||
100% 100%,
|
||||
20px 100%,
|
||||
0 calc(100% - 20px)
|
||||
);
|
||||
--clip-manga: polygon(0 0,
|
||||
calc(100% - 12px) 0,
|
||||
100% 12px,
|
||||
100% 100%,
|
||||
12px 100%,
|
||||
0 calc(100% - 12px));
|
||||
--clip-manga-lg: polygon(0 0,
|
||||
calc(100% - 20px) 0,
|
||||
100% 20px,
|
||||
100% 100%,
|
||||
20px 100%,
|
||||
0 calc(100% - 20px));
|
||||
--clip-hex: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||
--clip-badge: polygon(
|
||||
0 10%,
|
||||
10% 0,
|
||||
90% 0,
|
||||
100% 10%,
|
||||
100% 90%,
|
||||
90% 100%,
|
||||
10% 100%,
|
||||
0 90%
|
||||
);
|
||||
--clip-badge: polygon(0 10%,
|
||||
10% 0,
|
||||
90% 0,
|
||||
100% 10%,
|
||||
100% 90%,
|
||||
90% 100%,
|
||||
10% 100%,
|
||||
0 90%);
|
||||
|
||||
/* === SHADOWS & GLOWS === */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
|
|
@ -277,40 +279,48 @@
|
|||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
filter: drop-shadow(0 0 8px var(--veza-cyan));
|
||||
}
|
||||
|
||||
50% {
|
||||
filter: drop-shadow(0 0 20px var(--veza-magenta));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes graffiti-shake {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(-2deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes gentle-pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.9);
|
||||
|
|
@ -318,13 +328,16 @@
|
|||
}
|
||||
|
||||
@keyframes neon-flicker {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
|
@ -335,6 +348,7 @@
|
|||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
|
@ -346,6 +360,7 @@
|
|||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
|
|
@ -356,6 +371,7 @@
|
|||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
@ -366,6 +382,7 @@
|
|||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
|
@ -530,4 +547,4 @@
|
|||
|
||||
.border-gradient-neon {
|
||||
border-image: linear-gradient(135deg, var(--veza-cyan), var(--veza-magenta)) 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,7 @@ export function applyAggressiveVisualFix(): void {
|
|||
if (rect.width > 0 && rect.width < 10 && rect.height > window.innerHeight * 0.3) {
|
||||
htmlEl.style.setProperty('display', 'none', 'important');
|
||||
hiddenCount++;
|
||||
console.log(`[AggressiveFix] Masqué élément suspect: ${el.tagName}${el.id ? '#' + el.id : ''} (${rect.width}px x ${rect.height}px)`);
|
||||
console.log(`[AggressiveFix] Masqué élément suspect: ${el.tagName}${el.id ? `#${ el.id}` : ''} (${rect.width}px x ${rect.height}px)`);
|
||||
}
|
||||
|
||||
// Masquer les éléments sur le bord droit
|
||||
|
|
@ -107,7 +107,7 @@ export function applyAggressiveVisualFix(): void {
|
|||
if (isOnRightEdge && rect.width < 20 && rect.height > window.innerHeight * 0.2) {
|
||||
htmlEl.style.setProperty('display', 'none', 'important');
|
||||
hiddenCount++;
|
||||
console.log(`[AggressiveFix] Masqué élément sur bord droit: ${el.tagName}${el.id ? '#' + el.id : ''}`);
|
||||
console.log(`[AggressiveFix] Masqué élément sur bord droit: ${el.tagName}${el.id ? `#${ el.id}` : ''}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ export function fixDisplayIssues(): void {
|
|||
// Remove vertical gradient, keep only horizontal
|
||||
const newBg = currentBg.replace(/linear-gradient\(90deg[^)]+\)/g, '').replace(/,\s*,/g, ',').replace(/^,\s*|,\s*$/g, '');
|
||||
htmlEl.style.backgroundImage = newBg || 'none';
|
||||
console.log(`[FixDisplay] Removed vertical gradient from ${tag}${id ? '#' + id : ''}${className ? '.' + className.split(' ')[0] : ''}`);
|
||||
console.log(`[FixDisplay] Removed vertical gradient from ${tag}${id ? `#${ id}` : ''}${className ? `.${ className.split(' ')[0]}` : ''}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -430,7 +430,7 @@ export function fixDisplayIssues(): void {
|
|||
(isOnRightEdge && rect.width < 10 && rect.height > window.innerHeight * 0.3)) {
|
||||
htmlEl.style.setProperty('display', 'none', 'important');
|
||||
fixedCount++;
|
||||
console.log(`[FixDisplay] Hidden narrow vertical element: ${el.tagName}${el.id ? '#' + el.id : ''} (${rect.width}px x ${rect.height}px, right: ${rect.right}px)`);
|
||||
console.log(`[FixDisplay] Hidden narrow vertical element: ${el.tagName}${el.id ? `#${ el.id}` : ''} (${rect.width}px x ${rect.height}px, right: ${rect.right}px)`);
|
||||
}
|
||||
|
||||
// FIX: Remove vertical borders that could create lines
|
||||
|
|
@ -610,7 +610,7 @@ if (import.meta.env.DEV) {
|
|||
}
|
||||
// Force remove via inline style
|
||||
element.style.setProperty('background-image', element.style.backgroundImage?.replace(/linear-gradient\(90deg[^)]+\)/g, '').replace(/repeating-linear-gradient\(90deg[^)]+\)/g, '') || 'none', 'important');
|
||||
console.log(`[FixDisplay] Removed vertical gradient from newly added element: ${element.tagName}${element.id ? '#' + element.id : ''}`);
|
||||
console.log(`[FixDisplay] Removed vertical gradient from newly added element: ${element.tagName}${element.id ? `#${ element.id}` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -688,7 +688,7 @@ if (import.meta.env.DEV) {
|
|||
className: el.className?.toString().substring(0, 50) || '',
|
||||
reason: 'vertical-gradient-90deg'
|
||||
});
|
||||
console.log(`[FixDisplay] Removed vertical gradient from ${el.tagName}${el.id ? '#' + el.id : ''}${el.className ? '.' + el.className.toString().split(' ')[0] : ''} (z-index: ${zIndex})`);
|
||||
console.log(`[FixDisplay] Removed vertical gradient from ${el.tagName}${el.id ? `#${ el.id}` : ''}${el.className ? `.${ el.className.toString().split(' ')[0]}` : ''} (z-index: ${zIndex})`);
|
||||
}
|
||||
|
||||
// Check for suspicious elements that could create vertical lines
|
||||
|
|
@ -717,7 +717,7 @@ if (import.meta.env.DEV) {
|
|||
|
||||
if (beforeBg.includes('90deg') && (beforeBg.includes('linear-gradient') || beforeBg.includes('repeating-linear-gradient'))) {
|
||||
suspiciousElements.push({
|
||||
tag: el.tagName + '::before',
|
||||
tag: `${el.tagName }::before`,
|
||||
id: el.id || '',
|
||||
className: el.className?.toString().substring(0, 50) || '',
|
||||
reason: 'vertical-gradient-in-before'
|
||||
|
|
@ -725,7 +725,7 @@ if (import.meta.env.DEV) {
|
|||
}
|
||||
if (afterBg.includes('90deg') && (afterBg.includes('linear-gradient') || afterBg.includes('repeating-linear-gradient'))) {
|
||||
suspiciousElements.push({
|
||||
tag: el.tagName + '::after',
|
||||
tag: `${el.tagName }::after`,
|
||||
id: el.id || '',
|
||||
className: el.className?.toString().substring(0, 50) || '',
|
||||
reason: 'vertical-gradient-in-after'
|
||||
|
|
|
|||
Loading…
Reference in a new issue