mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
feat: add TanStack Start integration
This commit is contained in:
@@ -22,11 +22,11 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
return (
|
||||
<div className="md:w-10/12 overflow-hidden mt-10 mx-auto font-geist relative md:border-l-0 md:border-[1.2px] rounded-none -pr-2">
|
||||
<Plus className="absolute top-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 md:mx-0 grid-rows-4 md:grid-rows-4 w-full">
|
||||
<div className="grid w-full grid-cols-1 grid-rows-4 md:grid-cols-3 md:mx-0 md:grid-rows-4">
|
||||
<div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10 overflow-clip">
|
||||
<Plus className="absolute bottom-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
||||
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<PlugZap2Icon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Framework Agnostic{" "}
|
||||
@@ -35,12 +35,12 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
<div className="mt-2">
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-xl md:text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-xl font-normal tracking-tighter md:text-2xl">
|
||||
Supports popular <strong>frameworks</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
Supports your favorite frontend, backend and meta frameworks,
|
||||
including React, Vue, Svelte, Astro, Solid, Next.js, Nuxt, Hono,
|
||||
and more{" "}
|
||||
@@ -53,19 +53,19 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
<div className="relative items-start justify-start border-l-[1.2px] border-t-[1.2px] md:border-t-0 transform-gpu flex flex-col p-10">
|
||||
<Plus className="absolute bottom-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
||||
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<LockClosedIcon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">Authentication</p>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-2xl font-normal tracking-tighter">
|
||||
Email & Password <strong>Authentication</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
Builtin support for email and password authentication, with secure
|
||||
password hashing and account management features{" "}
|
||||
<a className="text-gray-50" href="/docs" target="_blank">
|
||||
@@ -77,19 +77,19 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
<div className="relative items-start justify-start md:border-l-[0.2px] border-t-[1.2px] md:border-t-0 flex flex-col p-10">
|
||||
<Plus className="absolute bottom-[-17px] left-[-17px] text-black/20 dark:text-white/30 w-8 h-8" />
|
||||
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<Webhook className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">Social Sign-on</p>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-2xl font-normal tracking-tighter">
|
||||
Support multiple <strong>OAuth providers.</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
Allow users to sign in with their accounts, including GitHub,
|
||||
Google, Discord, Twitter, and more.{" "}
|
||||
<a className="text-gray-50" href="#" target="_blank">
|
||||
@@ -99,19 +99,19 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="items-start justify-start border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 ">
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<ShieldCheckIcon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">Two Factor</p>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-2xl font-normal tracking-tighter">
|
||||
Two Factor <strong>Authentication</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
With our built-in two factor authentication plugin, you can add an
|
||||
extra layer of security to your account.{" "}
|
||||
<Link className="text-gray-50" href="/docs" target="_blank">
|
||||
@@ -121,7 +121,7 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="items-start justify-staart border-l-[1.2px] border-t-[1.2px] flex flex-col p-10 ">
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<RabbitIcon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Organization & Access Control{" "}
|
||||
@@ -130,12 +130,12 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
<div className="mt-2">
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-2xl font-normal tracking-tighter">
|
||||
Gain and manage <strong>access.</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
Manage users and their access to resources within your
|
||||
application.{" "}
|
||||
<a className="text-gray-50" href="/docs" target="_blank">
|
||||
@@ -145,7 +145,7 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="items-start justify-start border-l-[1.2px] border-t-[1.2px] transform-gpu relative flex flex-col p-10 ">
|
||||
<div className="flex gap-2 items-center my-1">
|
||||
<div className="flex items-center gap-2 my-1">
|
||||
<PlugIcon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Plugin Ecosystem{" "}
|
||||
@@ -153,13 +153,13 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
</div>
|
||||
<div className="max-w-full">
|
||||
<div className="flex gap-3 ">
|
||||
<p className="text-2xl tracking-tighter font-normal max-w-lg">
|
||||
<p className="max-w-lg text-2xl font-normal tracking-tighter">
|
||||
Extend your application with plugins
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<p className="text-left text-sm mt-2 text-muted-foreground">
|
||||
<p className="mt-2 text-sm text-left text-muted-foreground">
|
||||
Enhance your application with our official plugins and those
|
||||
created by the community.{" "}
|
||||
<a className="text-gray-50" href="/docs" target="_blank">
|
||||
@@ -169,15 +169,15 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative md:grid md:col-span-3 grid-cols-2 row-span-2 border-t-[1.2px] border-l-[1.2px] md:border-b-[1.2px] dark:border-b-0 h-full py-20 ">
|
||||
<div className="p-16 pt-10 md:px-10 h-full md:absolute top-0 left-0 w-full">
|
||||
<div className="flex flex-col gap-3 justify-center h-full items-center w-full">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="top-0 left-0 w-full h-full p-16 pt-10 md:px-10 md:absolute">
|
||||
<div className="flex flex-col items-center justify-center w-full h-full gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe2Icon className="w-4 h-4" />
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
Own your auth
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-4xl md:text-4xl mt-4 tracking-tighter font-normal max-w-md mx-auto text-center">
|
||||
<p className="max-w-md mx-auto mt-4 text-4xl font-normal tracking-tighter text-center md:text-4xl">
|
||||
<strong>Roll your own auth with confidence in minutes!</strong>
|
||||
</p>
|
||||
<div className="flex mt-[10px] z-20 justify-center items-start">
|
||||
@@ -190,6 +190,7 @@ export default function Features({ stars }: { stars: string | null }) {
|
||||
"solidStart",
|
||||
"react",
|
||||
"hono",
|
||||
"tanstack",
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -259,4 +259,40 @@ export const Icons = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
tanstack: (props?: SVGProps<any>) => (
|
||||
<svg
|
||||
className={cn(props?.className)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1.2em"
|
||||
height="1.2em"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<mask id="a" style={{maskType: "alpha"}} maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="50" className="fill-foreground"/>
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<circle cx="11" cy="119" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/>
|
||||
<circle cx="10" cy="125" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/>
|
||||
<circle cx="9" cy="131" r="52" className="fill-muted-foreground stroke-muted-foreground" strokeWidth="4"/>
|
||||
<circle cx="88" cy="119" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/>
|
||||
<path className="fill-foreground" d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z"/>
|
||||
<circle cx="89" cy="125" r="52" className="fill-muted-foreground stroke-foreground" strokeWidth="4"/>
|
||||
<circle cx="90" cy="131" r="52" className="fill-muted-foreground stroke-muted-foreground" strokeWidth="4"/>
|
||||
<ellipse cx="49.5" cy="119" rx="41.5" ry="51" className="fill-muted-foreground"/>
|
||||
<path d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z" className="fill-foreground stroke-muted-foreground"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z" className="fill-foreground"/>
|
||||
<path d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z" className="fill-foreground stroke-muted-foreground"/>
|
||||
<path d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z" className="fill-muted-foreground"/>
|
||||
<path d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z" className="stroke-muted-foreground"/>
|
||||
<path d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z" className="fill-foreground stroke-muted-foreground"/>
|
||||
<path d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z" className="fill-foreground"/>
|
||||
<path d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z" className="stroke-muted-foreground"/>
|
||||
<path d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z" className="fill-foreground"/>
|
||||
<path d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10" className="stroke-muted-foreground"/>
|
||||
<path d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z" className="fill-foreground"/>
|
||||
<path d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z" className="stroke-muted-foreground"/>
|
||||
<circle cx="91.5" cy="12.5" r="18.5" className="fill-foreground stroke-muted-foreground" strokeWidth="2"/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
};
|
||||
|
||||
@@ -601,12 +601,16 @@ export const contents: Content[] = [
|
||||
icon: Icons.svelteKit,
|
||||
href: "/docs/integrations/svelte-kit",
|
||||
},
|
||||
|
||||
{
|
||||
title: "Solid Start",
|
||||
icon: Icons.solidStart,
|
||||
href: "/docs/integrations/solid-start",
|
||||
},
|
||||
{
|
||||
title: "TanStack Start",
|
||||
icon: Icons.tanstack,
|
||||
href: "/docs/integrations/tanstack",
|
||||
},
|
||||
{
|
||||
group: true,
|
||||
title: "Backend",
|
||||
|
||||
@@ -34,4 +34,8 @@ export const techStackIcons: TechStackIconType = {
|
||||
name: "Astro",
|
||||
icon: <Icons.astro className="w-10 h-10" />,
|
||||
},
|
||||
tanstack: {
|
||||
name: "TanStack Start",
|
||||
icon: <Icons.tanstack className="w-10 h-10" />,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -292,7 +292,7 @@ The server provides a `session` object that you can use to access the session da
|
||||
|
||||
**Example: Using some popular frameworks**
|
||||
|
||||
<Tabs items={["NextJs", "Nuxt", "Svelte", "Astro", "Hono"]}>
|
||||
<Tabs items={["NextJs", "Nuxt", "Svelte", "Astro", "Hono", "TanStack"]}>
|
||||
<Tab value="NextJs">
|
||||
```ts title="server.ts"
|
||||
import { auth } from "./auth"; // path to your Better Auth server instance
|
||||
@@ -369,6 +369,20 @@ The server provides a `session` object that you can use to access the session da
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="TanStack">
|
||||
```ts title="app/routes/api/index.ts"
|
||||
import { auth } from "./auth";
|
||||
import { createAPIFileRoute } from "@tanstack/start/api";
|
||||
|
||||
export const Route = createAPIFileRoute("/api/$")({
|
||||
GET: async ({ request }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers
|
||||
})
|
||||
},
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Using Plugins
|
||||
|
||||
@@ -236,7 +236,7 @@ Create a new file or route in your framework's designated catch-all route handle
|
||||
Better Auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks.
|
||||
</Callout>
|
||||
|
||||
<Tabs items={["next-js", "nuxt", "svelte-kit", "remix", "solid-start", "hono", "express", "elysia"]} defaultValue="react">
|
||||
<Tabs items={["next-js", "nuxt", "svelte-kit", "remix", "solid-start", "hono", "express", "elysia", "tanstack-start"]} defaultValue="react">
|
||||
<Tab value="next-js">
|
||||
```ts title="/app/api/auth/[...all]/route.ts"
|
||||
import { auth } from "@/lib/auth"; // path to your auth file
|
||||
@@ -354,6 +354,22 @@ Better Auth supports any backend framework with standard Request and Response ob
|
||||
);
|
||||
```
|
||||
</Tab>
|
||||
<Tab value="tanstack-start">
|
||||
```ts
|
||||
// app/routes/api/auth/$.ts
|
||||
import { auth } from '~/lib/server/auth'
|
||||
import { createAPIFileRoute } from '@tanstack/start/api'
|
||||
|
||||
export const Route = createAPIFileRoute('/api/auth/$')({
|
||||
GET: ({ request }) => {
|
||||
return auth.handler(request)
|
||||
},
|
||||
POST: ({ request }) => {
|
||||
return auth.handler(request)
|
||||
},
|
||||
});
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
|
||||
29
docs/content/docs/integrations/tanstack.mdx
Normal file
29
docs/content/docs/integrations/tanstack.mdx
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: TanStack Start Integration
|
||||
description: Tanstack Start Integration Guide
|
||||
---
|
||||
|
||||
This integration guide is assuming you are using TanStack Start.
|
||||
|
||||
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
|
||||
|
||||
### Mount the handler
|
||||
|
||||
We need to mount the handler to a TanStack API endpoint.
|
||||
Create a new file: `/app/routes/api/auth/$.ts`
|
||||
|
||||
```ts
|
||||
import { auth } from '@/lib/auth'
|
||||
import { createAPIFileRoute } from '@tanstack/start/api'
|
||||
|
||||
export const Route = createAPIFileRoute('/api/auth/$')({
|
||||
GET: ({ request }) => {
|
||||
return auth.handler(request)
|
||||
},
|
||||
POST: ({ request }) => {
|
||||
return auth.handler(request)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This will allow you to access use the `getSession` method in all of your routes.
|
||||
27
examples/tanstack-example/README.md
Normal file
27
examples/tanstack-example/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||

|
||||
An example of using Better Auth with [TanStack Start](https://tanstack.com/start).
|
||||
|
||||
## Setup
|
||||
|
||||
To install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
To migrate Better-Auth:
|
||||
|
||||
```bash
|
||||
pnpx @better-auth/cli migrate
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Preview
|
||||

|
||||
|
||||
|
||||
12
examples/tanstack-example/app.config.ts
Normal file
12
examples/tanstack-example/app.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "@tanstack/start/config";
|
||||
import viteTsConfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [
|
||||
viteTsConfigPaths({
|
||||
projects: ["./tsconfig.json"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
6
examples/tanstack-example/app/api.ts
Normal file
6
examples/tanstack-example/app/api.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {
|
||||
createStartAPIHandler,
|
||||
defaultAPIFileRouteHandler,
|
||||
} from "@tanstack/start/api";
|
||||
|
||||
export default createStartAPIHandler(defaultAPIFileRouteHandler);
|
||||
12
examples/tanstack-example/app/client.tsx
Normal file
12
examples/tanstack-example/app/client.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { StartClient } from "@tanstack/start";
|
||||
import { hydrateRoot } from "react-dom/client";
|
||||
import { createRouter } from "./router";
|
||||
|
||||
const router = createRouter();
|
||||
|
||||
const root = document.getElementById("root");
|
||||
if (!root) {
|
||||
throw new Error("Root element not found");
|
||||
}
|
||||
|
||||
hydrateRoot(root, <StartClient router={router} />);
|
||||
78
examples/tanstack-example/app/components/login-form.tsx
Normal file
78
examples/tanstack-example/app/components/login-form.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { signIn } from "~/lib/client/auth";
|
||||
|
||||
export function LoginForm() {
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const form = e.target as HTMLFormElement;
|
||||
const data = new FormData(form);
|
||||
signIn.email(
|
||||
{
|
||||
email: data.get("email") as string,
|
||||
password: data.get("password") as string,
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
console.warn(error);
|
||||
toast.error(error.error.message);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("You have been logged in!");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="mx-auto max-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sign In</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to sign in to your account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
</div>
|
||||
<Input id="password" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Sign In
|
||||
</Button>
|
||||
</form>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link to="/auth/signup" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
90
examples/tanstack-example/app/components/register-form.tsx
Normal file
90
examples/tanstack-example/app/components/register-form.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { signUp } from "~/lib/client/auth";
|
||||
|
||||
export function RegisterForm() {
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const form = e.target as HTMLFormElement;
|
||||
const data = new FormData(form);
|
||||
console.log(data);
|
||||
signUp.email(
|
||||
{
|
||||
name: data.get("name") as string,
|
||||
email: data.get("email") as string,
|
||||
password: data.get("password") as string,
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
console.warn(error);
|
||||
toast.error(error.error.message);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Account has been created!");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="mx-auto max-w-sm">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sign Up</CardTitle>
|
||||
<CardDescription>
|
||||
Enter your email below to sign up to an account
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="grid gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Name</Label>
|
||||
<Input
|
||||
name="name"
|
||||
id="name"
|
||||
type="name"
|
||||
placeholder="John Doe"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
</div>
|
||||
<Input id="password" name="password" type="password" required />
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
Sign Up
|
||||
</Button>
|
||||
</form>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Already have an account?{" "}
|
||||
<Link to="/auth/signin" className="underline">
|
||||
Sign in
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
58
examples/tanstack-example/app/components/ui/button.tsx
Normal file
58
examples/tanstack-example/app/components/ui/button.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
88
examples/tanstack-example/app/components/ui/card.tsx
Normal file
88
examples/tanstack-example/app/components/ui/card.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardFooter.displayName = "CardFooter";
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
27
examples/tanstack-example/app/components/ui/input.tsx
Normal file
27
examples/tanstack-example/app/components/ui/input.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
26
examples/tanstack-example/app/components/ui/label.tsx
Normal file
26
examples/tanstack-example/app/components/ui/label.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
129
examples/tanstack-example/app/components/ui/navigation-menu.tsx
Normal file
129
examples/tanstack-example/app/components/ui/navigation-menu.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
"use client";
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
));
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName;
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
|
||||
);
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
));
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName;
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName;
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link;
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName;
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
));
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName;
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
};
|
||||
31
examples/tanstack-example/app/components/ui/sonner.tsx
Normal file
31
examples/tanstack-example/app/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner } from "sonner";
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton:
|
||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster };
|
||||
5
examples/tanstack-example/app/lib/client/auth.ts
Normal file
5
examples/tanstack-example/app/lib/client/auth.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
|
||||
export const { useSession, signIn, signOut, signUp } = createAuthClient({
|
||||
baseURL: "http://localhost:3000",
|
||||
});
|
||||
9
examples/tanstack-example/app/lib/server/auth.ts
Normal file
9
examples/tanstack-example/app/lib/server/auth.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import Database from "better-sqlite3";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new Database("data.db"),
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
81
examples/tanstack-example/app/lib/style/global.css
Normal file
81
examples/tanstack-example/app/lib/style/global.css
Normal file
@@ -0,0 +1,81 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
}
|
||||
6
examples/tanstack-example/app/lib/utils.ts
Normal file
6
examples/tanstack-example/app/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
9
examples/tanstack-example/app/login/page.tsx
Normal file
9
examples/tanstack-example/app/login/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { LoginForm } from "~/components/login-form"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="flex h-screen w-full items-center justify-center px-4">
|
||||
<LoginForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
136
examples/tanstack-example/app/routeTree.gen.ts
Normal file
136
examples/tanstack-example/app/routeTree.gen.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/* prettier-ignore-start */
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file is auto-generated by TanStack Router
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as AuthSignupImport } from './routes/auth/signup'
|
||||
import { Route as AuthSigninImport } from './routes/auth/signin'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AuthSignupRoute = AuthSignupImport.update({
|
||||
id: '/auth/signup',
|
||||
path: '/auth/signup',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AuthSigninRoute = AuthSigninImport.update({
|
||||
id: '/auth/signin',
|
||||
path: '/auth/signin',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/auth/signin': {
|
||||
id: '/auth/signin'
|
||||
path: '/auth/signin'
|
||||
fullPath: '/auth/signin'
|
||||
preLoaderRoute: typeof AuthSigninImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/auth/signup': {
|
||||
id: '/auth/signup'
|
||||
path: '/auth/signup'
|
||||
fullPath: '/auth/signup'
|
||||
preLoaderRoute: typeof AuthSignupImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/auth/signin': typeof AuthSigninRoute
|
||||
'/auth/signup': typeof AuthSignupRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/auth/signin': typeof AuthSigninRoute
|
||||
'/auth/signup': typeof AuthSignupRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
'/auth/signin': typeof AuthSigninRoute
|
||||
'/auth/signup': typeof AuthSignupRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/auth/signin' | '/auth/signup'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/auth/signin' | '/auth/signup'
|
||||
id: '__root__' | '/' | '/auth/signin' | '/auth/signup'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AuthSigninRoute: typeof AuthSigninRoute
|
||||
AuthSignupRoute: typeof AuthSignupRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AuthSigninRoute: AuthSigninRoute,
|
||||
AuthSignupRoute: AuthSignupRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
/* prettier-ignore-end */
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/auth/signin",
|
||||
"/auth/signup"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
},
|
||||
"/auth/signin": {
|
||||
"filePath": "auth/signin.tsx"
|
||||
},
|
||||
"/auth/signup": {
|
||||
"filePath": "auth/signup.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
16
examples/tanstack-example/app/router.tsx
Normal file
16
examples/tanstack-example/app/router.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
|
||||
export function createRouter() {
|
||||
const router = createTanStackRouter({
|
||||
routeTree,
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: ReturnType<typeof createRouter>;
|
||||
}
|
||||
}
|
||||
327
examples/tanstack-example/app/routes/__root.tsx
Normal file
327
examples/tanstack-example/app/routes/__root.tsx
Normal file
@@ -0,0 +1,327 @@
|
||||
import { Link, createRootRoute, useRouter } from "@tanstack/react-router";
|
||||
import { Outlet, ScrollRestoration } from "@tanstack/react-router";
|
||||
import { Body, Head, Html, Meta, Scripts } from "@tanstack/start";
|
||||
import type * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { signOut, useSession } from "~/lib/client/auth";
|
||||
import globalStylesheet from "~/lib/style/global.css?url";
|
||||
import "~/lib/style/global.css";
|
||||
import { DoorOpen, LoaderCircle, Moon, Sun } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "~/components/ui/navigation-menu";
|
||||
import { Toaster } from "~/components/ui/sonner";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
meta: () => [
|
||||
{
|
||||
charSet: "utf-8",
|
||||
},
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
},
|
||||
{
|
||||
title: "Better Auth - TanStack Start Example",
|
||||
},
|
||||
],
|
||||
links: () => [
|
||||
{
|
||||
rel: "stylesheet",
|
||||
href: globalStylesheet,
|
||||
},
|
||||
],
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
const [theme, setTheme] = useState<"light" | "dark">("light");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { data, isPending } = useSession();
|
||||
const { navigate } = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!data?.user) {
|
||||
if (!location.pathname.includes("auth/")) {
|
||||
navigate({ to: "/auth/signin" });
|
||||
}
|
||||
} else {
|
||||
navigate({ to: "/" });
|
||||
}
|
||||
setTheme(
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light",
|
||||
);
|
||||
}, [data, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPending) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [isPending]);
|
||||
|
||||
useEffect(() => {
|
||||
const root = window.document.documentElement;
|
||||
|
||||
root.classList.remove("light", "dark");
|
||||
|
||||
root.classList.add(theme);
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<RootDocument>
|
||||
{loading ? (
|
||||
<div className="flex h-screen w-screen items-center justify-center">
|
||||
<LoaderCircle className="animate-spin h-20 w-20" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<nav className="grid grid-cols-3 items-center w-full p-4">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<svg
|
||||
width="60"
|
||||
height="45"
|
||||
viewBox="0 0 60 45"
|
||||
fill="none"
|
||||
className="w-5 h-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Better Auth</title>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0 0H15V15H30V30H15V45H0V30V15V0ZM45 30V15H30V0H45H60V15V30V45H45H30V30H45Z"
|
||||
className="fill-black dark:fill-white"
|
||||
/>
|
||||
</svg>
|
||||
<p>BETTER-AUTH</p>
|
||||
<p>x</p>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="45"
|
||||
height="45"
|
||||
viewBox="0 0 100 100"
|
||||
fill="none"
|
||||
>
|
||||
<title>TanStack Start</title>
|
||||
<mask
|
||||
id="a"
|
||||
style={{ maskType: "alpha" }}
|
||||
maskUnits="userSpaceOnUse"
|
||||
x="0"
|
||||
y="0"
|
||||
width="100"
|
||||
height="100"
|
||||
>
|
||||
<circle cx="50" cy="50" r="50" className="fill-foreground" />
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<circle
|
||||
cx="11"
|
||||
cy="119"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<circle
|
||||
cx="10"
|
||||
cy="125"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="131"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-muted-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<circle
|
||||
cx="88"
|
||||
cy="119"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="fill-foreground"
|
||||
d="M89 35h2v5h-2zM83 34l2 1-1 4h-2zM77 31l2 1-3 4-2-1zM73 27l1 1-3 4-1-2zM70 23l1 1-4 3-1-2zM68 18v2l-4 1-1-2zM68 11l1 2-5 1-1-2zM69 6v2h-5V6z"
|
||||
/>
|
||||
<circle
|
||||
cx="89"
|
||||
cy="125"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<circle
|
||||
cx="90"
|
||||
cy="131"
|
||||
r="52"
|
||||
className="fill-muted-foreground stroke-muted-foreground"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<ellipse
|
||||
cx="49.5"
|
||||
cy="119"
|
||||
rx="41.5"
|
||||
ry="51"
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 38v-9c1 1 2 4 5 6l7 30-8 2c-1-23-2-23-4-29Z"
|
||||
className="fill-foreground stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M95 123c0 31-20 57-45 57S5 154 5 123c0-27 14-50 33-56l12-2c25 0 45 26 45 58Zm-45 47c22 0 39-22 39-50S72 70 50 70s-39 22-39 50 17 50 39 50Z"
|
||||
className="fill-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29c-4-8-11-5-14-4 2 3 5 4 9 4h5Z"
|
||||
className="fill-foreground stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M25 38c-1 6 0 14 2 18 5-7 7-13 7-18v-9c-5 1-7 5-9 9Z"
|
||||
className="fill-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29c-1 3-5 11-5 16m5-16c-5 1-7 5-9 9-1 6 0 14 2 18 5-7 7-13 7-18v-9Z"
|
||||
className="stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M44 18c-10 1-11 7-10 11l4-3c5-4 6-7 6-8Z"
|
||||
className="fill-foreground stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29h7l18 4c-3-6-9-14-21-7l-4 3Z"
|
||||
className="fill-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29c4-2 12-5 18-1m-18 1h7l18 4c-3-6-9-14-21-7l-4 3Z"
|
||||
className="stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M32 29a1189 1189 0 0 1-16 19c0-17 7-18 13-19h5a14 14 0 0 1-2 0Z"
|
||||
className="fill-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29c-5 1-7 5-9 9l-9 10c0-17 7-18 13-19h5Zm0 0c-5 2-11 3-14 10"
|
||||
className="stroke-muted-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M41 29c9 2 13 10 15 14a25 25 0 0 1-22-14h7Z"
|
||||
className="fill-foreground"
|
||||
/>
|
||||
<path
|
||||
d="M34 29c3 1 11 5 15 9m-15-9h7c9 2 13 10 15 14a25 25 0 0 1-22-14Z"
|
||||
className="stroke-muted-foreground"
|
||||
/>
|
||||
<circle
|
||||
cx="91.5"
|
||||
cy="12.5"
|
||||
r="18.5"
|
||||
className="fill-foreground stroke-muted-foreground"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<p>TANSTACK START.</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-4">
|
||||
{data?.user ? (
|
||||
<p>Hello {data.user.name}</p>
|
||||
) : (
|
||||
<NavigationMenu>
|
||||
<NavigationMenuList>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/auth/signin"
|
||||
className={navigationMenuTriggerStyle()}
|
||||
activeProps={{ className: "bg-accent/50" }}
|
||||
>
|
||||
Sign In
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/auth/signup"
|
||||
className={navigationMenuTriggerStyle()}
|
||||
activeProps={{ className: "bg-accent/50" }}
|
||||
>
|
||||
Sign Up
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</NavigationMenuList>
|
||||
</NavigationMenu>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 justify-center">
|
||||
{data?.user && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
signOut(
|
||||
{},
|
||||
{
|
||||
onError: (error) => {
|
||||
console.warn(error);
|
||||
toast.error(error.error.message);
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("You have been signed out!");
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
variant="destructive"
|
||||
>
|
||||
<DoorOpen className="w-5 h-5" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<Moon onClick={() => setTheme("dark")} className="w-5 h-5" />
|
||||
) : (
|
||||
<Sun onClick={() => setTheme("light")} className="w-5 h-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</nav>
|
||||
<Outlet />
|
||||
</>
|
||||
)}
|
||||
<Toaster richColors position="bottom-center" />
|
||||
</RootDocument>
|
||||
);
|
||||
}
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<Meta />
|
||||
</Head>
|
||||
<Body>
|
||||
{children}
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
11
examples/tanstack-example/app/routes/api/auth/$.ts
Normal file
11
examples/tanstack-example/app/routes/api/auth/$.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createAPIFileRoute } from "@tanstack/start/api";
|
||||
import { auth } from "~/lib/server/auth";
|
||||
|
||||
export const Route = createAPIFileRoute("/api/auth/$")({
|
||||
GET: ({ request }) => {
|
||||
return auth.handler(request);
|
||||
},
|
||||
POST: ({ request }) => {
|
||||
return auth.handler(request);
|
||||
},
|
||||
});
|
||||
14
examples/tanstack-example/app/routes/auth/signin.tsx
Normal file
14
examples/tanstack-example/app/routes/auth/signin.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { LoginForm } from "~/components/login-form";
|
||||
|
||||
export const Route = createFileRoute("/auth/signin")({
|
||||
component: SignIn,
|
||||
});
|
||||
|
||||
function SignIn() {
|
||||
return (
|
||||
<div className="container">
|
||||
<LoginForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
examples/tanstack-example/app/routes/auth/signup.tsx
Normal file
14
examples/tanstack-example/app/routes/auth/signup.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { RegisterForm } from "~/components/register-form";
|
||||
|
||||
export const Route = createFileRoute("/auth/signup")({
|
||||
component: SignUp,
|
||||
});
|
||||
|
||||
function SignUp() {
|
||||
return (
|
||||
<div className="container">
|
||||
<RegisterForm />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
47
examples/tanstack-example/app/routes/index.tsx
Normal file
47
examples/tanstack-example/app/routes/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { useSession } from "~/lib/client/auth";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
const { data } = useSession();
|
||||
|
||||
return (
|
||||
<div className="container flex justify-center">
|
||||
<Card className="w-fit">
|
||||
{data?.user && (
|
||||
<>
|
||||
<CardHeader>
|
||||
<CardTitle>Welcome, {data.user.name}!</CardTitle>
|
||||
<CardDescription>
|
||||
You are signed in as {data.user.email}.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-2 justify-start">
|
||||
<div className="flex flex-col">
|
||||
<p>Created At</p>
|
||||
<Input
|
||||
readOnly
|
||||
disabled
|
||||
value={data.user.createdAt.toLocaleString()}
|
||||
/>
|
||||
<p>Session ID</p>
|
||||
<Input readOnly disabled value={data.session.id} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
examples/tanstack-example/app/ssr.tsx
Normal file
12
examples/tanstack-example/app/ssr.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getRouterManifest } from "@tanstack/start/router-manifest";
|
||||
import {
|
||||
createStartHandler,
|
||||
defaultStreamHandler,
|
||||
} from "@tanstack/start/server";
|
||||
|
||||
import { createRouter } from "./router";
|
||||
|
||||
export default createStartHandler({
|
||||
createRouter,
|
||||
getRouterManifest,
|
||||
})(defaultStreamHandler);
|
||||
12
examples/tanstack-example/appconfig.ts
Normal file
12
examples/tanstack-example/appconfig.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from '@tanstack/start/config'
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths'
|
||||
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [
|
||||
viteTsConfigPaths({
|
||||
projects: ['./tsconfig.json']
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" boolean not null, "image" text, "createdAt" date not null, "updatedAt" date not null);
|
||||
|
||||
create table "session" ("id" text not null primary key, "expiresAt" date not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id"));
|
||||
|
||||
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "expiresAt" date, "password" text);
|
||||
|
||||
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" date not null)
|
||||
30
examples/tanstack-example/biome.json
Normal file
30
examples/tanstack-example/biome.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": []
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
examples/tanstack-example/components.json
Normal file
16
examples/tanstack-example/components.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.js",
|
||||
"css": "app/lib/style/global.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true
|
||||
},
|
||||
"aliases": {
|
||||
"components": "~/components",
|
||||
"utils": "~/lib/utils"
|
||||
}
|
||||
}
|
||||
BIN
examples/tanstack-example/header.webp
Normal file
BIN
examples/tanstack-example/header.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
44
examples/tanstack-example/package.json
Normal file
44
examples/tanstack-example/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "tanstack-example",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vinxi dev",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^22.8.2",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "^5.6.3",
|
||||
"vite-tsconfig-paths": "^5.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@tanstack/react-router": "^1.77.6",
|
||||
"@tanstack/start": "^1.77.6",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"better-auth": "^0.6.2",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vinxi": "^0.4.3"
|
||||
}
|
||||
}
|
||||
6
examples/tanstack-example/postcss.config.js
Normal file
6
examples/tanstack-example/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
examples/tanstack-example/preview.webp
Normal file
BIN
examples/tanstack-example/preview.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
85
examples/tanstack-example/tailwind.config.js
Normal file
85
examples/tanstack-example/tailwind.config.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: ["class"],
|
||||
content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'"GeistMono"',
|
||||
"ui-monospace",
|
||||
"SFMono-Regular",
|
||||
"Roboto Mono",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Liberation Mono",
|
||||
"DejaVu Sans Mono",
|
||||
"Courier New",
|
||||
"monospace",
|
||||
],
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
};
|
||||
32
examples/tanstack-example/tsconfig.json
Normal file
32
examples/tanstack-example/tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["app/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", ".output"]
|
||||
}
|
||||
1463
pnpm-lock.yaml
generated
1463
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user