Se agregaron 2 secciones al landing y se conectaron con la navbar. Agregue forms con modales para la carga de reparaciones y para contacto. Tambien se añadieron 2 paginas mas para luego añadir los terminos y condiciones y las politicas de privacidad. Tambien se definio el nombre del Proyecto como RepairIT
This commit is contained in:
parent
da9e934f37
commit
2e0ddcf23a
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostics
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Build output
|
||||
dist
|
||||
dist-ssr
|
||||
build
|
||||
out
|
||||
|
||||
# OS/Editor Files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
Thumbs.db
|
||||
24
frontend/.gitignore
vendored
24
frontend/.gitignore
vendored
|
|
@ -1,24 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
25
frontend/components.json
Normal file
25
frontend/components.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "radix-nova",
|
||||
"rsc": false,
|
||||
"tsx": false,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"rtl": false,
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"menuColor": "default",
|
||||
"menuAccent": "subtle",
|
||||
"registries": {}
|
||||
}
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<html lang="en" class="dark scroll-smooth">
|
||||
<head>
|
||||
<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" />
|
||||
<title>frontend</title>
|
||||
</head>
|
||||
<body>
|
||||
<title>RepairIT</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
10
frontend/jsconfig.json
Normal file
10
frontend/jsconfig.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
6699
frontend/package-lock.json
generated
6699
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -10,10 +10,27 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/geist": "^5.2.8",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.9.0",
|
||||
"next": "^16.2.4",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
"react-router-dom": "^7.14.2",
|
||||
"shadcn": "^4.4.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
import React from 'react';
|
||||
import Hero from './components/Hero';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
import { LandingPage } from './components/landing-page';
|
||||
import TermsPage from './components/terms-page';
|
||||
import PrivacyPage from './components/privacy-page';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-text-main font-sans selection:bg-accent/30">
|
||||
<main>
|
||||
<Hero />
|
||||
</main>
|
||||
</div>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/terms" element={<TermsPage />} />
|
||||
<Route path="/privacy" element={<PrivacyPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
72
frontend/src/components/ContactForm.jsx
Normal file
72
frontend/src/components/ContactForm.jsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { DialogClose } from "@/components/ui/dialog";
|
||||
|
||||
export default function ContactForm() {
|
||||
return (
|
||||
<div className="w-full px-6 py-8 sm:px-10">
|
||||
<form className="w-full text-left" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-bold text-foreground">
|
||||
Contactar a Ventas
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Dejanos tus datos y nos pondremos en contacto para ofrecerte el plan ideal para tu taller de reparación.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-border/50 mb-6" />
|
||||
|
||||
<div className="space-y-4 bg-surface/30 p-5 rounded-lg border border-border/50">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contactName">Nombre Completo</Label>
|
||||
<Input id="contactName" placeholder="Ej. Juan Pérez" className="bg-background" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="workshopName">Nombre del Taller</Label>
|
||||
<Input id="workshopName" placeholder="Ej. PC Fixers" className="bg-background" required />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contactEmail">Correo Electrónico</Label>
|
||||
<Input id="contactEmail" type="email" placeholder="taller@ejemplo.com" className="bg-background" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contactPhone">Teléfono / WhatsApp</Label>
|
||||
<Input id="contactPhone" type="tel" placeholder="+54 9 11 1234-5678" className="bg-background" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="contactMessage">¿En qué podemos ayudarte?</Label>
|
||||
<Textarea
|
||||
id="contactMessage"
|
||||
placeholder="Contanos sobre tu taller, volumen de reparaciones o consultas sobre nuestros planes..."
|
||||
className="bg-background min-h-[100px] resize-y"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-border/50 my-6" />
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-end gap-3 pb-2">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="ghost" className="w-full sm:w-auto">
|
||||
Cancelar
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="w-full sm:w-auto bg-brand hover:bg-brand-light text-white font-medium px-8">
|
||||
Enviar Mensaje
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
frontend/src/components/ContactSection.jsx
Normal file
29
frontend/src/components/ContactSection.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||
import ContactForm from './ContactForm';
|
||||
|
||||
export function ContactSection() {
|
||||
return (
|
||||
<section id="contacto" className="bg-background min-h-[60vh] flex flex-col justify-center py-16 relative z-10 border-t border-border/40 w-full">
|
||||
<div className="w-full mx-auto max-w-4xl px-6 text-center">
|
||||
<h2 className="text-4xl font-bold tracking-tight lg:text-5xl text-foreground mb-6">
|
||||
Impulsá tu taller hoy
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground mb-10 max-w-2xl mx-auto">
|
||||
Ponete en contacto con nosotros para conocer más sobre nuestros planes. Descubrí cómo RepairIT puede ayudarte a escalar tu taller.
|
||||
</p>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="lg" className="px-12 py-6 text-lg bg-brand hover:bg-brand-light text-white font-medium shadow-[0_0_20px_rgba(59,130,246,0.1)]">
|
||||
Contactar a Ventas
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="w-[95vw] sm:max-w-2xl bg-background border-border p-0 overflow-hidden rounded-xl">
|
||||
<ContactForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,17 +1,24 @@
|
|||
import React from 'react';
|
||||
import { BGPattern } from './ui/bg-pattern';
|
||||
import RepairForm from './form-layout';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<section className="relative overflow-hidden pt-32 pb-20 lg:pt-48 lg:pb-32">
|
||||
{/* Background Pattern */}
|
||||
<section id="inicio" className="relative overflow-hidden min-h-screen flex flex-col justify-center pt-16">
|
||||
{/* Patron de fondo del hero */}
|
||||
<BGPattern variant="grid" mask="fade-edges" fill="rgba(255, 255, 255, 0.05)" />
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
|
||||
<h1 className="text-5xl md:text-7xl font-bold tracking-tight mb-8">
|
||||
Gestiona tu taller con <br className="hidden md:block" />
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-accent-light via-accent to-accent-dark">
|
||||
<span className="text-transparent bg-clip-text bg-gradient-to-r from-brand-light via-brand to-brand-dark">
|
||||
precisión absoluta
|
||||
</span>
|
||||
</h1>
|
||||
|
|
@ -21,12 +28,16 @@ const Hero = () => {
|
|||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<button className="w-full sm:w-auto px-8 py-4 bg-accent hover:bg-accent-light text-white rounded-lg font-medium transition-all shadow-[0_0_20px_rgba(59,130,246,0.3)] hover:shadow-[0_0_30px_rgba(96,165,250,0.5)]">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="lg" className="w-full sm:w-auto px-8 bg-brand hover:bg-brand-light text-white font-medium">
|
||||
Comenzar Ahora
|
||||
</button>
|
||||
<button className="w-full sm:w-auto px-8 py-4 bg-surface hover:bg-surface-hover text-text-main rounded-lg font-medium transition-colors border border-surface-hover">
|
||||
Ver Demo
|
||||
</button>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="w-[95vw] sm:max-w-4xl lg:max-w-6xl bg-background border-border p-0 overflow-hidden rounded-xl">
|
||||
<RepairForm />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const Navbar = () => {
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b border-surface-hover">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
{/* Logo */}
|
||||
<div className="flex-shrink-0 flex items-center gap-2 cursor-pointer">
|
||||
<div className="w-8 h-8 bg-accent rounded-lg flex items-center justify-center shadow-[0_0_15px_rgba(59,130,246,0.5)]">
|
||||
<span className="text-white font-bold text-xl leading-none">H</span>
|
||||
</div>
|
||||
<span className="font-bold text-xl tracking-tight text-text-main">
|
||||
HardFix
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Desktop Menu */}
|
||||
<div className="hidden md:block">
|
||||
<div className="ml-10 flex items-baseline space-x-8">
|
||||
<a href="#" className="text-text-main font-medium hover:text-accent transition-colors">Inicio</a>
|
||||
<a href="#" className="text-text-muted hover:text-text-main transition-colors">Características</a>
|
||||
<a href="#" className="text-text-muted hover:text-text-main transition-colors">Precios</a>
|
||||
<a href="#" className="text-text-muted hover:text-text-main transition-colors">Testimonios</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="hidden md:flex items-center gap-4">
|
||||
<button className="text-text-muted hover:text-text-main font-medium transition-colors">
|
||||
Iniciar Sesión
|
||||
</button>
|
||||
<button className="px-4 py-2 bg-accent hover:bg-accent-light text-white rounded-md font-medium transition-all shadow-[0_0_10px_rgba(59,130,246,0.3)] hover:shadow-[0_0_20px_rgba(96,165,250,0.5)]">
|
||||
Probar Gratis
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<div className="md:hidden flex items-center">
|
||||
<button className="text-text-muted hover:text-text-main focus:outline-none">
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
76
frontend/src/components/features.jsx
Normal file
76
frontend/src/components/features.jsx
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
||||
import { ClipboardCheck, Package, MessageSquare } from 'lucide-react'
|
||||
|
||||
export function Features() {
|
||||
return (
|
||||
<section id="caracteristicas" className="bg-zinc-50 min-h-screen flex flex-col justify-center py-16 dark:bg-transparent relative z-10 w-full">
|
||||
<div className="w-full mx-auto max-w-5xl px-6">
|
||||
<div className="text-center">
|
||||
<h2 className="text-balance text-4xl font-bold tracking-tight lg:text-5xl text-foreground">
|
||||
Todo lo que tu taller necesita
|
||||
</h2>
|
||||
<p className="mt-4 text-muted-foreground max-w-2xl mx-auto">
|
||||
Herramientas diseñadas específicamente para agilizar la reparación de hardware y mejorar la experiencia de tus clientes.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="mx-auto mt-8 grid w-full max-w-sm grid-cols-1 gap-6 *:text-center md:max-w-full md:grid-cols-3 md:mt-16">
|
||||
<Card className="group shadow-black-950/5 bg-surface/40 border-border/50 backdrop-blur-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardDecorator>
|
||||
<ClipboardCheck className="size-6 text-brand" aria-hidden />
|
||||
</CardDecorator>
|
||||
|
||||
<h3 className="mt-6 font-semibold text-lg text-foreground">Trazabilidad Total</h3>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">Hacé un seguimiento detallado del estado de cada equipo, desde su ingreso hasta la entrega final al cliente.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="group shadow-black-950/5 bg-surface/40 border-border/50 backdrop-blur-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardDecorator>
|
||||
<Package className="size-6 text-brand" aria-hidden />
|
||||
</CardDecorator>
|
||||
|
||||
<h3 className="mt-6 font-semibold text-lg text-foreground">Control de Inventario</h3>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">Gestiona tus repuestos y componentes de forma eficiente para que nunca te falte lo esencial en una reparación.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="group shadow-black-950/5 bg-surface/40 border-border/50 backdrop-blur-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardDecorator>
|
||||
<MessageSquare className="size-6 text-brand" aria-hidden />
|
||||
</CardDecorator>
|
||||
|
||||
<h3 className="mt-6 font-semibold text-lg text-foreground">Comunicación Profesional</h3>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">Mantené a tus clientes informados con reportes claros y presupuestos detallados directamente desde la plataforma.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const CardDecorator = ({
|
||||
children
|
||||
}) => (
|
||||
<div
|
||||
aria-hidden
|
||||
className="relative mx-auto size-36 [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]">
|
||||
<div
|
||||
className="absolute inset-0 [--border:black] dark:[--border:white] bg-[linear-gradient(to_right,var(--border)_1px,transparent_1px),linear-gradient(to_bottom,var(--border)_1px,transparent_1px)] bg-[size:24px_24px] opacity-10" />
|
||||
<div
|
||||
className="bg-background absolute inset-0 m-auto flex size-12 items-center justify-center border-t border-l">{children}</div>
|
||||
</div>
|
||||
)
|
||||
47
frontend/src/components/footer.jsx
Normal file
47
frontend/src/components/footer.jsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const links = [
|
||||
{ title: 'Inicio', href: '#inicio' },
|
||||
{ title: 'Características', href: '#caracteristicas' },
|
||||
{ title: 'Contacto', href: '#contacto' },
|
||||
];
|
||||
|
||||
export function FooterSection() {
|
||||
return (
|
||||
<footer className="py-12 border-t border-border/40 bg-background z-10 relative">
|
||||
<div className="mx-auto max-w-5xl px-6">
|
||||
<div className="flex items-center justify-center mb-8">
|
||||
<span className="text-2xl font-bold tracking-tight text-foreground">
|
||||
Repair<span className="text-brand">IT</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 mb-4 flex flex-wrap justify-center gap-6 text-sm">
|
||||
{links.map((link, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={link.href}
|
||||
className="text-muted-foreground hover:text-foreground transition-colors duration-150">
|
||||
{link.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col items-center gap-4">
|
||||
<span className="text-muted-foreground block text-center text-sm">
|
||||
© {new Date().getFullYear()} RepairIT. Todos los derechos reservados.
|
||||
</span>
|
||||
<div className="flex flex-wrap justify-center gap-6 text-xs text-text-muted/60">
|
||||
<Link to="/terms" className="hover:text-foreground transition-colors">
|
||||
Términos y Condiciones
|
||||
</Link>
|
||||
<Link to="/privacy" className="hover:text-foreground transition-colors">
|
||||
Privacidad
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
150
frontend/src/components/form-layout.jsx
Normal file
150
frontend/src/components/form-layout.jsx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { DialogClose } from "@/components/ui/dialog";
|
||||
|
||||
export default function RepairForm() {
|
||||
const [deviceType, setDeviceType] = useState("laptop");
|
||||
|
||||
return (
|
||||
<div className="w-full max-h-[90vh] overflow-y-auto px-6 py-8 sm:px-10">
|
||||
<form className="w-full text-left" onSubmit={(e) => e.preventDefault()}>
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="text-2xl font-bold text-foreground">
|
||||
Registrar Nueva Reparación
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Completa los datos del cliente y los detalles técnicos del equipo para generar la orden de servicio.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator className="bg-border/50 mb-6" />
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10">
|
||||
|
||||
{/* Columna Izquierda: Información Personal */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold text-foreground">Información Personal</h4>
|
||||
<p className="text-sm text-muted-foreground">Datos de contacto del cliente.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 bg-surface/30 p-5 rounded-lg border border-border/50">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="firstName">Nombre</Label>
|
||||
<Input id="firstName" placeholder="Ej. Juan" className="bg-background" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="lastName">Apellido</Label>
|
||||
<Input id="lastName" placeholder="Ej. Pérez" className="bg-background" required />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Correo Electrónico</Label>
|
||||
<Input id="email" type="email" placeholder="juan@ejemplo.com" className="bg-background" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="phone">Teléfono / WhatsApp</Label>
|
||||
<Input id="phone" type="tel" placeholder="+54 9 11 1234-5678" className="bg-background" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Columna Derecha: Detalles del Equipo */}
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="text-lg font-semibold text-foreground">Detalles del Equipo</h4>
|
||||
<p className="text-sm text-muted-foreground">Especificaciones y fallo reportado.</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 bg-surface/30 p-5 rounded-lg border border-border/50">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="deviceType">Tipo de Equipo</Label>
|
||||
<Select value={deviceType} onValueChange={setDeviceType}>
|
||||
<SelectTrigger id="deviceType" className="w-full bg-background">
|
||||
<SelectValue placeholder="Selecciona el tipo" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="laptop">Notebook</SelectItem>
|
||||
<SelectItem value="pc">PC de Escritorio</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{deviceType === "laptop" ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="brand">Marca</Label>
|
||||
<Input id="brand" placeholder="Ej. Lenovo, HP..." className="bg-background" required />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="model">Modelo</Label>
|
||||
<Input id="model" placeholder="Ej. ThinkPad T490" className="bg-background" required />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="specs">Componentes Principales</Label>
|
||||
<Textarea
|
||||
id="specs"
|
||||
placeholder="Ej. Mother ASUS B450, Ryzen 5, 16GB RAM..."
|
||||
className="bg-background min-h-[60px] resize-y"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="issue">Descripción del Fallo</Label>
|
||||
<Textarea
|
||||
id="issue"
|
||||
placeholder="Describinos detalladamente el problema..."
|
||||
className="bg-background min-h-[80px] resize-y"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="password">Contraseña del equipo</Label>
|
||||
<span className="text-xs text-muted-foreground">Opcional</span>
|
||||
</div>
|
||||
<Input id="password" type="text" placeholder="Pin o contraseña" className="bg-background" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Separator className="bg-border/50 my-6" />
|
||||
|
||||
{/* Acciones */}
|
||||
<div className="flex flex-col sm:flex-row items-center justify-end gap-3 pb-2">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="ghost" className="w-full sm:w-auto">
|
||||
Cancelar
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="w-full sm:w-auto bg-brand hover:bg-brand-light text-white font-medium px-8">
|
||||
Registrar Reparación
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
frontend/src/components/landing-page.jsx
Normal file
20
frontend/src/components/landing-page.jsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import Hero from './Hero';
|
||||
import { NavigationBar } from './navigation-bar';
|
||||
import { Features } from './features';
|
||||
import { ContactSection } from './ContactSection';
|
||||
import { FooterSection } from './footer';
|
||||
|
||||
export function LandingPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-text-main font-sans selection:bg-brand/30 pt-16">
|
||||
<NavigationBar />
|
||||
<main>
|
||||
<Hero />
|
||||
<Features />
|
||||
<ContactSection />
|
||||
</main>
|
||||
<FooterSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
70
frontend/src/components/navigation-bar.jsx
Normal file
70
frontend/src/components/navigation-bar.jsx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu";
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function NavigationBar({ isSimple = false }) {
|
||||
return (
|
||||
<header className="fixed top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className={`container mx-auto h-16 max-w-7xl items-center px-4 sm:px-6 lg:px-8 ${isSimple ? 'flex justify-center' : 'grid grid-cols-3'}`}>
|
||||
|
||||
{/* Logo */}
|
||||
<div className={`flex items-center ${isSimple ? '' : 'justify-self-start'}`}>
|
||||
<Link to="/" className="text-xl font-bold tracking-tight text-foreground">
|
||||
Repair<span className="text-brand">IT</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{!isSimple && (
|
||||
<>
|
||||
{/* Enlaces centrales (Escritorio únicamente) */}
|
||||
<div className="hidden md:flex justify-self-center">
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<a href="/#inicio" className={cn(navigationMenuTriggerStyle(), "bg-transparent text-muted-foreground hover:text-foreground")}>
|
||||
Inicio
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<a href="/#caracteristicas" className={cn(navigationMenuTriggerStyle(), "bg-transparent text-muted-foreground hover:text-foreground")}>
|
||||
Características
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<a href="/#contacto" className={cn(navigationMenuTriggerStyle(), "bg-transparent text-muted-foreground hover:text-foreground")}>
|
||||
Contacto
|
||||
</a>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
</div>
|
||||
|
||||
{/* Botones de acción */}
|
||||
<div className="flex items-center gap-4 justify-self-end">
|
||||
<Button variant="ghost" className="hidden sm:inline-flex text-muted-foreground hover:text-foreground">
|
||||
Iniciar Sesión
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
24
frontend/src/components/privacy-page.jsx
Normal file
24
frontend/src/components/privacy-page.jsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import { NavigationBar } from './navigation-bar';
|
||||
import { FooterSection } from './footer';
|
||||
|
||||
const PrivacyPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-text-main font-sans pt-16">
|
||||
<NavigationBar isSimple={true} />
|
||||
<main className="max-w-4xl mx-auto px-6 py-20">
|
||||
<section className="mb-16">
|
||||
<h1 className="text-4xl font-bold mb-8 text-foreground">Política de Privacidad</h1>
|
||||
<div className="space-y-6 text-text-muted leading-relaxed">
|
||||
<p>
|
||||
En RepairIT, nos tomamos muy en serio la privacidad de tus datos y los de tus clientes.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<FooterSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrivacyPage;
|
||||
24
frontend/src/components/terms-page.jsx
Normal file
24
frontend/src/components/terms-page.jsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import { NavigationBar } from './navigation-bar';
|
||||
import { FooterSection } from './footer';
|
||||
|
||||
const TermsPage = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-text-main font-sans pt-16">
|
||||
<NavigationBar isSimple={true} />
|
||||
<main className="max-w-4xl mx-auto px-6 py-20">
|
||||
<section className="mb-16">
|
||||
<h1 className="text-4xl font-bold mb-8 text-foreground">Términos y Condiciones</h1>
|
||||
<div className="space-y-6 text-text-muted leading-relaxed">
|
||||
<p>
|
||||
Bienvenido a RepairIT. Al utilizar nuestra plataforma, aceptas cumplir con los siguientes términos y condiciones.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<FooterSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TermsPage;
|
||||
34
frontend/src/components/ui/badge.jsx
Normal file
34
frontend/src/components/ui/badge.jsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import * as React from "react"
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant,
|
||||
...props
|
||||
}) {
|
||||
return (<div className={cn(badgeVariants({ variant }), className)} {...props} />);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
47
frontend/src/components/ui/button.jsx
Normal file
47
frontend/src/components/ui/button.jsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props} />
|
||||
);
|
||||
})
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
50
frontend/src/components/ui/card.jsx
Normal file
50
frontend/src/components/ui/card.jsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||
{...props} />
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props} />
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
24
frontend/src/components/ui/checkbox.jsx
Normal file
24
frontend/src/components/ui/checkbox.jsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
||||
117
frontend/src/components/ui/cpu-architecture.jsx
Normal file
117
frontend/src/components/ui/cpu-architecture.jsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
|
||||
const CpuArchitecture = ({
|
||||
className,
|
||||
width = "100%",
|
||||
height = "100%",
|
||||
text = "R-IT",
|
||||
showCpuConnections = true,
|
||||
animateText = true,
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
className={cn("text-muted", className)}
|
||||
width={width}
|
||||
height={height}
|
||||
// viewBox="0 0 200 100" // Original
|
||||
// Chip is at x=85, y=40, width=30, height=20.
|
||||
// Pins stick out about 10 units in each direction.
|
||||
viewBox="70 25 60 50">
|
||||
|
||||
{/* CPU Box */}
|
||||
<g>
|
||||
{/* Cpu connections (Pins) */}
|
||||
{showCpuConnections && (
|
||||
<g fill="url(#cpu-connection-gradient)">
|
||||
{/* Top pins */}
|
||||
<rect x="92" y="37" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="100" y="37" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="108" y="37" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="116" y="37" width="2.5" height="5" rx="0.7" />
|
||||
|
||||
{/* Bottom pins */}
|
||||
<rect x="92" y="58" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="100" y="58" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="108" y="58" width="2.5" height="5" rx="0.7" />
|
||||
<rect x="116" y="58" width="2.5" height="5" rx="0.7" />
|
||||
|
||||
{/* Left pins */}
|
||||
<rect x="82" y="45" width="5" height="2.5" rx="0.7" />
|
||||
<rect x="82" y="52.5" width="5" height="2.5" rx="0.7" />
|
||||
|
||||
{/* Right pins */}
|
||||
<rect x="128" y="45" width="5" height="2.5" rx="0.7" />
|
||||
<rect x="128" y="52.5" width="5" height="2.5" rx="0.7" />
|
||||
</g>
|
||||
)}
|
||||
{/* Main CPU Rectangle */}
|
||||
<rect
|
||||
x="85"
|
||||
y="40"
|
||||
width="45"
|
||||
height="20"
|
||||
rx="2"
|
||||
fill="#181818"
|
||||
filter="url(#cpu-light-shadow)" />
|
||||
{/* CPU Text */}
|
||||
<text
|
||||
x="107.5"
|
||||
y="52.5"
|
||||
fontSize="7.5"
|
||||
textAnchor="middle"
|
||||
fill={animateText ? "url(#cpu-text-gradient)" : "white"}
|
||||
fontWeight="600"
|
||||
letterSpacing="0.05em">
|
||||
{text}
|
||||
</text>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<filter id="cpu-light-shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="1.5" dy="1.5" stdDeviation="1" floodColor="black" floodOpacity="0.3" />
|
||||
</filter>
|
||||
{/* Cpu connection gradient */}
|
||||
<linearGradient id="cpu-connection-gradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#888888" />
|
||||
<stop offset="100%" stopColor="#333333" />
|
||||
</linearGradient>
|
||||
{/* Add CPU Text Gradient */}
|
||||
<linearGradient id="cpu-text-gradient" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stopColor="#60a5fa">
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="-2; -1; 0"
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
calcMode="spline"
|
||||
keyTimes="0; 0.5; 1"
|
||||
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
|
||||
</stop>
|
||||
<stop offset="25%" stopColor="white">
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="-1; 0; 1"
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
calcMode="spline"
|
||||
keyTimes="0; 0.5; 1"
|
||||
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
|
||||
</stop>
|
||||
<stop offset="50%" stopColor="#60a5fa">
|
||||
<animate
|
||||
attributeName="offset"
|
||||
values="0; 1; 2;"
|
||||
dur="3s"
|
||||
repeatCount="indefinite"
|
||||
calcMode="spline"
|
||||
keyTimes="0; 0.5; 1"
|
||||
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
|
||||
</stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export { CpuArchitecture };
|
||||
151
frontend/src/components/ui/dialog.jsx
Normal file
151
frontend/src/components/ui/dialog.jsx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
import * as React from "react"
|
||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
||||
<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({
|
||||
className,
|
||||
showCloseButton = false,
|
||||
children,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("font-heading text-base leading-none font-medium", className)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn(
|
||||
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
19
frontend/src/components/ui/input.jsx
Normal file
19
frontend/src/components/ui/input.jsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props} />
|
||||
);
|
||||
})
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
16
frontend/src/components/ui/label.jsx
Normal file
16
frontend/src/components/ui/label.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
104
frontend/src/components/ui/navigation-menu.jsx
Normal file
104
frontend/src/components/ui/navigation-menu.jsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import * as React from "react"
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const NavigationMenu = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
))
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||
|
||||
const NavigationMenuList = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
)
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}>
|
||||
{children}{" "}
|
||||
<ChevronDownIcon
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true" />
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
))
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||
|
||||
const NavigationMenuContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props} />
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<div
|
||||
className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
}
|
||||
25
frontend/src/components/ui/popover.jsx
Normal file
25
frontend/src/components/ui/popover.jsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
29
frontend/src/components/ui/radio-group.jsx
Normal file
29
frontend/src/components/ui/radio-group.jsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import * as React from "react"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
import { Circle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const RadioGroup = React.forwardRef(({ className, ...props }, ref) => {
|
||||
return (<RadioGroupPrimitive.Root className={cn("grid gap-2", className)} {...props} ref={ref} />);
|
||||
})
|
||||
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
||||
|
||||
const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
|
||||
<Circle className="h-2.5 w-2.5 fill-current text-current" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
})
|
||||
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
||||
|
||||
export { RadioGroup, RadioGroupItem }
|
||||
122
frontend/src/components/ui/select.jsx
Normal file
122
frontend/src/components/ui/select.jsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn("p-1", position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]")}>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props} />
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props} />
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
23
frontend/src/components/ui/separator.jsx
Normal file
23
frontend/src/components/ui/separator.jsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Separator = React.forwardRef((
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
18
frontend/src/components/ui/textarea.jsx
Normal file
18
frontend/src/components/ui/textarea.jsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Textarea = React.forwardRef(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props} />
|
||||
);
|
||||
})
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
|
|
@ -1,9 +1,14 @@
|
|||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@import "@fontsource-variable/geist";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
--color-accent-light: #60a5fa; /* Blue 400 */
|
||||
--color-accent: #3b82f6; /* Blue 500 */
|
||||
--color-accent-dark: #2563eb; /* Blue 600 */
|
||||
--color-brand-light: #60a5fa; /* Blue 400 */
|
||||
--color-brand: #3b82f6; /* Blue 500 */
|
||||
--color-brand-dark: #2563eb; /* Blue 600 */
|
||||
--color-background: #09090b; /* Zinc 950 (Almost Black) */
|
||||
--color-surface: #18181b; /* Zinc 900 */
|
||||
--color-surface-hover: #27272a;/* Zinc 800 */
|
||||
|
|
@ -16,5 +21,124 @@
|
|||
background-color: var(--color-background);
|
||||
color: var(--color-text-main);
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--font-heading: var(--font-sans);
|
||||
--font-sans: 'Geist Variable', sans-serif;
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-ring: var(--ring);
|
||||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-background: var(--background);
|
||||
--radius-sm: calc(var(--radius) * 0.6);
|
||||
--radius-md: calc(var(--radius) * 0.8);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) * 1.4);
|
||||
--radius-2xl: calc(var(--radius) * 1.8);
|
||||
--radius-3xl: calc(var(--radius) * 2.2);
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.922 0 0);
|
||||
--primary-foreground: oklch(0.205 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { clsx } from "clsx"
|
||||
import { clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import path from "path"
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
|
|
@ -8,4 +9,9 @@ export default defineConfig({
|
|||
tailwindcss(),
|
||||
react()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user