diff --git a/bun.lock b/bun.lock
index 38ffadb..1a58771 100644
--- a/bun.lock
+++ b/bun.lock
@@ -17,6 +17,7 @@
"react-dom": "19.2.3",
"shadcn": "^3.6.3",
"sonner": "^2.0.7",
+ "swiper": "^12.1.2",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0",
"ua-parser-js": "^2.0.8",
@@ -1328,6 +1329,8 @@
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
+ "swiper": ["swiper@12.1.2", "", {}, "sha512-4gILrI3vXZqoZh71I1PALqukCFgk+gpOwe1tOvz5uE9kHtl2gTDzmYflYCwWvR4LOvCrJi6UEEU+gnuW5BtkgQ=="],
+
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
diff --git a/features/home/index.tsx b/features/home/index.tsx
index 65eb3c1..2de1d38 100644
--- a/features/home/index.tsx
+++ b/features/home/index.tsx
@@ -1,7 +1,11 @@
-"use client";
+import Hero from "./sections/Hero/wrapper";
const HomeIndex = () => {
- return
HomePage
;
+ return (
+
+
+
+ );
};
export default HomeIndex;
diff --git a/features/home/sections/Hero/components/Swiper.tsx b/features/home/sections/Hero/components/Swiper.tsx
new file mode 100644
index 0000000..82239d8
--- /dev/null
+++ b/features/home/sections/Hero/components/Swiper.tsx
@@ -0,0 +1,118 @@
+"use client";
+import "swiper/css";
+import { Badge } from "@/shared/libs/shadcn/ui/badge";
+import { Button } from "@/shared/libs/shadcn/ui/button";
+import { useRouter } from "next/navigation";
+import { Autoplay, Navigation, Pagination } from "swiper/modules";
+import { Swiper, SwiperSlide } from "swiper/react";
+
+export interface HeroSwiperProps {
+ data: {
+ id: string;
+ isClickable: boolean;
+ title: string;
+ tags: string[];
+ description: string;
+ buttonContent: string;
+ buttonLink: string;
+ imageUrl: string;
+ startDate: string;
+ endDate: string;
+ }[];
+}
+
+const HeroSwiper = (props: HeroSwiperProps) => {
+ const router = useRouter();
+ return (
+
+
console.log("slide change")}
+ onSwiper={(swiper) => console.log(swiper)}
+ className="h-full"
+ autoplay={{ delay: 5000, disableOnInteraction: false }}
+ modules={[Autoplay, Pagination, Navigation]}
+ >
+ {props.data.map((slide) =>
+ slide.imageUrl ? (
+ // Slide with image background
+
+
+ {slide.title && slide.description && (
+
+
+ {slide.title}
+
+
+ {slide.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ {slide.description}
+
+ {slide.isClickable && (
+
+ )}
+
+ )}
+
+ ) : (
+ // Fallback for slides without image
+
+
+ {slide.title}
+
+
+ {slide.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ {slide.description}
+
+ {slide.isClickable && (
+
+ )}
+
+ ),
+ )}
+
+
+ );
+};
+
+export default HeroSwiper;
diff --git a/features/home/sections/Hero/main.tsx b/features/home/sections/Hero/main.tsx
new file mode 100644
index 0000000..1261741
--- /dev/null
+++ b/features/home/sections/Hero/main.tsx
@@ -0,0 +1,17 @@
+import { backendFetch, BackendResponse } from "@/shared/helpers/backendFetch";
+import HeroSwiper, { HeroSwiperProps } from "./components/Swiper";
+
+const HeroMain = async () => {
+ const testing = async () => {
+ return (await backendFetch("hero-banner")) as BackendResponse<
+ HeroSwiperProps["data"]
+ >;
+ };
+
+ const response = await testing();
+ if (!response.data) return ;
+
+ return ;
+};
+
+export default HeroMain;
diff --git a/features/home/sections/Hero/skeleton.tsx b/features/home/sections/Hero/skeleton.tsx
new file mode 100644
index 0000000..ad7825c
--- /dev/null
+++ b/features/home/sections/Hero/skeleton.tsx
@@ -0,0 +1,8 @@
+import { Skeleton } from "@/shared/libs/shadcn/ui/skeleton";
+import React from "react";
+
+const HeroSkeleton = () => {
+ return ;
+};
+
+export default HeroSkeleton;
diff --git a/features/home/sections/Hero/wrapper.tsx b/features/home/sections/Hero/wrapper.tsx
new file mode 100644
index 0000000..f8aae8a
--- /dev/null
+++ b/features/home/sections/Hero/wrapper.tsx
@@ -0,0 +1,15 @@
+import { Suspense } from "react";
+import HeroSkeleton from "./skeleton";
+import HeroMain from "./main";
+
+const Hero = () => {
+ return (
+
+ }>
+
+
+
+ );
+};
+
+export default Hero;
diff --git a/package.json b/package.json
index 74d3a25..016837d 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"react-dom": "19.2.3",
"shadcn": "^3.6.3",
"sonner": "^2.0.7",
+ "swiper": "^12.1.2",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.4.0",
"ua-parser-js": "^2.0.8"
diff --git a/shared/libs/shadcn/ui/badge.tsx b/shared/libs/shadcn/ui/badge.tsx
new file mode 100644
index 0000000..6d11b09
--- /dev/null
+++ b/shared/libs/shadcn/ui/badge.tsx
@@ -0,0 +1,49 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/shared/libs/shadcn/lib/utils"
+
+const badgeVariants = cva(
+ "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
+ secondary:
+ "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
+ destructive:
+ "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
+ outline:
+ "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
+ ghost:
+ "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : "span"
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/shared/libs/shadcn/ui/button.tsx b/shared/libs/shadcn/ui/button.tsx
index 9704063..c38a3ba 100644
--- a/shared/libs/shadcn/ui/button.tsx
+++ b/shared/libs/shadcn/ui/button.tsx
@@ -1,29 +1,36 @@
-import * as React from "react"
-import { cva, type VariantProps } from "class-variance-authority"
-import { Slot } from "radix-ui"
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+import { Slot } from "radix-ui";
-import { cn } from "@/shared/libs/shadcn/lib/utils"
+import { cn } from "@/shared/libs/shadcn/lib/utils";
const buttonVariants = cva(
- "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 aria-invalid:ring-3 cursor-pointer [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/80",
- outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
- ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
- destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
+ outline:
+ "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground shadow-xs",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost:
+ "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
+ destructive:
+ "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
- default: "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
+ default:
+ "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5",
lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
icon: "size-9",
- "icon-xs": "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
- "icon-sm": "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
+ "icon-xs":
+ "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3",
+ "icon-sm":
+ "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md",
"icon-lg": "size-10",
},
},
@@ -31,8 +38,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
- }
-)
+ },
+);
function Button({
className,
@@ -42,9 +49,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps & {
- asChild?: boolean
+ asChild?: boolean;
}) {
- const Comp = asChild ? Slot.Root : "button"
+ const Comp = asChild ? Slot.Root : "button";
return (
- )
+ );
}
-export { Button, buttonVariants }
+export { Button, buttonVariants };
diff --git a/shared/libs/shadcn/ui/skeleton.tsx b/shared/libs/shadcn/ui/skeleton.tsx
new file mode 100644
index 0000000..76f9fcd
--- /dev/null
+++ b/shared/libs/shadcn/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/shared/libs/shadcn/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/shared/types/swiper.d.ts b/shared/types/swiper.d.ts
new file mode 100644
index 0000000..6c975f1
--- /dev/null
+++ b/shared/types/swiper.d.ts
@@ -0,0 +1,2 @@
+declare module "swiper/css";
+declare module "swiper/css/*";