ref: format

This commit is contained in:
Rémi 2025-01-04 16:11:22 +01:00
parent f54a8ccc0a
commit b81a058a1c
18 changed files with 484 additions and 474 deletions

View File

@ -1,4 +1,4 @@
'use client'
"use client";
import {
Avatar,
Button,
@ -9,79 +9,85 @@ import {
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem
} from '@nextui-org/react'
import { useSession } from 'next-auth/react'
import { ThemeSwitcher } from '../ThemeSwitcher/ThemeSwitcher'
import { axiosInstance } from '@/app/lib/axios'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
NavbarItem,
} from "@nextui-org/react";
import { useSession } from "next-auth/react";
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
import { axiosInstance } from "@/app/lib/axios";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
const getInitials = (name: string) => {
if (!name) return ''
if (!name) return "";
const nameParts = name.split(' ')
const nameParts = name.split(" ");
if (nameParts.length === 1) {
return name
return name;
}
const firstInitial = nameParts[0]?.[0] || ''
const secondInitial = nameParts[1]?.[0] || nameParts[0]?.[1] || ''
const firstInitial = nameParts[0]?.[0] || "";
const secondInitial = nameParts[1]?.[0] || nameParts[0]?.[1] || "";
return firstInitial + secondInitial
}
return firstInitial + secondInitial;
};
export const Header = () => {
const { data: session } = useSession()
const router = useRouter()
const { data: session } = useSession();
const router = useRouter();
const [userProfile, setUserProfile] = useState<{
id: string
username: string
role: 'ADMIN' | 'STUDENT'
}>()
id: string;
username: string;
role: "ADMIN" | "STUDENT";
}>();
const initials = session?.user?.name ? getInitials(session.user.name) : ''
const initials = session?.user?.name ? getInitials(session.user.name) : "";
const fetchUserProfile = async () => {
return await axiosInstance<{
id: string
username: string
role: 'ADMIN' | 'STUDENT'
}>('/@me')
}
id: string;
username: string;
role: "ADMIN" | "STUDENT";
}>("/@me");
};
useEffect(() => {
fetchUserProfile().then(r => {
setUserProfile(r.data)
})
}, [])
fetchUserProfile().then((r) => {
setUserProfile(r.data);
});
}, []);
return (
<Navbar className='mb-2'>
<Navbar className="mb-2">
<NavbarBrand>
<p className='font-bold text-inherit'>Toogether</p>
<p className="font-bold text-inherit">Toogether</p>
</NavbarBrand>
<NavbarContent as='div' justify='end'>
<Dropdown placement='bottom-end'>
<NavbarContent as="div" justify="end">
<Dropdown placement="bottom-end">
<DropdownTrigger>
<Avatar
isBordered
as='button'
className='transition-transform'
color='secondary'
as="button"
className="transition-transform"
color="secondary"
name={initials}
size='sm'
size="sm"
/>
</DropdownTrigger>
<DropdownMenu aria-label='Profile Actions' variant='flat'>
<DropdownItem key='profile' className='h-14 gap-2'>
<DropdownMenu aria-label="Profile Actions" variant="flat">
<DropdownItem key="profile" className="h-14 gap-2">
<p>Signed in as</p>
<p className='font-semibold'>{session?.user?.name}</p>
<p className="font-semibold">
{session?.user?.name}
</p>
</DropdownItem>
<DropdownItem key='settings'>Settings</DropdownItem>
<DropdownItem key='logout' color='danger' href='/auth/logout'>
<DropdownItem key="settings">Settings</DropdownItem>
<DropdownItem
key="logout"
color="danger"
href="/auth/logout"
>
Logout
</DropdownItem>
</DropdownMenu>
@ -89,12 +95,14 @@ export const Header = () => {
<NavbarItem>
<ThemeSwitcher />
</NavbarItem>
{userProfile?.role === 'ADMIN' ? (
{userProfile?.role === "ADMIN" ? (
<NavbarItem>
<Button onPress={() => router.push('/admin')}>🔧</Button>
<Button onPress={() => router.push("/admin")}>
🔧
</Button>
</NavbarItem>
) : null}
</NavbarContent>
</Navbar>
)
}
);
};

View File

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

View File

