feat: refactor RoomCard and RoomList components, add SkeletonRoomCard for loading state

This commit is contained in:
Rémi 2025-01-04 16:11:05 +01:00
parent 85deb66a54
commit f54a8ccc0a
5 changed files with 122 additions and 151 deletions

View File

@ -1,6 +1,6 @@
"use client"; 'use client';
import { parseDate, Time } from "@internationalized/date"; import { parseDate, Time } from '@internationalized/date';
import { import {
Button, Button,
@ -9,66 +9,80 @@ import {
CardHeader, CardHeader,
DateInput, DateInput,
Divider, Divider,
TimeInput, TimeInput
} from "@nextui-org/react"; } from '@nextui-org/react';
import { Room } from "./Room"; import { Room } from './Room';
import moment from "moment"; import moment from 'moment';
import { useRouter } from "next/navigation"; import { useRouter } from 'next/navigation';
export const RoomCard = ({ export const RoomCard = ({ id, name, date, Times, Presentator }: Room) => {
id,
name,
date,
Times,
Presentator
}: Room) => {
const router = useRouter(); const router = useRouter();
return ( return (
<Card className="w-[300px]"> <Card className='w-[300px]'>
<CardHeader> <CardHeader>
<div className="flex flex-col"> <div className='flex flex-col'>
<p className="text-md">{name}</p> <p className='text-md'>{name}</p>
<p className="text-small text-default-500">{Presentator.username}</p> <p className='text-small text-default-500'>
{Presentator.username}
</p>
</div> </div>
</CardHeader> </CardHeader>
<Divider /> <Divider />
<CardBody> <CardBody>
{Times.map((time) => ( {Times.map(time => (
<div className="flex flex-col gap-2" key={`${time.id}`}> <div className='flex flex-col gap-2' key={`${time.id}`}>
<DateInput isReadOnly label="Date" value={parseDate(moment(date).format("YYYY-MM-DD"))} /> <DateInput
<div className="flex items-center gap-2"> isReadOnly
label='Date'
value={parseDate(moment(date).format('YYYY-MM-DD'))}
/>
<div className='flex items-center gap-2'>
<TimeInput <TimeInput
isReadOnly isReadOnly
label="Start" label='Start'
hourCycle={24} hourCycle={24}
value={ value={
new Time(moment(time.startTime).hours(), moment(time.startTime).minutes()) new Time(
moment(time.startTime).hours(),
moment(time.startTime).minutes()
)
} }
/> />
<span>-</span> <span>-</span>
<TimeInput <TimeInput
isReadOnly isReadOnly
label="End" label='End'
hourCycle={24} hourCycle={24}
value={new Time(moment(time.endTime).hours(), moment(time.endTime).minutes())} value={
new Time(
moment(time.endTime).hours(),
moment(time.endTime).minutes()
)
}
/> />
</div> </div>
</div> </div>
))} ))}
</CardBody> </CardBody>
<div className="flex p-2"> {moment(date).dayOfYear() === moment().dayOfYear() && (
<Button <div className='flex p-2'>
className={"bg-transparent text-foreground border-default-200"} <Button
color="primary" className={
radius="full" ''
size="sm" }
variant={"bordered"} color='primary'
onPress={() => { radius='full'
router.push(`/room/${id}`); size='sm'
}} variant={'flat'}
>Join</Button> onPress={() => {
</div> router.push(`/room/${id}`);
}}
>
Join
</Button>
</div>
)}
</Card> </Card>
); );
}; };

View File

@ -2,6 +2,7 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { RoomCard } from './Card'; import { RoomCard } from './Card';
import { Room } from './Room'; import { Room } from './Room';
import { SkeletonRoomCard } from './SkeletonRoomCard';
export const RoomList = ({ rooms }: { rooms: Room[] }) => { export const RoomList = ({ rooms }: { rooms: Room[] }) => {
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -9,14 +10,17 @@ export const RoomList = ({ rooms }: { rooms: Room[] }) => {
const handleWheel = (event: WheelEvent) => { const handleWheel = (event: WheelEvent) => {
if (event.deltaY === 0) return; if (event.deltaY === 0) return;
if (event.ctrlKey || event.shiftKey || event.altKey) return; if (event.ctrlKey || event.shiftKey || event.altKey) return;
const scrollContainer = scrollContainerRef.current; const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return; if (!scrollContainer) return;
const goLeft = event.deltaY < 0; const goLeft = event.deltaY < 0;
const isEnd = goLeft ? scrollContainer.scrollLeft === 0 : scrollContainer.scrollLeft + scrollContainer.clientWidth >= scrollContainer.scrollWidth; const isEnd = goLeft
? scrollContainer.scrollLeft === 0
: scrollContainer.scrollLeft + scrollContainer.clientWidth >=
scrollContainer.scrollWidth;
if (isEnd) return; if (isEnd) return;
event.preventDefault(); event.preventDefault();
const scrollAmount = 10; const scrollAmount = 10;
@ -49,17 +53,27 @@ export const RoomList = ({ rooms }: { rooms: Room[] }) => {
className='overflow-x-auto scrollbar-hide rounded-xl bg-default-100' className='overflow-x-auto scrollbar-hide rounded-xl bg-default-100'
> >
<ul className='flex'> <ul className='flex'>
{rooms.map(room => ( {rooms?.length > 0 ? (
<li key={room.id} className='p-2'> rooms.map(room => (
<RoomCard <li key={room.id} className='p-2'>
id={room.id} <RoomCard
name={room.name} id={room.id}
date={room.date} name={room.name}
Presentator={room.Presentator} date={room.date}
Times={room.Times} Presentator={room.Presentator}
/> Times={room.Times}
</li> />
))} </li>
))
) : (
<>
{Array.from({ length: 5 }).map((_, i) => (
<li key={i} className='p-2'>
<SkeletonRoomCard />
</li>
))}
</>
)}
</ul> </ul>
</div> </div>
); );

View File

@ -0,0 +1,30 @@
"use client"
import { Card, Skeleton, Divider } from "@nextui-org/react"
export const SkeletonRoomCard = () => {
return (
<Card className='w-[200px] space-y-5 p-4' radius='lg'>
<div className='flex flex-col gap-2'>
<Skeleton className='w-4/5 rounded-lg'>
<div className='h-3 w-4/5 rounded-lg bg-default-200' />
</Skeleton>
<Skeleton className='w-2/5 rounded-lg'>
<div className='h-3 w-2/5 rounded-lg bg-default-300' />
</Skeleton>
</div>
<Divider />
<Skeleton className='rounded-lg'>
<div className='h-12 rounded-lg bg-default-300' />
</Skeleton>
<div className='flex items-center gap-2'>
<Skeleton className='rounded-lg w-1/2'>
<div className='h-10 rounded-lg bg-default-300' />
</Skeleton>
<span>-</span>
<Skeleton className='rounded-lg w-1/2'>
<div className='h-10 rounded-lg bg-default-300' />
</Skeleton>
</div>
</Card>
)
}

View File

@ -1,19 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer utilities {
.scrollbar::-webkit-scrollbar {
height: 10px;
}
.scrollbar::-webkit-scrollbar-track {
border-radius: 100vh;
background: #5a5a5a54;
}
.scrollbar::-webkit-scrollbar-thumb {
background: #414141;
border-radius: 100vh;
}
}

View File

@ -7,34 +7,6 @@ import { RoomList } from './components/Room/List';
import { Header } from './components/Header'; import { Header } from './components/Header';
import { axiosInstance } from './lib/axios'; import { axiosInstance } from './lib/axios';
const SkeletonCard = () => {
return (
<Card className='w-[200px] space-y-5 p-4' radius='lg'>
<div className='flex flex-col gap-2'>
<Skeleton className='w-4/5 rounded-lg'>
<div className='h-3 w-4/5 rounded-lg bg-default-200' />
</Skeleton>
<Skeleton className='w-2/5 rounded-lg'>
<div className='h-3 w-2/5 rounded-lg bg-default-300' />
</Skeleton>
</div>
<Divider />
<Skeleton className='rounded-lg'>
<div className='h-12 rounded-lg bg-default-300' />
</Skeleton>
<div className='flex items-center gap-2'>
<Skeleton className='rounded-lg w-1/2'>
<div className='h-10 rounded-lg bg-default-300' />
</Skeleton>
<span>-</span>
<Skeleton className='rounded-lg w-1/2'>
<div className='h-10 rounded-lg bg-default-300' />
</Skeleton>
</div>
</Card>
);
};
const HomePage = () => { const HomePage = () => {
const [roomsLoading, setRoomsLoading] = useState(true); const [roomsLoading, setRoomsLoading] = useState(true);
const [rooms, setRooms] = useState<{ const [rooms, setRooms] = useState<{
@ -80,61 +52,18 @@ const HomePage = () => {
<> <>
<Header /> <Header />
<main className='flex flex-col gap-8 p-4'> <main className='flex flex-col gap-8 p-4'>
{roomsLoading ? ( <section className='flex flex-col gap-2'>
<> <h2 className='font-semibold text-lg'>Upcoming</h2>
<section className='flex flex-col gap-2'> <RoomList rooms={rooms.future} />
<h2 className='font-semibold text-lg'> </section>
Cours a venir <section className='flex flex-col gap-2'>
</h2> <h2 className='font-semibold text-lg'>Current</h2>
<div className='flex gap-4'> <RoomList rooms={rooms.actual} />
<SkeletonCard /> </section>
<SkeletonCard /> <section className='flex flex-col gap-2'>
<SkeletonCard /> <h2 className='font-semibold text-lg'>Past</h2>
</div> <RoomList rooms={rooms.past} />
</section> </section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>
Cours actuels
</h2>
<div className='flex gap-4'>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
</div>
</section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>
Cours passés
</h2>
<div className='flex gap-4'>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
</div>
</section>
</>
) : (
<>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>
Cours a venir
</h2>
<RoomList rooms={rooms.future} />
</section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>
Cours actuels
</h2>
<RoomList rooms={rooms.actual} />
</section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>
Cours passés
</h2>
<RoomList rooms={rooms.past} />
</section>
</>
)}
</main> </main>
</> </>
); );