add react router

This commit is contained in:
Aman Varshney
2025-04-02 19:50:38 +05:30
parent 6325e647ce
commit dafefb8449
78 changed files with 2160 additions and 748 deletions

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,50 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port=3001",
"build": "vite build",
"serve": "vite preview",
"start": "vite",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@tanstack/react-query-devtools": "^5.69.0",
"@tanstack/react-router-devtools": "^1.114.27",
"@tanstack/router-plugin": "^1.114.27",
"@types/node": "^22.13.13",
"@types/react": "^19.0.12",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",
"vite": "^6.2.2"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@tanstack/react-form": "^1.0.5",
"@tailwindcss/vite": "^4.0.15",
"@tanstack/react-query": "^5.69.0",
"@tanstack/react-router": "^1.114.25",
"@trpc/client": "^11.0.0",
"@trpc/react-query": "^11.0.0",
"@trpc/tanstack-react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.473.0",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.0",
"tw-animate-css": "^1.2.5",
"zod": "^3.24.2"
}
}

View File

@@ -0,0 +1,31 @@
import { Link } from "@tanstack/react-router";
import { ModeToggle } from "./mode-toggle";
export default function Header() {
const links = [
{ to: "/", label: "Home" },
];
return (
<div>
<div className="flex flex-row items-center justify-between px-2 py-1">
<nav className="flex gap-4 text-lg">
{links.map(({ to, label }) => (
<Link
key={to}
to={to}
activeProps={{ className: "font-bold" }}
activeOptions={{ exact: true }}
>
{label}
</Link>
))}
</nav>
<div className="flex items-center gap-2">
<ModeToggle />
</div>
</div>
<hr />
</div>
);
}

View File

@@ -0,0 +1,33 @@
import { QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import ReactDOM from "react-dom/client";
import Loader from "./components/loader";
import { routeTree } from "./routeTree.gen";
import { queryClient, trpcClient } from "./utils/trpc";
const router = createRouter({
routeTree,
defaultPreload: "intent",
defaultPendingComponent: () => <Loader />,
context: { trpcClient },
Wrap: function WrapComponent({ children }) {
return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
},
});
// Register things for typesafety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
const rootElement = document.getElementById("app");
if (!rootElement) throw new Error("Root element not found");
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(<RouterProvider router={router} />);
}

View File

@@ -0,0 +1,60 @@
import Header from "@/components/header";
import Loader from "@/components/loader";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
import type { trpcClient } from "@/utils/trpc";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import {
HeadContent,
Outlet,
createRootRouteWithContext,
useRouterState,
} from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import "../index.css";
export interface RouterAppContext {
trpcClient: typeof trpcClient;
}
export const Route = createRootRouteWithContext<RouterAppContext>()({
component: RootComponent,
head: () => ({
meta: [
{
title: "My App",
},
{
name: "description",
content: "My App is a web application",
},
],
links: [
{
rel: "icon",
href: "/favicon.ico",
},
],
}),
});
function RootComponent() {
const isFetching = useRouterState({
select: (s) => s.isLoading,
});
return (
<>
<HeadContent />
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<div className="grid grid-rows-[auto_1fr] h-svh">
<Header />
{isFetching && <Loader />}
<Outlet />
</div>
<Toaster richColors />
</ThemeProvider>
<TanStackRouterDevtools position="bottom-left" />
<ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
</>
);
}

View File

@@ -0,0 +1,87 @@
import { trpc } from "@/utils/trpc";
import { useQuery } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
const TITLE_TEXT = `
██████╗ ███████╗████████╗████████╗███████╗██████╗
██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
██║ ███████╗ ██║ ███████║██║ █████╔╝
██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
`;
function HomeComponent() {
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
<div className="container mx-auto max-w-3xl px-4 py-2">
<pre className="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
<div className="grid gap-6">
<section className="rounded-lg border p-4">
<h2 className="mb-2 font-medium">API Status</h2>
<div className="flex items-center gap-2">
<div
className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
/>
<span className="text-sm text-muted-foreground">
{healthCheck.isLoading
? "Checking..."
: healthCheck.data
? "Connected"
: "Disconnected"}
</span>
</div>
</section>
<section>
<h2 className="mb-3 font-medium">Core Features</h2>
<ul className="grid grid-cols-2 gap-3">
<FeatureItem
title="Type-Safe API"
description="End-to-end type safety with tRPC"
/>
<FeatureItem
title="Modern React"
description="TanStack Router + TanStack Query"
/>
<FeatureItem
title="Fast Backend"
description="Lightweight Hono server"
/>
<FeatureItem
title="Beautiful UI"
description="TailwindCSS + shadcn/ui components"
/>
</ul>
</section>
</div>
</div>
);
}
function FeatureItem({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<li className="border-l-2 border-primary py-1 pl-3">
<h3 className="font-medium">{title}</h3>
<p className="text-sm text-muted-foreground">{description}</p>
</li>
);
}

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["vite/client"],
"rootDirs": ["."],
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -0,0 +1,14 @@
import tailwindcss from "@tailwindcss/vite";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss(), TanStackRouterVite({}), react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});