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.
@@ -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,11 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')(
4
+ {
5
+ component: LayoutAComponent,
6
+ },
7
+ )
8
+
9
+ function LayoutAComponent() {
10
+ return <div>I'm A!</div>
11
+ }
@@ -0,0 +1,11 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')(
4
+ {
5
+ component: LayoutBComponent,
6
+ },
7
+ )
8
+
9
+ function LayoutBComponent() {
10
+ return <div>I'm B!</div>
11
+ }
@@ -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,13 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/')({
4
+ component: Home,
5
+ })
6
+
7
+ function Home() {
8
+ return (
9
+ <div className="p-2">
10
+ <h3>Welcome Home!!!</h3>
11
+ </div>
12
+ )
13
+ }
@@ -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,9 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/posts/')({
4
+ component: PostsIndexComponent,
5
+ })
6
+
7
+ function PostsIndexComponent() {
8
+ return <div>Select a post.</div>
9
+ }
@@ -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,9 @@
1
+ import { createFileRoute, redirect } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/redirect')({
4
+ beforeLoad: async () => {
5
+ throw redirect({
6
+ to: '/posts',
7
+ })
8
+ },
9
+ })
@@ -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
+ )