create-start-app 0.3.0 → 0.4.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/README.md +41 -9
- package/dist/add-ons.js +69 -0
- package/dist/cli.js +78 -0
- package/dist/constants.js +4 -0
- package/dist/create-app.js +371 -0
- package/dist/index.js +2 -347
- package/dist/mcp.js +169 -0
- package/dist/options.js +260 -0
- package/dist/{utils/getPackageManager.js → package-manager.js} +1 -0
- package/dist/types.js +1 -0
- package/package.json +8 -4
- package/src/add-ons.ts +156 -0
- package/src/cli.ts +114 -0
- package/src/constants.ts +7 -0
- package/src/create-app.ts +582 -0
- package/src/index.ts +2 -507
- package/src/mcp.ts +205 -0
- package/src/options.ts +308 -0
- package/src/{utils/getPackageManager.ts → package-manager.ts} +1 -0
- package/src/types.ts +30 -0
- package/templates/react/add-on/clerk/README.md +3 -0
- package/templates/react/add-on/clerk/assets/_dot_env.local.append +2 -0
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/header-user.tsx +19 -0
- package/templates/react/add-on/clerk/assets/src/integrations/clerk/provider.tsx +18 -0
- package/templates/react/add-on/clerk/assets/src/routes/demo.clerk.tsx +20 -0
- package/templates/react/add-on/clerk/info.json +13 -0
- package/templates/react/add-on/clerk/package.json +5 -0
- package/templates/react/add-on/convex/README.md +4 -0
- package/templates/react/add-on/convex/assets/_dot_cursorrules.append +93 -0
- package/templates/react/add-on/convex/assets/_dot_env.local.append +3 -0
- package/templates/react/add-on/convex/assets/convex/products.ts +8 -0
- package/templates/react/add-on/convex/assets/convex/schema.ts +10 -0
- package/templates/react/add-on/convex/assets/src/integrations/convex/provider.tsx +20 -0
- package/templates/react/add-on/convex/assets/src/routes/demo.convex.tsx +33 -0
- package/templates/react/add-on/convex/info.json +13 -0
- package/templates/react/add-on/convex/package.json +6 -0
- package/templates/react/add-on/form/assets/src/routes/demo.form.tsx.ejs +62 -0
- package/templates/react/add-on/form/info.json +13 -0
- package/templates/react/add-on/form/package.json +5 -0
- package/templates/react/add-on/module-federation/assets/module-federation.config.js.ejs +31 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/react/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +11 -0
- package/templates/react/add-on/module-federation/info.json +7 -0
- package/templates/react/add-on/module-federation/package.json +5 -0
- package/templates/react/add-on/netlify/README.md +11 -0
- package/templates/react/add-on/netlify/info.json +7 -0
- package/templates/react/add-on/sentry/assets/_dot_cursorrules.append +22 -0
- package/templates/react/add-on/sentry/assets/_dot_env.local.append +2 -0
- package/templates/react/add-on/sentry/assets/src/app/global-middleware.ts +25 -0
- package/templates/react/add-on/sentry/assets/src/routes/demo.sentry.testing.tsx +480 -0
- package/templates/react/add-on/sentry/info.json +14 -0
- package/templates/react/add-on/sentry/package.json +7 -0
- package/templates/react/add-on/shadcn/README.md +7 -0
- package/templates/react/add-on/shadcn/assets/_dot_cursorrules.append +7 -0
- package/templates/react/add-on/shadcn/info.json +11 -0
- package/templates/react/add-on/start/assets/app.config.ts +16 -0
- package/templates/react/add-on/start/assets/postcss.config.ts +5 -0
- package/templates/react/add-on/start/assets/src/api.ts +6 -0
- package/templates/react/add-on/start/assets/src/client.tsx +10 -0
- package/templates/react/add-on/start/assets/src/router.tsx.ejs +51 -0
- package/templates/react/add-on/start/assets/src/routes/api.demo-names.ts +11 -0
- package/templates/react/add-on/start/assets/src/routes/demo.start.api-request.tsx.ejs +33 -0
- package/templates/react/add-on/start/assets/src/routes/demo.start.server-funcs.tsx +49 -0
- package/templates/react/add-on/start/assets/src/ssr.tsx +12 -0
- package/templates/react/add-on/start/info.json +19 -0
- package/templates/react/add-on/start/package.json +14 -0
- package/templates/react/add-on/store/assets/src/lib/demo-store.ts +13 -0
- package/templates/react/add-on/store/assets/src/routes/demo.store.tsx.ejs +75 -0
- package/templates/react/add-on/store/info.json +13 -0
- package/templates/react/add-on/store/package.json +6 -0
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/layout.tsx +5 -0
- package/templates/react/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +9 -0
- package/templates/react/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx.ejs +38 -0
- package/templates/react/add-on/tanstack-query/info.json +13 -0
- package/templates/react/add-on/tanstack-query/package.json +6 -0
- package/templates/{base → react/base}/README.md.ejs +9 -0
- package/templates/react/base/_dot_vscode/settings.json +11 -0
- package/templates/react/base/src/components/Header.tsx.ejs +25 -0
- package/templates/{base/tsconfig.json → react/base/tsconfig.json.ejs} +5 -1
- package/templates/react/base/vite.config.js.ejs +24 -0
- package/templates/{code-router → react/code-router}/src/main.tsx.ejs +17 -1
- package/templates/react/example/tanchat/README.md +37 -0
- package/templates/react/example/tanchat/assets/_dot_env.local.append +2 -0
- package/templates/react/example/tanchat/assets/src/components/demo.SettingsDialog.tsx +148 -0
- package/templates/react/example/tanchat/assets/src/demo.index.css +220 -0
- package/templates/react/example/tanchat/assets/src/routes/example.chat.tsx.ejs +375 -0
- package/templates/react/example/tanchat/assets/src/store/demo.hooks.ts +21 -0
- package/templates/react/example/tanchat/assets/src/store/demo.store.ts +133 -0
- package/templates/react/example/tanchat/assets/src/utils/demo.ai.ts +108 -0
- package/templates/react/example/tanchat/info.json +15 -0
- package/templates/react/example/tanchat/package.json +10 -0
- package/templates/react/file-router/src/routes/__root.tsx.ejs +71 -0
- package/templates/solid/add-on/form/assets/src/routes/demo.form.tsx.ejs +148 -0
- package/templates/solid/add-on/form/info.json +13 -0
- package/templates/solid/add-on/form/package.json +5 -0
- package/templates/solid/add-on/module-federation/assets/module-federation.config.js.ejs +27 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-component.tsx +3 -0
- package/templates/solid/add-on/module-federation/assets/src/demo-mf-self-contained.tsx +9 -0
- package/templates/solid/add-on/module-federation/info.json +7 -0
- package/templates/solid/add-on/module-federation/package.json +5 -0
- package/templates/solid/add-on/sentry/assets/_dot_cursorrules.append +22 -0
- package/templates/solid/add-on/sentry/assets/_dot_env.local.append +2 -0
- package/templates/solid/add-on/sentry/assets/src/routes/demo.sentry.bad-event-handler.tsx +20 -0
- package/templates/solid/add-on/sentry/info.json +13 -0
- package/templates/solid/add-on/sentry/package.json +5 -0
- package/templates/solid/add-on/solid-ui/README.md +9 -0
- package/templates/solid/add-on/solid-ui/assets/src/lib/utils.ts +6 -0
- package/templates/solid/add-on/solid-ui/assets/src/styles.css +138 -0
- package/templates/solid/add-on/solid-ui/assets/ui.config.json +13 -0
- package/templates/solid/add-on/solid-ui/info.json +11 -0
- package/templates/solid/add-on/solid-ui/package.json +9 -0
- package/templates/solid/add-on/store/assets/src/lib/demo-store.ts +13 -0
- package/templates/solid/add-on/store/assets/src/routes/demo.store.tsx.ejs +77 -0
- package/templates/solid/add-on/store/info.json +13 -0
- package/templates/solid/add-on/store/package.json +6 -0
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/header-user.tsx +5 -0
- package/templates/solid/add-on/tanstack-query/assets/src/integrations/tanstack-query/provider.tsx +15 -0
- package/templates/solid/add-on/tanstack-query/assets/src/routes/demo.tanstack-query.tsx +30 -0
- package/templates/solid/add-on/tanstack-query/info.json +13 -0
- package/templates/solid/add-on/tanstack-query/package.json +6 -0
- package/templates/solid/base/README.md.ejs +200 -0
- package/templates/solid/base/_dot_cursorrules.append +35 -0
- package/templates/solid/base/_dot_gitignore +5 -0
- package/templates/solid/base/_dot_vscode/settings.json +11 -0
- package/templates/solid/base/index.html.ejs +20 -0
- package/templates/solid/base/package.json +22 -0
- package/templates/solid/base/package.ts.json +5 -0
- package/templates/solid/base/package.tw.json +6 -0
- package/templates/solid/base/public/favicon.ico +0 -0
- package/templates/solid/base/public/logo192.png +0 -0
- package/templates/solid/base/public/logo512.png +0 -0
- package/templates/solid/base/public/manifest.json +25 -0
- package/templates/solid/base/public/robots.txt +3 -0
- package/templates/solid/base/src/App.css +0 -0
- package/templates/solid/base/src/App.tsx.ejs +47 -0
- package/templates/solid/base/src/components/Header.tsx.ejs +26 -0
- package/templates/solid/base/src/logo.svg +120 -0
- package/templates/solid/base/src/styles.css.ejs +15 -0
- package/templates/solid/base/tsconfig.json.ejs +30 -0
- package/templates/solid/base/vite.config.js.ejs +22 -0
- package/templates/solid/code-router/src/main.tsx.ejs +69 -0
- package/templates/solid/file-router/package.fr.json +5 -0
- package/templates/solid/file-router/src/main.tsx.ejs +44 -0
- package/templates/solid/file-router/src/routes/__root.tsx.ejs +41 -0
- package/templates/solid/file-router/src/routes/index.tsx +43 -0
- package/templates/base/vite.config.js.ejs +0 -15
- package/templates/file-router/src/routes/__root.tsx +0 -11
- /package/templates/{base/gitignore → react/base/_dot_gitignore} +0 -0
- /package/templates/{base → react/base}/index.html.ejs +0 -0
- /package/templates/{base → react/base}/package.json +0 -0
- /package/templates/{base → react/base}/package.ts.json +0 -0
- /package/templates/{base → react/base}/package.tw.json +0 -0
- /package/templates/{base → react/base}/public/favicon.ico +0 -0
- /package/templates/{base → react/base}/public/logo192.png +0 -0
- /package/templates/{base → react/base}/public/logo512.png +0 -0
- /package/templates/{base → react/base}/public/manifest.json +0 -0
- /package/templates/{base → react/base}/public/robots.txt +0 -0
- /package/templates/{base → react/base}/src/App.css +0 -0
- /package/templates/{base → react/base}/src/App.test.tsx.ejs +0 -0
- /package/templates/{base → react/base}/src/App.tsx.ejs +0 -0
- /package/templates/{base → react/base}/src/logo.svg +0 -0
- /package/templates/{base → react/base}/src/reportWebVitals.ts.ejs +0 -0
- /package/templates/{base → react/base}/src/styles.css.ejs +0 -0
- /package/templates/{file-router → react/file-router}/package.fr.json +0 -0
- /package/templates/{file-router → react/file-router}/src/main.tsx.ejs +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Store } from '@tanstack/store'
|
|
2
|
+
import type { Message } from '../utils/demo.ai'
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
export interface Prompt {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
content: string
|
|
9
|
+
is_active: boolean
|
|
10
|
+
created_at: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Conversation {
|
|
14
|
+
id: string
|
|
15
|
+
title: string
|
|
16
|
+
messages: Message[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface State {
|
|
20
|
+
prompts: Prompt[]
|
|
21
|
+
conversations: Conversation[]
|
|
22
|
+
currentConversationId: string | null
|
|
23
|
+
isLoading: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const initialState: State = {
|
|
27
|
+
prompts: [],
|
|
28
|
+
conversations: [],
|
|
29
|
+
currentConversationId: null,
|
|
30
|
+
isLoading: false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const store = new Store<State>(initialState)
|
|
34
|
+
|
|
35
|
+
export const actions = {
|
|
36
|
+
// Prompt actions
|
|
37
|
+
createPrompt: (name: string, content: string) => {
|
|
38
|
+
const id = Date.now().toString()
|
|
39
|
+
store.setState(state => {
|
|
40
|
+
const updatedPrompts = state.prompts.map(p => ({ ...p, is_active: false }))
|
|
41
|
+
return {
|
|
42
|
+
...state,
|
|
43
|
+
prompts: [
|
|
44
|
+
...updatedPrompts,
|
|
45
|
+
{
|
|
46
|
+
id,
|
|
47
|
+
name,
|
|
48
|
+
content,
|
|
49
|
+
is_active: true,
|
|
50
|
+
created_at: Date.now()
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
deletePrompt: (id: string) => {
|
|
58
|
+
store.setState(state => ({
|
|
59
|
+
...state,
|
|
60
|
+
prompts: state.prompts.filter(p => p.id !== id)
|
|
61
|
+
}))
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
setPromptActive: (id: string, shouldActivate: boolean) => {
|
|
65
|
+
store.setState(state => ({
|
|
66
|
+
...state,
|
|
67
|
+
prompts: state.prompts.map(p => ({
|
|
68
|
+
...p,
|
|
69
|
+
is_active: p.id === id ? shouldActivate : false
|
|
70
|
+
}))
|
|
71
|
+
}))
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Chat actions
|
|
75
|
+
setConversations: (conversations: Conversation[]) => {
|
|
76
|
+
store.setState(state => ({ ...state, conversations }))
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
setCurrentConversationId: (id: string | null) => {
|
|
80
|
+
store.setState(state => ({ ...state, currentConversationId: id }))
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
addConversation: (conversation: Conversation) => {
|
|
84
|
+
store.setState(state => ({
|
|
85
|
+
...state,
|
|
86
|
+
conversations: [...state.conversations, conversation],
|
|
87
|
+
currentConversationId: conversation.id
|
|
88
|
+
}))
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
updateConversationTitle: (id: string, title: string) => {
|
|
92
|
+
store.setState(state => ({
|
|
93
|
+
...state,
|
|
94
|
+
conversations: state.conversations.map(conv =>
|
|
95
|
+
conv.id === id ? { ...conv, title } : conv
|
|
96
|
+
)
|
|
97
|
+
}))
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
deleteConversation: (id: string) => {
|
|
101
|
+
store.setState(state => ({
|
|
102
|
+
...state,
|
|
103
|
+
conversations: state.conversations.filter(conv => conv.id !== id),
|
|
104
|
+
currentConversationId: state.currentConversationId === id ? null : state.currentConversationId
|
|
105
|
+
}))
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
addMessage: (conversationId: string, message: Message) => {
|
|
109
|
+
store.setState(state => ({
|
|
110
|
+
...state,
|
|
111
|
+
conversations: state.conversations.map(conv =>
|
|
112
|
+
conv.id === conversationId
|
|
113
|
+
? { ...conv, messages: [...conv.messages, message] }
|
|
114
|
+
: conv
|
|
115
|
+
)
|
|
116
|
+
}))
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
setLoading: (isLoading: boolean) => {
|
|
120
|
+
store.setState(state => ({ ...state, isLoading }))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Selectors
|
|
125
|
+
export const selectors = {
|
|
126
|
+
getActivePrompt: (state: State) => state.prompts.find(p => p.is_active),
|
|
127
|
+
getCurrentConversation: (state: State) =>
|
|
128
|
+
state.conversations.find(c => c.id === state.currentConversationId),
|
|
129
|
+
getPrompts: (state: State) => state.prompts,
|
|
130
|
+
getConversations: (state: State) => state.conversations,
|
|
131
|
+
getCurrentConversationId: (state: State) => state.currentConversationId,
|
|
132
|
+
getIsLoading: (state: State) => state.isLoading
|
|
133
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createServerFn } from '@tanstack/start'
|
|
2
|
+
import { Anthropic } from '@anthropic-ai/sdk'
|
|
3
|
+
|
|
4
|
+
export interface Message {
|
|
5
|
+
id: string
|
|
6
|
+
role: 'user' | 'assistant'
|
|
7
|
+
content: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DEFAULT_SYSTEM_PROMPT = `You are TanStack Chat, an AI assistant using Markdown for clear and structured responses. Format your responses following these guidelines:
|
|
11
|
+
|
|
12
|
+
1. Use headers for sections:
|
|
13
|
+
# For main topics
|
|
14
|
+
## For subtopics
|
|
15
|
+
### For subsections
|
|
16
|
+
|
|
17
|
+
2. For lists and steps:
|
|
18
|
+
- Use bullet points for unordered lists
|
|
19
|
+
- Number steps when sequence matters
|
|
20
|
+
|
|
21
|
+
3. For code:
|
|
22
|
+
- Use inline \`code\` for short snippets
|
|
23
|
+
- Use triple backticks with language for blocks:
|
|
24
|
+
\`\`\`python
|
|
25
|
+
def example():
|
|
26
|
+
return "like this"
|
|
27
|
+
\`\`\`
|
|
28
|
+
|
|
29
|
+
4. For emphasis:
|
|
30
|
+
- Use **bold** for important points
|
|
31
|
+
- Use *italics* for emphasis
|
|
32
|
+
- Use > for important quotes or callouts
|
|
33
|
+
|
|
34
|
+
5. For structured data:
|
|
35
|
+
| Use | Tables |
|
|
36
|
+
|-----|---------|
|
|
37
|
+
| When | Needed |
|
|
38
|
+
|
|
39
|
+
6. Break up long responses with:
|
|
40
|
+
- Clear section headers
|
|
41
|
+
- Appropriate spacing between sections
|
|
42
|
+
- Bullet points for better readability
|
|
43
|
+
- Short, focused paragraphs
|
|
44
|
+
|
|
45
|
+
7. For technical content:
|
|
46
|
+
- Always specify language for code blocks
|
|
47
|
+
- Use inline \`code\` for technical terms
|
|
48
|
+
- Include example usage where helpful
|
|
49
|
+
|
|
50
|
+
Keep responses concise and well-structured. Use appropriate Markdown formatting to enhance readability and understanding.`;
|
|
51
|
+
|
|
52
|
+
// Non-streaming implementation
|
|
53
|
+
export const genAIResponse = createServerFn({ method: 'GET' })
|
|
54
|
+
|
|
55
|
+
.validator((d: {
|
|
56
|
+
messages: Message[],
|
|
57
|
+
systemPrompt?: { value: string, enabled: boolean },
|
|
58
|
+
streamEnabled?: boolean
|
|
59
|
+
}) => d)
|
|
60
|
+
// .middleware([loggingMiddleware])
|
|
61
|
+
.handler(async ({ data }) => {
|
|
62
|
+
const anthropic = new Anthropic({
|
|
63
|
+
apiKey: import.meta.env.VITE_ANTHROPIC_API_KEY || '',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Filter out error messages and empty messages
|
|
67
|
+
const formattedMessages = data.messages
|
|
68
|
+
.filter(msg => msg.content.trim() !== '' && !msg.content.startsWith('Sorry, I encountered an error'))
|
|
69
|
+
.map(msg => ({
|
|
70
|
+
role: msg.role,
|
|
71
|
+
content: msg.content.trim()
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
if (formattedMessages.length === 0) {
|
|
75
|
+
return { error: 'No valid messages to send' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const systemPrompt = data.systemPrompt?.enabled
|
|
79
|
+
? `${DEFAULT_SYSTEM_PROMPT}\n\n${data.systemPrompt.value}`
|
|
80
|
+
: DEFAULT_SYSTEM_PROMPT;
|
|
81
|
+
|
|
82
|
+
// Debug log to verify prompt layering
|
|
83
|
+
console.log('System Prompt Configuration:', {
|
|
84
|
+
hasCustomPrompt: data.systemPrompt?.enabled,
|
|
85
|
+
customPromptValue: data.systemPrompt?.value,
|
|
86
|
+
finalPrompt: systemPrompt
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const response = await anthropic.messages.create({
|
|
91
|
+
model: "claude-3-5-sonnet-20241022",
|
|
92
|
+
max_tokens: 4096,
|
|
93
|
+
system: systemPrompt,
|
|
94
|
+
messages: formattedMessages,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (response.content[0].type === 'text') {
|
|
98
|
+
return { text: response.content[0].text };
|
|
99
|
+
}
|
|
100
|
+
return { error: 'Unexpected response type' };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Error in genAIResponse:', error);
|
|
103
|
+
if (error instanceof Error && error.message.includes('rate limit')) {
|
|
104
|
+
return { error: 'Rate limit exceeded. Please try again in a moment.' };
|
|
105
|
+
}
|
|
106
|
+
return { error: error instanceof Error ? error.message : 'Failed to get AI response' };
|
|
107
|
+
}
|
|
108
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "TanStack Chat",
|
|
3
|
+
"description": "A chat example that uses TanStack Start and TanStack Store. Features chat with Antrhopic Sonnet, chat history and custom prompts.",
|
|
4
|
+
"phase": "example",
|
|
5
|
+
"templates": ["file-router"],
|
|
6
|
+
"link": "",
|
|
7
|
+
"routes": [
|
|
8
|
+
{
|
|
9
|
+
"url": "/example/chat",
|
|
10
|
+
"name": "Chat"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"dependsOn": ["start", "store"],
|
|
14
|
+
"variables": []
|
|
15
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createRootRoute, Outlet<% if (addOnEnabled.start) { %>
|
|
2
|
+
,HeadContent, Scripts<% } %> } from '@tanstack/react-router'
|
|
3
|
+
import { TanStackRouterDevtools } from '@tanstack/router-devtools'
|
|
4
|
+
<% if (addOns.length) { %>
|
|
5
|
+
import Header from '../components/Header'
|
|
6
|
+
<% } %><% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider')) { %>
|
|
7
|
+
import <%= integration.name %> from "../<%= integration.path %>";
|
|
8
|
+
<% } %>
|
|
9
|
+
|
|
10
|
+
<% if (addOnEnabled.start) { %>
|
|
11
|
+
import appCss from '../styles.css?url'
|
|
12
|
+
<% } %>
|
|
13
|
+
|
|
14
|
+
export const Route = createRootRoute({
|
|
15
|
+
<% if (addOnEnabled.start) { %>
|
|
16
|
+
head: () => ({
|
|
17
|
+
meta: [
|
|
18
|
+
{
|
|
19
|
+
charSet: 'utf-8',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'viewport',
|
|
23
|
+
content: 'width=device-width, initial-scale=1',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: 'TanStack Start Starter',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
links: [
|
|
30
|
+
{
|
|
31
|
+
rel: 'stylesheet',
|
|
32
|
+
href: appCss,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
<% } %>
|
|
37
|
+
component: () => (
|
|
38
|
+
<% if (addOnEnabled.start) { %><RootDocument><% } else { %><><% } %>
|
|
39
|
+
<% for(const integration of integrations.filter(i => i.type === 'provider')) { %>
|
|
40
|
+
<<%= integration.name %>>
|
|
41
|
+
<% } %>
|
|
42
|
+
<% if (addOns.length) { %>
|
|
43
|
+
<Header />
|
|
44
|
+
<% } %>
|
|
45
|
+
<Outlet />
|
|
46
|
+
<TanStackRouterDevtools />
|
|
47
|
+
<% for(const integration of integrations.filter(i => i.type === 'layout')) { %>
|
|
48
|
+
<<%= integration.name %> />
|
|
49
|
+
<% } %>
|
|
50
|
+
<% for(const integration of integrations.filter(i => i.type === 'provider').toReversed()) { %>
|
|
51
|
+
</<%= integration.name %>>
|
|
52
|
+
<% } %>
|
|
53
|
+
<% if (addOnEnabled.start) { %></RootDocument><% } else { %></><% } %>
|
|
54
|
+
),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
<% if (addOnEnabled.start) { %>
|
|
58
|
+
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
59
|
+
return (
|
|
60
|
+
<html>
|
|
61
|
+
<head>
|
|
62
|
+
<HeadContent />
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
{children}
|
|
66
|
+
<Scripts />
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
<% } %>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { <% if (fileRouter) { %>createFileRoute<% } else { %>createRoute<% } %> } from '@tanstack/solid-router'
|
|
2
|
+
|
|
3
|
+
import { createForm } from '@tanstack/solid-form'
|
|
4
|
+
import type { AnyFieldApi } from '@tanstack/solid-form'
|
|
5
|
+
|
|
6
|
+
interface FieldInfoProps {
|
|
7
|
+
field: AnyFieldApi
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
<% if (codeRouter) { %>
|
|
11
|
+
import type { RootRoute } from '@tanstack/react-router'
|
|
12
|
+
<% } else { %>
|
|
13
|
+
export const Route = createFileRoute('/demo/form')({
|
|
14
|
+
component: FormExample,
|
|
15
|
+
})
|
|
16
|
+
<% } %>
|
|
17
|
+
function FieldInfo(props: FieldInfoProps) {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
{props.field.state.meta.isTouched &&
|
|
21
|
+
props.field.state.meta.errors.length ? (
|
|
22
|
+
<em>{props.field.state.meta.errors.join(',')}</em>
|
|
23
|
+
) : null}
|
|
24
|
+
{props.field.state.meta.isValidating ? 'Validating...' : null}
|
|
25
|
+
</>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function FormExample() {
|
|
30
|
+
const form = createForm(() => ({
|
|
31
|
+
defaultValues: {
|
|
32
|
+
firstName: '',
|
|
33
|
+
lastName: '',
|
|
34
|
+
},
|
|
35
|
+
onSubmit: async ({ value }) => {
|
|
36
|
+
// Do something with form data
|
|
37
|
+
console.log(value)
|
|
38
|
+
},
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div class="max-w-md mx-auto mt-10 p-6 bg-white rounded-lg shadow-md">
|
|
43
|
+
<h1 class="text-2xl font-bold mb-8 text-gray-800 leading-tight">
|
|
44
|
+
Simple Form Example
|
|
45
|
+
</h1>
|
|
46
|
+
<form
|
|
47
|
+
class="space-y-6"
|
|
48
|
+
onSubmit={(e) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
e.stopPropagation()
|
|
51
|
+
form.handleSubmit()
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<div class="space-y-1">
|
|
55
|
+
<form.Field
|
|
56
|
+
name="firstName"
|
|
57
|
+
validators={{
|
|
58
|
+
onChange: ({ value }) =>
|
|
59
|
+
!value
|
|
60
|
+
? 'A first name is required'
|
|
61
|
+
: value.length < 3
|
|
62
|
+
? 'First name must be at least 3 characters'
|
|
63
|
+
: undefined,
|
|
64
|
+
onChangeAsyncDebounceMs: 500,
|
|
65
|
+
onChangeAsync: async ({ value }) => {
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
67
|
+
return (
|
|
68
|
+
value.includes('error') && 'No "error" allowed in first name'
|
|
69
|
+
)
|
|
70
|
+
},
|
|
71
|
+
}}
|
|
72
|
+
children={(field) => {
|
|
73
|
+
return (
|
|
74
|
+
<>
|
|
75
|
+
<label
|
|
76
|
+
for={field().name}
|
|
77
|
+
class="block text-sm font-medium text-gray-700 mb-1 leading-tight"
|
|
78
|
+
>
|
|
79
|
+
First Name:
|
|
80
|
+
</label>
|
|
81
|
+
<input
|
|
82
|
+
id={field().name}
|
|
83
|
+
name={field().name}
|
|
84
|
+
value={field().state.value}
|
|
85
|
+
onBlur={field().handleBlur}
|
|
86
|
+
onInput={(e) => field().handleChange(e.target.value)}
|
|
87
|
+
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm leading-relaxed p-2"
|
|
88
|
+
/>
|
|
89
|
+
<FieldInfo field={field()} />
|
|
90
|
+
</>
|
|
91
|
+
)
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="space-y-1">
|
|
96
|
+
<form.Field
|
|
97
|
+
name="lastName"
|
|
98
|
+
children={(field) => (
|
|
99
|
+
<>
|
|
100
|
+
<label
|
|
101
|
+
for={field().name}
|
|
102
|
+
class="block text-sm font-medium text-gray-700 mb-1 leading-tight"
|
|
103
|
+
>
|
|
104
|
+
Last Name:
|
|
105
|
+
</label>
|
|
106
|
+
<input
|
|
107
|
+
id={field().name}
|
|
108
|
+
name={field().name}
|
|
109
|
+
value={field().state.value}
|
|
110
|
+
onBlur={field().handleBlur}
|
|
111
|
+
onInput={(e) => field().handleChange(e.target.value)}
|
|
112
|
+
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm leading-relaxed p-2"
|
|
113
|
+
/>
|
|
114
|
+
<FieldInfo field={field()} />
|
|
115
|
+
</>
|
|
116
|
+
)}
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
<form.Subscribe
|
|
120
|
+
selector={(state) => ({
|
|
121
|
+
canSubmit: state.canSubmit,
|
|
122
|
+
isSubmitting: state.isSubmitting,
|
|
123
|
+
})}
|
|
124
|
+
children={(state) => {
|
|
125
|
+
return (
|
|
126
|
+
<button
|
|
127
|
+
type="submit"
|
|
128
|
+
disabled={!state().canSubmit}
|
|
129
|
+
class="w-full flex justify-center py-2.5 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed leading-tight"
|
|
130
|
+
>
|
|
131
|
+
{state().isSubmitting ? '...' : 'Submit'}
|
|
132
|
+
</button>
|
|
133
|
+
)
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
</form>
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
<% if (codeRouter) { %>
|
|
142
|
+
export default (parentRoute: RootRoute) => createRoute({
|
|
143
|
+
path: '/demo/form',
|
|
144
|
+
component: FormExample,
|
|
145
|
+
getParentRoute: () => parentRoute,
|
|
146
|
+
})
|
|
147
|
+
<% } %>
|
|
148
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import packageJSON from './package.json'
|
|
2
|
+
|
|
3
|
+
function remoteConfig(name, url) {
|
|
4
|
+
return {
|
|
5
|
+
type: 'module',
|
|
6
|
+
name,
|
|
7
|
+
entry: url,
|
|
8
|
+
entryGlobalName: 'remote',
|
|
9
|
+
shareScope: 'default',
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
filename: 'remoteEntry.js',
|
|
15
|
+
name: '<%= projectName %>',
|
|
16
|
+
exposes: {
|
|
17
|
+
'./DemoMfComponent': './src/demo-mf-component.tsx',
|
|
18
|
+
'./DemoMfSelfContained': './src/demo-mf-self-contained.tsx',
|
|
19
|
+
},
|
|
20
|
+
remotes: {},
|
|
21
|
+
shared: {
|
|
22
|
+
'solid-js': {
|
|
23
|
+
singleton: true,
|
|
24
|
+
requiredVersion: packageJSON.dependencies['solid-js'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
We use Sentry for watching for errors in our deployed application, as well as for instrumentation of our application.
|
|
2
|
+
|
|
3
|
+
## Error collection
|
|
4
|
+
|
|
5
|
+
Error collection is automatic and configured in `src/router.tsx`.
|
|
6
|
+
|
|
7
|
+
## Instrumentation
|
|
8
|
+
|
|
9
|
+
We want our server functions intstrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import * as Sentry from '@sentry/browser'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then wrap the implementation of the server function with `Sentry.startSpan`, liks so:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
|
|
19
|
+
// Some lengthy operation here
|
|
20
|
+
await fetch('https://api.pokemon.com/data/')
|
|
21
|
+
})
|
|
22
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/solid-router'
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/demo/sentry/bad-event-handler')({
|
|
4
|
+
component: RouteComponent,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
function RouteComponent() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="p-4">
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
onClick={() => {
|
|
13
|
+
throw new Error('Sentry Frontend Error')
|
|
14
|
+
}}
|
|
15
|
+
>
|
|
16
|
+
Throw error
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Sentry",
|
|
3
|
+
"phase": "setup",
|
|
4
|
+
"description": "Add Sentry for error monitoring and crash reporting (requires Start).",
|
|
5
|
+
"link": "https://sentry.com/",
|
|
6
|
+
"templates": ["file-router"],
|
|
7
|
+
"routes": [
|
|
8
|
+
{
|
|
9
|
+
"url": "/demo/sentry/bad-event-handler",
|
|
10
|
+
"name": "Sentry"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|