mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-18 13:34:08 +01:00
ui: update landing page
This commit is contained in:
parent
8f5459acf9
commit
cbcf2b870b
@ -87,3 +87,69 @@ body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add custom styles for the blog content */
|
||||
.prose h2 {
|
||||
@apply text-2xl font-semibold mt-8 mb-4;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@apply mb-4 leading-relaxed;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
@apply list-disc pl-6 mb-4 space-y-2;
|
||||
}
|
||||
|
||||
/* Animation utilities */
|
||||
@keyframes blob {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: translate(20px, 15px) scale(1.1);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-15px, 10px) scale(0.9);
|
||||
}
|
||||
75% {
|
||||
transform: translate(15px, -25px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-15px);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-blob {
|
||||
animation: blob 15s infinite;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.animation-delay-4000 {
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
.animation-delay-1000 {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
@ -1,69 +1,274 @@
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Cloud, BarChart, Zap } from "lucide-react";
|
||||
import { Leaf } from "lucide-react";
|
||||
import { ArrowRight, Cloud, BarChart, Zap, Leaf, ChevronRight, Users, Shield, LineChart } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-400 via-green-600 to-green-900 text-white">
|
||||
<header className="container mx-auto px-4 py-6 flex justify-between items-center">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<span className="flex font-bold text-xl">
|
||||
<Leaf />
|
||||
ForFarm
|
||||
</span>
|
||||
</Link>
|
||||
<span className="flex space-x-4 items-center">
|
||||
<Link href="/documentation" className="hover:text-gray-200 transition-colors font-bold">
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="bg-white text-blue-600 font-bold px-4 py-2 rounded-full hover:bg-gray-100 transition-colors">
|
||||
Get started
|
||||
</Link>
|
||||
</span>
|
||||
</header>
|
||||
<div className="min-h-screen bg-gradient-to-br from-green-400 via-green-600 to-green-900 text-white relative overflow-hidden">
|
||||
{/* Animated background elements */}
|
||||
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0">
|
||||
<div className="absolute top-20 left-10 w-72 h-72 bg-white/10 rounded-full blur-3xl animate-blob"></div>
|
||||
<div className="absolute top-40 right-10 w-96 h-96 bg-green-300/10 rounded-full blur-3xl animate-blob animation-delay-2000"></div>
|
||||
<div className="absolute bottom-20 left-1/3 w-80 h-80 bg-green-800/20 rounded-full blur-3xl animate-blob animation-delay-4000"></div>
|
||||
</div>
|
||||
|
||||
<main className="container mx-auto px-4 py-20 text-center">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<Image
|
||||
src="/water-pot.png"
|
||||
alt="ForFarm Icon"
|
||||
width={100}
|
||||
height={100}
|
||||
className="mx-auto mb-8 rounded-2xl"
|
||||
/>
|
||||
<h1 className="text-6xl font-bold mb-6">Your Smart Farming Platform</h1>
|
||||
<p className="text-xl md:text-2xl mb-12 text-gray-200">
|
||||
It's a smart and easy way to optimize your agricultural business, with the help of AI-driven insights and
|
||||
real-time data.
|
||||
</p>
|
||||
<Link href="/setup">
|
||||
<Button className="bg-black text-white text-md font-bold px-4 py-6 rounded-full hover:bg-gray-600">
|
||||
Manage your farm
|
||||
</Button>
|
||||
</Link>
|
||||
{/* 3D floating elements */}
|
||||
<div className="absolute top-1/4 right-10 w-20 h-20 hidden lg:block">
|
||||
<div className="relative w-full h-full animate-float animation-delay-1000">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-green-300 to-green-500 rounded-xl shadow-lg transform rotate-12"></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div className="absolute bottom-1/4 left-10 w-16 h-16 hidden lg:block">
|
||||
<div className="relative w-full h-full animate-float">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-green-200 to-green-400 rounded-xl shadow-lg transform -rotate-12"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* <div className="absolute -inset-2 bg-gradient-to-r from-green-600 to-blue-600 rounded-lg blur opacity-10"></div> */}
|
||||
<div className="relative z-10">
|
||||
<header className="container mx-auto px-4 py-6 flex justify-between items-center">
|
||||
<Link href="/" className="flex items-center space-x-2 group">
|
||||
<span className="flex font-bold text-xl items-center gap-1.5">
|
||||
<Leaf className="h-5 w-5 group-hover:text-green-300 transition-colors" />
|
||||
ForFarm
|
||||
</span>
|
||||
<Badge variant="outline" className="bg-white/10 backdrop-blur-sm text-xs font-normal">
|
||||
BETA
|
||||
</Badge>
|
||||
</Link>
|
||||
<nav className="hidden md:flex space-x-6 items-center">
|
||||
<Link href="/features" className="hover:text-green-200 transition-colors">
|
||||
Features
|
||||
</Link>
|
||||
<Link href="/pricing" className="hover:text-green-200 transition-colors">
|
||||
Pricing
|
||||
</Link>
|
||||
<Link href="/knowledge-hub" className="hover:text-green-200 transition-colors">
|
||||
Knowledge Hub
|
||||
</Link>
|
||||
<Link href="/documentation" className="hover:text-green-200 transition-colors">
|
||||
Documentation
|
||||
</Link>
|
||||
</nav>
|
||||
<div className="flex space-x-3 items-center">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="hidden md:inline-block hover:text-green-200 transition-colors font-medium">
|
||||
Log in
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="bg-white text-green-700 font-bold px-4 py-2 rounded-full hover:bg-green-100 transition-colors shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 duration-200">
|
||||
Get started
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<footer className="container mx-auto px-4 py-6 text-center text-sm text-gray-300">
|
||||
<Link href="#" className="hover:text-white transition-colors">
|
||||
Terms
|
||||
</Link>
|
||||
{" • "}
|
||||
<Link href="#" className="hover:text-white transition-colors">
|
||||
Privacy
|
||||
</Link>
|
||||
{" • "}
|
||||
<Link href="#" className="hover:text-white transition-colors">
|
||||
Cookies
|
||||
</Link>
|
||||
</footer>
|
||||
<main className="container mx-auto px-4 py-12 md:py-20">
|
||||
{/* Hero section */}
|
||||
<div className="flex flex-col lg:flex-row items-center justify-between gap-12 mb-24">
|
||||
<div className="max-w-2xl text-left">
|
||||
<Badge className="mb-4 bg-white/20 backdrop-blur-sm text-white hover:bg-white/30">
|
||||
Smart Farming Solution
|
||||
</Badge>
|
||||
<h1 className="text-5xl md:text-6xl font-bold mb-6 leading-tight">
|
||||
Grow Smarter, <br />
|
||||
<span className="bg-clip-text text-transparent bg-gradient-to-r from-green-200 to-white">
|
||||
Harvest Better
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl mb-8 text-green-100 leading-relaxed">
|
||||
Optimize your agricultural business with AI-driven insights and real-time data monitoring. ForFarm helps
|
||||
you make informed decisions for sustainable farming.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<Link href="/setup">
|
||||
<Button className="bg-white text-green-700 text-md font-bold px-6 py-6 rounded-full hover:bg-green-100 transition-colors shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 duration-200 w-full sm:w-auto">
|
||||
Start managing your farm
|
||||
<ChevronRight className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/demo">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-white text-white text-md font-bold px-6 py-6 rounded-full hover:bg-white/10 transition-colors w-full sm:w-auto">
|
||||
Watch demo
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex items-center gap-4">
|
||||
<div className="flex -space-x-2">
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<div key={i} className="w-8 h-8 rounded-full border-2 border-green-600 overflow-hidden">
|
||||
<Image
|
||||
src={`/placeholder.svg?height=32&width=32`}
|
||||
alt="User"
|
||||
width={32}
|
||||
height={32}
|
||||
className="bg-green-200"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-sm text-green-100">
|
||||
<span className="font-bold">500+</span> farmers already using ForFarm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="absolute -inset-1 bg-gradient-to-r from-green-400 to-green-200 rounded-2xl blur-md opacity-75"></div>
|
||||
<div className="relative bg-gradient-to-br from-green-800/90 to-green-900/90 backdrop-blur-sm border border-green-700/50 rounded-2xl p-6 shadow-2xl">
|
||||
<div className="absolute -top-6 -right-6 bg-gradient-to-br from-green-400 to-green-600 p-3 rounded-xl shadow-lg transform rotate-6">
|
||||
<Leaf className="h-6 w-6" />
|
||||
</div>
|
||||
<Image
|
||||
src="/water-pot.png"
|
||||
alt="ForFarm Dashboard Preview"
|
||||
width={500}
|
||||
height={300}
|
||||
className="rounded-lg shadow-lg mb-4"
|
||||
/>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="font-bold">Farm Dashboard</h3>
|
||||
<p className="text-green-200 text-sm">Real-time monitoring</p>
|
||||
</div>
|
||||
<Badge className="bg-green-500">Live Demo</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features section */}
|
||||
<section className="mb-24">
|
||||
<div className="text-center mb-16">
|
||||
<Badge className="mb-4 bg-white/20 backdrop-blur-sm text-white hover:bg-white/30">
|
||||
Why Choose ForFarm
|
||||
</Badge>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-4">Smart Features for Modern Farming</h2>
|
||||
<p className="text-xl text-green-100 max-w-2xl mx-auto">
|
||||
Our platform combines cutting-edge technology with agricultural expertise to help you optimize every
|
||||
aspect of your farm.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{[
|
||||
{
|
||||
icon: <BarChart className="h-10 w-10 text-green-300" />,
|
||||
title: "Data-Driven Insights",
|
||||
description:
|
||||
"Make informed decisions with comprehensive analytics and reporting on all aspects of your farm.",
|
||||
},
|
||||
{
|
||||
icon: <Cloud className="h-10 w-10 text-green-300" />,
|
||||
title: "Weather Integration",
|
||||
description:
|
||||
"Get real-time weather forecasts and alerts tailored to your specific location and crops.",
|
||||
},
|
||||
{
|
||||
icon: <Zap className="h-10 w-10 text-green-300" />,
|
||||
title: "Resource Optimization",
|
||||
description: "Reduce waste and maximize efficiency with smart resource management tools.",
|
||||
},
|
||||
{
|
||||
icon: <Users className="h-10 w-10 text-green-300" />,
|
||||
title: "Team Collaboration",
|
||||
description: "Coordinate farm activities and share information seamlessly with your entire team.",
|
||||
},
|
||||
{
|
||||
icon: <Shield className="h-10 w-10 text-green-300" />,
|
||||
title: "Crop Protection",
|
||||
description: "Identify potential threats to your crops early and get recommendations for protection.",
|
||||
},
|
||||
{
|
||||
icon: <LineChart className="h-10 w-10 text-green-300" />,
|
||||
title: "Yield Prediction",
|
||||
description: "Use AI-powered models to forecast yields and plan your harvests more effectively.",
|
||||
},
|
||||
].map((feature, index) => (
|
||||
<div key={index} className="relative group">
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-green-400 to-green-200 rounded-xl blur opacity-0 group-hover:opacity-100 transition duration-300"></div>
|
||||
<div className="relative bg-gradient-to-br from-green-800/80 to-green-900/80 backdrop-blur-sm border border-green-700/50 rounded-xl p-6 h-full flex flex-col">
|
||||
<div className="bg-green-900/50 p-3 rounded-lg w-fit mb-4">{feature.icon}</div>
|
||||
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
|
||||
<p className="text-green-100">{feature.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA section */}
|
||||
<section className="relative">
|
||||
<div className="absolute -inset-4 bg-gradient-to-r from-green-500 to-green-300 rounded-2xl blur-md opacity-50"></div>
|
||||
<div className="relative bg-gradient-to-br from-green-700/90 to-green-800/90 backdrop-blur-sm border border-green-600/50 rounded-xl p-8 md:p-12 flex flex-col md:flex-row items-center justify-between gap-8">
|
||||
<div>
|
||||
<h2 className="text-2xl md:text-3xl font-bold mb-4">Ready to transform your farming?</h2>
|
||||
<p className="text-green-100 mb-6 md:mb-0 max-w-lg">
|
||||
Join hundreds of farmers who are already using ForFarm to increase yields, reduce costs, and farm more
|
||||
sustainably.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/auth/signup">
|
||||
<Button className="bg-white text-green-700 text-md font-bold px-6 py-6 rounded-full hover:bg-green-100 transition-colors shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 duration-200 whitespace-nowrap">
|
||||
Get started for free
|
||||
<ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="container mx-auto px-4 py-12 border-t border-green-600/30">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center mb-8">
|
||||
<Link href="/" className="flex items-center space-x-2 mb-4 md:mb-0">
|
||||
<span className="flex font-bold text-xl items-center gap-1.5">
|
||||
<Leaf className="h-5 w-5" />
|
||||
ForFarm
|
||||
</span>
|
||||
</Link>
|
||||
<nav className="flex flex-wrap justify-center gap-x-6 gap-y-2">
|
||||
<Link href="/features" className="hover:text-green-200 transition-colors text-sm">
|
||||
Features
|
||||
</Link>
|
||||
<Link href="/pricing" className="hover:text-green-200 transition-colors text-sm">
|
||||
Pricing
|
||||
</Link>
|
||||
<Link href="/knowledge-hub" className="hover:text-green-200 transition-colors text-sm">
|
||||
Knowledge Hub
|
||||
</Link>
|
||||
<Link href="/documentation" className="hover:text-green-200 transition-colors text-sm">
|
||||
Documentation
|
||||
</Link>
|
||||
<Link href="/about" className="hover:text-green-200 transition-colors text-sm">
|
||||
About Us
|
||||
</Link>
|
||||
<Link href="/contact" className="hover:text-green-200 transition-colors text-sm">
|
||||
Contact
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row justify-between items-center text-sm text-green-200">
|
||||
<div className="mb-4 md:mb-0">© {new Date().getFullYear()} ForFarm. All rights reserved.</div>
|
||||
<div className="flex gap-4">
|
||||
<Link href="/terms" className="hover:text-white transition-colors">
|
||||
Terms
|
||||
</Link>
|
||||
<Link href="/privacy" className="hover:text-white transition-colors">
|
||||
Privacy
|
||||
</Link>
|
||||
<Link href="/cookies" className="hover:text-white transition-colors">
|
||||
Cookies
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
76
frontend/components/ui/calendar.tsx
Normal file
76
frontend/components/ui/calendar.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
"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-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||
: "[&:has([aria-selected])]:rounded-md"
|
||||
),
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||
),
|
||||
day_range_start: "day-range-start",
|
||||
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: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("h-4 w-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
||||
117
frontend/components/ui/pagination.tsx
Normal file
117
frontend/components/ui/pagination.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
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,
|
||||
PaginationLink,
|
||||
PaginationItem,
|
||||
PaginationPrevious,
|
||||
PaginationNext,
|
||||
PaginationEllipsis,
|
||||
}
|
||||
33
frontend/components/ui/popover.tsx
Normal file
33
frontend/components/ui/popover.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
"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 PopoverAnchor = PopoverPrimitive.Anchor
|
||||
|
||||
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, PopoverAnchor }
|
||||
120
frontend/components/ui/table.tsx
Normal file
120
frontend/components/ui/table.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
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-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-progress": "^1.1.2",
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
@ -31,12 +32,14 @@
|
||||
"axios": "^1.7.9",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.475.0",
|
||||
"next": "15.1.0",
|
||||
"next-auth": "^4.24.11",
|
||||
"next-themes": "^0.4.4",
|
||||
"react": "^19.0.0",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
|
||||
@ -29,6 +29,9 @@ importers:
|
||||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-popover':
|
||||
specifier: ^1.1.6
|
||||
version: 1.1.6(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-progress':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -74,6 +77,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
date-fns:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
js-cookie:
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
@ -92,6 +98,9 @@ importers:
|
||||
react:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0
|
||||
react-day-picker:
|
||||
specifier: 8.10.1
|
||||
version: 8.10.1(date-fns@4.1.0)(react@19.0.0)
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.0.0(react@19.0.0)
|
||||
@ -631,6 +640,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popover@1.1.6':
|
||||
resolution: {integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.2':
|
||||
resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
|
||||
peerDependencies:
|
||||
@ -1207,6 +1229,9 @@ packages:
|
||||
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
date-fns@4.1.0:
|
||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
@ -2083,6 +2108,12 @@ packages:
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
react-day-picker@8.10.1:
|
||||
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
|
||||
peerDependencies:
|
||||
date-fns: ^2.28.0 || ^3.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-dom@19.0.0:
|
||||
resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==}
|
||||
peerDependencies:
|
||||
@ -2929,6 +2960,29 @@ snapshots:
|
||||
'@types/react': 19.0.8
|
||||
'@types/react-dom': 19.0.3(@types/react@19.0.8)
|
||||
|
||||
'@radix-ui/react-popover@1.1.6(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.1
|
||||
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.0.8)(react@19.0.0)
|
||||
'@radix-ui/react-context': 1.1.1(@types/react@19.0.8)(react@19.0.0)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.8)(react@19.0.0)
|
||||
'@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-id': 1.1.0(@types/react@19.0.8)(react@19.0.0)
|
||||
'@radix-ui/react-popper': 1.2.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-portal': 1.1.4(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-presence': 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@radix-ui/react-slot': 1.1.2(@types/react@19.0.8)(react@19.0.0)
|
||||
'@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.8)(react@19.0.0)
|
||||
aria-hidden: 1.2.4
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
react-remove-scroll: 2.6.3(@types/react@19.0.8)(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.0.8
|
||||
'@types/react-dom': 19.0.3(@types/react@19.0.8)
|
||||
|
||||
'@radix-ui/react-popper@1.2.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@ -3563,6 +3617,8 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
is-data-view: 1.0.2
|
||||
|
||||
date-fns@4.1.0: {}
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@ -4568,6 +4624,11 @@ snapshots:
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
react-day-picker@8.10.1(date-fns@4.1.0)(react@19.0.0):
|
||||
dependencies:
|
||||
date-fns: 4.1.0
|
||||
react: 19.0.0
|
||||
|
||||
react-dom@19.0.0(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
1
frontend/public/placeholder.svg
Normal file
1
frontend/public/placeholder.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in New Issue
Block a user