From 2c54071d97142a98131e19799cca01e88190b5f3 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Thu, 23 Oct 2025 10:02:34 +0800 Subject: [PATCH 01/34] fix(frontend): fix landing page responsive design --- app/page.tsx | 94 +++++++++++++++------------------------ components/audio-wave.tsx | 32 ++++++++----- 2 files changed, 58 insertions(+), 68 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 26ef448..c5400ab 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,14 +1,9 @@ -"use client"; - import { Description, Title } from "@/components/texts"; import { Button } from "@/components/ui/button"; import { Book, Disc3 } from "lucide-react"; import AudioWave from "../components/audio-wave"; -import { Badge } from "@/components/ui/badge"; -import { useIsTablet } from "@/hooks/use-tablet"; import ANIMODEVLOGO from "@/public/animo-dev-logo.jpg"; import Image from "next/image"; -import { useEffect, useState } from "react"; import { DropdownMenu, DropdownMenuContent, @@ -18,21 +13,9 @@ import { import Link from "next/link"; const LandingPage = () => { - const isTablet = useIsTablet(); - const [discSize, setDiscSize] = useState(45); - - useEffect(() => { - if (isTablet) { - setDiscSize(30); - } else { - setDiscSize(45); - } - }, [isTablet]); - return (
-
- +
{/* HERO SECTION */}
@@ -50,14 +33,6 @@ const LandingPage = () => { musikalista logo{" "}
-

- Musikalista IoT-powered Room Tracker monitors - every entry and exit in the Music Club Room, - syncing data to this website's dashboard so - club heads and members always know who is - practicing, when the room's occupied, and - who last used it, all without manual logs. -

@@ -67,50 +42,53 @@ const LandingPage = () => { -
- - {" "} - Developed and maintained by - - {" "} - ANIMO.DEV - {" "} - - + <div className="mt-20 md:mt-0 space-y-10"> + <div className="w-full flex items-center justify-center md:justify-start lg:justify-start mb-4"> + <div className="text-white flex items-center rounded-lg text-sm text-center gap-4 px-4 py-2 shadow-md bg-gradient-to-r from-yellow-500 via-purple-500 to-pink-500 font-bold border"> + <Disc3 className=" animate-spin size-8 md:size-5" />{" "} + <span> + Developed and maintained by + <Link href={"#"} className="underline"> + {" "} + ANIMO.DEV + </Link>{" "} + </span> + <Disc3 className=" animate-spin size-8 md:size-5" />{" "} + </div> + </div> + <Title className="text-5xl sm:text-6xl lg:text-7xl mb-4 font-bold break-words text-center md:text-left"> Never <span className="text-primary"> {" "} - L - <Disc3 - className="inline animate-spin" - size={discSize} - /> - se Track{" "} + Lose Track{" "} </span>{" "} - <br className="block md:hidden" /> of a Beat, <br className="hidden lg:block" /> or - <span className="text-primary"> - {" "} - Wh - <Disc3 - className="inline animate-spin" - size={discSize} - />{" "} - <br className="block md:hidden" /> - is{" "} - </span> + <span className="text-primary"> Who is </span> in the - <span className="text-primary"> Room</span>. + <span className="text-primary"> Room.</span>{" "} - + The official website for the Iot-powered Musikalista Room Tracker
{" "} that monitors check-ins and check-outs.
+ + Musikalista IoT-powered Room Tracker monitors every + entry and exit in the Music Club Room,{" "} +
syncing data to + this website's dashboard so club heads and + members always know who is practicing,{" "} +
when the + room's occupied, and who last used it, all + without manual logs. +
- - - +
+ + + + +
diff --git a/components/audio-wave.tsx b/components/audio-wave.tsx index 8d74ac2..0545c31 100644 --- a/components/audio-wave.tsx +++ b/components/audio-wave.tsx @@ -6,23 +6,35 @@ import { useIsMobile } from "@/hooks/use-mobile"; interface AudioWaveProps { className?: string; barCount?: number; - barWidth?: number; - barGap?: number; -}; + barWidth?: number; + barGap?: number; +} export const AudioWave: React.FC = ({ className = "", - barCount = 16, barWidth = 16, barGap = 6, }) => { const isMobile = useIsMobile(); - if (isMobile) { - barCount = 10; - } - const center = (barCount - 1) / 2; - const bars = Array.from({ length: barCount }).map((_, i) => { + // don't render until after mount so useIsTablet has stabilized (avoids initial flash) + const [mounted, setMounted] = React.useState(false); + React.useEffect(() => setMounted(true), []); + + if (!mounted) + return ( + + ); + + const computedBarCount = isMobile ? 6 : 16; + + const center = (computedBarCount - 1) / 2; + const bars = Array.from({ length: computedBarCount }).map((_, i) => { const offset = Math.abs(i - center); const delayMs = Math.round(offset * 80); return ( @@ -41,7 +53,7 @@ export const AudioWave: React.FC = ({ return (
{bars} From 9b7699478e41e55c57b8cc8abf278b685cc7a933 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Thu, 23 Oct 2025 10:31:28 +0800 Subject: [PATCH 02/34] fix(frontend) fix mobile sidebar layout --- .../custom-shortcut/custom-shortcut.tsx | 20 +++++++++++++--- components/date-picker.tsx | 6 +++-- components/header.tsx | 24 +++++++++++-------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/components/custom-shortcut/custom-shortcut.tsx b/components/custom-shortcut/custom-shortcut.tsx index cf5d736..05d3b4c 100644 --- a/components/custom-shortcut/custom-shortcut.tsx +++ b/components/custom-shortcut/custom-shortcut.tsx @@ -21,15 +21,29 @@ export interface DropdownCustomItemProps { setDropdownOpen: Dispatch>; } -type CustomShortcutProps = React.ComponentProps +interface CustomShortcutProps { + children?: React.ReactNode; + variant?: "default" | "link"; + className?: string; +} -const CustomShortcut : React.FC = (props) => { +const CustomShortcut: React.FC = ({ + children, + variant, + className, +}) => { const [dropdownOpen, setDropdownOpen] = useState(false); return ( - + {children ? ( +
{children}
+ ) : ( + + )}
Custom Shortcuts diff --git a/components/date-picker.tsx b/components/date-picker.tsx index e0e91f3..19affa2 100644 --- a/components/date-picker.tsx +++ b/components/date-picker.tsx @@ -19,9 +19,10 @@ interface DatePickerProps { setState: React.Dispatch>; className? : string; onDateSelect? : (date: Date) => void; + children? : React.ReactNode; } -const DatePicker: React.FC = ({ state, setState, className, onDateSelect }) => { +const DatePicker: React.FC = ({ state, setState, className, onDateSelect, children }) => { const [popoverOpen, setPopoverOpen] = useState(false); const isFutureDate = (date: Date) => { @@ -46,7 +47,7 @@ const DatePicker: React.FC = ({ state, setState, className, onD - + From c38c719206b23f5c121e675fd77683e2c6899d89 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Thu, 23 Oct 2025 10:55:57 +0800 Subject: [PATCH 05/34] feat(frontend): add content transition wrapper to transition initial component conditional rendering --- app/layout.tsx | 9 +++++--- components/content-transition.tsx | 36 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 components/content-transition.tsx diff --git a/app/layout.tsx b/app/layout.tsx index fde59c6..698db8f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import "./globals.css"; import { Toaster } from "sonner"; import { LoadingProvider } from "@/context/loading-context"; import NextTopLoader from "nextjs-toploader"; +import ContentTransition from "../components/content-transition"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -30,9 +31,11 @@ export default function RootLayout({ - - {children} - + + + {children} + + ); diff --git a/components/content-transition.tsx b/components/content-transition.tsx new file mode 100644 index 0000000..fb8cbad --- /dev/null +++ b/components/content-transition.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { useEffect, useState } from "react"; +import type { ReactNode } from "react"; +import { cn } from "@/lib/utils"; + +type Props = { + children: ReactNode; + className?: string; +}; + +export default function ContentTransition({ children, className }: Props) { + const [ready, setReady] = useState(false); + + useEffect(() => { + // Defer to next frame so initial styles paint before we transition + const id = requestAnimationFrame(() => setReady(true)); + return () => cancelAnimationFrame(id); + }, []); + + return ( +
+ {children} +
+ ); +} From 162890daa66570026cd2cdbb547fbefdde5317bd Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Thu, 23 Oct 2025 15:29:37 +0800 Subject: [PATCH 06/34] fix(frontend): fix sidebar not closing when navigating via custom shortcuts --- app/(main)/layout.tsx | 23 +++++++------ .../custom-shortcut/custom-shortcut.tsx | 9 +++-- .../dropdown-date-range-item.tsx | 2 ++ .../dropdown-student-on-date-item.tsx | 3 ++ components/header.tsx | 11 ++++--- context/sidebar-open-context.tsx | 33 +++++++++++++++++++ 6 files changed, 65 insertions(+), 16 deletions(-) create mode 100644 context/sidebar-open-context.tsx diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index 4239760..38a1de1 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -5,6 +5,7 @@ import React from "react"; import DaysSidebarServer from "@/components/days/days-sidebar-server"; import NextTopLoader from "nextjs-toploader"; import { DatesProvider } from "@/context/dates-context"; +import { SidebarOpenProvider } from "@/context/sidebar-open-context"; export const dynamic = "force-dynamic"; @@ -16,16 +17,18 @@ const MainLayout: React.FC = ({ children }) => {
-
-
- - -
- {children} -
-
- -
+ +
+
+ + +
+ {children} +
+
+ +
+
); diff --git a/components/custom-shortcut/custom-shortcut.tsx b/components/custom-shortcut/custom-shortcut.tsx index 05d3b4c..fc64ff6 100644 --- a/components/custom-shortcut/custom-shortcut.tsx +++ b/components/custom-shortcut/custom-shortcut.tsx @@ -15,10 +15,12 @@ import { Button } from "../ui/button"; import React, { useState } from "react"; import { Dispatch, SetStateAction } from "react"; +import { useSidebarOpen } from "@/context/sidebar-open-context"; // Used by the items in the custom shortcut dropdown menu export interface DropdownCustomItemProps { setDropdownOpen: Dispatch>; + setSidebarOpen : (open: boolean) => void; } interface CustomShortcutProps { @@ -32,6 +34,9 @@ const CustomShortcut: React.FC = ({ variant, className, }) => { + // sidebar open handler + const { setSidebarOpen } = useSidebarOpen(); + const [dropdownOpen, setDropdownOpen] = useState(false); return ( @@ -48,8 +53,8 @@ const CustomShortcut: React.FC = ({ Custom Shortcuts - - + +
); diff --git a/components/custom-shortcut/dropdown-date-range-item.tsx b/components/custom-shortcut/dropdown-date-range-item.tsx index 64937de..2c9b6f3 100644 --- a/components/custom-shortcut/dropdown-date-range-item.tsx +++ b/components/custom-shortcut/dropdown-date-range-item.tsx @@ -23,6 +23,7 @@ import { toast } from "sonner"; const DropdownDateRangeItem: React.FC = ({ setDropdownOpen, + setSidebarOpen }) => { const [startDate, setStartDate] = useState(undefined); const [endDate, setEndDate] = useState(undefined); @@ -35,6 +36,7 @@ const DropdownDateRangeItem: React.FC = ({ return; } + setSidebarOpen(false); setDropdownOpen(false); const formattedStartDate = formatDateAsYYYYMMDD(startDate); diff --git a/components/custom-shortcut/dropdown-student-on-date-item.tsx b/components/custom-shortcut/dropdown-student-on-date-item.tsx index 4c287e5..0cb3ce5 100644 --- a/components/custom-shortcut/dropdown-student-on-date-item.tsx +++ b/components/custom-shortcut/dropdown-student-on-date-item.tsx @@ -39,6 +39,7 @@ import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "sonner"; + const formSchema = z.object({ studentId: z.string().min(1, "Student ID is required"), date: z.date({ error: "Date is required" }), @@ -46,6 +47,7 @@ const formSchema = z.object({ const DropdownStudentOnDateItem: React.FC = ({ setDropdownOpen, + setSidebarOpen, }) => { const [dialogOpen, setDialogOpen] = useState(false); @@ -65,6 +67,7 @@ const DropdownStudentOnDateItem: React.FC = ({ return; } + setSidebarOpen(false); setDropdownOpen(false); setDialogOpen(false); diff --git a/components/header.tsx b/components/header.tsx index 2d5302d..9696626 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -19,6 +19,7 @@ import { useMemo } from "react"; import DatePicker from "./date-picker"; import { useRouter } from "next/navigation"; import { formatDateAsYYYYMMDD } from "@/lib/utils"; +import { useSidebarOpen } from "@/context/sidebar-open-context"; import CustomShortcut from "./custom-shortcut/custom-shortcut"; const Header = () => { @@ -86,7 +87,8 @@ const DesktopHeaderContent = () => { const ICON_SIZE = 19 as const; const MobileHeaderContent = () => { - const [sheetOpen, setSheetOpen] = useState(false); + const { sidebarOpen, setSidebarOpen } = useSidebarOpen(); + const [date, setDate] = useState(undefined); const navs = useMemo( @@ -105,7 +107,8 @@ const MobileHeaderContent = () => { const handleDateSelect = (d: Date) => { if (!d) return; - setSheetOpen(false); + setSidebarOpen(false); + const formattedDate = formatDateAsYYYYMMDD(d); router.push(`/day/${formattedDate}`); }; @@ -118,7 +121,7 @@ const MobileHeaderContent = () => { - +
- ) + ); } const student = [ @@ -91,26 +94,27 @@ const StudentOnDatePage: React.FC = async ({ }); return ( -
-
+ + Student Record for {studentId} on{" "} {formatDateForRender(date)} - If data is missing, it means the student did not check in or out - on that date. Refresh the page if you believe this is an error. + If data is missing, it means the student did not check in or + out on that date. Refresh the page if you believe this is an + error. -
+ {/* STUDENT PROFILE */} -
+
{student.map(({ value, label }) => ( - {value} + {value} {label} ))} @@ -140,7 +144,7 @@ const StudentOnDatePage: React.FC = async ({
- + ); }; From ce41fadb65e96b035a5cb9c076cca8122b7839cb Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Thu, 23 Oct 2025 16:38:14 +0800 Subject: [PATCH 09/34] refactor(frontend): add reusables folder for organization --- app/(main)/account/page.tsx | 4 ++-- app/(main)/day/[date]/page.tsx | 2 +- app/(main)/home/page.tsx | 6 +++--- app/(main)/student/[studentId]/loading.tsx | 2 +- app/(main)/student/[studentId]/page.tsx | 8 ++++---- app/page.tsx | 4 ++-- .../attendance-table/attendance-table-server.tsx | 6 +++--- components/attendance-table/attendance-table.tsx | 4 ++-- components/auth/auth-form.tsx | 4 ++-- .../custom-shortcut/dropdown-date-range-item.tsx | 2 +- components/days/day-cards.tsx | 4 ++-- components/days/days-sidebar-server.tsx | 2 +- components/days/days-sidebar.tsx | 4 ++-- components/header.tsx | 2 +- .../range-attendance-table/range-attendance-table.tsx | 10 +++++----- components/{ => reusables}/alert-message.tsx | 0 components/{ => reusables}/audio-wave.tsx | 0 components/{ => reusables}/bento-container.tsx | 0 components/{ => reusables}/loader.tsx | 0 components/{ => reusables}/no-data-message.tsx | 0 components/{ => reusables}/texts.tsx | 0 components/{ => ui}/date-picker.tsx | 2 +- 22 files changed, 33 insertions(+), 33 deletions(-) rename components/{ => reusables}/alert-message.tsx (100%) rename components/{ => reusables}/audio-wave.tsx (100%) rename components/{ => reusables}/bento-container.tsx (100%) rename components/{ => reusables}/loader.tsx (100%) rename components/{ => reusables}/no-data-message.tsx (100%) rename components/{ => reusables}/texts.tsx (100%) rename components/{ => ui}/date-picker.tsx (98%) diff --git a/app/(main)/account/page.tsx b/app/(main)/account/page.tsx index e3d00ef..53156d0 100644 --- a/app/(main)/account/page.tsx +++ b/app/(main)/account/page.tsx @@ -1,8 +1,8 @@ import { BentoContainer, BentoContainerHeader, -} from "@/components/bento-container"; -import { Title, Description, SubTitle } from "@/components/texts"; +} from "@/components/reusables/bento-container"; +import { Title, Description, SubTitle } from "@/components/reusables/texts"; import AccountField from "@/components/account/account-field"; import { Button } from "@/components/ui/button"; import { LogOut } from "lucide-react"; diff --git a/app/(main)/day/[date]/page.tsx b/app/(main)/day/[date]/page.tsx index d0a9538..287a7f3 100644 --- a/app/(main)/day/[date]/page.tsx +++ b/app/(main)/day/[date]/page.tsx @@ -1,6 +1,6 @@ import { Suspense } from "react"; import AttendanceTableServer from "@/components/attendance-table/attendance-table-server"; -import Loader from "@/components/loader"; +import Loader from "@/components/reusables/loader"; interface SingleDayPageProps { // When a page component is async, Next.js may provide params as a thenable. diff --git a/app/(main)/home/page.tsx b/app/(main)/home/page.tsx index 7c95ea2..90df19c 100644 --- a/app/(main)/home/page.tsx +++ b/app/(main)/home/page.tsx @@ -3,8 +3,8 @@ import { BentoContainer, BentoContainerHeader, -} from "@/components/bento-container"; -import { Description, Title } from "@/components/texts"; +} from "@/components/reusables/bento-container"; +import { Description, Title } from "@/components/reusables/texts"; import { useDates } from "@/context/dates-context"; import { DayCards, @@ -13,7 +13,7 @@ import { } from "@/components/days/day-cards"; import { groupDatesByMonth } from "@/lib/utils"; import { ScrollArea } from "@/components/ui/scroll-area"; -import AlertMessage from "@/components/alert-message"; +import AlertMessage from "@/components/reusables/alert-message"; const HomePage = () => { const { dates } = useDates(); diff --git a/app/(main)/student/[studentId]/loading.tsx b/app/(main)/student/[studentId]/loading.tsx index fb0e670..7c3dfd9 100644 --- a/app/(main)/student/[studentId]/loading.tsx +++ b/app/(main)/student/[studentId]/loading.tsx @@ -1,4 +1,4 @@ -import Loader from "@/components/loader"; +import Loader from "@/components/reusables/loader"; const LoadingHomePage = () => { return ( diff --git a/app/(main)/student/[studentId]/page.tsx b/app/(main)/student/[studentId]/page.tsx index c879ffd..86374a2 100644 --- a/app/(main)/student/[studentId]/page.tsx +++ b/app/(main)/student/[studentId]/page.tsx @@ -16,11 +16,11 @@ import { import { BentoContainer, BentoContainerHeader, -} from "@/components/bento-container"; -import { Description, SubTitle, Title } from "@/components/texts"; +} from "@/components/reusables/bento-container"; +import { Description, SubTitle, Title } from "@/components/reusables/texts"; import { ScrollArea } from "@/components/ui/scroll-area"; -import NoDataMessage from "@/components/no-data-message"; -import AudioWave from "@/components/audio-wave"; +import NoDataMessage from "@/components/reusables/no-data-message"; +import AudioWave from "@/components/reusables/audio-wave"; interface StudentOnDatePageProps { // params of the studentId diff --git a/app/page.tsx b/app/page.tsx index c5400ab..f8e690b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,7 @@ -import { Description, Title } from "@/components/texts"; +import { Description, Title } from "@/components/reusables/texts"; import { Button } from "@/components/ui/button"; import { Book, Disc3 } from "lucide-react"; -import AudioWave from "../components/audio-wave"; +import AudioWave from "../components/reusables/audio-wave"; import ANIMODEVLOGO from "@/public/animo-dev-logo.jpg"; import Image from "next/image"; import { diff --git a/components/attendance-table/attendance-table-server.tsx b/components/attendance-table/attendance-table-server.tsx index e845f7a..471d97e 100644 --- a/components/attendance-table/attendance-table-server.tsx +++ b/components/attendance-table/attendance-table-server.tsx @@ -1,9 +1,9 @@ import { DateResponse } from "@/lib/types"; -import { BentoContainer } from "../bento-container"; -import { SubTitle, Description } from "../texts"; +import { BentoContainer } from "../reusables/bento-container"; +import { SubTitle, Description } from "../reusables/texts"; import AttendanceTable from "./attendance-table"; import { fetchJSON } from "@/lib/utils"; -import NoDataMessage from "../no-data-message"; +import NoDataMessage from "../reusables/no-data-message"; interface AttendanceTableServerProps { className?: string; diff --git a/components/attendance-table/attendance-table.tsx b/components/attendance-table/attendance-table.tsx index cbd3af4..51dbf9c 100644 --- a/components/attendance-table/attendance-table.tsx +++ b/components/attendance-table/attendance-table.tsx @@ -2,8 +2,8 @@ import { ColumnDef } from "@tanstack/react-table"; import { DataTable } from "@/components/ui/data-table"; -import { BentoContainer } from "../bento-container"; -import { Description, SubTitle } from "../texts"; +import { BentoContainer } from "../reusables/bento-container"; +import { Description, SubTitle } from "../reusables/texts"; import React from "react"; import ShareButton from "./share-button"; import { formatDateForRender, formatTimeForRender } from "@/lib/utils"; diff --git a/components/auth/auth-form.tsx b/components/auth/auth-form.tsx index 5715215..6c8c7d2 100644 --- a/components/auth/auth-form.tsx +++ b/components/auth/auth-form.tsx @@ -18,8 +18,8 @@ import { import { Input } from "@/components/ui/input"; -import { Title, Description } from "../texts"; -import { BentoContainer } from "../bento-container"; +import { Title, Description } from "../reusables/texts"; +import { BentoContainer } from "../reusables/bento-container"; const authSchema = z.object({ email: z.string().min(1, "Email is required"), diff --git a/components/custom-shortcut/dropdown-date-range-item.tsx b/components/custom-shortcut/dropdown-date-range-item.tsx index 2c9b6f3..8e9fbdb 100644 --- a/components/custom-shortcut/dropdown-date-range-item.tsx +++ b/components/custom-shortcut/dropdown-date-range-item.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import DatePicker from "../date-picker"; +import DatePicker from "../ui/date-picker"; import { Button } from "../ui/button"; import { DropdownMenuItem } from "../ui/dropdown-menu"; diff --git a/components/days/day-cards.tsx b/components/days/day-cards.tsx index ca28def..4c0733f 100644 --- a/components/days/day-cards.tsx +++ b/components/days/day-cards.tsx @@ -1,5 +1,5 @@ -import {BentoContainer} from "../bento-container"; -import { SubTitle, Description, SubHeading } from "../texts"; +import {BentoContainer} from "../reusables/bento-container"; +import { SubTitle, Description, SubHeading } from "../reusables/texts"; import { ChevronRight, Sheet } from "lucide-react"; import Link from "next/link"; import { Button } from "../ui/button"; diff --git a/components/days/days-sidebar-server.tsx b/components/days/days-sidebar-server.tsx index 7b682d0..d1d7549 100644 --- a/components/days/days-sidebar-server.tsx +++ b/components/days/days-sidebar-server.tsx @@ -1,6 +1,6 @@ import { fetchJSON, formatDatesWithIndexAsId } from "@/lib/utils"; import DaysSidebar from "./days-sidebar"; -import NoDataMessage from "../no-data-message"; +import NoDataMessage from "../reusables/no-data-message"; import { AvailableDatesResponse } from "@/lib/types"; diff --git a/components/days/days-sidebar.tsx b/components/days/days-sidebar.tsx index 69bd26f..d54b8c8 100644 --- a/components/days/days-sidebar.tsx +++ b/components/days/days-sidebar.tsx @@ -1,8 +1,8 @@ "use client"; import React, { useEffect, useMemo } from "react"; -import { Description, SubTitle } from "../texts"; -import { BentoContainer } from "../bento-container"; +import { Description, SubTitle } from "../reusables/texts"; +import { BentoContainer } from "../reusables/bento-container"; import DaysSidebarItem from "./days-sidebar-item"; import { Calendar } from "lucide-react"; import { useDates } from "@/context/dates-context"; diff --git a/components/header.tsx b/components/header.tsx index 9696626..fb958a5 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -16,7 +16,7 @@ import { import { useIsTablet } from "@/hooks/use-tablet"; import { useState } from "react"; import { useMemo } from "react"; -import DatePicker from "./date-picker"; +import DatePicker from "./ui/date-picker"; import { useRouter } from "next/navigation"; import { formatDateAsYYYYMMDD } from "@/lib/utils"; import { useSidebarOpen } from "@/context/sidebar-open-context"; diff --git a/components/range-attendance-table/range-attendance-table.tsx b/components/range-attendance-table/range-attendance-table.tsx index 69adfeb..aa69920 100644 --- a/components/range-attendance-table/range-attendance-table.tsx +++ b/components/range-attendance-table/range-attendance-table.tsx @@ -6,14 +6,14 @@ import { formatDateForRender, groupDateRangeByDay, } from "@/lib/utils"; -import Loader from "@/components/loader"; -import NoDataMessage from "../no-data-message"; +import Loader from "@/components/reusables/loader"; +import NoDataMessage from "../reusables/no-data-message"; import AttendanceTable from "../attendance-table/attendance-table"; import { ScrollArea } from "../ui/scroll-area"; -import { BentoContainer, BentoContainerHeader } from "../bento-container"; +import { BentoContainer, BentoContainerHeader } from "../reusables/bento-container"; import { AttendanceRecord, AttendanceRecordResponse } from "@/lib/types"; -import { Description, Title } from "../texts"; -import AlertMessage from "../alert-message"; +import { Description, Title } from "../reusables/texts"; +import AlertMessage from "../reusables/alert-message"; import ShareButton from "../attendance-table/share-button"; interface RangeAttendanceTableProps { diff --git a/components/alert-message.tsx b/components/reusables/alert-message.tsx similarity index 100% rename from components/alert-message.tsx rename to components/reusables/alert-message.tsx diff --git a/components/audio-wave.tsx b/components/reusables/audio-wave.tsx similarity index 100% rename from components/audio-wave.tsx rename to components/reusables/audio-wave.tsx diff --git a/components/bento-container.tsx b/components/reusables/bento-container.tsx similarity index 100% rename from components/bento-container.tsx rename to components/reusables/bento-container.tsx diff --git a/components/loader.tsx b/components/reusables/loader.tsx similarity index 100% rename from components/loader.tsx rename to components/reusables/loader.tsx diff --git a/components/no-data-message.tsx b/components/reusables/no-data-message.tsx similarity index 100% rename from components/no-data-message.tsx rename to components/reusables/no-data-message.tsx diff --git a/components/texts.tsx b/components/reusables/texts.tsx similarity index 100% rename from components/texts.tsx rename to components/reusables/texts.tsx diff --git a/components/date-picker.tsx b/components/ui/date-picker.tsx similarity index 98% rename from components/date-picker.tsx rename to components/ui/date-picker.tsx index 19affa2..397b357 100644 --- a/components/date-picker.tsx +++ b/components/ui/date-picker.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "./ui/button"; +import { Button } from "../ui/button"; import { useState } from "react"; import { Calendar as CalendarIcon } from "lucide-react"; From ec8dd1ad158b9ce711e8bcfef5db9845a017e8ee Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Fri, 24 Oct 2025 08:57:15 +0800 Subject: [PATCH 10/34] fix(frontend): remove unecessary error components and utilize next.js error.tsx --- app/(main)/error.tsx | 10 ++++ app/(main)/student/[studentId]/page.tsx | 22 +++----- .../attendance-table-server.tsx | 6 +- components/days/days-sidebar-server.tsx | 20 +++++-- components/days/days-sidebar.tsx | 20 ++++--- components/error/fetch-failed.tsx | 56 +++++++++++++++++++ .../range-attendance-table.tsx | 47 +++++++++++----- components/reusables/no-data-message.tsx | 23 -------- lib/error-types.ts | 4 ++ lib/types.ts | 3 + lib/utils.ts | 5 -- 11 files changed, 144 insertions(+), 72 deletions(-) create mode 100644 app/(main)/error.tsx create mode 100644 components/error/fetch-failed.tsx delete mode 100644 components/reusables/no-data-message.tsx create mode 100644 lib/error-types.ts diff --git a/app/(main)/error.tsx b/app/(main)/error.tsx new file mode 100644 index 0000000..6f6c537 --- /dev/null +++ b/app/(main)/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { ErrorBoundaryProps } from "@/lib/error-types"; +import FetchFailed from "@/components/error/fetch-failed"; + +const ErrorPage: React.FC = ({ error, reset }) => { + return ; +}; + +export default ErrorPage; \ No newline at end of file diff --git a/app/(main)/student/[studentId]/page.tsx b/app/(main)/student/[studentId]/page.tsx index 86374a2..2ad48de 100644 --- a/app/(main)/student/[studentId]/page.tsx +++ b/app/(main)/student/[studentId]/page.tsx @@ -19,8 +19,6 @@ import { } from "@/components/reusables/bento-container"; import { Description, SubTitle, Title } from "@/components/reusables/texts"; import { ScrollArea } from "@/components/ui/scroll-area"; -import NoDataMessage from "@/components/reusables/no-data-message"; -import AudioWave from "@/components/reusables/audio-wave"; interface StudentOnDatePageProps { // params of the studentId @@ -43,22 +41,20 @@ const StudentOnDatePage: React.FC = async ({ const data = await fetchJSON(route); // error data - if (!data.success) return ; + if (!data.success) + throw new Error("Failed to fetch student attendance data."); // success but no data if (!data.data || data.data.length === 0) { return (
- -
- - No Attendance Records for {studentId} on{" "} - {formatDateForRender(date)} - - - The student did not check in or out on this date. - -
+ + No Attendance Records for {studentId} on{" "} + {formatDateForRender(date)} + + + The student did not check in or out on this date. +
); } diff --git a/components/attendance-table/attendance-table-server.tsx b/components/attendance-table/attendance-table-server.tsx index 471d97e..2b54cc3 100644 --- a/components/attendance-table/attendance-table-server.tsx +++ b/components/attendance-table/attendance-table-server.tsx @@ -3,7 +3,6 @@ import { BentoContainer } from "../reusables/bento-container"; import { SubTitle, Description } from "../reusables/texts"; import AttendanceTable from "./attendance-table"; import { fetchJSON } from "@/lib/utils"; -import NoDataMessage from "../reusables/no-data-message"; interface AttendanceTableServerProps { className?: string; @@ -18,7 +17,8 @@ const AttendanceTableServer: React.FC = async ({ const route = `${process.env.NEXT_PUBLIC_BASE_URL}/api/reports/date/${date}`; const data = await fetchJSON(route); - if (!data.success) return + if (!data.success) + throw new Error(data.message || "Failed to fetch attendance data."); const formattedData = data.data.data?.map((item, index) => { @@ -37,7 +37,7 @@ const AttendanceTableServer: React.FC = async ({ className={className} date={date} data={formattedData} - /> + /> ); }; diff --git a/components/days/days-sidebar-server.tsx b/components/days/days-sidebar-server.tsx index d1d7549..3e0ddee 100644 --- a/components/days/days-sidebar-server.tsx +++ b/components/days/days-sidebar-server.tsx @@ -1,8 +1,7 @@ import { fetchJSON, formatDatesWithIndexAsId } from "@/lib/utils"; import DaysSidebar from "./days-sidebar"; -import NoDataMessage from "../reusables/no-data-message"; import { AvailableDatesResponse } from "@/lib/types"; - +import FetchFailed from "../error/fetch-failed"; interface DaysSidebarProps { className?: string; @@ -12,11 +11,20 @@ const DaysSidebarServer: React.FC = async ({ className }) => { const route = `${process.env.NEXT_PUBLIC_BASE_URL}/api/reports/dates`; const data = await fetchJSON(route); - if (!data.success) return + // since this component is not used by a page + // manually return the error component + if (!data.success) { + const error = new Error( + data.message || "Failed to fetch available dates" + ); + return ; + } - const formattedDates = formatDatesWithIndexAsId(data.dates); + const formattedDates = formatDatesWithIndexAsId(data.dates); - return ; + return ( + + ); }; -export default DaysSidebarServer; \ No newline at end of file +export default DaysSidebarServer; diff --git a/components/days/days-sidebar.tsx b/components/days/days-sidebar.tsx index d54b8c8..783028a 100644 --- a/components/days/days-sidebar.tsx +++ b/components/days/days-sidebar.tsx @@ -72,11 +72,16 @@ const DaysSidebar: React.FC = ({ type="always" >
    - {datesMemo.map((date) => ( - + No available dates yet. + + ) : ( + datesMemo.map((date) => ( + = ({ ? "bg-accent text-primary hover:text-primary pl-4" : "" }`} - /> - ))} + /> + )) + )}
diff --git a/components/error/fetch-failed.tsx b/components/error/fetch-failed.tsx new file mode 100644 index 0000000..3c83f42 --- /dev/null +++ b/components/error/fetch-failed.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { Frown } from "lucide-react"; +import { + BentoContainer, + BentoContainerHeader, +} from "../reusables/bento-container"; +import { twMerge } from "tailwind-merge"; +import { Description, Title } from "../reusables/texts"; +import { Button } from "../ui/button"; +import { ErrorBoundaryProps } from "@/lib/error-types"; +import { useRouter } from "next/navigation"; + +// extend the error boundary props to have the className +interface FetchFailedProps extends ErrorBoundaryProps { + className?: string; +} + +const FetchFailed: React.FC = ({ + error, + reset, + className, +}) => { + const router = useRouter(); + + return ( +
+ + + + Something went wrong <Frown /> + + {error.message} + + + +
+ ); +}; + +export default FetchFailed; diff --git a/components/range-attendance-table/range-attendance-table.tsx b/components/range-attendance-table/range-attendance-table.tsx index aa69920..21bf26b 100644 --- a/components/range-attendance-table/range-attendance-table.tsx +++ b/components/range-attendance-table/range-attendance-table.tsx @@ -7,10 +7,12 @@ import { groupDateRangeByDay, } from "@/lib/utils"; import Loader from "@/components/reusables/loader"; -import NoDataMessage from "../reusables/no-data-message"; import AttendanceTable from "../attendance-table/attendance-table"; import { ScrollArea } from "../ui/scroll-area"; -import { BentoContainer, BentoContainerHeader } from "../reusables/bento-container"; +import { + BentoContainer, + BentoContainerHeader, +} from "../reusables/bento-container"; import { AttendanceRecord, AttendanceRecordResponse } from "@/lib/types"; import { Description, Title } from "../reusables/texts"; import AlertMessage from "../reusables/alert-message"; @@ -62,8 +64,11 @@ const RangeAttendanceTable: React.FC = ({ ); } - if (!data || !data.data || data.data.length === 0) { - return ; + if (!data?.success) { + throw new Error( + data?.message || + "Failed to fetch attendance data for the date range." + ); } const groupedDates = groupDateRangeByDay(data!.data); @@ -100,22 +105,34 @@ const RangeAttendanceTable: React.FC = ({ range. - Share Record Range + + Share Record Range + - {Object.entries(formattedDates).map(([date, records]) => ( -
- + {data.data.length === 0 ? ( +
+ No Attendance Records Found + + There are no attendance records for the selected + date range. +
- ))} + ) : ( + Object.entries(formattedDates).map(([date, records]) => ( +
+ +
+ )) + )} ); diff --git a/components/reusables/no-data-message.tsx b/components/reusables/no-data-message.tsx deleted file mode 100644 index 639da2b..0000000 --- a/components/reusables/no-data-message.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { twMerge } from "tailwind-merge"; -import { BentoContainer } from "./bento-container"; - -interface NoDataMessageProps { - className?: string; -} -const NoDataMessage: React.FC = ({ className }) => { - return ( - -

- {" "} - No data. Please refresh the page if you think this is a mistake.{" "} -

-
- ); -}; - -export default NoDataMessage; diff --git a/lib/error-types.ts b/lib/error-types.ts new file mode 100644 index 0000000..d9e430d --- /dev/null +++ b/lib/error-types.ts @@ -0,0 +1,4 @@ +export interface ErrorBoundaryProps { + error : Error; + reset?: () => void; +} \ No newline at end of file diff --git a/lib/types.ts b/lib/types.ts index ced22bb..a61108e 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -47,6 +47,8 @@ export interface AvailableDatesResponse { count: number; dates: string[]; timestamp : string; + error? : string; + message? : string; } export interface AttendanceRecord { @@ -77,6 +79,7 @@ export interface DateResponse { message : string; data : SheetsGetResponse; timestamp : string; + error? : string; } export interface ExternalApiResponse { diff --git a/lib/utils.ts b/lib/utils.ts index 6f66e09..e5fb582 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -88,10 +88,5 @@ export async function fetchJSON( options?: RequestInit ): Promise { const res = await fetch(url, options); - if (!res.ok) - return { - error: `An error occurred: ${res.status} ${res.statusText}`, - } as unknown as T; - return res.json() as Promise; } From 97bbc95e334fe9583cb997064e18f5526dbc288f Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Fri, 24 Oct 2025 11:58:17 +0800 Subject: [PATCH 11/34] feat(frontend): add global 404 not found page --- app/not-found.tsx | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 app/not-found.tsx diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000..88f5e91 --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,57 @@ +import AudioWave from "@/components/reusables/audio-wave"; +import { Description, Title } from "@/components/reusables/texts"; +import { Frown } from "lucide-react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Disc3 } from "lucide-react"; + +const NotFoundPage = () => { + return ( +
+
+

+ Project Harmony +

+
+ {" "} + + Developed and maintained by + + {" "} + ANIMO.DEV + {" "} + + {" "} +
+
+ +
+ + + 404 - Page Not Found <Frown />{" "} + + + The page you’re looking for doesn’t exist. + +
+ +
+
+ + + + + + +
+ + Contact support if you believe this is an error. + +
+
+ ); +}; + +export default NotFoundPage; From 0b2d75b95cd6706099f668006eb1004d5aa210f6 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Fri, 24 Oct 2025 12:00:50 +0800 Subject: [PATCH 12/34] refactor(frontend): add animo-dev-badge component for org gradient branding --- app/not-found.tsx | 16 +++------------- app/page.tsx | 15 +++------------ components/reusables/animo-dev-badge.tsx | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 25 deletions(-) create mode 100644 components/reusables/animo-dev-badge.tsx diff --git a/app/not-found.tsx b/app/not-found.tsx index 88f5e91..0081726 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -3,7 +3,7 @@ import { Description, Title } from "@/components/reusables/texts"; import { Frown } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/button"; -import { Disc3 } from "lucide-react"; +import AnimoDevBadge from "@/components/reusables/animo-dev-badge"; const NotFoundPage = () => { return ( @@ -12,17 +12,7 @@ const NotFoundPage = () => {

Project Harmony

-
- {" "} - - Developed and maintained by - - {" "} - ANIMO.DEV - {" "} - - {" "} -
+
@@ -31,7 +21,7 @@ const NotFoundPage = () => { 404 - Page Not Found {" "} - The page you’re looking for doesn’t exist. + The page you are looking for does not exist.
diff --git a/app/page.tsx b/app/page.tsx index f8e690b..2eea7d2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { Description, Title } from "@/components/reusables/texts"; import { Button } from "@/components/ui/button"; -import { Book, Disc3 } from "lucide-react"; +import { Book } from "lucide-react"; import AudioWave from "../components/reusables/audio-wave"; import ANIMODEVLOGO from "@/public/animo-dev-logo.jpg"; import Image from "next/image"; @@ -11,6 +11,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import Link from "next/link"; +import AnimoDevBadge from "@/components/reusables/animo-dev-badge"; const LandingPage = () => { return ( @@ -44,17 +45,7 @@ const LandingPage = () => {
-
- {" "} - - Developed and maintained by - - {" "} - ANIMO.DEV - {" "} - - {" "} -
+
Never diff --git a/components/reusables/animo-dev-badge.tsx b/components/reusables/animo-dev-badge.tsx new file mode 100644 index 0000000..7da32c0 --- /dev/null +++ b/components/reusables/animo-dev-badge.tsx @@ -0,0 +1,20 @@ +import { Disc3 } from "lucide-react"; +import Link from "next/link"; + +const AnimoDevBadge = () => { + return ( + <div className="text-white flex items-center rounded-lg text-sm text-center gap-4 px-4 py-2 shadow-md bg-gradient-to-r from-yellow-500 via-purple-500 to-pink-500 font-bold border"> + <Disc3 className=" animate-spin size-8 md:size-5" />{" "} + <span> + Developed and maintained by + <Link href={"#"} className="underline"> + {" "} + ANIMO.DEV + </Link>{" "} + </span> + <Disc3 className=" animate-spin size-8 md:size-5" />{" "} + </div> + ); +}; + +export default AnimoDevBadge; From 657f519d5aed9ff66f1978b243775f41c207a20b Mon Sep 17 00:00:00 2001 From: Kent0710 <https://github.com/Eduard-K-A/temp-project-launchpad> Date: Fri, 24 Oct 2025 12:19:29 +0800 Subject: [PATCH 13/34] fix(frontend): fix range page overflowing on mobile screen --- .../range-attendance-table/range-attendance-table.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/range-attendance-table/range-attendance-table.tsx b/components/range-attendance-table/range-attendance-table.tsx index 21bf26b..885463d 100644 --- a/components/range-attendance-table/range-attendance-table.tsx +++ b/components/range-attendance-table/range-attendance-table.tsx @@ -8,7 +8,6 @@ import { } from "@/lib/utils"; import Loader from "@/components/reusables/loader"; import AttendanceTable from "../attendance-table/attendance-table"; -import { ScrollArea } from "../ui/scroll-area"; import { BentoContainer, BentoContainerHeader, @@ -88,11 +87,10 @@ const RangeAttendanceTable: React.FC<RangeAttendanceTableProps> = ({ ); return ( - <ScrollArea - className={`w-full h-full overflow-y-auto ${className}`} - type="always" + <div + className={`w-full h-full overflow-y-auto ${className}`} > - <BentoContainer className="border-none bg-background space-y-8"> + <BentoContainer className="border-none bg-background space-y-8 w-full h-full"> <BentoContainerHeader className="flex items-center justify-between"> <div> <Title> @@ -134,7 +132,7 @@ const RangeAttendanceTable: React.FC<RangeAttendanceTableProps> = ({ )) )} </BentoContainer> - </ScrollArea> + </div> ); }; From 2780e1157eec940c1084d8f3a8cdc98dbb522f8a Mon Sep 17 00:00:00 2001 From: Kent0710 <https://github.com/Eduard-K-A/temp-project-launchpad> Date: Fri, 24 Oct 2025 12:25:56 +0800 Subject: [PATCH 14/34] feat(frontend): add tabs in date range page instead of scrolling to each data table --- .../range-attendance-table.tsx | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/components/range-attendance-table/range-attendance-table.tsx b/components/range-attendance-table/range-attendance-table.tsx index 885463d..3b37dee 100644 --- a/components/range-attendance-table/range-attendance-table.tsx +++ b/components/range-attendance-table/range-attendance-table.tsx @@ -16,6 +16,8 @@ import { AttendanceRecord, AttendanceRecordResponse } from "@/lib/types"; import { Description, Title } from "../reusables/texts"; import AlertMessage from "../reusables/alert-message"; import ShareButton from "../attendance-table/share-button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { twMerge } from "tailwind-merge"; interface RangeAttendanceTableProps { startDate: string; @@ -87,52 +89,46 @@ const RangeAttendanceTable: React.FC<RangeAttendanceTableProps> = ({ ); return ( - <div - className={`w-full h-full overflow-y-auto ${className}`} + <BentoContainer + className={twMerge( + `border-none bg-background space-y-8 w-full h-full overflow-y-auto`, + className + )} > - <BentoContainer className="border-none bg-background space-y-8 w-full h-full"> - <BentoContainerHeader className="flex items-center justify-between"> - <div> - <Title> - Attendance Records from{" "} - {formatDateForRender(startDate)} to{" "} - {formatDateForRender(endDate)} - - - Click the custom button again to change the date - range. - -
- - Share Record Range - - + +
+ + Attendance Records from {formatDateForRender(startDate)}{" "} + to {formatDateForRender(endDate)} + + + Click the custom button again to change the date range. + +
+ Share Record Range +
- + - {data.data.length === 0 ? ( -
- No Attendance Records Found - - There are no attendance records for the selected - date range. - -
- ) : ( - Object.entries(formattedDates).map(([date, records]) => ( -
- -
- )) - )} - -
+ + + {Object.keys(formattedDates).map((date) => ( + + {formatDateForRender(date)} + + ))} + + {Object.entries(formattedDates).map(([date, records]) => ( + + + + ))} + + ); }; From ebc4636604e2581709b58bc7a6136c4cd8dd7d74 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Fri, 24 Oct 2025 12:40:13 +0800 Subject: [PATCH 15/34] fix(frontend): fix header not sticky on top in mobile view --- app/(main)/layout.tsx | 31 ++++++++++++++++--------------- components/header.tsx | 6 +++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx index 38a1de1..69a713c 100644 --- a/app/(main)/layout.tsx +++ b/app/(main)/layout.tsx @@ -1,36 +1,37 @@ import { LoadingProvider } from "@/context/loading-context"; +import { SidebarOpenProvider } from "@/context/sidebar-open-context"; +import { DatesProvider } from "@/context/dates-context"; import Header from "@/components/header"; -import { Toaster } from "sonner"; -import React from "react"; import DaysSidebarServer from "@/components/days/days-sidebar-server"; +import { Toaster } from "sonner"; import NextTopLoader from "nextjs-toploader"; -import { DatesProvider } from "@/context/dates-context"; -import { SidebarOpenProvider } from "@/context/sidebar-open-context"; +import React from "react"; export const dynamic = "force-dynamic"; interface MainLayoutProps { children: React.ReactNode; } + const MainLayout: React.FC = ({ children }) => { return ( -
- - - + + +
+
-
+
- -
+ +
{children}
-
- - -
+ +
+ + ); }; diff --git a/components/header.tsx b/components/header.tsx index fb958a5..ddddcb0 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -33,7 +33,7 @@ const Header = () => { return (
+
diff --git a/app/page.tsx b/app/page.tsx index 2eea7d2..7e6378f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -16,7 +16,7 @@ import AnimoDevBadge from "@/components/reusables/animo-dev-badge"; const LandingPage = () => { return (
-
+
{/* HERO SECTION */}
From 095c0542825adde7a53a2bf973817ca34dbc1e25 Mon Sep 17 00:00:00 2001 From: Kent0710 Date: Fri, 24 Oct 2025 13:45:16 +0800 Subject: [PATCH 18/34] fix(frontend): fix overflowing error component styling and add global error.tsx in root --- app/error.tsx | 10 ++++++++++ app/not-found.tsx | 2 +- components/error/fetch-failed.tsx | 14 +++++++++----- 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 app/error.tsx diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..2352b72 --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { ErrorBoundaryProps } from "@/lib/error-types"; +import FetchFailed from "@/components/error/fetch-failed"; + +const ErrorPage: React.FC = ({ error, reset }) => { + return ; +}; + +export default ErrorPage; diff --git a/app/not-found.tsx b/app/not-found.tsx index 0081726..2d7c7e0 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -7,7 +7,7 @@ import AnimoDevBadge from "@/components/reusables/animo-dev-badge"; const NotFoundPage = () => { return ( -
+

Project Harmony diff --git a/components/error/fetch-failed.tsx b/components/error/fetch-failed.tsx index 3c83f42..a65439b 100644 --- a/components/error/fetch-failed.tsx +++ b/components/error/fetch-failed.tsx @@ -1,6 +1,5 @@ "use client"; -import { Frown } from "lucide-react"; import { BentoContainer, BentoContainerHeader, @@ -10,10 +9,12 @@ import { Description, Title } from "../reusables/texts"; import { Button } from "../ui/button"; import { ErrorBoundaryProps } from "@/lib/error-types"; import { useRouter } from "next/navigation"; +import AnimoDevBadge from "../reusables/animo-dev-badge"; // extend the error boundary props to have the className interface FetchFailedProps extends ErrorBoundaryProps { className?: string; + showMessage?: boolean; } const FetchFailed: React.FC = ({ @@ -26,16 +27,19 @@ const FetchFailed: React.FC = ({ return (

+ - - Something went wrong <Frown /> + <Title> + Something went wrong: {error.name} - {error.message} + + Unexpected error. Try again or contact support +