Cambios App web

This commit is contained in:
David Arranz 2026-04-12 12:07:15 +02:00
parent 3430ae3cb9
commit 515b09e2f7
8 changed files with 946 additions and 32 deletions

View File

@ -15,38 +15,43 @@
"format": "biome format --write"
},
"devDependencies": {
"@biomejs/biome": "^2.3.1",
"@tanstack/react-query-devtools": "^5.74.11",
"@types/dinero.js": "^1.9.4",
"@types/node": "^22.15.12",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@vitejs/plugin-react": "^4.4.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"typescript": "~5.8.3",
"vite": "^6.3.5"
"@biomejs/biome": "^2.4.11",
"@tanstack/react-query-devtools": "^5.98.0",
"@tailwindcss/postcss": "^4.1.5",
"@types/dinero.js": "^2.0.0",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"date-fns": "^4.1.0",
"typescript": "~6.0.2",
"vite": "^8.0.8"
},
"dependencies": {
"@erp/auth": "workspace:*",
"@erp/core": "workspace:*",
"@erp/customer-invoices": "workspace:*",
"@erp/customers": "workspace:*",
"@fontsource-variable/geist": "^5.2.8",
"@fontsource-variable/geist-mono": "^5.2.7",
"@repo/i18next": "workspace:*",
"@repo/rdx-ui": "workspace:*",
"@repo/shadcn-ui": "workspace:*",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.90.6",
"axios": "^1.14.0",
"dinero.js": "^1.9.1",
"react-error-boundary": "^6.0.0",
"@tailwindcss/vite": "^4.2.2",
"@tanstack/react-query": "^5.98.0",
"axios": "^1.15.0",
"dinero.js": "1.9.1",
"lucide-react": "^1.8.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-error-boundary": "^6.1.1",
"react-hook-form": "^7.72.1",
"react-i18next": "^15.0.1",
"react-router-dom": "^6.26.0",
"react-i18next": "^17.0.2",
"react-router-dom": "^7.14.0",
"react-secure-storage": "^1.3.2",
"sequelize": "^6.37.8",
"tailwindcss": "^4.1.10",
"tw-animate-css": "^1.2.9",
"tailwindcss": "^4.2.2",
"tw-animate-css": "^1.4.0",
"vite-plugin-html": "^3.2.2"
}
}

View File

View File

@ -1,6 +1,7 @@
import { AuthProvider, createAuthService } from "@erp/auth/client";
import { createAxiosDataSource, createAxiosInstance } from "@erp/core/client";
import { DataSourceProvider } from "@erp/core/hooks";
import { i18n } from "@repo/i18next";
import { LoadingOverlay } from "@repo/rdx-ui/components";
import { Toaster, TooltipProvider } from "@repo/shadcn-ui/components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@ -13,10 +14,6 @@ import { RouterProvider } from "react-router-dom";
import { clearAccessToken, getAccessToken, setAccessToken } from "./lib";
import { getAppRouter } from "./routes";
import "./app.css";
import { i18n } from "@repo/i18next";
export const App = () => {
DineroFactory.globalLocale = "es-ES";
@ -56,7 +53,7 @@ export const App = () => {
authService: createAuthService(),
}}
>
<TooltipProvider delayDuration={0}>
<TooltipProvider>
{/* Fallback Route */}
<Suspense fallback={<LoadingOverlay />}>
<RouterProvider router={appRouter} />

View File

