diff --git a/src/app/api/shared-timetable/[shareId]/route.ts b/src/app/api/shared-timetable/[shareId]/route.ts index 81702b5..72763ed 100644 --- a/src/app/api/shared-timetable/[shareId]/route.ts +++ b/src/app/api/shared-timetable/[shareId]/route.ts @@ -19,24 +19,30 @@ export async function GET(req: NextRequest) { } if (!timetable.isPublic) { - return NextResponse.json({ - success: false, - message: 'Timetable is private', - timetable: { - title: timetable.title, + return NextResponse.json( + { + success: false, + message: 'Timetable is private', + timetable: { + title: timetable.title, + }, }, - }); + { status: 403 } + ); } - return NextResponse.json({ - success: true, - timetable: { - title: timetable.title, - slots: timetable.slots, - owner: timetable.owner, - shareId: timetable.shareId, + return NextResponse.json( + { + success: true, + timetable: { + title: timetable.title, + slots: timetable.slots, + owner: timetable.owner, + shareId: timetable.shareId, + }, }, - }); + { status: 200 } + ); } catch { return NextResponse.json({ error: 'Server error' }, { status: 500 }); } diff --git a/src/app/denied-access/AccessDenied.tsx b/src/app/denied-access/AccessDenied.tsx new file mode 100644 index 0000000..297e799 --- /dev/null +++ b/src/app/denied-access/AccessDenied.tsx @@ -0,0 +1,54 @@ +'use client'; + +import Footer from '@/components/ui/Footer'; +import Image from 'next/image'; +import { ErrorCard } from '@/components/cards/ErrorCard'; +import Navbar from '@/components/ui/Navbar'; +import Loader from '@/components/ui/Loader'; +import useScreenSize from '@/hooks/useScreenSize'; + +export default function AccessDenied({ isMobile }: { isMobile: boolean }) { + const size = useScreenSize(); + + if (size === null) return ; + return ( +
+
+ Background +
+ + {!isMobile && } + +
+ {isMobile && ( + <> +
FFCS-inator
+
By CodeChef-VIT
+ + )} +
+ +
+ +
+ +
+
+ ); +} diff --git a/src/app/denied-access/page.tsx b/src/app/denied-access/page.tsx new file mode 100644 index 0000000..26c9c5c --- /dev/null +++ b/src/app/denied-access/page.tsx @@ -0,0 +1,11 @@ +'use client'; +import Loader from '@/components/ui/Loader'; +import useScreenSize from '@/hooks/useScreenSize'; +import AccessDenied from '@/app/denied-access/AccessDenied'; + +export default function Page() { + const size = useScreenSize(); + + if (size === null) return ; + return ; +} diff --git a/src/app/four04/404.tsx b/src/app/four04/404.tsx new file mode 100644 index 0000000..df88147 --- /dev/null +++ b/src/app/four04/404.tsx @@ -0,0 +1,47 @@ +import Navbar from '@/components/ui/Navbar'; +import Footer from '@/components/ui/Footer'; +import Image from 'next/image'; +import { ErrorCard } from '@/components/cards/ErrorCard'; + +export default function FourNotFound({ isMobile }: { isMobile: boolean }) { + return ( +
+
+ Background +
+ + {!isMobile && } + +
+ {isMobile && ( + <> +
FFCS-inator
+
By CodeChef-VIT
+ + )} + + +
+ +
+ +
+
+ ); +} diff --git a/src/app/four04/four04-mobile.tsx b/src/app/four04/four04-mobile.tsx index 2a84e35..709464d 100644 --- a/src/app/four04/four04-mobile.tsx +++ b/src/app/four04/four04-mobile.tsx @@ -1,13 +1,10 @@ 'use client'; -import { ZButton } from '@/components/ui/Buttons'; import Footer from '@/components/ui/Footer'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; +import { ErrorCard } from '@/components/cards/ErrorCard'; export default function NotFound() { - const router = useRouter(); - return (
@@ -28,43 +25,11 @@ export default function NotFound() {
By CodeChef-VIT
-
- {/*Shadow*/} - - 404 - - {/*Stroke*/} - - 404 - - {/*Fill*/} - - 404 - -
- -
- OOPS! You have found this secret page! -
- We have nothing to show here... -
- - router.push('/')} +
diff --git a/src/app/four04/four04.tsx b/src/app/four04/four04.tsx index a340b61..e3826ff 100644 --- a/src/app/four04/four04.tsx +++ b/src/app/four04/four04.tsx @@ -3,12 +3,10 @@ import Image from 'next/image'; import Navbar from '@/components/ui/Navbar'; -import { ZButton } from '@/components/ui/Buttons'; import Footer from '@/components/ui/Footer'; -import { useRouter } from 'next/navigation'; +import { ErrorCard } from '@/components/cards/ErrorCard'; export default function NotFound() { - const router = useRouter(); return (
@@ -27,43 +25,10 @@ export default function NotFound() {
-
- {/* Shadow */} - - 404 - - {/* Stroke */} - - 404 - - {/* Fill */} - - 404 - -
- -
- OOPS! You have found this secret page! -
- We have nothing to show here... -
- - router.push('/')} +
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 973e849..81b4479 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,14 +1,12 @@ 'use client'; import useScreenSize from '@/hooks/useScreenSize'; -import Four04Mobile from '@/app/four04/four04-mobile'; -import Four04 from './four04/four04'; import Loader from '@/components/ui/Loader'; +import FourNotFound from '@/app/four04/404'; export default function NotFound() { const size = useScreenSize(); if (size === null) return ; - if (size === 'mobile') return ; - return ; + return ; } diff --git a/src/app/share/[shareId]/shared-mobile.tsx b/src/app/share/[shareId]/shared-mobile.tsx index b5638ec..8522bf5 100644 --- a/src/app/share/[shareId]/shared-mobile.tsx +++ b/src/app/share/[shareId]/shared-mobile.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import Image from 'next/image'; import axios from 'axios'; @@ -42,29 +42,49 @@ export default function SharedTimetablePageMobile() { const [alertMsg, setAlertMsg] = useState(''); const [isSaving, setIsSaving] = useState(false); + const fetchTimeTable = useCallback(async () => { + try { + const res = await axios.get(`/api/shared-timetable/${shareId}`); + + const ttData = res.data; + if (Array.isArray(ttData?.timetable?.slots)) { + setTitle(ttData.timetable.title || ''); + setData( + ttData.timetable.slots.map( + (item: { courseCode: string; slot: string; facultyName: string }): dataProps => ({ + code: item.courseCode, + slot: item.slot, + name: item.facultyName, + }) + ) + ); + } else { + setNotFound(true); + } + } catch (error: unknown) { + const status = axios.isAxiosError(error) ? error.response?.status : undefined; + + if (status === 404) { + console.log('Timetable not found'); + router.push('/404'); + return; + } + + if (status === 403) { + console.log('Timetable is private'); + router.push('/denied-access'); + return; + } + + console.error('Unexpected error:', error); + setNotFound(true); + } + }, [router, shareId]); + useEffect(() => { if (!shareId) return; - axios - .get(`/api/shared-timetable/${shareId}`) - .then(res => { - const json = res.data; - if (json && json.timetable && Array.isArray(json.timetable.slots)) { - setTitle(json.timetable.title || ''); - setData( - json.timetable.slots.map( - (item: { courseCode: string; slot: string; facultyName: string }): dataProps => ({ - code: item.courseCode, - slot: item.slot, - name: item.facultyName, - }) - ) - ); - } else { - setNotFound(true); - } - }) - .catch(() => setNotFound(true)); - }, [shareId]); + fetchTimeTable(); + }, [fetchTimeTable, shareId]); if (notFound) { router.push('/404'); diff --git a/src/app/share/[shareId]/shared.tsx b/src/app/share/[shareId]/shared.tsx index 121a2cd..849040d 100644 --- a/src/app/share/[shareId]/shared.tsx +++ b/src/app/share/[shareId]/shared.tsx @@ -43,28 +43,47 @@ export default function SharedTimetablePage() { const [alertMsg, setAlertMsg] = useState(''); const [isSaving, setIsSaving] = useState(false); + const fetchTimeTable = async () => { + try { + const res = await axios.get(`/api/shared-timetable/${shareId}`); + + const ttData = res.data; + if (Array.isArray(ttData?.timetable?.slots)) { + setTitle(ttData.timetable.title || ''); + setData( + ttData.timetable.slots.map( + (item: { courseCode: string; slot: string; facultyName: string }): dataProps => ({ + code: item.courseCode, + slot: item.slot, + name: item.facultyName, + }) + ) + ); + } else { + setNotFound(true); + } + } catch (error: unknown) { + const status = axios.isAxiosError(error) ? error?.response?.status : undefined; + + if (status === 404) { + console.log('Timetable not found'); + router.push('/404'); + return; + } + + if (status === 403) { + console.log('Timetable is private'); + router.push('/denied-access'); + return; + } + console.error('Unexpected error:', error); + setNotFound(true); + } + }; + useEffect(() => { if (!shareId) return; - axios - .get(`/api/shared-timetable/${shareId}`) - .then(res => { - const json = res.data; - if (json && json.timetable && Array.isArray(json.timetable.slots)) { - setTitle(json.timetable.title || ''); - setData( - json.timetable.slots.map( - (item: { courseCode: string; slot: string; facultyName: string }): dataProps => ({ - code: item.courseCode, - slot: item.slot, - name: item.facultyName, - }) - ) - ); - } else { - setNotFound(true); - } - }) - .catch(() => setNotFound(true)); + fetchTimeTable(); }, [shareId]); if (notFound) { diff --git a/src/components/cards/ErrorCard.tsx b/src/components/cards/ErrorCard.tsx new file mode 100644 index 0000000..b5dadff --- /dev/null +++ b/src/components/cards/ErrorCard.tsx @@ -0,0 +1,74 @@ +import { ZButton } from '@/components/ui/Buttons'; +import { useRouter } from 'next/navigation'; + +type HeroMessageProps = { + bigText: string; + title: string; + subtitle: string; + mobile?: boolean; +}; + +export function ErrorCard({ bigText, title, subtitle, mobile = false }: HeroMessageProps) { + const router = useRouter(); + + const ui = mobile + ? ({ + numberSize: 'text-[96px]', + shadowOffset: 'left-1.5 top-1.5', + stroke: '8px black', + buttonType: 'regular' as const, + textSize: 'text-xl mb-16', + } as const) + : ({ + numberSize: 'text-[160px]', + shadowOffset: 'left-2 top-2', + stroke: '16px black', + buttonType: 'large' as const, + textSize: 'text-3xl mb-8', + } as const); + + return ( + <> +
+ {/*Shadow*/} + + {bigText} + + {/*Stroke*/} + + {bigText} + + {/*Fill*/} + + {bigText} + +
+ +
+ {title} +
+ {subtitle && subtitle} +
+ + router.push('/')} + /> + + ); +} diff --git a/src/types/pdfmake-vfs.d.ts b/src/types/pdfmake-vfs.d.ts new file mode 100644 index 0000000..b641adc --- /dev/null +++ b/src/types/pdfmake-vfs.d.ts @@ -0,0 +1,13 @@ +declare module 'pdfmake/build/pdfmake' { + const pdfMake: { + vfs: Record; + createPdf: (doc: unknown) => { download: (fileName?: string) => void }; + [key: string]: unknown; + }; + export default pdfMake; +} + +declare module 'pdfmake/build/vfs_fonts' { + const pdfFonts: { vfs: Record }; + export default pdfFonts; +}