diff --git a/src/app/dashboard/error.tsx b/src/app/dashboard/error.tsx new file mode 100644 index 0000000..1481979 --- /dev/null +++ b/src/app/dashboard/error.tsx @@ -0,0 +1,24 @@ +"use client" + +import { useEffect } from "react" + +export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { + useEffect(() => { + // Log the error to an error reporting service + console.error(error) + }, [error]) + + return ( +
+

Something went wrong!

+ +
+ ) +} diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..af1b7a5 --- /dev/null +++ b/src/app/dashboard/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from "next" + +import { Navbar } from "@/components/navbar" + +type Props = { + children: React.ReactNode +} + +export const metadata: Metadata = { + title: "Dashboard", +} + +export default function DashboardLayout({ children }: Props) { + return ( +
+ +
{children}
+
+ ) +} diff --git a/src/app/dashboard/loading.tsx b/src/app/dashboard/loading.tsx new file mode 100644 index 0000000..4ab9306 --- /dev/null +++ b/src/app/dashboard/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return

Loading...

+} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..04dd873 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,19 @@ +import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query" + +import { FormBuilderApi } from "@/api/form-builder/form-builder.api" +import { Dashboard } from "@/modules/dashboard" + +export default async function DashboardPage() { + const queryClient = new QueryClient() + + await queryClient.fetchQuery({ + queryKey: ["FormBuilderApi.getAllForms"], + queryFn: FormBuilderApi.getAllForms, + }) + + return ( + + + + ) +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..0b30b0d --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,22 @@ +import { Inter } from "next/font/google" + +import { cn } from "@/utils/classname" + +import "@/styles/globals.css" +import Providers from "./providers" + +export const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }) + +type Props = { + children: React.ReactNode +} + +export default function RootLayout({ children }: Props) { + return ( + + + {children} + + + ) +} diff --git a/src/app/providers.tsx b/src/app/providers.tsx new file mode 100644 index 0000000..3f759af --- /dev/null +++ b/src/app/providers.tsx @@ -0,0 +1,48 @@ +// In Next.js, this file would be called: app/providers.jsx +"use client" + +// We can not useState or useRef in a server component, which is why we are +// extracting this part out into it's own file with 'use client' on top +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" + +function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + // With SSR, we usually want to set some default staleTime + // above 0 to avoid refetching immediately on the client + staleTime: 60 * 1000, + }, + }, + }) +} + +let browserQueryClient: QueryClient | undefined = undefined + +function getQueryClient() { + if (typeof window === "undefined") { + // Server: always make a new query client + return makeQueryClient() + } else { + // Browser: make a new query client if we don't already have one + // This is very important so we don't re-make a new client if React + // suspends during the initial render. This may not be needed if we + // have a suspense boundary BELOW the creation of the query client + if (!browserQueryClient) browserQueryClient = makeQueryClient() + return browserQueryClient + } +} + +type ProviderProps = { + children: React.ReactNode +} + +export default function Providers({ children }: ProviderProps) { + // NOTE: Avoid useState when initializing the query client if you don't + // have a suspense boundary between this and the code that may + // suspend because React will throw away the client on the initial + // render if it suspends and there is no boundary + const queryClient = getQueryClient() + + return {children} +} diff --git a/src/components/navbar/__tests__/navbar.test.tsx b/src/components/navbar/__tests__/navbar.test.tsx index 3b79340..105a307 100644 --- a/src/components/navbar/__tests__/navbar.test.tsx +++ b/src/components/navbar/__tests__/navbar.test.tsx @@ -1,6 +1,5 @@ import "@testing-library/jest-dom" import { render, screen } from "@testing-library/react" -import mockRouter from "next-router-mock" import { Navbar } from ".." import { NAVBAR_LINKS } from "../navbar.constants" @@ -21,14 +20,4 @@ describe("Navbar", () => { expect(links[index]).toHaveAttribute("href", link.href) }) }) - - it("check if the active link is highlighted", () => { - const firstLink = NAVBAR_LINKS[0] - mockRouter.setCurrentUrl(firstLink.href) - - render() - - const activeLink = screen.getByTestId(firstLink.title.toLowerCase()) - expect(activeLink).toHaveAttribute("data-isactive", "true") - }) }) diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index af7b097..0e416ca 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -1,5 +1,7 @@ +"use client" + import Link from "next/link" -import { useRouter } from "next/router" +import { usePathname } from "next/navigation" import { cn } from "@/utils/classname" @@ -29,17 +31,21 @@ const NavbarLink = ({ href, title, isActive }: NavbarLinkProps) => { } export const Navbar = () => { - const { pathname } = useRouter() + const pathname = usePathname() const appName = process.env.NEXT_PUBLIC_APP_NAME return ( -