@@ -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;
+}