diff --git a/client/biome.json b/client/biome.json index 1f4ebc2..beed1ef 100644 --- a/client/biome.json +++ b/client/biome.json @@ -11,7 +11,7 @@ "noUnusedVariables": "warn", "useExhaustiveDependencies": "off" }, - "complexity": { "noStaticOnlyClass": "off" } + "complexity": { "noStaticOnlyClass": "off", "noForEach": "off" } } }, "formatter": { diff --git a/client/src/components/datagrids/links/links-columns.tsx b/client/src/components/datagrids/links/links-columns.tsx index c8c13e5..8f4589c 100644 --- a/client/src/components/datagrids/links/links-columns.tsx +++ b/client/src/components/datagrids/links/links-columns.tsx @@ -3,12 +3,12 @@ import type { ColumnDef } from '@tanstack/react-table'; import type { z } from 'zod'; -import { Checkbox } from '../../ui/checkbox'; -import { DataTableColumnHeader } from './links-table-column-header'; -import { DataTableRowActions } from './linkx-table-row-actions'; import { copyTextToClipboard } from '@/actions/copyTextToClipboard'; import { useToast } from '@/components/ui/use-toast'; import { Clipboard } from 'lucide-react'; +import { Checkbox } from '../../ui/checkbox'; +import { DataTableColumnHeader } from './links-table-column-header'; +import { DataTableRowActions } from './linkx-table-row-actions'; export const linksColumns: ColumnDef>[] = [ { @@ -44,7 +44,14 @@ export const linksColumns: ColumnDef>[] = [ const { toast } = useToast(); const shorter_url = `${import.meta.env.VITE_API_URL}/api/${row.original.shorter_name}`; return ( -
+
+ { + copyTextToClipboard(shorter_url); + toast({ title: 'Copied to clipboard' }); + }} + /> >[] = [ {row.original.shorter_name} - { - copyTextToClipboard(shorter_url); - toast({ title: 'Copied to clipboard' }); - }} - />
); }, @@ -72,7 +73,9 @@ export const linksColumns: ColumnDef>[] = [ return (
- {row.original.url} + + {row.original.url} +
); diff --git a/client/src/components/ui/toast.tsx b/client/src/components/ui/toast.tsx index a822477..aa3069d 100644 --- a/client/src/components/ui/toast.tsx +++ b/client/src/components/ui/toast.tsx @@ -1,11 +1,11 @@ -import * as React from "react" -import * as ToastPrimitives from "@radix-ui/react-toast" -import { cva, type VariantProps } from "class-variance-authority" -import { X } from "lucide-react" +import * as ToastPrimitives from '@radix-ui/react-toast'; +import { type VariantProps, cva } from 'class-variance-authority'; +import { X } from 'lucide-react'; +import * as React from 'react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -const ToastProvider = ToastPrimitives.Provider +const ToastProvider = ToastPrimitives.Provider; const ToastViewport = React.forwardRef< React.ElementRef, @@ -14,29 +14,29 @@ const ToastViewport = React.forwardRef< -)) -ToastViewport.displayName = ToastPrimitives.Viewport.displayName +)); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; const toastVariants = cva( - "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", + 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', { variants: { variant: { - default: "border bg-background text-foreground", + default: 'border bg-background text-foreground', destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", + 'destructive group border-destructive bg-destructive text-destructive-foreground', }, }, defaultVariants: { - variant: "default", + variant: 'default', }, - } -) + }, +); const Toast = React.forwardRef< React.ElementRef, @@ -49,9 +49,9 @@ const Toast = React.forwardRef< className={cn(toastVariants({ variant }), className)} {...props} /> - ) -}) -Toast.displayName = ToastPrimitives.Root.displayName + ); +}); +Toast.displayName = ToastPrimitives.Root.displayName; const ToastAction = React.forwardRef< React.ElementRef, @@ -60,13 +60,13 @@ const ToastAction = React.forwardRef< -)) -ToastAction.displayName = ToastPrimitives.Action.displayName +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; const ToastClose = React.forwardRef< React.ElementRef, @@ -75,16 +75,16 @@ const ToastClose = React.forwardRef< -)) -ToastClose.displayName = ToastPrimitives.Close.displayName +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; const ToastTitle = React.forwardRef< React.ElementRef, @@ -92,11 +92,11 @@ const ToastTitle = React.forwardRef< >(({ className, ...props }, ref) => ( -)) -ToastTitle.displayName = ToastPrimitives.Title.displayName +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; const ToastDescription = React.forwardRef< React.ElementRef, @@ -104,15 +104,15 @@ const ToastDescription = React.forwardRef< >(({ className, ...props }, ref) => ( -)) -ToastDescription.displayName = ToastPrimitives.Description.displayName +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; -type ToastProps = React.ComponentPropsWithoutRef +type ToastProps = React.ComponentPropsWithoutRef; -type ToastActionElement = React.ReactElement +type ToastActionElement = React.ReactElement; export { type ToastProps, @@ -124,4 +124,4 @@ export { ToastDescription, ToastClose, ToastAction, -} +}; diff --git a/client/src/components/ui/toaster.tsx b/client/src/components/ui/toaster.tsx index a2209ba..254156d 100644 --- a/client/src/components/ui/toaster.tsx +++ b/client/src/components/ui/toaster.tsx @@ -5,29 +5,25 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@/components/ui/toast" -import { useToast } from "@/components/ui/use-toast" +} from '@/components/ui/toast'; +import { useToast } from '@/components/ui/use-toast'; export function Toaster() { - const { toasts } = useToast() + const { toasts } = useToast(); return ( - {toasts.map(function ({ id, title, description, action, ...props }) { - return ( - -
- {title && {title}} - {description && ( - {description} - )} -
- {action} - -
- ) - })} + {toasts.map(({ id, title, description, action, ...props }) => ( + +
+ {title && {title}} + {description && {description}} +
+ {action} + +
+ ))}
- ) + ); } diff --git a/client/src/components/ui/use-toast.ts b/client/src/components/ui/use-toast.ts index 1671307..99d1e55 100644 --- a/client/src/components/ui/use-toast.ts +++ b/client/src/components/ui/use-toast.ts @@ -1,104 +1,101 @@ // Inspired by react-hot-toast library -import * as React from "react" +import * as React from 'react'; -import type { - ToastActionElement, - ToastProps, -} from "@/components/ui/toast" +import type { ToastActionElement, ToastProps } from '@/components/ui/toast'; -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; const actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const; -let count = 0 +let count = 0; function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() + count = (count + 1) % Number.MAX_SAFE_INTEGER; + return count.toString(); } -type ActionType = typeof actionTypes +type ActionType = typeof actionTypes; type Action = | { - type: ActionType["ADD_TOAST"] - toast: ToasterToast + type: ActionType['ADD_TOAST']; + toast: ToasterToast; } | { - type: ActionType["UPDATE_TOAST"] - toast: Partial + type: ActionType['UPDATE_TOAST']; + toast: Partial; } | { - type: ActionType["DISMISS_TOAST"] - toastId?: ToasterToast["id"] + type: ActionType['DISMISS_TOAST']; + toastId?: ToasterToast['id']; } | { - type: ActionType["REMOVE_TOAST"] - toastId?: ToasterToast["id"] - } + type: ActionType['REMOVE_TOAST']; + toastId?: ToasterToast['id']; + }; interface State { - toasts: ToasterToast[] + toasts: ToasterToast[]; } -const toastTimeouts = new Map>() +const toastTimeouts = new Map>(); const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { - return + return; } const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) + toastTimeouts.delete(toastId); dispatch({ - type: "REMOVE_TOAST", + type: 'REMOVE_TOAST', toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) + }); + }, TOAST_REMOVE_DELAY); - toastTimeouts.set(toastId, timeout) -} + toastTimeouts.set(toastId, timeout); +}; export const reducer = (state: State, action: Action): State => { switch (action.type) { - case "ADD_TOAST": + case 'ADD_TOAST': return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } + }; - case "UPDATE_TOAST": + case 'UPDATE_TOAST': return { ...state, toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t + t.id === action.toast.id ? { ...t, ...action.toast } : t, ), - } + }; - case "DISMISS_TOAST": { - const { toastId } = action + case 'DISMISS_TOAST': { + const { toastId } = action; // ! Side effects ! - This could be extracted into a dismissToast() action, // but I'll keep it here for simplicity if (toastId) { - addToRemoveQueue(toastId) + addToRemoveQueue(toastId); } else { state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) + addToRemoveQueue(toast.id); + }); } return { @@ -109,84 +106,84 @@ export const reducer = (state: State, action: Action): State => { ...t, open: false, } - : t + : t, ), - } + }; } - case "REMOVE_TOAST": + case 'REMOVE_TOAST': if (action.toastId === undefined) { return { ...state, toasts: [], - } + }; } return { ...state, toasts: state.toasts.filter((t) => t.id !== action.toastId), - } + }; } -} +}; -const listeners: Array<(state: State) => void> = [] +const listeners: Array<(state: State) => void> = []; -let memoryState: State = { toasts: [] } +let memoryState: State = { toasts: [] }; function dispatch(action: Action) { - memoryState = reducer(memoryState, action) + memoryState = reducer(memoryState, action); listeners.forEach((listener) => { - listener(memoryState) - }) + listener(memoryState); + }); } -type Toast = Omit +type Toast = Omit; function toast({ ...props }: Toast) { - const id = genId() + const id = genId(); const update = (props: ToasterToast) => dispatch({ - type: "UPDATE_TOAST", + type: 'UPDATE_TOAST', toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + }); + const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id }); dispatch({ - type: "ADD_TOAST", + type: 'ADD_TOAST', toast: { ...props, id, open: true, onOpenChange: (open) => { - if (!open) dismiss() + if (!open) dismiss(); }, }, - }) + }); return { id: id, dismiss, update, - } + }; } function useToast() { - const [state, setState] = React.useState(memoryState) + const [state, setState] = React.useState(memoryState); React.useEffect(() => { - listeners.push(setState) + listeners.push(setState); return () => { - const index = listeners.indexOf(setState) + const index = listeners.indexOf(setState); if (index > -1) { - listeners.splice(index, 1) + listeners.splice(index, 1); } - } - }, [state]) + }; + }, [state]); return { ...state, toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - } + dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), + }; } -export { useToast, toast } +export { useToast, toast };