feat: refactor RoomCard and RoomList components, add SkeletonRoomCard for loading state
This commit is contained in:
parent
85deb66a54
commit
f54a8ccc0a
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
30
src/app/components/Room/SkeletonRoomCard.tsx
Normal file
30
src/app/components/Room/SkeletonRoomCard.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user