Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.85.5",
"@tanstack/react-table": "^8.21.3",
"@tiptap/extension-image": "^3.2.0",
"@tiptap/extension-link": "^3.2.0",
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/app/[locale]/(public)/(user)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function UserLayout({ children }: UserLayoutProps) {
<ProtectedRoute>
<section className="container mx-auto px-5 pt-24 pb-28">
<div className="grid grid-cols-5 gap-8">
<aside className="fixed right-0 bottom-0 left-0 col-span-1 mt-8 flex w-full flex-col justify-between gap-4 self-start rounded-lg bg-white lg:sticky lg:top-24 lg:flex-col lg:justify-start lg:bg-transparent dark:bg-slate-950">
<aside className="mt-8 hidden w-full flex-col justify-between gap-4 self-start rounded-lg lg:sticky lg:top-24 lg:col-span-1 lg:flex lg:flex-col lg:justify-start lg:bg-transparent">
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src="" alt="profile" />
Expand All @@ -43,7 +43,7 @@ export default function UserLayout({ children }: UserLayoutProps) {
</nav>
</aside>

<div className="col-span-4">{children}</div>
<div className="col-span-5 lg:col-span-4">{children}</div>
</div>
</section>
</ProtectedRoute>
Expand Down
13 changes: 8 additions & 5 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { cookies } from "next/headers";
import { AuthJwtPayload } from "@/types";
import { jwtDecode } from "jwt-decode";
import { ThemeProvider } from "@/components/provider/ThemeProvider";
import TanstackProvider from "@/components/provider/TanstackProvider";
const sora = Sora({ subsets: ["latin"] });

type Props = {
Expand Down Expand Up @@ -57,11 +58,13 @@ export default async function LocaleRootLayout(props: Readonly<Props>) {
<html lang={locale} suppressHydrationWarning>
<body className={`${sora.className}`}>
<NextIntlClientProvider messages={messages}>
<AuthProvider payload={payload}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</AuthProvider>
<TanstackProvider>
<AuthProvider payload={payload}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</AuthProvider>
</TanstackProvider>
<Toaster />
</NextIntlClientProvider>
</body>
Expand Down
10 changes: 10 additions & 0 deletions src/components/common/Loader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { LoaderIcon } from "lucide-react";

const Loader = () => {
return (
<div className="flex h-[45vh] items-center justify-center">
<LoaderIcon className="size-12 animate-spin" />
</div>
);
};
export default Loader;
10 changes: 10 additions & 0 deletions src/components/common/NotFoundData/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FileX } from "lucide-react";

export const NotFoundData = ({ message = "No data found" }: { message?: string }) => {
return (
<div className="flex flex-col items-center justify-center py-12 text-center text-gray-500">
<FileX className="mb-4 h-12 w-12 text-gray-400" />
<p className="text-lg font-medium">{message}</p>
</div>
);
};
10 changes: 10 additions & 0 deletions src/components/provider/TanstackProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

const TanstackProvider = ({ children }: { children: React.ReactNode }) => {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};
export default TanstackProvider;
4 changes: 4 additions & 0 deletions src/components/ui/Badge/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const badgeVariants = cva(
soon: "border-transparent bg-green-500 text-white hover:bg-green-600",
closed: "border-transparent bg-red-500 text-white hover:bg-red-600",
open: "border-transparent bg-blue-500 text-white hover:bg-blue-600",
// for payment status
PENDING: "border-transparent bg-yellow-500 text-white hover:bg-yellow-600",
SUCCESS: "border-transparent bg-green-500 text-white hover:bg-green-600",
FAILED: "border-transparent bg-red-500 text-white hover:bg-red-600",
},
},
defaultVariants: {
Expand Down
1 change: 1 addition & 0 deletions src/components/ui/Toaster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
}
toastOptions={{
classNames: {
success: "!bg-green-500 !text-white",
error: "!bg-destructive !text-white",
description: "!text-muted-foreground",
},
Expand Down
1 change: 1 addition & 0 deletions src/constants/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const EVENTS_TYPE = ["Conference", "Tech Talk", "Ngobar"];
30 changes: 23 additions & 7 deletions src/domains/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ export const eventSchema = z.object({
id: z.number(),
title: z.string(),
description: z.string(),
slug: z.string().optional(),
author: z.string(),
image: z.string(),
date: z.string().optional(),
type: z.string(),
location: z.string(),
duration: z.string(),
capacity: z.number(),
status: z.enum(["open", "soon", "closed"]),
Tags: z
status: z.enum(["open", "soon", "closed", "comming soon"]),
tags: z
.array(
z.object({
id: z.number(),
Expand All @@ -21,7 +22,7 @@ export const eventSchema = z.object({
})
)
.optional(),
Speakers: z
speakers: z
.array(
z.object({
id: z.number(),
Expand All @@ -32,14 +33,12 @@ export const eventSchema = z.object({
.optional(),
registration_link: z.string(),
price: z.number(),
created_by: z.number(),
updated_by: z.number(),
deleted_by: z.number(),
reservation_start_date: z.string().optional(),
reseveration_end_date: z.string().optional(),
reservation_end_date: z.string().optional(),
created_at: z.string(),
updated_at: z.string().optional(),
deleted_at: z.string().optional(),
additional_link: z.string().optional(),
});

export type EventType = z.infer<typeof eventSchema>;
Expand All @@ -61,3 +60,20 @@ export const registrationSchema = z.object({
});

export type RegistrationForm = z.infer<typeof registrationSchema>;

export const userEventSchema = z.object({
id: z.number(),
order_no: z.string(),
event_id: z.number(),
user_id: z.string(),
name: z.string(),
email: z.string(),
phone_number: z.string(),
image_proof_payment: z.string().url(),
payment_date: z.string(),
status: z.enum(["PENDING", "SUCCESS", "FAILED"]),
created_at: z.string(),
event_detail: eventSchema,
});

export type UserEventType = z.infer<typeof userEventSchema>;
56 changes: 27 additions & 29 deletions src/features/events/components/MyEventCard.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,54 @@
import { FC } from "react";
import Image from "next/image";
import { Clock, Pin } from "lucide-react";
import Badge from "@/components/ui/Badge";
import { Card, CardContent, CardFooter } from "@/components/ui/Card";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/Tooltip";
import { EventType } from "@/domains/Events";
import { useFormatDate } from "@/lib/format";
import { Link } from "@/lib/navigation";
import { UserEventType } from "@/domains/Events";
import { useFormatDateEvent } from "@/lib/format";

const MyEventCard: FC<{ data: EventType }> = ({ data }) => {
const { id, title, date, image, status, duration, location } = data;
const MyEventCard = ({ data }: { data: UserEventType }) => {
const { order_no, status } = data;

return (
<Link href={`/events/${id}`}>
<section>
<div className="grid gap-4 lg:grid-cols-4">
<div className="bg-muted overflow-hidden rounded-lg">
<div className="relative flex items-center justify-center overflow-hidden rounded-lg border">
<div
className="absolute inset-0 scale-110 bg-cover bg-center blur-xs"
style={{
backgroundImage: `url(${data.event_detail.image ?? "/assets/images/events/fallbackImage.webp"})`,
}}
/>
<Image
alt={String(id)}
src={image ?? "/assets/images/events/fallbackImage.webp"}
alt={`Event ${order_no}`}
src={data.event_detail.image ?? "/assets/images/events/fallbackImage.webp"}
width={540}
height={240}
className="object-cover object-center"
className="relative z-10 object-cover object-center"
/>
</div>
<Card className="flex size-full flex-col rounded-lg border shadow-md lg:col-span-3">
<CardContent className="px-4 pt-4 pb-0">
<Badge className="mb-2" variant={`${status}`}>
{status}
</Badge>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<h2 className="text-hmc-blue-600 line-clamp-2 text-base font-bold sm:text-xl">{title}</h2>
</TooltipTrigger>
<TooltipContent>{title}</TooltipContent>
</Tooltip>
</TooltipProvider>
<div className="mb-2 flex items-center gap-2">
<span className="text-xs font-medium text-gray-500">#{order_no}</span>
<Badge variant={status}>{status}</Badge>
</div>

<h2 className="text-hmc-blue-600 line-clamp-2 text-base font-bold sm:text-xl">{data.event_detail.title}</h2>
</CardContent>
<CardFooter className="mt-auto flex flex-col items-start gap-2 px-4 pt-3 pb-4">
<p className="line-clamp-1">{useFormatDate(date)}</p>
<div className="flex items-center gap-2">
<Clock size={15} />
<p className="text-sm">{duration}</p>
<Clock size={15} className="text-gray-500" />
<p className="text-sm">{useFormatDateEvent(data.event_detail.date as string) || "-"}</p>
</div>
<div className="flex items-center gap-2">
<Pin size={15} />
<p className="text-sm">{location}</p>
<Pin size={15} className="text-gray-500" />
<p className="line-clamp-1 text-sm">{data.event_detail.location || "-"}</p>
</div>
</CardFooter>
</Card>
</div>
</Link>
</section>
);
};

export default MyEventCard;
38 changes: 16 additions & 22 deletions src/features/events/hooks/useMyEvent.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import { EventType } from "@/domains/Events";
import { eventsService } from "@/services/events";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";

export const useMyEvents = (page: number = 1, limit: number = 10) => {
const [myEvents, setMyEvents] = useState<EventType[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
export const useMyEvents = (page: number = 1, limit: number = 10, type: string) => {
const typeMemo = useMemo(() => {
return type === "all" ? "" : type;
}, [type]);

const getEvents = async () => {
setIsLoading(true);
try {
const res = await eventsService.getMyEvents(page, limit);
setMyEvents(res.data);
} catch (err) {
toast.error(err instanceof Error ? err.message : "Something went wrong.");
} finally {
setIsLoading(false);
}
};

useEffect(() => {
getEvents();
}, [toast]);
const { data, isLoading, error } = useQuery({
queryKey: ["getListMyEvents", page, limit, type],
queryFn: async () => eventsService.getMyEvents(page, limit, typeMemo),
});

return { myEvents, isLoading };
return {
myEvents: data?.data || [],
paginationMyEvents: data?.pagination,
isLoading,
error,
};
};
Loading