create-tsrouter-app 0.2.0 → 0.3.0-alpha.2

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.
Files changed (72) hide show
  1. package/dist/add-ons.js +53 -0
  2. package/dist/cli.js +51 -0
  3. package/dist/constants.js +2 -0
  4. package/dist/create-app.js +285 -0
  5. package/dist/index.js +2 -347
  6. package/dist/options.js +231 -0
  7. package/dist/types.js +1 -0
  8. package/package.json +4 -3
  9. package/src/add-ons.ts +141 -0
  10. package/src/cli.ts +74 -0
  11. package/src/constants.ts +2 -0
  12. package/src/create-app.ts +445 -0
  13. package/src/index.ts +2 -507
  14. package/src/options.ts +264 -0
  15. package/src/types.ts +24 -0
  16. package/templates/add-on/clerk/README.md +3 -0
  17. package/templates/add-on/clerk/assets/.env.local.append +2 -0
  18. package/templates/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
  19. package/templates/add-on/clerk/info.json +28 -0
  20. package/templates/add-on/clerk/package.json +5 -0
  21. package/templates/add-on/convex/README.md +4 -0
  22. package/templates/add-on/convex/assets/.cursorrules.append +93 -0
  23. package/templates/add-on/convex/assets/.env.local.append +3 -0
  24. package/templates/add-on/convex/assets/convex/products.ts +8 -0
  25. package/templates/add-on/convex/assets/convex/schema.ts +10 -0
  26. package/templates/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
  27. package/templates/add-on/convex/info.json +27 -0
  28. package/templates/add-on/convex/package.json +6 -0
  29. package/templates/add-on/form/assets/src/routes/demo.form.tsx +50 -0
  30. package/templates/add-on/form/info.json +12 -0
  31. package/templates/add-on/form/package.json +5 -0
  32. package/templates/add-on/netlify/README.md +11 -0
  33. package/templates/add-on/netlify/info.json +6 -0
  34. package/templates/add-on/react-query/assets/src/routes/demo.react-query.tsx +30 -0
  35. package/templates/add-on/react-query/info.json +30 -0
  36. package/templates/add-on/react-query/package.json +6 -0
  37. package/templates/add-on/sentry/assets/.cursorrules +22 -0
  38. package/templates/add-on/sentry/assets/.env.local.append +2 -0
  39. package/templates/add-on/sentry/assets/src/routes/demo.sentry.bad-server-func.tsx +29 -0
  40. package/templates/add-on/sentry/info.json +13 -0
  41. package/templates/add-on/sentry/package.json +5 -0
  42. package/templates/add-on/shadcn/README.md +7 -0
  43. package/templates/add-on/shadcn/info.json +10 -0
  44. package/templates/add-on/start/assets/app.config.ts +16 -0
  45. package/templates/add-on/start/assets/postcss.config.ts +5 -0
  46. package/templates/add-on/start/assets/src/api.ts +6 -0
  47. package/templates/add-on/start/assets/src/client.tsx +10 -0
  48. package/templates/add-on/start/assets/src/router.tsx.ejs +34 -0
  49. package/templates/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
  50. package/templates/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
  51. package/templates/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
  52. package/templates/add-on/start/assets/src/ssr.tsx +12 -0
  53. package/templates/add-on/start/info.json +18 -0
  54. package/templates/add-on/start/package.json +14 -0
  55. package/templates/add-on/store/assets/src/lib/demo-store.ts +5 -0
  56. package/templates/add-on/store/assets/src/routes/demo.store.page1.tsx +30 -0
  57. package/templates/add-on/store/assets/src/routes/demo.store.page2.tsx +30 -0
  58. package/templates/add-on/store/info.json +16 -0
  59. package/templates/add-on/store/package.json +6 -0
  60. package/templates/base/README.md.ejs +9 -0
  61. package/templates/base/package.json +1 -0
  62. package/templates/base/{tsconfig.json → tsconfig.json.ejs} +5 -1
  63. package/templates/base/vite.config.js.ejs +8 -0
  64. package/templates/example/ai-chat/assets/.env.local.append +2 -0
  65. package/templates/example/ai-chat/assets/src/routes/example.ai-chat.tsx.ejs +81 -0
  66. package/templates/example/ai-chat/info.json +27 -0
  67. package/templates/example/ai-chat/package.json +1 -0
  68. package/templates/file-router/src/components/Header.tsx.ejs +27 -0
  69. package/templates/file-router/src/routes/__root.tsx.ejs +80 -0
  70. package/templates/file-router/src/routes/__root.tsx +0 -11
  71. /package/dist/{utils/getPackageManager.js → package-manager.js} +0 -0
  72. /package/src/{utils/getPackageManager.ts → package-manager.ts} +0 -0
