Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
APPWRITE_ENDPOINT=
APPWRITE_API_KEY=
APPWRITE_BUCKET_ID=
APPWRITE_PROJECT_ID=
VITE_APPWRITE_ENDPOINT=
VITE_APPWRITE_PROJECT_ID=
VITE_APPWRITE_BUCKET_ID=
VITE_INSTRUMENTATION_SCRIPT_SRC=
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ bunx --bun run start

Copy `.env.example` to `.env` before running the app and provide values for the required secrets:

- `APPWRITE_ENDPOINT` – Base URL of your Appwrite instance.
- `VITE_APPWRITE_ENDPOINT` – Base URL of your Appwrite instance.
- `APPWRITE_API_KEY` – API key with permissions for the configured project.
- `APPWRITE_BUCKET_ID` – Identifier of the storage bucket used by the app.
- `APPWRITE_PROJECT_ID` – Appwrite project ID exposed to the client build.
- `VITE_APPWRITE_BUCKET_ID` – Identifier of the storage bucket used by the app.
- `VITE_APPWRITE_PROJECT_ID` – Appwrite project ID exposed to the client build.
- `VITE_INSTRUMENTATION_SCRIPT_SRC` – Script URL injected for analytics/instrumentation.

The app will fail to authenticate or access storage until these values are set.
Expand Down
502 changes: 277 additions & 225 deletions bun.lock

Large diffs are not rendered by default.

70 changes: 35 additions & 35 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,82 +19,82 @@
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.7",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-aspect-ratio": "^1.1.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-devtools": "^0.7.1",
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-router": "^1.132.31",
"@tanstack/react-router-devtools": "^1.132.31",
"@tanstack/react-router-ssr-query": "^1.132.31",
"@tanstack/react-start": "^1.132.32",
"@tanstack/router-plugin": "^1.132.31",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-devtools": "^0.7.11",
"@tanstack/react-query": "^5.90.12",
"@tanstack/react-query-devtools": "^5.91.1",
"@tanstack/react-router": "^1.141.6",
"@tanstack/react-router-devtools": "^1.141.6",
"@tanstack/react-router-ssr-query": "^1.141.6",
"@tanstack/react-start": "^1.141.7",
"@tanstack/router-plugin": "^1.141.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2",
"lucide-react": "^0.544.0",
"motion": "^12.23.22",
"motion": "^12.23.26",
"next-themes": "^0.4.6",
"node-appwrite": "^20.2.1",
"node-appwrite": "^21.1.0",
"react": "^19.1.1",
"react-day-picker": "^9.11.0",
"react-dom": "^19.1.1",
"react-hook-form": "^7.63.0",
"react-resizable-panels": "^3.0.6",
"recharts": "^3.2.1",
"recharts": "^3.6.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.1.13",
"tw-animate-css": "^1.3.6",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"vaul": "^1.1.2",
"vite-tsconfig-paths": "^5.1.4",
"zod": "^4.1.11"
"zod": "^4.2.1"
},
"devDependencies": {
"@tanstack/eslint-config": "^0.3.0",
"@tanstack/nitro-v2-vite-plugin": "^1.132.40",
"@tanstack/router-cli": "^1.132.21",
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0",
"@types/bun": "^1.2.22",
"@tanstack/eslint-config": "^0.3.4",
"@tanstack/nitro-v2-vite-plugin": "^1.141.0",
"@tanstack/router-cli": "^1.141.7",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.1",
"@types/bun": "^1.3.4",
"@types/node": "22.10.2",
"@types/react": "^19.1.13",
"@types/react-dom": "^19.1.9",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.50.0",
"@vitejs/plugin-react": "^5.0.3",
"@vitejs/plugin-react": "^5.1.2",
"eslint-plugin-unused-imports": "^4.3.0",
"jsdom": "^26.0.0",
"prettier": "^3.5.3",
"typescript": "^5.7.2",
"jsdom": "^26.1.0",
"prettier": "^3.7.4",
"typescript": "^5.9.3",
"vite": "npm:rolldown-vite@latest",
"vite-plugin-devtools-json": "^1.0.0",
"vitest": "^3.0.5",
"vitest": "^3.2.4",
"web-vitals": "^4.2.4"
}
}
38 changes: 31 additions & 7 deletions src/components/imagine-placeholder.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { Link, useLocation } from '@tanstack/react-router'
import { Link, useLocation, useNavigate } from '@tanstack/react-router'
import { useTheme } from 'next-themes'
import { Button } from './ui/button'
import { useServerFn } from '@tanstack/react-start'
import { signOutFn } from '@/server/functions/auth'
import { useAuth } from '@/hooks/use-auth'
import { Loader2 } from 'lucide-react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { authQueryKey } from '@/lib/react-query/query-keys'

