ultra-dex 3.1.0 → 3.3.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.
Files changed (52) hide show
  1. package/README.md +79 -74
  2. package/assets/code-patterns/clerk-middleware.ts +138 -0
  3. package/assets/code-patterns/prisma-schema.prisma +224 -0
  4. package/assets/code-patterns/rls-policies.sql +246 -0
  5. package/assets/code-patterns/server-actions.ts +191 -0
  6. package/assets/code-patterns/trpc-router.ts +258 -0
  7. package/assets/cursor-rules/13-ai-integration.mdc +155 -0
  8. package/assets/cursor-rules/14-server-components.mdc +81 -0
  9. package/assets/cursor-rules/15-server-actions.mdc +102 -0
  10. package/assets/cursor-rules/16-edge-middleware.mdc +105 -0
  11. package/assets/cursor-rules/17-streaming-ssr.mdc +138 -0
  12. package/bin/ultra-dex.js +50 -1
  13. package/lib/commands/agents.js +16 -13
  14. package/lib/commands/banner.js +43 -21
  15. package/lib/commands/build.js +26 -17
  16. package/lib/commands/cloud.js +780 -0
  17. package/lib/commands/doctor.js +98 -79
  18. package/lib/commands/exec.js +434 -0
  19. package/lib/commands/generate.js +19 -16
  20. package/lib/commands/github.js +475 -0
  21. package/lib/commands/init.js +52 -56
  22. package/lib/commands/scaffold.js +151 -0
  23. package/lib/commands/search.js +477 -0
  24. package/lib/commands/serve.js +15 -13
  25. package/lib/commands/state.js +43 -70
  26. package/lib/commands/swarm.js +31 -9
  27. package/lib/config/theme.js +47 -0
  28. package/lib/mcp/client.js +502 -0
  29. package/lib/providers/agent-sdk.js +630 -0
  30. package/lib/providers/anthropic-agents.js +580 -0
  31. package/lib/templates/code/clerk-middleware.ts +138 -0
  32. package/lib/templates/code/prisma-schema.prisma +224 -0
  33. package/lib/templates/code/rls-policies.sql +246 -0
  34. package/lib/templates/code/server-actions.ts +191 -0
  35. package/lib/templates/code/trpc-router.ts +258 -0
  36. package/lib/themes/doomsday.js +229 -0
  37. package/lib/ui/index.js +5 -0
  38. package/lib/ui/interface.js +241 -0
  39. package/lib/ui/spinners.js +116 -0
  40. package/lib/ui/theme.js +183 -0
  41. package/lib/utils/agents.js +32 -0
  42. package/lib/utils/browser.js +373 -0
  43. package/lib/utils/help.js +64 -0
  44. package/lib/utils/messages.js +35 -0
  45. package/lib/utils/progress.js +24 -0
  46. package/lib/utils/prompts.js +47 -0
  47. package/lib/utils/spinners.js +46 -0
  48. package/lib/utils/status.js +31 -0
  49. package/lib/utils/tables.js +41 -0
  50. package/lib/utils/theme-state.js +9 -0
  51. package/lib/utils/version-display.js +32 -0
  52. package/package.json +19 -4
