@tanstack/cta-framework-react-cra 0.23.0 → 0.23.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "dependencies": {
3
- "@clerk/clerk-react": "^5.22.13"
3
+ "@clerk/clerk-react": "^5.49.0"
4
4
  }
5
5
  }
@@ -1,5 +1,5 @@
1
- import { defineSchema, defineTable } from "convex/server";
2
- import { v } from "convex/values";
1
+ import { defineSchema, defineTable } from 'convex/server'
2
+ import { v } from 'convex/values'
3
3
 
4
4
  export default defineSchema({
5
5
  products: defineTable({
@@ -7,4 +7,8 @@ export default defineSchema({
7
7
  imageId: v.string(),
8
8
  price: v.number(),
9
9
  }),
10
- });
10
+ todos: defineTable({
11
+ text: v.string(),
12
+ completed: v.boolean(),
13
+ }),
14
+ })
@@ -0,0 +1,43 @@
1
+ import { mutation, query } from './_generated/server'
2
+ import { v } from 'convex/values'
3
+
4
+ export const list = query({
5
+ args: {},
6
+ handler: async (ctx) => {
7
+ return await ctx.db
8
+ .query('todos')
9
+ .withIndex('by_creation_time')
10
+ .order('desc')
11
+ .collect()
12
+ },
13
+ })
14
+
15
+ export const add = mutation({
16
+ args: { text: v.string() },
17
+ handler: async (ctx, args) => {
18
+ return await ctx.db.insert('todos', {
19
+ text: args.text,
20
+ completed: false,
21
+ })
22
+ },
23
+ })
24
+
25
+ export const toggle = mutation({
26
+ args: { id: v.id('todos') },
27
+ handler: async (ctx, args) => {
28
+ const todo = await ctx.db.get(args.id)
29
+ if (!todo) {
30
+ throw new Error('Todo not found')
31
+ }
32
+ return await ctx.db.patch(args.id, {
33
+ completed: !todo.completed,
34
+ })
35
+ },
36
+ })
37
+
38
+ export const remove = mutation({
39
+ args: { id: v.id('todos') },
40
+ handler: async (ctx, args) => {
41
+ return await ctx.db.delete(args.id)
42
+ },
43
+ })
@@ -1,33 +1,171 @@
1
- import { Suspense } from 'react'
1
+ import { useCallback, useState } from 'react'
2
2
  import { createFileRoute } from '@tanstack/react-router'
3
- import { useQuery } from 'convex/react'
3
+ import { useQuery, useMutation } from 'convex/react'
4
+ import { Trash2, Plus, Check, Circle } from 'lucide-react'
4
5
 
5
6
  import { api } from '../../convex/_generated/api'
6
7
 
7
8
  export const Route = createFileRoute('/demo/convex')({
8
- component: App,
9
+ ssr: false,
10
+ component: ConvexTodos,
9
11
  })
10
12
 