@ -1,8 +1,8 @@
'use client';
import { useEffect, useRef } from 'react';
import { RoomCard } from './Card';
import { Room } from './Room';
import { SkeletonRoomCard } from './SkeletonRoomCard';
"use client";
import { useEffect, useRef } from "react";
import { RoomCard } from "./Card";
import { Room } from "./Room";
import { SkeletonRoomCard } from "./SkeletonRoomCard";
export const RoomList = ({ rooms }: { rooms: Room[] }) => {
const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -40,22 +40,22 @@ export const RoomList = ({ rooms }: { rooms: Room[] }) => {
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;
scrollContainer.addEventListener('wheel', handleWheel);
scrollContainer.addEventListener("wheel", handleWheel);
return () => {
scrollContainer.removeEventListener('wheel', handleWheel);
scrollContainer.removeEventListener("wheel", handleWheel);
};
}, []);
return (
<div
ref={scrollContainerRef}
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?.length > 0 ? (
rooms.map(room => (
<li key={room.id} className='p-2'>
rooms.map((room) => (
<li key={room.id} className="p-2">
<RoomCard
id={room.id}
name={room.name}
@ -68,7 +68,7 @@ export const RoomList = ({ rooms }: { rooms: Room[] }) => {
) : (
<>
{Array.from({ length: 5 }).map((_, i) => (
<li key={i} className='p-2'>
<li key={i} className="p-2">
<SkeletonRoomCard />
</li>
))}

View File

@ -13,5 +13,5 @@ export interface Room {
username: string;
role: string;
createdAt: string;
}
};
}

View File

@ -1,30 +1,30 @@
"use client"
import { Card, Skeleton, Divider } from "@nextui-org/react"
"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' />
<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 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 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' />
<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 className="rounded-lg w-1/2">
<div className="h-10 rounded-lg bg-default-300" />
</Skeleton>
</div>
</Card>
)
}
);
};

View File

@ -1,21 +1,21 @@
'use client'
import { Button } from '@nextui-org/react'
import { useTheme } from 'next-themes'
import { useEffect, useState } from 'react'
"use client";
import { Button } from "@nextui-org/react";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export const ThemeSwitcher = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true)
}, [])
setMounted(true);
}, []);
if (!mounted) return null
if (!mounted) return null;
return (
<Button onPress={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
{theme === 'light' ? '🌑' : '☀️'}
<Button onPress={() => setTheme(theme === "light" ? "dark" : "light")}>
{theme === "light" ? "🌑" : "☀️"}
</Button>
)
}
);
};

View File

@ -1,3 +1,3 @@
export const UppercaseFirstLetter = (str: string) => {
return str.slice(0, 1).toLocaleUpperCase() + str.slice(1);
}
};

View File

@ -1,11 +1,11 @@
'use client';
import { Card, Divider, Skeleton } from '@nextui-org/react';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { Room } from './components/Room/Room';
import { RoomList } from './components/Room/List';
import { Header } from './components/Header';
import { axiosInstance } from './lib/axios';
"use client";
import { Card, Divider, Skeleton } from "@nextui-org/react";
import moment from "moment";
import { useEffect, useState } from "react";
import { Room } from "./components/Room/Room";
import { RoomList } from "./components/Room/List";
import { Header } from "./components/Header";
import { axiosInstance } from "./lib/axios";
const HomePage = () => {
const [roomsLoading, setRoomsLoading] = useState(true);
@ -16,30 +16,30 @@ const HomePage = () => {
}>({
future: [],
actual: [],
past: []
past: [],
});
useEffect(() => {
axiosInstance
.get<{ id: string; name: string; createdAt: string }[]>(
'/@me/class'
)
.then(classResponse => {
.get<
{ id: string; name: string; createdAt: string }[]
>("/@me/class")
.then((classResponse) => {
if (classResponse.data.length)
axiosInstance
.get<Room[]>(
`/@me/class/${classResponse.data[0].id}/rooms`
)
.then(classes => {
.get<
Room[]
>(`/@me/class/${classResponse.data[0].id}/rooms`)
.then((classes) => {
// Filter rooms by date, get future, actual and past rooms
const future = classes.data.filter(room =>
moment(room.date).isAfter(moment(), 'day')
const future = classes.data.filter((room) =>
moment(room.date).isAfter(moment(), "day"),
);
const actual = classes.data.filter(room =>
moment(room.date).isSame(moment(), 'day')
const actual = classes.data.filter((room) =>
moment(room.date).isSame(moment(), "day"),
);
const past = classes.data.filter(room =>
moment(room.date).isBefore(moment())
const past = classes.data.filter((room) =>
moment(room.date).isBefore(moment()),
);
setRooms({ future, actual, past });
@ -51,17 +51,17 @@ const HomePage = () => {
return (
<>
<Header />
<main className='flex flex-col gap-8 p-4'>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>Upcoming</h2>
<main className="flex flex-col gap-8 p-4">
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Upcoming</h2>
<RoomList rooms={rooms.future} />
</section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>Current</h2>
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Current</h2>
<RoomList rooms={rooms.actual} />
</section>
<section className='flex flex-col gap-2'>
<h2 className='font-semibold text-lg'>Past</h2>
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Past</h2>
<RoomList rooms={rooms.past} />
</section>
</main>

View File

@ -41,7 +41,9 @@ export const authOptions: AuthOptions = {
async jwt({ token, account, user }) {
if (account && user) {
token.accessToken = account.access_token;
token.accessTokenExpires = moment(account.expires_at * 1000).subtract(5, "s");
token.accessTokenExpires = moment(
account.expires_at * 1000,
).subtract(5, "s");
token.refreshToken = account.refresh_token;
token.user = user;
return token;
@ -67,7 +69,7 @@ export const authOptions: AuthOptions = {
pages: {
signIn: "/auth/login",
signOut: "/auth/logout",
}
},
};
const refreshAccessToken = async (token: JWT): Promise<JWT> => {
@ -88,19 +90,21 @@ const refreshAccessToken = async (token: JWT): Promise<JWT> => {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
}
},
);
if (response.status != 200) {
throw new Error(
response.data.error_description || "Failed to refresh access token"
response.data.error_description || "Failed to refresh access token",
);
}
return {
...token,
accessToken: response.data.access_token,
accessTokenExpires: moment().add(response.data.expires_in, "seconds").subtract(5, "s"),
accessTokenExpires: moment()
.add(response.data.expires_in, "seconds")
.subtract(5, "s"),
refreshToken: response.data.refresh_token,
};
};