rahman-resources 0.2.1 → 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 +90 -6
- package/lib/manifest.json +1 -1
- 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 +2 -2
package/README.md
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
# rahman-resources
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffolder + template installer for the [Rahman Resources kitab](https://github.com/rahmanef63/resource-site).
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx rahman-resources
|
|
9
|
-
|
|
8
|
+
npx rahman-resources init my-app
|
|
9
|
+
cd my-app
|
|
10
|
+
cp .env.example .env.local # fill NEXT_PUBLIC_CONVEX_URL
|
|
11
|
+
npm install --legacy-peer-deps
|
|
12
|
+
npx convex dev --once # generates convex/_generated
|
|
13
|
+
npm run dev
|
|
10
14
|
```
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
`init` ships a minimal Next 16 + React 19 + Convex + Tailwind 4 + shadcn/ui skeleton (~18 files). Then drop in any layout/recipe/feature with `add`.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
13
19
|
|
|
14
20
|
```bash
|
|
15
|
-
npx rahman-resources
|
|
21
|
+
npx rahman-resources init <app-name> # scaffold fresh project
|
|
22
|
+
npx rahman-resources add <slug> [target-dir] # drop in a layout/recipe/feature
|
|
23
|
+
npx rahman-resources list [layouts|recipes|features]
|
|
24
|
+
npx rahman-resources info <slug>
|
|
16
25
|
```
|
|
17
26
|
|
|
18
27
|
### Inspect a template
|
|
@@ -24,11 +33,11 @@ npx rahman-resources info personal-brand-os
|
|
|
24
33
|
### Install into a project
|
|
25
34
|
|
|
26
35
|
```bash
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
npx rahman-resources add personal-brand-os .
|
|
36
|
+
# fresh
|
|
37
|
+
npx rahman-resources init my-app
|
|
38
|
+
cd my-app && npx rahman-resources add personal-brand-os .
|
|
30
39
|
|
|
31
|
-
# existing
|
|
40
|
+
# existing
|
|
32
41
|
cd existing-app
|
|
33
42
|
npx rahman-resources add personal-brand-os .
|
|
34
43
|
```
|
package/bin/cli.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// rahman-resources — installer for the Rahman kitab.
|
|
3
3
|
// Usage:
|
|
4
|
+
// npx rahman-resources init <app-name> [--lite]
|
|
4
5
|
// npx rahman-resources add <slug> [target-dir]
|
|
5
6
|
// npx rahman-resources list [layouts|recipes|features]
|
|
6
7
|
// npx rahman-resources info <slug>
|
|
7
8
|
|
|
8
9
|
import { createRequire } from "node:module";
|
|
9
10
|
import { spawn } from "node:child_process";
|
|
10
|
-
import { existsSync } from "node:fs";
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "node:fs";
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import { fileURLToPath } from "node:url";
|
|
13
14
|
|
|
@@ -25,13 +26,21 @@ const KINDS = /** @type {const} */ (["layout", "recipe", "feature"]);
|
|
|
25
26
|
|
|
26
27
|
const [, , cmd, ...rest] = process.argv;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Defer to next tick so all module-level const declarations finish initializing
|
|
30
|
+
// before the dispatch reaches functions that reference them.
|
|
31
|
+
queueMicrotask(() =>
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
console.error(kleur.red("✖"), err.message ?? err);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
32
37
|
|
|
33
38
|
async function main() {
|
|
34
39
|
switch (cmd) {
|
|
40
|
+
case "init":
|
|
41
|
+
case "create":
|
|
42
|
+
case "new":
|
|
43
|
+
return runInit(rest);
|
|
35
44
|
case "add":
|
|
36
45
|
return runAdd(rest);
|
|
37
46
|
case "list":
|
|
@@ -62,14 +71,16 @@ function printVersion() {
|
|
|
62
71
|
|
|
63
72
|
function printHelp() {
|
|
64
73
|
console.log(`
|
|
65
|
-
${kleur.bold("rahman-resources")} — install templates, recipes,
|
|
74
|
+
${kleur.bold("rahman-resources")} — scaffold + install templates, recipes, features
|
|
66
75
|
|
|
67
76
|
${kleur.bold("Usage:")}
|
|
77
|
+
npx rahman-resources init <app-name> [--lite]
|
|
68
78
|
npx rahman-resources add <slug> [target-dir]
|
|
69
79
|
npx rahman-resources list [layouts|recipes|features]
|
|
70
80
|
npx rahman-resources info <slug>
|
|
71
81
|
|
|
72
82
|
${kleur.bold("Examples:")}
|
|
83
|
+
npx rahman-resources init my-app --lite ${kleur.dim("# scaffold from template-base, prune notion+builder+communications")}
|
|
73
84
|
npx rahman-resources add personal-brand-os my-app ${kleur.dim("# layout — pulls folders + installs deps")}
|
|
74
85
|
npx rahman-resources add ai-sdk-openrouter my-app ${kleur.dim("# feature — runs npm install")}
|
|
75
86
|
npx rahman-resources add block-editor ${kleur.dim("# recipe — prints code + source URL")}
|
|
@@ -136,6 +147,79 @@ ${t.description}
|
|
|
136
147
|
console.log(`${kleur.dim(`Source: ${t.source ?? "—"}`)}\n`);
|
|
137
148
|
}
|
|
138
149
|
|
|
150
|
+
// Files in lib/starter prefixed with `_` get renamed on copy.
|
|
151
|
+
// Done because npm pack filters .gitignore and warns on nested package.json.
|
|
152
|
+
const STARTER_RENAME_PAIRS = [
|
|
153
|
+
["_package", "package"],
|
|
154
|
+
["_gitignore", ".gitignore"],
|
|
155
|
+
["_env", ".env"],
|
|
156
|
+
["_README", "README"],
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
function renameStarterFile(name) {
|
|
160
|
+
for (const pair of STARTER_RENAME_PAIRS) {
|
|
161
|
+
const f = pair[0];
|
|
162
|
+
const t = pair[1];
|
|
163
|
+
if (name === f || name.startsWith(f + ".")) return t + name.slice(f.length);
|
|
164
|
+
}
|
|
165
|
+
return name;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function copyStarterTree(src, dest, appName, slug) {
|
|
169
|
+
mkdirSync(dest, { recursive: true });
|
|
170
|
+
for (const entry of readdirSync(src)) {
|
|
171
|
+
const sFull = path.join(src, entry);
|
|
172
|
+
const dEntry = renameStarterFile(entry);
|
|
173
|
+
const dFull = path.join(dest, dEntry);
|
|
174
|
+
const stat = statSync(sFull);
|
|
175
|
+
if (stat.isDirectory()) {
|
|
176
|
+
copyStarterTree(sFull, dFull, appName, slug);
|
|
177
|
+
} else {
|
|
178
|
+
let body = readFileSync(sFull, "utf8");
|
|
179
|
+
body = body.replaceAll("__APP_NAME__", appName).replaceAll("__APP_SLUG__", slug);
|
|
180
|
+
writeFileSync(dFull, body);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runInit([appName, ...flags]) {
|
|
186
|
+
if (!appName || appName.startsWith("-")) {
|
|
187
|
+
throw new Error("Usage: rahman-resources init <app-name>");
|
|
188
|
+
}
|
|
189
|
+
const slug = appName.replace(/[^a-z0-9-_]/gi, "-").toLowerCase();
|
|
190
|
+
const target = path.resolve(process.cwd(), appName);
|
|
191
|
+
if (existsSync(target)) {
|
|
192
|
+
throw new Error(`Directory already exists: ${target}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(kleur.bold(`\n→ Scaffolding ${kleur.cyan(slug)} (Next 16 + Convex + shadcn)\n`));
|
|
196
|
+
|
|
197
|
+
const starter = path.join(__dirname, "../lib/starter");
|
|
198
|
+
if (!existsSync(starter)) {
|
|
199
|
+
throw new Error(`Starter not found at ${starter}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
process.stdout.write(` copying starter ... `);
|
|
203
|
+
copyStarterTree(starter, target, appName, slug);
|
|
204
|
+
console.log(kleur.green("ok"));
|
|
205
|
+
|
|
206
|
+
// Ignore --lite flag for now (no template-base pull yet — starter is minimal already).
|
|
207
|
+
if (flags.includes("--lite")) {
|
|
208
|
+
console.log(kleur.dim(` (--lite is a no-op — starter is already minimal)`));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(`\n${kleur.green("✓")} Done. ${kleur.bold(slug)} scaffolded.\n`);
|
|
212
|
+
console.log(`${kleur.bold("Next:")}`);
|
|
213
|
+
console.log(` cd ${appName}`);
|
|
214
|
+
console.log(` cp .env.example .env.local ${kleur.dim("# fill NEXT_PUBLIC_CONVEX_URL")}`);
|
|
215
|
+
console.log(` npm install --legacy-peer-deps`);
|
|
216
|
+
console.log(` npx convex dev --once ${kleur.dim("# generates convex/_generated")}`);
|
|
217
|
+
console.log(` npm run dev\n`);
|
|
218
|
+
console.log(
|
|
219
|
+
`${kleur.dim("Then drop in a layout:")} ${kleur.cyan("npx rahman-resources add personal-brand-os .")}\n`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
139
223
|
async function runAdd([slug, targetArg = "."]) {
|
|
140
224
|
if (!slug) {
|
|
141
225
|
console.error(kleur.red("Missing slug."));
|
package/lib/manifest.json
CHANGED
|
@@ -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=
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__APP_NAME__",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev --turbo --port 3000",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "next lint",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"convex:dev": "convex dev",
|
|
13
|
+
"convex:codegen": "convex dev --once"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@convex-dev/auth": "^0.0.84",
|
|
17
|
+
"@radix-ui/react-label": "^2.1.0",
|
|
18
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
19
|
+
"class-variance-authority": "^0.7.1",
|
|
20
|
+
"clsx": "^2.1.1",
|
|
21
|
+
"convex": "^1.18.0",
|
|
22
|
+
"lucide-react": "^0.460.0",
|
|
23
|
+
"next": "^16.0.0",
|
|
24
|
+
"next-themes": "^0.4.4",
|
|
25
|
+
"react": "^19.0.0",
|
|
26
|
+
"react-dom": "^19.0.0",
|
|
27
|
+
"sonner": "^1.7.1",
|
|
28
|
+
"tailwind-merge": "^2.5.5"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
32
|
+
"@types/node": "^22.10.0",
|
|
33
|
+
"@types/react": "^19.0.0",
|
|
34
|
+
"@types/react-dom": "^19.0.0",
|
|
35
|
+
"postcss": "^8.5.0",
|
|
36
|
+
"tailwindcss": "^4.0.0",
|
|
37
|
+
"typescript": "^5.7.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--font-sans: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--background: oklch(1 0 0);
|
|
9
|
+
--foreground: oklch(0.145 0 0);
|
|
10
|
+
--card: oklch(1 0 0);
|
|
11
|
+
--card-foreground: oklch(0.145 0 0);
|
|
12
|
+
--popover: oklch(1 0 0);
|
|
13
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
14
|
+
--primary: oklch(0.205 0 0);
|
|
15
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
16
|
+
--secondary: oklch(0.97 0 0);
|
|
17
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
18
|
+
--muted: oklch(0.97 0 0);
|
|
19
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
20
|
+
--accent: oklch(0.97 0 0);
|
|
21
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
22
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
23
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
24
|
+
--border: oklch(0.922 0 0);
|
|
25
|
+
--input: oklch(0.922 0 0);
|
|
26
|
+
--ring: oklch(0.708 0 0);
|
|
27
|
+
--radius: 0.625rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dark {
|
|
31
|
+
--background: oklch(0.145 0 0);
|
|
32
|
+
--foreground: oklch(0.985 0 0);
|
|
33
|
+
--card: oklch(0.205 0 0);
|
|
34
|
+
--card-foreground: oklch(0.985 0 0);
|
|
35
|
+
--popover: oklch(0.205 0 0);
|
|
36
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
37
|
+
--primary: oklch(0.985 0 0);
|
|
38
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
39
|
+
--secondary: oklch(0.269 0 0);
|
|
40
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
41
|
+
--muted: oklch(0.269 0 0);
|
|
42
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
43
|
+
--accent: oklch(0.269 0 0);
|
|
44
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
45
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
46
|
+
--destructive-foreground: oklch(0.985 0 0);
|
|
47
|
+
--border: oklch(1 0 0 / 0.1);
|
|
48
|
+
--input: oklch(1 0 0 / 0.15);
|
|
49
|
+
--ring: oklch(0.556 0 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@theme inline {
|
|
53
|
+
--color-background: var(--background);
|
|
54
|
+
--color-foreground: var(--foreground);
|
|
55
|
+
--color-card: var(--card);
|
|
56
|
+
--color-card-foreground: var(--card-foreground);
|
|
57
|
+
--color-popover: var(--popover);
|
|
58
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
59
|
+
--color-primary: var(--primary);
|
|
60
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
61
|
+
--color-secondary: var(--secondary);
|
|
62
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
63
|
+
--color-muted: var(--muted);
|
|
64
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
65
|
+
--color-accent: var(--accent);
|
|
66
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
67
|
+
--color-destructive: var(--destructive);
|
|
68
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
69
|
+
--color-border: var(--border);
|
|
70
|
+
--color-input: var(--input);
|
|
71
|
+
--color-ring: var(--ring);
|
|
72
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
73
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
74
|
+
--radius-lg: var(--radius);
|
|
75
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
* {
|
|
79
|
+
border-color: var(--color-border);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
body {
|
|
83
|
+
background: var(--color-background);
|
|
84
|
+
color: var(--color-foreground);
|
|
85
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Inter } from "next/font/google";
|
|
3
|
+
import { ConvexClientProvider } from "@/components/convex-provider";
|
|
4
|
+
import { Toaster } from "sonner";
|
|
5
|
+
import "./globals.css";
|
|
6
|
+
|
|
7
|
+
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: { default: "__APP_NAME__", template: "%s — __APP_NAME__" },
|
|
11
|
+
description: "Built with rahman-resources kitab.",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en" suppressHydrationWarning>
|
|
17
|
+
<body className={`${inter.variable} font-sans antialiased`}>
|
|
18
|
+
<ConvexClientProvider>{children}</ConvexClientProvider>
|
|
19
|
+
<Toaster position="bottom-right" />
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
|
|
4
|
+
export default function HomePage() {
|
|
5
|
+
return (
|
|
6
|
+
<main className="flex min-h-screen flex-col items-center justify-center gap-6 p-8 text-center">
|
|
7
|
+
<div className="max-w-xl space-y-3">
|
|
8
|
+
<h1 className="text-4xl font-bold tracking-tight">__APP_NAME__</h1>
|
|
9
|
+
<p className="text-muted-foreground">
|
|
10
|
+
Scaffolded with <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-sm">rahman-resources</code>.
|
|
11
|
+
Drop in a layout next:
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
<pre className="rounded-md bg-muted px-4 py-3 text-left text-sm">
|
|
15
|
+
{`npx rahman-resources add personal-brand-os .
|
|
16
|
+
npx rahman-resources add landing-bento .
|
|
17
|
+
npx rahman-resources list`}
|
|
18
|
+
</pre>
|
|
19
|
+
<Button asChild>
|
|
20
|
+
<Link href="https://github.com/rahmanef63/resource-site" target="_blank" rel="noreferrer">
|
|
21
|
+
Browse the kitab
|
|
22
|
+
</Link>
|
|
23
|
+
</Button>
|
|
24
|
+
</main>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs";
|
|
4
|
+
import { ConvexReactClient } from "convex/react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
const url = process.env.NEXT_PUBLIC_CONVEX_URL;
|
|
8
|
+
const convex = url ? new ConvexReactClient(url) : null;
|
|
9
|
+
|
|
10
|
+
export function ConvexClientProvider({ children }: { children: ReactNode }) {
|
|
11
|
+
if (!convex) return <>{children}</>;
|
|
12
|
+
return <ConvexAuthNextjsProvider client={convex}>{children}</ConvexAuthNextjsProvider>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
12
|
+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
|
13
|
+
outline: "border bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
14
|
+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
15
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
16
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
17
|
+
},
|
|
18
|
+
size: {
|
|
19
|
+
default: "h-9 px-4 py-2",
|
|
20
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
21
|
+
lg: "h-10 rounded-md px-8",
|
|
22
|
+
icon: "h-9 w-9",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: { variant: "default", size: "default" },
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export interface ButtonProps
|
|
30
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
31
|
+
VariantProps<typeof buttonVariants> {
|
|
32
|
+
asChild?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
36
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
37
|
+
const Comp = asChild ? Slot : "button";
|
|
38
|
+
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
Button.displayName = "Button";
|
|
42
|
+
|
|
43
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { authTables } from "@convex-dev/auth/server";
|
|
2
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
3
|
+
import { v } from "convex/values";
|
|
4
|
+
|
|
5
|
+
export default defineSchema({
|
|
6
|
+
...authTables,
|
|
7
|
+
// Add app tables here. Example:
|
|
8
|
+
// notes: defineTable({
|
|
9
|
+
// userId: v.id("users"),
|
|
10
|
+
// title: v.string(),
|
|
11
|
+
// body: v.string(),
|
|
12
|
+
// createdAt: v.number(),
|
|
13
|
+
// }).index("by_user", ["userId"]),
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
cacheComponents: true,
|
|
4
|
+
experimental: {
|
|
5
|
+
serverActions: {
|
|
6
|
+
bodySizeLimit: "5mb",
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
images: {
|
|
10
|
+
remotePatterns: [
|
|
11
|
+
{ protocol: "https", hostname: "images.unsplash.com" },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default nextConfig;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Next 16 proxy (renamed from middleware.ts).
|
|
2
|
+
// Stub — extend with auth gate via convexAuthNextjsToken() when ready.
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
import type { NextRequest } from "next/server";
|
|
5
|
+
|
|
6
|
+
export function proxy(_req: NextRequest) {
|
|
7
|
+
return NextResponse.next();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const config = {
|
|
11
|
+
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
|
12
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules", "convex/_generated"]
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rahman-resources",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Scaffolder + installer for Rahman Resources kitab — npx rahman-resources init <app>; add <template>; list; info",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Rahman <casadezian@gmail.com>",
|