feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-03-07 11:42:58 +01:00
parent 3d37ce4582
commit 473b3e26c7
181 changed files with 30617 additions and 7170 deletions

View File

@@ -0,0 +1,53 @@
'use client'
import * as React from 'react'
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import { cn } from '@/lib/utils'
function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
className={cn(
'relative flex size-8 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn('aspect-square size-full', className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
'bg-muted flex size-full items-center justify-center rounded-full',
className,
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
@@ -292,4 +294,4 @@ export const ProgressBadge = React.forwardRef<
})
ProgressBadge.displayName = "ProgressBadge"
export { Badge, badgeVariants, StatusBadge, CounterBadge, ProgressBadge }
export { Badge, badgeVariants }

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
@@ -47,11 +49,10 @@ interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, loading = false, ripple = true, children, disabled, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const [ripples, setRipples] = React.useState<Array<{ id: number; x: number; y: number; size: number }>>([])
const createRipple = (event: React.MouseEvent<HTMLButtonElement>) => {
if (!ripple || disabled || loading) return
if (!ripple || disabled || loading || asChild) return
const button = event.currentTarget
const rect = button.getBoundingClientRect()
@@ -68,14 +69,27 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
setRipples(prev => [...prev, newRipple])
// Remove ripple after animation
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== newRipple.id))
}, 600)
}
// When asChild is true, just render the Slot with merged props (no extra children)
if (asChild) {
const Comp = Slot
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
>
{children}
</Comp>
)
}
return (
<Comp
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
disabled={disabled || loading}
@@ -134,7 +148,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{variant === 'premium' && (
<span className="absolute inset-0 bg-gradient-to-r from-transparent via-white/10 to-transparent -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-1000 ease-out pointer-events-none" />
)}
</Comp>
</button>
)
}
)

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"

View File

