@tanstack/cta-framework-react-cra 0.10.0-alpha.20 → 0.10.0-alpha.26
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/add-ons/neon/README.md +3 -0
- package/add-ons/neon/assets/_dot_env.local.append +2 -0
- package/add-ons/neon/assets/db/init.sql +14 -0
- package/add-ons/neon/assets/public/demo-neon.svg +1 -0
- package/add-ons/neon/assets/src/db.ts +13 -0
- package/add-ons/neon/assets/src/routes/demo.neon.tsx +168 -0
- package/add-ons/neon/info.json +17 -0
- package/add-ons/neon/package.json +5 -0
- package/add-ons/neon/small-logo.svg +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Schema for a simple to-do list
|
|
2
|
+
CREATE TABLE IF NOT EXISTS todos (
|
|
3
|
+
id SERIAL PRIMARY KEY,
|
|
4
|
+
title VARCHAR(255) NOT NULL,
|
|
5
|
+
description TEXT,
|
|
6
|
+
is_completed BOOLEAN DEFAULT FALSE,
|
|
7
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
-- Initial data for the to-do list
|
|
11
|
+
INSERT INTO todos (title, description, is_completed) VALUES
|
|
12
|
+
('Buy groceries', 'Milk, Bread, Eggs, and Butter', FALSE),
|
|
13
|
+
('Read a book', 'Finish reading "The Great Gatsby"', FALSE),
|
|
14
|
+
('Workout', 'Go for a 30-minute run', FALSE);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" width="256" height="256" preserveAspectRatio="xMidYMid"><defs><linearGradient id="a" x1="100%" x2="12.069%" y1="100%" y2="0%"><stop offset="0%" stop-color="#62F755"/><stop offset="100%" stop-color="#8FF986" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="100%" x2="40.603%" y1="100%" y2="76.897%"><stop offset="0%" stop-opacity=".9"/><stop offset="100%" stop-color="#1A1A1A" stop-opacity="0"/></linearGradient></defs><path fill="#00E0D9" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="url(#a)" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="url(#b)" fill-opacity=".4" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="#63F655" d="M211.861 0C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723a4.409 4.409 0 0 0 4.409-4.409V115.058c0-25.223 31.914-36.167 47.395-16.256l48.391 62.243V8.825c0-4.871-3.953-8.825-8.832-8.825Z"/></svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { neon } from '@neondatabase/serverless'
|
|
2
|
+
|
|
3
|
+
let client: ReturnType<typeof neon>
|
|
4
|
+
|
|
5
|
+
export async function getClient() {
|
|
6
|
+
if (!process.env.DATABASE_URL) {
|
|
7
|
+
return undefined
|
|
8
|
+
}
|
|
9
|
+
if (!client) {
|
|
10
|
+
client = await neon(process.env.DATABASE_URL!)
|
|
11
|
+
}
|
|
12
|
+
return client
|
|
13
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
2
|
+
import { createFileRoute, useRouter } from '@tanstack/react-router'
|
|
3
|
+
|
|
4
|
+
import { getClient } from '@/db'
|
|
5
|
+
|
|
6
|
+
const getTodos = createServerFn({
|
|
7
|
+
method: 'GET',
|
|
8
|
+
}).handler(async () => {
|
|
9
|
+
const client = await getClient()
|
|
10
|
+
if (!client) {
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
|
13
|
+
return (await client.query(`SELECT * FROM todos`)) as Array<{
|
|
14
|
+
id: number
|
|
15
|
+
title: string
|
|
16
|
+
}>
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const insertTodo = createServerFn({
|
|
20
|
+
method: 'POST',
|
|
21
|
+
})
|
|
22
|
+
.validator((d: { title: string }) => d)
|
|
23
|
+
.handler(async ({ data }) => {
|
|
24
|
+
const client = await getClient()
|
|
25
|
+
if (!client) {
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
await client.query(`INSERT INTO todos (title) VALUES ($1)`, [data.title])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export const Route = createFileRoute('/demo/neon')({
|
|
32
|
+
component: App,
|
|
33
|
+
loader: async () => {
|
|
34
|
+
const todos = await getTodos()
|
|
35
|
+
return { todos }
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
const { todos } = Route.useLoaderData()
|
|
41
|
+
const router = useRouter()
|
|
42
|
+
|
|
43
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
44
|
+
e.preventDefault()
|
|
45
|
+
const formData = new FormData(e.target as HTMLFormElement)
|
|
46
|
+
const data = Object.fromEntries(formData)
|
|
47
|
+
await insertTodo({ data: { title: data.title as string } })
|
|
48
|
+
router.invalidate()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!todos) {
|
|
52
|
+
return <DBConnectionError />
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className="flex items-center justify-center min-h-screen bg-gradient-to-br from-purple-100 to-blue-100 p-4 text-white"
|
|
58
|
+
style={{
|
|
59
|
+
backgroundImage:
|
|
60
|
+
'radial-gradient(circle at 5% 40%, #63F655 0%, #00E0D9 40%, #1a0f0a 100%)',
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<div className="w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10">
|
|
64
|
+
<div className="flex items-center justify-center gap-4 mb-8 bg-black/30 p-4 rounded-lg">
|
|
65
|
+
<div className="relative">
|
|
66
|
+
<div className="absolute -inset-1 bg-gradient-to-r from-emerald-400 to-cyan-400 rounded-lg blur opacity-75 group-hover:opacity-100 transition duration-1000"></div>
|
|
67
|
+
<div className="relative">
|
|
68
|
+
<img
|
|
69
|
+
src="/demo-neon.svg"
|
|
70
|
+
alt="Neon Logo"
|
|
71
|
+
className="w-12 h-12 transform hover:scale-110 transition-transform duration-200"
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
<h1 className="text-3xl font-bold bg-gradient-to-r from-emerald-200 to-cyan-200 text-transparent bg-clip-text">
|
|
76
|
+
Neon Database Demo
|
|
77
|
+
</h1>
|
|
78
|
+
</div>
|
|
79
|
+
{todos && (
|
|
80
|
+
<>
|
|
81
|
+
<h1 className="text-2xl font-bold mb-4">Todos</h1>
|
|
82
|
+
<ul className="space-y-3 mb-6">
|
|
83
|
+
{todos.map((todo: { id: number; title: string }) => (
|
|
84
|
+
<li
|
|
85
|
+
key={todo.id}
|
|
86
|
+
className="bg-white/10 backdrop-blur-sm rounded-lg p-4 shadow-sm border border-white/20 transition-all hover:bg-white/20 hover:scale-[1.02] cursor-pointer group"
|
|
87
|
+
>
|
|
88
|
+
<div className="flex items-center justify-between">
|
|
89
|
+
<span className="text-lg font-medium group-hover:text-white/90">
|
|
90
|
+
{todo.title}
|
|
91
|
+
</span>
|
|
92
|
+
<span className="text-xs text-white/50">#{todo.id}</span>
|
|
93
|
+
</div>
|
|
94
|
+
</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
97
|
+
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
|
|
98
|
+
<input
|
|
99
|
+
type="text"
|
|
100
|
+
name="title"
|
|
101
|
+
className="w-full px-4 py-2 rounded-md border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[#00E0D9] bg-black/20"
|
|
102
|
+
/>
|
|
103
|
+
<button
|
|
104
|
+
type="submit"
|
|
105
|
+
className="px-6 py-2 bg-[#00E0D9] text-black font-medium rounded-md hover:bg-[#00E0D9]/80 focus:outline-none focus:ring-2 focus:ring-[#00E0D9] focus:ring-offset-2 transition-colors disabled:opacity-50 whitespace-nowrap"
|
|
106
|
+
>
|
|
107
|
+
Add Todo
|
|
108
|
+
</button>
|
|
109
|
+
</form>
|
|
110
|
+
</>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function DBConnectionError() {
|
|
118
|
+
return (
|
|
119
|
+
<div className="text-center space-y-6">
|
|
120
|
+
<div className="flex items-center justify-center mb-4">
|
|
121
|
+
<svg
|
|
122
|
+
className="w-12 h-12 text-amber-500"
|
|
123
|
+
fill="none"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
viewBox="0 0 24 24"
|
|
126
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
127
|
+
>
|
|
128
|
+
<path
|
|
129
|
+
strokeLinecap="round"
|
|
130
|
+
strokeLinejoin="round"
|
|
131
|
+
strokeWidth="2"
|
|
132
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
133
|
+
/>
|
|
134
|
+
</svg>
|
|
135
|
+
</div>
|
|
136
|
+
<h2 className="text-2xl font-bold mb-4">Database Connection Issue</h2>
|
|
137
|
+
<div className="text-lg mb-6">The Neon database is not connected.</div>
|
|
138
|
+
<div className="bg-black/30 p-6 rounded-lg max-w-xl mx-auto">
|
|
139
|
+
<h3 className="text-lg font-semibold mb-4">Required Steps to Fix:</h3>
|
|
140
|
+
<ul className="space-y-4 text-left list-none">
|
|
141
|
+
<li className="flex items-start">
|
|
142
|
+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-500 text-black font-bold mr-3 min-w-8 min-h-8">
|
|
143
|
+
1
|
|
144
|
+
</span>
|
|
145
|
+
<div>
|
|
146
|
+
Use the{' '}
|
|
147
|
+
<code className="bg-black/30 px-2 py-1 rounded">db/init.sql</code>{' '}
|
|
148
|
+
file to create the database
|
|
149
|
+
</div>
|
|
150
|
+
</li>
|
|
151
|
+
<li className="flex items-start">
|
|
152
|
+
<span className="inline-flex items-center justify-center w-8 h-8 rounded-full bg-amber-500 text-black font-bold mr-3 min-w-8 min-h-8">
|
|
153
|
+
2
|
|
154
|
+
</span>
|
|
155
|
+
<div>
|
|
156
|
+
Set the{' '}
|
|
157
|
+
<code className="bg-black/30 px-2 py-1 rounded">
|
|
158
|
+
DATABASE_URL
|
|
159
|
+
</code>{' '}
|
|
160
|
+
environment variable to the connection string of your Neon
|
|
161
|
+
database
|
|
162
|
+
</div>
|
|
163
|
+
</li>
|
|
164
|
+
</ul>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Neon",
|
|
3
|
+
"description": "Add the Neon database to your application.",
|
|
4
|
+
"link": "https://neon.tech",
|
|
5
|
+
"phase": "add-on",
|
|
6
|
+
"type": "add-on",
|
|
7
|
+
"modes": ["file-router"],
|
|
8
|
+
"routes": [
|
|
9
|
+
{
|
|
10
|
+
"url": "/demo/neon",
|
|
11
|
+
"name": "Neon",
|
|
12
|
+
"path": "src/routes/demo.neon.tsx",
|
|
13
|
+
"jsName": "NeonDemo"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"dependsOn": ["start"]
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" width="256" height="256" preserveAspectRatio="xMidYMid"><defs><linearGradient id="a" x1="100%" x2="12.069%" y1="100%" y2="0%"><stop offset="0%" stop-color="#62F755"/><stop offset="100%" stop-color="#8FF986" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="100%" x2="40.603%" y1="100%" y2="76.897%"><stop offset="0%" stop-opacity=".9"/><stop offset="100%" stop-color="#1A1A1A" stop-opacity="0"/></linearGradient></defs><path fill="#00E0D9" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="url(#a)" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="url(#b)" fill-opacity=".4" d="M0 44.139C0 19.762 19.762 0 44.139 0H211.86C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723h-76.36C19.763 256 0 236.238 0 211.861V44.14Zm44.139-8.825c-4.879 0-8.825 3.946-8.825 8.818v167.73c0 4.878 3.946 8.831 8.818 8.831h77.688c2.44 0 3.087-1.977 3.087-4.416v-101.22c0-25.222 31.914-36.166 47.395-16.255l48.391 62.243V44.14c0-4.879.455-8.825-4.416-8.825H44.14Z"/><path fill="#63F655" d="M211.861 0C236.238 0 256 19.762 256 44.139v142.649c0 25.216-31.915 36.16-47.388 16.256l-48.392-62.251v75.484c0 21.939-17.784 39.723-39.722 39.723a4.409 4.409 0 0 0 4.409-4.409V115.058c0-25.223 31.914-36.167 47.395-16.256l48.391 62.243V8.825c0-4.871-3.953-8.825-8.832-8.825Z"/></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/cta-framework-react-cra",
|
|
3
|
-
"version": "0.10.0-alpha.
|
|
3
|
+
"version": "0.10.0-alpha.26",
|
|
4
4
|
"description": "CTA Framework for React (Create React App)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"author": "Jack Herrington <jherr@pobox.com>",
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@tanstack/cta-engine": "0.10.0-alpha.
|
|
26
|
+
"@tanstack/cta-engine": "0.10.0-alpha.26"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/node": "^22.13.4",
|