wcz-test 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/api.ts +6 -0
- package/src/client.tsx +8 -0
- package/src/components/DefaultCatchBoundary.tsx +53 -0
- package/src/components/MyComponent.tsx +6 -0
- package/src/components/NotFound.tsx +25 -0
- package/src/components/PostError.tsx +5 -0
- package/src/components/UserError.tsx +5 -0
- package/src/global-middleware.ts +6 -0
- package/src/index.ts +1 -0
- package/src/routeTree.gen.ts +483 -0
- package/src/router.tsx +22 -0
- package/src/routes/__root.tsx +139 -0
- package/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +11 -0
- package/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +11 -0
- package/src/routes/_pathlessLayout/_nested-layout.tsx +34 -0
- package/src/routes/_pathlessLayout.tsx +16 -0
- package/src/routes/api/users.$id.ts +24 -0
- package/src/routes/api/users.ts +17 -0
- package/src/routes/deferred.tsx +62 -0
- package/src/routes/index.tsx +13 -0
- package/src/routes/posts.$postId.tsx +34 -0
- package/src/routes/posts.index.tsx +9 -0
- package/src/routes/posts.route.tsx +38 -0
- package/src/routes/posts_.$postId.deep.tsx +29 -0
- package/src/routes/redirect.tsx +9 -0
- package/src/routes/users.$userId.tsx +33 -0
- package/src/routes/users.index.tsx +10 -0
- package/src/routes/users.route.tsx +48 -0
- package/src/ssr.tsx +13 -0
- package/src/styles/app.css +22 -0
- package/src/utils/loggingMiddleware.tsx +41 -0
- package/src/utils/posts.tsx +36 -0
- package/src/utils/seo.ts +33 -0
- package/src/utils/users.tsx +7 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HeadContent,
|
|
3
|
+
Link,
|
|
4
|
+
Outlet,
|
|
5
|
+
Scripts,
|
|
6
|
+
createRootRoute,
|
|
7
|
+
} from '@tanstack/react-router'
|
|
8
|
+
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
|
9
|
+
import * as React from 'react'
|
|
10
|
+
import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
|
|
11
|
+
import { NotFound } from '~/components/NotFound'
|
|
12
|
+
import appCss from '~/styles/app.css?url'
|
|
13
|
+
import { seo } from '~/utils/seo'
|
|
14
|
+
|
|
15
|
+
export const Route = createRootRoute({
|
|
16
|
+
head: () => ({
|
|
17
|
+
meta: [
|
|
18
|
+
{
|
|
19
|
+
charSet: 'utf-8',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'viewport',
|
|
23
|
+
content: 'width=device-width, initial-scale=1',
|
|
24
|
+
},
|
|
25
|
+
...seo({
|
|
26
|
+
title:
|
|
27
|
+
'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
|
|
28
|
+
description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
links: [
|
|
32
|
+
{ rel: 'stylesheet', href: appCss },
|
|
33
|
+
{
|
|
34
|
+
rel: 'apple-touch-icon',
|
|
35
|
+
sizes: '180x180',
|
|
36
|
+
href: '/apple-touch-icon.png',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
rel: 'icon',
|
|
40
|
+
type: 'image/png',
|
|
41
|
+
sizes: '32x32',
|
|
42
|
+
href: '/favicon-32x32.png',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
rel: 'icon',
|
|
46
|
+
type: 'image/png',
|
|
47
|
+
sizes: '16x16',
|
|
48
|
+
href: '/favicon-16x16.png',
|
|
49
|
+
},
|
|
50
|
+
{ rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
|
|
51
|
+
{ rel: 'icon', href: '/favicon.ico' },
|
|
52
|
+
],
|
|
53
|
+
}),
|
|
54
|
+
errorComponent: (props) => {
|
|
55
|
+
return (
|
|
56
|
+
<RootDocument>
|
|
57
|
+
<DefaultCatchBoundary {...props} />
|
|
58
|
+
</RootDocument>
|
|
59
|
+
)
|
|
60
|
+
},
|
|
61
|
+
notFoundComponent: () => <NotFound />,
|
|
62
|
+
component: RootComponent,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
function RootComponent() {
|
|
66
|
+
return (
|
|
67
|
+
<RootDocument>
|
|
68
|
+
<Outlet />
|
|
69
|
+
</RootDocument>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
74
|
+
return (
|
|
75
|
+
<html>
|
|
76
|
+
<head>
|
|
77
|
+
<HeadContent />
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<div className="p-2 flex gap-2 text-lg">
|
|
81
|
+
<Link
|
|
82
|
+
to="/"
|
|
83
|
+
activeProps={{
|
|
84
|
+
className: 'font-bold',
|
|
85
|
+
}}
|
|
86
|
+
activeOptions={{ exact: true }}
|
|
87
|
+
>
|
|
88
|
+
Home
|
|
89
|
+
</Link>{' '}
|
|
90
|
+
<Link
|
|
91
|
+
to="/posts"
|
|
92
|
+
activeProps={{
|
|
93
|
+
className: 'font-bold',
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
Posts
|
|
97
|
+
</Link>{' '}
|
|
98
|
+
<Link
|
|
99
|
+
to="/users"
|
|
100
|
+
activeProps={{
|
|
101
|
+
className: 'font-bold',
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
Users
|
|
105
|
+
</Link>{' '}
|
|
106
|
+
<Link
|
|
107
|
+
to="/route-a"
|
|
108
|
+
activeProps={{
|
|
109
|
+
className: 'font-bold',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
Pathless Layout
|
|
113
|
+
</Link>{' '}
|
|
114
|
+
<Link
|
|
115
|
+
to="/deferred"
|
|
116
|
+
activeProps={{
|
|
117
|
+
className: 'font-bold',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
Deferred
|
|
121
|
+
</Link>{' '}
|
|
122
|
+
<Link
|
|
123
|
+
// @ts-expect-error
|
|
124
|
+
to="/this-route-does-not-exist"
|
|
125
|
+
activeProps={{
|
|
126
|
+
className: 'font-bold',
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
This Route Does Not Exist
|
|
130
|
+
</Link>
|
|
131
|
+
</div>
|
|
132
|
+
<hr />
|
|
133
|
+
{children}
|
|
134
|
+
<TanStackRouterDevtools position="bottom-right" />
|
|
135
|
+
<Scripts />
|
|
136
|
+
</body>
|
|
137
|
+
</html>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({
|
|
4
|
+
component: LayoutComponent,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function LayoutComponent() {
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
<div>I'm a nested layout</div>
|
|
11
|
+
<div className="flex gap-2 border-b">
|
|
12
|
+
<Link
|
|
13
|
+
to="/route-a"
|
|
14
|
+
activeProps={{
|
|
15
|
+
className: 'font-bold',
|
|
16
|
+
}}
|
|
17
|
+
>
|
|
18
|
+
Go to route A
|
|
19
|
+
</Link>
|
|
20
|
+
<Link
|
|
21
|
+
to="/route-b"
|
|
22
|
+
activeProps={{
|
|
23
|
+
className: 'font-bold',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
Go to route B
|
|
27
|
+
</Link>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<Outlet />
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/_pathlessLayout')({
|
|
4
|
+
component: LayoutComponent,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function LayoutComponent() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="p-2">
|
|
10
|
+
<div className="border-b">I'm a layout</div>
|
|
11
|
+
<div>
|
|
12
|
+
<Outlet />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
import axios from 'redaxios'
|
|
4
|
+
import type { User } from '../../utils/users'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/users/$id')({
|
|
7
|
+
GET: async ({ request, params }) => {
|
|
8
|
+
console.info(`Fetching users by id=${params.id}... @`, request.url)
|
|
9
|
+
try {
|
|
10
|
+
const res = await axios.get<User>(
|
|
11
|
+
'https://jsonplaceholder.typicode.com/users/' + params.id,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
return json({
|
|
15
|
+
id: res.data.id,
|
|
16
|
+
name: res.data.name,
|
|
17
|
+
email: res.data.email,
|
|
18
|
+
})
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.error(e)
|
|
21
|
+
return json({ error: 'User not found' }, { status: 404 })
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { json } from '@tanstack/react-start'
|
|
2
|
+
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
|
3
|
+
import axios from 'redaxios'
|
|
4
|
+
import type { User } from '../../utils/users'
|
|
5
|
+
|
|
6
|
+
export const APIRoute = createAPIFileRoute('/api/users')({
|
|
7
|
+
GET: async ({ request }) => {
|
|
8
|
+
console.info('Fetching users... @', request.url)
|
|
9
|
+
const res = await axios.get<Array<User>>(
|
|
10
|
+
'https://jsonplaceholder.typicode.com/users',
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const list = res.data.slice(0, 10)
|
|
14
|
+
|
|
15
|
+
return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
|
|
16
|
+
},
|
|
17
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Await, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
+
import { Suspense, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
const personServerFn = createServerFn({ method: 'GET' })
|
|
6
|
+
.validator((d: string) => d)
|
|
7
|
+
.handler(({ data: name }) => {
|
|
8
|
+
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const slowServerFn = createServerFn({ method: 'GET' })
|
|
12
|
+
.validator((d: string) => d)
|
|
13
|
+
.handler(async ({ data: name }) => {
|
|
14
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
15
|
+
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const Route = createFileRoute('/deferred')({
|
|
19
|
+
loader: async () => {
|
|
20
|
+
return {
|
|
21
|
+
deferredStuff: new Promise<string>((r) =>
|
|
22
|
+
setTimeout(() => r('Hello deferred!'), 2000),
|
|
23
|
+
),
|
|
24
|
+
deferredPerson: slowServerFn({ data: 'Tanner Linsley' }),
|
|
25
|
+
person: await personServerFn({ data: 'John Doe' }),
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
component: Deferred,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
function Deferred() {
|
|
32
|
+
const [count, setCount] = useState(0)
|
|
33
|
+
const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="p-2">
|
|
37
|
+
<div data-testid="regular-person">
|
|
38
|
+
{person.name} - {person.randomNumber}
|
|
39
|
+
</div>
|
|
40
|
+
<Suspense fallback={<div>Loading person...</div>}>
|
|
41
|
+
<Await
|
|
42
|
+
promise={deferredPerson}
|
|
43
|
+
children={(data) => (
|
|
44
|
+
<div data-testid="deferred-person">
|
|
45
|
+
{data.name} - {data.randomNumber}
|
|
46
|
+
</div>
|
|
47
|
+
)}
|
|
48
|
+
/>
|
|
49
|
+
</Suspense>
|
|
50
|
+
<Suspense fallback={<div>Loading stuff...</div>}>
|
|
51
|
+
<Await
|
|
52
|
+
promise={deferredStuff}
|
|
53
|
+
children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
|
|
54
|
+
/>
|
|
55
|
+
</Suspense>
|
|
56
|
+
<div>Count: {count}</div>
|
|
57
|
+
<div>
|
|
58
|
+
<button onClick={() => setCount(count + 1)}>Increment</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPost } from '../utils/posts'
|
|
3
|
+
import { NotFound } from '~/components/NotFound'
|
|
4
|
+
import { PostErrorComponent } from '~/components/PostError'
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
7
|
+
loader: ({ params: { postId } }) => fetchPost({ data: postId }),
|
|
8
|
+
errorComponent: PostErrorComponent,
|
|
9
|
+
component: PostComponent,
|
|
10
|
+
notFoundComponent: () => {
|
|
11
|
+
return <NotFound>Post not found</NotFound>
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
function PostComponent() {
|
|
16
|
+
const post = Route.useLoaderData()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="space-y-2">
|
|
20
|
+
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
|
21
|
+
<div className="text-sm">{post.body}</div>
|
|
22
|
+
<Link
|
|
23
|
+
to="/posts/$postId/deep"
|
|
24
|
+
params={{
|
|
25
|
+
postId: post.id,
|
|
26
|
+
}}
|
|
27
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
28
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
29
|
+
>
|
|
30
|
+
Deep View
|
|
31
|
+
</Link>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPosts } from '../utils/posts'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/posts')({
|
|
5
|
+
loader: async () => fetchPosts(),
|
|
6
|
+
component: PostsLayoutComponent,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function PostsLayoutComponent() {
|
|
10
|
+
const posts = Route.useLoaderData()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="p-2 flex gap-2">
|
|
14
|
+
<ul className="list-disc pl-4">
|
|
15
|
+
{[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
|
|
16
|
+
(post) => {
|
|
17
|
+
return (
|
|
18
|
+
<li key={post.id} className="whitespace-nowrap">
|
|
19
|
+
<Link
|
|
20
|
+
to="/posts/$postId"
|
|
21
|
+
params={{
|
|
22
|
+
postId: post.id,
|
|
23
|
+
}}
|
|
24
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
25
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
26
|
+
>
|
|
27
|
+
<div>{post.title.substring(0, 20)}</div>
|
|
28
|
+
</Link>
|
|
29
|
+
</li>
|
|
30
|
+
)
|
|
31
|
+
},
|
|
32
|
+
)}
|
|
33
|
+
</ul>
|
|
34
|
+
<hr />
|
|
35
|
+
<Outlet />
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchPost } from '../utils/posts'
|
|
3
|
+
import { PostErrorComponent } from '~/components/PostError'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/posts_/$postId/deep')({
|
|
6
|
+
loader: async ({ params: { postId } }) =>
|
|
7
|
+
fetchPost({
|
|
8
|
+
data: postId,
|
|
9
|
+
}),
|
|
10
|
+
errorComponent: PostErrorComponent,
|
|
11
|
+
component: PostDeepComponent,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
function PostDeepComponent() {
|
|
15
|
+
const post = Route.useLoaderData()
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="p-2 space-y-2">
|
|
19
|
+
<Link
|
|
20
|
+
to="/posts"
|
|
21
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
22
|
+
>
|
|
23
|
+
← All Posts
|
|
24
|
+
</Link>
|
|
25
|
+
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
|
26
|
+
<div className="text-sm">{post.body}</div>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import axios from 'redaxios'
|
|
3
|
+
import type { User } from '~/utils/users'
|
|
4
|
+
import { DEPLOY_URL } from '~/utils/users'
|
|
5
|
+
import { NotFound } from '~/components/NotFound'
|
|
6
|
+
import { UserErrorComponent } from '~/components/UserError'
|
|
7
|
+
|
|
8
|
+
export const Route = createFileRoute('/users/$userId')({
|
|
9
|
+
loader: async ({ params: { userId } }) => {
|
|
10
|
+
return await axios
|
|
11
|
+
.get<User>(DEPLOY_URL + '/api/users/' + userId)
|
|
12
|
+
.then((r) => r.data)
|
|
13
|
+
.catch(() => {
|
|
14
|
+
throw new Error('Failed to fetch user')
|
|
15
|
+
})
|
|
16
|
+
},
|
|
17
|
+
errorComponent: UserErrorComponent,
|
|
18
|
+
component: UserComponent,
|
|
19
|
+
notFoundComponent: () => {
|
|
20
|
+
return <NotFound>User not found</NotFound>
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function UserComponent() {
|
|
25
|
+
const user = Route.useLoaderData()
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="space-y-2">
|
|
29
|
+
<h4 className="text-xl font-bold underline">{user.name}</h4>
|
|
30
|
+
<div className="text-sm">{user.email}</div>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { MyComponent } from '~/components/MyComponent'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/users/')({
|
|
5
|
+
component: UsersIndexComponent,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
function UsersIndexComponent() {
|
|
9
|
+
return <div><MyComponent /></div>
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import axios from 'redaxios'
|
|
3
|
+
import { DEPLOY_URL } from '../utils/users'
|
|
4
|
+
import type { User } from '../utils/users'
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute('/users')({
|
|
7
|
+
loader: async () => {
|
|
8
|
+
return await axios
|
|
9
|
+
.get<Array<User>>(DEPLOY_URL + '/api/users')
|
|
10
|
+
.then((r) => r.data)
|
|
11
|
+
.catch(() => {
|
|
12
|
+
throw new Error('Failed to fetch users')
|
|
13
|
+
})
|
|
14
|
+
},
|
|
15
|
+
component: UsersLayoutComponent,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
function UsersLayoutComponent() {
|
|
19
|
+
const users = Route.useLoaderData()
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="p-2 flex gap-2">
|
|
23
|
+
<ul className="list-disc pl-4">
|
|
24
|
+
{[
|
|
25
|
+
...users,
|
|
26
|
+
{ id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
|
|
27
|
+
].map((user) => {
|
|
28
|
+
return (
|
|
29
|
+
<li key={user.id} className="whitespace-nowrap">
|
|
30
|
+
<Link
|
|
31
|
+
to="/users/$userId"
|
|
32
|
+
params={{
|
|
33
|
+
userId: String(user.id),
|
|
34
|
+
}}
|
|
35
|
+
className="block py-1 text-blue-800 hover:text-blue-600"
|
|
36
|
+
activeProps={{ className: 'text-black font-bold' }}
|
|
37
|
+
>
|
|
38
|
+
<div>{user.name}</div>
|
|
39
|
+
</Link>
|
|
40
|
+
</li>
|
|
41
|
+
)
|
|
42
|
+
})}
|
|
43
|
+
</ul>
|
|
44
|
+
<hr />
|
|
45
|
+
<Outlet />
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
package/src/ssr.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="vinxi/types/server" />
|
|
2
|
+
import {
|
|
3
|
+
createStartHandler,
|
|
4
|
+
defaultStreamHandler,
|
|
5
|
+
} from '@tanstack/react-start/server'
|
|
6
|
+
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
|
|
7
|
+
|
|
8
|
+
import { createRouter } from './router'
|
|
9
|
+
|
|
10
|
+
export default createStartHandler({
|
|
11
|
+
createRouter,
|
|
12
|
+
getRouterManifest,
|
|
13
|
+
})(defaultStreamHandler)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
html {
|
|
7
|
+
color-scheme: light dark;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
* {
|
|
11
|
+
@apply border-gray-200 dark:border-gray-800;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
html,
|
|
15
|
+
body {
|
|
16
|
+
@apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.using-mouse * {
|
|
20
|
+
outline: none !important;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
2
|
+
|
|
3
|
+
const preLogMiddleware = createMiddleware()
|
|
4
|
+
.client(async (ctx) => {
|
|
5
|
+
const clientTime = new Date()
|
|
6
|
+
|
|
7
|
+
return ctx.next({
|
|
8
|
+
context: {
|
|
9
|
+
clientTime,
|
|
10
|
+
},
|
|
11
|
+
sendContext: {
|
|
12
|
+
clientTime,
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
.server(async (ctx) => {
|
|
17
|
+
const serverTime = new Date()
|
|
18
|
+
|
|
19
|
+
return ctx.next({
|
|
20
|
+
sendContext: {
|
|
21
|
+
serverTime,
|
|
22
|
+
durationToServer:
|
|
23
|
+
serverTime.getTime() - ctx.context.clientTime.getTime(),
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const logMiddleware = createMiddleware()
|
|
29
|
+
.middleware([preLogMiddleware])
|
|
30
|
+
.client(async (ctx) => {
|
|
31
|
+
const res = await ctx.next()
|
|
32
|
+
|
|
33
|
+
const now = new Date()
|
|
34
|
+
console.log('Client Req/Res:', {
|
|
35
|
+
duration: res.context.clientTime.getTime() - now.getTime(),
|
|
36
|
+
durationToServer: res.context.durationToServer,
|
|
37
|
+
durationFromServer: now.getTime() - res.context.serverTime.getTime(),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return res
|
|
41
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { notFound } from '@tanstack/react-router'
|
|
2
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
3
|
+
import axios from 'redaxios'
|
|
4
|
+
|
|
5
|
+
export type PostType = {
|
|
6
|
+
id: string
|
|
7
|
+
title: string
|
|
8
|
+
body: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const fetchPost = createServerFn({ method: 'GET' })
|
|
12
|
+
.validator((d: string) => d)
|
|
13
|
+
.handler(async ({ data }) => {
|
|
14
|
+
console.info(`Fetching post with id ${data}...`)
|
|
15
|
+
const post = await axios
|
|
16
|
+
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${data}`)
|
|
17
|
+
.then((r) => r.data)
|
|
18
|
+
.catch((err) => {
|
|
19
|
+
console.error(err)
|
|
20
|
+
if (err.status === 404) {
|
|
21
|
+
throw notFound()
|
|
22
|
+
}
|
|
23
|
+
throw err
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return post
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const fetchPosts = createServerFn({ method: 'GET' }).handler(
|
|
30
|
+
async () => {
|
|
31
|
+
console.info('Fetching posts...')
|
|
32
|
+
return axios
|
|
33
|
+
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
|
|
34
|
+
.then((r) => r.data.slice(0, 10))
|
|
35
|
+
},
|
|
36
|
+
)
|