@@ -0,0 +1,224 @@
1
+ // Ultra-Dex Production Pattern: Full Prisma Schema
2
+ // Copy to prisma/schema.prisma
3
+
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "postgresql"
10
+ url = env("DATABASE_URL")
11
+ }
12
+
13
+ // =============================================================================
14
+ // CORE MODELS
15
+ // =============================================================================
16
+
17
+ model User {
18
+ id String @id @default(cuid())
19
+ clerkId String @unique
20
+ email String @unique
21
+ name String?
22
+ imageUrl String?
23
+ role UserRole @default(USER)
24
+ createdAt DateTime @default(now())
25
+ updatedAt DateTime @updatedAt
26
+
27
+ // Relations
28
+ organizationMemberships OrganizationMember[]
29
+ createdOrganizations Organization[] @relation("OrganizationCreator")
30
+ projects Project[]
31
+ comments Comment[]
32
+ activityLogs ActivityLog[]
33
+
34
+ @@index([clerkId])
35
+ @@index([email])
36
+ @@map("users")
37
+ }
38
+
39
+ enum UserRole {
40
+ USER
41
+ ADMIN
42
+ SUPER_ADMIN
43
+ }
44
+
45
+ // =============================================================================
46
+ // MULTI-TENANCY MODELS
47
+ // =============================================================================
48
+
49
+ model Organization {
50
+ id String @id @default(cuid())
51
+ name String
52
+ slug String @unique
53
+ logo String?
54
+ plan Plan @default(FREE)
55
+ createdAt DateTime @default(now())
56
+ updatedAt DateTime @updatedAt
57
+
58
+ // Relations
59
+ creatorId String
60
+ creator User @relation("OrganizationCreator", fields: [creatorId], references: [id])
61
+ members OrganizationMember[]
62
+ projects Project[]
63
+ invoices Invoice[]
64
+
65
+ @@index([slug])
66
+ @@map("organizations")
67
+ }
68
+
69
+ model OrganizationMember {
70
+ id String @id @default(cuid())
71
+ role MemberRole @default(MEMBER)
72
+ joinedAt DateTime @default(now())
73
+ organizationId String
74
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
75
+ userId String
76
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
77
+
78
+ @@unique([organizationId, userId])
79
+ @@map("organization_members")
80
+ }
81
+
82
+ enum MemberRole {
83
+ OWNER
84
+ ADMIN
85
+ MEMBER
86
+ VIEWER
87
+ }
88
+
89
+ enum Plan {
90
+ FREE
91
+ PRO
92
+ ENTERPRISE
93
+ }
94
+
95
+ // =============================================================================
96
+ // PROJECT MODELS
97
+ // =============================================================================
98
+
99
+ model Project {
100
+ id String @id @default(cuid())
101
+ name String
102
+ description String?
103
+ status ProjectStatus @default(ACTIVE)
104
+ createdAt DateTime @default(now())
105
+ updatedAt DateTime @updatedAt
106
+
107
+ // Relations
108
+ organizationId String
109
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
110
+ ownerId String
111
+ owner User @relation(fields: [ownerId], references: [id])
112
+ tasks Task[]
113
+ comments Comment[]
114
+
115
+ @@index([organizationId])
116
+ @@map("projects")
117
+ }
118
+
119
+ enum ProjectStatus {
120
+ ACTIVE
121
+ ARCHIVED
122
+ COMPLETED
123
+ }
124
+
125
+ model Task {
126
+ id String @id @default(cuid())
127
+ title String
128
+ description String?
129
+ status TaskStatus @default(TODO)
130
+ priority Priority @default(MEDIUM)
131
+ dueDate DateTime?
132
+ createdAt DateTime @default(now())
133
+ updatedAt DateTime @updatedAt
134
+
135
+ // Relations
136
+ projectId String
137
+ project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
138
+ comments Comment[]
139
+
140
+ @@index([projectId])
141
+ @@index([status])
142
+ @@map("tasks")
143
+ }
144
+
145
+ enum TaskStatus {
146
+ TODO
147
+ IN_PROGRESS
148
+ IN_REVIEW
149
+ DONE
150
+ }
151
+
152
+ enum Priority {
153
+ LOW
154
+ MEDIUM
155
+ HIGH
156
+ URGENT
157
+ }
158
+
159
+ model Comment {
160
+ id String @id @default(cuid())
161
+ content String
162
+ createdAt DateTime @default(now())
163
+ updatedAt DateTime @updatedAt
164
+
165
+ // Relations
166
+ authorId String
167
+ author User @relation(fields: [authorId], references: [id])
168
+ projectId String?
169
+ project Project? @relation(fields: [projectId], references: [id], onDelete: Cascade)
170
+ taskId String?
171
+ task Task? @relation(fields: [taskId], references: [id], onDelete: Cascade)
172
+
173
+ @@map("comments")
174
+ }
175
+
176
+ // =============================================================================
177
+ // BILLING MODELS
178
+ // =============================================================================
179
+
180
+ model Invoice {
181
+ id String @id @default(cuid())
182
+ stripeId String @unique
183
+ amount Int
184
+ currency String @default("usd")
185
+ status InvoiceStatus @default(PENDING)
186
+ paidAt DateTime?
187
+ createdAt DateTime @default(now())
188
+
189
+ // Relations
190
+ organizationId String
191
+ organization Organization @relation(fields: [organizationId], references: [id])
192
+
193
+ @@index([organizationId])
194
+ @@map("invoices")
195
+ }
196
+
197
+ enum InvoiceStatus {
198
+ PENDING
199
+ PAID
200
+ FAILED
201
+ REFUNDED
202
+ }
203
+
204
+ // =============================================================================
205
+ // AUDIT LOG
206
+ // =============================================================================
207
+
208
+ model ActivityLog {
209
+ id String @id @default(cuid())
210
+ action String
211
+ entity String
212
+ entityId String
213
+ metadata Json?
214
+ createdAt DateTime @default(now())
215
+
216
+ // Relations
217
+ userId String
218
+ user User @relation(fields: [userId], references: [id])
219
+
220
+ @@index([userId])
221
+ @@index([entity, entityId])
222
+ @@index([createdAt])
223
+ @@map("activity_logs")
224
+ }
@@ -0,0 +1,246 @@
1
+ -- Ultra-Dex Production Pattern: Row-Level Security Policies
2
+ -- Run after Prisma migrations to add RLS
3
+
4
+ -- =============================================================================
5
+ -- ENABLE RLS ON TABLES
6
+ -- =============================================================================
7
+
8
+ ALTER TABLE users ENABLE ROW LEVEL SECURITY;
9
+ ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
10
+ ALTER TABLE organization_members ENABLE ROW LEVEL SECURITY;
11
+ ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
12
+ ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
13
+ ALTER TABLE comments ENABLE ROW LEVEL SECURITY;
14
+ ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
15
+ ALTER TABLE activity_logs ENABLE ROW LEVEL SECURITY;
16
+
17
+ -- =============================================================================
18
+ -- HELPER FUNCTIONS
19
+ -- =============================================================================
20
+
21
+ -- Get current user's clerk ID from session
22
+ CREATE OR REPLACE FUNCTION auth.clerk_id()
23
+ RETURNS TEXT AS $$
24
+ SELECT current_setting('app.clerk_id', true)::TEXT;
25
+ $$ LANGUAGE SQL STABLE;
26
+
27
+ -- Get current user's internal ID
28
+ CREATE OR REPLACE FUNCTION auth.user_id()
29
+ RETURNS TEXT AS $$
30
+ SELECT id FROM users WHERE clerk_id = auth.clerk_id();
31
+ $$ LANGUAGE SQL STABLE;
32
+
33
+ -- Check if user is member of organization
34
+ CREATE OR REPLACE FUNCTION auth.is_org_member(org_id TEXT)
35
+ RETURNS BOOLEAN AS $$
36
+ SELECT EXISTS (
37
+ SELECT 1 FROM organization_members om
38
+ JOIN users u ON u.id = om.user_id
39
+ WHERE om.organization_id = org_id
40
+ AND u.clerk_id = auth.clerk_id()
41
+ );
42
+ $$ LANGUAGE SQL STABLE;
43
+
44
+ -- Check if user is admin of organization
45
+ CREATE OR REPLACE FUNCTION auth.is_org_admin(org_id TEXT)
46
+ RETURNS BOOLEAN AS $$
47
+ SELECT EXISTS (
48
+ SELECT 1 FROM organization_members om
49
+ JOIN users u ON u.id = om.user_id
50
+ WHERE om.organization_id = org_id
51
+ AND u.clerk_id = auth.clerk_id()
52
+ AND om.role IN ('OWNER', 'ADMIN')
53
+ );
54
+ $$ LANGUAGE SQL STABLE;
55
+
56
+ -- =============================================================================
57
+ -- USERS POLICIES
58
+ -- =============================================================================
59
+
60
+ -- Users can read their own data
61
+ CREATE POLICY "users_select_own" ON users
62
+ FOR SELECT
63
+ USING (clerk_id = auth.clerk_id());
64
+
65
+ -- Users can update their own data
66
+ CREATE POLICY "users_update_own" ON users
67
+ FOR UPDATE
68
+ USING (clerk_id = auth.clerk_id());
69
+
70
+ -- Admins can read all users
71
+ CREATE POLICY "users_select_admin" ON users
72
+ FOR SELECT
73
+ USING (
74
+ EXISTS (
75
+ SELECT 1 FROM users u
76
+ WHERE u.clerk_id = auth.clerk_id()
77
+ AND u.role IN ('ADMIN', 'SUPER_ADMIN')
78
+ )
79
+ );
80
+
81
+ -- =============================================================================
82
+ -- ORGANIZATIONS POLICIES
83
+ -- =============================================================================
84
+
85
+ -- Members can view their organizations
86
+ CREATE POLICY "organizations_select_members" ON organizations
87
+ FOR SELECT
88
+ USING (auth.is_org_member(id));
89
+
90
+ -- Only admins can update organizations
91
+ CREATE POLICY "organizations_update_admin" ON organizations
92
+ FOR UPDATE
93
+ USING (auth.is_org_admin(id));
94
+
95
+ -- Only owners can delete organizations
96
+ CREATE POLICY "organizations_delete_owner" ON organizations
97
+ FOR DELETE
98
+ USING (
99
+ EXISTS (
100
+ SELECT 1 FROM organization_members om
101
+ JOIN users u ON u.id = om.user_id
102
+ WHERE om.organization_id = organizations.id
103
+ AND u.clerk_id = auth.clerk_id()
104
+ AND om.role = 'OWNER'
105
+ )
106
+ );
107
+
108
+ -- =============================================================================
109
+ -- ORGANIZATION MEMBERS POLICIES
110
+ -- =============================================================================
111
+
112
+ -- Members can view org membership
113
+ CREATE POLICY "org_members_select" ON organization_members
114
+ FOR SELECT
115
+ USING (auth.is_org_member(organization_id));
116
+
117
+ -- Only admins can add/remove members
118
+ CREATE POLICY "org_members_insert" ON organization_members
119
+ FOR INSERT
120
+ WITH CHECK (auth.is_org_admin(organization_id));
121
+
122
+ CREATE POLICY "org_members_delete" ON organization_members
123
+ FOR DELETE
124
+ USING (auth.is_org_admin(organization_id));
125
+
126
+ -- =============================================================================
127
+ -- PROJECTS POLICIES
128
+ -- =============================================================================
129
+
130
+ -- Members can view projects
131
+ CREATE POLICY "projects_select" ON projects
132
+ FOR SELECT
133
+ USING (auth.is_org_member(organization_id));
134
+
135
+ -- Members (non-viewers) can create projects
136
+ CREATE POLICY "projects_insert" ON projects
137
+ FOR INSERT
138
+ WITH CHECK (
139
+ EXISTS (
140
+ SELECT 1 FROM organization_members om
141
+ JOIN users u ON u.id = om.user_id
142
+ WHERE om.organization_id = projects.organization_id
143
+ AND u.clerk_id = auth.clerk_id()
144
+ AND om.role IN ('OWNER', 'ADMIN', 'MEMBER')
145
+ )
146
+ );
147
+
148
+ -- Members can update projects they own, admins can update any
149
+ CREATE POLICY "projects_update" ON projects
150
+ FOR UPDATE
151
+ USING (
152
+ (owner_id = auth.user_id()) OR auth.is_org_admin(organization_id)
153
+ );
154
+
155
+ -- Only admins can delete projects
156
+ CREATE POLICY "projects_delete" ON projects
157
+ FOR DELETE
158
+ USING (auth.is_org_admin(organization_id));
159
+
160
+ -- =============================================================================
161
+ -- TASKS POLICIES
162
+ -- =============================================================================
163
+
164
+ -- Inherit from project access
165
+ CREATE POLICY "tasks_select" ON tasks
166
+ FOR SELECT
167
+ USING (
168
+ EXISTS (
169
+ SELECT 1 FROM projects p
170
+ WHERE p.id = tasks.project_id
171
+ AND auth.is_org_member(p.organization_id)
172
+ )
173
+ );
174
+
175
+ CREATE POLICY "tasks_insert" ON tasks
176
+ FOR INSERT
177
+ WITH CHECK (
178
+ EXISTS (
179
+ SELECT 1 FROM projects p
180
+ JOIN organization_members om ON om.organization_id = p.organization_id
181
+ JOIN users u ON u.id = om.user_id
182
+ WHERE p.id = tasks.project_id
183
+ AND u.clerk_id = auth.clerk_id()
184
+ AND om.role IN ('OWNER', 'ADMIN', 'MEMBER')
185
+ )
186
+ );
187
+
188
+ CREATE POLICY "tasks_update" ON tasks
189
+ FOR UPDATE
190
+ USING (
191
+ EXISTS (
192
+ SELECT 1 FROM projects p
193
+ WHERE p.id = tasks.project_id
194
+ AND auth.is_org_member(p.organization_id)
195
+ )
196
+ );
197
+
198
+ CREATE POLICY "tasks_delete" ON tasks
199
+ FOR DELETE
200
+ USING (
201
+ EXISTS (
202
+ SELECT 1 FROM projects p
203
+ WHERE p.id = tasks.project_id
204
+ AND auth.is_org_admin(p.organization_id)
205
+ )
206
+ );
207
+
208
+ -- =============================================================================
209
+ -- INVOICES POLICIES
210
+ -- =============================================================================
211
+
212
+ -- Only org admins can view invoices
213
+ CREATE POLICY "invoices_select" ON invoices
214
+ FOR SELECT
215
+ USING (auth.is_org_admin(organization_id));
216
+
217
+ -- =============================================================================
218
+ -- ACTIVITY LOGS POLICIES
219
+ -- =============================================================================
220
+
221
+ -- Users can view their own activity
222
+ CREATE POLICY "activity_logs_select_own" ON activity_logs
223
+ FOR SELECT
224
+ USING (user_id = auth.user_id());
225
+
226
+ -- Admins can view all activity in their orgs
227
+ -- (would need to add org_id to activity_logs for this)
228
+
229
+ -- =============================================================================
230
+ -- USAGE: Set session context before queries
231
+ -- =============================================================================
232
+
233
+ /*
234
+ -- In your API routes or middleware, set the clerk_id:
235
+
236
+ -- For Prisma with raw queries:
237
+ await prisma.$executeRaw`SELECT set_config('app.clerk_id', ${clerkId}, true)`;
238
+
239
+ -- Then all subsequent queries will respect RLS policies
240
+
241
+ -- For Supabase:
242
+ const { data } = await supabase
243
+ .from('projects')
244
+ .select('*')
245
+ // RLS automatically applied based on auth.uid()
246
+ */
@@ -0,0 +1,191 @@
1
+ // Ultra-Dex Production Pattern: Next.js 15 Server Actions
2
+ // Copy this file to your app/actions/ directory
3
+
4
+ 'use server';
5
+
6
+ import { z } from 'zod';
7
+ import { revalidatePath } from 'next/cache';
8
+ import { redirect } from 'next/navigation';
9
+ import { auth } from '@clerk/nextjs/server';
10
+ import { prisma } from '@/lib/prisma';
11
+
12
+ // =============================================================================
13
+ // SCHEMA DEFINITIONS
14
+ // =============================================================================
15
+
16
+ const CreateUserSchema = z.object({
17
+ email: z.string().email('Invalid email address'),
18
+ name: z.string().min(2, 'Name must be at least 2 characters'),
19
+ role: z.enum(['USER', 'ADMIN']).default('USER'),
20
+ });
21
+
22
+ const UpdateUserSchema = z.object({
23
+ id: z.string().uuid(),
24
+ name: z.string().min(2).optional(),
25
+ email: z.string().email().optional(),
26
+ });
27
+
28
+ // =============================================================================
29
+ // ACTION RESPONSE TYPE
30
+ // =============================================================================
31
+
32
+ type ActionResponse<T = void> =
33
+ | { success: true; data: T }
34
+ | { success: false; error: string; fieldErrors?: Record<string, string[]> };
35
+
36
+ // =============================================================================
37
+ // USER ACTIONS
38
+ // =============================================================================
39
+
40
+ export async function createUser(
41
+ prevState: ActionResponse<{ id: string }> | null,
42
+ formData: FormData
43
+ ): Promise<ActionResponse<{ id: string }>> {
44
+ const { userId } = await auth();
45
+
46
+ if (!userId) {
47
+ return { success: false, error: 'Unauthorized' };
48
+ }
49
+
50
+ const rawData = {
51
+ email: formData.get('email'),
52
+ name: formData.get('name'),
53
+ role: formData.get('role') || 'USER',
54
+ };
55
+
56
+ const validated = CreateUserSchema.safeParse(rawData);
57
+
58
+ if (!validated.success) {
59
+ return {
60
+ success: false,
61
+ error: 'Validation failed',
62
+ fieldErrors: validated.error.flatten().fieldErrors,
63
+ };
64
+ }
65
+
66
+ try {
67
+ const user = await prisma.user.create({
68
+ data: validated.data,
69
+ });
70
+
71
+ console.log(JSON.stringify({
72
+ level: 'info',
73
+ event: 'user_created',
74
+ userId: user.id,
75
+ createdBy: userId,
76
+ }));
77
+
78
+ revalidatePath('/users');
79
+ return { success: true, data: { id: user.id } };
80
+ } catch (error) {
81
+ console.error('Failed to create user:', error);
82
+ return { success: false, error: 'Failed to create user' };
83
+ }
84
+ }
85
+
86
+ export async function updateUser(
87
+ prevState: ActionResponse | null,
88
+ formData: FormData
89
+ ): Promise<ActionResponse> {
90
+ const { userId } = await auth();
91
+
92
+ if (!userId) {
93
+ return { success: false, error: 'Unauthorized' };
94
+ }
95
+
96
+ const rawData = {
97
+ id: formData.get('id'),
98
+ name: formData.get('name'),
99
+ email: formData.get('email'),
100
+ };
101
+
102
+ const validated = UpdateUserSchema.safeParse(rawData);
103
+
104
+ if (!validated.success) {
105
+ return {
106
+ success: false,
107
+ error: 'Validation failed',
108
+ fieldErrors: validated.error.flatten().fieldErrors,
109
+ };
110
+ }
111
+
112
+ try {
113
+ await prisma.user.update({
114
+ where: { id: validated.data.id },
115
+ data: {
116
+ ...(validated.data.name && { name: validated.data.name }),
117
+ ...(validated.data.email && { email: validated.data.email }),
118
+ },
119
+ });
120
+
121
+ revalidatePath('/users');
122
+ revalidatePath(`/users/${validated.data.id}`);
123
+ return { success: true, data: undefined };
124
+ } catch (error) {
125
+ console.error('Failed to update user:', error);
126
+ return { success: false, error: 'Failed to update user' };
127
+ }
128
+ }
129
+
130
+ export async function deleteUser(id: string): Promise<ActionResponse> {
131
+ const { userId } = await auth();
132
+
133
+ if (!userId) {
134
+ return { success: false, error: 'Unauthorized' };
135
+ }
136
+
137
+ try {
138
+ await prisma.user.delete({
139
+ where: { id },
140
+ });
141
+
142
+ console.log(JSON.stringify({
143
+ level: 'info',
144
+ event: 'user_deleted',
145
+ deletedUserId: id,
146
+ deletedBy: userId,
147
+ }));
148
+
149
+ revalidatePath('/users');
150
+ return { success: true, data: undefined };
151
+ } catch (error) {
152
+ console.error('Failed to delete user:', error);
153
+ return { success: false, error: 'Failed to delete user' };
154
+ }
155
+ }
156
+
157
+ // =============================================================================
158
+ // USAGE IN COMPONENTS
159
+ // =============================================================================
160
+
161
+ /*
162
+ // In a Client Component:
163
+
164
+ 'use client';
165
+
166
+ import { useActionState } from 'react';
167
+ import { createUser } from '@/app/actions/user';
168
+
169
+ export function CreateUserForm() {
170
+ const [state, formAction, isPending] = useActionState(createUser, null);
171
+
172
+ return (
173
+ <form action={formAction}>
174
+ <input name="name" placeholder="Name" required />
175
+ <input name="email" type="email" placeholder="Email" required />
176
+
177
+ {state?.error && (
178
+ <p className="text-red-500">{state.error}</p>
179
+ )}
180
+
181
+ {state?.fieldErrors?.email && (
182
+ <p className="text-red-500">{state.fieldErrors.email[0]}</p>
183
+ )}
184
+
185
+ <button type="submit" disabled={isPending}>
186
+ {isPending ? 'Creating...' : 'Create User'}
187
+ </button>
188
+ </form>
189
+ );
190
+ }
191
+ */