From 917c2ddd35ce9afd986db92f32b6e55c5ce8d047 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 03:02:24 +0800 Subject: [PATCH 1/7] Add maintenance page and refactor directories --- app/{ => (dashboard)}/(home)/page.tsx | 0 .../@breadcrumbs/[...slug]/page.tsx | 2 +- .../@breadcrumbs/default.tsx | 0 app/{ => (dashboard)}/about/page.tsx | 0 .../cars/DistributionPieChart.tsx | 0 .../cars/fuel-types/[fuelType]/TrendChart.tsx | 0 .../cars/fuel-types/[fuelType]/page.tsx | 2 +- .../cars/makes/[make]/TrendChart.tsx | 0 .../cars/makes/[make]/columns.tsx | 10 +++- .../cars/makes/[make]/page.tsx | 4 +- .../cars/opengraph-image.png | Bin app/{ => (dashboard)}/cars/page.tsx | 0 app/{ => (dashboard)}/cars/twitter-image.png | Bin .../cars/utils/fetchMonths.ts | 0 .../cars/vehicle-types/[vehicleType]/page.tsx | 2 +- .../coe/(prices)/TrendTable.tsx | 2 +- .../coe/(prices)/columns.tsx | 0 .../coe/(prices)/opengraph-image.png | Bin app/{ => (dashboard)}/coe/(prices)/page.tsx | 2 +- .../coe/(prices)/twitter-image.png | Bin app/{ => (dashboard)}/coe/bidding/page.tsx | 0 app/{ => (dashboard)}/contact/page.tsx | 0 app/{ => (dashboard)}/layout.tsx | 4 +- app/{ => (dashboard)}/opengraph-image.png | Bin app/{ => (dashboard)}/twitter-image.png | Bin app/components/CarOverviewTrends.tsx | 2 +- app/maintenance/layout.tsx | 17 +++++++ app/maintenance/page.tsx | 8 +++ components/DataTable.tsx | 4 +- components/Leaderboard.tsx | 2 +- components/Maintenance.tsx | 46 ++++++++++++++++++ components/StatisticsCard.tsx | 2 +- middleware.ts | 4 ++ 33 files changed, 98 insertions(+), 15 deletions(-) rename app/{ => (dashboard)}/(home)/page.tsx (100%) rename app/{ => (dashboard)}/@breadcrumbs/[...slug]/page.tsx (97%) rename app/{ => (dashboard)}/@breadcrumbs/default.tsx (100%) rename app/{ => (dashboard)}/about/page.tsx (100%) rename app/{ => (dashboard)}/cars/DistributionPieChart.tsx (100%) rename app/{ => (dashboard)}/cars/fuel-types/[fuelType]/TrendChart.tsx (100%) rename app/{ => (dashboard)}/cars/fuel-types/[fuelType]/page.tsx (98%) rename app/{ => (dashboard)}/cars/makes/[make]/TrendChart.tsx (100%) rename app/{ => (dashboard)}/cars/makes/[make]/columns.tsx (80%) rename app/{ => (dashboard)}/cars/makes/[make]/page.tsx (96%) rename app/{ => (dashboard)}/cars/opengraph-image.png (100%) rename app/{ => (dashboard)}/cars/page.tsx (100%) rename app/{ => (dashboard)}/cars/twitter-image.png (100%) rename app/{ => (dashboard)}/cars/utils/fetchMonths.ts (100%) rename app/{ => (dashboard)}/cars/vehicle-types/[vehicleType]/page.tsx (98%) rename app/{ => (dashboard)}/coe/(prices)/TrendTable.tsx (93%) rename app/{ => (dashboard)}/coe/(prices)/columns.tsx (100%) rename app/{ => (dashboard)}/coe/(prices)/opengraph-image.png (100%) rename app/{ => (dashboard)}/coe/(prices)/page.tsx (98%) rename app/{ => (dashboard)}/coe/(prices)/twitter-image.png (100%) rename app/{ => (dashboard)}/coe/bidding/page.tsx (100%) rename app/{ => (dashboard)}/contact/page.tsx (100%) rename app/{ => (dashboard)}/layout.tsx (96%) rename app/{ => (dashboard)}/opengraph-image.png (100%) rename app/{ => (dashboard)}/twitter-image.png (100%) create mode 100644 app/maintenance/layout.tsx create mode 100644 app/maintenance/page.tsx create mode 100644 components/Maintenance.tsx diff --git a/app/(home)/page.tsx b/app/(dashboard)/(home)/page.tsx similarity index 100% rename from app/(home)/page.tsx rename to app/(dashboard)/(home)/page.tsx diff --git a/app/@breadcrumbs/[...slug]/page.tsx b/app/(dashboard)/@breadcrumbs/[...slug]/page.tsx similarity index 97% rename from app/@breadcrumbs/[...slug]/page.tsx rename to app/(dashboard)/@breadcrumbs/[...slug]/page.tsx index 6b1510d..870739b 100644 --- a/app/@breadcrumbs/[...slug]/page.tsx +++ b/app/(dashboard)/@breadcrumbs/[...slug]/page.tsx @@ -50,7 +50,7 @@ const Breadcrumbs = async (props: { params: Params }) => { - Home + Home {breadcrumbs.map(({ isLastItem, href, label }) => ( diff --git a/app/@breadcrumbs/default.tsx b/app/(dashboard)/@breadcrumbs/default.tsx similarity index 100% rename from app/@breadcrumbs/default.tsx rename to app/(dashboard)/@breadcrumbs/default.tsx diff --git a/app/about/page.tsx b/app/(dashboard)/about/page.tsx similarity index 100% rename from app/about/page.tsx rename to app/(dashboard)/about/page.tsx diff --git a/app/cars/DistributionPieChart.tsx b/app/(dashboard)/cars/DistributionPieChart.tsx similarity index 100% rename from app/cars/DistributionPieChart.tsx rename to app/(dashboard)/cars/DistributionPieChart.tsx diff --git a/app/cars/fuel-types/[fuelType]/TrendChart.tsx b/app/(dashboard)/cars/fuel-types/[fuelType]/TrendChart.tsx similarity index 100% rename from app/cars/fuel-types/[fuelType]/TrendChart.tsx rename to app/(dashboard)/cars/fuel-types/[fuelType]/TrendChart.tsx diff --git a/app/cars/fuel-types/[fuelType]/page.tsx b/app/(dashboard)/cars/fuel-types/[fuelType]/page.tsx similarity index 98% rename from app/cars/fuel-types/[fuelType]/page.tsx rename to app/(dashboard)/cars/fuel-types/[fuelType]/page.tsx index 2116cef..3d73fff 100644 --- a/app/cars/fuel-types/[fuelType]/page.tsx +++ b/app/(dashboard)/cars/fuel-types/[fuelType]/page.tsx @@ -1,5 +1,5 @@ import { Suspense } from "react"; -import { fetchMonths } from "@/app/cars/utils/fetchMonths"; +import { fetchMonths } from "@/app/(dashboard)/cars/utils/fetchMonths"; import { CarOverviewTrends } from "@/app/components/CarOverviewTrends"; import { EmptyData } from "@/components/EmptyData"; import { MonthSelector } from "@/components/MonthSelector"; diff --git a/app/cars/makes/[make]/TrendChart.tsx b/app/(dashboard)/cars/makes/[make]/TrendChart.tsx similarity index 100% rename from app/cars/makes/[make]/TrendChart.tsx rename to app/(dashboard)/cars/makes/[make]/TrendChart.tsx diff --git a/app/cars/makes/[make]/columns.tsx b/app/(dashboard)/cars/makes/[make]/columns.tsx similarity index 80% rename from app/cars/makes/[make]/columns.tsx rename to app/(dashboard)/cars/makes/[make]/columns.tsx index 7fbf6f1..4d6b91d 100644 --- a/app/cars/makes/[make]/columns.tsx +++ b/app/(dashboard)/cars/makes/[make]/columns.tsx @@ -27,7 +27,9 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { const type = row.getValue("fuel_type") as string; return ( - {type} + + {type} + ); }, }, @@ -37,7 +39,11 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { const type = row.getValue("vehicle_type") as string; return ( - {type} + + {type} + ); }, }, diff --git a/app/cars/makes/[make]/page.tsx b/app/(dashboard)/cars/makes/[make]/page.tsx similarity index 96% rename from app/cars/makes/[make]/page.tsx rename to app/(dashboard)/cars/makes/[make]/page.tsx index b7e0315..5762529 100644 --- a/app/cars/makes/[make]/page.tsx +++ b/app/(dashboard)/cars/makes/[make]/page.tsx @@ -1,5 +1,5 @@ -import { TrendChart } from "@/app/cars/makes/[make]/TrendChart"; -import { columns } from "@/app/cars/makes/[make]/columns"; +import { TrendChart } from "@/app/(dashboard)/cars/makes/[make]/TrendChart"; +import { columns } from "@/app/(dashboard)/cars/makes/[make]/columns"; import { MakeSelector } from "@/app/components/MakeSelector"; import { EmptyData } from "@/components/EmptyData"; import { StructuredData } from "@/components/StructuredData"; diff --git a/app/cars/opengraph-image.png b/app/(dashboard)/cars/opengraph-image.png similarity index 100% rename from app/cars/opengraph-image.png rename to app/(dashboard)/cars/opengraph-image.png diff --git a/app/cars/page.tsx b/app/(dashboard)/cars/page.tsx similarity index 100% rename from app/cars/page.tsx rename to app/(dashboard)/cars/page.tsx diff --git a/app/cars/twitter-image.png b/app/(dashboard)/cars/twitter-image.png similarity index 100% rename from app/cars/twitter-image.png rename to app/(dashboard)/cars/twitter-image.png diff --git a/app/cars/utils/fetchMonths.ts b/app/(dashboard)/cars/utils/fetchMonths.ts similarity index 100% rename from app/cars/utils/fetchMonths.ts rename to app/(dashboard)/cars/utils/fetchMonths.ts diff --git a/app/cars/vehicle-types/[vehicleType]/page.tsx b/app/(dashboard)/cars/vehicle-types/[vehicleType]/page.tsx similarity index 98% rename from app/cars/vehicle-types/[vehicleType]/page.tsx rename to app/(dashboard)/cars/vehicle-types/[vehicleType]/page.tsx index 23592b0..3710f59 100644 --- a/app/cars/vehicle-types/[vehicleType]/page.tsx +++ b/app/(dashboard)/cars/vehicle-types/[vehicleType]/page.tsx @@ -1,4 +1,4 @@ -import { fetchMonths } from "@/app/cars/utils/fetchMonths"; +import { fetchMonths } from "@/app/(dashboard)/cars/utils/fetchMonths"; import { CarOverviewTrends } from "@/app/components/CarOverviewTrends"; import { EmptyData } from "@/components/EmptyData"; import { MonthSelector } from "@/components/MonthSelector"; diff --git a/app/coe/(prices)/TrendTable.tsx b/app/(dashboard)/coe/(prices)/TrendTable.tsx similarity index 93% rename from app/coe/(prices)/TrendTable.tsx rename to app/(dashboard)/coe/(prices)/TrendTable.tsx index d0d6e9c..ed3beb2 100644 --- a/app/coe/(prices)/TrendTable.tsx +++ b/app/(dashboard)/coe/(prices)/TrendTable.tsx @@ -1,7 +1,7 @@ "use client"; import { useMemo } from "react"; -import { columns } from "@/app/coe/(prices)/columns"; +import { columns } from "@/app/(dashboard)/coe/(prices)/columns"; import useStore from "@/app/store"; import { DataTable } from "@/components/ui/data-table"; import type { COEResult } from "@/types"; diff --git a/app/coe/(prices)/columns.tsx b/app/(dashboard)/coe/(prices)/columns.tsx similarity index 100% rename from app/coe/(prices)/columns.tsx rename to app/(dashboard)/coe/(prices)/columns.tsx diff --git a/app/coe/(prices)/opengraph-image.png b/app/(dashboard)/coe/(prices)/opengraph-image.png similarity index 100% rename from app/coe/(prices)/opengraph-image.png rename to app/(dashboard)/coe/(prices)/opengraph-image.png diff --git a/app/coe/(prices)/page.tsx b/app/(dashboard)/coe/(prices)/page.tsx similarity index 98% rename from app/coe/(prices)/page.tsx rename to app/(dashboard)/coe/(prices)/page.tsx index f895c4c..f3357eb 100644 --- a/app/coe/(prices)/page.tsx +++ b/app/(dashboard)/coe/(prices)/page.tsx @@ -1,4 +1,4 @@ -import { TrendTable } from "@/app/coe/(prices)/TrendTable"; +import { TrendTable } from "@/app/(dashboard)/coe/(prices)/TrendTable"; import { COECategories } from "@/components/COECategories"; import { COEPremiumChart } from "@/components/COEPremiumChart"; import { StructuredData } from "@/components/StructuredData"; diff --git a/app/coe/(prices)/twitter-image.png b/app/(dashboard)/coe/(prices)/twitter-image.png similarity index 100% rename from app/coe/(prices)/twitter-image.png rename to app/(dashboard)/coe/(prices)/twitter-image.png diff --git a/app/coe/bidding/page.tsx b/app/(dashboard)/coe/bidding/page.tsx similarity index 100% rename from app/coe/bidding/page.tsx rename to app/(dashboard)/coe/bidding/page.tsx diff --git a/app/contact/page.tsx b/app/(dashboard)/contact/page.tsx similarity index 100% rename from app/contact/page.tsx rename to app/(dashboard)/contact/page.tsx diff --git a/app/layout.tsx b/app/(dashboard)/layout.tsx similarity index 96% rename from app/layout.tsx rename to app/(dashboard)/layout.tsx index 8632caf..403adb5 100644 --- a/app/layout.tsx +++ b/app/(dashboard)/layout.tsx @@ -3,14 +3,14 @@ import { Inter } from "next/font/google"; import Script from "next/script"; import { GoogleAnalytics } from "@next/third-parties/google"; import classNames from "classnames"; +import { Analytics } from "@/app/components/Analytics"; import { Announcement } from "@/app/components/Announcement"; // import { Footer } from "@/app/components/Footer"; import { AppSidebar } from "@/components/AppSidebar"; import { Header } from "@/components/Header"; import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; import { ANNOUNCEMENT, SITE_TITLE, SITE_URL } from "@/config"; -import "./globals.css"; -import { Analytics } from "./components/Analytics"; +import "../globals.css"; import type { Metadata } from "next"; const inter = Inter({ subsets: ["latin"] }); diff --git a/app/opengraph-image.png b/app/(dashboard)/opengraph-image.png similarity index 100% rename from app/opengraph-image.png rename to app/(dashboard)/opengraph-image.png diff --git a/app/twitter-image.png b/app/(dashboard)/twitter-image.png similarity index 100% rename from app/twitter-image.png rename to app/(dashboard)/twitter-image.png diff --git a/app/components/CarOverviewTrends.tsx b/app/components/CarOverviewTrends.tsx index a7aa1c6..2f9a597 100644 --- a/app/components/CarOverviewTrends.tsx +++ b/app/components/CarOverviewTrends.tsx @@ -1,5 +1,5 @@ import { Suspense } from "react"; -import { TrendChart } from "@/app/cars/fuel-types/[fuelType]/TrendChart"; +import { TrendChart } from "@/app/(dashboard)/cars/fuel-types/[fuelType]/TrendChart"; import { DataTable } from "@/components/DataTable"; import { Card, diff --git a/app/maintenance/layout.tsx b/app/maintenance/layout.tsx new file mode 100644 index 0000000..c7d11f3 --- /dev/null +++ b/app/maintenance/layout.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from "react"; +import { Inter } from "next/font/google"; +import classNames from "classnames"; +import "../globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +const layout = ({ children }: { children: ReactNode }) => { + return ( + + +
{children}
+ + + ); +}; +export default layout; diff --git a/app/maintenance/page.tsx b/app/maintenance/page.tsx new file mode 100644 index 0000000..3dc743e --- /dev/null +++ b/app/maintenance/page.tsx @@ -0,0 +1,8 @@ +import { Maintenance } from "@/components/Maintenance"; + +const MaintenancePage = () => { + // Building it this way as I may add stuff to it later + return ; +}; + +export default MaintenancePage; diff --git a/components/DataTable.tsx b/components/DataTable.tsx index 1e41ab3..97d19bd 100644 --- a/components/DataTable.tsx +++ b/components/DataTable.tsx @@ -48,7 +48,9 @@ export const DataTable = ({ data }: Props) => { data.map(({ make, number }, index) => ( - {make} + + {make} + {number} diff --git a/components/Leaderboard.tsx b/components/Leaderboard.tsx index 4e8cab0..4d39b8c 100644 --- a/components/Leaderboard.tsx +++ b/components/Leaderboard.tsx @@ -134,7 +134,7 @@ export const Leaderboard = ({ cars }: LeaderboardProps) => { return (
diff --git a/components/Maintenance.tsx b/components/Maintenance.tsx new file mode 100644 index 0000000..f374730 --- /dev/null +++ b/components/Maintenance.tsx @@ -0,0 +1,46 @@ +import { AlertTriangle, Clock } from "lucide-react"; +import { Header } from "@/app/components/Header"; +import { SITE_TITLE } from "@/config"; + +export const Maintenance = () => ( +
+
+
+
+
+ +
+

+ System Maintenance +

+
+

+ We are currently performing scheduled maintenance to improve your + experience. Our services will be back online shortly. +

+
+ + Estimated downtime: 2 hours +
+
+
+

+ For urgent inquiries, please contact our support team at: +
+ + support@sgcarstrends.com + +

+
+
+
+
+
+ © {new Date().getFullYear()} {SITE_TITLE}. All Rights Reserved. +
+
+
+); diff --git a/components/StatisticsCard.tsx b/components/StatisticsCard.tsx index ef56edc..cb3bb10 100644 --- a/components/StatisticsCard.tsx +++ b/components/StatisticsCard.tsx @@ -2,7 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { ArrowRight } from "lucide-react"; -import { DistributionPieChart } from "@/app/cars/DistributionPieChart"; +import { DistributionPieChart } from "@/app/(dashboard)/cars/DistributionPieChart"; import { Badge } from "@/components/ui/badge"; import { Card, diff --git a/middleware.ts b/middleware.ts index 2938e40..e00ca46 100644 --- a/middleware.ts +++ b/middleware.ts @@ -39,3 +39,7 @@ export const middleware = (request: NextRequest) => { }, }); }; + +export const config = { + matcher: "/:path*", +}; From ec6884722e27adfc9e2e8b43d7e8d1110bf24a91 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 20:30:05 +0800 Subject: [PATCH 2/7] Clean up --- app/(dashboard)/cars/page.tsx | 139 +++++++++++++++++----------------- 1 file changed, 68 insertions(+), 71 deletions(-) diff --git a/app/(dashboard)/cars/page.tsx b/app/(dashboard)/cars/page.tsx index 3539ae7..30989ce 100644 --- a/app/(dashboard)/cars/page.tsx +++ b/app/(dashboard)/cars/page.tsx @@ -1,4 +1,3 @@ -import { Suspense } from "react"; import { notFound } from "next/navigation"; import { Leaderboard } from "@/components/Leaderboard"; import { MonthSelector } from "@/components/MonthSelector"; @@ -91,7 +90,7 @@ const CarsPage = async (props: { searchParams: SearchParams }) => { } Object.assign(car, { - vehicle_type: VEHICLE_TYPE_MAP[vehicle_type] || vehicle_type, + vehicle_type: VEHICLE_TYPE_MAP[vehicle_type] ?? vehicle_type, }); return car; @@ -158,78 +157,76 @@ const CarsPage = async (props: { searchParams: SearchParams }) => { )} {cars.length > 0 && ( - <> -
-
- - - Total Registrations - - -

{total}

-
-
- - - Top Fuel Type - - -

- {topFuelType} ({topFuelTypeValue}) -

-

Highest adoption rate

-
-
- - - Top Vehicle Type - - -

- {topVehicleType} ({topVehicleTypeValue}) -

-

Highest adoption rate

-
-
- {/**/} - {/* */} - {/* */} - {/* Trend*/} - {/* */} - {/* */} - {/*

*/} - {/* Electric*/} - {/*

*/} - {/*

*/} - {/* Steady increase in registrations*/} - {/*

*/} - {/*
*/} - {/*
*/} - {/*
*/} +
+
+ + + Total Registrations + + +

{total}

+
+
+ + + Top Fuel Type + + +

+ {topFuelType} ({topFuelTypeValue}) +

+

Highest adoption rate

+
+
+ + + Top Vehicle Type + + +

+ {topVehicleType} ({topVehicleTypeValue}) +

+

Highest adoption rate

+
+
+ {/**/} + {/* */} + {/* */} + {/* Trend*/} + {/* */} + {/* */} + {/*

*/} + {/* Electric*/} + {/*

*/} + {/*

*/} + {/* Steady increase in registrations*/} + {/*

*/} + {/*
*/} + {/*
*/} + {/*
*/} +
+
+
+ +
-
-
- - -
-
- -
+
+
- +
)}
From 5670b906c75438e0584c201a1b8e6407cc636025 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 21:05:12 +0800 Subject: [PATCH 3/7] Add middleware to control maintenance mode --- app/maintenance/page.tsx | 34 +++++++++++++++++++++++++++++++++- middleware.ts | 30 ++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/app/maintenance/page.tsx b/app/maintenance/page.tsx index 3dc743e..1ca0145 100644 --- a/app/maintenance/page.tsx +++ b/app/maintenance/page.tsx @@ -1,7 +1,39 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { Maintenance } from "@/components/Maintenance"; const MaintenancePage = () => { - // Building it this way as I may add stuff to it later + const router = useRouter(); + const searchParams = useSearchParams(); + + useEffect(() => { + const checkMaintenance = async () => { + try { + // TODO: Simulating this for now + const isMaintenanceMode = process.env.MAINTENANCE_MODE; + + if (!isMaintenanceMode) { + const from = searchParams.get("from"); + if (from) { + router.replace(decodeURIComponent(from)); + } else { + router.replace("/"); + } + } + } catch (e) { + console.error("Error checking maintenance status:", e); + } + }; + + const interval = setInterval(checkMaintenance, 5000); + + void checkMaintenance(); + + return () => clearInterval(interval); + }, [router, searchParams]); + return ; }; diff --git a/middleware.ts b/middleware.ts index e00ca46..baac33a 100644 --- a/middleware.ts +++ b/middleware.ts @@ -2,15 +2,27 @@ import { NextRequest, NextResponse } from "next/server"; import { DOMAIN_NAME } from "@/config"; export const middleware = (request: NextRequest) => { + // TODO: Temporary method for now + const isMaintenanceMode = process.env.MAINTENANCE_MODE === "true"; + + if ( + isMaintenanceMode && + !request.nextUrl.pathname.startsWith("/maintenance") + ) { + const maintenanceUrl = new URL("/maintenance", request.url); + maintenanceUrl.searchParams.set("from", request.url); + return NextResponse.redirect(maintenanceUrl); + } + const nonce = Buffer.from(crypto.randomUUID()).toString("base64"); const cspHeader = ` - default-src 'self'; - script-src 'self' 'unsafe-eval' 'unsafe-inline' *.${DOMAIN_NAME} *.googletagmanager.com; - style-src 'self' 'unsafe-inline'; - img-src 'self' blob: data:; - connect-src *; - font-src 'self'; -`; + default-src 'self'; + script-src 'self' 'unsafe-eval' 'unsafe-inline' *.${DOMAIN_NAME} *.googletagmanager.com; + style-src 'self' 'unsafe-inline'; + img-src 'self' blob: data:; + connect-src *; + font-src 'self'; + `; const requestHeaders = new Headers(request.headers); requestHeaders.set("x-nonce", nonce); @@ -41,5 +53,7 @@ export const middleware = (request: NextRequest) => { }; export const config = { - matcher: "/:path*", + matcher: [ + "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)", + ], }; From fc5f5836b291bb57d82e7273dab1e3041cc15488 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 22:14:47 +0800 Subject: [PATCH 4/7] Refactor and clean up components --- app/components/Footer.tsx | 172 ------------------ app/components/Header.tsx | 10 -- app/components/NavMenu.tsx | 242 +++++++++++++------------- app/maintenance/components/Footer.tsx | 96 ++++++++++ app/maintenance/components/Header.tsx | 14 ++ app/maintenance/layout.tsx | 10 ++ app/maintenance/page.tsx | 43 ++++- components/BrandLogo.tsx | 6 +- components/Maintenance.tsx | 46 ----- package.json | 1 + pnpm-lock.yaml | 12 ++ 11 files changed, 297 insertions(+), 355 deletions(-) delete mode 100644 app/components/Footer.tsx delete mode 100644 app/components/Header.tsx create mode 100644 app/maintenance/components/Footer.tsx create mode 100644 app/maintenance/components/Header.tsx delete mode 100644 components/Maintenance.tsx diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx deleted file mode 100644 index f72a110..0000000 --- a/app/components/Footer.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import type { ElementType } from "react"; -import Link from "next/link"; -import { Facebook, Github, Instagram, Linkedin, Twitter } from "lucide-react"; -import { BrandLogo } from "@/components/BrandLogo"; -import { ComingSoon } from "@/components/ComingSoon"; -import { Separator } from "@/components/ui/separator"; -import { COE_LINKS, FUEL_TYPE_LINKS, VEHICLE_TYPE_LINKS } from "@/config"; -import type { LinkItem } from "@/types"; - -interface FooterLink { - title: string; - links: LinkItem[]; -} - -interface SocialMediaLink { - name: string; - url: string; - icon: ElementType; -} - -const socialMediaLinks: SocialMediaLink[] = [ - { - name: "Facebook", - url: "https://facebook.com/sgcarstrends", - icon: Facebook, - }, - { - name: "Twitter", - url: "https://twitter.com/sgcarstrends", - icon: Twitter, - }, - { - name: "Instagram", - url: "https://instagram.com/sgcarstrends", - icon: Instagram, - }, - { - name: "LinkedIn", - url: "https://linkedin.com/company/sgcarstrends", - icon: Linkedin, - }, - { - name: "GitHub", - url: "https://github.com/sgcarstrends", - icon: Github, - }, - // { - // name: "Threads", - // url: "https://threads.net/sgcarstrends", - // icon: Threads, - // }, -]; - -const FooterSection = ({ title, links }: FooterLink) => ( -
-

{title}

-
    - {links.map(({ href, label, comingSoon }) => { - if (comingSoon) { - return ( - - - {label} - - - ); - } - - return ( -
  • - - {label} - -
  • - ); - })} -
-
-); - -export const Footer = () => { - const currentYear = new Date().getFullYear(); - - const sortByName = (a: SocialMediaLink, b: SocialMediaLink) => - a.name.localeCompare(b.name); - - return ( - - ); -}; - -const footerLinks: FooterLink[] = [ - // { - // title: "Monthly", - // links: [ - // { label: "Cars", href: "/cars" }, - // { label: "COE", href: "/coe" }, - // ], - // }, - { - title: "Fuel Types", - links: FUEL_TYPE_LINKS, - }, - { title: "Vehicle Types", links: VEHICLE_TYPE_LINKS }, - { - title: "COE", - links: COE_LINKS, - }, - // TODO: Coming Soon! - // { - // title: "Resources", - // links: [ - // { href: "/about", label: "About" }, - // { href: "/contact", label: "Contact" }, - // ], - // }, -]; diff --git a/app/components/Header.tsx b/app/components/Header.tsx deleted file mode 100644 index e40a0f3..0000000 --- a/app/components/Header.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { DesktopNavMenu, MobileNavMenu } from "@/app/components/NavMenu"; - -export const Header = () => { - return ( -
- - -
- ); -}; diff --git a/app/components/NavMenu.tsx b/app/components/NavMenu.tsx index ebceda2..66f4cc9 100644 --- a/app/components/NavMenu.tsx +++ b/app/components/NavMenu.tsx @@ -104,137 +104,135 @@ export const NavMenu = () => ( ); export const DesktopNavMenu = () => ( -
- - -
-
- -
- -
-
-
-
-
+ ); export const MobileNavMenu = () => { const [isOpen, setIsOpen] = useState(false); return ( -
+
- - - - - - setIsOpen(false)} - > - - - -
- -
- - -
-
- - - Cars - COE - - - - setIsOpen(false)}> - - Monthly - - - - - Fuel Type - -
- {FUEL_TYPE_LINKS.map( - ({ label, description, href, icon: Icon }) => ( - setIsOpen(false)} - > -
- {Icon && } -
{label}
-
- - {description} - - - ), - )} -
-
-
- - Vehicle Type - -
- {VEHICLE_TYPE_LINKS.map(({ label, href }) => ( - setIsOpen(false)} - > - {label} - - ))} -
-
-
-
-
- - - - COE - - setIsOpen(false)}> - Dashboard - - - - - -
-
-
-
-
-
+ {/**/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* setIsOpen(false)}*/} + {/* >*/} + {/* */} + {/* */} + {/* */} + {/*
*/} + {/* */} + {/*
*/} + {/* */} + {/* */} + {/*
*/} + {/*
*/} + {/* */} + {/* */} + {/* Cars*/} + {/* COE*/} + {/* */} + {/* */} + {/* */} + {/* setIsOpen(false)}>*/} + {/* */} + {/* Monthly */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* Fuel Type*/} + {/* */} + {/*
*/} + {/* {FUEL_TYPE_LINKS.map(*/} + {/* ({ label, description, href, icon: Icon }) => (*/} + {/* setIsOpen(false)}*/} + {/* >*/} + {/*
*/} + {/* {Icon && }*/} + {/*
{label}
*/} + {/*
*/} + {/* */} + {/* {description}*/} + {/* */} + {/* */} + {/* ),*/} + {/* )}*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/* */} + {/* Vehicle Type*/} + {/* */} + {/*
*/} + {/* {VEHICLE_TYPE_LINKS.map(({ label, href }) => (*/} + {/* setIsOpen(false)}*/} + {/* >*/} + {/* {label}*/} + {/* */} + {/* ))}*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/* */} + {/* */} + {/* */} + {/* COE*/} + {/* */} + {/* setIsOpen(false)}>*/} + {/* Dashboard*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/} + {/*
*/}
); }; diff --git a/app/maintenance/components/Footer.tsx b/app/maintenance/components/Footer.tsx new file mode 100644 index 0000000..15997b2 --- /dev/null +++ b/app/maintenance/components/Footer.tsx @@ -0,0 +1,96 @@ +import { + type IconType, + SiBluesky, + SiFacebook, + SiGithub, + SiInstagram, + SiLinkedin, + SiThreads, + SiX, +} from "@icons-pack/react-simple-icons"; +import { Separator } from "@/components/ui/separator"; + +interface SocialMediaLink { + name: string; + url: string; + icon: IconType; +} + +const socialMediaLinks: SocialMediaLink[] = [ + { + name: "Facebook", + url: "https://facebook.com/sgcarstrends", + icon: SiFacebook, + }, + { + name: "Twitter", + url: "https://twitter.com/sgcarstrends", + icon: SiX, + }, + { + name: "Instagram", + url: "https://instagram.com/sgcarstrends", + icon: SiInstagram, + }, + { + name: "LinkedIn", + url: "https://linkedin.com/company/sgcarstrends", + icon: SiLinkedin, + }, + { + name: "GitHub", + url: "https://github.com/sgcarstrends", + icon: SiGithub, + }, + { + name: "Threads", + url: "https://threads.net/sgcarstrends", + icon: SiThreads, + }, + { + name: "Bluesky", + url: "https://bsky.app/profile/sgcarstrends.com", + icon: SiBluesky, + }, +].toSorted((a, b) => a.name.localeCompare(b.name)); + +export const Footer = () => { + const currentYear = new Date().getFullYear(); + + return ( +
+
+
Follow Us
+
+ {socialMediaLinks.map(({ name, url, icon: Icon }) => ( + + + + ))} +
+
+ +
+
+ Data provided by{" "} + + Land Transport Datamall + +
+
© {currentYear}. All Rights Reserved.
+
+
+ ); +}; diff --git a/app/maintenance/components/Header.tsx b/app/maintenance/components/Header.tsx new file mode 100644 index 0000000..8bf2fe2 --- /dev/null +++ b/app/maintenance/components/Header.tsx @@ -0,0 +1,14 @@ +import Link from "next/link"; +import { BrandLogo } from "@/components/BrandLogo"; + +export const Header = () => { + return ( +
+ +
+ ); +}; diff --git a/app/maintenance/layout.tsx b/app/maintenance/layout.tsx index c7d11f3..d16292e 100644 --- a/app/maintenance/layout.tsx +++ b/app/maintenance/layout.tsx @@ -1,10 +1,20 @@ import type { ReactNode } from "react"; import { Inter } from "next/font/google"; import classNames from "classnames"; +import type { Metadata } from "next"; import "../globals.css"; const inter = Inter({ subsets: ["latin"] }); +export const metadata: Metadata = { + title: "Site Maintenance", + description: "Site is currently under maintenance", + robots: { + index: false, + follow: false, + }, +}; + const layout = ({ children }: { children: ReactNode }) => { return ( diff --git a/app/maintenance/page.tsx b/app/maintenance/page.tsx index 1ca0145..7ec23fc 100644 --- a/app/maintenance/page.tsx +++ b/app/maintenance/page.tsx @@ -2,7 +2,9 @@ import { useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; -import { Maintenance } from "@/components/Maintenance"; +import { AlertTriangle, Clock } from "lucide-react"; +import { Footer } from "@/app/maintenance/components/Footer"; +import { Header } from "@/app/maintenance/components/Header"; const MaintenancePage = () => { const router = useRouter(); @@ -34,7 +36,44 @@ const MaintenancePage = () => { return () => clearInterval(interval); }, [router, searchParams]); - return ; + return ( +
+
+
+
+
+ +
+

+ System Maintenance +

+
+

+ We are currently performing scheduled maintenance to improve your + experience. Our services will be back online shortly. +

+
+ + Estimated downtime: 2 hours +
+
+
+

+ For urgent inquiries, please contact our support team at: +
+ + support@sgcarstrends.com + +

+
+
+
+
+
+ ); }; export default MaintenancePage; diff --git a/components/BrandLogo.tsx b/components/BrandLogo.tsx index 656405d..572e036 100644 --- a/components/BrandLogo.tsx +++ b/components/BrandLogo.tsx @@ -1,9 +1,9 @@ import { TrendingUp } from "lucide-react"; export const BrandLogo = () => ( -
- - +
+ + SGCars Trends diff --git a/components/Maintenance.tsx b/components/Maintenance.tsx deleted file mode 100644 index f374730..0000000 --- a/components/Maintenance.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { AlertTriangle, Clock } from "lucide-react"; -import { Header } from "@/app/components/Header"; -import { SITE_TITLE } from "@/config"; - -export const Maintenance = () => ( -
-
-
-
-
- -
-

- System Maintenance -

-
-

- We are currently performing scheduled maintenance to improve your - experience. Our services will be back online shortly. -

-
- - Estimated downtime: 2 hours -
-
-
-

- For urgent inquiries, please contact our support team at: -
- - support@sgcarstrends.com - -

-
-
-
-
-
- © {new Date().getFullYear()} {SITE_TITLE}. All Rights Reserved. -
-
-
-); diff --git a/package.json b/package.json index 50cf455..4f6cf76 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@heroicons/react": "^2.1.1", + "@icons-pack/react-simple-icons": "^10.2.0", "@mdi/js": "^7.3.67", "@mdi/react": "^1.6.1", "@neondatabase/serverless": "^0.9.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5764d2..423f9fd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@heroicons/react': specifier: ^2.1.1 version: 2.1.1(react@19.0.0-rc.1) + '@icons-pack/react-simple-icons': + specifier: ^10.2.0 + version: 10.2.0(react@19.0.0-rc.1) '@mdi/js': specifier: ^7.3.67 version: 7.4.47 @@ -1198,6 +1201,11 @@ packages: '@vue/compiler-sfc': optional: true + '@icons-pack/react-simple-icons@10.2.0': + resolution: {integrity: sha512-QDUxup8D3GdIIzwGpxQs6bjeFV5mJes25qqf4aqP/PaBYQNCar7AiyD8C14636TosCG0A/QqAUwm/Hviep4d4g==} + peerDependencies: + react: ^16.13 || ^17 || ^18 || ^19 + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -6215,6 +6223,10 @@ snapshots: - supports-color optional: true + '@icons-pack/react-simple-icons@10.2.0(react@19.0.0-rc.1)': + dependencies: + react: 19.0.0-rc.1 + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 From 3f8467a13af67ff2ec6f359e63e85371906e6352 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 22:22:19 +0800 Subject: [PATCH 5/7] Wrap MaintenanceNotice component with Suspense --- .../components/MaintenanceNotice.tsx | 44 ++++++++++++++++++ app/maintenance/page.tsx | 45 +++---------------- 2 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 app/maintenance/components/MaintenanceNotice.tsx diff --git a/app/maintenance/components/MaintenanceNotice.tsx b/app/maintenance/components/MaintenanceNotice.tsx new file mode 100644 index 0000000..16cf4ab --- /dev/null +++ b/app/maintenance/components/MaintenanceNotice.tsx @@ -0,0 +1,44 @@ +import { AlertTriangle, Clock } from "lucide-react"; +import { Footer } from "@/app/maintenance/components/Footer"; +import { Header } from "@/app/maintenance/components/Header"; + +export const MaintenanceNotice = () => { + return ( +
+
+
+
+
+ +
+

+ System Maintenance +

+
+

+ We are currently performing scheduled maintenance to improve your + experience. Our services will be back online shortly. +

+
+ + Estimated downtime: 2 hours +
+
+
+

+ For urgent inquiries, please contact our support team at: +
+ + support@sgcarstrends.com + +

+
+
+
+
+
+ ); +}; diff --git a/app/maintenance/page.tsx b/app/maintenance/page.tsx index 7ec23fc..ec2a8a0 100644 --- a/app/maintenance/page.tsx +++ b/app/maintenance/page.tsx @@ -1,10 +1,8 @@ "use client"; -import { useEffect } from "react"; +import { Suspense, useEffect } from "react"; import { useRouter, useSearchParams } from "next/navigation"; -import { AlertTriangle, Clock } from "lucide-react"; -import { Footer } from "@/app/maintenance/components/Footer"; -import { Header } from "@/app/maintenance/components/Header"; +import { MaintenanceNotice } from "@/app/maintenance/components/MaintenanceNotice"; const MaintenancePage = () => { const router = useRouter(); @@ -37,42 +35,9 @@ const MaintenancePage = () => { }, [router, searchParams]); return ( -
-
-
-
-
- -
-

- System Maintenance -

-
-

- We are currently performing scheduled maintenance to improve your - experience. Our services will be back online shortly. -

-
- - Estimated downtime: 2 hours -
-
-
-

- For urgent inquiries, please contact our support team at: -
- - support@sgcarstrends.com - -

-
-
-
-
-
+ + + ); }; From 63e2ca3cfe781151a700ab82425d9513f720e0b5 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 22:42:15 +0800 Subject: [PATCH 6/7] Fix build error --- .../components/MaintenanceNotice.tsx | 33 ++++++++++++++++ app/maintenance/page.tsx | 39 +------------------ 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/app/maintenance/components/MaintenanceNotice.tsx b/app/maintenance/components/MaintenanceNotice.tsx index 16cf4ab..dd426db 100644 --- a/app/maintenance/components/MaintenanceNotice.tsx +++ b/app/maintenance/components/MaintenanceNotice.tsx @@ -1,8 +1,41 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; import { AlertTriangle, Clock } from "lucide-react"; import { Footer } from "@/app/maintenance/components/Footer"; import { Header } from "@/app/maintenance/components/Header"; export const MaintenanceNotice = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + + useEffect(() => { + const checkMaintenance = async () => { + try { + // TODO: Simulating this for now + const isMaintenanceMode = process.env.MAINTENANCE_MODE; + + if (!isMaintenanceMode) { + const from = searchParams.get("from"); + if (from) { + router.replace(decodeURIComponent(from)); + } else { + router.replace("/"); + } + } + } catch (e) { + console.error("Error checking maintenance status:", e); + } + }; + + const interval = setInterval(checkMaintenance, 5000); + + void checkMaintenance(); + + return () => clearInterval(interval); + }, [router, searchParams]); + return (
diff --git a/app/maintenance/page.tsx b/app/maintenance/page.tsx index ec2a8a0..e4c7c8d 100644 --- a/app/maintenance/page.tsx +++ b/app/maintenance/page.tsx @@ -1,44 +1,9 @@ "use client"; -import { Suspense, useEffect } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import { MaintenanceNotice } from "@/app/maintenance/components/MaintenanceNotice"; +import { Suspense } from "react"; const MaintenancePage = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - useEffect(() => { - const checkMaintenance = async () => { - try { - // TODO: Simulating this for now - const isMaintenanceMode = process.env.MAINTENANCE_MODE; - - if (!isMaintenanceMode) { - const from = searchParams.get("from"); - if (from) { - router.replace(decodeURIComponent(from)); - } else { - router.replace("/"); - } - } - } catch (e) { - console.error("Error checking maintenance status:", e); - } - }; - - const interval = setInterval(checkMaintenance, 5000); - - void checkMaintenance(); - - return () => clearInterval(interval); - }, [router, searchParams]); - - return ( - - - - ); + return ; }; export default MaintenancePage; From 1f5393747741d89fadcd0ae00a3d9e25a5d35068 Mon Sep 17 00:00:00 2001 From: Ru Chern Chong Date: Sat, 30 Nov 2024 22:43:48 +0800 Subject: [PATCH 7/7] Remove unused component --- app/components/NavMenu.tsx | 274 ------------------------------------- 1 file changed, 274 deletions(-) delete mode 100644 app/components/NavMenu.tsx diff --git a/app/components/NavMenu.tsx b/app/components/NavMenu.tsx deleted file mode 100644 index 66f4cc9..0000000 --- a/app/components/NavMenu.tsx +++ /dev/null @@ -1,274 +0,0 @@ -"use client"; - -import * as React from "react"; -import { useState } from "react"; -import Link from "next/link"; -import { ArrowRight, type LucideIcon, Menu, Search } from "lucide-react"; -import { BrandLogo } from "@/components/BrandLogo"; -import Typography from "@/components/Typography"; -import { UnreleasedFeature } from "@/components/UnreleasedFeature"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, - navigationMenuTriggerStyle, -} from "@/components/ui/navigation-menu"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { FUEL_TYPE_LINKS, VEHICLE_TYPE_LINKS } from "@/config"; -import { cn } from "@/lib/utils"; - -export const NavMenu = () => ( - - - - - - Monthly - - - - - Fuel Types - -
    - {/*
  • */} - {/* */} - {/* */} - {/* */} - {/*
    Monthly
    */} - {/*

    */} - {/* Car registration data for the latest month and previous*/} - {/* months, broken down by fuel type and vehicle type.*/} - {/*

    */} - {/* */} - {/*
    */} - {/*
  • */} - {FUEL_TYPE_LINKS.map(({ label, description, href, icon }) => ( - - {description} - - ))} -
-
-
- - Vehicle Types - -
    - {VEHICLE_TYPE_LINKS.map(({ label, description, href, icon }) => ( - - {description} - - ))} -
-
-
- - - - COE - - - - {/**/} - {/* COE*/} - {/* */} - {/*
    */} - {/* */} - {/* Latest*/} - {/* */} - {/* */} - {/* Historical*/} - {/* */} - {/*
*/} - {/*
*/} - {/*
*/} -
-
-); - -export const DesktopNavMenu = () => ( - -); - -export const MobileNavMenu = () => { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - - - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* setIsOpen(false)}*/} - {/* >*/} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/* */} - {/*
*/} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/* */} - {/* */} - {/* Cars*/} - {/* COE*/} - {/* */} - {/* */} - {/* */} - {/* setIsOpen(false)}>*/} - {/* */} - {/* Monthly */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* Fuel Type*/} - {/* */} - {/*
*/} - {/* {FUEL_TYPE_LINKS.map(*/} - {/* ({ label, description, href, icon: Icon }) => (*/} - {/* setIsOpen(false)}*/} - {/* >*/} - {/*
*/} - {/* {Icon && }*/} - {/*
{label}
*/} - {/*
*/} - {/* */} - {/* {description}*/} - {/* */} - {/* */} - {/* ),*/} - {/* )}*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/* */} - {/* Vehicle Type*/} - {/* */} - {/*
*/} - {/* {VEHICLE_TYPE_LINKS.map(({ label, href }) => (*/} - {/* setIsOpen(false)}*/} - {/* >*/} - {/* {label}*/} - {/* */} - {/* ))}*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/* */} - {/* */} - {/* */} - {/* COE*/} - {/* */} - {/* setIsOpen(false)}>*/} - {/* Dashboard*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} - {/*
*/} -
- ); -}; - -const ListItem = React.forwardRef< - React.ElementRef<"a">, - React.ComponentPropsWithoutRef<"a"> & { - title: string; - href: string; - icon?: LucideIcon; - } ->(({ className, title, children, href, icon: Icon, ...props }, ref) => { - return ( -
  • - - - -
    - {Icon && } -
    {title}
    -
    -

    - {children} -

    -
    - -
    -
  • - ); -}); - -ListItem.displayName = "ListItem";