openprompt-lang 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 +483 -32
- package/bin/cli.js +232 -41
- package/bin/create.js +135 -0
- package/bin/lint.js +20 -21
- package/docs/COMMANDS.md +200 -127
- package/docs/COMMITS/INDEX.md +1 -0
- package/docs/PROMPT_AI_CONTEXT.md +99 -0
- package/docs/langs/dotnet.md +36 -0
- package/docs/langs/java-spring.md +45 -0
- package/docs/langs/python-fastapi.md +35 -0
- package/docs/langs/unity.md +30 -0
- package/docs/langs/vue-nuxt.md +36 -0
- package/package.json +31 -3
- package/scaffolds/.cursorrules +6 -0
- package/scaffolds/AGENTS.md +27 -0
- package/scaffolds/Dockerfile +11 -0
- package/scaffolds/capacitor.config.ts +17 -0
- package/scaffolds/netlify.toml +8 -0
- package/scaffolds/prompt-lang.json +15 -0
- package/scaffolds/railway.json +12 -0
- package/scaffolds/tailwind.config.js +8 -0
- package/scaffolds/tauri.conf.json +26 -0
- package/schemas/language-module.json +116 -0
- package/schemas/prompt-lang.json +38 -3
- package/schemas/structures.json +145 -0
- package/src/ai/prompt-builder.js +184 -0
- package/src/ai/providers.js +247 -0
- package/src/annotations/registry.js +39 -0
- package/src/annotations/tags.json +24 -0
- package/src/commands/ai-gen.js +161 -0
- package/src/commands/component.js +242 -212
- package/src/commands/context.js +184 -109
- package/src/commands/extract.js +242 -0
- package/src/commands/figma.js +15 -15
- package/src/commands/init.js +197 -93
- package/src/commands/integrate.js +406 -0
- package/src/commands/lang.js +148 -0
- package/src/commands/qa-gen.js +139 -0
- package/src/commands/scaffold.js +127 -0
- package/src/commands/suggest.js +24 -14
- package/src/commands/teach.js +110 -0
- package/src/commands/validate.js +143 -83
- package/src/commands/wizard.js +456 -0
- package/src/generators/figma-prompt.js +20 -12
- package/src/language-service/plugin.cjs +94 -0
- package/src/language-service/plugin.d.ts +6 -0
- package/src/mcp-server.js +605 -0
- package/src/templates/langs/react/INDEX.json +262 -0
- package/src/templates/langs/react/MODULE.json +166 -0
- package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
- package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
- package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
- package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
- package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
- package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
- package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
- package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
- package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
- package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
- package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
- package/src/templates/langs/vue/INDEX.json +246 -0
- package/src/templates/langs/vue/MODULE.json +105 -0
- package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
- package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
- package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
- package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
- package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
- package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
- package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
- package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
- package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
- package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
- package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
- package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
- package/src/templates/scripts/log-actividad.sh +32 -0
- package/src/templates/scripts/log-commit.sh +35 -0
- package/src/templates/scripts/log-error.sh +45 -0
- package/src/templates/scripts/validate.sh +23 -0
- package/src/ts-transformer/index.cjs +86 -0
- package/src/utils/ai.js +35 -53
- package/src/utils/annotations.js +260 -214
- package/src/utils/config.js +61 -13
- package/src/utils/error-learner.js +203 -0
- package/src/utils/file-utils.js +119 -0
- package/src/utils/language-loader.js +167 -0
- package/src/utils/template-utils.js +45 -0
- package/src/vite-plugin/index.js +54 -0
- package/vscode-extension/package.json +23 -2
- package/vscode-extension/snippets/promptlang.json +1 -3
- package/vscode-extension/syntaxes/annotations-code.tmGrammar.json +15 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
{
|
|
2
|
+
"language": "react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"updated": "2026-05-14",
|
|
5
|
+
"categories": {
|
|
6
|
+
"ui/button": {
|
|
7
|
+
"name": "Buttons",
|
|
8
|
+
"description": "Button components with variants and states"
|
|
9
|
+
},
|
|
10
|
+
"ui/modal": {
|
|
11
|
+
"name": "Modals",
|
|
12
|
+
"description": "Dialogs, confirmations, and popovers"
|
|
13
|
+
},
|
|
14
|
+
"ui/form": {
|
|
15
|
+
"name": "Forms",
|
|
16
|
+
"description": "Inputs, selects, and form utilities"
|
|
17
|
+
},
|
|
18
|
+
"ui/data-display": {
|
|
19
|
+
"name": "Data Display",
|
|
20
|
+
"description": "Tables, lists, cards, and data visualizers"
|
|
21
|
+
},
|
|
22
|
+
"auth": {
|
|
23
|
+
"name": "Authentication",
|
|
24
|
+
"description": "Auth hooks, providers, and guards"
|
|
25
|
+
},
|
|
26
|
+
"hook/general": {
|
|
27
|
+
"name": "General Hooks",
|
|
28
|
+
"description": "Reusable React hooks"
|
|
29
|
+
},
|
|
30
|
+
"service/api": {
|
|
31
|
+
"name": "API Services",
|
|
32
|
+
"description": "HTTP clients and API abstractions"
|
|
33
|
+
},
|
|
34
|
+
"util/date": {
|
|
35
|
+
"name": "Date Utilities",
|
|
36
|
+
"description": "Date formatting and manipulation"
|
|
37
|
+
},
|
|
38
|
+
"util/validation": {
|
|
39
|
+
"name": "Validation",
|
|
40
|
+
"description": "Input validation schemas and utilities"
|
|
41
|
+
},
|
|
42
|
+
"layout": {
|
|
43
|
+
"name": "Layout",
|
|
44
|
+
"description": "Page layout components (sidebar, navbar, shell)"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"templates": [
|
|
48
|
+
{
|
|
49
|
+
"id": "button-shadcn",
|
|
50
|
+
"name": "Button",
|
|
51
|
+
"description": "Versatile button with variants, loading state, and dark mode",
|
|
52
|
+
"category": "ui/button",
|
|
53
|
+
"purposes": ["primary-action", "confirmation", "danger", "navigation"],
|
|
54
|
+
"file": "templates/ui/Button.template.tsx",
|
|
55
|
+
"testFile": null,
|
|
56
|
+
"variants": ["primary", "secondary", "danger", "ghost", "outline"],
|
|
57
|
+
"sizes": ["sm", "default", "lg", "icon"],
|
|
58
|
+
"darkMode": true,
|
|
59
|
+
"responsive": true,
|
|
60
|
+
"dependencies": ["class-variance-authority", "lucide-react"],
|
|
61
|
+
"tags": ["button", "action", "form", "shadcn", "cva", "loading"],
|
|
62
|
+
"hasTeachMe": true,
|
|
63
|
+
"fixHistory": ["BUTTON-001"]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "hook-useAuth",
|
|
67
|
+
"name": "useAuth",
|
|
68
|
+
"description": "Authentication hook with Supabase and Zustand session persistence",
|
|
69
|
+
"category": "auth",
|
|
70
|
+
"purposes": ["login", "register", "session", "logout", "protected-routes"],
|
|
71
|
+
"file": "templates/hooks/useAuth.template.ts",
|
|
72
|
+
"testFile": null,
|
|
73
|
+
"variants": [],
|
|
74
|
+
"sizes": [],
|
|
75
|
+
"darkMode": false,
|
|
76
|
+
"responsive": false,
|
|
77
|
+
"dependencies": ["@supabase/supabase-js", "zustand"],
|
|
78
|
+
"tags": ["auth", "supabase", "session", "hook", "rls"],
|
|
79
|
+
"hasTeachMe": true,
|
|
80
|
+
"fixHistory": ["AUTH-001", "AUTH-002"]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "input-shadcn",
|
|
84
|
+
"name": "Input",
|
|
85
|
+
"description": "Form input with label, error state, helper text, and icon support",
|
|
86
|
+
"category": "ui/form",
|
|
87
|
+
"purposes": ["text-input", "search", "email", "password", "number"],
|
|
88
|
+
"file": "templates/ui/Input.template.tsx",
|
|
89
|
+
"testFile": null,
|
|
90
|
+
"variants": ["default", "error", "flushed"],
|
|
91
|
+
"sizes": ["sm", "default", "lg"],
|
|
92
|
+
"darkMode": true,
|
|
93
|
+
"responsive": true,
|
|
94
|
+
"dependencies": ["lucide-react"],
|
|
95
|
+
"tags": ["input", "form", "label", "error", "shadcn"],
|
|
96
|
+
"hasTeachMe": true,
|
|
97
|
+
"fixHistory": []
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "select-shadcn",
|
|
101
|
+
"name": "Select",
|
|
102
|
+
"description": "Custom styled select dropdown with label, error state, and placeholder",
|
|
103
|
+
"category": "ui/form",
|
|
104
|
+
"purposes": ["dropdown", "option-picker", "multi-select"],
|
|
105
|
+
"file": "templates/ui/Select.template.tsx",
|
|
106
|
+
"testFile": null,
|
|
107
|
+
"variants": ["default", "error", "multiple"],
|
|
108
|
+
"sizes": ["sm", "default", "lg"],
|
|
109
|
+
"darkMode": true,
|
|
110
|
+
"responsive": true,
|
|
111
|
+
"dependencies": ["lucide-react"],
|
|
112
|
+
"tags": ["select", "dropdown", "form", "shadcn"],
|
|
113
|
+
"hasTeachMe": true,
|
|
114
|
+
"fixHistory": []
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "card-shadcn",
|
|
118
|
+
"name": "Card",
|
|
119
|
+
"description": "Container card with title, subtitle, footer, and interactive variant",
|
|
120
|
+
"category": "ui/data-display",
|
|
121
|
+
"purposes": ["container", "summary", "feature-card", "dashboard-card"],
|
|
122
|
+
"file": "templates/ui/Card.template.tsx",
|
|
123
|
+
"testFile": null,
|
|
124
|
+
"variants": ["default", "bordered", "elevated", "interactive"],
|
|
125
|
+
"sizes": ["sm", "default", "full"],
|
|
126
|
+
"darkMode": true,
|
|
127
|
+
"responsive": true,
|
|
128
|
+
"dependencies": [],
|
|
129
|
+
"tags": ["card", "container", "dashboard", "shadcn"],
|
|
130
|
+
"hasTeachMe": true,
|
|
131
|
+
"fixHistory": []
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"id": "modal-dialog",
|
|
135
|
+
"name": "Modal",
|
|
136
|
+
"description": "Accessible modal dialog with focus trap, portal rendering, and escape-to-close",
|
|
137
|
+
"category": "ui/modal",
|
|
138
|
+
"purposes": ["confirm-action", "form-dialog", "alert", "fullscreen"],
|
|
139
|
+
"file": "templates/ui/Modal.template.tsx",
|
|
140
|
+
"testFile": null,
|
|
141
|
+
"variants": ["dialog", "confirm-destructive", "fullscreen"],
|
|
142
|
+
"sizes": ["sm", "default", "lg", "xl", "fullscreen"],
|
|
143
|
+
"darkMode": true,
|
|
144
|
+
"responsive": true,
|
|
145
|
+
"dependencies": ["lucide-react"],
|
|
146
|
+
"tags": ["modal", "dialog", "overlay", "portal", "a11y"],
|
|
147
|
+
"hasTeachMe": true,
|
|
148
|
+
"fixHistory": []
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "datatable-shadcn",
|
|
152
|
+
"name": "DataTable",
|
|
153
|
+
"description": "Typed data table with sorting, loading skeleton, empty state, and keyboard a11y",
|
|
154
|
+
"category": "ui/data-display",
|
|
155
|
+
"purposes": ["data-table", "sortable-table", "paginated-list"],
|
|
156
|
+
"file": "templates/ui/DataTable.template.tsx",
|
|
157
|
+
"testFile": null,
|
|
158
|
+
"variants": ["default", "compact", "striped", "sortable"],
|
|
159
|
+
"sizes": ["default", "compact"],
|
|
160
|
+
"darkMode": true,
|
|
161
|
+
"responsive": true,
|
|
162
|
+
"dependencies": ["lucide-react"],
|
|
163
|
+
"tags": ["table", "data", "sortable", "shadcn"],
|
|
164
|
+
"hasTeachMe": true,
|
|
165
|
+
"fixHistory": []
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "hook-useDebounce",
|
|
169
|
+
"name": "useDebounce",
|
|
170
|
+
"description": "Debounce hook for input values, search delays, and resize throttling",
|
|
171
|
+
"category": "hook/general",
|
|
172
|
+
"purposes": ["input-debounce", "search-delay", "resize-throttle"],
|
|
173
|
+
"file": "templates/hooks/useDebounce.template.ts",
|
|
174
|
+
"testFile": null,
|
|
175
|
+
"variants": ["default", "leading", "trailing"],
|
|
176
|
+
"sizes": [],
|
|
177
|
+
"darkMode": false,
|
|
178
|
+
"responsive": false,
|
|
179
|
+
"dependencies": [],
|
|
180
|
+
"tags": ["debounce", "hook", "search", "throttle"],
|
|
181
|
+
"hasTeachMe": true,
|
|
182
|
+
"fixHistory": []
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"id": "hook-usePagination",
|
|
186
|
+
"name": "usePagination",
|
|
187
|
+
"description": "Pagination hook with ellipsis logic, page buttons, and server-side support",
|
|
188
|
+
"category": "hook/general",
|
|
189
|
+
"purposes": ["paginated-list", "table-pagination", "infinite-scroll"],
|
|
190
|
+
"file": "templates/hooks/usePagination.template.ts",
|
|
191
|
+
"testFile": null,
|
|
192
|
+
"variants": ["client-side", "server-side"],
|
|
193
|
+
"sizes": [],
|
|
194
|
+
"darkMode": false,
|
|
195
|
+
"responsive": true,
|
|
196
|
+
"dependencies": [],
|
|
197
|
+
"tags": ["pagination", "hook", "table", "navigation"],
|
|
198
|
+
"hasTeachMe": true,
|
|
199
|
+
"fixHistory": []
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "hook-useForm",
|
|
203
|
+
"name": "useForm",
|
|
204
|
+
"description": "Lightweight form state manager with validation, async submit, and touched tracking",
|
|
205
|
+
"category": "ui/form",
|
|
206
|
+
"purposes": ["form-state", "validation", "async-submit"],
|
|
207
|
+
"file": "templates/hooks/useForm.template.ts",
|
|
208
|
+
"testFile": null,
|
|
209
|
+
"variants": ["controlled", "uncontrolled"],
|
|
210
|
+
"sizes": [],
|
|
211
|
+
"darkMode": false,
|
|
212
|
+
"responsive": false,
|
|
213
|
+
"dependencies": [],
|
|
214
|
+
"tags": ["form", "validation", "hook", "state"],
|
|
215
|
+
"hasTeachMe": true,
|
|
216
|
+
"fixHistory": []
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": "service-api",
|
|
220
|
+
"name": "ApiClient",
|
|
221
|
+
"description": "Generic REST API client with typed responses, auth, timeout, and error handling",
|
|
222
|
+
"category": "service/api",
|
|
223
|
+
"purposes": ["crud", "rest-api", "http-client", "data-fetching"],
|
|
224
|
+
"file": "templates/services/apiService.template.ts",
|
|
225
|
+
"testFile": null,
|
|
226
|
+
"variants": ["rest", "supabase"],
|
|
227
|
+
"sizes": [],
|
|
228
|
+
"darkMode": false,
|
|
229
|
+
"responsive": false,
|
|
230
|
+
"dependencies": [],
|
|
231
|
+
"tags": ["api", "rest", "http", "client", "service"],
|
|
232
|
+
"hasTeachMe": true,
|
|
233
|
+
"fixHistory": ["AUTH-002"]
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"errorsLearned": [
|
|
237
|
+
{
|
|
238
|
+
"id": "BUTTON-001",
|
|
239
|
+
"template": "button-shadcn",
|
|
240
|
+
"problem": "Button without type='button' caused unintended form submission",
|
|
241
|
+
"solution": "Added type='button' as default to prevent browser submit behavior",
|
|
242
|
+
"date": "2026-04-15",
|
|
243
|
+
"category": "html-behavior"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "AUTH-001",
|
|
247
|
+
"template": "hook-useAuth",
|
|
248
|
+
"problem": "Session not persisted on page refresh (Zustand store lost)",
|
|
249
|
+
"solution": "Added localStorage persistence middleware to Zustand store",
|
|
250
|
+
"date": "2026-04-18",
|
|
251
|
+
"category": "state-persistence"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"id": "AUTH-002",
|
|
255
|
+
"template": "hook-useAuth",
|
|
256
|
+
"problem": "Auth token exposed in client console.log during debug",
|
|
257
|
+
"solution": "Removed debug logs from production build using if(dev) guard",
|
|
258
|
+
"date": "2026-04-20",
|
|
259
|
+
"category": "security"
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "react",
|
|
3
|
+
"name": "React + TypeScript (Vite)",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"tags": {
|
|
6
|
+
"kind": {
|
|
7
|
+
"hook": {
|
|
8
|
+
"extends": "base",
|
|
9
|
+
"limit": 80,
|
|
10
|
+
"requires": ["contract"],
|
|
11
|
+
"forbids": ["props"],
|
|
12
|
+
"description": "Custom React hook"
|
|
13
|
+
},
|
|
14
|
+
"component": {
|
|
15
|
+
"extends": "base",
|
|
16
|
+
"limit": 120,
|
|
17
|
+
"suggests": ["props", "state"],
|
|
18
|
+
"forbids": ["contract"],
|
|
19
|
+
"description": "React UI component"
|
|
20
|
+
},
|
|
21
|
+
"page": {
|
|
22
|
+
"extends": "base",
|
|
23
|
+
"limit": 200,
|
|
24
|
+
"requires": ["compose"],
|
|
25
|
+
"description": "Full page component"
|
|
26
|
+
},
|
|
27
|
+
"service": {
|
|
28
|
+
"extends": "base",
|
|
29
|
+
"limit": 150,
|
|
30
|
+
"requires": ["contract"],
|
|
31
|
+
"forbids": ["props", "state"],
|
|
32
|
+
"description": "API service layer"
|
|
33
|
+
},
|
|
34
|
+
"store": {
|
|
35
|
+
"extends": "base",
|
|
36
|
+
"limit": 100,
|
|
37
|
+
"suggests": ["deps"],
|
|
38
|
+
"description": "Global state (Zustand)"
|
|
39
|
+
},
|
|
40
|
+
"util": {
|
|
41
|
+
"extends": "base",
|
|
42
|
+
"limit": 100,
|
|
43
|
+
"forbids": ["state"],
|
|
44
|
+
"description": "Utility function"
|
|
45
|
+
},
|
|
46
|
+
"type": {
|
|
47
|
+
"extends": "base",
|
|
48
|
+
"forbids": ["limit", "state"],
|
|
49
|
+
"description": "TypeScript type/interface"
|
|
50
|
+
},
|
|
51
|
+
"layout": { "extends": "base", "limit": 150, "description": "Layout wrapper component" },
|
|
52
|
+
"feature": {
|
|
53
|
+
"extends": "base",
|
|
54
|
+
"requires": ["compose"],
|
|
55
|
+
"description": "Feature module bundle"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"platforms": ["web", "mobile", "desktop"],
|
|
59
|
+
"forbidden": ["any", "css-modules", "class-components"]
|
|
60
|
+
},
|
|
61
|
+
"structure": {
|
|
62
|
+
"folders": [
|
|
63
|
+
"src/components/ui",
|
|
64
|
+
"src/components/layout",
|
|
65
|
+
"src/components/shared",
|
|
66
|
+
"src/features",
|
|
67
|
+
"src/hooks",
|
|
68
|
+
"src/services",
|
|
69
|
+
"src/store",
|
|
70
|
+
"src/types",
|
|
71
|
+
"src/utils",
|
|
72
|
+
"docs/COMMITS",
|
|
73
|
+
"docs/LOGS/ERRORES",
|
|
74
|
+
"docs/LOGS/ACTIVIDAD",
|
|
75
|
+
"docs/BACKLOG",
|
|
76
|
+
"docs/PROMPTS/PLANTILLAS",
|
|
77
|
+
"docs/PROMPTS/STACK",
|
|
78
|
+
"scripts"
|
|
79
|
+
],
|
|
80
|
+
"scaffolds": ["prompt-lang.json", "AGENTS.md", ".gitignore", "tailwind.config.js"],
|
|
81
|
+
"deps": {
|
|
82
|
+
"base": {
|
|
83
|
+
"dev": [
|
|
84
|
+
"typescript",
|
|
85
|
+
"vite",
|
|
86
|
+
"@vitejs/plugin-react",
|
|
87
|
+
"tailwindcss",
|
|
88
|
+
"@tailwindcss/vite",
|
|
89
|
+
"vitest"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"supabase": { "prod": ["@supabase/supabase-js", "@supabase/ssr"] },
|
|
93
|
+
"stripe": { "prod": ["@stripe/react-stripe-js", "@stripe/stripe-js"] },
|
|
94
|
+
"ionic": {
|
|
95
|
+
"prod": ["@ionic/react", "@ionic/react-router", "@capacitor/core"],
|
|
96
|
+
"dev": ["@capacitor/cli"]
|
|
97
|
+
},
|
|
98
|
+
"tauri": { "dev": ["@tauri-apps/cli"] },
|
|
99
|
+
"prisma": { "dev": ["prisma"], "prod": ["@prisma/client"] }
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"projectTypes": {
|
|
103
|
+
"saas": {
|
|
104
|
+
"name": "SaaS / Dashboard",
|
|
105
|
+
"ui": true,
|
|
106
|
+
"description": "Multi-tenant SaaS with dashboard"
|
|
107
|
+
},
|
|
108
|
+
"web": { "name": "Corporate Website", "ui": true, "description": "Company landing + content" },
|
|
109
|
+
"landing": { "name": "Landing Page", "ui": true, "description": "Single product/service page" },
|
|
110
|
+
"portfolio": { "name": "Portfolio", "ui": true, "description": "Personal dev portfolio" },
|
|
111
|
+
"api": { "name": "API / Microservice", "ui": false, "description": "Backend-only service" },
|
|
112
|
+
"mobile": { "name": "Mobile App", "ui": true, "description": "Capacitor/Ionic mobile app" },
|
|
113
|
+
"desktop": { "name": "Desktop App", "ui": true, "description": "Tauri desktop app" }
|
|
114
|
+
},
|
|
115
|
+
"uiStyles": ["shadcn", "mantine", "chakra", "headless", "mui", "none"],
|
|
116
|
+
"extensions": [
|
|
117
|
+
{
|
|
118
|
+
"name": "Supabase (auth + DB)",
|
|
119
|
+
"value": "supabase",
|
|
120
|
+
"deps": ["@supabase/supabase-js", "@supabase/ssr"],
|
|
121
|
+
"description": "Backend as a service with auth"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "Stripe (payments)",
|
|
125
|
+
"value": "stripe",
|
|
126
|
+
"deps": ["@stripe/react-stripe-js", "@stripe/stripe-js"],
|
|
127
|
+
"description": "Payment processing"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "Prisma ORM",
|
|
131
|
+
"value": "prisma",
|
|
132
|
+
"deps": ["prisma", "@prisma/client"],
|
|
133
|
+
"description": "Type-safe database ORM"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "Ionic (mobile)",
|
|
137
|
+
"value": "ionic",
|
|
138
|
+
"deps": ["@ionic/react", "@ionic/react-router", "@capacitor/core"],
|
|
139
|
+
"description": "Cross-platform mobile"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "Tauri (desktop)",
|
|
143
|
+
"value": "tauri",
|
|
144
|
+
"deps": ["@tauri-apps/cli"],
|
|
145
|
+
"description": "Desktop application"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "Next.js (SSR)",
|
|
149
|
+
"value": "next",
|
|
150
|
+
"deps": ["next", "react", "react-dom"],
|
|
151
|
+
"description": "Server-side rendering"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"name": "Auth0",
|
|
155
|
+
"value": "auth0",
|
|
156
|
+
"deps": ["@auth0/auth0-react"],
|
|
157
|
+
"description": "Authentication as a service"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "Webpay CL",
|
|
161
|
+
"value": "webpay",
|
|
162
|
+
"deps": ["transbank-sdk"],
|
|
163
|
+
"description": "Chilean payment gateway"
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @template
|
|
2
|
+
// @id: hook-useAuth
|
|
3
|
+
// @category: auth
|
|
4
|
+
// @purpose: login, register, session, logout
|
|
5
|
+
// @framework: react
|
|
6
|
+
// @style: none
|
|
7
|
+
// @darkMode: false
|
|
8
|
+
// @responsive: false
|
|
9
|
+
// @deps: [@supabase/supabase-js, zustand]
|
|
10
|
+
// @props: {}
|
|
11
|
+
// @states: [loading, authenticated, unauthenticated, error]
|
|
12
|
+
// @example: const { user, login, logout, loading } = useAuth()
|
|
13
|
+
// @teachMe
|
|
14
|
+
//
|
|
15
|
+
// ## Anatomy
|
|
16
|
+
// This hook combines Supabase auth with Zustand for client-side session persistence.
|
|
17
|
+
// The Zustand store uses localStorage middleware to survive page refreshes.
|
|
18
|
+
// Auth state is derived from both the Supabase session and the local store.
|
|
19
|
+
//
|
|
20
|
+
// @goodPractice: Use Zustand middleware for localStorage persistence
|
|
21
|
+
// @goodPractice: Derive auth state from supabase.getSession() on mount
|
|
22
|
+
// @goodPractice: Wrap login/register in try/catch with typed errors
|
|
23
|
+
// @badPractice: Don't store raw passwords or tokens in localStorage (see AUTH-002)
|
|
24
|
+
// @badPractice: Don't rely only on Supabase session — it resets on refresh without persistence
|
|
25
|
+
// @badPractice: Don't expose internal state setters outside the hook
|
|
26
|
+
// @fixHistory: AUTH-001 — added Zustand persistence for page refresh
|
|
27
|
+
// @fixHistory: AUTH-002 — removed console.log of auth token in production
|
|
28
|
+
|
|
29
|
+
import { create } from "zustand"
|
|
30
|
+
import { persist } from "zustand/middleware"
|
|
31
|
+
import { useEffect, useState } from "react"
|
|
32
|
+
import { createClient, type User, type Session } from "@supabase/supabase-js"
|
|
33
|
+
|
|
34
|
+
const supabase = createClient(
|
|
35
|
+
import.meta.env.VITE_SUPABASE_URL,
|
|
36
|
+
import.meta.env.VITE_SUPABASE_ANON_KEY
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
interface AuthState {
|
|
40
|
+
user: User | null
|
|
41
|
+
session: Session | null
|
|
42
|
+
setAuth: (user: User | null, session: Session | null) => void
|
|
43
|
+
clearAuth: () => void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const useAuthStore = create<AuthState>()(
|
|
47
|
+
persist(
|
|
48
|
+
(set) => ({
|
|
49
|
+
user: null,
|
|
50
|
+
session: null,
|
|
51
|
+
setAuth: (user, session) => set({ user, session }),
|
|
52
|
+
clearAuth: () => set({ user: null, session: null }),
|
|
53
|
+
}),
|
|
54
|
+
{
|
|
55
|
+
name: "auth-storage",
|
|
56
|
+
partialize: (state) => ({ user: state.user }),
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
interface AuthResult {
|
|
62
|
+
user: User | null
|
|
63
|
+
loading: boolean
|
|
64
|
+
error: string | null
|
|
65
|
+
login: (email: string, password: string) => Promise<void>
|
|
66
|
+
register: (email: string, password: string) => Promise<void>
|
|
67
|
+
logout: () => Promise<void>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useAuth(): AuthResult {
|
|
71
|
+
const [loading, setLoading] = useState(true)
|
|
72
|
+
const [error, setError] = useState<string | null>(null)
|
|
73
|
+
const { user, setAuth, clearAuth } = useAuthStore()
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
supabase.auth.getSession().then(({ data: { session } }) => {
|
|
77
|
+
if (session?.user) {
|
|
78
|
+
setAuth(session.user, session)
|
|
79
|
+
}
|
|
80
|
+
setLoading(false)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const {
|
|
84
|
+
data: { subscription },
|
|
85
|
+
} = supabase.auth.onAuthStateChange((_event, session) => {
|
|
86
|
+
if (session?.user) {
|
|
87
|
+
setAuth(session.user, session)
|
|
88
|
+
} else {
|
|
89
|
+
clearAuth()
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
return () => subscription?.unsubscribe()
|
|
94
|
+
}, [])
|
|
95
|
+
|
|
96
|
+
const login = async (email: string, password: string) => {
|
|
97
|
+
setLoading(true)
|
|
98
|
+
setError(null)
|
|
99
|
+
const { error: authError } = await supabase.auth.signInWithPassword({
|
|
100
|
+
email,
|
|
101
|
+
password,
|
|
102
|
+
})
|
|
103
|
+
if (authError) {
|
|
104
|
+
setError(authError.message)
|
|
105
|
+
setLoading(false)
|
|
106
|
+
throw authError
|
|
107
|
+
}
|
|
108
|
+
setLoading(false)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const register = async (email: string, password: string) => {
|
|
112
|
+
setLoading(true)
|
|
113
|
+
setError(null)
|
|
114
|
+
const { error: authError } = await supabase.auth.signUp({
|
|
115
|
+
email,
|
|
116
|
+
password,
|
|
117
|
+
})
|
|
118
|
+
if (authError) {
|
|
119
|
+
setError(authError.message)
|
|
120
|
+
setLoading(false)
|
|
121
|
+
throw authError
|
|
122
|
+
}
|
|
123
|
+
setLoading(false)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const logout = async () => {
|
|
127
|
+
setLoading(true)
|
|
128
|
+
await supabase.auth.signOut()
|
|
129
|
+
clearAuth()
|
|
130
|
+
setLoading(false)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { user, loading, error, login, register, logout }
|
|
134
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @template
|
|
2
|
+
// @id: hook-useDebounce
|
|
3
|
+
// @category: hook/general
|
|
4
|
+
// @purpose: input-debounce, search-delay, resize-throttle
|
|
5
|
+
// @framework: react
|
|
6
|
+
// @style: generic
|
|
7
|
+
// @variants: [default, leading, trailing]
|
|
8
|
+
// @sizes: []
|
|
9
|
+
// @darkMode: false
|
|
10
|
+
// @responsive: false
|
|
11
|
+
// @deps: []
|
|
12
|
+
// @props: { value: T, delay?: number }
|
|
13
|
+
// @states: [pending, settled]
|
|
14
|
+
// @example: const debouncedSearch = useDebounce(searchTerm, 300)
|
|
15
|
+
// @teachMe
|
|
16
|
+
//
|
|
17
|
+
// ## Anatomy
|
|
18
|
+
// Returns a debounced version of the input value that only updates after `delay` ms of inactivity.
|
|
19
|
+
// Uses useEffect + setTimeout to track the last change. Cleanup on unmount prevents stale updates.
|
|
20
|
+
// Leading variant fires immediately then debounces; trailing (default) waits for pause.
|
|
21
|
+
//
|
|
22
|
+
// @goodPractice: Clean up timeout on unmount to prevent memory leaks and state updates
|
|
23
|
+
// @goodPractice: Use 300ms for search, 150ms for resize, 500ms for API saves
|
|
24
|
+
// @goodPractice: Pair with useMemo for derived data that depends on debounced value
|
|
25
|
+
// @badPractice: Don't debounce form submission — debounce only the input value
|
|
26
|
+
// @badPractice: Don't use debounce for animation frames — use requestAnimationFrame
|
|
27
|
+
// @badPractice: Don't set delay < 100ms — defeats the purpose of debouncing
|
|
28
|
+
|
|
29
|
+
import { useState, useEffect } from "react"
|
|
30
|
+
|
|
31
|
+
function useDebounce<T>(value: T, delay: number = 300): T {
|
|
32
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value)
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
setDebouncedValue(value)
|
|
37
|
+
}, delay)
|
|
38
|
+
|
|
39
|
+
return () => clearTimeout(timer)
|
|
40
|
+
}, [value, delay])
|
|
41
|
+
|
|
42
|
+
return debouncedValue
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { useDebounce }
|