@ -1,10 +1,12 @@
import secureLocalStorage from "react-secure-storage";
import secureLocalStorageImport from "react-secure-storage";
/**
* Servicio para manejar la obtención del token JWT desde el almacenamiento local.
* Este archivo puede evolucionar a un AuthService más completo en el futuro.
*/
const secureLocalStorage = (secureLocalStorageImport as any)?.default ?? secureLocalStorageImport;
/**
* Clave utilizada en el almacenamiento local para el token JWT.
*/
@ -17,7 +19,7 @@ const TOKEN_STORAGE_KEY = "factuges.auth";
* @returns El token como string, o null si no está disponible.
*/
export const getAccessToken = (): string | null => {
const authInfo = secureLocalStorage.getItem(TOKEN_STORAGE_KEY) as { token?: string } | null;
const authInfo = secureLocalStorage?.getItem?.(TOKEN_STORAGE_KEY) as { token?: string } | null;
return typeof authInfo?.token === "string" ? authInfo.token : null;
};
@ -27,14 +29,12 @@ export const getAccessToken = (): string | null => {
* @params El token como string.
*/
export const setAccessToken = (token: string): void => {
secureLocalStorage.setItem(TOKEN_STORAGE_KEY, token);
secureLocalStorage?.setItem?.(TOKEN_STORAGE_KEY, token);
};
setAccessToken;
/**
* Limpia el token JWT del almacenamiento local.
*/
export const clearAccessToken = (): void => {
secureLocalStorage.removeItem(TOKEN_STORAGE_KEY);
secureLocalStorage?.removeItem?.(TOKEN_STORAGE_KEY);
};

View File

@ -0,0 +1,295 @@
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
Badge,
Button,
ButtonGroup,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Checkbox,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Field,
FieldGroup,
InputGroup,
InputGroupAddon,
InputGroupInput,
InputGroupText,
Item,
ItemActions,
ItemContent,
ItemDescription,
ItemTitle,
RadioGroup,
RadioGroupItem,
Slider,
Switch,
Textarea,
} from "@repo/shadcn-ui/components";
import {
ArrowLeftIcon,
ArrowRightIcon,
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
ChevronUpIcon,
CircleAlertIcon,
CopyIcon,
Loader2Icon,
MinusIcon,
MoreHorizontalIcon,
PlusIcon,
SearchIcon,
SettingsIcon,
ShareIcon,
ShoppingBagIcon,
TrashIcon,
} from "lucide-react";
import * as React from "react";
export default function ShadcnShowcasePage() {
const [sliderValue, setSliderValue] = React.useState<number[]>([500]);
const handleSliderValueChange = React.useCallback((value: number[]) => {
setSliderValue(value);
}, []);
return (
<div className="flex min-h-screen w-full flex-col items-center justify-center bg-muted p-4 sm:p-6 lg:p-12 dark:bg-background">
<div className="grid max-w-3xl gap-4 sm:grid-cols-2">
<div className="flex flex-col gap-4">
<Card>
<CardHeader>
<CardTitle>Style Overview</CardTitle>
<CardDescription className="line-clamp-2">
Designers love packing quirky glyphs into test phrases. This is a preview of the
typography styles.
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-6 gap-3">
{[
"--background",
"--foreground",
"--primary",
"--secondary",
"--muted",
"--accent",
"--border",
"--chart-1",
"--chart-2",
"--chart-3",
"--chart-4",
"--chart-5",
].map((variant) => (
<div className="flex flex-col flex-wrap items-center gap-2" key={variant}>
<div
className="relative aspect-square w-full rounded-lg bg-(--color) after:absolute after:inset-0 after:rounded-lg after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten"
style={
{
"--color": `var(${variant})`,
} as React.CSSProperties
}
/>
<div className="hidden max-w-14 truncate font-mono text-[0.60rem] md:block">
{variant}
</div>
</div>
))}
</div>
</CardContent>
</Card>
<Card>
<CardContent>
<div className="grid grid-cols-8 place-items-center gap-4">
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<CopyIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<CircleAlertIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<TrashIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ShareIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ShoppingBagIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<MoreHorizontalIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<Loader2Icon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<PlusIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<MinusIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ArrowLeftIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ArrowRightIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<CheckIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ChevronDownIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<ChevronRightIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<SearchIcon />
</Card>
<Card className="flex size-8 items-center justify-center rounded-md p-0 ring ring-border *:[svg]:size-4">
<SettingsIcon />
</Card>
</div>
</CardContent>
</Card>
</div>
<div className="flex flex-col gap-4">
<Card className="w-full">
<CardContent className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
<div className="flex flex-wrap gap-2">
<Button>Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
</div>
<Item variant="outline">
<ItemContent>
<ItemTitle>Two-factor authentication</ItemTitle>
<ItemDescription className="text-pretty xl:hidden 2xl:block">
Verify via email or phone number.
</ItemDescription>
</ItemContent>
<ItemActions className="hidden md:flex">
<Button size="sm" variant="secondary">
Enable
</Button>
</ItemActions>
</Item>
</div>
<Slider
aria-label="Slider"
className="flex-1"
max={1000}
min={0}
onValueChange={handleSliderValueChange}
step={10}
value={sliderValue}
/>
<FieldGroup>
<Field>
<InputGroup>
<InputGroupInput placeholder="Name" />
<InputGroupAddon align="inline-end">
<InputGroupText>
<SearchIcon />
</InputGroupText>
</InputGroupAddon>
</InputGroup>
</Field>
<Field className="flex-1">
<Textarea className="resize-none" placeholder="Message" />
</Field>
</FieldGroup>
<div className="flex items-center gap-2">
<div className="flex gap-2">
<Badge>Badge</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
<RadioGroup className="ml-auto flex w-fit gap-3" defaultValue="apple">
<RadioGroupItem value="apple" />
<RadioGroupItem value="banana" />
</RadioGroup>
<div className="flex gap-3">
<Checkbox defaultChecked />
<Checkbox />
</div>
</div>
<div className="flex items-center gap-4">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="outline">
<span className="hidden md:block">Alert Dialog</span>
<span className="block md:hidden">Dialog</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent size="sm">
<AlertDialogHeader>
<AlertDialogTitle>Allow accessory to connect?</AlertDialogTitle>
<AlertDialogDescription>
Do you want to allow the USB accessory to connect to this device and your
data?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Don&apos;t allow</AlertDialogCancel>
<AlertDialogAction>Allow</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ButtonGroup>
<Button variant="outline">Button Group</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="icon" variant="outline">
<ChevronUpIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-fit" side="top">
<DropdownMenuGroup>
<DropdownMenuLabel>Quick Actions</DropdownMenuLabel>
<DropdownMenuItem>Mute Conversation</DropdownMenuItem>
<DropdownMenuItem>Mark as Read</DropdownMenuItem>
<DropdownMenuItem>Block User</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuLabel>Conversation</DropdownMenuLabel>
<DropdownMenuItem>Share Conversation</DropdownMenuItem>
<DropdownMenuItem>Copy Conversation</DropdownMenuItem>
<DropdownMenuItem>Report Conversation</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem variant="destructive">
Delete Conversation
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
<Switch className="ml-auto" defaultChecked />
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,608 @@
// src/assets/tailwindcss-page.tsx
const textSizes = [
"text-xs",
"text-sm",
"text-base",
"text-lg",
"text-xl",
"text-2xl",
"text-3xl",
"text-4xl",
"text-5xl",
] as const;
const fontFamilies = ["font-sans", "font-serif", "font-mono"] as const;
const fontWeights = [
"font-thin",
"font-extralight",
"font-light",
"font-normal",
"font-medium",
"font-semibold",
"font-bold",
"font-extrabold",
"font-black",
] as const;
const tracking = [
"tracking-tighter",
"tracking-tight",
"tracking-normal",
"tracking-wide",
"tracking-wider",
"tracking-widest",
] as const;
const leading = [
"leading-none",
"leading-tight",
"leading-snug",
"leading-normal",
"leading-relaxed",
"leading-loose",
] as const;
const palette = [
"slate",
"gray",
"zinc",
"neutral",
"stone",
"red",
"orange",
"amber",
"yellow",
"lime",
"green",
"emerald",
"teal",
"cyan",
"sky",
"blue",
"indigo",
"violet",
"purple",
"fuchsia",
"pink",
"rose",
] as const;
const shades = [
"50",
"100",
"200",
"300",
"400",
"500",
"600",
"700",
"800",
"900",
"950",
] as const;
const colorClasses = {
slate: {
"50": "bg-slate-50 text-black",
"100": "bg-slate-100 text-black",
"200": "bg-slate-200 text-black",
"300": "bg-slate-300 text-black",
"400": "bg-slate-400 text-black",
"500": "bg-slate-500 text-white",
"600": "bg-slate-600 text-white",
"700": "bg-slate-700 text-white",
"800": "bg-slate-800 text-white",
"900": "bg-slate-900 text-white",
"950": "bg-slate-950 text-white",
},
gray: {
"50": "bg-gray-50 text-black",
"100": "bg-gray-100 text-black",
"200": "bg-gray-200 text-black",
"300": "bg-gray-300 text-black",
"400": "bg-gray-400 text-black",
"500": "bg-gray-500 text-white",
"600": "bg-gray-600 text-white",
"700": "bg-gray-700 text-white",
"800": "bg-gray-800 text-white",
"900": "bg-gray-900 text-white",
"950": "bg-gray-950 text-white",
},
zinc: {
"50": "bg-zinc-50 text-black",
"100": "bg-zinc-100 text-black",
"200": "bg-zinc-200 text-black",
"300": "bg-zinc-300 text-black",
"400": "bg-zinc-400 text-black",
"500": "bg-zinc-500 text-white",
"600": "bg-zinc-600 text-white",
"700": "bg-zinc-700 text-white",
"800": "bg-zinc-800 text-white",
"900": "bg-zinc-900 text-white",
"950": "bg-zinc-950 text-white",
},
neutral: {
"50": "bg-neutral-50 text-black",
"100": "bg-neutral-100 text-black",
"200": "bg-neutral-200 text-black",
"300": "bg-neutral-300 text-black",
"400": "bg-neutral-400 text-black",
"500": "bg-neutral-500 text-white",
"600": "bg-neutral-600 text-white",
"700": "bg-neutral-700 text-white",
"800": "bg-neutral-800 text-white",
"900": "bg-neutral-900 text-white",
"950": "bg-neutral-950 text-white",
},
stone: {
"50": "bg-stone-50 text-black",
"100": "bg-stone-100 text-black",
"200": "bg-stone-200 text-black",
"300": "bg-stone-300 text-black",
"400": "bg-stone-400 text-black",
"500": "bg-stone-500 text-white",
"600": "bg-stone-600 text-white",
"700": "bg-stone-700 text-white",
"800": "bg-stone-800 text-white",
"900": "bg-stone-900 text-white",
"950": "bg-stone-950 text-white",
},
red: {
"50": "bg-red-50 text-black",
"100": "bg-red-100 text-black",
"200": "bg-red-200 text-black",
"300": "bg-red-300 text-black",
"400": "bg-red-400 text-black",
"500": "bg-red-500 text-white",
"600": "bg-red-600 text-white",
"700": "bg-red-700 text-white",
"800": "bg-red-800 text-white",
"900": "bg-red-900 text-white",
"950": "bg-red-950 text-white",
},
orange: {
"50": "bg-orange-50 text-black",
"100": "bg-orange-100 text-black",
"200": "bg-orange-200 text-black",
"300": "bg-orange-300 text-black",
"400": "bg-orange-400 text-black",
"500": "bg-orange-500 text-white",
"600": "bg-orange-600 text-white",
"700": "bg-orange-700 text-white",
"800": "bg-orange-800 text-white",
"900": "bg-orange-900 text-white",
"950": "bg-orange-950 text-white",
},
amber: {
"50": "bg-amber-50 text-black",
"100": "bg-amber-100 text-black",
"200": "bg-amber-200 text-black",
"300": "bg-amber-300 text-black",
"400": "bg-amber-400 text-black",
"500": "bg-amber-500 text-black",
"600": "bg-amber-600 text-white",
"700": "bg-amber-700 text-white",
"800": "bg-amber-800 text-white",
"900": "bg-amber-900 text-white",
"950": "bg-amber-950 text-white",
},
yellow: {
"50": "bg-yellow-50 text-black",
"100": "bg-yellow-100 text-black",
"200": "bg-yellow-200 text-black",
"300": "bg-yellow-300 text-black",
"400": "bg-yellow-400 text-black",
"500": "bg-yellow-500 text-black",
"600": "bg-yellow-600 text-black",
"700": "bg-yellow-700 text-white",
"800": "bg-yellow-800 text-white",
"900": "bg-yellow-900 text-white",
"950": "bg-yellow-950 text-white",
},
lime: {
"50": "bg-lime-50 text-black",
"100": "bg-lime-100 text-black",
"200": "bg-lime-200 text-black",
"300": "bg-lime-300 text-black",
"400": "bg-lime-400 text-black",
"500": "bg-lime-500 text-black",
"600": "bg-lime-600 text-black",
"700": "bg-lime-700 text-white",
"800": "bg-lime-800 text-white",
"900": "bg-lime-900 text-white",
"950": "bg-lime-950 text-white",
},
green: {
"50": "bg-green-50 text-black",
"100": "bg-green-100 text-black",
"200": "bg-green-200 text-black",
"300": "bg-green-300 text-black",
"400": "bg-green-400 text-black",
"500": "bg-green-500 text-white",
"600": "bg-green-600 text-white",
"700": "bg-green-700 text-white",
"800": "bg-green-800 text-white",
"900": "bg-green-900 text-white",
"950": "bg-green-950 text-white",
},
emerald: {
"50": "bg-emerald-50 text-black",
"100": "bg-emerald-100 text-black",
"200": "bg-emerald-200 text-black",
"300": "bg-emerald-300 text-black",
"400": "bg-emerald-400 text-black",
"500": "bg-emerald-500 text-white",
"600": "bg-emerald-600 text-white",
"700": "bg-emerald-700 text-white",
"800": "bg-emerald-800 text-white",
"900": "bg-emerald-900 text-white",
"950": "bg-emerald-950 text-white",
},
teal: {
"50": "bg-teal-50 text-black",
"100": "bg-teal-100 text-black",
"200": "bg-teal-200 text-black",
"300": "bg-teal-300 text-black",
"400": "bg-teal-400 text-black",
"500": "bg-teal-500 text-white",
"600": "bg-teal-600 text-white",
"700": "bg-teal-700 text-white",
"800": "bg-teal-800 text-white",
"900": "bg-teal-900 text-white",
"950": "bg-teal-950 text-white",
},
cyan: {
"50": "bg-cyan-50 text-black",
"100": "bg-cyan-100 text-black",
"200": "bg-cyan-200 text-black",
"300": "bg-cyan-300 text-black",
"400": "bg-cyan-400 text-black",
"500": "bg-cyan-500 text-black",
"600": "bg-cyan-600 text-black",
"700": "bg-cyan-700 text-white",
"800": "bg-cyan-800 text-white",
"900": "bg-cyan-900 text-white",
"950": "bg-cyan-950 text-white",
},
sky: {
"50": "bg-sky-50 text-black",
"100": "bg-sky-100 text-black",
"200": "bg-sky-200 text-black",
"300": "bg-sky-300 text-black",
"400": "bg-sky-400 text-black",
"500": "bg-sky-500 text-white",
"600": "bg-sky-600 text-white",
"700": "bg-sky-700 text-white",
"800": "bg-sky-800 text-white",
"900": "bg-sky-900 text-white",
"950": "bg-sky-950 text-white",
},
blue: {
"50": "bg-blue-50 text-black",
"100": "bg-blue-100 text-black",
"200": "bg-blue-200 text-black",
"300": "bg-blue-300 text-black",
"400": "bg-blue-400 text-black",
"500": "bg-blue-500 text-white",
"600": "bg-blue-600 text-white",
"700": "bg-blue-700 text-white",
"800": "bg-blue-800 text-white",
"900": "bg-blue-900 text-white",
"950": "bg-blue-950 text-white",
},
indigo: {
"50": "bg-indigo-50 text-black",
"100": "bg-indigo-100 text-black",
"200": "bg-indigo-200 text-black",
"300": "bg-indigo-300 text-black",
"400": "bg-indigo-400 text-black",
"500": "bg-indigo-500 text-white",
"600": "bg-indigo-600 text-white",
"700": "bg-indigo-700 text-white",
"800": "bg-indigo-800 text-white",
"900": "bg-indigo-900 text-white",
"950": "bg-indigo-950 text-white",
},
violet: {
"50": "bg-violet-50 text-black",
"100": "bg-violet-100 text-black",
"200": "bg-violet-200 text-black",
"300": "bg-violet-300 text-black",
"400": "bg-violet-400 text-black",
"500": "bg-violet-500 text-white",
"600": "bg-violet-600 text-white",
"700": "bg-violet-700 text-white",
"800": "bg-violet-800 text-white",
"900": "bg-violet-900 text-white",
"950": "bg-violet-950 text-white",
},
purple: {
"50": "bg-purple-50 text-black",
"100": "bg-purple-100 text-black",
"200": "bg-purple-200 text-black",
"300": "bg-purple-300 text-black",
"400": "bg-purple-400 text-black",
"500": "bg-purple-500 text-white",
"600": "bg-purple-600 text-white",
"700": "bg-purple-700 text-white",
"800": "bg-purple-800 text-white",
"900": "bg-purple-900 text-white",
"950": "bg-purple-950 text-white",
},
fuchsia: {
"50": "bg-fuchsia-50 text-black",
"100": "bg-fuchsia-100 text-black",
"200": "bg-fuchsia-200 text-black",
"300": "bg-fuchsia-300 text-black",
"400": "bg-fuchsia-400 text-black",
"500": "bg-fuchsia-500 text-white",
"600": "bg-fuchsia-600 text-white",
"700": "bg-fuchsia-700 text-white",
"800": "bg-fuchsia-800 text-white",
"900": "bg-fuchsia-900 text-white",
"950": "bg-fuchsia-950 text-white",
},
pink: {
"50": "bg-pink-50 text-black",
"100": "bg-pink-100 text-black",
"200": "bg-pink-200 text-black",
"300": "bg-pink-300 text-black",
"400": "bg-pink-400 text-black",
"500": "bg-pink-500 text-white",
"600": "bg-pink-600 text-white",
"700": "bg-pink-700 text-white",
"800": "bg-pink-800 text-white",
"900": "bg-pink-900 text-white",
"950": "bg-pink-950 text-white",
},
rose: {
"50": "bg-rose-50 text-black",
"100": "bg-rose-100 text-black",
"200": "bg-rose-200 text-black",
"300": "bg-rose-300 text-black",
"400": "bg-rose-400 text-black",
"500": "bg-rose-500 text-white",
"600": "bg-rose-600 text-white",
"700": "bg-rose-700 text-white",
"800": "bg-rose-800 text-white",
"900": "bg-rose-900 text-white",
"950": "bg-rose-950 text-white",
},
} as const;
export default function TailwindV4ShowcasePage() {
return (
<div className="min-h-screen bg-background text-foreground">
<div className="container mx-auto flex max-w-7xl flex-col gap-10 px-6 py-10">
<header className="space-y-3">
<p className="text-sm uppercase tracking-widest text-muted-foreground">Tailwind CSS v4</p>
<h1 className="text-4xl font-bold tracking-tight">Typography + Colors Showcase</h1>
<p className="max-w-3xl text-muted-foreground">
Página de prueba para validar escala tipográfica, pesos, tracking, line-height, tokens
semánticos, paleta base y estados de color.
</p>
</header>
<div>
<div>
<div>Tokens semánticos del tema</div>
<div>
Estos bloques validan que los tokens del sistema y el theme estén correctamente
conectados.
</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div className="rounded-xl border bg-background p-4">
<p className="font-medium">bg-background</p>
<p className="text-sm text-muted-foreground">foreground / muted-foreground</p>
</div>
<div className="rounded-xl border bg-card p-4 text-card-foreground">
<p className="font-medium">bg-card</p>
<p className="text-sm opacity-70">text-card-foreground</p>
</div>
<div className="rounded-xl bg-primary p-4 text-primary-foreground">
<p className="font-medium">bg-primary</p>
<p className="text-sm opacity-80">text-primary-foreground</p>
</div>
<div className="rounded-xl bg-secondary p-4 text-secondary-foreground">
<p className="font-medium">bg-secondary</p>
<p className="text-sm opacity-80">text-secondary-foreground</p>
</div>
<div className="rounded-xl bg-muted p-4 text-muted-foreground">
<p className="font-medium text-foreground">bg-muted</p>
<p className="text-sm">text-muted-foreground</p>
</div>
<div className="rounded-xl bg-accent p-4 text-accent-foreground">
<p className="font-medium">bg-accent</p>
<p className="text-sm opacity-80">text-accent-foreground</p>
</div>
<div className="rounded-xl bg-destructive p-4 text-destructive-foreground">
<p className="font-medium">bg-destructive</p>
<p className="text-sm opacity-80">text-destructive-foreground</p>
</div>
<div className="rounded-xl border border-border p-4">
<p className="font-medium">border-border</p>
<p className="text-sm text-muted-foreground">ring, input, radius, etc.</p>
</div>
</div>
</div>
<div>
<div>
<div>Escala tipográfica</div>
<div>Tamaños de texto del sistema.</div>
</div>
<div className="space-y-4">
{textSizes.map((cls) => (
<div className="rounded-lg border p-4" key={cls}>
<p className={`${cls} font-medium`}>
{cls} The quick brown fox jumps over the lazy dog.
</p>
</div>
))}
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<div>
<div>
<div>Familias tipográficas</div>
</div>
<div className="space-y-4">
{fontFamilies.map((cls) => (
<div className="rounded-lg border p-4" key={cls}>
<p className={`${cls} text-xl`}>{cls} Sphinx of black quartz, judge my vow.</p>
</div>
))}
</div>
</div>
<div>
<div>
<div>Pesos tipográficos</div>
</div>
<div className="space-y-4">
{fontWeights.map((cls) => (
<div className="rounded-lg border p-4" key={cls}>
<p className={`${cls} text-lg`}>
{cls} Pack my box with five dozen liquor jugs.
</p>
</div>
))}
</div>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<div>
<div>
<div>Tracking</div>
</div>
<div className="space-y-4">
{tracking.map((cls) => (
<div className="rounded-lg border p-4" key={cls}>
<p className={`${cls} text-lg uppercase`}>{cls} Tailwind v4</p>
</div>
))}
</div>
</div>
<div>
<div>
<div>Leading</div>
</div>
<div className="space-y-4">
{leading.map((cls) => (
<div className="rounded-lg border p-4" key={cls}>
<p className={`${cls} max-w-xl`}>
<span className="font-medium">{cls}</span> Lorem ipsum dolor sit amet,
consectetur adipisicing elit. Provident sapiente similique fugit quos tempora
commodi animi minima porro quas.
</p>
</div>
))}
</div>
</div>
</div>
<div>
<div>
<div>Decoración y estilos de texto</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div className="rounded-lg border p-4">
<p className="underline decoration-2">underline decoration-2</p>
</div>
<div className="rounded-lg border p-4">
<p className="line-through">line-through</p>
</div>
<div className="rounded-lg border p-4">
<p className="italic">italic</p>
</div>
<div className="rounded-lg border p-4">
<p className="not-italic">not-italic</p>
</div>
<div className="rounded-lg border p-4">
<p className="uppercase">uppercase</p>
</div>
<div className="rounded-lg border p-4">
<p className="lowercase">LOWERCASE</p>
</div>
<div className="rounded-lg border p-4">
<p className="capitalize">capitalize every first letter</p>
</div>
<div className="rounded-lg border p-4">
<p className="truncate">
truncate este texto es deliberadamente muy largo para validar la elipsis en una
sola línea dentro del layout
</p>
</div>
<div className="rounded-lg border p-4">
<p className="text-balance text-lg">
text-balance para titulares y bloques con wrapping más estable.
</p>
</div>
</div>
</div>
<br />
<section className="space-y-6">
<div>
<h2 className="text-2xl font-semibold tracking-tight">Paleta completa</h2>
<p className="text-muted-foreground">
Cada bloque usa clases explícitas, para que Tailwind pueda detectarlas al compilar.
</p>
</div>
{palette.map((color) => (
<div key={color}>
<div>
<div className="capitalize">{color}</div>
<div>Shades 50 950</div>
</div>
<div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-11">
{shades.map((shade) => (
<div
className={`rounded-lg border p-3 ${colorClasses[color][shade]}`}
key={`${color}-${shade}`}
>
<p className="text-sm font-semibold">
{color}-{shade}
</p>
<p className="text-xs opacity-80">bg / preview</p>
</div>
))}
</div>
</div>
</div>
))}
</section>
<div>
<div>
<div>Estados interactivos</div>
<div>Hover, focus, ring y transiciones básicas.</div>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<button className="rounded-lg border bg-background px-4 py-3 text-left transition hover:bg-accent hover:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring">
Hover + focus ring
</button>
<button className="rounded-lg bg-primary px-4 py-3 text-left text-primary-foreground transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-ring">
Primary action
</button>
<button className="rounded-lg bg-secondary px-4 py-3 text-left text-secondary-foreground transition hover:bg-secondary/80 focus:outline-none focus:ring-2 focus:ring-ring">
Secondary action
</button>
<button className="rounded-lg border border-destructive/30 bg-destructive/10 px-4 py-3 text-left text-destructive transition hover:bg-destructive hover:text-destructive-foreground focus:outline-none focus:ring-2 focus:ring-ring">
Destructive action
</button>
</div>
</div>
</div>
</div>
);
}

View File

@ -3,6 +3,8 @@ import { AppLayout } from "@repo/rdx-ui/components";
import { Navigate, Route, createBrowserRouter, createRoutesFromElements } from "react-router-dom";
import { ModuleRoutes } from "@/components/module-routes";
import ShadcnShowcasePage from "@/pages/shadcn-ui-page";
import TailwindV4ShowcasePage from "@/pages/tailwindcss-page";
import { ErrorPage, LoginForm } from "../pages";
import { modules } from "../register-modules"; // Aquí ca
@ -48,6 +50,10 @@ export const getAppRouter = () => {
{/* Dynamic Module Routes */}
<Route element={<ModuleRoutes modules={grouped.app} params={params} />} path="*" />
{/* Test */}
<Route element={<ShadcnShowcasePage />} path="/shadcnui" />
<Route element={<TailwindV4ShowcasePage />} path="/tailwindcss4" />
{/* Main Layout */}
<Route element={<ErrorPage />} path="/dashboard" />
<Route element={<ErrorPage />} path="/settings" />

View File

@ -6,7 +6,10 @@ import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
build: {
sourcemap: true,
sourcemap: true
},
define: {
"process.env": {}
},
plugins: [react(), tailwindcss()],
resolve: {