package/src/options.ts ADDED
@@ -0,0 +1,264 @@
1
+ import {
2
+ cancel,
3
+ confirm,
4
+ isCancel,
5
+ multiselect,
6
+ select,
7
+ text,
8
+ } from '@clack/prompts'
9
+
10
+ import {
11
+ DEFAULT_PACKAGE_MANAGER,
12
+ SUPPORTED_PACKAGE_MANAGERS,
13
+ getPackageManager,
14
+ } from './package-manager.js'
15
+ import { CODE_ROUTER, FILE_ROUTER } from './constants.js'
16
+ import { finalizeAddOns, getAllAddOns } from './add-ons.js'
17
+ import type { Variable } from './add-ons.js'
18
+
19
+ import type { CliOptions, Options } from './types.js'
20
+
21
+ // If all CLI options are provided, use them directly
22
+ export function normalizeOptions(
23
+ cliOptions: CliOptions,
24
+ ): Required<Options> | undefined {
25
+ if (cliOptions.projectName) {
26
+ const typescript =
27
+ cliOptions.template === 'typescript' ||
28
+ cliOptions.template === 'file-router'
29
+
30
+ return {
31
+ projectName: cliOptions.projectName,
32
+ typescript,
33
+ tailwind: !!cliOptions.tailwind,
34
+ packageManager: cliOptions.packageManager || DEFAULT_PACKAGE_MANAGER,
35
+ mode: cliOptions.template === 'file-router' ? FILE_ROUTER : CODE_ROUTER,
36
+ git: !!cliOptions.git,
37
+ addOns: !!cliOptions.addOns,
38
+ chosenAddOns: [],
39
+ variableValues: {},
40
+ }
41
+ }
42
+ }
43
+
44
+ async function collectVariables(
45
+ variables: Array<Variable>,
46
+ ): Promise<Record<string, string | number | boolean>> {
47
+ const responses: Record<string, string | number | boolean> = {}
48
+ for (const variable of variables) {
49
+ if (variable.type === 'string') {
50
+ const response = await text({
51
+ message: variable.description,
52
+ initialValue: variable.default,
53
+ })
54
+ if (isCancel(response)) {
55
+ cancel('Operation cancelled.')
56
+ process.exit(0)
57
+ }
58
+ responses[variable.name] = response
59
+ } else if (variable.type === 'number') {
60
+ const response = await text({
61
+ message: variable.description,
62
+ initialValue: variable.default.toString(),
63
+ })
64
+ if (isCancel(response)) {
65
+ cancel('Operation cancelled.')
66
+ process.exit(0)
67
+ }
68
+ responses[variable.name] = Number(response)
69
+ } else {
70
+ const response = await confirm({
71
+ message: variable.description,
72
+ initialValue: variable.default === true,
73
+ })
74
+ if (isCancel(response)) {
75
+ cancel('Operation cancelled.')
76
+ process.exit(0)
77
+ }
78
+ responses[variable.name] = response
79
+ }
80
+ }
81
+ return responses
82
+ }
83
+
84
+ export async function promptForOptions(
85
+ cliOptions: CliOptions,
86
+ ): Promise<Required<Options>> {
87
+ const options = {} as Required<Options>
88
+
89
+ if (!cliOptions.projectName) {
90
+ const value = await text({
91
+ message: 'What would you like to name your project?',
92
+ defaultValue: 'my-app',
93
+ validate(value) {
94
+ if (!value) {
95
+ return 'Please enter a name'
96
+ }
97
+ },
98
+ })
99
+ if (isCancel(value)) {
100
+ cancel('Operation cancelled.')
101
+ process.exit(0)
102
+ }
103
+ options.projectName = value
104
+ }
105
+
106
+ // Router type selection
107
+ if (!cliOptions.template) {
108
+ const routerType = await select({
109
+ message: 'Select the router type:',
110
+ options: [
111
+ {
112
+ value: FILE_ROUTER,
113
+ label: 'File Router - File-based routing structure',
114
+ },
115
+ {
116
+ value: CODE_ROUTER,
117
+ label: 'Code Router - Traditional code-based routing',
118
+ },
119
+ ],
120
+ initialValue: FILE_ROUTER,
121
+ })
122
+ if (isCancel(routerType)) {
123
+ cancel('Operation cancelled.')
124
+ process.exit(0)
125
+ }
126
+ options.mode = routerType as typeof CODE_ROUTER | typeof FILE_ROUTER
127
+ } else {
128
+ options.mode = cliOptions.template as
129
+ | typeof CODE_ROUTER
130
+ | typeof FILE_ROUTER
131
+ if (options.mode === FILE_ROUTER) {
132
+ options.typescript = true
133
+ }
134
+ }
135
+
136
+ // TypeScript selection (if using Code Router)
137
+ if (!options.typescript) {
138
+ if (options.mode === CODE_ROUTER) {
139
+ const typescriptEnable = await confirm({
140
+ message: 'Would you like to use TypeScript?',
141
+ initialValue: true,
142
+ })
143
+ if (isCancel(typescriptEnable)) {
144
+ cancel('Operation cancelled.')
145
+ process.exit(0)
146
+ }
147
+ options.typescript = typescriptEnable
148
+ } else {
149
+ options.typescript = true
150
+ }
151
+ }
152
+
153
+ // Tailwind selection
154
+ if (cliOptions.tailwind === undefined) {
155
+ const tailwind = await confirm({
156
+ message: 'Would you like to use Tailwind CSS?',
157
+ initialValue: true,
158
+ })
159
+ if (isCancel(tailwind)) {
160
+ cancel('Operation cancelled.')
161
+ process.exit(0)
162
+ }
163
+ options.tailwind = tailwind
164
+ } else {
165
+ options.tailwind = cliOptions.tailwind
166
+ }
167
+
168
+ // Package manager selection
169
+ if (cliOptions.packageManager === undefined) {
170
+ const detectedPackageManager = getPackageManager()
171
+ if (!detectedPackageManager) {
172
+ const pm = await select({
173
+ message: 'Select package manager:',
174
+ options: SUPPORTED_PACKAGE_MANAGERS.map((pm) => ({
175
+ value: pm,
176
+ label: pm,
177
+ })),
178
+ initialValue: DEFAULT_PACKAGE_MANAGER,
179
+ })
180
+ if (isCancel(pm)) {
181
+ cancel('Operation cancelled.')
182
+ process.exit(0)
183
+ }
184
+ options.packageManager = pm
185
+ } else {
186
+ options.packageManager = detectedPackageManager
187
+ }
188
+ } else {
189
+ options.packageManager = cliOptions.packageManager
190
+ }
191
+
192
+ // Select any add-ons
193
+ if (options.mode === FILE_ROUTER && cliOptions.addOns) {
194
+ const addOns = await getAllAddOns()
195
+
196
+ const selectedAddOns = await multiselect({
197
+ message: 'What add-ons would you like for your project:',
198
+ options: addOns
199
+ .filter((addOn) => addOn.type === 'add-on')
200
+ .map((addOn) => ({
201
+ value: addOn.id,
202
+ label: addOn.name,
203
+ hint: addOn.description,
204
+ })),
205
+ required: false,
206
+ })
207
+
208
+ if (isCancel(selectedAddOns)) {
209
+ cancel('Operation cancelled.')
210
+ process.exit(0)
211
+ }
212
+
213
+ const selectedExamples = await multiselect({
214
+ message: 'Would you like any examples?',
215
+ options: addOns
216
+ .filter((addOn) => addOn.type === 'example')
217
+ .map((addOn) => ({
218
+ value: addOn.id,
219
+ label: addOn.name,
220
+ hint: addOn.description,
221
+ })),
222
+ required: false,
223
+ })
224
+
225
+ if (isCancel(selectedExamples)) {
226
+ cancel('Operation cancelled.')
227
+ process.exit(0)
228
+ }
229
+
230
+ options.chosenAddOns = await finalizeAddOns([
231
+ ...selectedAddOns,
232
+ ...selectedExamples,
233
+ ])
234
+ options.tailwind = true
235
+ } else {
236
+ options.chosenAddOns = []
237
+ }
238
+
239
+ // Collect variables
240
+ const variables: Array<Variable> = []
241
+ for (const addOn of options.chosenAddOns) {
242
+ for (const variable of addOn.variables ?? []) {
243
+ variables.push(variable)
244
+ }
245
+ }
246
+ options.variableValues = await collectVariables(variables)
247
+
248
+ // Git selection
249
+ if (cliOptions.git === undefined) {
250
+ const git = await confirm({
251
+ message: 'Would you like to initialize a new git repository?',
252
+ initialValue: true,
253
+ })
254
+ if (isCancel(git)) {
255
+ cancel('Operation cancelled.')
256
+ process.exit(0)
257
+ }
258
+ options.git = git
259
+ } else {
260
+ options.git = !!cliOptions.git
261
+ }
262
+
263
+ return options
264
+ }
package/src/types.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { AddOn } from './add-ons.js'
2
+ import type { CODE_ROUTER, FILE_ROUTER } from './constants.js'
3
+ import type { PackageManager } from './package-manager.js'
4
+
5
+ export interface Options {
6
+ projectName: string
7
+ typescript: boolean
8
+ tailwind: boolean
9
+ packageManager: PackageManager
10
+ mode: typeof CODE_ROUTER | typeof FILE_ROUTER
11
+ addOns: boolean
12
+ chosenAddOns: Array<AddOn>
13
+ git: boolean
14
+ variableValues: Record<string, string | number | boolean>
15
+ }
16
+
17
+ export interface CliOptions {
18
+ template?: 'typescript' | 'javascript' | 'file-router'
19
+ tailwind?: boolean
20
+ packageManager?: PackageManager
21
+ projectName?: string
22
+ git?: boolean
23
+ addOns?: boolean
24
+ }
@@ -0,0 +1,3 @@
1
+ ## Setting up Clerk
2
+
3
+ - Set the `VITE_CLERK_PUBLISHABLE_KEY` in your `.env.local`.
@@ -0,0 +1,2 @@
1
+ # Clerk configuration, get this key from your [Dashboard](https://clerk.com/dashboard).
2
+ VITE_CLERK_PUBLISHABLE_KEY=
@@ -0,0 +1,20 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useUser } from '@clerk/clerk-react'
3
+
4
+ export const Route = createFileRoute('/demo/clerk')({
5
+ component: App,
6
+ })
7
+
8
+ function App() {
9
+ const { isSignedIn, user, isLoaded } = useUser()
10
+
11
+ if (!isLoaded) {
12
+ return <div className="p-4">Loading...</div>
13
+ }
14
+
15
+ if (!isSignedIn) {
16
+ return <div className="p-4">Sign in to view this page</div>
17
+ }
18
+
19
+ return <div className="p-4">Hello {user.firstName}!</div>
20
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "Clerk",
3
+ "description": "Add Clerk authentication to your application.",
4
+ "phase": "add-on",
5
+ "link": "https://clerk.com",
6
+ "main": {
7
+ "imports": ["import { ClerkProvider } from '@clerk/clerk-react'"],
8
+ "initialize": [
9
+ "const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY\nif (!PUBLISHABLE_KEY) {\n throw new Error('Add your Clerk Publishable Key to the .env.local file');\n}"
10
+ ],
11
+ "providers": [
12
+ {
13
+ "open": "<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl=\"/\">",
14
+ "close": "</ClerkProvider>"
15
+ }
16
+ ]
17
+ },
18
+ "userUi": {
19
+ "import": "import { SignedIn, SignInButton, SignedOut, UserButton } from '@clerk/clerk-react'",
20
+ "jsx": "<SignedIn><UserButton /></SignedIn><SignedOut><SignInButton /></SignedOut>"
21
+ },
22
+ "routes": [
23
+ {
24
+ "url": "/demo/clerk",
25
+ "name": "Clerk"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@clerk/clerk-react": "^5.22.13"
4
+ }
5
+ }
@@ -0,0 +1,4 @@
1
+ ## Setting up Convex
2
+
3
+ - Set the `VITE_CONVEX_URL` and `CONVEX_DEPLOYMENT` environment variables in your `.env.local`. (Or run `npx convex init` to set them automatically.)
4
+ - Run `npx convex dev` to start the Convex server.
@@ -0,0 +1,93 @@
1
+ This document serves as some special instructions when working with Convex.
2
+
3
+ # Schemas
4
+
5
+ When designing the schema please see this page on built in System fields and data types available: https://docs.convex.dev/database/types
6
+
7
+ Here are some specifics that are often mishandled:
8
+
9
+ ## v (https://docs.convex.dev/api/modules/values#v)
10
+
11
+ The validator builder.
12
+
13
+ This builder allows you to build validators for Convex values.
14
+
15
+ Validators can be used in schema definitions and as input validators for Convex functions.
16
+
17
+ Type declaration
18
+ Name Type
19
+ id <TableName>(tableName: TableName) => VId<GenericId<TableName>, "required">
20
+ null () => VNull<null, "required">
21
+ number () => VFloat64<number, "required">
22
+ float64 () => VFloat64<number, "required">
23
+ bigint () => VInt64<bigint, "required">
24
+ int64 () => VInt64<bigint, "required">
25
+ boolean () => VBoolean<boolean, "required">
26
+ string () => VString<string, "required">
27
+ bytes () => VBytes<ArrayBuffer, "required">
28
+ literal <T>(literal: T) => VLiteral<T, "required">
29
+ array <T>(element: T) => VArray<T["type"][], T, "required">
30
+ object <T>(fields: T) => VObject<Expand<{ [Property in string | number | symbol]?: Exclude<Infer<T[Property]>, undefined> } & { [Property in string | number | symbol]: Infer<T[Property]> }>, T, "required", { [Property in string | number | symbol]: Property | `${Property & string}.${T[Property]["fieldPaths"]}` }[keyof T] & string>
31
+ record <Key, Value>(keys: Key, values: Value) => VRecord<Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>
32
+ union <T>(...members: T) => VUnion<T[number]["type"], T, "required", T[number]["fieldPaths"]>
33
+ any () => VAny<any, "required", string>
34
+ optional <T>(value: T) => VOptional<T>
35
+
36
+ ## System fields (https://docs.convex.dev/database/types#system-fields)
37
+
38
+ Every document in Convex has two automatically-generated system fields:
39
+
40
+ _id: The document ID of the document.
41
+ _creationTime: The time this document was created, in milliseconds since the Unix epoch.
42
+
43
+ You do not need to add indices as these are added automatically.
44
+
45
+ ## Example Schema
46
+
47
+ This is an example of a well crafted schema.
48
+
49
+ ```ts
50
+ import { defineSchema, defineTable } from "convex/server";
51
+ import { v } from "convex/values";
52
+
53
+ export default defineSchema(
54
+ {
55
+ users: defineTable({
56
+ name: v.string(),
57
+ }),
58
+
59
+ sessions: defineTable({
60
+ userId: v.id("users"),
61
+ sessionId: v.string(),
62
+ }).index("sessionId", ["sessionId"]),
63
+
64
+ threads: defineTable({
65
+ uuid: v.string(),
66
+ summary: v.optional(v.string()),
67
+ summarizer: v.optional(v.id("_scheduled_functions")),
68
+ }).index("uuid", ["uuid"]),
69
+
70
+ messages: defineTable({
71
+ message: v.string(),
72
+ threadId: v.id("threads"),
73
+ author: v.union(
74
+ v.object({
75
+ role: v.literal("system"),
76
+ }),
77
+ v.object({
78
+ role: v.literal("assistant"),
79
+ context: v.array(v.id("messages")),
80
+ model: v.optional(v.string()),
81
+ }),
82
+ v.object({
83
+ role: v.literal("user"),
84
+ userId: v.id("users"),
85
+ }),
86
+ ),
87
+ })
88
+ .index("threadId", ["threadId"]),
89
+ },
90
+ );
91
+ ```
92
+
93
+ Sourced from: https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules/convex-cursorrules-prompt-file/.cursorrules
@@ -0,0 +1,3 @@
1
+ # Convex configuration, get this URL from your [Dashboard](https://convex.dev/dashboard).
2
+ CONVEX_DEPLOYMENT=
3
+ VITE_CONVEX_URL=
@@ -0,0 +1,8 @@
1
+ import { query } from "./_generated/server";
2
+
3
+ export const get = query({
4
+ args: {},
5
+ handler: async (ctx) => {
6
+ return await ctx.db.query("products").collect();
7
+ },
8
+ });
@@ -0,0 +1,10 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+
4
+ export default defineSchema({
5
+ products: defineTable({
6
+ title: v.string(),
7
+ imageId: v.string(),
8
+ price: v.number(),
9
+ }),
10
+ });
@@ -0,0 +1,33 @@
1
+ import { Suspense } from 'react'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { useQuery } from 'convex/react'
4
+
5
+ import { api } from '../../convex/_generated/api'
6
+
7
+ export const Route = createFileRoute('/demo/convex')({
8
+ component: App,
9
+ })
10
+
11
+ function Products() {
12
+ const products = useQuery(api.products.get)
13
+
14
+ return (
15
+ <ul>
16
+ {(products || []).map((p) => (
17
+ <li key={p._id}>
18
+ {p.title} - {p.price}
19
+ </li>
20
+ ))}
21
+ </ul>
22
+ )
23
+ }
24
+
25
+ function App() {
26
+ return (
27
+ <div className="p-4">
28
+ <Suspense fallback={<div>Loading...</div>}>
29
+ <Products />
30
+ </Suspense>
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "Convex",
3
+ "description": "Add the Convex database to your application.",
4
+ "link": "https://convex.dev",
5
+ "phase": "add-on",
6
+ "main": {
7
+ "imports": [
8
+ "import { ConvexProvider } from 'convex/react'",
9
+ "import { ConvexQueryClient } from '@convex-dev/react-query'"
10
+ ],
11
+ "initialize": [
12
+ "const CONVEX_URL = (import.meta as any).env.VITE_CONVEX_URL!\nif (!CONVEX_URL) {\n console.error(\"missing envar CONVEX_URL\");\n}\nconst convexQueryClient = new ConvexQueryClient(CONVEX_URL);"
13
+ ],
14
+ "providers": [
15
+ {
16
+ "open": "<ConvexProvider client={convexQueryClient.convexClient}>",
17
+ "close": "</ConvexProvider>"
18
+ }
19
+ ]
20
+ },
21
+ "routes": [
22
+ {
23
+ "url": "/demo/convex",
24
+ "name": "Convex"
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "@convex-dev/react-query": "0.0.0-alpha.8",
4
+ "convex": "^1.19.2"
5
+ }
6
+ }
@@ -0,0 +1,50 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useForm } from '@tanstack/react-form'
3
+
4
+ export const Route = createFileRoute('/demo/form')({
5
+ component: App,
6
+ })
7
+
8
+ export default function App() {
9
+ const form = useForm({
10
+ defaultValues: {
11
+ fullName: '',
12
+ },
13
+ onSubmit: async ({ value }) => {
14
+ console.log(value)
15
+ },
16
+ })
17
+
18
+ return (
19
+ <div className="p-4">
20
+ <form
21
+ onSubmit={(e) => {
22
+ e.preventDefault()
23
+ e.stopPropagation()
24
+ form.handleSubmit()
25
+ }}
26
+ >
27
+ <div>
28
+ <form.Field
29
+ name="fullName"
30
+ children={(field) => (
31
+ <input
32
+ name={field.name}
33
+ value={field.state.value}
34
+ onBlur={field.handleBlur}
35
+ onChange={(e) => field.handleChange(e.target.value)}
36
+ className="block w-full px-4 py-2 text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500"
37
+ />
38
+ )}
39
+ />
40
+ </div>
41
+ <button
42
+ type="submit"
43
+ className="mt-4 inline-flex items-center px-6 py-3 border border-transparent shadow-sm text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
44
+ >
45
+ Submit
46
+ </button>
47
+ </form>
48
+ </div>
49
+ )
50
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "Form",
3
+ "description": "TansStack Form",
4
+ "phase": "add-on",
5
+ "link": "https://tanstack.com/form/latest",
6
+ "routes": [
7
+ {
8
+ "url": "/demo/form",
9
+ "name": "Form"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/react-form": "^0.42.0"
4
+ }
5
+ }
@@ -0,0 +1,11 @@
1
+ ## Setting up Netlify
2
+
3
+ First install the Netlify CLI with:
4
+
5
+ ```bash
6
+ npm install -g netlify-cli`
7
+ ```
8
+
9
+ ```bash
10
+ netlify init
11
+ ```
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "Netlify",
3
+ "description": "Netlify deployment setup",
4
+ "link": "https://docs.netlify.com",
5
+ "phase": "add-on"
6
+ }
@@ -0,0 +1,30 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { useQuery } from '@tanstack/react-query'
3
+
4
+ export const Route = createFileRoute('/demo/react-query')({
5
+ component: App,
6
+ })
7
+
8
+ function App() {
9
+ const { data } = useQuery({
10
+ queryKey: ['people'],
11
+ queryFn: () =>
12
+ fetch('https://swapi.dev/api/people')
13
+ .then((res) => res.json())
14
+ .then((data) => data.results as { name: string }[]),
15
+ initialData: [],
16
+ })
17
+
18
+ return (
19
+ <div className="p-4">
20
+ <h1 className="text-2xl mb-4">People list from Swapi</h1>
21
+ <ul>
22
+ {data.map((person) => (
23
+ <li key={person.name}>{person.name}</li>
24
+ ))}
25
+ </ul>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default App