create-reactivite 1.2.0 → 1.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 +95 -3
- package/index.js +175 -30
- package/package.json +6 -4
- package/template/src/App.tsx +7 -2
- package/template/src/components/author-credit.tsx +25 -0
- package/template2/app/[locale]/layout.tsx +2 -0
- package/template2/components/author-credit.tsx +25 -0
- package/template2/tsconfig.tsbuildinfo +1 -1
- package/template3/README.md +34 -0
- package/template3/_gitignore +16 -0
- package/template3/index.html +11 -0
- package/template3/package.json +22 -0
- package/template3/rspack.config.mjs +51 -0
- package/template3/src/App.tsx +16 -0
- package/template3/src/components/author-credit.tsx +42 -0
- package/template3/src/index.css +46 -0
- package/template3/src/main.tsx +10 -0
- package/template3/tsconfig.json +20 -0
- package/template2/pnpm-lock.yaml +0 -6804
package/README.md
CHANGED
|
@@ -4,12 +4,13 @@ A modern frontend boilerplate generator. On run it asks which template you want,
|
|
|
4
4
|
|
|
5
5
|
## 🚀 Templates
|
|
6
6
|
|
|
7
|
-
When you run the CLI you pick one of
|
|
7
|
+
When you run the CLI you pick one of three templates:
|
|
8
8
|
|
|
9
9
|
| Template | Stack |
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| **React + Vite** | React 19, Vite 8 (Rolldown), Tailwind v4, shadcn/ui, React Router v7, Recharts |
|
|
12
12
|
| **Next.js 16** | Next.js 16 (App Router), React 19, Tailwind v4, shadcn/ui, i18n, TanStack Query, axios + orval, Zustand, react-hook-form + zod, husky, Vitest + MSW |
|
|
13
|
+
| **Rspack** | React 19 + TypeScript, Rspack bundler (SWC). Minimal starter — grow it yourself. |
|
|
13
14
|
|
|
14
15
|
```
|
|
15
16
|
$ npx create-reactivite my-app
|
|
@@ -18,6 +19,7 @@ $ npx create-reactivite my-app
|
|
|
18
19
|
? Pick a template: ›
|
|
19
20
|
❯ React + Vite (Tailwind v4, shadcn/ui, React Router)
|
|
20
21
|
Next.js 16 (App Router, i18n, TanStack Query, orval, Zustand, husky)
|
|
22
|
+
Rspack (minimal React + TypeScript starter)
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## 📦 Installation
|
|
@@ -46,6 +48,32 @@ npx create-reactivite .
|
|
|
46
48
|
|
|
47
49
|
Dependencies install automatically (`pnpm`, falling back to `npm`).
|
|
48
50
|
|
|
51
|
+
## 🛠️ CLI usage
|
|
52
|
+
|
|
53
|
+
The CLI runs interactively (prompts) or non-interactively (flags) — flags skip the matching prompt, so it works in scripts and CI.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
npx create-reactivite [name] [options]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Argument / Flag | Alias | Description |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `name` | | Project folder. Use `.` to scaffold into the current directory. Omit to be prompted. Must be lowercase letters, digits, `-` `.` `_` `~`. |
|
|
62
|
+
| `--template <name>` | `-t` | `template` · `template2` · `template3`. Omit to be prompted. |
|
|
63
|
+
| `--help` | `-h` | Print usage and exit. |
|
|
64
|
+
| `--version` | `-v` | Print version and exit. |
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# fully non-interactive (no prompts)
|
|
68
|
+
npx create-reactivite my-app --template template2
|
|
69
|
+
npx create-reactivite my-app -t template3
|
|
70
|
+
|
|
71
|
+
npx create-reactivite --help
|
|
72
|
+
npx create-reactivite --version
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
After copying the template the CLI sets `name` in the new `package.json`, removes any bundled lockfile (fresh resolve), runs `git init`, then installs dependencies.
|
|
76
|
+
|
|
49
77
|
---
|
|
50
78
|
|
|
51
79
|
## ⚛️ Template 1 — React + Vite
|
|
@@ -169,15 +197,79 @@ my-app/
|
|
|
169
197
|
|
|
170
198
|
---
|
|
171
199
|
|
|
200
|
+
## 🦀 Template 3 — Rspack (minimal)
|
|
201
|
+
|
|
202
|
+
A deliberately bare **React + TypeScript** starter on [Rspack](https://rspack.dev/) (Rust-based bundler, built-in SWC). No router, no UI kit — a clean base to build on.
|
|
203
|
+
|
|
204
|
+
### Scripts
|
|
205
|
+
|
|
206
|
+
- `pnpm dev` — dev server (port 5174)
|
|
207
|
+
- `pnpm build` — production build → `dist/`
|
|
208
|
+
- `pnpm preview` — serve the production build
|
|
209
|
+
|
|
210
|
+
### Structure
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
my-app/
|
|
214
|
+
├── index.html
|
|
215
|
+
├── rspack.config.mjs # entry, swc loader, html plugin, native CSS
|
|
216
|
+
├── tsconfig.json
|
|
217
|
+
└── src/
|
|
218
|
+
├── main.tsx
|
|
219
|
+
├── App.tsx
|
|
220
|
+
├── index.css
|
|
221
|
+
└── components/author-credit.tsx
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 👤 Author
|
|
227
|
+
|
|
228
|
+
Every template renders a small credit linking to the author. Built by **Javid Salimov** —
|
|
229
|
+
[GitHub](https://github.com/javidselimov) ·
|
|
230
|
+
[LinkedIn](https://www.linkedin.com/in/javidsalim/) ·
|
|
231
|
+
[npm](https://www.npmjs.com/~ubuligan).
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
172
235
|
## 🎨 Adding shadcn/ui Components
|
|
173
236
|
|
|
174
|
-
|
|
237
|
+
The React + Vite and Next.js templates use shadcn/ui (new-york, lucide icons):
|
|
175
238
|
|
|
176
239
|
```bash
|
|
177
240
|
npx shadcn@latest add button
|
|
178
241
|
npx shadcn@latest add form
|
|
179
242
|
```
|
|
180
243
|
|
|
244
|
+
## 🔄 Maintaining template versions
|
|
245
|
+
|
|
246
|
+
This repo ships a Claude Code skill that bumps every template's dependencies to their latest published versions in one shot — useful when keeping the scaffolds current.
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
/update-template-deps
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
It runs `npm-check-updates` inside `template/`, `template2/` and `template3/`, rewrites each `package.json` to the latest, and reports what changed — flagging **major** bumps that need a build check. The generator's own root `package.json` is left untouched.
|
|
253
|
+
|
|
254
|
+
You can also run the helper directly:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# all templates, latest (includes majors)
|
|
258
|
+
node .claude/skills/update-template-deps/update-deps.mjs
|
|
259
|
+
|
|
260
|
+
# safe — no major bumps
|
|
261
|
+
node .claude/skills/update-template-deps/update-deps.mjs --target minor
|
|
262
|
+
|
|
263
|
+
# a single template
|
|
264
|
+
node .claude/skills/update-template-deps/update-deps.mjs --template template2
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
After a major bump, verify the affected template still builds before publishing:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
cd template2 && pnpm install && pnpm build
|
|
271
|
+
```
|
|
272
|
+
|
|
181
273
|
## 🤝 Contributing
|
|
182
274
|
|
|
183
275
|
Contributions welcome — open a PR.
|
|
@@ -188,7 +280,7 @@ MIT
|
|
|
188
280
|
|
|
189
281
|
## 🔗 Links
|
|
190
282
|
|
|
191
|
-
- [React](https://react.dev/) · [Next.js](https://nextjs.org/) · [Vite](https://vite.dev/)
|
|
283
|
+
- [React](https://react.dev/) · [Next.js](https://nextjs.org/) · [Vite](https://vite.dev/) · [Rspack](https://rspack.dev/)
|
|
192
284
|
- [Tailwind CSS](https://tailwindcss.com/) · [shadcn/ui](https://ui.shadcn.com/)
|
|
193
285
|
- [React Router](https://reactrouter.com/) · [TanStack Query](https://tanstack.com/query) · [orval](https://orval.dev/)
|
|
194
286
|
- [Zustand](https://zustand.docs.pmnd.rs/) · [Recharts](https://recharts.org/) · [TypeScript](https://www.typescriptlang.org/)
|
package/index.js
CHANGED
|
@@ -6,41 +6,160 @@ import { fileURLToPath } from "url";
|
|
|
6
6
|
import { copy } from "fs-extra";
|
|
7
7
|
import prompts from "prompts";
|
|
8
8
|
import { execa } from "execa";
|
|
9
|
+
import figlet from "figlet";
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
12
13
|
|
|
14
|
+
// ANSI rəng köməkçiləri (xarici asılılıq olmadan)
|
|
15
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
16
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
17
|
+
|
|
18
|
+
// Mövcud template-lər (CLI seçim + --template flag validasiyası üçün)
|
|
19
|
+
const TEMPLATES = ["template", "template2", "template3"];
|
|
20
|
+
|
|
21
|
+
// 🧩 argv parse — `name` positional + `--template/-t <val>` + `--help/--version`
|
|
22
|
+
const parseArgs = (argv) => {
|
|
23
|
+
const args = { _: [], template: undefined, help: false, version: false };
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a === "--help" || a === "-h") args.help = true;
|
|
27
|
+
else if (a === "--version" || a === "-v") args.version = true;
|
|
28
|
+
else if (a === "--template" || a === "-t") args.template = argv[++i];
|
|
29
|
+
else if (a.startsWith("--template=")) args.template = a.split("=")[1];
|
|
30
|
+
else if (!a.startsWith("-")) args._.push(a);
|
|
31
|
+
}
|
|
32
|
+
return args;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const printHelp = () => {
|
|
36
|
+
console.log(`
|
|
37
|
+
${cyan("create-reactivite")} — scaffold React / Next.js / Rspack apps
|
|
38
|
+
|
|
39
|
+
${dim("Usage:")}
|
|
40
|
+
npx create-reactivite [name] [options]
|
|
41
|
+
|
|
42
|
+
${dim("Options:")}
|
|
43
|
+
-t, --template <name> ${TEMPLATES.join(" | ")}
|
|
44
|
+
-h, --help Show this help
|
|
45
|
+
-v, --version Show version
|
|
46
|
+
|
|
47
|
+
${dim("Examples:")}
|
|
48
|
+
npx create-reactivite my-app
|
|
49
|
+
npx create-reactivite my-app --template template2
|
|
50
|
+
npx create-reactivite .
|
|
51
|
+
`);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getVersion = () => {
|
|
55
|
+
try {
|
|
56
|
+
const pkg = JSON.parse(
|
|
57
|
+
fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
|
|
58
|
+
);
|
|
59
|
+
return pkg.version;
|
|
60
|
+
} catch {
|
|
61
|
+
return "unknown";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// 🧩 Banner — CLI açılışında göstərilir
|
|
66
|
+
const printBanner = () => {
|
|
67
|
+
try {
|
|
68
|
+
console.log(cyan(figlet.textSync("Reactivite", { font: "Standard" })));
|
|
69
|
+
} catch {
|
|
70
|
+
// figlet uğursuz olsa sadə mətnə keç
|
|
71
|
+
console.log(cyan("\n create-reactivite"));
|
|
72
|
+
}
|
|
73
|
+
console.log(dim(" Scaffold React / Next.js / Rspack apps · by Javid Salimov\n"));
|
|
74
|
+
};
|
|
75
|
+
|
|
13
76
|
(async () => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
77
|
+
const argv = parseArgs(process.argv.slice(2));
|
|
78
|
+
|
|
79
|
+
// --version / --help erkən çıxış (banner olmadan)
|
|
80
|
+
if (argv.version) {
|
|
81
|
+
console.log(getVersion());
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
if (argv.help) {
|
|
85
|
+
printHelp();
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
printBanner();
|
|
90
|
+
|
|
91
|
+
const nameRegex = /^[a-z0-9-~][a-z0-9-._~]*$/;
|
|
92
|
+
|
|
93
|
+
// 🧩 Layihə adı — argv-dən gəlirsə validasiya et, yoxsa soruş
|
|
94
|
+
let projectName = argv._[0];
|
|
95
|
+
if (projectName !== undefined) {
|
|
96
|
+
if (
|
|
97
|
+
projectName !== "." &&
|
|
98
|
+
projectName !== "./" &&
|
|
99
|
+
!nameRegex.test(projectName)
|
|
100
|
+
) {
|
|
101
|
+
console.error(
|
|
102
|
+
`❌ Invalid project name "${projectName}" (lowercase letters, digits, - . _ ~ only)`
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const res = await prompts({
|
|
108
|
+
type: "text",
|
|
109
|
+
name: "projectName",
|
|
110
|
+
message: "Project name:",
|
|
111
|
+
initial: "my-react-app",
|
|
112
|
+
// npm paket adı qaydası: kiçik hərf, rəqəm, - . _ ~ (və `.` current dir)
|
|
113
|
+
validate: (name) =>
|
|
114
|
+
name === "." || name === "./" || nameRegex.test(name)
|
|
115
|
+
? true
|
|
116
|
+
: "Invalid name (lowercase letters, digits, - . _ ~ only)",
|
|
117
|
+
});
|
|
118
|
+
projectName = res.projectName;
|
|
119
|
+
// Ctrl+C name promptunda — çıx
|
|
120
|
+
if (projectName === undefined) {
|
|
121
|
+
console.log("❌ Operation cancelled.");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 🧩 0. Template — argv-dən gəlirsə validasiya et, yoxsa soruş
|
|
127
|
+
let template = argv.template;
|
|
128
|
+
if (template !== undefined) {
|
|
129
|
+
if (!TEMPLATES.includes(template)) {
|
|
130
|
+
console.error(
|
|
131
|
+
`❌ Unknown template "${template}". Valid: ${TEMPLATES.join(", ")}`
|
|
132
|
+
);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
const res = await prompts({
|
|
137
|
+
type: "select",
|
|
138
|
+
name: "template",
|
|
139
|
+
message: "Pick a template:",
|
|
140
|
+
choices: [
|
|
141
|
+
{
|
|
142
|
+
title: "React + Vite (Tailwind v4, shadcn/ui, React Router)",
|
|
143
|
+
value: "template",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title:
|
|
147
|
+
"Next.js 16 (App Router, i18n, TanStack Query, orval, Zustand, husky)",
|
|
148
|
+
value: "template2",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
title: "Rspack (minimal React + TypeScript starter)",
|
|
152
|
+
value: "template3",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
initial: 0,
|
|
156
|
+
});
|
|
157
|
+
template = res.template;
|
|
158
|
+
// İstifadəçi seçimi ləğv etdisə (Ctrl+C) — çıx
|
|
159
|
+
if (!template) {
|
|
160
|
+
console.log("❌ Operation cancelled.");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
44
163
|
}
|
|
45
164
|
|
|
46
165
|
// 🧩 1. Target folderı müəyyən edirik
|
|
@@ -86,6 +205,32 @@ const __dirname = path.dirname(__filename);
|
|
|
86
205
|
fs.renameSync(gitignoreSrc, path.join(targetPath, ".gitignore"));
|
|
87
206
|
}
|
|
88
207
|
|
|
208
|
+
// 🧩 3a. Layihənin adını target package.json-a yazırıq
|
|
209
|
+
// (template-dən gələn ad `template`/`template2`/`template3` olur)
|
|
210
|
+
const pkgPath = path.join(targetPath, "package.json");
|
|
211
|
+
if (!isCurrentDir && fs.existsSync(pkgPath)) {
|
|
212
|
+
try {
|
|
213
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
214
|
+
pkg.name = projectName;
|
|
215
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
216
|
+
} catch {
|
|
217
|
+
// package.json oxunmadı — keç
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 🧩 3b. Köhnə lockfile-ları silirik ki, fresh resolve olsun
|
|
222
|
+
for (const lock of ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"]) {
|
|
223
|
+
const lockPath = path.join(targetPath, lock);
|
|
224
|
+
if (fs.existsSync(lockPath)) fs.rmSync(lockPath);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 🧩 3c. git repo init (husky `prepare` skriptinin də işləməsi üçün)
|
|
228
|
+
try {
|
|
229
|
+
await execa("git", ["init"], { cwd: targetPath });
|
|
230
|
+
} catch {
|
|
231
|
+
// git yoxdursa — keç
|
|
232
|
+
}
|
|
233
|
+
|
|
89
234
|
// 🧩 4. Asılılıqları quraşdırırıq
|
|
90
235
|
console.log("📥 Installing dependencies...");
|
|
91
236
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-reactivite",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "⚡ Scaffold modern frontend projects in seconds — pick a template: React + Vite or Next.js 16 (App Router). Both ship Tailwind v4, shadcn/ui, TypeScript and a clean, production-ready structure. The Next.js template adds i18n, TanStack Query, axios/orval, Zustand, husky and Vitest. Zero setup hassle.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-reactivite": "./index.js"
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
11
|
"template/",
|
|
12
|
-
"template2/"
|
|
12
|
+
"template2/",
|
|
13
|
+
"template3/"
|
|
13
14
|
],
|
|
14
15
|
"dependencies": {
|
|
15
16
|
"prompts": "^2.4.2",
|
|
16
17
|
"fs-extra": "^11.2.0",
|
|
17
|
-
"execa": "^7.1.0"
|
|
18
|
+
"execa": "^7.1.0",
|
|
19
|
+
"figlet": "^1.9.3"
|
|
18
20
|
},
|
|
19
21
|
"repository": {
|
|
20
22
|
"type": "git",
|
|
@@ -49,4 +51,4 @@
|
|
|
49
51
|
"url": "https://github.com/jsznpm"
|
|
50
52
|
},
|
|
51
53
|
"license": "MIT"
|
|
52
|
-
}
|
|
54
|
+
}
|
package/template/src/App.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { createBrowserRouter } from "react-router";
|
|
|
5
5
|
import { RouterProvider } from "react-router/dom";
|
|
6
6
|
import Home from './pages/Homepage/Homepage';
|
|
7
7
|
import DashboardPage from './pages/Dashboard/Dashboard';
|
|
8
|
+
import { AuthorCredit } from './components/author-credit';
|
|
8
9
|
const router = createBrowserRouter([
|
|
9
10
|
{
|
|
10
11
|
path: "/",
|
|
@@ -20,8 +21,12 @@ const router = createBrowserRouter([
|
|
|
20
21
|
},
|
|
21
22
|
]);
|
|
22
23
|
function App() {
|
|
23
|
-
return
|
|
24
|
-
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<RouterProvider router={router} />
|
|
27
|
+
<AuthorCredit />
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
export default App
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const links = [
|
|
2
|
+
{ label: "GitHub", href: "https://github.com/javidselimov" },
|
|
3
|
+
{ label: "LinkedIn", href: "https://www.linkedin.com/in/javidsalim/" },
|
|
4
|
+
{ label: "npm", href: "https://www.npmjs.com/~ubuligan" },
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
export function AuthorCredit() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-full border bg-background/80 px-4 py-2 text-sm shadow-lg backdrop-blur">
|
|
10
|
+
<span className="font-medium">Javid Salimov</span>
|
|
11
|
+
<span className="text-muted-foreground">·</span>
|
|
12
|
+
{links.map((l) => (
|
|
13
|
+
<a
|
|
14
|
+
key={l.label}
|
|
15
|
+
href={l.href}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noreferrer"
|
|
18
|
+
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
+
>
|
|
20
|
+
{l.label}
|
|
21
|
+
</a>
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -9,6 +9,7 @@ import { TranslationProvider } from '@/contexts/translation-context';
|
|
|
9
9
|
import { Toaster } from '@/components/ui/sonner';
|
|
10
10
|
import { QueryProvider } from '@/hoc/provider';
|
|
11
11
|
import { AuthEventListener } from '@/components/AuthEventListener';
|
|
12
|
+
import { AuthorCredit } from '@/components/author-credit';
|
|
12
13
|
import { LOCALES, type Locale } from '@/config/constants';
|
|
13
14
|
import { getDictionary } from './locales';
|
|
14
15
|
|
|
@@ -46,6 +47,7 @@ export default async function LocaleLayout({
|
|
|
46
47
|
<TranslationProvider locale={locale} messages={messages}>
|
|
47
48
|
<AuthEventListener />
|
|
48
49
|
<Suspense fallback={null}>{children}</Suspense>
|
|
50
|
+
<AuthorCredit />
|
|
49
51
|
</TranslationProvider>
|
|
50
52
|
<Toaster />
|
|
51
53
|
</ThemeProvider>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const links = [
|
|
2
|
+
{ label: 'GitHub', href: 'https://github.com/javidselimov' },
|
|
3
|
+
{ label: 'LinkedIn', href: 'https://www.linkedin.com/in/javidsalim/' },
|
|
4
|
+
{ label: 'npm', href: 'https://www.npmjs.com/~ubuligan' },
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
export function AuthorCredit() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="fixed bottom-4 right-4 z-50 flex items-center gap-3 rounded-full border bg-background/80 px-4 py-2 text-sm shadow-lg backdrop-blur">
|
|
10
|
+
<span className="font-medium">Javid Salimov</span>
|
|
11
|
+
<span className="text-muted-foreground">·</span>
|
|
12
|
+
{links.map((l) => (
|
|
13
|
+
<a
|
|
14
|
+
key={l.label}
|
|
15
|
+
href={l.href}
|
|
16
|
+
target="_blank"
|
|
17
|
+
rel="noreferrer"
|
|
18
|
+
className="text-muted-foreground transition-colors hover:text-foreground"
|
|
19
|
+
>
|
|
20
|
+
{l.label}
|
|
21
|
+
</a>
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|