- New /account route: edit name/email, change password, delete account (detaches the user's orders instead of leaving a dangling reference, since orders.customerId has no DB-level FK to better-auth's user table) - Better Auth: allow email changes without a verification email (no email sending is wired up anywhere in this app) and enable account deletion - Header avatar: fall back to the initials placeholder if the profile image fails to load, not just when there's no image URL - Fix low-contrast "Continue with Google" button in dark mode (it used the page background color and a hardcoded dark-gray label, so it nearly disappeared against a dark background) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
import { createServerFn, createServerOnlyFn } from '@tanstack/react-start'
|
|
import { getRequest } from '@tanstack/react-start/server'
|
|
|
|
import { auth } from '#/lib/auth.ts'
|
|
|
|
// Wrapped in createServerOnlyFn: this file is exported for reuse by several
|
|
// server/*.ts modules, so (unlike a private, unexported helper) the
|
|
// framework's client/server code-split can't tree-shake it away on its own
|
|
// — without this it drags getRequest (and the rest of this file) into any
|
|
// client bundle that transitively imports one of this module's exports,
|
|
// which @tanstack/react-start's import-protection then refuses to load.
|
|
export const getSessionUser = createServerOnlyFn(async () => {
|
|
const session = await auth.api.getSession({ headers: getRequest().headers })
|
|
return session?.user
|
|
})
|
|
|
|
export const getSessionCustomerId = createServerOnlyFn(
|
|
async () => (await getSessionUser())?.id,
|
|
)
|
|
|
|
// Defense in depth: an admin-only server function must check this itself
|
|
// rather than trust the caller's route already gated on role, since a
|
|
// server function is reachable directly over its RPC endpoint.
|
|
export const requireAdmin = createServerOnlyFn(async () => {
|
|
const user = await getSessionUser()
|
|
if (user?.role !== 'admin') throw new Error('Admin access required')
|
|
})
|
|
|
|
// A minimal round trip the admin layout route's loader can call to gate
|
|
// every /admin/* page behind one check, without fetching any page's data.
|
|
export const requireAdminSession = createServerFn({ method: 'GET' }).handler(
|
|
async () => {
|
|
await requireAdmin()
|
|
},
|
|
)
|
|
|
|
// The account settings route's loader calls this to both fetch the
|
|
// signed-in user and gate the page — thrown message is matched against by
|
|
// the route the same way withAdminGuard matches "Admin access required".
|
|
export const getCurrentUser = createServerFn({ method: 'GET' }).handler(
|
|
async () => {
|
|
const user = await getSessionUser()
|
|
if (!user) throw new Error('Not signed in')
|
|
return user
|
|
},
|
|
)
|