export function ImaginePlaceholder() {
const { currentUser } = useAuth()
const signOut = useServerFn(signOutFn)
const { currentUser, signOut } = useAuth()
const queryClient = useQueryClient()
const location = useLocation()
const navigate = useNavigate()

const { theme } = useTheme()

const signOutMutation = useMutation({
mutationFn: () => signOut(),
onSuccess: async () => {
// Invalidate auth state so it will be refetched
await queryClient.invalidateQueries({ queryKey: authQueryKey() })
// Navigate to the sign-in page
await navigate({ to: '/sign-in' })
},
})

return (
<div className="flex-grow flex flex-col justify-center items-center gap-6 text-center">
<img
Expand All @@ -29,8 +42,19 @@ export function ImaginePlaceholder() {
You are signed in as{' '}
<span className="font-medium">{currentUser.email}</span>
</p>
<Button size="sm" onClick={() => signOut()}>
Sign out
<Button
size="sm"
onClick={() => signOutMutation.mutate()}
disabled={signOutMutation.isPending}
>
{signOutMutation.isPending ? (
<div className="flex items-center justify-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" />
Signing out...
</div>
) : (
'Sign out'
)}
</Button>
</>
) : (
Expand Down
18 changes: 11 additions & 7 deletions src/hooks/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { signOutFn } from '@/server/functions/auth'
import { useLoaderData } from '@tanstack/react-router'
import { authQueryKey } from '@/lib/react-query/query-keys'
import { authMiddleware, signOutFn } from '@/server/functions/auth'
import { useQuery } from '@tanstack/react-query'
import { useServerFn } from '@tanstack/react-start'
import { Models } from 'node-appwrite'

export const authQueryOptions = () => ({
queryKey: authQueryKey(),
queryFn: () => authMiddleware(),
staleTime: 30000, // 30 seconds
})

export function useAuth() {
const { currentUser } = useLoaderData({ from: '__root__' }) as {
currentUser: Models.User<Models.Preferences>
}
const { data } = useQuery(authQueryOptions())
const signOut = useServerFn(signOutFn)

return {
currentUser,
currentUser: data?.currentUser,
signOut,
}
}
20 changes: 0 additions & 20 deletions src/integrations/tanstack-query/root-provider.tsx

This file was deleted.

1 change: 0 additions & 1 deletion src/lib/react-query/index.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/lib/react-query/query-client.ts

This file was deleted.

2 changes: 2 additions & 0 deletions src/lib/react-query/query-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Query key factory for auth
export const authQueryKey = () => ['account'] as const
28 changes: 1 addition & 27 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { Route as AuthRouteImport } from './routes/_auth'
import { Route as PublicIndexRouteImport } from './routes/_public/index'
import { Route as ProtectedExampleProtectedRouteRouteImport } from './routes/_protected/example-protected-route'
import { Route as AuthSignUpRouteImport } from './routes/_auth/sign-up'
import { Route as AuthSignOutRouteImport } from './routes/_auth/sign-out'
import { Route as AuthSignInRouteImport } from './routes/_auth/sign-in'
import { Route as ApiHelloRouteImport } from './routes/_api/hello'

Expand Down Expand Up @@ -47,11 +46,6 @@ const AuthSignUpRoute = AuthSignUpRouteImport.update({
path: '/sign-up',
getParentRoute: () => AuthRoute,
} as any)
const AuthSignOutRoute = AuthSignOutRouteImport.update({
id: '/sign-out',
path: '/sign-out',
getParentRoute: () => AuthRoute,
} as any)
const AuthSignInRoute = AuthSignInRouteImport.update({
id: '/sign-in',
path: '/sign-in',
Expand All @@ -66,15 +60,13 @@ const ApiHelloRoute = ApiHelloRouteImport.update({
export interface FileRoutesByFullPath {
'/hello': typeof ApiHelloRoute
'/sign-in': typeof AuthSignInRoute
'/sign-out': typeof AuthSignOutRoute
'/sign-up': typeof AuthSignUpRoute
'/example-protected-route': typeof ProtectedExampleProtectedRouteRoute
'/': typeof PublicIndexRoute
}
export interface FileRoutesByTo {
'/hello': typeof ApiHelloRoute
'/sign-in': typeof AuthSignInRoute
'/sign-out': typeof AuthSignOutRoute
'/sign-up': typeof AuthSignUpRoute
'/example-protected-route': typeof ProtectedExampleProtectedRouteRoute
'/': typeof PublicIndexRoute
Expand All @@ -86,7 +78,6 @@ export interface FileRoutesById {
'/_public': typeof PublicRouteWithChildren
'/_api/hello': typeof ApiHelloRoute
'/_auth/sign-in': typeof AuthSignInRoute
'/_auth/sign-out': typeof AuthSignOutRoute
'/_auth/sign-up': typeof AuthSignUpRoute
'/_protected/example-protected-route': typeof ProtectedExampleProtectedRouteRoute
'/_public/': typeof PublicIndexRoute
Expand All @@ -96,26 +87,18 @@ export interface FileRouteTypes {
fullPaths:
| '/hello'
| '/sign-in'
| '/sign-out'
| '/sign-up'
| '/example-protected-route'
| '/'
fileRoutesByTo: FileRoutesByTo
to:
| '/hello'
| '/sign-in'
| '/sign-out'
| '/sign-up'
| '/example-protected-route'
| '/'
to: '/hello' | '/sign-in' | '/sign-up' | '/example-protected-route' | '/'
id:
| '__root__'
| '/_auth'
| '/_protected'
| '/_public'
| '/_api/hello'
| '/_auth/sign-in'
| '/_auth/sign-out'
| '/_auth/sign-up'
| '/_protected/example-protected-route'
| '/_public/'
Expand Down Expand Up @@ -172,13 +155,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthSignUpRouteImport
parentRoute: typeof AuthRoute
}
'/_auth/sign-out': {
id: '/_auth/sign-out'
path: '/sign-out'
fullPath: '/sign-out'
preLoaderRoute: typeof AuthSignOutRouteImport
parentRoute: typeof AuthRoute
}
'/_auth/sign-in': {
id: '/_auth/sign-in'
path: '/sign-in'
Expand All @@ -198,13 +174,11 @@ declare module '@tanstack/react-router' {

interface AuthRouteChildren {
AuthSignInRoute: typeof AuthSignInRoute
AuthSignOutRoute: typeof AuthSignOutRoute
AuthSignUpRoute: typeof AuthSignUpRoute
}

const AuthRouteChildren: AuthRouteChildren = {
AuthSignInRoute: AuthSignInRoute,
AuthSignOutRoute: AuthSignOutRoute,
AuthSignUpRoute: AuthSignUpRoute,
}

Expand Down
15 changes: 4 additions & 11 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
import { createRouter } from '@tanstack/react-router'
import { QueryClient } from '@tanstack/react-query'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import * as TanstackQuery from './integrations/tanstack-query/root-provider'

// Import the generated route tree
import { routeTree } from './routeTree.gen'
import { ErrorComponent } from './components/error-component'

// Create a new router instance
export const getRouter = () => {
const rqContext = TanstackQuery.getContext()
const queryClient = new QueryClient()

const router = createRouter({
routeTree,
context: { ...rqContext },
context: { queryClient },
defaultPreload: 'intent',
defaultErrorComponent: ({ error, info, reset }) => (
<ErrorComponent error={error} info={info} reset={reset} />
),
Wrap: (props: { children: React.ReactNode }) => {
return (
<TanstackQuery.Provider {...rqContext}>
{props.children}
</TanstackQuery.Provider>
)
},
})

setupRouterSsrQueryIntegration({ router, queryClient: rqContext.queryClient })
setupRouterSsrQueryIntegration({ router, queryClient })

return router
}
Loading
Loading