Commit 89139adc by mvginghina

Added TicketApp

parents
Showing with 9090 additions and 0 deletions
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# next.js
/.next/
/out/
# production
/build
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
\ No newline at end of file
"use client"
import { useState } from "react"
import Link from "next/link"
import Image from "next/image"
import { ArrowLeft } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
export default function AccountPage() {
const [interests, setInterests] = useState({
festivals: false,
gamingEvents: true,
hikes: false,
boardgames: true,
dances: false,
beerpong: true,
bowling: false,
concerts: false,
})
const handleInterestChange = (interest: keyof typeof interests) => {
setInterests((prev) => ({
...prev,
[interest]: !prev[interest],
}))
}
return (
<div className="flex min-h-screen flex-col">
{/* Header */}
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<Link href="/" className="flex items-center gap-2">
<ArrowLeft className="h-5 w-5" />
</Link>
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<div className="max-w-3xl mx-auto">
<div className="flex flex-col md:flex-row gap-8">
{/* Left column - Profile picture and interests */}
<div className="md:w-1/3">
<div className="flex flex-col items-center mb-6">
<div className="w-32 h-32 rounded-full bg-gray-300 overflow-hidden relative mb-4">
<Image
src="/placeholder.svg?height=128&width=128"
alt="Profile picture"
fill
className="object-cover"
/>
</div>
<Button variant="outline" size="sm">
Upload New Photo
</Button>
</div>
<div className="mb-6">
<h2 className="text-lg font-semibold mb-3">My interests</h2>
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox
id="festivals"
checked={interests.festivals}
onCheckedChange={() => handleInterestChange("festivals")}
/>
<Label htmlFor="festivals">Festivals</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="gamingEvents"
checked={interests.gamingEvents}
onCheckedChange={() => handleInterestChange("gamingEvents")}
/>
<Label htmlFor="gamingEvents">Gaming Events</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="hikes"
checked={interests.hikes}
onCheckedChange={() => handleInterestChange("hikes")}
/>
<Label htmlFor="hikes">Hikes</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="boardgames"
checked={interests.boardgames}
onCheckedChange={() => handleInterestChange("boardgames")}
/>
<Label htmlFor="boardgames">Boardgames</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="dances"
checked={interests.dances}
onCheckedChange={() => handleInterestChange("dances")}
/>
<Label htmlFor="dances">Dances</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="beerpong"
checked={interests.beerpong}
onCheckedChange={() => handleInterestChange("beerpong")}
/>
<Label htmlFor="beerpong">Beerpong</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="bowling"
checked={interests.bowling}
onCheckedChange={() => handleInterestChange("bowling")}
/>
<Label htmlFor="bowling">Bowling</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="concerts"
checked={interests.concerts}
onCheckedChange={() => handleInterestChange("concerts")}
/>
<Label htmlFor="concerts">Concerts</Label>
</div>
</div>
</div>
</div>
{/* Right column - Account details */}
<div className="md:w-2/3">
<div className="flex justify-end mb-4">
<Button variant="destructive" size="sm">
Delete account
</Button>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="username">Username</Label>
<Input id="username" defaultValue="callmenoob77" />
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" defaultValue="sm@sher.es" />
</div>
<div>
<Label htmlFor="fullname">Full Name</Label>
<Input id="fullname" defaultValue="Mohamed Ali" />
</div>
<div>
<Label htmlFor="details">The rest of the details</Label>
<Textarea id="details" defaultValue="bla bla bla" />
</div>
<div className="flex justify-end mt-6">
<Button className="bg-black hover:bg-gray-800">Update</Button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
)
}
import Link from "next/link"
import { ArrowLeft, Save } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Card, CardContent } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
// This would normally come from a database or API
const getEventById = (id: string) => {
return {
id,
title: "Tickets to Untold Festival 2025",
description:
"The biggest music festival in Romania returns for its 2025 edition with an amazing lineup of international artists.",
priceRange: "70€ - 300€",
standardPrice: "70",
vipPrice: "150",
premiumPrice: "300",
location: "Cluj Napoca, Romania",
venue: "Central Park",
startDate: "2025-08-07",
endDate: "2025-08-10",
startTime: "12:00",
endTime: "23:00",
capacity: "5000",
organizer: "Untold Festival SRL",
image: "/placeholder.svg?height=400&width=800",
}
}
export default function EditEventPage({ params }: { params: { id: string } }) {
const event = getEventById(params.id)
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="container flex h-14 items-center">
<Link href="/admin" className="flex items-center gap-2">
<ArrowLeft className="h-5 w-5" />
<span>Back to Admin</span>
</Link>
<div className="mx-auto font-bold text-xl">TickMeIn Admin</div>
</div>
</header>
<div className="bg-yellow-300 py-2">
<div className="container flex justify-between items-center">
<div className="font-semibold">Edit Event</div>
<Button size="sm" className="bg-black hover:bg-gray-800">
<Save className="h-4 w-4 mr-2" />
Save Changes
</Button>
</div>
</div>
<main className="flex-1 py-6">
<div className="container">
<Card>
<CardContent className="p-6">
<Tabs defaultValue="details">
<TabsList className="mb-6">
<TabsTrigger value="details">Event Details</TabsTrigger>
<TabsTrigger value="tickets">Tickets</TabsTrigger>
<TabsTrigger value="location">Location</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>
<TabsContent value="details" className="space-y-6">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="event-title" className="required">
Event Title
</Label>
<Input id="event-title" defaultValue={event.title} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-description">Description</Label>
<Textarea id="event-description" rows={5} defaultValue={event.description} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-organizer">Organizer</Label>
<Input id="event-organizer" defaultValue={event.organizer} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-capacity">Capacity</Label>
<Input id="event-capacity" type="number" defaultValue={event.capacity} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="event-start-date">Start Date</Label>
<Input id="event-start-date" type="date" defaultValue={event.startDate} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-end-date">End Date</Label>
<Input id="event-end-date" type="date" defaultValue={event.endDate} />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="event-start-time">Start Time</Label>
<Input id="event-start-time" type="time" defaultValue={event.startTime} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-end-time">End Time</Label>
<Input id="event-end-time" type="time" defaultValue={event.endTime} />
</div>
</div>
</div>
</TabsContent>
<TabsContent value="tickets" className="space-y-6">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="standard-price">Standard Ticket Price (€)</Label>
<Input id="standard-price" type="number" defaultValue={event.standardPrice} />
</div>
<div className="grid gap-2">
<Label htmlFor="vip-price">VIP Ticket Price (€)</Label>
<Input id="vip-price" type="number" defaultValue={event.vipPrice} />
</div>
<div className="grid gap-2">
<Label htmlFor="premium-price">Premium Ticket Price (€)</Label>
<Input id="premium-price" type="number" defaultValue={event.premiumPrice} />
</div>
<div className="grid gap-2">
<Label htmlFor="discount">Discount (%)</Label>
<Input id="discount" type="number" defaultValue="0" min="0" max="100" />
</div>
</div>
</TabsContent>
<TabsContent value="location" className="space-y-6">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="event-location">City, Country</Label>
<Input id="event-location" defaultValue={event.location} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-venue">Venue</Label>
<Input id="event-venue" defaultValue={event.venue} />
</div>
<div className="grid gap-2">
<Label htmlFor="event-address">Address</Label>
<Input id="event-address" placeholder="Enter venue address" />
</div>
<div className="aspect-[2/1] bg-gray-100 rounded-md flex items-center justify-center">
<p className="text-gray-500">Map will be displayed here</p>
</div>
</div>
</TabsContent>
<TabsContent value="settings" className="space-y-6">
<div className="grid gap-4">
<div className="flex items-center gap-2">
<input type="checkbox" id="publish-event" className="rounded" defaultChecked />
<Label htmlFor="publish-event">Publish event</Label>
</div>
<div className="flex items-center gap-2">
<input type="checkbox" id="featured-event" className="rounded" />
<Label htmlFor="featured-event">Feature on homepage</Label>
</div>
<div className="flex items-center gap-2">
<input type="checkbox" id="require-approval" className="rounded" />
<Label htmlFor="require-approval">Require approval for ticket purchases</Label>
</div>
<div className="grid gap-2">
<Label htmlFor="event-tags">Tags (comma separated)</Label>
<Input id="event-tags" placeholder="music, festival, electronic" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-category">Category</Label>
<select
id="event-category"
className="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 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"
>
<option value="music">Music</option>
<option value="sports">Sports</option>
<option value="arts">Arts & Theater</option>
<option value="food">Food & Drink</option>
<option value="business">Business & Networking</option>
</select>
</div>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
</main>
</div>
)
}
import Link from "next/link"
import { ArrowLeft, Save } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Card, CardContent } from "@/components/ui/card"
export default function NewEventPage() {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="container flex h-14 items-center">
<Link href="/admin" className="flex items-center gap-2">
<ArrowLeft className="h-5 w-5" />
<span>Back to Admin</span>
</Link>
<div className="mx-auto font-bold text-xl">TickMeIn Admin</div>
</div>
</header>
<div className="bg-yellow-300 py-2">
<div className="container flex justify-between items-center">
<div className="font-semibold">Create New Event</div>
<Button size="sm" className="bg-black hover:bg-gray-800">
<Save className="h-4 w-4 mr-2" />
Create Event
</Button>
</div>
</div>
<main className="flex-1 py-6">
<div className="container">
<Card>
<CardContent className="p-6">
<div className="space-y-6">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="event-title" className="after:content-['*'] after:ml-0.5 after:text-red-500">
Event Title
</Label>
<Input id="event-title" placeholder="Enter event title" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-description">Description</Label>
<Textarea id="event-description" rows={5} placeholder="Describe your event" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-organizer">Organizer</Label>
<Input id="event-organizer" placeholder="Who is organizing this event?" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-capacity">Capacity</Label>
<Input id="event-capacity" type="number" placeholder="Maximum number of attendees" />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="event-start-date">Start Date</Label>
<Input id="event-start-date" type="date" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-end-date">End Date</Label>
<Input id="event-end-date" type="date" />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="event-start-time">Start Time</Label>
<Input id="event-start-time" type="time" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-end-time">End Time</Label>
<Input id="event-end-time" type="time" />
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="standard-price">Standard Ticket Price (€)</Label>
<Input id="standard-price" type="number" placeholder="0" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-location">City, Country</Label>
<Input id="event-location" placeholder="Where will this event take place?" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-venue">Venue</Label>
<Input id="event-venue" placeholder="Name of the venue" />
</div>
<div className="grid gap-2">
<Label htmlFor="event-image">Event Image</Label>
<div className="flex items-center gap-2">
<Input id="event-image" type="file" className="flex-1" />
<Button variant="outline" size="sm">
Upload
</Button>
</div>
<p className="text-xs text-gray-500">Recommended size: 1200x600px. Max file size: 5MB.</p>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</main>
</div>
)
}
export default function Loading() {
return null
}
import Link from "next/link"
import { Search, Plus, Filter, User, Bell } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import AdminEventList from "@/components/admin-event-list"
export default function AdminPage() {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="container flex h-14 items-center">
<div className="mr-4 flex items-center gap-2 font-bold text-xl">
<span>TickMeIn Admin</span>
</div>
<div className="flex-1 flex items-center gap-2 md:gap-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search events..."
className="pl-8 bg-gray-800 border-gray-700 text-white"
/>
</div>
</div>
<nav className="ml-auto flex items-center gap-4">
<Link href="/" className="text-sm font-medium text-white hover:text-gray-300">
View Site
</Link>
<Button variant="outline" size="icon" className="text-white border-gray-700">
<Bell className="h-4 w-4" />
<span className="sr-only">Notifications</span>
</Button>
<Button variant="outline" size="icon" className="text-white border-gray-700">
<User className="h-4 w-4" />
<span className="sr-only">Account</span>
</Button>
</nav>
</div>
</header>
<main className="flex-1 flex">
<aside className="hidden md:flex w-64 flex-col bg-gray-900 text-white">
<div className="p-4 font-medium">
<Link href="/admin/my-events" className="flex items-center py-2 px-3 bg-cyan-600 rounded">
My events
</Link>
</div>
<div className="p-4">
<h3 className="mb-2 text-xs uppercase text-gray-400">Filter by</h3>
<div className="space-y-2">
<div className="relative">
<Input placeholder="Type of event" className="bg-gray-800 border-gray-700 text-white" />
</div>
<div>
<h4 className="mb-1 text-xs text-gray-400">Price range</h4>
<div className="flex items-center gap-2">
<Input type="range" className="w-full" min="0" max="500" defaultValue="250" />
</div>
</div>
<div className="space-y-1">
<label className="flex items-center gap-2 text-sm">
<input type="checkbox" className="rounded text-cyan-600" />
<span>Accessible venues</span>
</label>
<label className="flex items-center gap-2 text-sm">
<input type="checkbox" className="rounded text-cyan-600" />
<span>Parking spaces for disabilities</span>
</label>
<label className="flex items-center gap-2 text-sm">
<input type="checkbox" className="rounded text-cyan-600" />
<span>Age restriction</span>
</label>
</div>
</div>
</div>
</aside>
<div className="flex-1 overflow-auto">
<div className="container py-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Manage Events</h1>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<Filter className="h-4 w-4 mr-2" />
Filter
</Button>
<Link href="/admin/events/new">
<Button size="sm">
<Plus className="h-4 w-4 mr-2" />
Create Event
</Button>
</Link>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div className="bg-gray-100 p-4 rounded-lg">
<div className="text-2xl font-bold">24</div>
<div className="text-sm text-gray-500">Active Events</div>
</div>
<div className="bg-gray-100 p-4 rounded-lg">
<div className="text-2xl font-bold">1,245</div>
<div className="text-sm text-gray-500">Tickets Sold</div>
</div>
<div className="bg-gray-100 p-4 rounded-lg">
<div className="text-2xl font-bold">€12,450</div>
<div className="text-sm text-gray-500">Revenue</div>
</div>
</div>
<div className="mb-6">
<h2 className="text-lg font-semibold mb-4">Here are the most popular events of the day:</h2>
<AdminEventList />
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import type React from "react"
import { useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Eye, EyeOff } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
export default function SignInPage() {
const router = useRouter()
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [rememberMe, setRememberMe] = useState(false)
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState("")
const handleSignIn = (e: React.FormEvent) => {
e.preventDefault()
// Simple validation
if (!email || !password) {
setError("Please fill in all fields")
return
}
try {
// In a real app, you would validate credentials against a backend
// For demo purposes, we'll just simulate a successful login
localStorage.setItem("isLoggedIn", "true")
router.push("/events")
} catch (err) {
console.error("Error setting localStorage:", err)
// Fallback for environments where localStorage is not available
router.push("/events")
}
}
return (
<div className="min-h-screen flex flex-col">
<header className="bg-black text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
</header>
<main className="flex-1 flex items-center justify-center p-6">
<div className="w-full max-w-md">
<div className="border rounded-md p-6">
<h2 className="text-2xl font-bold mb-6">Sign In</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>
)}
<form onSubmit={handleSignIn} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email address or username</Label>
<Input
id="email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email or username"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
/>
<button
type="button"
className="absolute right-3 top-1/2 transform -translate-y-1/2"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-gray-500" />
) : (
<Eye className="h-4 w-4 text-gray-500" />
)}
</button>
</div>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="remember"
checked={rememberMe}
onCheckedChange={(checked) => setRememberMe(checked as boolean)}
/>
<Label htmlFor="remember" className="text-sm">
Remember me
</Label>
</div>
<Button type="submit" className="w-full bg-gray-800 hover:bg-black">
Log In
</Button>
</form>
<div className="mt-4 text-center text-sm">
<p>
Don't have an account?{" "}
<Link href="/auth/signup" className="text-blue-600 hover:underline">
Sign up
</Link>
</p>
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import type React from "react"
import { useState, useRef } from "react"
import Link from "next/link"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { Eye, EyeOff, Upload } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
export default function SignUpPage() {
const router = useRouter()
const fileInputRef = useRef<HTMLInputElement>(null)
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
confirmPassword: "",
city: "",
preferences: "",
})
const [profileImage, setProfileImage] = useState<string | null>(null)
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState("")
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
}
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
if (e.target?.result) {
setProfileImage(e.target.result as string)
}
}
reader.readAsDataURL(file)
}
}
const handleSignUp = (e: React.FormEvent) => {
e.preventDefault()
// Simple validation
if (!formData.name || !formData.email || !formData.password) {
setError("Please fill in all required fields")
return
}
if (formData.password !== formData.confirmPassword) {
setError("Passwords do not match")
return
}
// In a real app, you would send this data to a backend
// For demo purposes, we'll just simulate a successful registration
localStorage.setItem("isLoggedIn", "true")
router.push("/events")
}
return (
<div className="min-h-screen flex flex-col">
<header className="bg-black text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
</header>
<main className="flex-1 flex items-center justify-center p-6">
<div className="w-full max-w-md">
<div className="border rounded-md p-6">
<h2 className="text-2xl font-bold mb-6">Sign Up</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>
)}
<form onSubmit={handleSignUp} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Enter your full name"
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="Enter your email"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<div className="relative">
<Input
id="password"
name="password"
type={showPassword ? "text" : "password"}
value={formData.password}
onChange={handleChange}
placeholder="Create a password"
/>
<button
type="button"
className="absolute right-3 top-1/2 transform -translate-y-1/2"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-gray-500" />
) : (
<Eye className="h-4 w-4 text-gray-500" />
)}
</button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
placeholder="Confirm your password"
/>
</div>
<div className="space-y-2">
<Label htmlFor="city">City/Location</Label>
<Select>
<SelectTrigger id="city">
<SelectValue placeholder="Select your city" />
</SelectTrigger>
<SelectContent>
<SelectItem value="bucharest">Bucharest</SelectItem>
<SelectItem value="cluj">Cluj-Napoca</SelectItem>
<SelectItem value="timisoara">Timisoara</SelectItem>
<SelectItem value="iasi">Iasi</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<Label htmlFor="preferences">Preferences</Label>
<span className="text-xs text-blue-600 cursor-pointer">optional</span>
</div>
<Input
id="preferences"
name="preferences"
value={formData.preferences}
onChange={handleChange}
placeholder="Music, Sports, Theater, etc."
/>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<Label>Profile Picture</Label>
<span className="text-xs text-blue-600 cursor-pointer">optional</span>
</div>
<div className="flex flex-col items-center space-y-4">
<div className="w-24 h-24 rounded-full bg-gray-200 overflow-hidden relative">
{profileImage ? (
<Image
src={profileImage || "/placeholder.svg"}
alt="Profile preview"
fill
className="object-cover"
/>
) : (
<div className="flex items-center justify-center h-full">
<Upload className="h-8 w-8 text-gray-400" />
</div>
)}
</div>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
className="hidden"
accept="image/*"
/>
<Button type="button" variant="outline" size="sm" onClick={() => fileInputRef.current?.click()}>
Upload ID
</Button>
</div>
</div>
<div className="pt-4">
<Button type="submit" className="w-full bg-gray-800 hover:bg-black">
Sign Up
</Button>
</div>
<div className="text-xs text-center text-gray-500 mt-4">
By creating an account, you agree to our{" "}
<Link href="#" className="text-blue-600 hover:underline">
Terms of Service
</Link>{" "}
and{" "}
<Link href="#" className="text-blue-600 hover:underline">
Privacy Policy
</Link>
.
</div>
</form>
<div className="mt-4 text-center text-sm border-t pt-4">
<p>
Already have an account?{" "}
<Link href="/auth/signin" className="text-blue-600 hover:underline">
Log in here
</Link>
</p>
</div>
</div>
</div>
</main>
</div>
)
}
import Link from "next/link"
import Image from "next/image"
import { ArrowLeft, CreditCard, Calendar, Lock } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Separator } from "@/components/ui/separator"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
export default function CheckoutPage() {
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 w-full border-b bg-primary">
<div className="container flex h-16 items-center">
<Link href="/events/1" className="flex items-center gap-2">
<ArrowLeft className="h-5 w-5 text-primary-foreground" />
<span className="text-primary-foreground">Back to Event</span>
</Link>
</div>
</header>
<main className="flex-1 py-12">
<div className="container grid gap-12 px-4 md:grid-cols-2 md:px-6 lg:gap-16">
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold">Checkout</h1>
<p className="text-muted-foreground">Complete your purchase</p>
</div>
<div className="space-y-4">
<h2 className="text-xl font-semibold">Contact Information</h2>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="Enter your email" />
</div>
<div className="grid gap-2">
<Label htmlFor="phone">Phone</Label>
<Input id="phone" type="tel" placeholder="Enter your phone number" />
</div>
</div>
</div>
<Separator />
<div className="space-y-4">
<h2 className="text-xl font-semibold">Payment Method</h2>
<RadioGroup defaultValue="card" className="grid gap-4">
<div className="flex items-center space-x-2">
<RadioGroupItem value="card" id="card" />
<Label htmlFor="card" className="flex items-center gap-2">
<CreditCard className="h-4 w-4" />
Credit or Debit Card
</Label>
</div>
<Card>
<CardContent className="p-4">
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="card-number">Card Number</Label>
<div className="relative">
<Input id="card-number" placeholder="1234 5678 9012 3456" />
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1">
<Image src="/placeholder.svg?height=20&width=30" alt="Visa" width={30} height={20} />
<Image src="/placeholder.svg?height=20&width=30" alt="Mastercard" width={30} height={20} />
<Image src="/placeholder.svg?height=20&width=30" alt="Amex" width={30} height={20} />
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="expiry">Expiry Date</Label>
<div className="relative">
<Input id="expiry" placeholder="MM/YY" />
<Calendar className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="cvc">CVC</Label>
<div className="relative">
<Input id="cvc" placeholder="123" />
<Lock className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="name">Name on Card</Label>
<Input id="name" placeholder="Enter the name on your card" />
</div>
</div>
</CardContent>
</Card>
<div className="flex items-center space-x-2">
<RadioGroupItem value="paypal" id="paypal" />
<Label htmlFor="paypal" className="flex items-center gap-2">
<Image src="/placeholder.svg?height=20&width=60" alt="PayPal" width={60} height={20} />
PayPal
</Label>
</div>
</RadioGroup>
</div>
<Separator />
<div className="space-y-4">
<h2 className="text-xl font-semibold">Billing Address</h2>
<div className="grid gap-4">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="first-name">First Name</Label>
<Input id="first-name" placeholder="Enter your first name" />
</div>
<div className="grid gap-2">
<Label htmlFor="last-name">Last Name</Label>
<Input id="last-name" placeholder="Enter your last name" />
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="address">Address</Label>
<Input id="address" placeholder="Enter your address" />
</div>
<div className="grid gap-2">
<Label htmlFor="city">City</Label>
<Input id="city" placeholder="Enter your city" />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="state">State</Label>
<Select>
<SelectTrigger id="state">
<SelectValue placeholder="Select state" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ny">New York</SelectItem>
<SelectItem value="ca">California</SelectItem>
<SelectItem value="tx">Texas</SelectItem>
<SelectItem value="fl">Florida</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="zip">ZIP Code</Label>
<Input id="zip" placeholder="Enter your ZIP code" />
</div>
</div>
</div>
</div>
</div>
<div>
<Card className="sticky top-24">
<CardHeader>
<CardTitle>Order Summary</CardTitle>
<CardDescription>Review your order details</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-start gap-4">
<div className="relative aspect-[3/2] w-24 overflow-hidden rounded-md">
<Image
src="/placeholder.svg?height=200&width=300"
alt="Event thumbnail"
fill
className="object-cover"
/>
</div>
<div className="flex-1 space-y-1">
<h3 className="font-semibold">Summer Music Festival</h3>
<p className="text-sm text-muted-foreground">Aug 15-17, 2023</p>
<p className="text-sm text-muted-foreground">Central Park, New York</p>
</div>
</div>
<Separator />
<div className="space-y-2">
<div className="flex justify-between">
<span>Standard Ticket</span>
<span>$89.99</span>
</div>
<div className="flex justify-between">
<span>Quantity</span>
<span>2</span>
</div>
<div className="flex justify-between text-muted-foreground">
<span>Subtotal</span>
<span>$179.98</span>
</div>
<div className="flex justify-between text-muted-foreground">
<span>Service fee</span>
<span>$18.00</span>
</div>
<div className="flex justify-between text-muted-foreground">
<span>Tax</span>
<span>$15.84</span>
</div>
</div>
<Separator />
<div className="flex justify-between font-bold">
<span>Total</span>
<span>$213.82</span>
</div>
</CardContent>
<CardFooter className="flex flex-col gap-4">
<Button className="w-full">Complete Purchase</Button>
<p className="text-center text-xs text-muted-foreground">
By completing this purchase, you agree to our{" "}
<Link href="#" className="underline underline-offset-2">
Terms of Service
</Link>{" "}
and{" "}
<Link href="#" className="underline underline-offset-2">
Privacy Policy
</Link>
.
</p>
</CardFooter>
</Card>
</div>
</div>
</main>
<footer className="border-t py-6">
<div className="container flex flex-col items-center justify-between gap-4 px-4 md:flex-row md:px-6">
<p className="text-center text-sm text-muted-foreground md:text-left">
© 2023 EventTix. All rights reserved.
</p>
<div className="flex gap-4">
<Link href="#" className="text-sm text-muted-foreground hover:underline">
Terms
</Link>
<Link href="#" className="text-sm text-muted-foreground hover:underline">
Privacy
</Link>
<Link href="#" className="text-sm text-muted-foreground hover:underline">
Contact
</Link>
</div>
</div>
</footer>
</div>
)
}
"use client"
import { useState, useEffect, useCallback } from "react"
import Image from "next/image"
import { MapPin, Calendar, Clock, User, Heart } from "lucide-react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { useTrackedEvents } from "@/context/tracked-events-context"
import { usePurchasedTickets } from "@/context/purchased-tickets-context"
import { BackButton } from "@/components/back-button"
// Define event data outside the component to avoid recreation on each render
const eventData = {
"untold-2025": {
id: "untold-2025",
title: "Tickets to Untold Romania 2025",
description:
"The UNTOLD Festival, Romania's largest electronic music event, is gearing up for its monumental 10th anniversary edition, dubbed UNTOLD X, scheduled from August 7 to 10, 2025, in Cluj-Napoca, Transylvania. UNTOLD X promises an extraordinary celebration of music, love, friendship, and unity. Attendees can anticipate an impressive lineup of top DJs and live acts spanning genres like EDM, house, techno, hip-hop, and pop. The festival is renowned for its breathtaking stage designs, immersive experiences, and a variety of activities beyond music, including VR games, fashion showcases, and diverse culinary offerings.",
priceRange: "50€ - 300€",
location: "Cluj-Napoca, Romania",
date: "August 7-10, 2025",
time: "12:00 PM - 11:00 PM",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
},
"mario-casa": {
id: "mario-casa",
title: "Meet & Greet feat. Mario Casa",
description:
"Join us for an exclusive meet and greet with the famous actor Mario Casa. This is your chance to meet the star in person, take photos, and get autographs. The event will include a Q&A session where fans can ask questions directly to Mario.",
priceRange: "15€ - 100€",
location: "Jaen, Spain",
date: "February 25, 2025",
time: "6:00 PM - 9:00 PM",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
},
beerpong: {
id: "beerpong",
title: "Beerpong Party",
description:
"Get ready for the ultimate Beerpong Party at Comedy Club in London! Join us for a night of fun, competition, and great drinks. Whether you're a beerpong pro or a first-timer, this event is perfect for everyone looking to have a good time.",
priceRange: "5€",
location: "Comedy Club, London",
date: "May 1, 2025",
time: "8:00 PM - 2:00 AM",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
},
"gaming-con": {
id: "gaming-con",
title: "Gaming Convention 2025",
description:
"The biggest gaming event of the year is coming to Berlin! Experience the latest games, meet developers, participate in tournaments, and connect with fellow gamers from around the world.",
priceRange: "25€ - 75€",
location: "Berlin, Germany",
date: "March 15-17, 2025",
time: "10:00 AM - 8:00 PM",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
},
"summer-music": {
id: "summer-music",
title: "Summer Music Festival",
description:
"Celebrate summer with the hottest music festival in Barcelona! Featuring top artists across multiple genres, food vendors, art installations, and more in a beautiful outdoor setting.",
priceRange: "40€ - 120€",
location: "Barcelona, Spain",
date: "June 20-22, 2025",
time: "2:00 PM - 1:00 AM",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
},
}
// Default event for when no match is found
const defaultEvent = {
id: "unknown",
title: "Event Not Found",
description: "The event you're looking for could not be found.",
priceRange: "N/A",
location: "Unknown",
date: "Unknown",
time: "Unknown",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
}
export default function EventDetailPage({ params }: { params: { id: string } }) {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
const [event, setEvent] = useState(defaultEvent)
const { addTrackedEvent, removeTrackedEvent, isEventTracked } = useTrackedEvents()
const [isTracked, setIsTracked] = useState(false)
const { hasTicketForEvent } = usePurchasedTickets()
const [hasTicket, setHasTicket] = useState(false)
// Get event data based on id - only run once on mount and when params.id changes
useEffect(() => {
// Find the event by id
const foundEvent = eventData[params.id as keyof typeof eventData] || defaultEvent
setEvent(foundEvent)
// Check if event is tracked
setIsTracked(isEventTracked(params.id))
// Check if user has a ticket for this event
setHasTicket(hasTicketForEvent(params.id))
// Check authentication
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
} else {
setIsLoading(false)
}
} catch (err) {
console.error("Error checking authentication:", err)
setIsLoading(false)
}
}, [params.id, router, isEventTracked, hasTicketForEvent])
const handleBuyTicket = useCallback(() => {
router.push(`/tickets/${event.id}`)
}, [router, event.id])
const handleViewTicket = useCallback(() => {
router.push(`/tickets/${event.id}`)
}, [router, event.id])
const handleTrackEvent = useCallback(() => {
if (isTracked) {
removeTrackedEvent(event.id)
setIsTracked(false)
alert("Event removed from tracked events")
} else {
addTrackedEvent({
id: event.id,
title: event.title,
priceRange: event.priceRange,
location: event.location,
date: event.date,
image: event.image,
})
setIsTracked(true)
alert("Event added to tracked events")
}
}, [isTracked, removeTrackedEvent, addTrackedEvent, event, setIsTracked])
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return (
<div className="min-h-screen flex flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-bold mb-6">{event.title}</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
<div className="md:col-span-2">
{/* Main Image */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
</div>
{/* Map View */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.mapImage || "/placeholder.svg"}
alt="Event location map"
fill
className="object-cover"
/>
<div className="absolute bottom-2 left-2 bg-black text-white p-1 rounded-full">
<MapPin className="h-5 w-5" />
</div>
</div>
{/* Description */}
<div className="mb-6">
<p className="text-sm leading-relaxed">{event.description}</p>
</div>
{/* Event Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div className="flex items-center gap-2">
<Calendar className="h-5 w-5 text-gray-500" />
<span>{event.date}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="h-5 w-5 text-gray-500" />
<span>{event.time}</span>
</div>
<div className="flex items-center gap-2">
<MapPin className="h-5 w-5 text-gray-500" />
<span>{event.location}</span>
</div>
<div className="flex items-center gap-2">
<User className="h-5 w-5 text-gray-500" />
<span>All ages welcome</span>
</div>
</div>
</div>
<div>
<Card className="mb-4">
<CardContent className="p-4">
<div className="mb-4">
<div className="text-lg font-semibold">Price: {event.priceRange}</div>
</div>
{hasTicket ? (
<Button className="w-full bg-green-600 hover:bg-green-700 mb-2" onClick={handleViewTicket}>
View Ticket
</Button>
) : (
<Button className="w-full bg-black hover:bg-gray-800 mb-2" onClick={handleBuyTicket}>
Buy Ticket
</Button>
)}
<Button
variant={isTracked ? "default" : "outline"}
className={`w-full ${isTracked ? "bg-pink-600 hover:bg-pink-700" : ""}`}
onClick={handleTrackEvent}
>
<Heart className={`h-4 w-4 mr-2 ${isTracked ? "fill-white" : ""}`} />
{isTracked ? "Untrack Event" : "Track Event"}
</Button>
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</div>
)
}
export default function Loading() {
return null
}
"use client"
import { useState, useEffect, useCallback } from "react"
import { Calendar, Filter, Menu, Search, User } from "lucide-react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Slider } from "@/components/ui/slider"
import { Checkbox } from "@/components/ui/checkbox"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import EventCard from "@/components/event-card"
import { MenuPopup } from "@/components/menu-popup"
import { usePurchasedTickets } from "@/context/purchased-tickets-context"
// Define all events
const allEvents = [
{
title: "Tickets to Untold Romania 2025",
priceRange: "50€ - 300€",
location: "Cluj-Napoca Romania",
date: "Time Period: 7 - 10 August",
image: "/placeholder.svg?height=200&width=200",
id: "untold-2025",
category: "festivals",
minPrice: 50,
maxPrice: 300,
hasVeganFood: true,
hasAccessibleParking: true,
hasAgeRestriction: false,
},
{
title: "Meet & Greet feat. Mario Casa",
priceRange: "15€ - 100€",
location: "Jaen, Spain",
date: "Time: 25th of February",
image: "/placeholder.svg?height=200&width=200",
id: "mario-casa",
category: "meetups",
minPrice: 15,
maxPrice: 100,
hasVeganFood: false,
hasAccessibleParking: true,
hasAgeRestriction: false,
},
{
title: "Beerpong Party",
priceRange: "5€",
location: "Comedy Club, London",
date: "Time: 1st of May",
image: "/placeholder.svg?height=200&width=200",
id: "beerpong",
category: "parties",
minPrice: 5,
maxPrice: 5,
hasVeganFood: false,
hasAccessibleParking: false,
hasAgeRestriction: true,
},
{
title: "Gaming Convention 2025",
priceRange: "25€ - 75€",
location: "Berlin, Germany",
date: "Time: 15-17 March",
image: "/placeholder.svg?height=200&width=200",
id: "gaming-con",
category: "conventions",
minPrice: 25,
maxPrice: 75,
hasVeganFood: true,
hasAccessibleParking: true,
hasAgeRestriction: false,
},
{
title: "Summer Music Festival",
priceRange: "40€ - 120€",
location: "Barcelona, Spain",
date: "Time: 20-22 June",
image: "/placeholder.svg?height=200&width=200",
id: "summer-music",
category: "festivals",
minPrice: 40,
maxPrice: 120,
hasVeganFood: true,
hasAccessibleParking: true,
hasAgeRestriction: false,
},
]
export default function EventsPage() {
const router = useRouter()
const { hasTicketForEvent } = usePurchasedTickets()
const [priceRange, setPriceRange] = useState([50])
const [menuOpen, setMenuOpen] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [filteredEvents, setFilteredEvents] = useState(allEvents)
// Filter states
const [selectedCategory, setSelectedCategory] = useState("all")
const [searchLocation, setSearchLocation] = useState("")
const [searchDate, setSearchDate] = useState("")
const [veganFood, setVeganFood] = useState(false)
const [accessibleParking, setAccessibleParking] = useState(false)
const [ageRestriction, setAgeRestriction] = useState(false)
// Check if user is logged in - only run once on mount
useEffect(() => {
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
} else {
setIsLoading(false)
}
} catch (err) {
console.error("Error checking authentication:", err)
setIsLoading(false)
}
}, [router])
// Apply filters whenever filter criteria change
useEffect(() => {
let result = allEvents
// Filter by category
if (selectedCategory !== "all") {
result = result.filter((event) => event.category === selectedCategory)
}
// Filter by price
result = result.filter((event) => event.minPrice <= priceRange[0])
// Filter by location
if (searchLocation) {
result = result.filter((event) => event.location.toLowerCase().includes(searchLocation.toLowerCase()))
}
// Filter by date
if (searchDate) {
result = result.filter((event) => event.date.toLowerCase().includes(searchDate.toLowerCase()))
}
// Filter by vegan food
if (veganFood) {
result = result.filter((event) => event.hasVeganFood)
}
// Filter by accessible parking
if (accessibleParking) {
result = result.filter((event) => event.hasAccessibleParking)
}
// Filter by age restriction
if (ageRestriction) {
result = result.filter((event) => event.hasAgeRestriction)
}
setFilteredEvents(result)
}, [selectedCategory, priceRange, searchLocation, searchDate, veganFood, accessibleParking, ageRestriction])
const handleBuyTicket = useCallback(
(eventId: string) => {
router.push(`/tickets/${eventId}`)
},
[router],
)
const handleViewEvent = useCallback(
(eventId: string) => {
router.push(`/events/${eventId}`)
},
[router],
)
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return (
<div className="flex min-h-screen flex-col">
{/* Header */}
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<div className="w-10"></div> {/* Spacer for centering */}
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon" className="text-white" onClick={() => setMenuOpen(!menuOpen)}>
<Menu className="h-5 w-5" />
</Button>
<Link href="/account">
<Button variant="ghost" size="icon" className="text-white">
<User className="h-5 w-5" />
</Button>
</Link>
</div>
</div>
</header>
{/* Menu Popup */}
{menuOpen && (
<MenuPopup
onClose={() => setMenuOpen(false)}
onLogout={() => {
localStorage.removeItem("isLoggedIn")
router.push("/auth/signin")
}}
/>
)}
<div className="flex flex-1">
{/* Sidebar */}
<aside className="w-[220px] bg-zinc-700 text-white">
<div className="p-4 bg-zinc-600">
<div className="flex items-center gap-2">
<Filter className="h-4 w-4" />
<span className="text-sm">Filter by...</span>
</div>
</div>
<div className="p-4 border-b border-zinc-600">
<label className="text-sm mb-2 block">Type of event</label>
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
<SelectTrigger className="bg-white text-black">
<SelectValue placeholder="All events" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All events</SelectItem>
<SelectItem value="festivals">Festivals</SelectItem>
<SelectItem value="concerts">Concerts</SelectItem>
<SelectItem value="parties">Parties</SelectItem>
<SelectItem value="conventions">Conventions</SelectItem>
<SelectItem value="meetups">Meet & Greets</SelectItem>
</SelectContent>
</Select>
</div>
<div className="p-4 border-b border-zinc-600">
<label className="text-sm mb-2 block">Price range (€)</label>
<div className="space-y-4">
<Slider
defaultValue={[50]}
max={300}
step={5}
value={priceRange}
onValueChange={setPriceRange}
className="py-4"
/>
<div className="flex justify-between text-sm">
<span>€0</span>
<span>{priceRange[0]}</span>
<span>€300</span>
</div>
</div>
</div>
<div className="p-4 space-y-4">
<div className="flex items-center space-x-2">
<Checkbox
id="vegan"
checked={veganFood}
onCheckedChange={(checked) => setVeganFood(checked as boolean)}
/>
<label htmlFor="vegan" className="text-sm">
Vegan food choices
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="parking"
checked={accessibleParking}
onCheckedChange={(checked) => setAccessibleParking(checked as boolean)}
/>
<label htmlFor="parking" className="text-sm">
Parking spaces for disabilities
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="age"
checked={ageRestriction}
onCheckedChange={(checked) => setAgeRestriction(checked as boolean)}
/>
<label htmlFor="age" className="text-sm">
Age restriction
</label>
</div>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 p-6 overflow-auto">
{/* Search Filters */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
<div className="relative">
<Input
placeholder="Choose location..."
className="pl-10"
value={searchLocation}
onChange={(e) => setSearchLocation(e.target.value)}
/>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
<div className="relative">
<Input
placeholder="Choose starting date..."
className="pl-10"
value={searchDate}
onChange={(e) => setSearchDate(e.target.value)}
/>
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
</div>
{/* Popular Events Section */}
<div className="mb-6">
<Card className="mb-6">
<CardContent className="p-4">
<h2 className="text-lg font-semibold">
{filteredEvents.length > 0
? `Found ${filteredEvents.length} events matching your criteria`
: "No events match your current filters"}
</h2>
</CardContent>
</Card>
{filteredEvents.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">No events match your current filters.</p>
<Button
onClick={() => {
setPriceRange([50])
setSelectedCategory("all")
setSearchLocation("")
setSearchDate("")
setVeganFood(false)
setAccessibleParking(false)
setAgeRestriction(false)
}}
>
Reset Filters
</Button>
</div>
) : (
<div className="space-y-4 max-h-[calc(100vh-250px)] overflow-y-auto pr-2">
{filteredEvents.map((event, index) => {
const hasTicket = hasTicketForEvent(event.id)
return (
<div key={index} className="relative">
<EventCard
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
id={event.id}
/>
<div className="absolute bottom-4 right-4">
{!hasTicket ? (
<Button
size="sm"
className="bg-black hover:bg-gray-800"
onClick={() => handleBuyTicket(event.id)}
>
Buy Ticket
</Button>
) : (
<Button
size="sm"
className="bg-green-600 hover:bg-green-700"
onClick={() => handleViewEvent(event.id)}
>
View Event
</Button>
)}
</div>
</div>
)
})}
</div>
)}
</div>
</main>
</div>
</div>
)
}
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import type React from "react"
import type { Metadata } from "next"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { TrackedEventsProvider } from "@/context/tracked-events-context"
import { Toaster } from "@/components/toaster"
import { PurchasedTicketsProvider } from "@/context/purchased-tickets-context"
import { NavigationProvider } from "@/context/navigation-context"
export const metadata: Metadata = {
title: "EventTicket App",
description: "Book tickets for your favorite events",
generator: "v0.dev",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body suppressHydrationWarning>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem>
<NavigationProvider>
<TrackedEventsProvider>
<PurchasedTicketsProvider>
{children}
<Toaster />
</PurchasedTicketsProvider>
</TrackedEventsProvider>
</NavigationProvider>
</ThemeProvider>
</body>
</html>
)
}
import './globals.css'
\ No newline at end of file
export default function Loading() {
return null
}
"use client"
import { useEffect } from "react"
import { useRouter } from "next/navigation"
export default function LogoutPage() {
const router = useRouter()
useEffect(() => {
// In a real app, you would handle logout logic here
// For now, we'll just redirect to the home page
setTimeout(() => {
router.push("/")
}, 1000)
}, [router])
return (
<div className="flex min-h-screen flex-col items-center justify-center">
<h1 className="text-2xl font-bold mb-4">Logging out...</h1>
<p>You will be redirected shortly.</p>
</div>
)
}
"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { usePurchasedTickets } from "@/context/purchased-tickets-context"
import EventCard from "@/components/event-card"
import { Button } from "@/components/ui/button"
import { BackButton } from "@/components/back-button"
export default function MyTicketsPage() {
const { purchasedTickets } = usePurchasedTickets()
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Check authentication
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
} else {
setIsLoading(false)
}
} catch (err) {
console.error("Error checking authentication:", err)
setIsLoading(false)
}
}, [router])
const handleViewTicket = (eventId: string) => {
router.push(`/tickets/${eventId}`)
}
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return (
<div className="flex min-h-screen flex-col">
{/* Header */}
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">My Tickets</h1>
{purchasedTickets.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't purchased any tickets yet.</p>
<Link href="/events">
<span className="text-blue-600 hover:underline">Browse events</span>
</Link>
</div>
) : (
<div className="space-y-4">
{purchasedTickets.map((ticket) => (
<div key={ticket.id} className="relative">
<EventCard
title={ticket.eventName}
priceRange={ticket.price}
location={ticket.location}
date={`Time: ${ticket.eventDate}`}
image={ticket.image}
id={ticket.eventId}
/>
<div className="absolute bottom-4 right-4">
<Button size="sm" className="bg-black hover:bg-gray-800" onClick={() => handleViewTicket(ticket.id)}>
View Ticket
</Button>
</div>
</div>
))}
</div>
)}
</main>
</div>
)
}
"use client"
import { useEffect, useState } from "react"
import { useRouter } from "next/navigation"
export default function Home() {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
try {
// Check if user is logged in
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn === "true") {
// If logged in, go to events page
router.push("/events")
} else {
// If not logged in, redirect to sign in page
router.push("/auth/signin")
}
} catch (err) {
console.error("Error accessing localStorage:", err)
// Fallback for environments where localStorage is not available
router.push("/auth/signin")
} finally {
setIsLoading(false)
}
}, [router])
// Show a simple loading state
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return null // This page just redirects
}
"use client"
import { useState } from "react"
import Image from "next/image"
import { MapPin, Star } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Textarea } from "@/components/ui/textarea"
import { BackButton } from "@/components/back-button"
export default function PastEventPage({ params }: { params: { slug: string } }) {
const [rating, setRating] = useState(0)
const [review, setReview] = useState("")
// This would normally come from a database or API
const event = {
title: "Untold Festival 2025",
description:
"The UNTOLD Festival, Romania's largest electronic music event, is gearing up for its monumental 10th anniversary edition, dubbed UNTOLD X, scheduled from August 7 to 10, 2025, in Cluj-Napoca, Transylvania. UNTOLD X promises an extraordinary celebration of music, love, friendship, and unity. Attendees can anticipate an impressive lineup of top DJs and live acts spanning genres like EDM, house, techno, hip-hop, and pop. The festival is renowned for its breathtaking stage designs, immersive experiences, and a variety of activities beyond music, including VR games, fashion showcases, and diverse culinary offerings.",
location: "Cluj-Napoca, Romania",
date: "August 7-10, 2025",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
reviews: [
{
name: "Adrian Ioaniche",
rating: 4,
comment: "Had fun I guess. I'll be back next year if the artists are nice and all.",
},
{
name: "Smeker Becher",
rating: 5,
comment: "The event was amazing. 5 out of 5.",
},
],
}
const handleSubmitReview = () => {
alert("Review submitted!")
setRating(0)
setReview("")
}
return (
<div className="flex min-h-screen flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-bold mb-6">{event.title}</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
<div className="md:col-span-2">
{/* Map View */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.mapImage || "/placeholder.svg"}
alt="Event location map"
fill
className="object-cover"
/>
<div className="absolute bottom-2 left-2 bg-black text-white p-1 rounded-full">
<MapPin className="h-5 w-5" />
</div>
</div>
{/* Gallery */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.galleryImages[0] || "/placeholder.svg"}
alt="Event gallery"
fill
className="object-cover"
/>
</div>
{/* Description */}
<div className="mb-6">
<p className="text-sm leading-relaxed">{event.description}</p>
</div>
{/* Reviews Section */}
<div className="mb-6">
<h2 className="text-xl font-bold mb-4">Reviews ({event.reviews.length}):</h2>
<div className="space-y-4 mb-6">
{event.reviews.map((review, index) => (
<Card key={index}>
<CardContent className="p-4">
<div className="flex justify-between items-start mb-2">
<h3 className="font-semibold">{review.name}</h3>
<div className="flex">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-4 w-4 ${i < review.rating ? "fill-yellow-400 text-yellow-400" : "text-gray-300"}`}
/>
))}
</div>
</div>
<p className="text-sm">{review.comment}</p>
</CardContent>
</Card>
))}
</div>
{/* Leave a Review */}
<div>
<h3 className="text-lg font-semibold mb-3">Leave a review:</h3>
<div className="flex justify-center mb-4">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-8 w-8 cursor-pointer ${i < rating ? "fill-yellow-400 text-yellow-400" : "text-gray-300"}`}
onClick={() => setRating(i + 1)}
/>
))}
</div>
<Textarea
placeholder="Type a comment..."
className="mb-4"
value={review}
onChange={(e) => setReview(e.target.value)}
/>
<div className="flex justify-end">
<Button className="bg-black hover:bg-gray-800" onClick={handleSubmitReview}>
Post
</Button>
</div>
</div>
</div>
</div>
<div>
<Card className="mb-4">
<CardContent className="p-4">
<div className="mb-4">
<div className="font-semibold mb-2">Date:</div>
<div className="mb-4">
<div className="bg-gray-100 p-2 rounded">
<div className="text-center border-b pb-1 mb-2">February 11, 2025</div>
<div className="grid grid-cols-7 text-center text-xs">
<div>Su</div>
<div>Mo</div>
<div>Tu</div>
<div>We</div>
<div>Th</div>
<div>Fr</div>
<div>Sa</div>
</div>
<div className="grid grid-cols-7 text-center text-xs gap-1 mt-1">
{[...Array(28)].map((_, i) => (
<div key={i} className={`p-1 ${i + 1 === 11 ? "bg-black text-white rounded-full" : ""}`}>
{i + 1}
</div>
))}
</div>
</div>
</div>
<div className="font-semibold mb-2">Timetable:</div>
<div className="flex gap-2 mb-4">
<div className="flex-1 border rounded p-2 text-center text-sm">02/11/2025</div>
<div className="flex-1 border rounded p-2 text-center text-sm">02/11/2025</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import { EventCard } from "@/components/event-card"
import { BackButton } from "@/components/back-button"
export default function PastEventsPage() {
const events = [
{
title: "Summer Music Festival",
priceRange: "40€ - 120€",
location: "Barcelona, Spain",
date: "Time: 20-22 June 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "summer-music-festival",
},
{
title: "International Food Fair",
priceRange: "10€",
location: "Paris, France",
date: "Time: 5-7 April 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "international-food-fair",
},
{
title: "Untold Festival 2023",
priceRange: "50€ - 300€",
location: "Cluj-Napoca, Romania",
date: "Time: 3-6 August 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "untold-festival-2023",
},
]
return (
<div className="flex min-h-screen flex-col">
{/* Header */}
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">Past Events</h1>
<div className="space-y-4">
{events.map((event, index) => (
<div
key={index}
onClick={() => (window.location.href = `/past-events/${event.slug}`)}
className="cursor-pointer"
>
<EventCard
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
/>
</div>
))}
</div>
</main>
</div>
)
}
"use client"
import { useEffect, useState, useCallback } from "react"
import { Download, Share, RefreshCcw } from "lucide-react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { usePurchasedTickets } from "@/context/purchased-tickets-context"
import { BackButton } from "@/components/back-button"
// Define ticket data outside the component
const ticketDataMap: Record<string, any> = {
"untold-2025": {
eventName: "Untold Festival 2025",
description:
"The UNTOLD Festival, Romania's largest electronic music event, is gearing up for its monumental 10th anniversary edition.",
ticketType: "General Admission",
price: "50€",
eventDate: "August 7-10, 2025",
location: "Cluj-Napoca, Romania",
},
"mario-casa": {
eventName: "Meet & Greet feat. Mario Casa",
description: "Join us for an exclusive meet and greet with the famous actor Mario Casa.",
ticketType: "VIP Access",
price: "15€",
eventDate: "February 25, 2025",
location: "Jaen, Spain",
},
beerpong: {
eventName: "Beerpong Party",
description: "Get ready for the ultimate Beerpong Party at Comedy Club in London!",
ticketType: "Standard Entry",
price: "5€",
eventDate: "May 1, 2025",
location: "Comedy Club, London",
},
"gaming-con": {
eventName: "Gaming Convention 2025",
description: "The biggest gaming event of the year is coming to Berlin!",
ticketType: "Weekend Pass",
price: "25€",
eventDate: "March 15-17, 2025",
location: "Berlin, Germany",
},
"summer-music": {
eventName: "Summer Music Festival",
description: "Celebrate summer with the hottest music festival in Barcelona!",
ticketType: "Day Pass",
price: "40€",
eventDate: "June 20-22, 2025",
location: "Barcelona, Spain",
},
}
export default function TicketPage({ params }: { params: { id: string } }) {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
const { addPurchasedTicket, removePurchasedTicket } = usePurchasedTickets()
const [ticketId, setTicketId] = useState("")
const [eventName, setEventName] = useState("Event Not Found")
const [description, setDescription] = useState("The ticket you're looking for could not be found.")
const [ticketType, setTicketType] = useState("Unknown")
const [price, setPrice] = useState("N/A")
const [purchaseDate, setPurchaseDate] = useState("")
const [showQR, setShowQR] = useState(false)
const [ticketAdded, setTicketAdded] = useState(false)
// Load ticket data only once on mount and when params.id changes
useEffect(() => {
// Check authentication
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Set ticket ID
setTicketId(params.id)
// Set purchase date - use a fixed date to avoid hydration issues
setPurchaseDate("April 6, 2025")
// Get ticket data
const eventData = ticketDataMap[params.id as keyof typeof ticketDataMap]
if (eventData) {
setEventName(eventData.eventName)
setDescription(eventData.description)
setTicketType(eventData.ticketType)
setPrice(eventData.price)
}
setIsLoading(false)
// Set showQR to true after initial render to avoid hydration issues
setTimeout(() => {
setShowQR(true)
}, 100)
} catch (err) {
console.error("Error loading ticket:", err)
setIsLoading(false)
}
}, [params.id, router])
// Add ticket to purchased tickets - only run once
useEffect(() => {
if (!isLoading && !ticketAdded) {
const eventData = ticketDataMap[params.id as keyof typeof ticketDataMap]
if (eventData) {
addPurchasedTicket({
id: params.id,
eventId: params.id,
eventName: eventData.eventName,
description: eventData.description,
ticketType: eventData.ticketType,
price: eventData.price,
purchaseDate: "April 6, 2025",
eventDate: eventData.eventDate || "Upcoming",
location: eventData.location || "TBD",
image: "/placeholder.svg?height=200&width=200",
})
setTicketAdded(true)
}
}
}, [isLoading, ticketAdded, params.id, addPurchasedTicket])
const handleDownloadTicket = useCallback(() => {
alert("Ticket download functionality would be implemented here")
}, [])
const handleShareTicket = useCallback(() => {
alert("Ticket sharing functionality would be implemented here")
}, [])
const handleRefundTicket = useCallback(() => {
const confirmRefund = confirm("Are you sure you want to refund this ticket?")
if (confirmRefund) {
removePurchasedTicket(ticketId)
alert("Ticket has been refunded successfully")
router.push("/events")
}
}, [removePurchasedTicket, ticketId, router])
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return (
<div className="min-h-screen flex flex-col">
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div>
</div>
</header>
<main className="flex-1 p-6">
<div className="max-w-2xl mx-auto">
<Card className="overflow-hidden">
<CardContent className="p-6">
<h2 className="text-2xl font-bold mb-4">Ticket for {eventName}</h2>
<p className="text-sm mb-6">{description}</p>
<div className="flex justify-center mb-6">
<div className="p-4 bg-white rounded-lg">
{showQR && (
<div className="w-[150px] h-[150px] bg-gray-200 flex items-center justify-center">
<p className="text-sm text-center">
QR Code for
<br />
Ticket: {ticketId}
</p>
</div>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<p className="text-sm text-gray-500">Ticket ID</p>
<p className="font-medium">{ticketId}</p>
</div>
<div>
<p className="text-sm text-gray-500">Purchase Date</p>
<p className="font-medium">{purchaseDate}</p>
</div>
<div>
<p className="text-sm text-gray-500">Ticket Type</p>
<p className="font-medium">{ticketType}</p>
</div>
<div>
<p className="text-sm text-gray-500">Price</p>
<p className="font-medium">{price}</p>
</div>
</div>
<div className="flex gap-4">
<Button variant="outline" className="flex-1" onClick={handleDownloadTicket}>
<Download className="h-4 w-4 mr-2" />
Download
</Button>
<Button variant="outline" className="flex-1" onClick={handleShareTicket}>
<Share className="h-4 w-4 mr-2" />
Share
</Button>
<Button variant="destructive" className="flex-1" onClick={handleRefundTicket}>
<RefreshCcw className="h-4 w-4 mr-2" />
Refund
</Button>
</div>
</CardContent>
</Card>
</div>
</main>
</div>
)
}
"use client"
import { useEffect, useState } from "react"
import Link from "next/link"
import { EventCard } from "@/components/event-card"
import { useTrackedEvents } from "@/context/tracked-events-context"
import { useRouter } from "next/navigation"
import { BackButton } from "@/components/back-button"
export default function TrackedEventsPage() {
const { trackedEvents } = useTrackedEvents()
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// Check authentication
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
} else {
setIsLoading(false)
}
} catch (err) {
console.error("Error checking authentication:", err)
setIsLoading(false)
}
}, [router])
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">Loading...</div>
}
return (
<div className="flex min-h-screen flex-col">
{/* Header */}
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<BackButton />
<div className="flex items-center gap-4">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
<div className="w-10"></div> {/* Spacer for centering */}
</div>
</header>
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">Tracked Events</h1>
{trackedEvents.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't tracked any events yet.</p>
<Link href="/events">
<span className="text-blue-600 hover:underline">Browse events</span>
</Link>
</div>
) : (
<div className="space-y-4">
{trackedEvents.map((event) => (
<EventCard
key={event.id}
id={event.id}
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
/>
))}
</div>
)}
</main>
</div>
)
}
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
\ No newline at end of file
import Image from "next/image"
import Link from "next/link"
import { Edit, Trash2, MoreHorizontal } from "lucide-react"
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
export default function AdminEventList() {
const events = [
{
id: "1",
title: "Tickets to Untold Romania 2025",
priceRange: "70€ - 300€",
location: "Cluj Napoca, Romania",
timePeriod: "7 - 10 August",
ticketsSold: 450,
revenue: "€45,000",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "2",
title: "Meet & Greet feat. Mario Casa",
priceRange: "15€ - 100€",
location: "Jaen, Spain",
timePeriod: "25th of February",
ticketsSold: 120,
revenue: "€8,400",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "3",
title: "Beerpong Party",
priceRange: "5€",
location: "Comedy Club, London",
timePeriod: "1st of May",
ticketsSold: 75,
revenue: "€375",
image: "/placeholder.svg?height=100&width=100",
},
]
return (
<div className="space-y-4">
{events.map((event) => (
<Card key={event.id} className="overflow-hidden">
<CardContent className="p-0">
<div className="flex">
<div className="w-24 h-24 relative bg-gray-200">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
</div>
<div className="flex-1 p-4">
<div className="flex justify-between">
<h3 className="font-semibold text-lg">{event.title}</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Actions</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Link href={`/admin/events/${event.id}`} className="flex items-center w-full">
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="mt-1 text-sm">
<div className="flex flex-col sm:flex-row sm:gap-4">
<div>
<span className="font-medium">Ticket prices:</span> {event.priceRange}
</div>
<div>
<span className="font-medium">Tickets sold:</span> {event.ticketsSold}
</div>
<div>
<span className="font-medium">Revenue:</span> {event.revenue}
</div>
</div>
<div>
<span className="font-medium">Location:</span> {event.location}
</div>
<div>
<span className="font-medium">Time Period:</span> {event.timePeriod}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
)
}
"use client"
import { ArrowLeft } from "lucide-react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
export function BackButton() {
const router = useRouter()
const handleBack = () => {
// Use browser history to go back if possible
if (typeof window !== "undefined" && window.history.length > 1) {
router.back()
} else {
// Fallback to events page if no history
router.push("/events")
}
}
return (
<Button variant="ghost" size="icon" className="text-white" onClick={handleBack} aria-label="Go back">
<ArrowLeft className="h-5 w-5" />
</Button>
)
}
import Link from "next/link"
import { Music, Utensils, Trophy, Ticket, Palette, Mic, Landmark, Tent } from "lucide-react"
export function CategoryFilter() {
const categories = [
{
name: "Concerts",
icon: <Music className="h-6 w-6" />,
color: "bg-pink-100 text-pink-700",
href: "/events/category/concerts",
},
{
name: "Food & Drink",
icon: <Utensils className="h-6 w-6" />,
color: "bg-orange-100 text-orange-700",
href: "/events/category/food-drink",
},
{
name: "Sports",
icon: <Trophy className="h-6 w-6" />,
color: "bg-blue-100 text-blue-700",
href: "/events/category/sports",
},
{
name: "Theater",
icon: <Ticket className="h-6 w-6" />,
color: "bg-purple-100 text-purple-700",
href: "/events/category/theater",
},
{
name: "Art",
icon: <Palette className="h-6 w-6" />,
color: "bg-green-100 text-green-700",
href: "/events/category/art",
},
{
name: "Comedy",
icon: <Mic className="h-6 w-6" />,
color: "bg-yellow-100 text-yellow-700",
href: "/events/category/comedy",
},
{
name: "Conferences",
icon: <Landmark className="h-6 w-6" />,
color: "bg-red-100 text-red-700",
href: "/events/category/conferences",
},
{
name: "Festivals",
icon: <Tent className="h-6 w-6" />,
color: "bg-indigo-100 text-indigo-700",
href: "/events/category/festivals",
},
]
return (
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 gap-4">
{categories.map((category) => (
<Link
key={category.name}
href={category.href}
className="flex flex-col items-center justify-center p-4 rounded-lg hover:bg-muted transition-colors"
>
<div className={`p-3 rounded-full ${category.color} mb-2`}>{category.icon}</div>
<span className="text-sm font-medium text-center">{category.name}</span>
</Link>
))}
</div>
)
}
"use client"
import Image from "next/image"
import { usePathname } from "next/navigation"
import Link from "next/link"
import { Card, CardContent } from "@/components/ui/card"
interface EventCardProps {
title: string
priceRange: string
location: string
date: string
image: string
id?: string
}
export function EventCard({ title, priceRange, location, date, image, id }: EventCardProps) {
const pathname = usePathname()
const isPastEventsPage = pathname.includes("/past-events")
return (
<Card className="overflow-hidden">
<CardContent className="p-0">
<div className="flex flex-col md:flex-row">
<div className="w-full md:w-[150px] bg-gray-200 relative aspect-square md:aspect-auto">
<Image src={image || "/placeholder.svg"} alt={title} fill className="object-cover" />
</div>
<div className="p-4 flex-1">
{isPastEventsPage ? (
<h3 className="font-bold text-lg">{title}</h3>
) : (
<Link href={`/events/${id || "unknown"}`} className="hover:underline">
<h3 className="font-bold text-lg">{title}</h3>
</Link>
)}
<div className="mt-2 space-y-1 text-sm">
<p>
<span className="font-semibold">Ticket prices may vary:</span> {priceRange}
</p>
<p>
<span className="font-semibold">Location:</span> {location}
</p>
<p>
<span className="font-semibold">{date}</span>
</p>
</div>
</div>
</div>
</CardContent>
</Card>
)
}
export default EventCard
import Image from "next/image"
import Link from "next/link"
import { Card, CardContent } from "@/components/ui/card"
export default function EventList() {
const events = [
{
id: "1",
title: "Tickets to Untold Romania 2025",
priceRange: "70€ - 300€",
location: "Cluj Napoca, Romania",
timePeriod: "7 - 10 August",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "2",
title: "Meet & Greet feat. Mario Casa",
priceRange: "15€ - 100€",
location: "Jaen, Spain",
timePeriod: "25th of February",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "3",
title: "Beerpong Party",
priceRange: "5€",
location: "Comedy Club, London",
timePeriod: "1st of May",
image: "/placeholder.svg?height=100&width=100",
},
]
return (
<div className="space-y-4">
{events.map((event) => (
<Link key={event.id} href={`/events/${event.id}`}>
<Card className="overflow-hidden hover:shadow-md transition-shadow">
<CardContent className="p-0">
<div className="flex">
<div className="w-24 h-24 relative bg-gray-200">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
</div>
<div className="flex-1 p-4">
<h3 className="font-semibold text-lg">{event.title}</h3>
<div className="mt-1 text-sm">
<div className="flex flex-col sm:flex-row sm:gap-4">
<div>
<span className="font-medium">Ticket prices:</span> {event.priceRange}
</div>
</div>
<div>
<span className="font-medium">Location:</span> {event.location}
</div>
<div>
<span className="font-medium">Time Period:</span> {event.timePeriod}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import Image from "next/image"
import Link from "next/link"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { useMobile } from "@/hooks/use-mobile"
export function FeaturedEvents() {
const [current, setCurrent] = useState(0)
const isMobile = useMobile()
const featuredEvents = [
{
id: "1",
title: "Summer Music Festival",
description: "The biggest music festival of the year with top artists from around the world.",
date: "Aug 15-17, 2023",
location: "Central Park, New York",
price: 89.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Music",
},
{
id: "2",
title: "International Food Festival",
description: "Taste cuisines from over 50 countries in one amazing weekend event.",
date: "Aug 20-22, 2023",
location: "Pier 39, San Francisco",
price: 39.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Food",
},
{
id: "3",
title: "Tech Conference 2023",
description: "Join industry leaders and innovators at the premier tech event of the year.",
date: "Sep 5-7, 2023",
location: "Convention Center, San Francisco",
price: 199.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Conference",
},
]
const next = () => {
setCurrent((current + 1) % featuredEvents.length)
}
const previous = () => {
setCurrent((current - 1 + featuredEvents.length) % featuredEvents.length)
}
// Auto-advance carousel
useEffect(() => {
const timer = setTimeout(() => {
next()
}, 5000)
return () => clearTimeout(timer)
}, [current])
return (
<div className="relative">
<div className="overflow-hidden rounded-xl">
{featuredEvents.map((event, index) => (
<div
key={event.id}
className={`transition-all duration-500 ease-in-out ${index === current ? "block" : "hidden"}`}
>
<div className="relative">
<div className="relative aspect-[21/9] w-full overflow-hidden rounded-xl">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
</div>
<Card className="absolute bottom-4 left-4 right-4 md:bottom-8 md:left-8 md:right-auto md:max-w-md bg-background/80 backdrop-blur">
<CardContent className="p-4 md:p-6">
<div className="space-y-2">
<Badge>{event.category}</Badge>
<h3 className="text-xl font-bold md:text-2xl">{event.title}</h3>
<p className="text-sm text-muted-foreground line-clamp-2 md:line-clamp-3">{event.description}</p>
<div className="flex flex-wrap gap-2 text-sm">
<div className="text-muted-foreground">{event.date}</div>
<div className="text-muted-foreground"></div>
<div className="text-muted-foreground">{event.location}</div>
</div>
<div className="flex items-center justify-between pt-2">
<div className="font-semibold">${event.price.toFixed(2)}</div>
<Link href={`/events/${event.id}`}>
<Button size="sm">View Details</Button>
</Link>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
))}
</div>
<div className="absolute right-4 bottom-4 md:right-8 md:bottom-8 flex gap-2">
<Button
variant="secondary"
size="icon"
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur"
onClick={previous}
>
<ChevronLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
<Button
variant="secondary"
size="icon"
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur"
onClick={next}
>
<ChevronRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
</div>
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-1">
{featuredEvents.map((_, index) => (
<Button
key={index}
variant="ghost"
size="icon"
className={`h-2 w-2 rounded-full ${index === current ? "bg-primary" : "bg-muted"}`}
onClick={() => setCurrent(index)}
>
<span className="sr-only">Go to slide {index + 1}</span>
</Button>
))}
</div>
</div>
)
}
"use client"
import Link from "next/link"
interface MenuPopupProps {
onClose: () => void
onLogout?: () => void
}
export function MenuPopup({ onClose, onLogout }: MenuPopupProps) {
return (
<div className="absolute top-16 right-0 z-50 w-48 bg-gray-700 text-white shadow-lg rounded-bl-md overflow-hidden">
<div className="py-1">
<Link href="/tracked-events" className="block px-4 py-2 hover:bg-gray-600" onClick={onClose}>
Tracked events
</Link>
<Link href="/my-tickets" className="block px-4 py-2 hover:bg-gray-600" onClick={onClose}>
My tickets
</Link>
<Link href="/past-events" className="block px-4 py-2 hover:bg-gray-600" onClick={onClose}>
Past events
</Link>
<button
className="block w-full text-left px-4 py-2 hover:bg-gray-600"
onClick={() => {
if (onLogout) onLogout()
onClose()
}}
>
Log out
</button>
</div>
</div>
)
}
"use client"
import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
"use client"
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(({ id, title, description, action, ...props }) => (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && <ToastDescription>{description}</ToastDescription>}
</div>
{action}
<ToastClose />
</Toast>
))}
<ToastViewport />
</ToastProvider>
)
}
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
"use client"
import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
const AlertDialog = AlertDialogPrimitive.Root
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
const AlertDialogPortal = AlertDialogPrimitive.Portal
const AlertDialogOverlay = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
const AlertDialogContent = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
/>
</AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
const AlertDialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
AlertDialogHeader.displayName = "AlertDialogHeader"
const AlertDialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
AlertDialogFooter.displayName = "AlertDialogFooter"
const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold", className)}
{...props}
/>
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
AlertDialogDescription.displayName =
AlertDialogPrimitive.Description.displayName
const AlertDialogAction = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Action>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Action
ref={ref}
className={cn(buttonVariants(), className)}
{...props}
/>
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
const AlertDialogCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn(
buttonVariants({ variant: "outline" }),
"mt-2 sm:mt-0",
className
)}
{...props}
/>
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
export {
AlertDialog,
AlertDialogPortal,
AlertDialogOverlay,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
}
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root
export { AspectRatio }
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
import * as React from "react"
import { cva, type VariantProps } 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",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className
)}
{...props}
/>
))
BreadcrumbList.displayName = "BreadcrumbList"
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
))
BreadcrumbItem.displayName = "BreadcrumbItem"
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
return (
<Comp
ref={ref}
className={cn("transition-colors hover:text-foreground", className)}
{...props}
/>
)
})
BreadcrumbLink.displayName = "BreadcrumbLink"
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
))
BreadcrumbPage.displayName = "BreadcrumbPage"
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
)
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
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",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ 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 }
"use client"
import * as React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { DayPicker } from "react-day-picker"
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
export type CalendarProps = React.ComponentProps<typeof DayPicker>
function Calendar({
className,
classNames,
showOutsideDays = true,
...props
}: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn("p-3", className)}
classNames={{
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
month: "space-y-4",
caption: "flex justify-center pt-1 relative items-center",
caption_label: "text-sm font-medium",
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
day: cn(
buttonVariants({ variant: "ghost" }),
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
),
day_range_end: "day-range-end",
day_selected:
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
day_today: "bg-accent text-accent-foreground",
day_outside:
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
day_disabled: "text-muted-foreground opacity-50",
day_range_middle:
"aria-selected:bg-accent aria-selected:text-accent-foreground",
day_hidden: "invisible",
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
)
}
Calendar.displayName = "Calendar"
export { Calendar }
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ 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<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ 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<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ 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 }
"use client"
import * as React from "react"
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react"
import { ArrowLeft, ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
type CarouselApi = UseEmblaCarouselType[1]
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
type CarouselOptions = UseCarouselParameters[0]
type CarouselPlugin = UseCarouselParameters[1]
type CarouselProps = {
opts?: CarouselOptions
plugins?: CarouselPlugin
orientation?: "horizontal" | "vertical"
setApi?: (api: CarouselApi) => void
}
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
api: ReturnType<typeof useEmblaCarousel>[1]
scrollPrev: () => void
scrollNext: () => void
canScrollPrev: boolean
canScrollNext: boolean
} & CarouselProps
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
function useCarousel() {
const context = React.useContext(CarouselContext)
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />")
}
return context
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return
}
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
}, [api])
const scrollNext = React.useCallback(() => {
api?.scrollNext()
}, [api])
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault()
scrollPrev()
} else if (event.key === "ArrowRight") {
event.preventDefault()
scrollNext()
}
},
[scrollPrev, scrollNext]
)
React.useEffect(() => {
if (!api || !setApi) {
return
}
setApi(api)
}, [api, setApi])
React.useEffect(() => {
if (!api) {
return
}
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)
return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
)
}
)
Carousel.displayName = "Carousel"
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel()
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
)
})
CarouselContent.displayName = "CarouselContent"
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel()
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
)
})
CarouselItem.displayName = "CarouselItem"
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
)
})
CarouselPrevious.displayName = "CarouselPrevious"
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel()
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
)
})
CarouselNext.displayName = "CarouselNext"
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
}
"use client"
import * as React from "react"
import * as RechartsPrimitive from "recharts"
import { cn } from "@/lib/utils"
// Format: { THEME_NAME: CSS_SELECTOR }
const THEMES = { light: "", dark: ".dark" } as const
export type ChartConfig = {
[k in string]: {
label?: React.ReactNode
icon?: React.ComponentType
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
)
}
type ChartContextProps = {
config: ChartConfig
}
const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() {
const context = React.useContext(ChartContext)
if (!context) {
throw new Error("useChart must be used within a <ChartContainer />")
}
return context
}
const ChartContainer = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
config: ChartConfig
children: React.ComponentProps<
typeof RechartsPrimitive.ResponsiveContainer
>["children"]
}
>(({ id, className, children, config, ...props }, ref) => {
const uniqueId = React.useId()
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
return (
<ChartContext.Provider value={{ config }}>
<div
data-chart={chartId}
ref={ref}
className={cn(
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
className
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
</div>
</ChartContext.Provider>
)
})
ChartContainer.displayName = "Chart"
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
const colorConfig = Object.entries(config).filter(
([_, config]) => config.theme || config.color
)
if (!colorConfig.length) {
return null
}
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(
([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
itemConfig.color
return color ? ` --color-${key}: ${color};` : null
})
.join("\n")}
}
`
)
.join("\n"),
}}
/>
)
}
const ChartTooltip = RechartsPrimitive.Tooltip
const ChartTooltipContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}
>(
(
{
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
},
ref
) => {
const { config } = useChart()
const tooltipLabel = React.useMemo(() => {
if (hideLabel || !payload?.length) {
return null
}
const [item] = payload
const key = `${labelKey || item.dataKey || item.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value =
!labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label
: itemConfig?.label
if (labelFormatter) {
return (
<div className={cn("font-medium", labelClassName)}>
{labelFormatter(value, payload)}
</div>
)
}
if (!value) {
return null
}
return <div className={cn("font-medium", labelClassName)}>{value}</div>
}, [
label,
labelFormatter,
payload,
hideLabel,
labelClassName,
config,
labelKey,
])
if (!active || !payload?.length) {
return null
}
const nestLabel = payload.length === 1 && indicator !== "dot"
return (
<div
ref={ref}
className={cn(
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
className
)}
>
{!nestLabel ? tooltipLabel : null}
<div className="grid gap-1.5">
{payload.map((item, index) => {
const key = `${nameKey || item.name || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const indicatorColor = color || item.payload.fill || item.color
return (
<div
key={item.dataKey}
className={cn(
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
indicator === "dot" && "items-center"
)}
>
{formatter && item?.value !== undefined && item.name ? (
formatter(item.value, item.name, item, index, item.payload)
) : (
<>
{itemConfig?.icon ? (
<itemConfig.icon />
) : (
!hideIndicator && (
<div
className={cn(
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
{
"h-2.5 w-2.5": indicator === "dot",
"w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
style={
{
"--color-bg": indicatorColor,
"--color-border": indicatorColor,
} as React.CSSProperties
}
/>
)
)}
<div
className={cn(
"flex flex-1 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
>
<div className="grid gap-1.5">
{nestLabel ? tooltipLabel : null}
<span className="text-muted-foreground">
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
<span className="font-mono font-medium tabular-nums text-foreground">
{item.value.toLocaleString()}
</span>
)}
</div>
</>
)}
</div>
)
})}
</div>
</div>
)
}
)
ChartTooltipContent.displayName = "ChartTooltip"
const ChartLegend = RechartsPrimitive.Legend
const ChartLegendContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> &
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}
>(
(
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
ref
) => {
const { config } = useChart()
if (!payload?.length) {
return null
}
return (
<div
ref={ref}
className={cn(
"flex items-center justify-center gap-4",
verticalAlign === "top" ? "pb-3" : "pt-3",
className
)}
>
{payload.map((item) => {
const key = `${nameKey || item.dataKey || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
return (
<div
key={item.value}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
)}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />
) : (
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{
backgroundColor: item.color,
}}
/>
)}
{itemConfig?.label}
</div>
)
})}
</div>
)
}
)
ChartLegendContent.displayName = "ChartLegend"
// Helper to extract item config from a payload.
function getPayloadConfigFromPayload(
config: ChartConfig,
payload: unknown,
key: string
) {
if (typeof payload !== "object" || payload === null) {
return undefined
}
const payloadPayload =
"payload" in payload &&
typeof payload.payload === "object" &&
payload.payload !== null
? payload.payload
: undefined
let configLabelKey: string = key
if (
key in payload &&
typeof payload[key as keyof typeof payload] === "string"
) {
configLabelKey = payload[key as keyof typeof payload] as string
} else if (
payloadPayload &&
key in payloadPayload &&
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
) {
configLabelKey = payloadPayload[
key as keyof typeof payloadPayload
] as string
}
return configLabelKey in config
? config[configLabelKey]
: config[key as keyof typeof config]
}
export {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
ChartStyle,
}
"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<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ 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 }
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
"use client"
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { Command as CommandPrimitive } from "cmdk"
import { Search } from "lucide-react"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
const CommandDialog = ({ children, ...props }: DialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}
"use client"
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const ContextMenu = ContextMenuPrimitive.Root
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
const ContextMenuGroup = ContextMenuPrimitive.Group
const ContextMenuPortal = ContextMenuPrimitive.Portal
const ContextMenuSub = ContextMenuPrimitive.Sub
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 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}
/>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex 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
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex 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">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
"use client"
import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"
import { cn } from "@/lib/utils"
const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root
shouldScaleBackground={shouldScaleBackground}
{...props}
/>
)
Drawer.displayName = "Drawer"
const DrawerTrigger = DrawerPrimitive.Trigger
const DrawerPortal = DrawerPrimitive.Portal
const DrawerClose = DrawerPrimitive.Close
const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
))
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
))
DrawerContent.displayName = "DrawerContent"
const DrawerHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...props}
/>
)
DrawerHeader.displayName = "DrawerHeader"
const DrawerFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
DrawerFooter.displayName = "DrawerFooter"
const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
export {
Drawer,
DrawerPortal,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerTitle,
DrawerDescription,
}
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 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",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors 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">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
"use client"
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 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}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
export { HoverCard, HoverCardTrigger, HoverCardContent }
"use client"
import * as React from "react"
import { OTPInput, OTPInputContext } from "input-otp"
import { Dot } from "lucide-react"
import { cn } from "@/lib/utils"
const InputOTP = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
"flex items-center gap-2 has-[:disabled]:opacity-50",
containerClassName
)}
className={cn("disabled:cursor-not-allowed", className)}
{...props}
/>
))
InputOTP.displayName = "InputOTP"
const InputOTPGroup = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center", className)} {...props} />
))
InputOTPGroup.displayName = "InputOTPGroup"
const InputOTPSlot = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div"> & { index: number }
>(({ index, className, ...props }, ref) => {
const inputOTPContext = React.useContext(OTPInputContext)
const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
return (
<div
ref={ref}
className={cn(
"relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
isActive && "z-10 ring-2 ring-ring ring-offset-background",
className
)}
{...props}
>
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
)
})
InputOTPSlot.displayName = "InputOTPSlot"
const InputOTPSeparator = React.forwardRef<
React.ElementRef<"div">,
React.ComponentPropsWithoutRef<"div">
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Dot />
</div>
))
InputOTPSeparator.displayName = "InputOTPSeparator"
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
({ 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-base 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 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } 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<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }
"use client"
import * as React from "react"
import * as MenubarPrimitive from "@radix-ui/react-menubar"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const MenubarMenu = MenubarPrimitive.Menu
const MenubarGroup = MenubarPrimitive.Group
const MenubarPortal = MenubarPrimitive.Portal
const MenubarSub = MenubarPrimitive.Sub
const MenubarRadioGroup = MenubarPrimitive.RadioGroup
const Menubar = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
"flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
className
)}
{...props}
/>
))
Menubar.displayName = MenubarPrimitive.Root.displayName
const MenubarTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
className
)}
{...props}
/>
))
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
const MenubarSubTrigger = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</MenubarPrimitive.SubTrigger>
))
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
const MenubarSubContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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}
/>
))
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
const MenubarContent = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
>(
(
{ className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
ref
) => (
<MenubarPrimitive.Portal>
<MenubarPrimitive.Content
ref={ref}
align={align}
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in 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}
/>
</MenubarPrimitive.Portal>
)
)
MenubarContent.displayName = MenubarPrimitive.Content.displayName
const MenubarItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarItem.displayName = MenubarPrimitive.Item.displayName
const MenubarCheckboxItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex 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
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<MenubarPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.CheckboxItem>
))
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
const MenubarRadioItem = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex 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">
<MenubarPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</MenubarPrimitive.ItemIndicator>
</span>
{children}
</MenubarPrimitive.RadioItem>
))
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
const MenubarLabel = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
MenubarLabel.displayName = MenubarPrimitive.Label.displayName
const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
const MenubarShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
MenubarShortcut.displayname = "MenubarShortcut"
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
}
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ 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<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ 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-10 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<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ 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<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ 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-lg 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<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ 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,
}
import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
Pagination.displayName = "Pagination"
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
))
PaginationContent.displayName = "PaginationContent"
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
)
PaginationLink.displayName = "PaginationLink"
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
}
"use client"
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<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ 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 }
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }
"use client"
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<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ 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 }
"use client"
import { GripVertical } from "lucide-react"
import * as ResizablePrimitive from "react-resizable-panels"
import { cn } from "@/lib/utils"
const ResizablePanelGroup = ({
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
<ResizablePrimitive.PanelGroup
className={cn(
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
className
)}
{...props}
/>
)
const ResizablePanel = ResizablePrimitive.Panel
const ResizableHandle = ({
withHandle,
className,
...props
}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
withHandle?: boolean
}) => (
<ResizablePrimitive.PanelResizeHandle
className={cn(
"relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
{...props}
>
{withHandle && (
<div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
<GripVertical className="h-2.5 w-2.5" />
</div>
)}
</ResizablePrimitive.PanelResizeHandle>
)
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
"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<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ 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<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ 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,
}
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ 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 }
"use client"
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}
"use client"
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { VariantProps, cva } from "class-variance-authority"
import { PanelLeft } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { Sheet, SheetContent } from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
const SIDEBAR_COOKIE_NAME = "sidebar:state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
type SidebarContext = {
state: "expanded" | "collapsed"
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContext | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider.")
}
return context
}
const SidebarProvider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}
>(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref
) => {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value
if (setOpenProp) {
setOpenProp(openState)
} else {
_setOpen(openState)
}
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo<SidebarContext>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
)
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
className
)}
ref={ref}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
)
}
)
SidebarProvider.displayName = "SidebarProvider"
const Sidebar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
side?: "left" | "right"
variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"
}
>(
(
{
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
...props
},
ref
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") {
return (
<div
className={cn(
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
className
)}
ref={ref}
{...props}
>
{children}
</div>
)
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
)
}
return (
<div
ref={ref}
className="group peer hidden md:block text-sidebar-foreground"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
data-side={side}
>
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
"duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
)}
/>
<div
className={cn(
"duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className
)}
{...props}
>
<div
data-sidebar="sidebar"
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
>
{children}
</div>
</div>
</div>
)
}
)
Sidebar.displayName = "Sidebar"
const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar()
return (
<Button
ref={ref}
data-sidebar="trigger"
variant="ghost"
size="icon"
className={cn("h-7 w-7", className)}
onClick={(event) => {
onClick?.(event)
toggleSidebar()
}}
{...props}
>
<PanelLeft />
<span className="sr-only">Toggle Sidebar</span>
</Button>
)
})
SidebarTrigger.displayName = "SidebarTrigger"
const SidebarRail = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar()
return (
<button
ref={ref}
data-sidebar="rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
)}
{...props}
/>
)
})
SidebarRail.displayName = "SidebarRail"
const SidebarInset = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"main">
>(({ className, ...props }, ref) => {
return (
<main
ref={ref}
className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background",
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className
)}
{...props}
/>
)
})
SidebarInset.displayName = "SidebarInset"
const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>,
React.ComponentProps<typeof Input>
>(({ className, ...props }, ref) => {
return (
<Input
ref={ref}
data-sidebar="input"
className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className
)}
{...props}
/>
)
})
SidebarInput.displayName = "SidebarInput"
const SidebarHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="header"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
)
})
SidebarHeader.displayName = "SidebarHeader"
const SidebarFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="footer"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
)
})
SidebarFooter.displayName = "SidebarFooter"
const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>,
React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => {
return (
<Separator
ref={ref}
data-sidebar="separator"
className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props}
/>
)
})
SidebarSeparator.displayName = "SidebarSeparator"
const SidebarContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className
)}
{...props}
/>
)
})
SidebarContent.displayName = "SidebarContent"
const SidebarGroup = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="group"
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props}
/>
)
})
SidebarGroup.displayName = "SidebarGroup"
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div"
return (
<Comp
ref={ref}
data-sidebar="group-label"
className={cn(
"duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className
)}
{...props}
/>
)
})
SidebarGroupLabel.displayName = "SidebarGroupLabel"
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
data-sidebar="group-action"
className={cn(
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
)
})
SidebarGroupAction.displayName = "SidebarGroupAction"
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="group-content"
className={cn("w-full text-sm", className)}
{...props}
/>
))
SidebarGroupContent.displayName = "SidebarGroupContent"
const SidebarMenu = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu"
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props}
/>
))
SidebarMenu.displayName = "SidebarMenu"
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
data-sidebar="menu-item"
className={cn("group/menu-item relative", className)}
{...props}
/>
))
SidebarMenuItem.displayName = "SidebarMenuItem"
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref
) => {
const Comp = asChild ? Slot : "button"
const { isMobile, state } = useSidebar()
const button = (
<Comp
ref={ref}
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
)
if (!tooltip) {
return button
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
)
}
)
SidebarMenuButton.displayName = "SidebarMenuButton"
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean
showOnHover?: boolean
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
data-sidebar="menu-action"
className={cn(
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className
)}
{...props}
/>
)
})
SidebarMenuAction.displayName = "SidebarMenuAction"
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="menu-badge"
className={cn(
"absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
))
SidebarMenuBadge.displayName = "SidebarMenuBadge"
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
showIcon?: boolean
}
>(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
}, [])
return (
<div
ref={ref}
data-sidebar="menu-skeleton"
className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 flex-1 max-w-[--skeleton-width]"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
/>
</div>
)
})
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu-sub"
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
))
SidebarMenuSub.displayName = "SidebarMenuSub"
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ ...props }, ref) => <li ref={ref} {...props} />)
SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean
size?: "sm" | "md"
isActive?: boolean
}
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
return (
<Comp
ref={ref}
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
)
})
SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
}
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-muted", className)}
{...props}
/>
)
}
export { Skeleton }
"use client"
import * as React from "react"
import * as SliderPrimitive from "@radix-ui/react-slider"
import { cn } from "@/lib/utils"
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background 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" />
</SliderPrimitive.Root>
))
Slider.displayName = SliderPrimitive.Root.displayName
export { Slider }
"use client"
import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"
type ToasterProps = React.ComponentProps<typeof Sonner>
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}
export { Toaster }
"use client"
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import { cn } from "@/lib/utils"
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
export { Switch }
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<"textarea">
>(({ 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-base 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 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
})
Textarea.displayName = "Textarea"
export { Textarea }
"use client"
import * as React from "react"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className,
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive: "destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className,
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className,
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} />
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
"use client"
import { useToast } from "@/hooks/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
"use client"
import * as React from "react"
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
import { type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
import { toggleVariants } from "@/components/ui/toggle"
const ToggleGroupContext = React.createContext<
VariantProps<typeof toggleVariants>
>({
size: "default",
variant: "default",
})
const ToggleGroup = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, children, ...props }, ref) => (
<ToggleGroupPrimitive.Root
ref={ref}
className={cn("flex items-center justify-center gap-1", className)}
{...props}
>
<ToggleGroupContext.Provider value={{ variant, size }}>
{children}
</ToggleGroupContext.Provider>
</ToggleGroupPrimitive.Root>
))
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
const ToggleGroupItem = React.forwardRef<
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
VariantProps<typeof toggleVariants>
>(({ className, children, variant, size, ...props }, ref) => {
const context = React.useContext(ToggleGroupContext)
return (
<ToggleGroupPrimitive.Item
ref={ref}
className={cn(
toggleVariants({
variant: context.variant || variant,
size: context.size || size,
}),
className
)}
{...props}
>
{children}
</ToggleGroupPrimitive.Item>
)
})
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
export { ToggleGroup, ToggleGroupItem }
"use client"
import * as React from "react"
import * as TogglePrimitive from "@radix-ui/react-toggle"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-3 min-w-10",
sm: "h-9 px-2.5 min-w-9",
lg: "h-11 px-5 min-w-11",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
))
Toggle.displayName = TogglePrimitive.Root.displayName
export { Toggle, toggleVariants }
"use client"
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type { ToastActionElement, ToastProps } from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_VALUE
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
"use client"
import { createContext, useContext, useState, type ReactNode } from "react"
interface NavigationContextType {
previousPath: string
setPreviousPath: (path: string) => void
}
const NavigationContext = createContext<NavigationContextType | undefined>(undefined)
export function NavigationProvider({ children }: { children: ReactNode }) {
const [previousPath, setPreviousPath] = useState("/events")
return <NavigationContext.Provider value={{ previousPath, setPreviousPath }}>{children}</NavigationContext.Provider>
}
export function useNavigation() {
const context = useContext(NavigationContext)
if (context === undefined) {
throw new Error("useNavigation must be used within a NavigationProvider")
}
return context
}
"use client"
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
type TicketPrice = {
type: string
amount: string
}
type PurchasedTicket = {
id: string
eventId: string
eventName: string
description: string
ticketType: string
price: string
purchaseDate: string
eventDate: string
location: string
image: string
}
interface PurchasedTicketsContextType {
purchasedTickets: PurchasedTicket[]
addPurchasedTicket: (ticket: PurchasedTicket) => void
removePurchasedTicket: (ticketId: string) => void
hasTicketForEvent: (eventId: string) => boolean
}
const PurchasedTicketsContext = createContext<PurchasedTicketsContextType | undefined>(undefined)
export function PurchasedTicketsProvider({ children }: { children: ReactNode }) {
const [purchasedTickets, setPurchasedTickets] = useState<PurchasedTicket[]>([])
// Load purchased tickets from localStorage on initial render
useEffect(() => {
try {
const savedTickets = localStorage.getItem("purchasedTickets")
if (savedTickets) {
setPurchasedTickets(JSON.parse(savedTickets))
}
} catch (error) {
console.error("Error loading purchased tickets:", error)
}
}, [])
// Save purchased tickets to localStorage whenever they change
useEffect(() => {
try {
localStorage.setItem("purchasedTickets", JSON.stringify(purchasedTickets))
} catch (error) {
console.error("Error saving purchased tickets:", error)
}
}, [purchasedTickets])
const addPurchasedTicket = (ticket: PurchasedTicket) => {
setPurchasedTickets((prev) => {
// Check if ticket is already purchased
if (prev.some((t) => t.id === ticket.id)) {
return prev
}
return [...prev, ticket]
})
}
const removePurchasedTicket = (ticketId: string) => {
setPurchasedTickets((prev) => prev.filter((ticket) => ticket.id !== ticketId))
}
const hasTicketForEvent = (eventId: string) => {
return purchasedTickets.some((ticket) => ticket.eventId === eventId)
}
return (
<PurchasedTicketsContext.Provider
value={{ purchasedTickets, addPurchasedTicket, removePurchasedTicket, hasTicketForEvent }}
>
{children}
</PurchasedTicketsContext.Provider>
)
}
export function usePurchasedTickets() {
const context = useContext(PurchasedTicketsContext)
if (context === undefined) {
throw new Error("usePurchasedTickets must be used within a PurchasedTicketsProvider")
}
return context
}
"use client"
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
type TrackedEvent = {
id: string
title: string
priceRange: string
location: string
date: string
image: string
}
interface TrackedEventsContextType {
trackedEvents: TrackedEvent[]
addTrackedEvent: (event: TrackedEvent) => void
removeTrackedEvent: (eventId: string) => void
isEventTracked: (eventId: string) => boolean
}
const TrackedEventsContext = createContext<TrackedEventsContextType | undefined>(undefined)
export function TrackedEventsProvider({ children }: { children: ReactNode }) {
const [trackedEvents, setTrackedEvents] = useState<TrackedEvent[]>([])
// Load tracked events from localStorage on initial render
useEffect(() => {
try {
const savedEvents = localStorage.getItem("trackedEvents")
if (savedEvents) {
setTrackedEvents(JSON.parse(savedEvents))
}
} catch (error) {
console.error("Error loading tracked events:", error)
}
}, [])
// Save tracked events to localStorage whenever they change
useEffect(() => {
try {
localStorage.setItem("trackedEvents", JSON.stringify(trackedEvents))
} catch (error) {
console.error("Error saving tracked events:", error)
}
}, [trackedEvents])
const addTrackedEvent = (event: TrackedEvent) => {
setTrackedEvents((prev) => {
// Check if event is already tracked
if (prev.some((e) => e.id === event.id)) {
return prev
}
return [...prev, event]
})
}
const removeTrackedEvent = (eventId: string) => {
setTrackedEvents((prev) => prev.filter((event) => event.id !== eventId))
}
const isEventTracked = (eventId: string) => {
return trackedEvents.some((event) => event.id === eventId)
}
return (
<TrackedEventsContext.Provider value={{ trackedEvents, addTrackedEvent, removeTrackedEvent, isEventTracked }}>
{children}
</TrackedEventsContext.Provider>
)
}
export function useTrackedEvents() {
const context = useContext(TrackedEventsContext)
if (context === undefined) {
throw new Error("useTrackedEvents must be used within a TrackedEventsProvider")
}
return context
}
"use client"
import { useState, useEffect } from "react"
export function useMobile() {
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkIfMobile = () => {
setIsMobile(window.innerWidth < 768)
}
// Initial check
checkIfMobile()
// Add event listener
window.addEventListener("resize", checkIfMobile)
// Clean up
return () => window.removeEventListener("resize", checkIfMobile)
}, [])
return isMobile
}
"use client"
import { usePathname, useRouter } from "next/navigation"
import { useNavigation } from "@/context/navigation-context"
export function usePageNavigation() {
const router = useRouter()
const currentPath = usePathname()
const { setPreviousPath, getPreviousPath } = useNavigation()
// Function to navigate back
const navigateBack = () => {
const previousPath = getPreviousPath(currentPath)
router.push(previousPath)
}
// Function to navigate to a new page and store the current path as previous
const navigateTo = (path: string) => {
setPreviousPath(path, currentPath)
router.push(path)
}
return {
navigateBack,
navigateTo,
currentPath,
}
}
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
let userConfig = undefined
try {
// try to import ESM first
userConfig = await import('./v0-user-next.config.mjs')
} catch (e) {
try {
// fallback to CJS import
userConfig = await import("./v0-user-next.config");
} catch (innerError) {
// ignore error
}
}
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
experimental: {
webpackBuildWorker: true,
parallelServerBuildTraces: true,
parallelServerCompiles: true,
},
}
if (userConfig) {
// ESM imports will have a "default" property
const config = userConfig.default || userConfig
for (const key in config) {
if (
typeof nextConfig[key] === 'object' &&
!Array.isArray(nextConfig[key])
) {
nextConfig[key] = {
...nextConfig[key],
...config[key],
}
} else {
nextConfig[key] = config[key]
}
}
}
export default nextConfig
{
"name": "my-v0-project",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-aspect-ratio": "^1.1.1",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-menubar": "^1.1.4",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "latest",
"@radix-ui/react-toggle": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.6",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "15.2.4",
"next-themes": "latest",
"react": "^19",
"react-day-picker": "8.10.1",
"react-dom": "^19",
"react-hook-form": "^7.54.1",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.0",
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.6",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8",
"tailwindcss": "^3.4.17",
"typescript": "^5"
}
}
\ No newline at end of file
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
\ No newline at end of file
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
\ No newline at end of file
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
"*.{js,ts,jsx,tsx,mdx}",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config
export default config
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment