From e3211d240aa5b34279c88c5955d85e94e60c00e5 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Thu, 12 Mar 2026 12:00:00 +0700 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20add=20recommendation?= =?UTF-8?q?=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/actions/getRecommenationAnime.ts | 116 ++++++++++++++++++ features/home/index.tsx | 2 + .../home/sections/Recommendation/main.tsx | 13 ++ .../home/sections/Recommendation/skeleton.tsx | 5 + .../home/sections/Recommendation/wrapper.tsx | 20 +++ 5 files changed, 156 insertions(+) create mode 100644 features/home/actions/getRecommenationAnime.ts create mode 100644 features/home/sections/Recommendation/main.tsx create mode 100644 features/home/sections/Recommendation/skeleton.tsx create mode 100644 features/home/sections/Recommendation/wrapper.tsx diff --git a/features/home/actions/getRecommenationAnime.ts b/features/home/actions/getRecommenationAnime.ts new file mode 100644 index 0000000..36277e4 --- /dev/null +++ b/features/home/actions/getRecommenationAnime.ts @@ -0,0 +1,116 @@ +"use server"; + +export const getRecommendationAnimeAction = async () => { + await new Promise((resolve) => setTimeout(resolve, 2000)); + + return [ + { + title: "Frieren: Beyond Journey's End", + rating: 9.39, + type: "TV", + status: "finished", + episodes: 28, + release_year: "2023", + thumbnail_url: "https://example.com/images/frieren.jpg", + }, + { + title: "Steins;Gate", + rating: 9.07, + type: "TV", + status: "finished", + episodes: 24, + release_year: "2011", + thumbnail_url: "https://example.com/images/steinsgate.jpg", + }, + { + title: "Spirited Away", + rating: 8.78, + type: "Movie", + status: "finished", + episodes: 1, + release_year: "2001", + thumbnail_url: "https://example.com/images/spirited-away.jpg", + }, + { + title: "One Piece", + rating: 8.72, + type: "TV", + status: "airing", + episodes: 1100, + release_year: "1999", + thumbnail_url: "https://example.com/images/onepiece.jpg", + }, + { + title: "Cyberpunk: Edgerunners", + rating: 8.6, + type: "ONA", + status: "finished", + episodes: 10, + release_year: "2022", + thumbnail_url: "https://example.com/images/edgerunners.jpg", + }, + { + title: "Your Name", + rating: 8.85, + type: "Movie", + status: "finished", + episodes: 1, + release_year: "2016", + thumbnail_url: "https://example.com/images/yourname.jpg", + }, + { + title: "Hunter x Hunter (2011)", + rating: 9.04, + type: "TV", + status: "finished", + episodes: 148, + release_year: "2011", + thumbnail_url: "https://example.com/images/hxh.jpg", + }, + { + title: "Hellsing Ultimate", + rating: 8.36, + type: "OVA", + status: "finished", + episodes: 10, + release_year: "2006", + thumbnail_url: "https://example.com/images/hellsing.jpg", + }, + { + title: "Tower of God Season 2", + rating: 7.5, + type: "TV", + status: "airing", + episodes: 12, + release_year: "2024", + thumbnail_url: "https://example.com/images/tog-s2.jpg", + }, + { + title: "Violet Evergarden: The Movie", + rating: 8.89, + type: "Movie", + status: "finished", + episodes: 1, + release_year: "2020", + thumbnail_url: "https://example.com/images/violet-movie.jpg", + }, + { + title: "Devilman Crybaby", + rating: 7.75, + type: "ONA", + status: "finished", + episodes: 10, + release_year: "2018", + thumbnail_url: "https://example.com/images/devilman.jpg", + }, + { + title: "Mobile Suit Gundam: The Origin", + rating: 8.42, + type: "OVA", + status: "finished", + episodes: 6, + release_year: "2015", + thumbnail_url: "https://example.com/images/gundam-origin.jpg", + }, + ]; +}; diff --git a/features/home/index.tsx b/features/home/index.tsx index 2de1d38..b09d8e5 100644 --- a/features/home/index.tsx +++ b/features/home/index.tsx @@ -1,9 +1,11 @@ import Hero from "./sections/Hero/wrapper"; +import Recommendation from "./sections/Recommendation/wrapper"; const HomeIndex = () => { return (
+
); }; diff --git a/features/home/sections/Recommendation/main.tsx b/features/home/sections/Recommendation/main.tsx new file mode 100644 index 0000000..0be7ce7 --- /dev/null +++ b/features/home/sections/Recommendation/main.tsx @@ -0,0 +1,13 @@ +import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime"; + +const RecommendationMain = async () => { + const data = async () => await getRecommendationAnimeAction(); + const result = await data(); + return ( +
+
{JSON.stringify(result)}
+
+ ); +}; + +export default RecommendationMain; diff --git a/features/home/sections/Recommendation/skeleton.tsx b/features/home/sections/Recommendation/skeleton.tsx new file mode 100644 index 0000000..4e69a90 --- /dev/null +++ b/features/home/sections/Recommendation/skeleton.tsx @@ -0,0 +1,5 @@ +const RecommendationSkeleton = () => { + return
loading...
; +}; + +export default RecommendationSkeleton; diff --git a/features/home/sections/Recommendation/wrapper.tsx b/features/home/sections/Recommendation/wrapper.tsx new file mode 100644 index 0000000..923b533 --- /dev/null +++ b/features/home/sections/Recommendation/wrapper.tsx @@ -0,0 +1,20 @@ +import { Suspense } from "react"; +import RecommendationMain from "./main"; +import RecommendationSkeleton from "./skeleton"; +import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime"; + +const Recommendation = async () => { + return ( +
+

+ Maybe You Like +
+

+ }> + + +
+ ); +}; + +export default Recommendation; -- 2.49.0 From c02832674b2f08b6d264606b28d15bc46cbe87e1 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Fri, 13 Mar 2026 12:00:00 +0700 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20add=20image=20support?= =?UTF-8?q?=20to=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/globals.css | 22 +++++++++ .../home/actions/getRecommenationAnime.ts | 45 +++++++++++++------ features/home/index.tsx | 2 +- .../Recommendation/components/Card.tsx | 18 ++++++++ .../home/sections/Recommendation/main.tsx | 7 ++- 5 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 features/home/sections/Recommendation/components/Card.tsx diff --git a/app/globals.css b/app/globals.css index f4040d4..d5c556e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -124,3 +124,25 @@ @apply bg-background text-foreground; } } + +/* ===== Scrollbar CSS ===== */ +/* Firefox */ +* { + scrollbar-width: auto; + scrollbar-color: #4a4a4a #0a0a0a; +} + +/* Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 12px; +} + +*::-webkit-scrollbar-track { + background: #0a0a0a; +} + +*::-webkit-scrollbar-thumb { + background-color: #4a4a4a; + border-radius: 9px; + border: 3px none #000000; +} diff --git a/features/home/actions/getRecommenationAnime.ts b/features/home/actions/getRecommenationAnime.ts index 36277e4..fbe220b 100644 --- a/features/home/actions/getRecommenationAnime.ts +++ b/features/home/actions/getRecommenationAnime.ts @@ -1,7 +1,19 @@ "use server"; -export const getRecommendationAnimeAction = async () => { - await new Promise((resolve) => setTimeout(resolve, 2000)); +export type RecommendationAnime = { + title: string; + rating?: number; + type: string; + status: string; + episodes: number; + release_year: string; + thumbnail_url: string; +}; + +export const getRecommendationAnimeAction = async (): Promise< + RecommendationAnime[] +> => { + // await new Promise((resolve) => setTimeout(resolve, 2000)); return [ { @@ -11,7 +23,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 28, release_year: "2023", - thumbnail_url: "https://example.com/images/frieren.jpg", + thumbnail_url: "https://m.media-amazon.com/images/I/816AbVQc+0L.jpg", }, { title: "Steins;Gate", @@ -20,7 +32,8 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 24, release_year: "2011", - thumbnail_url: "https://example.com/images/steinsgate.jpg", + thumbnail_url: + "https://m.media-amazon.com/images/M/MV5BZjI1YjZiMDUtZTI3MC00YTA5LWIzMmMtZmQ0NTZiYWM4NTYwXkEyXkFqcGc@._V1_FMjpg_UX1000_.jpg", }, { title: "Spirited Away", @@ -29,7 +42,8 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 1, release_year: "2001", - thumbnail_url: "https://example.com/images/spirited-away.jpg", + thumbnail_url: + "https://printedoriginals.com/cdn/shop/products/spirited-away-french-143975.jpg?v=1602427397", }, { title: "One Piece", @@ -38,7 +52,7 @@ export const getRecommendationAnimeAction = async () => { status: "airing", episodes: 1100, release_year: "1999", - thumbnail_url: "https://example.com/images/onepiece.jpg", + thumbnail_url: "https://myanimelist.net/images/anime/1244/138851.jpg", }, { title: "Cyberpunk: Edgerunners", @@ -47,7 +61,8 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 10, release_year: "2022", - thumbnail_url: "https://example.com/images/edgerunners.jpg", + thumbnail_url: + "https://myanimelist.net/images/about_me/ranking_items/14292440-859e4272-536e-4760-845f-78fb48eccafe.jpg?t=1767555420", }, { title: "Your Name", @@ -56,7 +71,8 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 1, release_year: "2016", - thumbnail_url: "https://example.com/images/yourname.jpg", + thumbnail_url: + "https://m.media-amazon.com/images/M/MV5BMjM4YTE3OGEtYTY1OS00ZWEzLTg1OTctMTkyODA0ZDM3ZmJlXkEyXkFqcGc@._V1_.jpg", }, { title: "Hunter x Hunter (2011)", @@ -65,7 +81,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 148, release_year: "2011", - thumbnail_url: "https://example.com/images/hxh.jpg", + thumbnail_url: "https://myanimelist.net/images/anime/1337/99013.jpg", }, { title: "Hellsing Ultimate", @@ -74,7 +90,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 10, release_year: "2006", - thumbnail_url: "https://example.com/images/hellsing.jpg", + thumbnail_url: "https://cdn.myanimelist.net/images/anime/6/7333l.jpg", }, { title: "Tower of God Season 2", @@ -83,7 +99,8 @@ export const getRecommendationAnimeAction = async () => { status: "airing", episodes: 12, release_year: "2024", - thumbnail_url: "https://example.com/images/tog-s2.jpg", + thumbnail_url: + "https://www.animationmagazine.net/wordpress/wp-content/uploads/TOG2_ENLOGO_v2.jpg", }, { title: "Violet Evergarden: The Movie", @@ -92,7 +109,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 1, release_year: "2020", - thumbnail_url: "https://example.com/images/violet-movie.jpg", + thumbnail_url: "https://myanimelist.net/images/anime/1614/106512l.jpg", }, { title: "Devilman Crybaby", @@ -101,7 +118,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 10, release_year: "2018", - thumbnail_url: "https://example.com/images/devilman.jpg", + thumbnail_url: "https://myanimelist.net/images/anime/1046/122722.jpg", }, { title: "Mobile Suit Gundam: The Origin", @@ -110,7 +127,7 @@ export const getRecommendationAnimeAction = async () => { status: "finished", episodes: 6, release_year: "2015", - thumbnail_url: "https://example.com/images/gundam-origin.jpg", + thumbnail_url: "https://myanimelist.net/images/anime/4/72702.jpg", }, ]; }; diff --git a/features/home/index.tsx b/features/home/index.tsx index b09d8e5..baea98a 100644 --- a/features/home/index.tsx +++ b/features/home/index.tsx @@ -3,7 +3,7 @@ import Recommendation from "./sections/Recommendation/wrapper"; const HomeIndex = () => { return ( -
+
diff --git a/features/home/sections/Recommendation/components/Card.tsx b/features/home/sections/Recommendation/components/Card.tsx new file mode 100644 index 0000000..8606de4 --- /dev/null +++ b/features/home/sections/Recommendation/components/Card.tsx @@ -0,0 +1,18 @@ +import { RecommendationAnime } from "@/features/home/actions/getRecommenationAnime"; + +const AnimeRecommendationCard = ({ data }: { data: RecommendationAnime }) => { + return ( +
+
+ {data.title} +
+
+ ); +}; + +export default AnimeRecommendationCard; diff --git a/features/home/sections/Recommendation/main.tsx b/features/home/sections/Recommendation/main.tsx index 0be7ce7..f7ef6e2 100644 --- a/features/home/sections/Recommendation/main.tsx +++ b/features/home/sections/Recommendation/main.tsx @@ -1,11 +1,14 @@ import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime"; +import AnimeRecommendationCard from "./components/Card"; const RecommendationMain = async () => { const data = async () => await getRecommendationAnimeAction(); const result = await data(); return ( -
-
{JSON.stringify(result)}
+
+ {result.map((item, index) => ( + + ))}
); }; -- 2.49.0 From 8393e6393cc8271a45d154a6343999f7dc2724eb Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sat, 14 Mar 2026 12:00:00 +0700 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=9A=A7=20wip:=20add=20rating=20to=20c?= =?UTF-8?q?ard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/home/sections/Recommendation/components/Card.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/features/home/sections/Recommendation/components/Card.tsx b/features/home/sections/Recommendation/components/Card.tsx index 8606de4..d7708d0 100644 --- a/features/home/sections/Recommendation/components/Card.tsx +++ b/features/home/sections/Recommendation/components/Card.tsx @@ -1,9 +1,15 @@ import { RecommendationAnime } from "@/features/home/actions/getRecommenationAnime"; +import { Icon } from "@iconify/react"; +import { StarOff } from "lucide-react"; const AnimeRecommendationCard = ({ data }: { data: RecommendationAnime }) => { return (
-
+
+
+ + {data.rating ?? "N/A"} +
Date: Sun, 15 Mar 2026 21:11:20 +0700 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=91=94=20feat:=20add=20title=20and=20?= =?UTF-8?q?additional=20attributes=20to=20card?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(session)/(main)/layout.tsx | 2 +- app/globals.css | 51 +++++++++++++++++++ .../home/actions/getRecommenationAnime.ts | 3 +- .../Recommendation/components/Card.tsx | 22 ++++++-- .../home/sections/Recommendation/main.tsx | 2 +- .../home/sections/Recommendation/wrapper.tsx | 10 ++-- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/app/(session)/(main)/layout.tsx b/app/(session)/(main)/layout.tsx index fb2280e..5b83505 100644 --- a/app/(session)/(main)/layout.tsx +++ b/app/(session)/(main)/layout.tsx @@ -3,7 +3,7 @@ import React from "react"; const layout = ({ children }: Readonly<{ children: React.ReactNode }>) => { return ( -
+
{children}
diff --git a/app/globals.css b/app/globals.css index d5c556e..4998592 100644 --- a/app/globals.css +++ b/app/globals.css @@ -146,3 +146,54 @@ border-radius: 9px; border: 3px none #000000; } + +@keyframes aircraft-strobe { + /* Kedipan 1: Agak lambat/lama */ + 0%, + 20% { + opacity: 1; + } + + /* Jeda singkat */ + 35% { + opacity: 0; + } + + /* Kedipan 2: Cepat */ + 40% { + opacity: 1; + } + 43% { + opacity: 0; + } + + /* Jeda sangat singkat */ + 48% { + opacity: 0; + } + + /* Kedipan 3: Cepat */ + 53% { + opacity: 1; + } + 56% { + opacity: 0; + } + + /* Jeda panjang sebelum mengulang loop (gelap) */ + 100% { + opacity: 1; + } +} + +/* Class untuk diterapkan ke elemen */ +.blink-strobe { + animation: aircraft-strobe 2s linear infinite; +} + +.hide-scrollbar { + scrollbar-width: none; /* Firefox */ +} +.hide-scrollbar::-webkit-scrollbar { + display: none; /* Chrome, Edge, Safari */ +} diff --git a/features/home/actions/getRecommenationAnime.ts b/features/home/actions/getRecommenationAnime.ts index fbe220b..a05b646 100644 --- a/features/home/actions/getRecommenationAnime.ts +++ b/features/home/actions/getRecommenationAnime.ts @@ -121,7 +121,8 @@ export const getRecommendationAnimeAction = async (): Promise< thumbnail_url: "https://myanimelist.net/images/anime/1046/122722.jpg", }, { - title: "Mobile Suit Gundam: The Origin", + title: + "Mobile Suit Gundam: The Origin (lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua)", rating: 8.42, type: "OVA", status: "finished", diff --git a/features/home/sections/Recommendation/components/Card.tsx b/features/home/sections/Recommendation/components/Card.tsx index d7708d0..d4662c4 100644 --- a/features/home/sections/Recommendation/components/Card.tsx +++ b/features/home/sections/Recommendation/components/Card.tsx @@ -1,14 +1,24 @@ import { RecommendationAnime } from "@/features/home/actions/getRecommenationAnime"; import { Icon } from "@iconify/react"; -import { StarOff } from "lucide-react"; const AnimeRecommendationCard = ({ data }: { data: RecommendationAnime }) => { return (
-
+ {data.status === "airing" && ( +
+ + Airing +
+ )} +
- {data.rating ?? "N/A"} + + {data.rating ?? "N/A"} +
{ draggable={false} />
+
+

{data.title}

+
+ {data.release_year} +
+
); }; diff --git a/features/home/sections/Recommendation/main.tsx b/features/home/sections/Recommendation/main.tsx index f7ef6e2..430821c 100644 --- a/features/home/sections/Recommendation/main.tsx +++ b/features/home/sections/Recommendation/main.tsx @@ -5,7 +5,7 @@ const RecommendationMain = async () => { const data = async () => await getRecommendationAnimeAction(); const result = await data(); return ( -
+
{result.map((item, index) => ( ))} diff --git a/features/home/sections/Recommendation/wrapper.tsx b/features/home/sections/Recommendation/wrapper.tsx index 923b533..caf67b2 100644 --- a/features/home/sections/Recommendation/wrapper.tsx +++ b/features/home/sections/Recommendation/wrapper.tsx @@ -1,15 +1,15 @@ import { Suspense } from "react"; import RecommendationMain from "./main"; import RecommendationSkeleton from "./skeleton"; -import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime"; const Recommendation = async () => { return (
-

- Maybe You Like -
-

+
+

+ Maybe You Like +

+
}> -- 2.49.0 From 97ef74e0f772d4a0fd39d48849717f7ac57c1252 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sun, 15 Mar 2026 21:25:53 +0700 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=92=84=20style:=20add=20scroll=20butt?= =?UTF-8?q?on=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScrollingButton.tsx | 21 +++++ .../home/sections/Recommendation/wrapper.tsx | 6 +- shared/libs/shadcn/ui/button-group.tsx | 83 +++++++++++++++++++ shared/libs/shadcn/ui/separator.tsx | 2 +- 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 features/home/sections/Recommendation/components/ScrollingButton.tsx create mode 100644 shared/libs/shadcn/ui/button-group.tsx diff --git a/features/home/sections/Recommendation/components/ScrollingButton.tsx b/features/home/sections/Recommendation/components/ScrollingButton.tsx new file mode 100644 index 0000000..05c8725 --- /dev/null +++ b/features/home/sections/Recommendation/components/ScrollingButton.tsx @@ -0,0 +1,21 @@ +import { Button } from "@/shared/libs/shadcn/ui/button"; +import { ButtonGroup } from "@/shared/libs/shadcn/ui/button-group"; +import { ArrowLeft, ArrowRight } from "lucide-react"; +import React from "react"; + +const ScrollingButton = () => { + return ( +
+ + + + +
+ ); +}; + +export default ScrollingButton; diff --git a/features/home/sections/Recommendation/wrapper.tsx b/features/home/sections/Recommendation/wrapper.tsx index caf67b2..c4d15aa 100644 --- a/features/home/sections/Recommendation/wrapper.tsx +++ b/features/home/sections/Recommendation/wrapper.tsx @@ -1,14 +1,18 @@ import { Suspense } from "react"; import RecommendationMain from "./main"; import RecommendationSkeleton from "./skeleton"; +import ScrollingButton from "./components/ScrollingButton"; const Recommendation = async () => { return (
-
+

Maybe You Like

+
+ +
}> diff --git a/shared/libs/shadcn/ui/button-group.tsx b/shared/libs/shadcn/ui/button-group.tsx new file mode 100644 index 0000000..9a022ff --- /dev/null +++ b/shared/libs/shadcn/ui/button-group.tsx @@ -0,0 +1,83 @@ +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/shared/libs/shadcn/lib/utils" +import { Separator } from "@/shared/libs/shadcn/ui/separator" + +const buttonGroupVariants = cva( + "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1", + { + variants: { + orientation: { + horizontal: + "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-r-md!", + vertical: + "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none [&>[data-slot]:not(:has(~[data-slot]))]:rounded-b-md!", + }, + }, + defaultVariants: { + orientation: "horizontal", + }, + } +) + +function ButtonGroup({ + className, + orientation, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function ButtonGroupText({ + className, + asChild = false, + ...props +}: React.ComponentProps<"div"> & { + asChild?: boolean +}) { + const Comp = asChild ? Slot.Root : "div" + + return ( + + ) +} + +function ButtonGroupSeparator({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + ButtonGroup, + ButtonGroupSeparator, + ButtonGroupText, + buttonGroupVariants, +} diff --git a/shared/libs/shadcn/ui/separator.tsx b/shared/libs/shadcn/ui/separator.tsx index 99a09e3..4d1d21f 100644 --- a/shared/libs/shadcn/ui/separator.tsx +++ b/shared/libs/shadcn/ui/separator.tsx @@ -17,7 +17,7 @@ function Separator({ decorative={decorative} orientation={orientation} className={cn( - "bg-border shrink-0 data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", + "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch", className )} {...props} -- 2.49.0 From 74ad82c4f0dcdc5777ecffa91128dec9ae169896 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sun, 15 Mar 2026 22:23:41 +0700 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20scroll=20button?= =?UTF-8?q?=20and=20card=20skeleton=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../home/actions/getRecommenationAnime.ts | 2 +- .../components/ScrollingButton.tsx | 13 ++++-- .../sections/Recommendation/main.client.tsx | 46 +++++++++++++++++++ .../home/sections/Recommendation/main.tsx | 10 +--- .../home/sections/Recommendation/skeleton.tsx | 16 ++++++- .../home/sections/Recommendation/wrapper.tsx | 5 +- 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 features/home/sections/Recommendation/main.client.tsx diff --git a/features/home/actions/getRecommenationAnime.ts b/features/home/actions/getRecommenationAnime.ts index a05b646..c69cee6 100644 --- a/features/home/actions/getRecommenationAnime.ts +++ b/features/home/actions/getRecommenationAnime.ts @@ -13,7 +13,7 @@ export type RecommendationAnime = { export const getRecommendationAnimeAction = async (): Promise< RecommendationAnime[] > => { - // await new Promise((resolve) => setTimeout(resolve, 2000)); + await new Promise((resolve) => setTimeout(resolve, 2000)); return [ { diff --git a/features/home/sections/Recommendation/components/ScrollingButton.tsx b/features/home/sections/Recommendation/components/ScrollingButton.tsx index 05c8725..ad30a98 100644 --- a/features/home/sections/Recommendation/components/ScrollingButton.tsx +++ b/features/home/sections/Recommendation/components/ScrollingButton.tsx @@ -1,16 +1,21 @@ import { Button } from "@/shared/libs/shadcn/ui/button"; import { ButtonGroup } from "@/shared/libs/shadcn/ui/button-group"; import { ArrowLeft, ArrowRight } from "lucide-react"; -import React from "react"; -const ScrollingButton = () => { +const ScrollingButton = ({ + scrollLeft, + scrollRight, +}: { + scrollLeft: () => void; + scrollRight: () => void; +}) => { return (
- - diff --git a/features/home/sections/Recommendation/main.client.tsx b/features/home/sections/Recommendation/main.client.tsx new file mode 100644 index 0000000..5fe7faa --- /dev/null +++ b/features/home/sections/Recommendation/main.client.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useRef } from "react"; +import { RecommendationAnime } from "../../actions/getRecommenationAnime"; +import AnimeRecommendationCard from "./components/Card"; +import ScrollingButton from "./components/ScrollingButton"; +import { Skeleton } from "@/shared/libs/shadcn/ui/skeleton"; + +const RecommendationClient = ({ + result, +}: { + result: RecommendationAnime[]; +}) => { + const scrollingContainer = useRef(null); + + const scrollLeft = () => { + console.log("scroll left"); + if (scrollingContainer.current) { + scrollingContainer.current.scrollBy({ left: -788, behavior: "smooth" }); + } + }; + const scrollRight = () => { + console.log("scroll right"); + if (scrollingContainer.current) { + scrollingContainer.current.scrollBy({ left: 788, behavior: "smooth" }); + } + }; + + return ( +
+
+ +
+
+ {result.map((item, index) => ( + + ))} +
+
+ ); +}; + +export default RecommendationClient; diff --git a/features/home/sections/Recommendation/main.tsx b/features/home/sections/Recommendation/main.tsx index 430821c..e4d3229 100644 --- a/features/home/sections/Recommendation/main.tsx +++ b/features/home/sections/Recommendation/main.tsx @@ -1,16 +1,10 @@ import { getRecommendationAnimeAction } from "../../actions/getRecommenationAnime"; -import AnimeRecommendationCard from "./components/Card"; +import RecommendationClient from "./main.client"; const RecommendationMain = async () => { const data = async () => await getRecommendationAnimeAction(); const result = await data(); - return ( -
- {result.map((item, index) => ( - - ))} -
- ); + return ; }; export default RecommendationMain; diff --git a/features/home/sections/Recommendation/skeleton.tsx b/features/home/sections/Recommendation/skeleton.tsx index 4e69a90..ed77a66 100644 --- a/features/home/sections/Recommendation/skeleton.tsx +++ b/features/home/sections/Recommendation/skeleton.tsx @@ -1,5 +1,19 @@ +import { Skeleton } from "@/shared/libs/shadcn/ui/skeleton"; + const RecommendationSkeleton = () => { - return
loading...
; + const skeletonLenght = 6; + + return ( +
+ {[...Array(skeletonLenght)].map((_, index) => ( +
+ + + +
+ ))} +
+ ); }; export default RecommendationSkeleton; diff --git a/features/home/sections/Recommendation/wrapper.tsx b/features/home/sections/Recommendation/wrapper.tsx index c4d15aa..24baaed 100644 --- a/features/home/sections/Recommendation/wrapper.tsx +++ b/features/home/sections/Recommendation/wrapper.tsx @@ -5,14 +5,11 @@ import ScrollingButton from "./components/ScrollingButton"; const Recommendation = async () => { return ( -
+

Maybe You Like

-
- -
}> -- 2.49.0 From eecaeb13e8e0dc552e48c22f3464762973942c56 Mon Sep 17 00:00:00 2001 From: Rafi Arrafif Date: Sun, 15 Mar 2026 22:32:21 +0700 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=9A=A8=20fix:=20resolve=20linting=20t?= =?UTF-8?q?ype=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eslint.config.mjs | 7 +++++++ features/home/sections/Recommendation/main.client.tsx | 1 - features/home/sections/Recommendation/skeleton.tsx | 2 +- features/home/sections/Recommendation/wrapper.tsx | 1 - 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d..e764709 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,6 +5,13 @@ import nextTs from "eslint-config-next/typescript"; const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, + { + rules: { + // Disable the rule that enforces the use of `next/image` for image optimization. + "@next/next/no-img-element": "off", + }, + }, + // Override default ignores of eslint-config-next. globalIgnores([ // Default ignores of eslint-config-next: diff --git a/features/home/sections/Recommendation/main.client.tsx b/features/home/sections/Recommendation/main.client.tsx index 5fe7faa..3fae04f 100644 --- a/features/home/sections/Recommendation/main.client.tsx +++ b/features/home/sections/Recommendation/main.client.tsx @@ -4,7 +4,6 @@ import { useRef } from "react"; import { RecommendationAnime } from "../../actions/getRecommenationAnime"; import AnimeRecommendationCard from "./components/Card"; import ScrollingButton from "./components/ScrollingButton"; -import { Skeleton } from "@/shared/libs/shadcn/ui/skeleton"; const RecommendationClient = ({ result, diff --git a/features/home/sections/Recommendation/skeleton.tsx b/features/home/sections/Recommendation/skeleton.tsx index ed77a66..7b9ee76 100644 --- a/features/home/sections/Recommendation/skeleton.tsx +++ b/features/home/sections/Recommendation/skeleton.tsx @@ -6,7 +6,7 @@ const RecommendationSkeleton = () => { return (
{[...Array(skeletonLenght)].map((_, index) => ( -
+
diff --git a/features/home/sections/Recommendation/wrapper.tsx b/features/home/sections/Recommendation/wrapper.tsx index 24baaed..e30071c 100644 --- a/features/home/sections/Recommendation/wrapper.tsx +++ b/features/home/sections/Recommendation/wrapper.tsx @@ -1,7 +1,6 @@ import { Suspense } from "react"; import RecommendationMain from "./main"; import RecommendationSkeleton from "./skeleton"; -import ScrollingButton from "./components/ScrollingButton"; const Recommendation = async () => { return ( -- 2.49.0