rahman-resources 0.1.0 → 0.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.
- package/README.md +19 -10
- package/bin/cli.js +218 -47
- package/lib/manifest.json +389 -20
- package/lib/starter/_README.md +37 -0
- package/lib/starter/_env.example +16 -0
- package/lib/starter/_gitignore +12 -0
- package/lib/starter/_package.json +39 -0
- package/lib/starter/app/globals.css +85 -0
- package/lib/starter/app/layout.tsx +23 -0
- package/lib/starter/app/page.tsx +26 -0
- package/lib/starter/components/convex-provider.tsx +13 -0
- package/lib/starter/components/ui/button.tsx +43 -0
- package/lib/starter/components.json +21 -0
- package/lib/starter/convex/auth.ts +6 -0
- package/lib/starter/convex/http.ts +7 -0
- package/lib/starter/convex/schema.ts +14 -0
- package/lib/starter/lib/utils.ts +6 -0
- package/lib/starter/next.config.mjs +16 -0
- package/lib/starter/postcss.config.mjs +3 -0
- package/lib/starter/proxy.ts +12 -0
- package/lib/starter/tsconfig.json +23 -0
- package/package.json +4 -3
package/lib/manifest.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"version": 2,
|
|
3
|
+
"generatedAt": "2026-05-05T12:19:53.839Z",
|
|
3
4
|
"repo": "rahmanef63/resource-site",
|
|
4
5
|
"branch": "main",
|
|
5
|
-
"
|
|
6
|
+
"layouts": [
|
|
6
7
|
{
|
|
7
8
|
"slug": "personal-brand-os",
|
|
8
9
|
"title": "Personal Brand OS",
|
|
@@ -16,6 +17,9 @@
|
|
|
16
17
|
"convex/templates/personal-brand-os"
|
|
17
18
|
],
|
|
18
19
|
"files": [
|
|
20
|
+
"app/preview/personal-brand-os/robots.ts",
|
|
21
|
+
"app/preview/personal-brand-os/sitemap.ts",
|
|
22
|
+
"app/preview/personal-brand-os/opengraph-image.tsx",
|
|
19
23
|
"app/preview/personal-brand-os/public/layout.tsx",
|
|
20
24
|
"app/preview/personal-brand-os/public/page.tsx",
|
|
21
25
|
"app/preview/personal-brand-os/public/blog/page.tsx",
|
|
@@ -32,17 +36,51 @@
|
|
|
32
36
|
"app/preview/personal-brand-os/admin/posts/new/page.tsx",
|
|
33
37
|
"app/preview/personal-brand-os/admin/posts/[id]/page.tsx",
|
|
34
38
|
"app/preview/personal-brand-os/admin/portfolio/page.tsx",
|
|
39
|
+
"app/preview/personal-brand-os/admin/portfolio/new/page.tsx",
|
|
40
|
+
"app/preview/personal-brand-os/admin/portfolio/[id]/page.tsx",
|
|
41
|
+
"app/preview/personal-brand-os/admin/services/page.tsx",
|
|
42
|
+
"app/preview/personal-brand-os/admin/resources/page.tsx",
|
|
35
43
|
"app/preview/personal-brand-os/admin/leads/page.tsx",
|
|
36
44
|
"app/preview/personal-brand-os/admin/comments/page.tsx",
|
|
37
45
|
"app/preview/personal-brand-os/admin/chatbot/page.tsx",
|
|
46
|
+
"app/preview/personal-brand-os/admin/newsletter/page.tsx",
|
|
47
|
+
"app/preview/personal-brand-os/admin/analytics/page.tsx",
|
|
48
|
+
"app/preview/personal-brand-os/admin/settings/site/page.tsx",
|
|
49
|
+
"app/preview/personal-brand-os/admin/settings/team/page.tsx",
|
|
50
|
+
"app/preview/personal-brand-os/admin/settings/ai/page.tsx",
|
|
38
51
|
"components/templates/personal-brand/shared/types.ts",
|
|
39
52
|
"components/templates/personal-brand/shared/store.tsx",
|
|
40
53
|
"components/templates/personal-brand/shared/seed.ts",
|
|
54
|
+
"components/templates/personal-brand/shared/site-config.ts",
|
|
41
55
|
"components/templates/personal-brand/shared/ui/site-nav.tsx",
|
|
42
56
|
"components/templates/personal-brand/shared/ui/site-footer.tsx",
|
|
43
57
|
"components/templates/personal-brand/shared/ui/chat-fab.tsx",
|
|
58
|
+
"components/templates/personal-brand/shared/ui/section-head.tsx",
|
|
59
|
+
"components/templates/personal-brand/slices/home/HomePage.tsx",
|
|
60
|
+
"components/templates/personal-brand/slices/home/NewsletterBlock.tsx",
|
|
61
|
+
"components/templates/personal-brand/slices/blog/BlogList.tsx",
|
|
62
|
+
"components/templates/personal-brand/slices/blog/BlogDetail.tsx",
|
|
63
|
+
"components/templates/personal-brand/slices/portfolio/PortfolioListPage.tsx",
|
|
64
|
+
"components/templates/personal-brand/slices/portfolio/PortfolioDetailPage.tsx",
|
|
65
|
+
"components/templates/personal-brand/slices/services/ServicesPage.tsx",
|
|
66
|
+
"components/templates/personal-brand/slices/resources/ResourcesPage.tsx",
|
|
67
|
+
"components/templates/personal-brand/slices/about/AboutPage.tsx",
|
|
68
|
+
"components/templates/personal-brand/slices/contact/ContactPage.tsx",
|
|
44
69
|
"components/templates/personal-brand/slices/admin/shell/admin-sidebar.tsx",
|
|
45
70
|
"components/templates/personal-brand/slices/admin/shell/admin-topbar.tsx",
|
|
71
|
+
"components/templates/personal-brand/slices/admin/dashboard/DashboardView.tsx",
|
|
72
|
+
"components/templates/personal-brand/slices/admin/posts/PostsList.tsx",
|
|
73
|
+
"components/templates/personal-brand/slices/admin/posts/PostEditor.tsx",
|
|
74
|
+
"components/templates/personal-brand/slices/admin/portfolio/PortfolioListAdmin.tsx",
|
|
75
|
+
"components/templates/personal-brand/slices/admin/portfolio/PortfolioEditor.tsx",
|
|
76
|
+
"components/templates/personal-brand/slices/admin/services/ServicesAdminView.tsx",
|
|
77
|
+
"components/templates/personal-brand/slices/admin/resources/ResourcesAdminView.tsx",
|
|
78
|
+
"components/templates/personal-brand/slices/admin/leads/LeadsView.tsx",
|
|
79
|
+
"components/templates/personal-brand/slices/admin/comments/CommentsView.tsx",
|
|
80
|
+
"components/templates/personal-brand/slices/admin/chatbot/ChatbotAdminView.tsx",
|
|
81
|
+
"components/templates/personal-brand/slices/admin/newsletter/NewsletterView.tsx",
|
|
82
|
+
"components/templates/personal-brand/slices/admin/analytics/AnalyticsView.tsx",
|
|
83
|
+
"components/templates/personal-brand/slices/admin/settings/SettingsView.tsx",
|
|
46
84
|
"convex/templates/personal-brand-os/schema.ts",
|
|
47
85
|
"convex/templates/personal-brand-os/posts.ts",
|
|
48
86
|
"convex/templates/personal-brand-os/portfolio.ts",
|
|
@@ -64,9 +102,18 @@
|
|
|
64
102
|
"next-themes",
|
|
65
103
|
"tailwindcss@^4",
|
|
66
104
|
"convex",
|
|
67
|
-
"@convex-dev/auth"
|
|
105
|
+
"@convex-dev/auth",
|
|
106
|
+
"@radix-ui/react-avatar",
|
|
107
|
+
"@radix-ui/react-dialog",
|
|
108
|
+
"@radix-ui/react-dropdown-menu",
|
|
109
|
+
"@radix-ui/react-label",
|
|
110
|
+
"@radix-ui/react-scroll-area",
|
|
111
|
+
"@radix-ui/react-select",
|
|
112
|
+
"@radix-ui/react-separator",
|
|
113
|
+
"@radix-ui/react-slot",
|
|
114
|
+
"@radix-ui/react-tabs"
|
|
68
115
|
],
|
|
69
|
-
"agentRecipe": "Personal Brand OS = full-app template (public + admin).
|
|
116
|
+
"agentRecipe": "Personal Brand OS = full-app template (public + admin). 1) Move app/preview/personal-brand-os/{robots,sitemap,opengraph-image}.* to app root. 2) Copy app/preview/personal-brand-os/public into app/(public)/, app/preview/personal-brand-os/admin into app/(admin)/. 3) Edit components/templates/personal-brand/shared/site-config.ts — set brandName, ownerName, baseUrl, twitter, email. 4) Wire convex/templates/personal-brand-os/* to convex/_generated and add @convex-dev/auth on admin routes. 5) Replace localStorage StoreProvider with Convex queries (schema mirrors localStorage shape).",
|
|
70
117
|
"tags": [
|
|
71
118
|
"template",
|
|
72
119
|
"personal-brand",
|
|
@@ -74,7 +121,8 @@
|
|
|
74
121
|
"portfolio",
|
|
75
122
|
"admin",
|
|
76
123
|
"saas"
|
|
77
|
-
]
|
|
124
|
+
],
|
|
125
|
+
"primaryFile": "app/preview/personal-brand-os/public/page.tsx"
|
|
78
126
|
},
|
|
79
127
|
{
|
|
80
128
|
"slug": "landing-hero-carousel",
|
|
@@ -84,7 +132,7 @@
|
|
|
84
132
|
"source": "cescadesigns",
|
|
85
133
|
"repoPath": "cookbook/layouts/landing-hero-carousel",
|
|
86
134
|
"pullPaths": [
|
|
87
|
-
"
|
|
135
|
+
"app/preview/landing-hero-carousel"
|
|
88
136
|
],
|
|
89
137
|
"files": [],
|
|
90
138
|
"dependencies": [],
|
|
@@ -94,7 +142,8 @@
|
|
|
94
142
|
"carousel",
|
|
95
143
|
"image",
|
|
96
144
|
"cms"
|
|
97
|
-
]
|
|
145
|
+
],
|
|
146
|
+
"primaryFile": "src/HeroSection.tsx"
|
|
98
147
|
},
|
|
99
148
|
{
|
|
100
149
|
"slug": "landing-asymmetric-masonry",
|
|
@@ -104,7 +153,7 @@
|
|
|
104
153
|
"source": "rahmanef.com",
|
|
105
154
|
"repoPath": "cookbook/layouts/landing-asymmetric-masonry",
|
|
106
155
|
"pullPaths": [
|
|
107
|
-
"
|
|
156
|
+
"app/preview/landing-asymmetric-masonry"
|
|
108
157
|
],
|
|
109
158
|
"files": [],
|
|
110
159
|
"dependencies": [],
|
|
@@ -114,7 +163,8 @@
|
|
|
114
163
|
"portfolio",
|
|
115
164
|
"masonry",
|
|
116
165
|
"scroll-reveal"
|
|
117
|
-
]
|
|
166
|
+
],
|
|
167
|
+
"primaryFile": "src/PortfolioGrid.tsx"
|
|
118
168
|
},
|
|
119
169
|
{
|
|
120
170
|
"slug": "landing-bento",
|
|
@@ -124,7 +174,7 @@
|
|
|
124
174
|
"source": "synthesized",
|
|
125
175
|
"repoPath": "cookbook/layouts/landing-bento",
|
|
126
176
|
"pullPaths": [
|
|
127
|
-
"
|
|
177
|
+
"app/preview/landing-bento"
|
|
128
178
|
],
|
|
129
179
|
"files": [],
|
|
130
180
|
"dependencies": [],
|
|
@@ -133,7 +183,8 @@
|
|
|
133
183
|
"marketing",
|
|
134
184
|
"bento",
|
|
135
185
|
"features"
|
|
136
|
-
]
|
|
186
|
+
],
|
|
187
|
+
"primaryFile": "README.md"
|
|
137
188
|
},
|
|
138
189
|
{
|
|
139
190
|
"slug": "landing-kinetic-text",
|
|
@@ -143,7 +194,7 @@
|
|
|
143
194
|
"source": "rahmanef.com",
|
|
144
195
|
"repoPath": "cookbook/layouts/landing-kinetic-text",
|
|
145
196
|
"pullPaths": [
|
|
146
|
-
"
|
|
197
|
+
"app/preview/landing-kinetic-text"
|
|
147
198
|
],
|
|
148
199
|
"files": [],
|
|
149
200
|
"dependencies": [],
|
|
@@ -152,7 +203,8 @@
|
|
|
152
203
|
"marketing",
|
|
153
204
|
"motion",
|
|
154
205
|
"type"
|
|
155
|
-
]
|
|
206
|
+
],
|
|
207
|
+
"primaryFile": "README.md"
|
|
156
208
|
},
|
|
157
209
|
{
|
|
158
210
|
"slug": "dashboard-three-column",
|
|
@@ -162,7 +214,7 @@
|
|
|
162
214
|
"source": "kitab-core",
|
|
163
215
|
"repoPath": "cookbook/layouts/dashboard-three-column",
|
|
164
216
|
"pullPaths": [
|
|
165
|
-
"
|
|
217
|
+
"app/preview/dashboard-three-column"
|
|
166
218
|
],
|
|
167
219
|
"files": [],
|
|
168
220
|
"dependencies": [],
|
|
@@ -172,7 +224,8 @@
|
|
|
172
224
|
"three-column",
|
|
173
225
|
"resizable",
|
|
174
226
|
"responsive"
|
|
175
|
-
]
|
|
227
|
+
],
|
|
228
|
+
"primaryFile": "template-base/frontend/shared/ui/layout/container/three-column/ThreeColumnLayout.tsx"
|
|
176
229
|
},
|
|
177
230
|
{
|
|
178
231
|
"slug": "dashboard-ide",
|
|
@@ -182,7 +235,7 @@
|
|
|
182
235
|
"source": "synthesized",
|
|
183
236
|
"repoPath": "cookbook/layouts/dashboard-ide",
|
|
184
237
|
"pullPaths": [
|
|
185
|
-
"
|
|
238
|
+
"app/preview/dashboard-ide"
|
|
186
239
|
],
|
|
187
240
|
"files": [],
|
|
188
241
|
"dependencies": [],
|
|
@@ -191,7 +244,8 @@
|
|
|
191
244
|
"dashboard",
|
|
192
245
|
"ide",
|
|
193
246
|
"editor"
|
|
194
|
-
]
|
|
247
|
+
],
|
|
248
|
+
"primaryFile": "README.md"
|
|
195
249
|
},
|
|
196
250
|
{
|
|
197
251
|
"slug": "dashboard-mobile-dock",
|
|
@@ -201,7 +255,7 @@
|
|
|
201
255
|
"source": "kitab-core",
|
|
202
256
|
"repoPath": "cookbook/layouts/dashboard-mobile-dock",
|
|
203
257
|
"pullPaths": [
|
|
204
|
-
"
|
|
258
|
+
"app/preview/dashboard-mobile-dock"
|
|
205
259
|
],
|
|
206
260
|
"files": [],
|
|
207
261
|
"dependencies": [],
|
|
@@ -210,7 +264,8 @@
|
|
|
210
264
|
"dashboard",
|
|
211
265
|
"mobile",
|
|
212
266
|
"pwa"
|
|
213
|
-
]
|
|
267
|
+
],
|
|
268
|
+
"primaryFile": "template-base/frontend/shared/ui/layout/dashboard/MobileDashboardShell.tsx"
|
|
214
269
|
},
|
|
215
270
|
{
|
|
216
271
|
"slug": "cms-public-storefront",
|
|
@@ -220,7 +275,7 @@
|
|
|
220
275
|
"source": "kitab-core cms-lite",
|
|
221
276
|
"repoPath": "cookbook/layouts/cms-public-storefront",
|
|
222
277
|
"pullPaths": [
|
|
223
|
-
"
|
|
278
|
+
"app/preview/cms-public-storefront"
|
|
224
279
|
],
|
|
225
280
|
"files": [],
|
|
226
281
|
"dependencies": [],
|
|
@@ -229,6 +284,320 @@
|
|
|
229
284
|
"cms",
|
|
230
285
|
"ecommerce",
|
|
231
286
|
"storefront"
|
|
287
|
+
],
|
|
288
|
+
"primaryFile": "README.md"
|
|
289
|
+
}
|
|
290
|
+
],
|
|
291
|
+
"recipes": [
|
|
292
|
+
{
|
|
293
|
+
"slug": "block-editor",
|
|
294
|
+
"title": "Notion-Style Block Editor",
|
|
295
|
+
"description": "21-block contenteditable editor with slash command menu, markdown shortcuts, drag handles. Real-time via Convex.",
|
|
296
|
+
"source": "notion-page-clone",
|
|
297
|
+
"files": [
|
|
298
|
+
"frontend/slices/notion/slices/editor/BlockEditor.tsx",
|
|
299
|
+
"frontend/slices/notion/slices/editor/SlashMenu.tsx",
|
|
300
|
+
"frontend/slices/notion/slices/editor/blockSpecs.ts"
|
|
301
|
+
],
|
|
302
|
+
"exampleCode": "import { BlockEditor } from \"@/frontend/slices/notion/slices/editor/BlockEditor\";\n\n<BlockEditor pageId={pageId} />",
|
|
303
|
+
"agentRecipe": "Already copied at frontend/slices/notion/slices/editor/. See PORT-NOTION.md for Vite→Next port checklist (routing rewrite, use-client markers, Convex API surface rename).",
|
|
304
|
+
"tags": [
|
|
305
|
+
"editor",
|
|
306
|
+
"notion",
|
|
307
|
+
"blocks",
|
|
308
|
+
"real-time"
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"slug": "page-tree-sidebar",
|
|
313
|
+
"title": "Page Tree Sidebar",
|
|
314
|
+
"description": "Hierarchical workspace sidebar with @dnd-kit drag-drop reordering, favorites, recents.",
|
|
315
|
+
"source": "notion-page-clone",
|
|
316
|
+
"files": [
|
|
317
|
+
"frontend/slices/notion/slices/workspace-sidebar/components/WorkspaceSidebar.tsx",
|
|
318
|
+
"frontend/slices/notion/slices/workspace-sidebar/components/SortablePageRow.tsx"
|
|
319
|
+
],
|
|
320
|
+
"exampleCode": "import { WorkspaceSidebar } from \"@/frontend/slices/notion/slices/workspace-sidebar/components/WorkspaceSidebar\";\n\n<WorkspaceSidebar />",
|
|
321
|
+
"agentRecipe": "Mount WorkspaceSidebar inside the left slot of <ThreeColumnLayout>. State backed by Zustand store at frontend/slices/notion/shared/lib/store.tsx.",
|
|
322
|
+
"tags": [
|
|
323
|
+
"sidebar",
|
|
324
|
+
"tree",
|
|
325
|
+
"dnd-kit",
|
|
326
|
+
"navigation"
|
|
327
|
+
]
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"slug": "multi-block-selection",
|
|
331
|
+
"title": "Multi-Block Selection",
|
|
332
|
+
"description": "Marquee + click+shift selection of editor blocks with floating toolbar. Bulk actions: delete, duplicate, convert.",
|
|
333
|
+
"source": "notion-page-clone",
|
|
334
|
+
"files": [
|
|
335
|
+
"frontend/slices/notion/slices/block-selection/components/BlockSelectionProvider.tsx",
|
|
336
|
+
"frontend/slices/notion/slices/block-selection/components/MarqueeOverlay.tsx"
|
|
337
|
+
],
|
|
338
|
+
"exampleCode": "import { BlockSelectionProvider } from \"@/frontend/slices/notion/slices/block-selection/components/BlockSelectionProvider\";\n\n<BlockSelectionProvider>\n <BlockEditor />\n</BlockSelectionProvider>",
|
|
339
|
+
"agentRecipe": "Wrap BlockEditor with BlockSelectionProvider. The marquee overlay attaches to document; toolbar floats above the selection bounding box.",
|
|
340
|
+
"tags": [
|
|
341
|
+
"selection",
|
|
342
|
+
"editor",
|
|
343
|
+
"bulk",
|
|
344
|
+
"marquee"
|
|
345
|
+
]
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"slug": "database-views",
|
|
349
|
+
"title": "Database Views (11 types)",
|
|
350
|
+
"description": "Properties+rows database with 11 view types: table, board, calendar, timeline, chart, gallery, map. Per-view filter/sort/group.",
|
|
351
|
+
"source": "notion-page-clone",
|
|
352
|
+
"files": [
|
|
353
|
+
"frontend/slices/notion/slices/databases/DatabaseBlock.tsx",
|
|
354
|
+
"frontend/slices/notion/slices/databases/views/TableView.tsx",
|
|
355
|
+
"frontend/slices/notion/slices/databases/views/BoardView.tsx"
|
|
356
|
+
],
|
|
357
|
+
"exampleCode": "import { DatabaseBlock } from \"@/frontend/slices/notion/slices/databases/DatabaseBlock\";\n\n<DatabaseBlock databaseId={dbId} />",
|
|
358
|
+
"agentRecipe": "DatabaseBlock auto-routes to the active view component. Add custom property types by extending PropertyCell.tsx.",
|
|
359
|
+
"tags": [
|
|
360
|
+
"database",
|
|
361
|
+
"views",
|
|
362
|
+
"table",
|
|
363
|
+
"kanban",
|
|
364
|
+
"calendar"
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"slug": "command-palette",
|
|
369
|
+
"title": "Command Palette (⌘K)",
|
|
370
|
+
"description": "Cmd+K modal: feature navigation, workspace switching, theme, sign-out, custom commands. Auto-builds from feature registry.",
|
|
371
|
+
"source": "kitab-core + notion-page-clone",
|
|
372
|
+
"files": [
|
|
373
|
+
"frontend/shared/foundation/utils/system/command-menu/components.tsx"
|
|
374
|
+
],
|
|
375
|
+
"exampleCode": "import { CommandMenu } from \"@/frontend/shared/foundation/utils/system/command-menu/components\";\n\n<CommandMenu actions={customActions} />",
|
|
376
|
+
"agentRecipe": "Mount CommandMenu once at the app shell level. It listens for Cmd+K globally. Pass extra commands via the actions prop or register via the command-registry.",
|
|
377
|
+
"tags": [
|
|
378
|
+
"palette",
|
|
379
|
+
"cmd-k",
|
|
380
|
+
"navigation"
|
|
381
|
+
]
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"slug": "comments-threaded",
|
|
385
|
+
"title": "Threaded Comments",
|
|
386
|
+
"description": "Page + block-level threaded comments with resolved state. Real-time via Convex.",
|
|
387
|
+
"source": "notion-page-clone",
|
|
388
|
+
"files": [
|
|
389
|
+
"frontend/slices/notion/slices/comments/components/BlockCommentsPopover.tsx",
|
|
390
|
+
"frontend/slices/notion/slices/comments/hooks/useComments.ts"
|
|
391
|
+
],
|
|
392
|
+
"exampleCode": "import { BlockCommentsPopover } from \"@/frontend/slices/notion/slices/comments/components/BlockCommentsPopover\";\n\n<BlockCommentsPopover blockId={blockId} pageId={pageId} />",
|
|
393
|
+
"agentRecipe": "Anchor comments by passing pageId (always) and optional blockId. Use useComments(blockId) hook for the reactive list.",
|
|
394
|
+
"tags": [
|
|
395
|
+
"comments",
|
|
396
|
+
"real-time",
|
|
397
|
+
"threading"
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
"slug": "theme-preset-switcher",
|
|
402
|
+
"title": "Theme Preset Switcher",
|
|
403
|
+
"description": "Runtime theme swap (colors + fonts + shadows + tracking). OKLch CSS vars per preset. Persists to localStorage + Convex.",
|
|
404
|
+
"source": "rahmanef.com",
|
|
405
|
+
"files": [
|
|
406
|
+
"frontend/shared/theme/theme-presets.ts",
|
|
407
|
+
"frontend/shared/ui/components/theme-preset-switcher.tsx"
|
|
408
|
+
],
|
|
409
|
+
"exampleCode": "import { ThemePresetSwitcher } from \"@/frontend/shared/ui/components/theme-preset-switcher\";\n\n<ThemePresetSwitcher />",
|
|
410
|
+
"agentRecipe": "Add a new preset by appending a CSS block in app/globals.css with [data-theme=\"<name>\"], then register in preset-groups.ts.",
|
|
411
|
+
"tags": [
|
|
412
|
+
"theme",
|
|
413
|
+
"presets",
|
|
414
|
+
"oklch",
|
|
415
|
+
"design-system"
|
|
416
|
+
]
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"slug": "contact-form-resend",
|
|
420
|
+
"title": "Contact Form + Resend",
|
|
421
|
+
"description": "Contact form posting to Resend email API. Server Action + Zod input validation.",
|
|
422
|
+
"source": "cescadesigns",
|
|
423
|
+
"files": [
|
|
424
|
+
"recipes/contact-form-resend/src/page.tsx"
|
|
425
|
+
],
|
|
426
|
+
"exampleCode": "// app/api/contact/route.ts\nimport { Resend } from \"resend\";\nconst resend = new Resend(process.env.RESEND_API_KEY!);\n\nexport async function POST(req: Request) {\n const data = await req.formData();\n await resend.emails.send({\n from: \"form@yourdomain.com\",\n to: \"you@yourdomain.com\",\n subject: `From ${data.get(\"name\")}`,\n html: `<p>${data.get(\"message\")}</p>`,\n });\n return Response.json({ ok: true });\n}",
|
|
427
|
+
"agentRecipe": "Wire ContactForm.tsx (form action /api/contact) to the route handler. Always validate inputs with Zod server-side.",
|
|
428
|
+
"tags": [
|
|
429
|
+
"form",
|
|
430
|
+
"email",
|
|
431
|
+
"resend",
|
|
432
|
+
"server-action"
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
],
|
|
436
|
+
"features": [
|
|
437
|
+
{
|
|
438
|
+
"slug": "ai-sdk-openrouter",
|
|
439
|
+
"title": "AI SDK — OpenRouter Router",
|
|
440
|
+
"category": "ai",
|
|
441
|
+
"description": "Tier-routed LLM calls via OpenRouter. Nano (Haiku/4o-mini) for classification, mid (Sonnet/4o) for drafting, flagship (Opus) for deep reasoning. Cost log + retry baked in.",
|
|
442
|
+
"source": "@openrouter/ai-sdk-provider + ai",
|
|
443
|
+
"docsUrl": "https://sdk.vercel.ai/docs",
|
|
444
|
+
"install": "npm i ai @openrouter/ai-sdk-provider",
|
|
445
|
+
"npmPackages": [
|
|
446
|
+
"ai",
|
|
447
|
+
"@openrouter/ai-sdk-provider"
|
|
448
|
+
],
|
|
449
|
+
"exampleCode": "// convex/shared/ai/router.ts\nimport { action } from \"./_generated/server\";\nimport { v } from \"convex/values\";\nimport { generateText } from \"ai\";\nimport { createOpenRouter } from \"@openrouter/ai-sdk-provider\";\n\nconst router = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY! });\n\nconst TIER_TO_MODEL = {\n nano: \"anthropic/claude-haiku-4-5\",\n mid: \"anthropic/claude-sonnet-4-6\",\n flagship: \"anthropic/claude-opus-4-7\",\n};\n\nexport const callModel = action({\n args: {\n feature: v.string(),\n prompt: v.string(),\n tier: v.union(v.literal(\"nano\"), v.literal(\"mid\"), v.literal(\"flagship\")),\n },\n handler: async (ctx, { feature, prompt, tier }) => {\n const { text, usage } = await generateText({\n model: router(TIER_TO_MODEL[tier]),\n prompt,\n });\n await ctx.runMutation(internal.ai.logUsage, { feature, tier, usage });\n return text;\n },\n});",
|
|
450
|
+
"agentRecipe": "Wrap every AI call through ai-router action. Pick tier based on workload: nano for spam-flag/headline-suggest, mid for chat/draft, flagship for methodology-review. Log token usage to ai_usage table for cost dashboard.",
|
|
451
|
+
"tags": [
|
|
452
|
+
"ai",
|
|
453
|
+
"llm",
|
|
454
|
+
"openrouter",
|
|
455
|
+
"vercel-ai-sdk"
|
|
456
|
+
]
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
"slug": "convex-auth",
|
|
460
|
+
"title": "Convex Auth — Email Magic Link",
|
|
461
|
+
"category": "auth",
|
|
462
|
+
"description": "@convex-dev/auth with email magic link only. No Clerk, no NextAuth. Self-hosted Convex friendly. Hard mandate per kitab CLAUDE.md.",
|
|
463
|
+
"source": "@convex-dev/auth",
|
|
464
|
+
"docsUrl": "https://labs.convex.dev/auth",
|
|
465
|
+
"install": "npm i @convex-dev/auth @auth/core resend",
|
|
466
|
+
"npmPackages": [
|
|
467
|
+
"@convex-dev/auth",
|
|
468
|
+
"@auth/core",
|
|
469
|
+
"resend"
|
|
470
|
+
],
|
|
471
|
+
"exampleCode": "// convex/auth.ts\nimport { convexAuth } from \"@convex-dev/auth/server\";\nimport { ResendOTP } from \"./auth/ResendOTP\";\n\nexport const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({\n providers: [ResendOTP],\n});\n\n// app/proxy.ts (Next 16 — NOT middleware.ts)\nimport { convexAuthNextjsMiddleware } from \"@convex-dev/auth/nextjs/server\";\nexport default convexAuthNextjsMiddleware();",
|
|
472
|
+
"agentRecipe": "Mount auth in convex/auth.ts. Wire ResendOTP for magic-link delivery. Use convexAuthNextjsMiddleware in app/proxy.ts (Next 16 renamed middleware.ts → proxy.ts). Forbid Clerk per CLAUDE.md.",
|
|
473
|
+
"tags": [
|
|
474
|
+
"auth",
|
|
475
|
+
"convex",
|
|
476
|
+
"email-magic-link",
|
|
477
|
+
"no-clerk"
|
|
478
|
+
]
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"slug": "broadcast-channel-sync",
|
|
482
|
+
"title": "BroadcastChannel — Cross-iframe Live Sync",
|
|
483
|
+
"category": "realtime",
|
|
484
|
+
"description": "Same-origin iframe live sync without backend. Used in T1 split preview tab — submit form di Public, action propagates ke Admin secara realtime via window.BroadcastChannel.",
|
|
485
|
+
"source": "Web Platform — BroadcastChannel API",
|
|
486
|
+
"docsUrl": "https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API",
|
|
487
|
+
"install": "// no install — Web Platform API",
|
|
488
|
+
"npmPackages": [],
|
|
489
|
+
"exampleCode": "\"use client\";\nimport * as React from \"react\";\n\nexport function StoreProvider({ children }) {\n const [state, baseDispatch] = React.useReducer(reducer, SEED_STATE);\n const channelRef = React.useRef<BroadcastChannel | null>(null);\n\n React.useEffect(() => {\n const ch = new BroadcastChannel(\"pbos:sync\");\n channelRef.current = ch;\n ch.onmessage = (e) => baseDispatch(e.data);\n return () => ch.close();\n }, []);\n\n const dispatch = React.useCallback((action) => {\n baseDispatch(action);\n channelRef.current?.postMessage(action);\n }, []);\n\n return <Ctx.Provider value={{ state, dispatch }}>{children}</Ctx.Provider>;\n}",
|
|
490
|
+
"agentRecipe": "Use BroadcastChannel only for demo / cross-iframe state mirroring. Production data still goes through Convex realtime. The channel does not echo to the sender so no loop.",
|
|
491
|
+
"tags": [
|
|
492
|
+
"realtime",
|
|
493
|
+
"broadcast-channel",
|
|
494
|
+
"cross-iframe",
|
|
495
|
+
"demo-pattern"
|
|
496
|
+
]
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"slug": "convex-vector-search",
|
|
500
|
+
"title": "Convex Vector Index — Semantic Search",
|
|
501
|
+
"category": "search",
|
|
502
|
+
"description": "Built-in vector index on any Convex table. Embed via OpenAI text-embedding-3-small (1536-dim), query via vectorIndex().",
|
|
503
|
+
"source": "convex (built-in)",
|
|
504
|
+
"docsUrl": "https://docs.convex.dev/database/vector-search",
|
|
505
|
+
"install": "npm i openai",
|
|
506
|
+
"npmPackages": [
|
|
507
|
+
"openai"
|
|
508
|
+
],
|
|
509
|
+
"exampleCode": "// convex/schema.ts\nposts: defineTable({\n title: v.string(),\n body: v.string(),\n embedding: v.array(v.float64()),\n}).vectorIndex(\"by_embedding\", {\n vectorField: \"embedding\",\n dimensions: 1536,\n filterFields: [\"workspaceId\", \"status\"],\n}),\n\n// convex/posts.ts\nexport const search = action({\n args: { query: v.string(), workspaceId: v.id(\"workspaces\") },\n handler: async (ctx, args) => {\n const emb = await embed(args.query);\n return await ctx.vectorSearch(\"posts\", \"by_embedding\", {\n vector: emb,\n limit: 10,\n filter: (q) => q.eq(\"workspaceId\", args.workspaceId),\n });\n },\n});",
|
|
510
|
+
"agentRecipe": "Add embedding field + vectorIndex per searchable table. Re-embed on upsert via Convex action. Cache embeddings — don't re-call OpenAI on every read.",
|
|
511
|
+
"tags": [
|
|
512
|
+
"search",
|
|
513
|
+
"vector",
|
|
514
|
+
"convex",
|
|
515
|
+
"rag"
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
"slug": "resend-newsletter",
|
|
520
|
+
"title": "Resend — Transactional & Newsletter",
|
|
521
|
+
"category": "email",
|
|
522
|
+
"description": "Transactional email + newsletter blast via Resend. Double opt-in flow + audience segmentation. Magic-link delivery for Convex Auth.",
|
|
523
|
+
"source": "resend + react-email",
|
|
524
|
+
"docsUrl": "https://resend.com/docs",
|
|
525
|
+
"install": "npm i resend react-email @react-email/components",
|
|
526
|
+
"npmPackages": [
|
|
527
|
+
"resend",
|
|
528
|
+
"react-email",
|
|
529
|
+
"@react-email/components"
|
|
530
|
+
],
|
|
531
|
+
"exampleCode": "// convex/shared/email/resend.ts\nimport { Resend } from \"resend\";\nimport { action } from \"../../_generated/server\";\n\nconst resend = new Resend(process.env.RESEND_API_KEY!);\n\nexport const sendNewsletter = action({\n args: { audienceId: v.string(), subject: v.string(), html: v.string() },\n handler: async (_, args) => {\n await resend.broadcasts.create({\n audienceId: args.audienceId,\n from: \"lorem.dev <hi@lorem.dev>\",\n subject: args.subject,\n html: args.html,\n });\n },\n});",
|
|
532
|
+
"agentRecipe": "Use Resend Audiences API for newsletter — store subscriber emails in Convex too for segmentation. Double opt-in: subscriber.create with status 'pending' → click link → status 'confirmed'.",
|
|
533
|
+
"tags": [
|
|
534
|
+
"email",
|
|
535
|
+
"resend",
|
|
536
|
+
"newsletter",
|
|
537
|
+
"transactional"
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
"slug": "midtrans-payment",
|
|
542
|
+
"title": "Midtrans — Indonesia Payment",
|
|
543
|
+
"category": "payment",
|
|
544
|
+
"description": "Pembayaran lokal Indonesia via Midtrans Snap (BCA, Mandiri, BRI, e-wallet GoPay/OVO/Dana, QRIS). Webhook untuk konfirmasi.",
|
|
545
|
+
"source": "midtrans-client",
|
|
546
|
+
"docsUrl": "https://docs.midtrans.com",
|
|
547
|
+
"install": "npm i midtrans-client",
|
|
548
|
+
"npmPackages": [
|
|
549
|
+
"midtrans-client"
|
|
550
|
+
],
|
|
551
|
+
"exampleCode": "// convex/shared/billing/midtrans.ts\nimport midtransClient from \"midtrans-client\";\nimport { action } from \"../../_generated/server\";\n\nconst snap = new midtransClient.Snap({\n isProduction: false,\n serverKey: process.env.MIDTRANS_SERVER_KEY!,\n});\n\nexport const createPayment = action({\n args: { orderId: v.string(), amount: v.number(), customer: v.any() },\n handler: async (_, args) => {\n const tx = await snap.createTransaction({\n transaction_details: { order_id: args.orderId, gross_amount: args.amount },\n customer_details: args.customer,\n });\n return tx.redirect_url;\n },\n});",
|
|
552
|
+
"agentRecipe": "Midtrans Snap untuk pembayaran instant. Webhook ke Convex HTTP action /api/midtrans-callback untuk update order status. Ingat: PPN 11% sudah included di amount, jangan double-count.",
|
|
553
|
+
"tags": [
|
|
554
|
+
"payment",
|
|
555
|
+
"midtrans",
|
|
556
|
+
"indonesia",
|
|
557
|
+
"qris"
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
"slug": "mdx-blog",
|
|
562
|
+
"title": "MDX — Blog Content",
|
|
563
|
+
"category": "content",
|
|
564
|
+
"description": "Markdown-with-JSX untuk blog post. Auto-generate ToC, reading-time, syntax highlight, plus embed React components inline.",
|
|
565
|
+
"source": "next-mdx-remote",
|
|
566
|
+
"docsUrl": "https://github.com/hashicorp/next-mdx-remote",
|
|
567
|
+
"install": "npm i next-mdx-remote rehype-pretty-code remark-gfm reading-time",
|
|
568
|
+
"npmPackages": [
|
|
569
|
+
"next-mdx-remote",
|
|
570
|
+
"rehype-pretty-code",
|
|
571
|
+
"remark-gfm",
|
|
572
|
+
"reading-time"
|
|
573
|
+
],
|
|
574
|
+
"exampleCode": "// app/blog/[slug]/page.tsx\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport readingTime from \"reading-time\";\n\nexport default async function Page({ params }) {\n const { slug } = await params;\n const post = await getPost(slug);\n const stats = readingTime(post.body);\n\n return (\n <article>\n <h1>{post.title}</h1>\n <p>{stats.text}</p>\n <MDXRemote\n source={post.body}\n options={{ mdxOptions: { rehypePlugins: [rehypePrettyCode], remarkPlugins: [remarkGfm] } }}\n />\n </article>\n );\n}",
|
|
575
|
+
"agentRecipe": "Store post body sebagai markdown di Convex. Render dengan MDXRemote di [slug]/page.tsx. Auto-extract headings ke ToC via remark plugin custom.",
|
|
576
|
+
"tags": [
|
|
577
|
+
"mdx",
|
|
578
|
+
"markdown",
|
|
579
|
+
"blog",
|
|
580
|
+
"content"
|
|
581
|
+
]
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"slug": "cal-com-booking",
|
|
585
|
+
"title": "Cal.com — Booking Embed",
|
|
586
|
+
"category": "data",
|
|
587
|
+
"description": "Embed Cal.com booking widget di halaman Services. Self-hosted atau cloud. Webhook ke Convex untuk sync booking ke leads table.",
|
|
588
|
+
"source": "@calcom/embed-react",
|
|
589
|
+
"docsUrl": "https://cal.com/docs/integrations/web-app/embed",
|
|
590
|
+
"install": "npm i @calcom/embed-react",
|
|
591
|
+
"npmPackages": [
|
|
592
|
+
"@calcom/embed-react"
|
|
593
|
+
],
|
|
594
|
+
"exampleCode": "\"use client\";\nimport Cal from \"@calcom/embed-react\";\n\nexport function CalEmbed({ eventType }: { eventType: string }) {\n return (\n <Cal\n calLink={`lorem/${eventType}`}\n style={{ width: \"100%\", height: \"600px\", overflow: \"scroll\" }}\n config={{ layout: \"month_view\", theme: \"dark\" }}\n />\n );\n}",
|
|
595
|
+
"agentRecipe": "Embed Cal.com via @calcom/embed-react di halaman services. Configure webhook di Cal.com dashboard → POST ke /api/cal-webhook → create lead di Convex.",
|
|
596
|
+
"tags": [
|
|
597
|
+
"booking",
|
|
598
|
+
"cal-com",
|
|
599
|
+
"scheduling",
|
|
600
|
+
"embed"
|
|
232
601
|
]
|
|
233
602
|
}
|
|
234
603
|
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# __APP_NAME__
|
|
2
|
+
|
|
3
|
+
Scaffolded with [`rahman-resources`](https://www.npmjs.com/package/rahman-resources) — Next 16 + React 19 + Convex (self-hosted) + Tailwind 4 + shadcn/ui.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --legacy-peer-deps
|
|
9
|
+
cp .env.example .env.local # fill NEXT_PUBLIC_CONVEX_URL etc.
|
|
10
|
+
npx convex dev --once # generates convex/_generated
|
|
11
|
+
npm run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Add a layout / recipe / feature
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx rahman-resources list
|
|
18
|
+
npx rahman-resources info <slug>
|
|
19
|
+
npx rahman-resources add personal-brand-os . # full-app template (T1)
|
|
20
|
+
npx rahman-resources add ai-sdk-openrouter . # feature (npm install)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Hard rules
|
|
24
|
+
|
|
25
|
+
- **NO Clerk.** Auth = `@convex-dev/auth`.
|
|
26
|
+
- **shadcn primitives only** — no raw `<dialog>`, `<input type=date|file>`.
|
|
27
|
+
- Use `proxy.ts` (not `middleware.ts`) on Next 16.
|
|
28
|
+
- `convex/_generated` MUST be committed before deploy.
|
|
29
|
+
|
|
30
|
+
## Stack
|
|
31
|
+
|
|
32
|
+
| | |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Framework | Next.js 16 (App Router + cacheComponents) |
|
|
35
|
+
| UI | React 19 + Tailwind 4 + shadcn |
|
|
36
|
+
| Backend | Convex (self-hosted compatible) |
|
|
37
|
+
| Auth | `@convex-dev/auth` (Password provider by default) |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Convex (self-hosted or cloud)
|
|
2
|
+
NEXT_PUBLIC_CONVEX_URL=
|
|
3
|
+
|
|
4
|
+
# @convex-dev/auth — required for auth signing
|
|
5
|
+
JWKS=
|
|
6
|
+
JWT_PRIVATE_KEY=
|
|
7
|
+
SITE_URL=http://localhost:3000
|
|
8
|
+
|
|
9
|
+
# Server actions encryption (multi-instance — pin once, share across instances)
|
|
10
|
+
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=
|
|
11
|
+
|
|
12
|
+
# Optional OAuth providers
|
|
13
|
+
AUTH_GITHUB_ID=
|
|
14
|
+
AUTH_GITHUB_SECRET=
|
|
15
|
+
AUTH_GOOGLE_ID=
|
|
16
|
+
AUTH_GOOGLE_SECRET=
|