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.
Files changed (90) hide show
  1. package/README.md +483 -32
  2. package/bin/cli.js +232 -41
  3. package/bin/create.js +135 -0
  4. package/bin/lint.js +20 -21
  5. package/docs/COMMANDS.md +200 -127
  6. package/docs/COMMITS/INDEX.md +1 -0
  7. package/docs/PROMPT_AI_CONTEXT.md +99 -0
  8. package/docs/langs/dotnet.md +36 -0
  9. package/docs/langs/java-spring.md +45 -0
  10. package/docs/langs/python-fastapi.md +35 -0
  11. package/docs/langs/unity.md +30 -0
  12. package/docs/langs/vue-nuxt.md +36 -0
  13. package/package.json +31 -3
  14. package/scaffolds/.cursorrules +6 -0
  15. package/scaffolds/AGENTS.md +27 -0
  16. package/scaffolds/Dockerfile +11 -0
  17. package/scaffolds/capacitor.config.ts +17 -0
  18. package/scaffolds/netlify.toml +8 -0
  19. package/scaffolds/prompt-lang.json +15 -0
  20. package/scaffolds/railway.json +12 -0
  21. package/scaffolds/tailwind.config.js +8 -0
  22. package/scaffolds/tauri.conf.json +26 -0
  23. package/schemas/language-module.json +116 -0
  24. package/schemas/prompt-lang.json +38 -3
  25. package/schemas/structures.json +145 -0
  26. package/src/ai/prompt-builder.js +184 -0
  27. package/src/ai/providers.js +247 -0
  28. package/src/annotations/registry.js +39 -0
  29. package/src/annotations/tags.json +24 -0
  30. package/src/commands/ai-gen.js +161 -0
  31. package/src/commands/component.js +242 -212
  32. package/src/commands/context.js +184 -109
  33. package/src/commands/extract.js +242 -0
  34. package/src/commands/figma.js +15 -15
  35. package/src/commands/init.js +197 -93
  36. package/src/commands/integrate.js +406 -0
  37. package/src/commands/lang.js +148 -0
  38. package/src/commands/qa-gen.js +139 -0
  39. package/src/commands/scaffold.js +127 -0
  40. package/src/commands/suggest.js +24 -14
  41. package/src/commands/teach.js +110 -0
  42. package/src/commands/validate.js +143 -83
  43. package/src/commands/wizard.js +456 -0
  44. package/src/generators/figma-prompt.js +20 -12
  45. package/src/language-service/plugin.cjs +94 -0
  46. package/src/language-service/plugin.d.ts +6 -0
  47. package/src/mcp-server.js +605 -0
  48. package/src/templates/langs/react/INDEX.json +262 -0
  49. package/src/templates/langs/react/MODULE.json +166 -0
  50. package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
  51. package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
  52. package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
  53. package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
  54. package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
  55. package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
  56. package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
  57. package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
  58. package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
  59. package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
  60. package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
  61. package/src/templates/langs/vue/INDEX.json +246 -0
  62. package/src/templates/langs/vue/MODULE.json +105 -0
  63. package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
  64. package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
  65. package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
  66. package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
  67. package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
  68. package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
  69. package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
  70. package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
  71. package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
  72. package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
  73. package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
  74. package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
  75. package/src/templates/scripts/log-actividad.sh +32 -0
  76. package/src/templates/scripts/log-commit.sh +35 -0
  77. package/src/templates/scripts/log-error.sh +45 -0
  78. package/src/templates/scripts/validate.sh +23 -0
  79. package/src/ts-transformer/index.cjs +86 -0
  80. package/src/utils/ai.js +35 -53
  81. package/src/utils/annotations.js +260 -214
  82. package/src/utils/config.js +61 -13
  83. package/src/utils/error-learner.js +203 -0
  84. package/src/utils/file-utils.js +119 -0
  85. package/src/utils/language-loader.js +167 -0
  86. package/src/utils/template-utils.js +45 -0
  87. package/src/vite-plugin/index.js +54 -0
  88. package/vscode-extension/package.json +23 -2
  89. package/vscode-extension/snippets/promptlang.json +1 -3
  90. 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 }