mycontext-cli 4.2.6 → 4.2.8
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 +531 -144
- package/dist/README.md +531 -144
- package/dist/cli.js +14 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +19 -47
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init-interactive.d.ts +147 -0
- package/dist/commands/init-interactive.d.ts.map +1 -0
- package/dist/commands/init-interactive.js +917 -0
- package/dist/commands/init-interactive.js.map +1 -0
- package/dist/config/shadcn-catalog.json +93 -0
- package/dist/core/brain/BrainClient.d.ts +1 -1
- package/dist/core/brain/BrainClient.d.ts.map +1 -1
- package/dist/core/brain/BrainClient.js +5 -5
- package/dist/core/brain/BrainClient.js.map +1 -1
- package/dist/doctor/DoctorEngine.d.ts.map +1 -1
- package/dist/doctor/DoctorEngine.js +21 -11
- package/dist/doctor/DoctorEngine.js.map +1 -1
- package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -1
- package/dist/doctor/rules/dead-code-rules.js +33 -0
- package/dist/doctor/rules/dead-code-rules.js.map +1 -1
- package/dist/doctor/rules/index.d.ts +3 -1
- package/dist/doctor/rules/index.d.ts.map +1 -1
- package/dist/doctor/rules/index.js +7 -1
- package/dist/doctor/rules/index.js.map +1 -1
- package/dist/doctor/rules/instantdb-rules.d.ts +3 -0
- package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -0
- package/dist/doctor/rules/instantdb-rules.js +544 -0
- package/dist/doctor/rules/instantdb-rules.js.map +1 -0
- package/dist/doctor/rules/nextjs-rules.d.ts.map +1 -1
- package/dist/doctor/rules/nextjs-rules.js +53 -3
- package/dist/doctor/rules/nextjs-rules.js.map +1 -1
- package/dist/doctor/rules/typescript-rules.d.ts +3 -0
- package/dist/doctor/rules/typescript-rules.d.ts.map +1 -0
- package/dist/doctor/rules/typescript-rules.js +177 -0
- package/dist/doctor/rules/typescript-rules.js.map +1 -0
- package/dist/package.json +4 -2
- package/dist/services/ComponentInferenceEngine.d.ts +66 -0
- package/dist/services/ComponentInferenceEngine.d.ts.map +1 -0
- package/dist/services/ComponentInferenceEngine.js +302 -0
- package/dist/services/ComponentInferenceEngine.js.map +1 -0
- package/dist/services/ComponentRegistry.d.ts +61 -0
- package/dist/services/ComponentRegistry.d.ts.map +1 -0
- package/dist/services/ComponentRegistry.js +128 -0
- package/dist/services/ComponentRegistry.js.map +1 -0
- package/dist/services/InferenceEngine.d.ts +41 -0
- package/dist/services/InferenceEngine.d.ts.map +1 -0
- package/dist/services/InferenceEngine.js +307 -0
- package/dist/services/InferenceEngine.js.map +1 -0
- package/dist/services/Planner.d.ts +77 -0
- package/dist/services/Planner.d.ts.map +1 -0
- package/dist/services/Planner.js +828 -0
- package/dist/services/Planner.js.map +1 -0
- package/dist/services/ProjectScanner.d.ts.map +1 -1
- package/dist/services/ProjectScanner.js +15 -1
- package/dist/services/ProjectScanner.js.map +1 -1
- package/dist/services/ScaffoldEngine.d.ts +87 -0
- package/dist/services/ScaffoldEngine.d.ts.map +1 -0
- package/dist/services/ScaffoldEngine.js +409 -0
- package/dist/services/ScaffoldEngine.js.map +1 -0
- package/dist/services/ScaffoldPreview.d.ts +62 -0
- package/dist/services/ScaffoldPreview.d.ts.map +1 -0
- package/dist/services/ScaffoldPreview.js +292 -0
- package/dist/services/ScaffoldPreview.js.map +1 -0
- package/dist/services/TemplateEngine.d.ts +136 -0
- package/dist/services/TemplateEngine.d.ts.map +1 -0
- package/dist/services/TemplateEngine.js +483 -0
- package/dist/services/TemplateEngine.js.map +1 -0
- package/dist/services/TemplateHelpers.d.ts +9 -0
- package/dist/services/TemplateHelpers.d.ts.map +1 -0
- package/dist/services/TemplateHelpers.js +212 -0
- package/dist/services/TemplateHelpers.js.map +1 -0
- package/dist/templates/actions/auth-actions.ts.hbs +140 -0
- package/dist/templates/actions/crud-actions.ts.hbs +113 -0
- package/dist/templates/components/auth/login-form.tsx.hbs +67 -0
- package/dist/templates/components/auth/login-skeleton.tsx.hbs +24 -0
- package/dist/templates/components/auth/register-form.tsx.hbs +116 -0
- package/dist/templates/components/crud/entity-card.tsx.hbs +71 -0
- package/dist/templates/components/crud/entity-form.tsx.hbs +158 -0
- package/dist/templates/components/crud/entity-skeleton.tsx.hbs +90 -0
- package/dist/templates/components/crud/entity-table.tsx.hbs +129 -0
- package/dist/templates/components/ui/button.tsx.hbs +53 -0
- package/dist/templates/components/ui/card.tsx.hbs +68 -0
- package/dist/templates/components/ui/input.tsx.hbs +33 -0
- package/dist/templates/components/ui/label.tsx.hbs +20 -0
- package/dist/templates/components/ui/skeleton.tsx.hbs +15 -0
- package/dist/templates/components/ui/theme-provider.tsx.hbs +66 -0
- package/dist/templates/components/ui/theme-toggle.tsx.hbs +30 -0
- package/dist/templates/config/app.css.hbs +150 -0
- package/dist/templates/layouts/dashboard-layout.tsx.hbs +69 -0
- package/dist/templates/layouts/error.tsx.hbs +51 -0
- package/dist/templates/layouts/loading.tsx.hbs +22 -0
- package/dist/templates/layouts/not-found.tsx.hbs +24 -0
- package/dist/templates/layouts/root-layout.tsx.hbs +40 -0
- package/dist/templates/lib/instant.ts.hbs +19 -0
- package/dist/templates/lib/utils.ts.hbs +24 -0
- package/dist/templates/pages/auth/login-page.tsx.hbs +30 -0
- package/dist/templates/pages/auth/register-page.tsx.hbs +38 -0
- package/dist/templates/pages/crud/create-page.tsx.hbs +42 -0
- package/dist/templates/pages/crud/detail-page.tsx.hbs +90 -0
- package/dist/templates/pages/crud/list-page.tsx.hbs +60 -0
- package/dist/templates/pages/crud/loading.tsx.hbs +13 -0
- package/dist/templates/pages/landing-page.tsx.hbs +111 -0
- package/dist/types/asl.d.ts +387 -0
- package/dist/types/asl.d.ts.map +1 -0
- package/dist/types/asl.js +139 -0
- package/dist/types/asl.js.map +1 -0
- package/dist/types/living-context.d.ts +1 -1
- package/dist/types/living-context.d.ts.map +1 -1
- package/dist/utils/FileGenerator.js +3 -3
- package/dist/utils/FileGenerator.js.map +1 -1
- package/dist/utils/generateTypesFromSchema.d.ts +47 -0
- package/dist/utils/generateTypesFromSchema.d.ts.map +1 -0
- package/dist/utils/generateTypesFromSchema.js +298 -0
- package/dist/utils/generateTypesFromSchema.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.TemplateHelpers = void 0;
|
|
40
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
41
|
+
const fs = __importStar(require("fs-extra"));
|
|
42
|
+
class TemplateHelpers {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.handlebars = handlebars_1.default.create();
|
|
45
|
+
this.registerHelpers();
|
|
46
|
+
}
|
|
47
|
+
registerHelpers() {
|
|
48
|
+
// String transformation helpers
|
|
49
|
+
this.handlebars.registerHelper('lowercase', (str) => {
|
|
50
|
+
if (!str)
|
|
51
|
+
return '';
|
|
52
|
+
return str.toLowerCase();
|
|
53
|
+
});
|
|
54
|
+
this.handlebars.registerHelper('uppercase', (str) => {
|
|
55
|
+
if (!str)
|
|
56
|
+
return '';
|
|
57
|
+
return str.toUpperCase();
|
|
58
|
+
});
|
|
59
|
+
this.handlebars.registerHelper('capitalize', (str) => {
|
|
60
|
+
if (!str)
|
|
61
|
+
return '';
|
|
62
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
63
|
+
});
|
|
64
|
+
this.handlebars.registerHelper('camelCase', (str) => {
|
|
65
|
+
if (!str)
|
|
66
|
+
return '';
|
|
67
|
+
return str.replace(/-([a-z])/g, (g) => (g[1] ? g[1].toUpperCase() : ''));
|
|
68
|
+
});
|
|
69
|
+
this.handlebars.registerHelper('pascalCase', (str) => {
|
|
70
|
+
if (!str)
|
|
71
|
+
return '';
|
|
72
|
+
const camel = str.replace(/-([a-z])/g, (g) => (g[1] ? g[1].toUpperCase() : ''));
|
|
73
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
74
|
+
});
|
|
75
|
+
// Type mapping helpers for Zod
|
|
76
|
+
this.handlebars.registerHelper('zodType', (aslType) => {
|
|
77
|
+
const typeMap = {
|
|
78
|
+
string: 'string()',
|
|
79
|
+
text: 'string()',
|
|
80
|
+
number: 'number()',
|
|
81
|
+
boolean: 'boolean()',
|
|
82
|
+
date: 'date()',
|
|
83
|
+
json: 'any()',
|
|
84
|
+
ref: 'string()',
|
|
85
|
+
email: 'string().email()',
|
|
86
|
+
};
|
|
87
|
+
return typeMap[aslType] || 'string()';
|
|
88
|
+
});
|
|
89
|
+
// Type mapping helpers for TypeScript
|
|
90
|
+
this.handlebars.registerHelper('tsType', (aslType) => {
|
|
91
|
+
const typeMap = {
|
|
92
|
+
string: 'string',
|
|
93
|
+
text: 'string',
|
|
94
|
+
number: 'number',
|
|
95
|
+
boolean: 'boolean',
|
|
96
|
+
date: 'Date',
|
|
97
|
+
json: 'any',
|
|
98
|
+
ref: 'string',
|
|
99
|
+
email: 'string',
|
|
100
|
+
};
|
|
101
|
+
return typeMap[aslType] || 'string';
|
|
102
|
+
});
|
|
103
|
+
// Comparison helpers
|
|
104
|
+
this.handlebars.registerHelper('eq', (a, b) => {
|
|
105
|
+
return a === b;
|
|
106
|
+
});
|
|
107
|
+
this.handlebars.registerHelper('neq', (a, b) => {
|
|
108
|
+
return a !== b;
|
|
109
|
+
});
|
|
110
|
+
this.handlebars.registerHelper('gt', (a, b) => {
|
|
111
|
+
return a > b;
|
|
112
|
+
});
|
|
113
|
+
this.handlebars.registerHelper('lt', (a, b) => {
|
|
114
|
+
return a < b;
|
|
115
|
+
});
|
|
116
|
+
// Logical helpers
|
|
117
|
+
this.handlebars.registerHelper('and', (...args) => {
|
|
118
|
+
// Remove the Handlebars options object (last argument)
|
|
119
|
+
const values = args.slice(0, -1);
|
|
120
|
+
return values.every(Boolean);
|
|
121
|
+
});
|
|
122
|
+
this.handlebars.registerHelper('or', (...args) => {
|
|
123
|
+
// Remove the Handlebars options object (last argument)
|
|
124
|
+
const values = args.slice(0, -1);
|
|
125
|
+
return values.some(Boolean);
|
|
126
|
+
});
|
|
127
|
+
this.handlebars.registerHelper('not', (value) => {
|
|
128
|
+
return !value;
|
|
129
|
+
});
|
|
130
|
+
// Array/iteration helpers
|
|
131
|
+
this.handlebars.registerHelper('join', (arr, separator = ', ') => {
|
|
132
|
+
if (!Array.isArray(arr))
|
|
133
|
+
return '';
|
|
134
|
+
return arr.join(separator);
|
|
135
|
+
});
|
|
136
|
+
this.handlebars.registerHelper('length', (arr) => {
|
|
137
|
+
if (!Array.isArray(arr))
|
|
138
|
+
return 0;
|
|
139
|
+
return arr.length;
|
|
140
|
+
});
|
|
141
|
+
// Index helpers for iterations
|
|
142
|
+
this.handlebars.registerHelper('isFirst', function (index) {
|
|
143
|
+
return index === 0;
|
|
144
|
+
});
|
|
145
|
+
this.handlebars.registerHelper('isLast', function (index, array) {
|
|
146
|
+
return index === array.length - 1;
|
|
147
|
+
});
|
|
148
|
+
// JSON helpers
|
|
149
|
+
this.handlebars.registerHelper('json', (context) => {
|
|
150
|
+
return JSON.stringify(context, null, 2);
|
|
151
|
+
});
|
|
152
|
+
// Conditional helpers for field types
|
|
153
|
+
this.handlebars.registerHelper('isTextField', (type) => {
|
|
154
|
+
return type === 'string' || type === 'text' || type === 'email';
|
|
155
|
+
});
|
|
156
|
+
this.handlebars.registerHelper('isNumberField', (type) => {
|
|
157
|
+
return type === 'number';
|
|
158
|
+
});
|
|
159
|
+
this.handlebars.registerHelper('isBooleanField', (type) => {
|
|
160
|
+
return type === 'boolean';
|
|
161
|
+
});
|
|
162
|
+
this.handlebars.registerHelper('isDateField', (type) => {
|
|
163
|
+
return type === 'date';
|
|
164
|
+
});
|
|
165
|
+
// Pluralization helper
|
|
166
|
+
this.handlebars.registerHelper('pluralize', (str) => {
|
|
167
|
+
if (!str)
|
|
168
|
+
return '';
|
|
169
|
+
// Simple pluralization logic
|
|
170
|
+
if (str.endsWith('y')) {
|
|
171
|
+
return str.slice(0, -1) + 'ies';
|
|
172
|
+
}
|
|
173
|
+
if (str.endsWith('s') || str.endsWith('sh') || str.endsWith('ch')) {
|
|
174
|
+
return str + 'es';
|
|
175
|
+
}
|
|
176
|
+
return str + 's';
|
|
177
|
+
});
|
|
178
|
+
// Singularize helper
|
|
179
|
+
this.handlebars.registerHelper('singularize', (str) => {
|
|
180
|
+
if (!str)
|
|
181
|
+
return '';
|
|
182
|
+
// Simple singularization logic
|
|
183
|
+
if (str.endsWith('ies')) {
|
|
184
|
+
return str.slice(0, -3) + 'y';
|
|
185
|
+
}
|
|
186
|
+
if (str.endsWith('es')) {
|
|
187
|
+
return str.slice(0, -2);
|
|
188
|
+
}
|
|
189
|
+
if (str.endsWith('s')) {
|
|
190
|
+
return str.slice(0, -1);
|
|
191
|
+
}
|
|
192
|
+
return str;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
async render(templatePath, data) {
|
|
196
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
197
|
+
const template = this.handlebars.compile(templateContent);
|
|
198
|
+
return template(data);
|
|
199
|
+
}
|
|
200
|
+
async renderFromString(templateString, data) {
|
|
201
|
+
const template = this.handlebars.compile(templateString);
|
|
202
|
+
return template(data);
|
|
203
|
+
}
|
|
204
|
+
// Helper method to get file extension from template path
|
|
205
|
+
getOutputExtension(templatePath) {
|
|
206
|
+
// Extract extension from .tsx.hbs or .ts.hbs or .css.hbs
|
|
207
|
+
const match = templatePath.match(/\.([^.]+)\.hbs$/);
|
|
208
|
+
return match ? `.${match[1]}` : '';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.TemplateHelpers = TemplateHelpers;
|
|
212
|
+
//# sourceMappingURL=TemplateHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateHelpers.js","sourceRoot":"","sources":["../../src/services/TemplateHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import { revalidatePath } from 'next/cache'
|
|
4
|
+
import { cookies } from 'next/headers'
|
|
5
|
+
import { db } from '@/lib/instant'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
const loginSchema = z.object({
|
|
9
|
+
email: z.string().email('Invalid email address'),
|
|
10
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const registerSchema = z.object({
|
|
14
|
+
name: z.string().min(1, 'Name is required'),
|
|
15
|
+
email: z.string().email('Invalid email address'),
|
|
16
|
+
password: z.string().min(8, 'Password must be at least 8 characters'),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export async function login(formData: FormData) {
|
|
20
|
+
try {
|
|
21
|
+
const validated = loginSchema.parse({
|
|
22
|
+
email: formData.get('email'),
|
|
23
|
+
password: formData.get('password'),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// Authenticate with InstantDB
|
|
27
|
+
const { user, error } = await db.auth.signInWithPassword({
|
|
28
|
+
email: validated.email,
|
|
29
|
+
password: validated.password,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
if (error) {
|
|
33
|
+
return { error: error.message || 'Invalid credentials' }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Store session in cookie
|
|
37
|
+
const cookieStore = await cookies()
|
|
38
|
+
cookieStore.set('session', user.id, {
|
|
39
|
+
httpOnly: true,
|
|
40
|
+
secure: process.env.NODE_ENV === 'production',
|
|
41
|
+
sameSite: 'lax',
|
|
42
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
revalidatePath('/')
|
|
46
|
+
return { success: true }
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof z.ZodError) {
|
|
49
|
+
return { error: error.errors[0].message }
|
|
50
|
+
}
|
|
51
|
+
return { error: 'An unexpected error occurred' }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function register(formData: FormData) {
|
|
56
|
+
try {
|
|
57
|
+
const validated = registerSchema.parse({
|
|
58
|
+
name: formData.get('name'),
|
|
59
|
+
email: formData.get('email'),
|
|
60
|
+
password: formData.get('password'),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Create account with InstantDB
|
|
64
|
+
const { user, error } = await db.auth.signUp({
|
|
65
|
+
email: validated.email,
|
|
66
|
+
password: validated.password,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (error) {
|
|
70
|
+
return { error: error.message || 'Failed to create account' }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Update user profile with name
|
|
74
|
+
await db.transact([
|
|
75
|
+
db.tx.users[user.id].update({
|
|
76
|
+
name: validated.name,
|
|
77
|
+
}),
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
// Store session in cookie
|
|
81
|
+
const cookieStore = await cookies()
|
|
82
|
+
cookieStore.set('session', user.id, {
|
|
83
|
+
httpOnly: true,
|
|
84
|
+
secure: process.env.NODE_ENV === 'production',
|
|
85
|
+
sameSite: 'lax',
|
|
86
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
revalidatePath('/')
|
|
90
|
+
return { success: true }
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof z.ZodError) {
|
|
93
|
+
return { error: error.errors[0].message }
|
|
94
|
+
}
|
|
95
|
+
return { error: 'An unexpected error occurred' }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function logout() {
|
|
100
|
+
try {
|
|
101
|
+
await db.auth.signOut()
|
|
102
|
+
|
|
103
|
+
const cookieStore = await cookies()
|
|
104
|
+
cookieStore.delete('session')
|
|
105
|
+
|
|
106
|
+
revalidatePath('/')
|
|
107
|
+
return { success: true }
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return { error: 'Failed to log out' }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function getCurrentUser() {
|
|
114
|
+
try {
|
|
115
|
+
const cookieStore = await cookies()
|
|
116
|
+
const sessionCookie = cookieStore.get('session')
|
|
117
|
+
|
|
118
|
+
if (!sessionCookie) {
|
|
119
|
+
return { user: null }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { data, error } = await db.query({
|
|
123
|
+
users: {
|
|
124
|
+
$: {
|
|
125
|
+
where: {
|
|
126
|
+
id: sessionCookie.value,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
if (error || !data.users?.[0]) {
|
|
133
|
+
return { user: null }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { user: data.users[0] }
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return { user: null }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import { revalidatePath } from 'next/cache'
|
|
4
|
+
import { db } from '@/lib/instant'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
const {{entityLower}}Schema = z.object({
|
|
8
|
+
{{#each fields}}
|
|
9
|
+
{{name}}: z.{{zodType type}}(){{#if required}}.min(1, '{{capitalize name}} is required'){{/if}},
|
|
10
|
+
{{/each}}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export async function create{{entityName}}(formData: FormData) {
|
|
14
|
+
try {
|
|
15
|
+
const validated = {{entityLower}}Schema.parse({
|
|
16
|
+
{{#each fields}}
|
|
17
|
+
{{name}}: formData.get('{{name}}'),
|
|
18
|
+
{{/each}}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const id = db.id()
|
|
22
|
+
|
|
23
|
+
await db.transact([
|
|
24
|
+
db.tx.{{entityName}}[id].update(validated)
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
revalidatePath('/{{entityLower}}s')
|
|
28
|
+
revalidatePath(`/{{entityLower}}s/${id}`)
|
|
29
|
+
|
|
30
|
+
return { success: true, id }
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof z.ZodError) {
|
|
33
|
+
return { success: false, error: error.errors[0].message }
|
|
34
|
+
}
|
|
35
|
+
return { success: false, error: 'Failed to create {{entityLower}}' }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function update{{entityName}}(id: string, formData: FormData) {
|
|
40
|
+
try {
|
|
41
|
+
const validated = {{entityLower}}Schema.parse({
|
|
42
|
+
{{#each fields}}
|
|
43
|
+
{{name}}: formData.get('{{name}}'),
|
|
44
|
+
{{/each}}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
await db.transact([
|
|
48
|
+
db.tx.{{entityName}}[id].update(validated)
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
revalidatePath('/{{entityLower}}s')
|
|
52
|
+
revalidatePath(`/{{entityLower}}s/${id}`)
|
|
53
|
+
|
|
54
|
+
return { success: true }
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof z.ZodError) {
|
|
57
|
+
return { success: false, error: error.errors[0].message }
|
|
58
|
+
}
|
|
59
|
+
return { success: false, error: 'Failed to update {{entityLower}}' }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function delete{{entityName}}(id: string) {
|
|
64
|
+
try {
|
|
65
|
+
await db.transact([
|
|
66
|
+
db.tx.{{entityName}}[id].delete()
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
revalidatePath('/{{entityLower}}s')
|
|
70
|
+
|
|
71
|
+
return { success: true }
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return { success: false, error: 'Failed to delete {{entityLower}}' }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function get{{entityName}}(id: string) {
|
|
78
|
+
try {
|
|
79
|
+
const { data, error } = await db.query({
|
|
80
|
+
{{entityLower}}s: {
|
|
81
|
+
$: {
|
|
82
|
+
where: {
|
|
83
|
+
id,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
if (error || !data.{{entityLower}}s?.[0]) {
|
|
90
|
+
return { {{entityLower}}: null, error: 'Not found' }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { {{entityLower}}: data.{{entityLower}}s[0] }
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return { {{entityLower}}: null, error: 'Failed to fetch {{entityLower}}' }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function list{{entityName}}s() {
|
|
100
|
+
try {
|
|
101
|
+
const { data, error } = await db.query({
|
|
102
|
+
{{entityLower}}s: {},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (error) {
|
|
106
|
+
return { {{entityLower}}s: [], error: error.message }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { {{entityLower}}s: data.{{entityLower}}s || [] }
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return { {{entityLower}}s: [], error: 'Failed to fetch {{entityLower}}s' }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useTransition } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Input } from '@/components/ui/input'
|
|
7
|
+
import { Label } from '@/components/ui/label'
|
|
8
|
+
import { login } from '@/app/actions/auth'
|
|
9
|
+
|
|
10
|
+
export function LoginForm() {
|
|
11
|
+
const router = useRouter()
|
|
12
|
+
const [isPending, startTransition] = useTransition()
|
|
13
|
+
const [error, setError] = useState<string | null>(null)
|
|
14
|
+
|
|
15
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
16
|
+
e.preventDefault()
|
|
17
|
+
setError(null)
|
|
18
|
+
|
|
19
|
+
const formData = new FormData(e.currentTarget)
|
|
20
|
+
|
|
21
|
+
startTransition(async () => {
|
|
22
|
+
const result = await login(formData)
|
|
23
|
+
if (result.error) {
|
|
24
|
+
setError(result.error)
|
|
25
|
+
} else {
|
|
26
|
+
router.push('/dashboard')
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
33
|
+
<div className="space-y-2">
|
|
34
|
+
<Label htmlFor="email">Email</Label>
|
|
35
|
+
<Input
|
|
36
|
+
id="email"
|
|
37
|
+
name="email"
|
|
38
|
+
type="email"
|
|
39
|
+
placeholder="you@example.com"
|
|
40
|
+
required
|
|
41
|
+
disabled={isPending}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div className="space-y-2">
|
|
46
|
+
<Label htmlFor="password">Password</Label>
|
|
47
|
+
<Input
|
|
48
|
+
id="password"
|
|
49
|
+
name="password"
|
|
50
|
+
type="password"
|
|
51
|
+
required
|
|
52
|
+
disabled={isPending}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{error && (
|
|
57
|
+
<p className="text-sm text-destructive" role="alert">
|
|
58
|
+
{error}
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
<Button type="submit" className="w-full" disabled={isPending}>
|
|
63
|
+
{isPending ? 'Signing in...' : 'Sign In'}
|
|
64
|
+
</Button>
|
|
65
|
+
</form>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
2
|
+
import { Card, CardContent, CardHeader } from '@/components/ui/card'
|
|
3
|
+
|
|
4
|
+
export function LoginSkeleton() {
|
|
5
|
+
return (
|
|
6
|
+
<Card>
|
|
7
|
+
<CardHeader>
|
|
8
|
+
<Skeleton className="h-8 w-3/4" />
|
|
9
|
+
<Skeleton className="h-4 w-full" />
|
|
10
|
+
</CardHeader>
|
|
11
|
+
<CardContent className="space-y-4">
|
|
12
|
+
<div className="space-y-2">
|
|
13
|
+
<Skeleton className="h-4 w-16" />
|
|
14
|
+
<Skeleton className="h-10 w-full" />
|
|
15
|
+
</div>
|
|
16
|
+
<div className="space-y-2">
|
|
17
|
+
<Skeleton className="h-4 w-20" />
|
|
18
|
+
<Skeleton className="h-10 w-full" />
|
|
19
|
+
</div>
|
|
20
|
+
<Skeleton className="h-10 w-full" />
|
|
21
|
+
</CardContent>
|
|
22
|
+
</Card>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useTransition } from 'react'
|
|
4
|
+
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Input } from '@/components/ui/input'
|
|
7
|
+
import { Label } from '@/components/ui/label'
|
|
8
|
+
import { register } from '@/app/actions/auth'
|
|
9
|
+
|
|
10
|
+
export function RegisterForm() {
|
|
11
|
+
const router = useRouter()
|
|
12
|
+
const [isPending, startTransition] = useTransition()
|
|
13
|
+
const [error, setError] = useState<string | null>(null)
|
|
14
|
+
|
|
15
|
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
16
|
+
e.preventDefault()
|
|
17
|
+
setError(null)
|
|
18
|
+
|
|
19
|
+
const formData = new FormData(e.currentTarget)
|
|
20
|
+
|
|
21
|
+
// Client-side password confirmation validation
|
|
22
|
+
const password = formData.get('password') as string
|
|
23
|
+
const confirmPassword = formData.get('confirmPassword') as string
|
|
24
|
+
|
|
25
|
+
if (password !== confirmPassword) {
|
|
26
|
+
setError('Passwords do not match')
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
startTransition(async () => {
|
|
31
|
+
const result = await register(formData)
|
|
32
|
+
if (result.error) {
|
|
33
|
+
setError(result.error)
|
|
34
|
+
} else {
|
|
35
|
+
router.push('/dashboard')
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
42
|
+
<div className="space-y-2">
|
|
43
|
+
<Label htmlFor="name" required>
|
|
44
|
+
Name
|
|
45
|
+
</Label>
|
|
46
|
+
<Input
|
|
47
|
+
id="name"
|
|
48
|
+
name="name"
|
|
49
|
+
type="text"
|
|
50
|
+
placeholder="John Doe"
|
|
51
|
+
required
|
|
52
|
+
disabled={isPending}
|
|
53
|
+
autoComplete="name"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="space-y-2">
|
|
58
|
+
<Label htmlFor="email" required>
|
|
59
|
+
Email
|
|
60
|
+
</Label>
|
|
61
|
+
<Input
|
|
62
|
+
id="email"
|
|
63
|
+
name="email"
|
|
64
|
+
type="email"
|
|
65
|
+
placeholder="you@example.com"
|
|
66
|
+
required
|
|
67
|
+
disabled={isPending}
|
|
68
|
+
autoComplete="email"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="space-y-2">
|
|
73
|
+
<Label htmlFor="password" required>
|
|
74
|
+
Password
|
|
75
|
+
</Label>
|
|
76
|
+
<Input
|
|
77
|
+
id="password"
|
|
78
|
+
name="password"
|
|
79
|
+
type="password"
|
|
80
|
+
required
|
|
81
|
+
disabled={isPending}
|
|
82
|
+
autoComplete="new-password"
|
|
83
|
+
minLength={8}
|
|
84
|
+
/>
|
|
85
|
+
<p className="text-xs text-muted-foreground">
|
|
86
|
+
Must be at least 8 characters
|
|
87
|
+
</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="space-y-2">
|
|
91
|
+
<Label htmlFor="confirmPassword" required>
|
|
92
|
+
Confirm Password
|
|
93
|
+
</Label>
|
|
94
|
+
<Input
|
|
95
|
+
id="confirmPassword"
|
|
96
|
+
name="confirmPassword"
|
|
97
|
+
type="password"
|
|
98
|
+
required
|
|
99
|
+
disabled={isPending}
|
|
100
|
+
autoComplete="new-password"
|
|
101
|
+
minLength={8}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{error && (
|
|
106
|
+
<p className="text-sm text-destructive" role="alert">
|
|
107
|
+
{error}
|
|
108
|
+
</p>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<Button type="submit" className="w-full" disabled={isPending}>
|
|
112
|
+
{isPending ? 'Creating account...' : 'Create Account'}
|
|
113
|
+
</Button>
|
|
114
|
+
</form>
|
|
115
|
+
)
|
|
116
|
+
}
|