@@ -0,0 +1,71 @@
"use client";
import { Globe } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useI18n, type Locale } from "@/lib/i18n";
const languages: { value: Locale; label: string; flag: string }[] = [
{ value: "en", label: "English", flag: "🇬🇧" },
{ value: "fr", label: "Français", flag: "🇫🇷" },
];
interface LanguageSwitcherProps {
variant?: "select" | "button";
}
export function LanguageSwitcher({ variant = "select" }: LanguageSwitcherProps) {
const { locale, setLocale } = useI18n();
if (variant === "button") {
const currentIndex = languages.findIndex((l) => l.value === locale);
const nextIndex = (currentIndex + 1) % languages.length;
const nextLang = languages[nextIndex];
return (
<Button
variant="ghost"
size="sm"
onClick={() => setLocale(nextLang.value)}
className="gap-1.5 text-muted-foreground hover:text-foreground"
>
<Globe className="h-4 w-4" />
<span className="text-sm">{nextLang.flag}</span>
</Button>
);
}
return (
<Select value={locale} onValueChange={(v) => setLocale(v as Locale)}>
<SelectTrigger className="w-[130px] h-9 bg-transparent border-border-dark hover:bg-surface-dark">
<SelectValue>
<span className="flex items-center gap-2">
<Globe className="h-4 w-4" />
{languages.find((l) => l.value === locale)?.flag}{" "}
{languages.find((l) => l.value === locale)?.label}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent className="bg-surface-dark border-border-dark">
{languages.map((lang) => (
<SelectItem
key={lang.value}
value={lang.value}
className="hover:bg-primary/10 focus:bg-primary/10"
>
<span className="flex items-center gap-2">
<span>{lang.flag}</span>
<span>{lang.label}</span>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
);
}

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { X, CheckCircle, AlertCircle, AlertTriangle, Info, Loader2 } from "lucide-react"
@@ -189,23 +191,25 @@ const Notification = React.forwardRef<HTMLDivElement, NotificationProps>(
Notification.displayName = "Notification"
// Notification Context
type NotificationItem = {
id: string
title?: string
description?: string
variant?: VariantProps<typeof notificationVariants>["variant"]
duration?: number
action?: React.ReactNode
icon?: React.ReactNode
closable?: boolean
autoClose?: boolean
}
interface NotificationContextType {
notifications: Array<{
id: string
title?: string
description?: string
variant?: VariantProps<typeof notificationVariants>["variant"]
duration?: number
action?: React.ReactNode
icon?: React.ReactNode
closable?: boolean
autoClose?: boolean
}>
notify: (notification: Omit<NotificationContextType["notifications"][0], "id">) => void
success: (notification: Omit<NotificationContextType["notifications"][0], "variant">) => void
error: (notification: Omit<NotificationContextType["notifications"][0], "variant">) => void
warning: (notification: Omit<NotificationContextType["notifications"][0], "variant">) => void
info: (notification: Omit<NotificationContextType["notifications"][0], "variant">) => void
notifications: NotificationItem[]
notify: (notification: Omit<NotificationItem, "id">) => void
success: (notification: Omit<NotificationItem, "id" | "variant">) => void
error: (notification: Omit<NotificationItem, "id" | "variant">) => void
warning: (notification: Omit<NotificationItem, "id" | "variant">) => void
info: (notification: Omit<NotificationItem, "id" | "variant">) => void
dismiss: (id: string) => void
dismissAll: () => void
}
@@ -213,10 +217,10 @@ interface NotificationContextType {
const NotificationContext = React.createContext<NotificationContextType | undefined>(undefined)
export function NotificationProvider({ children }: { children: React.ReactNode }) {
const [notifications, setNotifications] = React.useState<NotificationContextType["notifications"]>([])
const [notifications, setNotifications] = React.useState<NotificationItem[]>([])
const notify = React.useCallback(
(notification: Omit<NotificationContextType["notifications"][0], "id">) => {
(notification: Omit<NotificationItem, "id">) => {
const id = Math.random().toString(36).substr(2, 9)
setNotifications(prev => [...prev, { ...notification, id }])
},
@@ -224,25 +228,25 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
)
const success = React.useCallback(
(notification: Omit<NotificationContextType["notifications"][0], "variant">) =>
(notification: Omit<NotificationItem, "id" | "variant">) =>
notify({ ...notification, variant: "success" }),
[notify]
)
const error = React.useCallback(
(notification: Omit<NotificationContextType["notifications"][0], "variant">) =>
(notification: Omit<NotificationItem, "id" | "variant">) =>
notify({ ...notification, variant: "destructive" }),
[notify]
)
const warning = React.useCallback(
(notification: Omit<NotificationContextType["notifications"][0], "variant">) =>
(notification: Omit<NotificationItem, "id" | "variant">) =>
notify({ ...notification, variant: "warning" }),
[notify]
)
const info = React.useCallback(
(notification: Omit<NotificationContextType["notifications"][0], "variant">) =>
(notification: Omit<NotificationItem, "id" | "variant">) =>
notify({ ...notification, variant: "info" }),
[notify]
)

View File

@@ -8,8 +8,9 @@ import { cn } from "@/lib/utils"
function Progress({
className,
value,
animate = true,
...props
}: React.ComponentProps<typeof ProgressPrimitive.Root>) {
}: React.ComponentProps<typeof ProgressPrimitive.Root> & { animate?: boolean }) {
return (
<ProgressPrimitive.Root
data-slot="progress"
@@ -21,7 +22,7 @@ function Progress({
>
<ProgressPrimitive.Indicator
data-slot="progress-indicator"
className="bg-primary h-full w-full flex-1 transition-all"
className={cn("bg-primary h-full w-full flex-1", animate && "transition-all duration-500")}
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>

View File

@@ -0,0 +1,121 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom-0", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("border-b border-border", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t border-border bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b border-border/50 transition-colors hover:bg-secondary/50 data-[state=selected]:bg-secondary",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
@@ -116,7 +118,7 @@ const Toast = React.forwardRef<
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive"
)}
alt={typeof action === 'string' ? action : undefined}
altText={typeof action === 'string' ? action : 'Action'}
>
{action}
</ToastPrimitives.Action>
@@ -146,6 +148,21 @@ const ToastAction = React.forwardRef<
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
{...props}
/>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
@@ -185,7 +202,7 @@ export function useToast() {
description?: string
variant?: VariantProps<typeof toastVariants>["variant"]
duration?: number
action?: ToastActionElement
action?: React.ReactNode
icon?: React.ReactNode
}>>([])
@@ -267,7 +284,7 @@ export const ToastContainer = ({ children }: { children: React.ReactNode }) => {
// Individual Toast Component for use in ToastContainer
export const ToastItem = React.forwardRef<
HTMLDivElement,
HTMLLIElement,
{
toast: {
id: string
@@ -275,15 +292,15 @@ export const ToastItem = React.forwardRef<
description?: string
variant?: VariantProps<typeof toastVariants>["variant"]
duration?: number
action?: ToastActionElement
action?: React.ReactNode
icon?: React.ReactNode
}
onDismiss: (id: string) => void
}
>(({ toast, onDismiss, ...props }, ref) => {
return (
<li ref={ref}>
<Toast
ref={ref}
variant={toast.variant}
title={toast.title}
description={toast.description}
@@ -297,6 +314,7 @@ export const ToastItem = React.forwardRef<
}}
{...props}
/>
</li>
)
})
ToastItem.displayName = "ToastItem"