feat: remove README and cursor rules, update dependencies, and implement authentication middleware
This commit is contained in:
22
.cursorrules
22
.cursorrules
@@ -1,22 +0,0 @@
|
|||||||
We use Sentry for watching for errors in our deployed application, as well as for instrumentation of our application.
|
|
||||||
|
|
||||||
## Error collection
|
|
||||||
|
|
||||||
Error collection is automatic and configured in `src/router.tsx`.
|
|
||||||
|
|
||||||
## Instrumentation
|
|
||||||
|
|
||||||
We want our server functions instrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import * as Sentry from '@sentry/tanstackstart-react'
|
|
||||||
```
|
|
||||||
|
|
||||||
And then wrap the implementation of the server function with `Sentry.startSpan`, like so:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
|
|
||||||
// Some lengthy operation here
|
|
||||||
await fetch('https://api.pokemon.com/data/')
|
|
||||||
})
|
|
||||||
```
|
|
||||||
51
CLAUDE.md
Normal file
51
CLAUDE.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
||||||
|
Tradeoff: These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||||
|
1. Think Before Coding
|
||||||
|
Don't assume. Don't hide confusion. Surface tradeoffs.
|
||||||
|
Before implementing:
|
||||||
|
|
||||||
|
* State your assumptions explicitly. If uncertain, ask.
|
||||||
|
* If multiple interpretations exist, present them - don't pick silently.
|
||||||
|
* If a simpler approach exists, say so. Push back when warranted.
|
||||||
|
* If something is unclear, stop. Name what's confusing. Ask.
|
||||||
|
2. Simplicity First
|
||||||
|
Minimum code that solves the problem. Nothing speculative.
|
||||||
|
|
||||||
|
* No features beyond what was asked.
|
||||||
|
* No abstractions for single-use code.
|
||||||
|
* No "flexibility" or "configurability" that wasn't requested.
|
||||||
|
* No error handling for impossible scenarios.
|
||||||
|
* If you write 200 lines and it could be 50, rewrite it.
|
||||||
|
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||||
|
3. Surgical Changes
|
||||||
|
Touch only what you must. Clean up only your own mess.
|
||||||
|
When editing existing code:
|
||||||
|
|
||||||
|
* Don't "improve" adjacent code, comments, or formatting.
|
||||||
|
* Don't refactor things that aren't broken.
|
||||||
|
* Match existing style, even if you'd do it differently.
|
||||||
|
* If you notice unrelated dead code, mention it - don't delete it.
|
||||||
|
When your changes create orphans:
|
||||||
|
|
||||||
|
* Remove imports/variables/functions that YOUR changes made unused.
|
||||||
|
* Don't remove pre-existing dead code unless asked.
|
||||||
|
The test: Every changed line should trace directly to the user's request.
|
||||||
|
4. Goal-Driven Execution
|
||||||
|
Define success criteria. Loop until verified.
|
||||||
|
Transform tasks into verifiable goals:
|
||||||
|
|
||||||
|
* "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||||
|
* "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||||
|
* "Refactor X" → "Ensure tests pass before and after"
|
||||||
|
For multi-step tasks, state a brief plan:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. [Step] → verify: [check]
|
||||||
|
2. [Step] → verify: [check]
|
||||||
|
3. [Step] → verify: [check]
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||||
|
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||||
222
README.md
222
README.md
@@ -1,222 +0,0 @@
|
|||||||
Welcome to your new TanStack Start app!
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
To run this application:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
# Building For Production
|
|
||||||
|
|
||||||
To build this application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
|
|
||||||
|
|
||||||
### Removing Tailwind CSS
|
|
||||||
|
|
||||||
If you prefer not to use Tailwind CSS:
|
|
||||||
|
|
||||||
1. Remove the demo pages in `src/routes/demo/`
|
|
||||||
2. Replace the Tailwind import in `src/styles.css` with your own styles
|
|
||||||
3. Remove `tailwindcss()` from the plugins array in `vite.config.ts`
|
|
||||||
4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D`
|
|
||||||
|
|
||||||
## Linting & Formatting
|
|
||||||
|
|
||||||
This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available:
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint
|
|
||||||
npm run format
|
|
||||||
npm run check
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Shadcn
|
|
||||||
|
|
||||||
Add components using the latest version of [Shadcn](https://ui.shadcn.com/).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx shadcn@latest add button
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# Paraglide i18n
|
|
||||||
|
|
||||||
This add-on wires up ParaglideJS for localized routing and message formatting.
|
|
||||||
|
|
||||||
- Messages live in `project.inlang/messages`.
|
|
||||||
- URLs are localized through the Paraglide Vite plugin and router `rewrite` hooks.
|
|
||||||
- Run the dev server or build to regenerate the `src/paraglide` outputs.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
|
|
||||||
This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`.
|
|
||||||
|
|
||||||
### Adding A Route
|
|
||||||
|
|
||||||
To add a new route to your application just add a new file in the `./src/routes` directory.
|
|
||||||
|
|
||||||
TanStack will automatically generate the content of the route file for you.
|
|
||||||
|
|
||||||
Now that you have two routes you can use a `Link` component to navigate between them.
|
|
||||||
|
|
||||||
### Adding Links
|
|
||||||
|
|
||||||
To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
```
|
|
||||||
|
|
||||||
Then anywhere in your JSX you can use it like so:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a link that will navigate to the `/about` route.
|
|
||||||
|
|
||||||
More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
|
|
||||||
|
|
||||||
### Using A Layout
|
|
||||||
|
|
||||||
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 render `{children}` in the `shellComponent`.
|
|
||||||
|
|
||||||
Here is an example layout that includes a header:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{ charSet: 'utf-8' },
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
||||||
{ title: 'My App' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
shellComponent: ({ children }) => (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
{children}
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
|
|
||||||
|
|
||||||
## Server Functions
|
|
||||||
|
|
||||||
TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createServerFn } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
const getServerTime = createServerFn({
|
|
||||||
method: 'GET',
|
|
||||||
}).handler(async () => {
|
|
||||||
return new Date().toISOString()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use in a component
|
|
||||||
function MyComponent() {
|
|
||||||
const [time, setTime] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getServerTime().then(setTime)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div>Server time: {time}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Routes
|
|
||||||
|
|
||||||
You can create API routes by using the `server` property in your route definitions:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
import { json } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/api/hello')({
|
|
||||||
server: {
|
|
||||||
handlers: {
|
|
||||||
GET: () => json({ message: 'Hello, World!' }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Fetching
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/people')({
|
|
||||||
loader: async () => {
|
|
||||||
const response = await fetch('https://swapi.dev/api/people')
|
|
||||||
return response.json()
|
|
||||||
},
|
|
||||||
component: PeopleComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
function PeopleComponent() {
|
|
||||||
const data = Route.useLoaderData()
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{data.results.map((person) => (
|
|
||||||
<li key={person.name}>{person.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
# Demo files
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
# Learn More
|
|
||||||
|
|
||||||
You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
|
|
||||||
|
|
||||||
For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start).
|
|
||||||
62
package.json
62
package.json
@@ -14,45 +14,45 @@
|
|||||||
"machine-translate": "inlang machine translate --project project.inlang"
|
"machine-translate": "inlang machine translate --project project.inlang"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroui/react": "^3.0.0-rc.1",
|
"@heroui/react": "^3.0.5",
|
||||||
"@heroui/styles": "^3.0.0-rc.1",
|
"@heroui/styles": "^3.0.5",
|
||||||
"@sentry/tanstackstart-react": "^10.45.0",
|
"@sentry/tanstackstart-react": "^10.45.0",
|
||||||
"@supabase/ssr": "^0.9.0",
|
"@supabase/ssr": "^0.10.3",
|
||||||
"@supabase/supabase-js": "^2.99.3",
|
"@supabase/supabase-js": "^2.106.1",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.3.0",
|
||||||
"@tanstack/react-query": "^5.91.2",
|
"@tanstack/react-query": "^5.100.11",
|
||||||
"@tanstack/react-router": "^1.167.5",
|
"@tanstack/react-router": "^1.170.7",
|
||||||
"@tanstack/react-router-ssr-query": "^1.166.9",
|
"@tanstack/react-router-ssr-query": "^1.167.0",
|
||||||
"@tanstack/react-start": "^1.166.17",
|
"@tanstack/react-start": "^1.168.10",
|
||||||
"@tanstack/router-plugin": "^1.166.14",
|
"@tanstack/router-plugin": "^1.168.10",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.577.0",
|
"lucide-react": "^1.16.0",
|
||||||
"maplibre-gl": "^5.20.2",
|
"maplibre-gl": "^5.24.0",
|
||||||
"nitro": "^3.0.1-alpha.2",
|
"nitro": "^3.0.260522-beta",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.6",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.3.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.4.8",
|
"@biomejs/biome": "^2.4.15",
|
||||||
"@inlang/paraglide-js": "^2.15.0",
|
"@inlang/paraglide-js": "^2.18.1",
|
||||||
"@tanstack/devtools-vite": "^0.6.0",
|
"@tanstack/devtools-vite": "^0.7.0",
|
||||||
"@tanstack/react-devtools": "^0.10.0",
|
"@tanstack/react-devtools": "^0.10.5",
|
||||||
"@tanstack/react-query-devtools": "^5.91.3",
|
"@tanstack/react-query-devtools": "^5.100.11",
|
||||||
"@tanstack/react-router-devtools": "^1.166.9",
|
"@tanstack/react-router-devtools": "^1.167.0",
|
||||||
"@testing-library/dom": "^10.4.1",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.2",
|
"@testing-library/react": "^16.3.2",
|
||||||
"@types/node": "^22.19.15",
|
"@types/node": "^22.19.19",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.15",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"jsdom": "^29.0.0",
|
"jsdom": "^29.1.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.1",
|
"vite": "^8.0.14",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
"vite-tsconfig-paths": "^6.1.1",
|
||||||
"vitest": "^4.1.0"
|
"vitest": "^4.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ export function getSupabaseServerClient() {
|
|||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
setAll(cookies) {
|
setAll(cookies) {
|
||||||
cookies.forEach((cookie) => {
|
cookies.forEach(({ name, value, options }) => {
|
||||||
setCookie(cookie.name, cookie.value)
|
setCookie(name, value, options)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/lib/server/middleware.ts
Normal file
45
src/lib/server/middleware.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { createMiddleware } from "@tanstack/react-start"
|
||||||
|
import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
|
||||||
|
|
||||||
|
export const authMiddleware = createMiddleware().server(async ({ next }) => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
const { data, error } = await supabase.auth.getUser()
|
||||||
|
|
||||||
|
if (error || !data.user) {
|
||||||
|
throw new Error("Unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({
|
||||||
|
context: {
|
||||||
|
user: {
|
||||||
|
id: data.user.id,
|
||||||
|
email: data.user.email,
|
||||||
|
name: data.user.user_metadata?.name || "",
|
||||||
|
location: data.user.user_metadata?.location || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const optionalAuthMiddleware = createMiddleware().server(
|
||||||
|
async ({ next }) => {
|
||||||
|
const supabase = getSupabaseServerClient()
|
||||||
|
const { data, error } = await supabase.auth.getUser()
|
||||||
|
|
||||||
|
const userData =
|
||||||
|
!error && data.user
|
||||||
|
? {
|
||||||
|
id: data.user.id,
|
||||||
|
email: data.user.email,
|
||||||
|
name: data.user.user_metadata?.name || "",
|
||||||
|
location: data.user.user_metadata?.location || ""
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
|
||||||
|
return next({
|
||||||
|
context: {
|
||||||
|
user: userData
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -4,8 +4,9 @@ import { getSupabaseServerClient } from "@/integrations/supabase/supabase"
|
|||||||
import {
|
import {
|
||||||
loginFormSchema,
|
loginFormSchema,
|
||||||
signupFormSchema,
|
signupFormSchema,
|
||||||
userListParamsSchema,
|
userListParamsSchema
|
||||||
} from "../validation/user"
|
} from "@/lib/validation/user"
|
||||||
|
import { authMiddleware, optionalAuthMiddleware } from "./middleware"
|
||||||
|
|
||||||
const login = createServerFn({ method: "POST" })
|
const login = createServerFn({ method: "POST" })
|
||||||
.inputValidator(loginFormSchema)
|
.inputValidator(loginFormSchema)
|
||||||
@@ -14,38 +15,41 @@ const login = createServerFn({ method: "POST" })
|
|||||||
|
|
||||||
const login = await supabase.auth.signInWithPassword({
|
const login = await supabase.auth.signInWithPassword({
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: data.password
|
||||||
})
|
})
|
||||||
|
|
||||||
if (login.error) {
|
if (login.error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: login.error.message,
|
message: login.error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
throw redirect({
|
||||||
error: false,
|
to: "/dashboard",
|
||||||
}
|
replace: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const logout = createServerFn().handler(async () => {
|
const logout = createServerFn()
|
||||||
|
.middleware([authMiddleware])
|
||||||
|
.handler(async () => {
|
||||||
const supabase = getSupabaseServerClient()
|
const supabase = getSupabaseServerClient()
|
||||||
|
|
||||||
const { error } = await supabase.auth.signOut()
|
const { error } = await supabase.auth.signOut()
|
||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: "/",
|
to: "/",
|
||||||
viewTransition: true,
|
viewTransition: true,
|
||||||
replace: true,
|
replace: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const signup = createServerFn({ method: "POST" })
|
const signup = createServerFn({ method: "POST" })
|
||||||
.inputValidator(signupFormSchema)
|
.inputValidator(signupFormSchema)
|
||||||
@@ -57,41 +61,36 @@ const signup = createServerFn({ method: "POST" })
|
|||||||
options: {
|
options: {
|
||||||
data: {
|
data: {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
location: data.location,
|
location: data.location
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw redirect({
|
throw redirect({
|
||||||
href: data.redirectUrl || "/",
|
href: data.redirectUrl || "/"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const userData = createServerFn().handler(async () => {
|
const userData = createServerFn()
|
||||||
const supabase = getSupabaseServerClient()
|
.middleware([optionalAuthMiddleware])
|
||||||
const { data, error } = await supabase.auth.getUser()
|
.handler(async ({ context }) => {
|
||||||
if (error || !data.user) {
|
if (!context.user) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error?.message ?? "Unknown error",
|
message: "Not authenticated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
user: {
|
user: context.user,
|
||||||
id: data.user.id,
|
error: false
|
||||||
email: data.user.email,
|
|
||||||
name: data.user.user_metadata.name || "",
|
|
||||||
location: data.user.user_metadata.location || "",
|
|
||||||
},
|
|
||||||
error: false,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const resendConfirmationEmail = createServerFn({ method: "POST" })
|
const resendConfirmationEmail = createServerFn({ method: "POST" })
|
||||||
.inputValidator(signupFormSchema.pick({ email: true }))
|
.inputValidator(signupFormSchema.pick({ email: true }))
|
||||||
@@ -102,33 +101,34 @@ const resendConfirmationEmail = createServerFn({ method: "POST" })
|
|||||||
if (error) {
|
if (error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: error.message,
|
message: error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const userList = createServerFn()
|
const userList = createServerFn()
|
||||||
|
.middleware([authMiddleware])
|
||||||
.inputValidator(userListParamsSchema)
|
.inputValidator(userListParamsSchema)
|
||||||
.handler(async ({ data }) => {
|
.handler(async ({ data }) => {
|
||||||
const supabase = getSupabaseServerClient()
|
const supabase = getSupabaseServerClient()
|
||||||
const users = await supabase.auth.admin.listUsers({
|
const users = await supabase.auth.admin.listUsers({
|
||||||
page: data.page,
|
page: data.page,
|
||||||
perPage: data.limit,
|
perPage: data.limit
|
||||||
})
|
})
|
||||||
|
|
||||||
if (users.error) {
|
if (users.error) {
|
||||||
return {
|
return {
|
||||||
error: true,
|
error: true,
|
||||||
message: users.error.message,
|
message: users.error.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
users: users.data,
|
users: users.data,
|
||||||
error: false,
|
error: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -138,5 +138,5 @@ export const user = {
|
|||||||
signup,
|
signup,
|
||||||
userData,
|
userData,
|
||||||
resendConfirmationEmail,
|
resendConfirmationEmail,
|
||||||
userList,
|
userList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import appCss from "@/styles/globals.css?url"
|
|||||||
|
|
||||||
interface MyRouterContext {
|
interface MyRouterContext {
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
user: null
|
user: Awaited<ReturnType<typeof user.userData>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { createFileRoute, Link, Outlet } from "@tanstack/react-router"
|
import { createFileRoute, Link, Outlet, redirect } from "@tanstack/react-router"
|
||||||
|
|
||||||
export const Route = createFileRoute("/_auth")({
|
export const Route = createFileRoute("/_auth")({
|
||||||
beforeLoad: ({ context }) => {
|
beforeLoad: ({ context }) => {
|
||||||
if (context.user.error) {
|
if (context.user.error) {
|
||||||
throw new Error("Not authenticated")
|
redirect({
|
||||||
|
to: "/access/login",
|
||||||
|
replace: true,
|
||||||
|
throw: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errorComponent: ({ error }) => {
|
errorComponent: ({ error }) => {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,20 +5,21 @@ import { tanstackStart } from "@tanstack/react-start/plugin/vite"
|
|||||||
import viteReact from "@vitejs/plugin-react"
|
import viteReact from "@vitejs/plugin-react"
|
||||||
import { nitro } from "nitro/vite"
|
import { nitro } from "nitro/vite"
|
||||||
import { defineConfig } from "vite"
|
import { defineConfig } from "vite"
|
||||||
import tsconfigPaths from "vite-tsconfig-paths"
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
devtools(),
|
devtools(),
|
||||||
paraglideVitePlugin({
|
paraglideVitePlugin({
|
||||||
project: "./project.inlang",
|
project: "./project.inlang",
|
||||||
outdir: "./src/paraglide",
|
outdir: "./src/integrations/paraglide",
|
||||||
strategy: ["url", "baseLocale"]
|
strategy: ["url", "baseLocale"]
|
||||||
}),
|
}),
|
||||||
nitro({ rollupConfig: { external: [/^@sentry\//] } }),
|
nitro({ rollupConfig: { external: [/^@sentry\//] } }),
|
||||||
tsconfigPaths({ projects: ["./tsconfig.json"] }),
|
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
tanstackStart(),
|
tanstackStart(),
|
||||||
viteReact()
|
viteReact()
|
||||||
]
|
],
|
||||||
|
resolve: {
|
||||||
|
tsconfigPaths: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user