11
- function Products() {
12
- const products = useQuery(api.products.get)
13
+ function ConvexTodos() {
14
+ const todos = useQuery(api.todos.list)
15
+ const addTodo = useMutation(api.todos.add)
16
+ const toggleTodo = useMutation(api.todos.toggle)
17
+ const removeTodo = useMutation(api.todos.remove)
13
18
 
14
- return (
15
- <ul>
16
- {(products || []).map((p) => (
17
- <li key={p._id}>
18
- {p.title} - {p.price}
19
- </li>
20
- ))}
21
- </ul>
19
+ const [newTodo, setNewTodo] = useState('')
20
+
21
+ const handleAddTodo = useCallback(async () => {
22
+ if (newTodo.trim()) {
23
+ await addTodo({ text: newTodo.trim() })
24
+ setNewTodo('')
25
+ }
26
+ }, [addTodo, newTodo])
27
+
28
+ const handleToggleTodo = useCallback(
29
+ async (id: string) => {
30
+ await toggleTodo({ id })
31
+ },
32
+ [toggleTodo],
33
+ )
34
+
35
+ const handleRemoveTodo = useCallback(
36
+ async (id: string) => {
37
+ await removeTodo({ id })
38
+ },
39
+ [removeTodo],
22
40
  )
23
- }
24
41
 
25
- function App() {
42
+ const completedCount = todos?.filter((todo) => todo.completed).length || 0
43
+ const totalCount = todos?.length || 0
44
+
26
45
  return (
27
- <div className="p-4">
28
- <Suspense fallback={<div>Loading...</div>}>
29
- <Products />
30
- </Suspense>
46
+ <div
47
+ className="min-h-screen flex items-center justify-center p-4"
48
+ style={{
49
+ background:
50
+ 'linear-gradient(135deg, #667a56 0%, #8fbc8f 25%, #90ee90 50%, #98fb98 75%, #f0fff0 100%)',
51
+ }}
52
+ >
53
+ <div className="w-full max-w-2xl">
54
+ {/* Header Card */}
55
+ <div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-2xl border border-green-200/50 p-8 mb-6">
56
+ <div className="text-center">
57
+ <h1 className="text-4xl font-bold text-green-800 mb-2">
58
+ Convex Todos
59
+ </h1>
60
+ <p className="text-green-600 text-lg">Powered by real-time sync</p>
61
+ {totalCount > 0 && (
62
+ <div className="mt-4 flex justify-center space-x-6 text-sm">
63
+ <span className="text-green-700 font-medium">
64
+ {completedCount} completed
65
+ </span>
66
+ <span className="text-gray-600">
67
+ {totalCount - completedCount} remaining
68
+ </span>
69
+ </div>
70
+ )}
71
+ </div>
72
+ </div>
73
+
74
+ {/* Add Todo Card */}
75
+ <div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-xl border border-green-200/50 p-6 mb-6">
76
+ <div className="flex gap-3">
77
+ <input
78
+ type="text"
79
+ value={newTodo}
80
+ onChange={(e) => setNewTodo(e.target.value)}
81
+ onKeyDown={(e) => {
82
+ if (e.key === 'Enter') {
83
+ handleAddTodo()
84
+ }
85
+ }}
86
+ placeholder="What needs to be done?"
87
+ className="flex-1 px-4 py-3 rounded-xl border-2 border-green-200 focus:border-green-400 focus:outline-none text-gray-800 placeholder-gray-500 bg-white/80 transition-colors"
88
+ />
89
+ <button
90
+ onClick={handleAddTodo}
91
+ disabled={!newTodo.trim()}
92
+ className="bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 disabled:from-gray-300 disabled:to-gray-400 disabled:cursor-not-allowed text-white font-semibold py-3 px-6 rounded-xl transition-all duration-200 flex items-center gap-2 shadow-lg hover:shadow-xl"
93
+ >
94
+ <Plus size={20} />
95
+ Add
96
+ </button>
97
+ </div>
98
+ </div>
99
+
100
+ {/* Todos List */}
101
+ <div className="bg-white/95 backdrop-blur-sm rounded-2xl shadow-xl border border-green-200/50 overflow-hidden">
102
+ {!todos ? (
103
+ <div className="p-8 text-center">
104
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-green-500 mx-auto mb-4"></div>
105
+ <p className="text-green-600">Loading todos...</p>
106
+ </div>
107
+ ) : todos.length === 0 ? (
108
+ <div className="p-12 text-center">
109
+ <Circle size={48} className="text-green-300 mx-auto mb-4" />
110
+ <h3 className="text-xl font-semibold text-green-800 mb-2">
111
+ No todos yet
112
+ </h3>
113
+ <p className="text-green-600">
114
+ Add your first todo above to get started!
115
+ </p>
116
+ </div>
117
+ ) : (
118
+ <div className="divide-y divide-green-100">
119
+ {todos.map((todo, index) => (
120
+ <div
121
+ key={todo._id}
122
+ className={`p-4 flex items-center gap-4 hover:bg-green-50/50 transition-colors ${
123
+ todo.completed ? 'opacity-75' : ''
124
+ }`}
125
+ style={{
126
+ animationDelay: `${index * 50}ms`,
127
+ }}
128
+ >
129
+ <button
130
+ onClick={() => handleToggleTodo(todo._id)}
131
+ className={`flex-shrink-0 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all duration-200 ${
132
+ todo.completed
133
+ ? 'bg-green-500 border-green-500 text-white'
134
+ : 'border-green-300 hover:border-green-400 text-transparent hover:text-green-400'
135
+ }`}
136
+ >
137
+ <Check size={14} />
138
+ </button>
139
+
140
+ <span
141
+ className={`flex-1 text-lg transition-all duration-200 ${
142
+ todo.completed
143
+ ? 'line-through text-gray-500'
144
+ : 'text-gray-800'
145
+ }`}
146
+ >
147
+ {todo.text}
148
+ </span>
149
+
150
+ <button
151
+ onClick={() => handleRemoveTodo(todo._id)}
152
+ className="flex-shrink-0 p-2 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors"
153
+ >
154
+ <Trash2 size={18} />
155
+ </button>
156
+ </div>
157
+ ))}
158
+ </div>
159
+ )}
160
+ </div>
161
+
162
+ {/* Footer */}
163
+ <div className="text-center mt-6">
164
+ <p className="text-green-700/80 text-sm">
165
+ Built with Convex • Real-time updates • Always in sync
166
+ </p>
167
+ </div>
168
+ </div>
31
169
  </div>
32
170
  )
33
171
  }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "@convex-dev/react-query": "0.0.0-alpha.8",
4
- "convex": "^1.19.2"
4
+ "convex": "^1.27.3",
5
+ "lucide-react": "^0.544.0"
5
6
  }
6
7
  }
@@ -1,4 +1,5 @@
1
1
  import { createFileRoute } from '@tanstack/react-router'
2
+ import { json } from '@tanstack/react-start'
2
3
 
3
4
  import { createCollection, localOnlyCollectionOptions } from '@tanstack/db'
4
5
  import { z } from 'zod'
@@ -72,6 +73,7 @@ export const Route = createFileRoute('/demo/db-chat-api')({
72
73
  return new Response(message.error.message, { status: 400 })
73
74
  }
74
75
  sendMessage(message.data)
76
+ return json(message.data)
75
77
  },
76
78
  },
77
79
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-framework-react-cra",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
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.23.0"
26
+ "@tanstack/cta-engine": "0.23.2"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.13.4",
@@ -1,8 +0,0 @@
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
- });