├── .env.example
├── src
├── database
│ ├── schema.ts
│ └── db.ts
├── lib
│ ├── auth-client.ts
│ ├── utils.ts
│ └── auth.ts
├── routes
│ ├── index.tsx
│ ├── api
│ │ └── auth
│ │ │ └── $.ts
│ ├── auth
│ │ └── $path.tsx
│ ├── account
│ │ └── $path.tsx
│ └── __root.tsx
├── router.tsx
├── styles
│ ├── custom.css
│ └── styles.css
├── components
│ ├── meta-theme.ts
│ ├── providers.tsx
│ ├── mode-toggle.tsx
│ ├── header.tsx
│ └── ui
│ │ ├── button.tsx
│ │ └── dropdown-menu.tsx
├── routeTree.gen.ts
└── logo.svg
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── tanstack-circle-logo.png
├── manifest.json
└── tanstack-word-logo-white.svg
├── .gitignore
├── components.json
├── vite.config.ts
├── tsconfig.json
├── biome.json
├── auth-schema.ts
├── package.json
└── README.md
/.env.example:
--------------------------------------------------------------------------------
1 | BETTER_AUTH_SECRET=""
2 | DATABASE_URL=""
--------------------------------------------------------------------------------
/src/database/schema.ts:
--------------------------------------------------------------------------------
1 | export * from "@/../auth-schema"
2 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/database/db.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from "drizzle-orm/node-postgres"
2 | export const db = drizzle(process.env.DATABASE_URL!)
3 |
--------------------------------------------------------------------------------
/public/tanstack-circle-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daveyplate/better-auth-tanstack-starter/HEAD/public/tanstack-circle-logo.png
--------------------------------------------------------------------------------
/src/lib/auth-client.ts:
--------------------------------------------------------------------------------
1 | import { createAuthClient } from "better-auth/react"
2 |
3 | export const authClient = createAuthClient({})
4 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | count.txt
7 | .env
8 | .nitro
9 | .tanstack
10 | .wrangler
11 | .output
12 | .vinxi
13 | todos.json
14 | .vscode
15 | .vercel
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from "@tanstack/react-router"
2 |
3 | export const Route = createFileRoute("/")({ component: IndexPage })
4 |
5 | function IndexPage() {
6 | return (
7 |
8 | Hello, world.
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/router.tsx:
--------------------------------------------------------------------------------
1 | import { createRouter } from "@tanstack/react-router"
2 |
3 | // Import the generated route tree
4 | import { routeTree } from "./routeTree.gen"
5 |
6 | // Create a new router instance
7 | export const getRouter = () => {
8 | return createRouter({
9 | routeTree,
10 | scrollRestoration: true,
11 | defaultPreloadStaleTime: 0
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { betterAuth } from "better-auth"
2 | import { drizzleAdapter } from "better-auth/adapters/drizzle"
3 |
4 | import { db } from "@/database/db"
5 | import * as schema from "@/database/schema"
6 |
7 | export const auth = betterAuth({
8 | database: drizzleAdapter(db, {
9 | provider: "pg",
10 | usePlural: true,
11 | schema
12 | }),
13 | emailAndPassword: {
14 | enabled: true
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/src/routes/api/auth/$.ts:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from "@tanstack/react-router"
2 | import { auth } from "@/lib/auth"
3 |
4 | export const Route = createFileRoute("/api/auth/$")({
5 | server: {
6 | handlers: {
7 | GET: ({ request }) => {
8 | return auth.handler(request)
9 | },
10 | POST: ({ request }) => {
11 | return auth.handler(request)
12 | }
13 | }
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/src/routes/auth/$path.tsx:
--------------------------------------------------------------------------------
1 | import { AuthView } from "@daveyplate/better-auth-ui"
2 | import { createFileRoute } from "@tanstack/react-router"
3 |
4 | export const Route = createFileRoute("/auth/$path")({
5 | component: RouteComponent
6 | })
7 |
8 | function RouteComponent() {
9 | const { path } = Route.useParams()
10 |
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "src/styles.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/account/$path.tsx:
--------------------------------------------------------------------------------
1 | import { AccountView } from "@daveyplate/better-auth-ui"
2 | import { createFileRoute } from "@tanstack/react-router"
3 |
4 | export const Route = createFileRoute("/account/$path")({
5 | component: RouteComponent
6 | })
7 |
8 | function RouteComponent() {
9 | const { path } = Route.useParams()
10 |
11 | return (
12 |
13 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "TanStack App",
3 | "name": "Create TanStack App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | // import { cloudflare } from "@cloudflare/vite-plugin"
2 | import tailwindcss from "@tailwindcss/vite"
3 | import { nitroV2Plugin } from "@tanstack/nitro-v2-vite-plugin"
4 | import { tanstackStart } from "@tanstack/react-start/plugin/vite"
5 | import viteReact from "@vitejs/plugin-react"
6 | import { defineConfig } from "vite"
7 | import devtoolsJson from "vite-plugin-devtools-json"
8 | import viteTsConfigPaths from "vite-tsconfig-paths"
9 |
10 | const config = defineConfig({
11 | plugins: [
12 | viteTsConfigPaths({
13 | projects: ["./tsconfig.json"]
14 | }),
15 | tailwindcss(),
16 | tanstackStart(),
17 | nitroV2Plugin({ preset: "vercel" }),
18 | viteReact(),
19 | devtoolsJson()
20 | ]
21 | })
22 |
23 | export default config
24 |
--------------------------------------------------------------------------------
/src/styles/custom.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss-safe-area";
2 |
3 | @import "@daveyplate/better-auth-ui/css";
4 |
5 | @layer base {
6 | button:not(:disabled),
7 | [role="button"]:not(:disabled) {
8 | cursor: pointer;
9 | }
10 | }
11 |
12 | [role="menuitem"]:not(:disabled) {
13 | cursor: pointer;
14 | }
15 |
16 | :root {
17 | --warning: hsl(38 92% 50%);
18 | --warning-foreground: hsl(48 96% 89%);
19 | }
20 |
21 | .dark {
22 | --warning: hsl(48 96% 89%);
23 | --warning-foreground: hsl(38 92% 50%);
24 | }
25 |
26 | @theme inline {
27 | --color-warning: var(--warning);
28 | --color-warning-foreground: var(--warning-foreground);
29 | }
30 |
31 | /** iOS Dynamic System Font Scaling */
32 | /* @supports (-webkit-touch-callout:none) {
33 | html {
34 | font: -apple-system-body;
35 | }
36 | } */
37 |
--------------------------------------------------------------------------------
/src/components/meta-theme.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react"
2 |
3 | export function MetaTheme() {
4 | useEffect(() => {
5 | const updateThemeColor = () => {
6 | const bgColor = window.getComputedStyle(
7 | document.body
8 | ).backgroundColor
9 |
10 | const metaThemeColor = document.querySelector(
11 | "meta[name=theme-color]"
12 | )
13 |
14 | metaThemeColor?.setAttribute("content", bgColor)
15 | }
16 |
17 | const observer = new MutationObserver(updateThemeColor)
18 |
19 | observer.observe(document.documentElement, {
20 | attributes: true,
21 | attributeFilter: ["class"]
22 | })
23 |
24 | return () => observer.disconnect()
25 | }, [])
26 |
27 | return null
28 | }
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.ts", "**/*.tsx"],
3 | "compilerOptions": {
4 | "target": "ES2022",
5 | "jsx": "react-jsx",
6 | "module": "ESNext",
7 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
8 | "types": ["vite/client"],
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "verbatimModuleSyntax": false,
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "skipLibCheck": true,
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "noUncheckedSideEffectImports": true,
23 | "baseUrl": ".",
24 | "paths": {
25 | "@/*": ["./src/*"]
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/2.2.7/schema.json",
3 | "vcs": {
4 | "enabled": true,
5 | "clientKind": "git",
6 | "useIgnoreFile": true
7 | },
8 | "files": {
9 | "ignoreUnknown": true,
10 | "includes": ["**", "!**/src/routeTree.gen.ts", "!**/*.css"]
11 | },
12 | "formatter": {
13 | "enabled": true,
14 | "indentStyle": "space",
15 | "indentWidth": 4
16 | },
17 | "assist": { "actions": { "source": { "organizeImports": "on" } } },
18 | "linter": {
19 | "enabled": true,
20 | "rules": {
21 | "recommended": true,
22 | "style": {
23 | "noNonNullAssertion": "off"
24 | }
25 | }
26 | },
27 | "javascript": {
28 | "formatter": {
29 | "quoteStyle": "double",
30 | "semicolons": "asNeeded",
31 | "trailingCommas": "none"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/providers.tsx:
--------------------------------------------------------------------------------
1 | import { AuthUIProvider } from "@daveyplate/better-auth-ui"
2 | import { Link, useRouter } from "@tanstack/react-router"
3 | import { ThemeProvider } from "next-themes"
4 |
5 | import { authClient } from "@/lib/auth-client"
6 | import { MetaTheme } from "./meta-theme"
7 |
8 | export function Providers({ children }: { children: React.ReactNode }) {
9 | const { navigate } = useRouter()
10 |
11 | return (
12 |
18 | navigate({ href })}
21 | replace={(href) => navigate({ href, replace: true })}
22 | Link={({ href, ...props }) => }
23 | >
24 | {children}
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/mode-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react"
4 | import { useTheme } from "next-themes"
5 |
6 | import { Button } from "./ui/button"
7 | import {
8 | DropdownMenu,
9 | DropdownMenuContent,
10 | DropdownMenuItem,
11 | DropdownMenuTrigger
12 | } from "./ui/dropdown-menu"
13 |
14 | export function ModeToggle() {
15 | const { setTheme } = useTheme()
16 |
17 | return (
18 |
19 |
20 |
25 |
26 |
27 | Toggle theme
28 |
29 |
30 | e.preventDefault()}
33 | >
34 | setTheme("light")}>
35 |
36 | Light
37 |
38 | setTheme("dark")}>
39 |
40 | Dark
41 |
42 | setTheme("system")}>
43 |
44 | System
45 |
46 |
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/src/routes/__root.tsx:
--------------------------------------------------------------------------------
1 | import { TanStackDevtools } from "@tanstack/react-devtools"
2 | import { createRootRoute, HeadContent, Scripts } from "@tanstack/react-router"
3 | import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"
4 | import { Header } from "@/components/header"
5 | import { Providers } from "@/components/providers"
6 | import appCss from "../styles/styles.css?url"
7 |
8 | export const Route = createRootRoute({
9 | head: () => ({
10 | meta: [
11 | { title: "Better Auth Starter" },
12 | { charSet: "utf-8" },
13 | {
14 | name: "viewport",
15 | content: "width=device-width, initial-scale=1"
16 | },
17 | {
18 | name: "theme-color",
19 | content: "var(--bg-background)"
20 | }
21 | ],
22 | links: [
23 | {
24 | rel: "stylesheet",
25 | href: appCss
26 | }
27 | ]
28 | }),
29 |
30 | shellComponent: RootDocument
31 | })
32 |
33 | function RootDocument({ children }: { children: React.ReactNode }) {
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {children}
45 |
46 |
47 |
55 | }
56 | ]}
57 | />
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/header.tsx:
--------------------------------------------------------------------------------
1 | import { GitHubIcon, UserButton } from "@daveyplate/better-auth-ui"
2 | import { Link } from "@tanstack/react-router"
3 | import { ModeToggle } from "./mode-toggle"
4 | import { Button } from "./ui/button"
5 |
6 | export function Header() {
7 | return (
8 |
9 |
10 |
19 | Better Auth UI Logo
20 |
26 |
27 | BETTER-AUTH. STARTER
28 |
29 |
30 |
49 |
50 | )
51 | }
52 |
--------------------------------------------------------------------------------
/auth-schema.ts:
--------------------------------------------------------------------------------
1 | import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"
2 |
3 | export const users = pgTable("users", {
4 | id: text("id").primaryKey(),
5 | name: text("name").notNull(),
6 | email: text("email").notNull().unique(),
7 | emailVerified: boolean("email_verified").notNull(),
8 | image: text("image"),
9 | createdAt: timestamp("created_at").notNull(),
10 | updatedAt: timestamp("updated_at").notNull()
11 | })
12 |
13 | export const sessions = pgTable("sessions", {
14 | id: text("id").primaryKey(),
15 | expiresAt: timestamp("expires_at").notNull(),
16 | token: text("token").notNull().unique(),
17 | createdAt: timestamp("created_at").notNull(),
18 | updatedAt: timestamp("updated_at").notNull(),
19 | ipAddress: text("ip_address"),
20 | userAgent: text("user_agent"),
21 | userId: text("user_id")
22 | .notNull()
23 | .references(() => users.id, { onDelete: "cascade" })
24 | })
25 |
26 | export const accounts = pgTable("accounts", {
27 | id: text("id").primaryKey(),
28 | accountId: text("account_id").notNull(),
29 | providerId: text("provider_id").notNull(),
30 | userId: text("user_id")
31 | .notNull()
32 | .references(() => users.id, { onDelete: "cascade" }),
33 | accessToken: text("access_token"),
34 | refreshToken: text("refresh_token"),
35 | idToken: text("id_token"),
36 | accessTokenExpiresAt: timestamp("access_token_expires_at"),
37 | refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
38 | scope: text("scope"),
39 | password: text("password"),
40 | createdAt: timestamp("created_at").notNull(),
41 | updatedAt: timestamp("updated_at").notNull()
42 | })
43 |
44 | export const verifications = pgTable("verifications", {
45 | id: text("id").primaryKey(),
46 | identifier: text("identifier").notNull(),
47 | value: text("value").notNull(),
48 | expiresAt: timestamp("expires_at").notNull(),
49 | createdAt: timestamp("created_at"),
50 | updatedAt: timestamp("updated_at")
51 | })
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tanstack-start-hybrid",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite dev --port 3000",
7 | "build": "vite build",
8 | "serve": "vite preview",
9 | "test": "vitest run",
10 | "format": "biome format",
11 | "lint": "biome lint",
12 | "check": "biome check"
13 | },
14 | "dependencies": {
15 | "@daveyplate/better-auth-ui": "^3.3.0",
16 | "@radix-ui/react-dropdown-menu": "^2.1.16",
17 | "@radix-ui/react-slot": "^1.2.4",
18 | "@tailwindcss/vite": "^4.1.18",
19 | "@tanstack/nitro-v2-vite-plugin": "^1.141.0",
20 | "@tanstack/react-devtools": "^0.8.4",
21 | "@tanstack/react-router": "^1.141.2",
22 | "@tanstack/react-router-devtools": "^1.141.2",
23 | "@tanstack/react-router-ssr-query": "^1.141.2",
24 | "@tanstack/react-start": "^1.141.3",
25 | "@tanstack/router-plugin": "^1.141.2",
26 | "better-auth": "^1.4.7",
27 | "class-variance-authority": "^0.7.1",
28 | "clsx": "^2.1.1",
29 | "dotenv": "^17.2.3",
30 | "drizzle-orm": "^0.45.1",
31 | "lucide-react": "^0.561.0",
32 | "next-themes": "^0.4.6",
33 | "pg": "^8.16.3",
34 | "react": "^19.2.3",
35 | "react-dom": "^19.2.3",
36 | "tailwind-merge": "^3.4.0",
37 | "tailwindcss": "^4.1.18",
38 | "tailwindcss-safe-area": "^1.2.0",
39 | "vite-tsconfig-paths": "^6.0.1"
40 | },
41 | "devDependencies": {
42 | "@biomejs/biome": "2.3.8",
43 | "@testing-library/dom": "^10.4.1",
44 | "@testing-library/react": "^16.3.0",
45 | "@types/node": "^25.0.2",
46 | "@types/pg": "^8.16.0",
47 | "@types/react": "^19.2.7",
48 | "@types/react-dom": "^19.2.3",
49 | "@vitejs/plugin-react": "^5.1.2",
50 | "drizzle-kit": "^0.31.8",
51 | "jsdom": "^27.3.0",
52 | "tsx": "^4.21.0",
53 | "tw-animate-css": "^1.4.0",
54 | "typescript": "^5.9.3",
55 | "vite": "^7.2.7",
56 | "vite-plugin-devtools-json": "^1.0.0",
57 | "vitest": "^4.0.15",
58 | "web-vitals": "^5.1.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 | import type * as React from "react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
16 | outline:
17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18 | secondary:
19 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21 | link: "text-primary underline-offset-4 hover:underline"
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2 has-[>svg]:px-3",
25 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27 | icon: "size-9",
28 | "icon-sm": "size-8",
29 | "icon-lg": "size-10"
30 | }
31 | },
32 | defaultVariants: {
33 | variant: "default",
34 | size: "default"
35 | }
36 | }
37 | )
38 |
39 | function Button({
40 | className,
41 | variant,
42 | size,
43 | asChild = false,
44 | ...props
45 | }: React.ComponentProps<"button"> &
46 | VariantProps & {
47 | asChild?: boolean
48 | }) {
49 | const Comp = asChild ? Slot : "button"
50 |
51 | return (
52 |
57 | )
58 | }
59 |
60 | export { Button, buttonVariants }
61 |
--------------------------------------------------------------------------------
/src/routeTree.gen.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | // @ts-nocheck
4 |
5 | // noinspection JSUnusedGlobalSymbols
6 |
7 | // This file was automatically generated by TanStack Router.
8 | // You should NOT make any changes in this file as it will be overwritten.
9 | // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10 |
11 | import { Route as rootRouteImport } from './routes/__root'
12 | import { Route as IndexRouteImport } from './routes/index'
13 | import { Route as AuthPathRouteImport } from './routes/auth/$path'
14 | import { Route as AccountPathRouteImport } from './routes/account/$path'
15 | import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
16 |
17 | const IndexRoute = IndexRouteImport.update({
18 | id: '/',
19 | path: '/',
20 | getParentRoute: () => rootRouteImport,
21 | } as any)
22 | const AuthPathRoute = AuthPathRouteImport.update({
23 | id: '/auth/$path',
24 | path: '/auth/$path',
25 | getParentRoute: () => rootRouteImport,
26 | } as any)
27 | const AccountPathRoute = AccountPathRouteImport.update({
28 | id: '/account/$path',
29 | path: '/account/$path',
30 | getParentRoute: () => rootRouteImport,
31 | } as any)
32 | const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
33 | id: '/api/auth/$',
34 | path: '/api/auth/$',
35 | getParentRoute: () => rootRouteImport,
36 | } as any)
37 |
38 | export interface FileRoutesByFullPath {
39 | '/': typeof IndexRoute
40 | '/account/$path': typeof AccountPathRoute
41 | '/auth/$path': typeof AuthPathRoute
42 | '/api/auth/$': typeof ApiAuthSplatRoute
43 | }
44 | export interface FileRoutesByTo {
45 | '/': typeof IndexRoute
46 | '/account/$path': typeof AccountPathRoute
47 | '/auth/$path': typeof AuthPathRoute
48 | '/api/auth/$': typeof ApiAuthSplatRoute
49 | }
50 | export interface FileRoutesById {
51 | __root__: typeof rootRouteImport
52 | '/': typeof IndexRoute
53 | '/account/$path': typeof AccountPathRoute
54 | '/auth/$path': typeof AuthPathRoute
55 | '/api/auth/$': typeof ApiAuthSplatRoute
56 | }
57 | export interface FileRouteTypes {
58 | fileRoutesByFullPath: FileRoutesByFullPath
59 | fullPaths: '/' | '/account/$path' | '/auth/$path' | '/api/auth/$'
60 | fileRoutesByTo: FileRoutesByTo
61 | to: '/' | '/account/$path' | '/auth/$path' | '/api/auth/$'
62 | id: '__root__' | '/' | '/account/$path' | '/auth/$path' | '/api/auth/$'
63 | fileRoutesById: FileRoutesById
64 | }
65 | export interface RootRouteChildren {
66 | IndexRoute: typeof IndexRoute
67 | AccountPathRoute: typeof AccountPathRoute
68 | AuthPathRoute: typeof AuthPathRoute
69 | ApiAuthSplatRoute: typeof ApiAuthSplatRoute
70 | }
71 |
72 | declare module '@tanstack/react-router' {
73 | interface FileRoutesByPath {
74 | '/': {
75 | id: '/'
76 | path: '/'
77 | fullPath: '/'
78 | preLoaderRoute: typeof IndexRouteImport
79 | parentRoute: typeof rootRouteImport
80 | }
81 | '/auth/$path': {
82 | id: '/auth/$path'
83 | path: '/auth/$path'
84 | fullPath: '/auth/$path'
85 | preLoaderRoute: typeof AuthPathRouteImport
86 | parentRoute: typeof rootRouteImport
87 | }
88 | '/account/$path': {
89 | id: '/account/$path'
90 | path: '/account/$path'
91 | fullPath: '/account/$path'
92 | preLoaderRoute: typeof AccountPathRouteImport
93 | parentRoute: typeof rootRouteImport
94 | }
95 | '/api/auth/$': {
96 | id: '/api/auth/$'
97 | path: '/api/auth/$'
98 | fullPath: '/api/auth/$'
99 | preLoaderRoute: typeof ApiAuthSplatRouteImport
100 | parentRoute: typeof rootRouteImport
101 | }
102 | }
103 | }
104 |
105 | const rootRouteChildren: RootRouteChildren = {
106 | IndexRoute: IndexRoute,
107 | AccountPathRoute: AccountPathRoute,
108 | AuthPathRoute: AuthPathRoute,
109 | ApiAuthSplatRoute: ApiAuthSplatRoute,
110 | }
111 | export const routeTree = rootRouteImport
112 | ._addFileChildren(rootRouteChildren)
113 | ._addFileTypes()
114 |
115 | import type { getRouter } from './router.tsx'
116 | import type { createStart } from '@tanstack/react-start'
117 | declare module '@tanstack/react-start' {
118 | interface Register {
119 | ssr: true
120 | router: Awaited>
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/styles/styles.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 | @import "tw-animate-css";
3 |
4 | @import "./custom.css";
5 |
6 | @custom-variant dark (&:is(.dark *));
7 |
8 | @theme inline {
9 | --radius-sm: calc(var(--radius) - 4px);
10 | --radius-md: calc(var(--radius) - 2px);
11 | --radius-lg: var(--radius);
12 | --radius-xl: calc(var(--radius) + 4px);
13 | --color-background: var(--background);
14 | --color-foreground: var(--foreground);
15 | --color-card: var(--card);
16 | --color-card-foreground: var(--card-foreground);
17 | --color-popover: var(--popover);
18 | --color-popover-foreground: var(--popover-foreground);
19 | --color-primary: var(--primary);
20 | --color-primary-foreground: var(--primary-foreground);
21 | --color-secondary: var(--secondary);
22 | --color-secondary-foreground: var(--secondary-foreground);
23 | --color-muted: var(--muted);
24 | --color-muted-foreground: var(--muted-foreground);
25 | --color-accent: var(--accent);
26 | --color-accent-foreground: var(--accent-foreground);
27 | --color-destructive: var(--destructive);
28 | --color-border: var(--border);
29 | --color-input: var(--input);
30 | --color-ring: var(--ring);
31 | --color-chart-1: var(--chart-1);
32 | --color-chart-2: var(--chart-2);
33 | --color-chart-3: var(--chart-3);
34 | --color-chart-4: var(--chart-4);
35 | --color-chart-5: var(--chart-5);
36 | --color-sidebar: var(--sidebar);
37 | --color-sidebar-foreground: var(--sidebar-foreground);
38 | --color-sidebar-primary: var(--sidebar-primary);
39 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
40 | --color-sidebar-accent: var(--sidebar-accent);
41 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
42 | --color-sidebar-border: var(--sidebar-border);
43 | --color-sidebar-ring: var(--sidebar-ring);
44 | }
45 |
46 | :root {
47 | --radius: 0.625rem;
48 | --background: oklch(1 0 0);
49 | --foreground: oklch(0.145 0 0);
50 | --card: oklch(1 0 0);
51 | --card-foreground: oklch(0.145 0 0);
52 | --popover: oklch(1 0 0);
53 | --popover-foreground: oklch(0.145 0 0);
54 | --primary: oklch(0.205 0 0);
55 | --primary-foreground: oklch(0.985 0 0);
56 | --secondary: oklch(0.97 0 0);
57 | --secondary-foreground: oklch(0.205 0 0);
58 | --muted: oklch(0.97 0 0);
59 | --muted-foreground: oklch(0.556 0 0);
60 | --accent: oklch(0.97 0 0);
61 | --accent-foreground: oklch(0.205 0 0);
62 | --destructive: oklch(0.577 0.245 27.325);
63 | --border: oklch(0.922 0 0);
64 | --input: oklch(0.922 0 0);
65 | --ring: oklch(0.708 0 0);
66 | --chart-1: oklch(0.646 0.222 41.116);
67 | --chart-2: oklch(0.6 0.118 184.704);
68 | --chart-3: oklch(0.398 0.07 227.392);
69 | --chart-4: oklch(0.828 0.189 84.429);
70 | --chart-5: oklch(0.769 0.188 70.08);
71 | --sidebar: oklch(0.985 0 0);
72 | --sidebar-foreground: oklch(0.145 0 0);
73 | --sidebar-primary: oklch(0.205 0 0);
74 | --sidebar-primary-foreground: oklch(0.985 0 0);
75 | --sidebar-accent: oklch(0.97 0 0);
76 | --sidebar-accent-foreground: oklch(0.205 0 0);
77 | --sidebar-border: oklch(0.922 0 0);
78 | --sidebar-ring: oklch(0.708 0 0);
79 | }
80 |
81 | .dark {
82 | --background: oklch(0.145 0 0);
83 | --foreground: oklch(0.985 0 0);
84 | --card: oklch(0.205 0 0);
85 | --card-foreground: oklch(0.985 0 0);
86 | --popover: oklch(0.205 0 0);
87 | --popover-foreground: oklch(0.985 0 0);
88 | --primary: oklch(0.922 0 0);
89 | --primary-foreground: oklch(0.205 0 0);
90 | --secondary: oklch(0.269 0 0);
91 | --secondary-foreground: oklch(0.985 0 0);
92 | --muted: oklch(0.269 0 0);
93 | --muted-foreground: oklch(0.708 0 0);
94 | --accent: oklch(0.269 0 0);
95 | --accent-foreground: oklch(0.985 0 0);
96 | --destructive: oklch(0.704 0.191 22.216);
97 | --border: oklch(1 0 0 / 10%);
98 | --input: oklch(1 0 0 / 15%);
99 | --ring: oklch(0.556 0 0);
100 | --chart-1: oklch(0.488 0.243 264.376);
101 | --chart-2: oklch(0.696 0.17 162.48);
102 | --chart-3: oklch(0.769 0.188 70.08);
103 | --chart-4: oklch(0.627 0.265 303.9);
104 | --chart-5: oklch(0.645 0.246 16.439);
105 | --sidebar: oklch(0.205 0 0);
106 | --sidebar-foreground: oklch(0.985 0 0);
107 | --sidebar-primary: oklch(0.488 0.243 264.376);
108 | --sidebar-primary-foreground: oklch(0.985 0 0);
109 | --sidebar-accent: oklch(0.269 0 0);
110 | --sidebar-accent-foreground: oklch(0.985 0 0);
111 | --sidebar-border: oklch(1 0 0 / 10%);
112 | --sidebar-ring: oklch(0.556 0 0);
113 | }
114 |
115 | @layer base {
116 | * {
117 | @apply border-border outline-ring/50;
118 | }
119 | body {
120 | @apply bg-background text-foreground;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Welcome to your new TanStack app!
2 |
3 | # Getting Started
4 |
5 | To run this application:
6 |
7 | ```bash
8 | pnpm install
9 | pnpm start
10 | ```
11 |
12 | # Building For Production
13 |
14 | To build this application for production:
15 |
16 | ```bash
17 | pnpm build
18 | ```
19 |
20 | ## Testing
21 |
22 | This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
23 |
24 | ```bash
25 | pnpm test
26 | ```
27 |
28 | ## Styling
29 |
30 | This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
31 |
32 |
33 | ## Linting & Formatting
34 |
35 | This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available:
36 |
37 |
38 | ```bash
39 | pnpm lint
40 | pnpm format
41 | pnpm check
42 | ```
43 |
44 |
45 |
46 | ## Routing
47 | This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`.
48 |
49 | ### Adding A Route
50 |
51 | To add a new route to your application just add another a new file in the `./src/routes` directory.
52 |
53 | TanStack will automatically generate the content of the route file for you.
54 |
55 | Now that you have two routes you can use a `Link` component to navigate between them.
56 |
57 | ### Adding Links
58 |
59 | To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
60 |
61 | ```tsx
62 | import { Link } from "@tanstack/react-router";
63 | ```
64 |
65 | Then anywhere in your JSX you can use it like so:
66 |
67 | ```tsx
68 | About
69 | ```
70 |
71 | This will create a link that will navigate to the `/about` route.
72 |
73 | More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
74 |
75 | ### Using A Layout
76 |
77 | In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the ` ` component.
78 |
79 | Here is an example layout that includes a header:
80 |
81 | ```tsx
82 | import { Outlet, createRootRoute } from '@tanstack/react-router'
83 | import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
84 |
85 | import { Link } from "@tanstack/react-router";
86 |
87 | export const Route = createRootRoute({
88 | component: () => (
89 | <>
90 |
91 |
92 | Home
93 | About
94 |
95 |
96 |
97 |
98 | >
99 | ),
100 | })
101 | ```
102 |
103 | The ` ` component is not required so you can remove it if you don't want it in your layout.
104 |
105 | More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
106 |
107 |
108 | ## Data Fetching
109 |
110 | There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
111 |
112 | For example:
113 |
114 | ```tsx
115 | const peopleRoute = createRoute({
116 | getParentRoute: () => rootRoute,
117 | path: "/people",
118 | loader: async () => {
119 | const response = await fetch("https://swapi.dev/api/people");
120 | return response.json() as Promise<{
121 | results: {
122 | name: string;
123 | }[];
124 | }>;
125 | },
126 | component: () => {
127 | const data = peopleRoute.useLoaderData();
128 | return (
129 |
130 | {data.results.map((person) => (
131 | {person.name}
132 | ))}
133 |
134 | );
135 | },
136 | });
137 | ```
138 |
139 | Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
140 |
141 | ### React-Query
142 |
143 | React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze.
144 |
145 | First add your dependencies:
146 |
147 | ```bash
148 | pnpm add @tanstack/react-query @tanstack/react-query-devtools
149 | ```
150 |
151 | Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`.
152 |
153 | ```tsx
154 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
155 |
156 | // ...
157 |
158 | const queryClient = new QueryClient();
159 |
160 | // ...
161 |
162 | if (!rootElement.innerHTML) {
163 | const root = ReactDOM.createRoot(rootElement);
164 |
165 | root.render(
166 |
167 |
168 |
169 | );
170 | }
171 | ```
172 |
173 | You can also add TanStack Query Devtools to the root route (optional).
174 |
175 | ```tsx
176 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
177 |
178 | const rootRoute = createRootRoute({
179 | component: () => (
180 | <>
181 |
182 |
183 |
184 | >
185 | ),
186 | });
187 | ```
188 |
189 | Now you can use `useQuery` to fetch your data.
190 |
191 | ```tsx
192 | import { useQuery } from "@tanstack/react-query";
193 |
194 | import "./App.css";
195 |
196 | function App() {
197 | const { data } = useQuery({
198 | queryKey: ["people"],
199 | queryFn: () =>
200 | fetch("https://swapi.dev/api/people")
201 | .then((res) => res.json())
202 | .then((data) => data.results as { name: string }[]),
203 | initialData: [],
204 | });
205 |
206 | return (
207 |
208 |
209 | {data.map((person) => (
210 | {person.name}
211 | ))}
212 |
213 |
214 | );
215 | }
216 |
217 | export default App;
218 | ```
219 |
220 | You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview).
221 |
222 | ## State Management
223 |
224 | Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project.
225 |
226 | First you need to add TanStack Store as a dependency:
227 |
228 | ```bash
229 | pnpm add @tanstack/store
230 | ```
231 |
232 | Now let's create a simple counter in the `src/App.tsx` file as a demonstration.
233 |
234 | ```tsx
235 | import { useStore } from "@tanstack/react-store";
236 | import { Store } from "@tanstack/store";
237 | import "./App.css";
238 |
239 | const countStore = new Store(0);
240 |
241 | function App() {
242 | const count = useStore(countStore);
243 | return (
244 |
245 | countStore.setState((n) => n + 1)}>
246 | Increment - {count}
247 |
248 |
249 | );
250 | }
251 |
252 | export default App;
253 | ```
254 |
255 | One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates.
256 |
257 | Let's check this out by doubling the count using derived state.
258 |
259 | ```tsx
260 | import { useStore } from "@tanstack/react-store";
261 | import { Store, Derived } from "@tanstack/store";
262 | import "./App.css";
263 |
264 | const countStore = new Store(0);
265 |
266 | const doubledStore = new Derived({
267 | fn: () => countStore.state * 2,
268 | deps: [countStore],
269 | });
270 | doubledStore.mount();
271 |
272 | function App() {
273 | const count = useStore(countStore);
274 | const doubledCount = useStore(doubledStore);
275 |
276 | return (
277 |
278 |
countStore.setState((n) => n + 1)}>
279 | Increment - {count}
280 |
281 |
Doubled - {doubledCount}
282 |
283 | );
284 | }
285 |
286 | export default App;
287 | ```
288 |
289 | We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating.
290 |
291 | Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook.
292 |
293 | You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest).
294 |
295 | # Demo files
296 |
297 | Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
298 |
299 | # Learn More
300 |
301 | You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
302 |
--------------------------------------------------------------------------------
/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
2 | import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
3 | import type * as React from "react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | function DropdownMenu({
8 | ...props
9 | }: React.ComponentProps) {
10 | return
11 | }
12 |
13 | function DropdownMenuPortal({
14 | ...props
15 | }: React.ComponentProps) {
16 | return (
17 |
21 | )
22 | }
23 |
24 | function DropdownMenuTrigger({
25 | ...props
26 | }: React.ComponentProps) {
27 | return (
28 |
32 | )
33 | }
34 |
35 | function DropdownMenuContent({
36 | className,
37 | sideOffset = 4,
38 | ...props
39 | }: React.ComponentProps) {
40 | return (
41 |
42 |
51 |
52 | )
53 | }
54 |
55 | function DropdownMenuGroup({
56 | ...props
57 | }: React.ComponentProps) {
58 | return (
59 |
63 | )
64 | }
65 |
66 | function DropdownMenuItem({
67 | className,
68 | inset,
69 | variant = "default",
70 | ...props
71 | }: React.ComponentProps & {
72 | inset?: boolean
73 | variant?: "default" | "destructive"
74 | }) {
75 | return (
76 |
86 | )
87 | }
88 |
89 | function DropdownMenuCheckboxItem({
90 | className,
91 | children,
92 | checked,
93 | ...props
94 | }: React.ComponentProps) {
95 | return (
96 |
105 |
106 |
107 |
108 |
109 |
110 | {children}
111 |
112 | )
113 | }
114 |
115 | function DropdownMenuRadioGroup({
116 | ...props
117 | }: React.ComponentProps) {
118 | return (
119 |
123 | )
124 | }
125 |
126 | function DropdownMenuRadioItem({
127 | className,
128 | children,
129 | ...props
130 | }: React.ComponentProps) {
131 | return (
132 |
140 |
141 |
142 |
143 |
144 |
145 | {children}
146 |
147 | )
148 | }
149 |
150 | function DropdownMenuLabel({
151 | className,
152 | inset,
153 | ...props
154 | }: React.ComponentProps & {
155 | inset?: boolean
156 | }) {
157 | return (
158 |
167 | )
168 | }
169 |
170 | function DropdownMenuSeparator({
171 | className,
172 | ...props
173 | }: React.ComponentProps) {
174 | return (
175 |
180 | )
181 | }
182 |
183 | function DropdownMenuShortcut({
184 | className,
185 | ...props
186 | }: React.ComponentProps<"span">) {
187 | return (
188 |
196 | )
197 | }
198 |
199 | function DropdownMenuSub({
200 | ...props
201 | }: React.ComponentProps) {
202 | return (
203 |
204 | )
205 | }
206 |
207 | function DropdownMenuSubTrigger({
208 | className,
209 | inset,
210 | children,
211 | ...props
212 | }: React.ComponentProps & {
213 | inset?: boolean
214 | }) {
215 | return (
216 |
225 | {children}
226 |
227 |
228 | )
229 | }
230 |
231 | function DropdownMenuSubContent({
232 | className,
233 | ...props
234 | }: React.ComponentProps) {
235 | return (
236 |
244 | )
245 | }
246 |
247 | export {
248 | DropdownMenu,
249 | DropdownMenuPortal,
250 | DropdownMenuTrigger,
251 | DropdownMenuContent,
252 | DropdownMenuGroup,
253 | DropdownMenuLabel,
254 | DropdownMenuItem,
255 | DropdownMenuCheckboxItem,
256 | DropdownMenuRadioGroup,
257 | DropdownMenuRadioItem,
258 | DropdownMenuSeparator,
259 | DropdownMenuShortcut,
260 | DropdownMenuSub,
261 | DropdownMenuSubTrigger,
262 | DropdownMenuSubContent
263 | }
264 |
--------------------------------------------------------------------------------
/public/tanstack-word-logo-white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | logo
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------