create-vite-redux 1.0.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 +98 -0
- package/index.js +169 -0
- package/package.json +30 -0
- package/templates/react-redux-tw/.env.example +1 -0
- package/templates/react-redux-tw/.prettierignore +2 -0
- package/templates/react-redux-tw/.prettierrc +8 -0
- package/templates/react-redux-tw/README.md +81 -0
- package/templates/react-redux-tw/_gitignore +31 -0
- package/templates/react-redux-tw/_package.json +49 -0
- package/templates/react-redux-tw/eslint.config.js +25 -0
- package/templates/react-redux-tw/index.html +13 -0
- package/templates/react-redux-tw/src/App.tsx +15 -0
- package/templates/react-redux-tw/src/components/layout/Footer.tsx +11 -0
- package/templates/react-redux-tw/src/components/layout/Header.tsx +16 -0
- package/templates/react-redux-tw/src/components/ui/button.tsx +54 -0
- package/templates/react-redux-tw/src/components/ui/card.tsx +73 -0
- package/templates/react-redux-tw/src/components/ui/dialog.tsx +118 -0
- package/templates/react-redux-tw/src/components/ui/form.tsx +138 -0
- package/templates/react-redux-tw/src/components/ui/input.tsx +18 -0
- package/templates/react-redux-tw/src/components/ui/label.tsx +18 -0
- package/templates/react-redux-tw/src/components/ui/sonner.tsx +19 -0
- package/templates/react-redux-tw/src/features/auth/LoginForm.tsx +92 -0
- package/templates/react-redux-tw/src/features/auth/authApi.ts +40 -0
- package/templates/react-redux-tw/src/features/auth/authSchema.ts +19 -0
- package/templates/react-redux-tw/src/features/auth/authSlice.ts +31 -0
- package/templates/react-redux-tw/src/features/counter/Counter.tsx +34 -0
- package/templates/react-redux-tw/src/features/counter/counterSlice.ts +32 -0
- package/templates/react-redux-tw/src/hooks/redux.ts +5 -0
- package/templates/react-redux-tw/src/index.css +123 -0
- package/templates/react-redux-tw/src/lib/utils.ts +6 -0
- package/templates/react-redux-tw/src/main.tsx +17 -0
- package/templates/react-redux-tw/src/router/index.tsx +26 -0
- package/templates/react-redux-tw/src/services/api.ts +10 -0
- package/templates/react-redux-tw/src/store/index.ts +16 -0
- package/templates/react-redux-tw/tsconfig.app.json +32 -0
- package/templates/react-redux-tw/tsconfig.json +7 -0
- package/templates/react-redux-tw/tsconfig.node.json +24 -0
- package/templates/react-redux-tw/vite.config.ts +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# create-vite-redux
|
|
2
|
+
|
|
3
|
+
> Scaffold a production-ready React app in seconds.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create vite-redux my-app
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## What you get
|
|
10
|
+
|
|
11
|
+
| Tool | Role |
|
|
12
|
+
|------|------|
|
|
13
|
+
| React 19 + Vite 6 | UI & bundler |
|
|
14
|
+
| TypeScript 5 | Static types |
|
|
15
|
+
| Redux Toolkit 2 | Global state |
|
|
16
|
+
| RTK Query | Data fetching & caching |
|
|
17
|
+
| React Router v7 | Client-side routing |
|
|
18
|
+
| Tailwind CSS v4 | Utility-first styling |
|
|
19
|
+
| shadcn/ui (new-york) | Pre-built accessible components |
|
|
20
|
+
| Zod 3 | Runtime validation + type inference |
|
|
21
|
+
| React Hook Form 7 | Form state management |
|
|
22
|
+
| ESLint v9 + Prettier | Linting & formatting |
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# npm
|
|
28
|
+
npm create vite-redux my-app
|
|
29
|
+
|
|
30
|
+
# pnpm
|
|
31
|
+
pnpm create vite-redux my-app
|
|
32
|
+
|
|
33
|
+
# yarn
|
|
34
|
+
yarn create vite-redux my-app
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You'll be prompted for one option:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
✔ Include example auth feature? › Yes / No
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Everything else — TypeScript, Redux, RTK Query, Tailwind, shadcn, Zod, React Router, ESLint, Prettier — is always included.
|
|
44
|
+
|
|
45
|
+
## Get started
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd my-app
|
|
49
|
+
npm install
|
|
50
|
+
npm run dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Project structure
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
src/
|
|
57
|
+
├── components/
|
|
58
|
+
│ ├── ui/ ← pre-baked shadcn components
|
|
59
|
+
│ └── layout/ ← Header, Footer
|
|
60
|
+
├── features/
|
|
61
|
+
│ ├── counter/ ← Redux slice demo
|
|
62
|
+
│ └── auth/ ← optional (LoginForm + Zod + RTK Query)
|
|
63
|
+
├── hooks/
|
|
64
|
+
│ └── redux.ts ← useAppDispatch, useAppSelector
|
|
65
|
+
├── router/
|
|
66
|
+
│ └── index.tsx ← React Router config
|
|
67
|
+
├── services/
|
|
68
|
+
│ └── api.ts ← RTK Query base API
|
|
69
|
+
├── store/
|
|
70
|
+
│ └── index.ts ← Redux store
|
|
71
|
+
├── lib/
|
|
72
|
+
│ └── utils.ts ← cn() utility
|
|
73
|
+
├── App.tsx
|
|
74
|
+
├── main.tsx
|
|
75
|
+
└── index.css ← Tailwind v4 + shadcn OKLCH tokens
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environment variables
|
|
79
|
+
|
|
80
|
+
Copy `.env.example` to `.env` and set your API URL:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
VITE_API_URL=http://localhost:3000/api
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Scripts
|
|
87
|
+
|
|
88
|
+
| Script | Description |
|
|
89
|
+
|--------|-------------|
|
|
90
|
+
| `npm run dev` | Start dev server |
|
|
91
|
+
| `npm run build` | Production build |
|
|
92
|
+
| `npm run lint` | Run ESLint |
|
|
93
|
+
| `npm run format` | Run Prettier |
|
|
94
|
+
| `npm run preview` | Preview production build |
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import prompts from 'prompts'
|
|
7
|
+
import pc from 'picocolors'
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
10
|
+
|
|
11
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function toValidPackageName(name) {
|
|
14
|
+
return name
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/\s+/g, '-')
|
|
18
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function replaceInFile(filePath, replacements) {
|
|
22
|
+
if (!fs.existsSync(filePath)) return
|
|
23
|
+
let content = fs.readFileSync(filePath, 'utf-8')
|
|
24
|
+
for (const [from, to] of replacements) {
|
|
25
|
+
content = content.replaceAll(from, to)
|
|
26
|
+
}
|
|
27
|
+
fs.writeFileSync(filePath, content, 'utf-8')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Remove lines that contain a specific string (used to strip auth imports/reducers)
|
|
31
|
+
function removeLinesContaining(filePath, ...patterns) {
|
|
32
|
+
if (!fs.existsSync(filePath)) return
|
|
33
|
+
const lines = fs.readFileSync(filePath, 'utf-8').split('\n')
|
|
34
|
+
const filtered = lines.filter((line) => !patterns.some((p) => line.includes(p)))
|
|
35
|
+
fs.writeFileSync(filePath, filtered.join('\n'), 'utf-8')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
console.log()
|
|
41
|
+
console.log(pc.bold(pc.cyan('┌─────────────────────────────────────┐')))
|
|
42
|
+
console.log(pc.bold(pc.cyan('│') + pc.bold(' create-vite-redux v1.0.0 ') + pc.bold(pc.cyan('│'))))
|
|
43
|
+
console.log(pc.bold(pc.cyan('└─────────────────────────────────────┘')))
|
|
44
|
+
console.log()
|
|
45
|
+
|
|
46
|
+
// ─── Get project name from argv or prompt ─────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
let targetDir = process.argv[2]
|
|
49
|
+
|
|
50
|
+
const response = await prompts(
|
|
51
|
+
[
|
|
52
|
+
{
|
|
53
|
+
type: targetDir ? null : 'text',
|
|
54
|
+
name: 'projectName',
|
|
55
|
+
message: 'Project name:',
|
|
56
|
+
initial: 'my-app',
|
|
57
|
+
validate: (v) => (v.trim().length > 0 ? true : 'Project name cannot be empty'),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'confirm',
|
|
61
|
+
name: 'includeAuth',
|
|
62
|
+
message: 'Include example auth feature? (LoginForm + Zod + RTK Query)',
|
|
63
|
+
initial: true,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
{
|
|
67
|
+
onCancel() {
|
|
68
|
+
console.log(pc.red('\n✖ Cancelled'))
|
|
69
|
+
process.exit(1)
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const projectName = targetDir ?? response.projectName
|
|
75
|
+
const packageName = toValidPackageName(projectName)
|
|
76
|
+
const { includeAuth } = response
|
|
77
|
+
|
|
78
|
+
// ─── Resolve paths ────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const cwd = process.cwd()
|
|
81
|
+
const dest = path.resolve(cwd, projectName)
|
|
82
|
+
const templateDir = path.resolve(__dirname, 'templates', 'react-redux-tw')
|
|
83
|
+
|
|
84
|
+
// ─── Safety check ─────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
if (fs.existsSync(dest)) {
|
|
87
|
+
const { overwrite } = await prompts({
|
|
88
|
+
type: 'confirm',
|
|
89
|
+
name: 'overwrite',
|
|
90
|
+
message: `Target directory ${pc.yellow(projectName)} already exists. Overwrite?`,
|
|
91
|
+
initial: false,
|
|
92
|
+
})
|
|
93
|
+
if (!overwrite) {
|
|
94
|
+
console.log(pc.red('\n✖ Aborted'))
|
|
95
|
+
process.exit(1)
|
|
96
|
+
}
|
|
97
|
+
fs.removeSync(dest)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Copy template ────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
console.log()
|
|
103
|
+
console.log(pc.dim(' Setting up project…'))
|
|
104
|
+
|
|
105
|
+
await fs.copy(templateDir, dest, {
|
|
106
|
+
// preserve dotfiles (.gitignore, .env.example, etc.)
|
|
107
|
+
filter: () => true,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// ─── Rename _package.json → package.json ─────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
const pkgSrc = path.join(dest, '_package.json')
|
|
113
|
+
const pkgDest = path.join(dest, 'package.json')
|
|
114
|
+
fs.moveSync(pkgSrc, pkgDest)
|
|
115
|
+
|
|
116
|
+
// ─── Rename _gitignore → .gitignore ──────────────────────────────────────────
|
|
117
|
+
// npm strips .gitignore from tarballs, so it's stored as _gitignore in the template
|
|
118
|
+
|
|
119
|
+
const gitignoreSrc = path.join(dest, '_gitignore')
|
|
120
|
+
const gitignoreDest = path.join(dest, '.gitignore')
|
|
121
|
+
if (fs.existsSync(gitignoreSrc)) {
|
|
122
|
+
fs.moveSync(gitignoreSrc, gitignoreDest)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Inject project name ──────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
const nameReplacements = [['{{PROJECT_NAME}}', projectName]]
|
|
128
|
+
|
|
129
|
+
replaceInFile(pkgDest, [['{{PROJECT_NAME}}', packageName]])
|
|
130
|
+
replaceInFile(path.join(dest, 'index.html'), nameReplacements)
|
|
131
|
+
replaceInFile(path.join(dest, 'README.md'), nameReplacements)
|
|
132
|
+
replaceInFile(path.join(dest, 'src', 'router', 'index.tsx'), nameReplacements)
|
|
133
|
+
replaceInFile(path.join(dest, 'src', 'components', 'layout', 'Header.tsx'), nameReplacements)
|
|
134
|
+
|
|
135
|
+
// ─── Remove auth feature if not included ──────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
if (!includeAuth) {
|
|
138
|
+
fs.removeSync(path.join(dest, 'src', 'features', 'auth'))
|
|
139
|
+
|
|
140
|
+
// Remove auth import and reducer from store/index.ts
|
|
141
|
+
removeLinesContaining(
|
|
142
|
+
path.join(dest, 'src', 'store', 'index.ts'),
|
|
143
|
+
"import authReducer from '@/features/auth/authSlice'",
|
|
144
|
+
'auth: authReducer,'
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Done ─────────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
console.log()
|
|
151
|
+
console.log(' ' + pc.green('✔') + pc.bold(' Done! Your project is ready.'))
|
|
152
|
+
console.log()
|
|
153
|
+
console.log(' ' + pc.dim('Included:'))
|
|
154
|
+
console.log(' ' + pc.dim(' ·') + ' React 19 + Vite 6 + TypeScript')
|
|
155
|
+
console.log(' ' + pc.dim(' ·') + ' Redux Toolkit + RTK Query')
|
|
156
|
+
console.log(' ' + pc.dim(' ·') + ' React Router v7')
|
|
157
|
+
console.log(' ' + pc.dim(' ·') + ' Tailwind CSS v4 + shadcn/ui (new-york)')
|
|
158
|
+
console.log(' ' + pc.dim(' ·') + ' Zod + React Hook Form')
|
|
159
|
+
console.log(' ' + pc.dim(' ·') + ' ESLint v9 + Prettier')
|
|
160
|
+
if (includeAuth) {
|
|
161
|
+
console.log(' ' + pc.dim(' ·') + ' Auth feature (LoginForm + authSlice + authApi)')
|
|
162
|
+
}
|
|
163
|
+
console.log()
|
|
164
|
+
console.log(' ' + pc.bold('Get started:'))
|
|
165
|
+
console.log()
|
|
166
|
+
console.log(' ' + pc.cyan(`cd ${projectName}`))
|
|
167
|
+
console.log(' ' + pc.cyan('npm install'))
|
|
168
|
+
console.log(' ' + pc.cyan('npm run dev'))
|
|
169
|
+
console.log()
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-vite-redux",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a React 19 + Vite 6 + TypeScript + Redux Toolkit + RTK Query + Tailwind v4 + shadcn/ui + Zod + RHF project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-vite-redux": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react",
|
|
15
|
+
"vite",
|
|
16
|
+
"redux",
|
|
17
|
+
"tailwind",
|
|
18
|
+
"shadcn",
|
|
19
|
+
"template",
|
|
20
|
+
"starter",
|
|
21
|
+
"scaffold"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"fs-extra": "^11",
|
|
27
|
+
"picocolors": "^1",
|
|
28
|
+
"prompts": "^2"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_URL=http://localhost:3000/api
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A React 19 app scaffolded with [create-vite-redux](https://www.npmjs.com/package/create-vite-redux).
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
| Tool | Role |
|
|
8
|
+
|------|------|
|
|
9
|
+
| React 19 + Vite 6 | UI & bundler |
|
|
10
|
+
| TypeScript 5 | Static types |
|
|
11
|
+
| Redux Toolkit 2 + RTK Query | State & data fetching |
|
|
12
|
+
| React Router v7 | Routing |
|
|
13
|
+
| Tailwind CSS v4 + shadcn/ui | Styling & components |
|
|
14
|
+
| Zod + React Hook Form | Validation & forms |
|
|
15
|
+
|
|
16
|
+
## Getting started
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Project structure
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
src/
|
|
27
|
+
├── components/
|
|
28
|
+
│ ├── ui/ ← shadcn/ui components (button, card, form, dialog, sonner…)
|
|
29
|
+
│ └── layout/ ← Header, Footer
|
|
30
|
+
├── features/
|
|
31
|
+
│ ├── counter/ ← Redux slice demo — start here
|
|
32
|
+
│ └── auth/ ← Auth feature (if included at scaffold time)
|
|
33
|
+
├── hooks/
|
|
34
|
+
│ └── redux.ts ← useAppDispatch, useAppSelector
|
|
35
|
+
├── router/
|
|
36
|
+
│ └── index.tsx ← Add new routes here
|
|
37
|
+
├── services/
|
|
38
|
+
│ └── api.ts ← RTK Query base API — inject endpoints into this
|
|
39
|
+
├── store/
|
|
40
|
+
│ └── index.ts ← Redux store
|
|
41
|
+
├── lib/
|
|
42
|
+
│ └── utils.ts ← cn() helper
|
|
43
|
+
├── App.tsx ← Root layout (Header + Footer + <Outlet />)
|
|
44
|
+
├── main.tsx ← Entry point
|
|
45
|
+
└── index.css ← Tailwind v4 + OKLCH design tokens
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Environment variables
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cp .env.example .env
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Variable | Description |
|
|
55
|
+
|----------|-------------|
|
|
56
|
+
| `VITE_API_URL` | Base URL for RTK Query (`/api` by default) |
|
|
57
|
+
|
|
58
|
+
## Adding a new feature
|
|
59
|
+
|
|
60
|
+
1. Create `src/features/my-feature/mySlice.ts` — define your slice
|
|
61
|
+
2. Register the reducer in `src/store/index.ts`
|
|
62
|
+
3. Create `src/features/my-feature/myApi.ts` — inject RTK Query endpoints into `api`
|
|
63
|
+
4. Add a route in `src/router/index.tsx`
|
|
64
|
+
|
|
65
|
+
## Adding shadcn components
|
|
66
|
+
|
|
67
|
+
Although components are pre-baked, you can add more via the shadcn CLI:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx shadcn@latest add <component>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Scripts
|
|
74
|
+
|
|
75
|
+
| Script | Description |
|
|
76
|
+
|--------|-------------|
|
|
77
|
+
| `npm run dev` | Start dev server |
|
|
78
|
+
| `npm run build` | Production build |
|
|
79
|
+
| `npm run lint` | Run ESLint |
|
|
80
|
+
| `npm run format` | Run Prettier |
|
|
81
|
+
| `npm run preview` | Preview production build |
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode/*
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
.idea
|
|
19
|
+
.DS_Store
|
|
20
|
+
*.suo
|
|
21
|
+
*.ntvs*
|
|
22
|
+
*.njsproj
|
|
23
|
+
*.sln
|
|
24
|
+
*.sw?
|
|
25
|
+
|
|
26
|
+
# Environment files
|
|
27
|
+
.env
|
|
28
|
+
.env.local
|
|
29
|
+
.env.development.local
|
|
30
|
+
.env.test.local
|
|
31
|
+
.env.production.local
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"format": "prettier --write .",
|
|
11
|
+
"preview": "vite preview"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0",
|
|
16
|
+
"react-router-dom": "^7.0.0",
|
|
17
|
+
"@reduxjs/toolkit": "^2.0.0",
|
|
18
|
+
"react-redux": "^9.0.0",
|
|
19
|
+
"react-hook-form": "^7.0.0",
|
|
20
|
+
"@hookform/resolvers": "^3.0.0",
|
|
21
|
+
"zod": "^3.0.0",
|
|
22
|
+
"clsx": "^2.0.0",
|
|
23
|
+
"tailwind-merge": "^2.0.0",
|
|
24
|
+
"class-variance-authority": "^0.7.0",
|
|
25
|
+
"lucide-react": "latest",
|
|
26
|
+
"sonner": "^1.0.0",
|
|
27
|
+
"@radix-ui/react-label": "^2.0.0",
|
|
28
|
+
"@radix-ui/react-slot": "^1.0.0",
|
|
29
|
+
"@radix-ui/react-dialog": "^1.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"vite": "^6.0.0",
|
|
33
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
34
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
35
|
+
"tailwindcss": "^4.0.0",
|
|
36
|
+
"tw-animate-css": "^1.0.0",
|
|
37
|
+
"typescript": "^5.0.0",
|
|
38
|
+
"@types/react": "^19.0.0",
|
|
39
|
+
"@types/react-dom": "^19.0.0",
|
|
40
|
+
"eslint": "^9.0.0",
|
|
41
|
+
"@eslint/js": "^9.0.0",
|
|
42
|
+
"globals": "^15.0.0",
|
|
43
|
+
"typescript-eslint": "^8.0.0",
|
|
44
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
45
|
+
"eslint-plugin-react-refresh": "^0.4.0",
|
|
46
|
+
"prettier": "^3.0.0",
|
|
47
|
+
"prettier-plugin-tailwindcss": "^0.6.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{ ignores: ['dist'] },
|
|
9
|
+
{
|
|
10
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
globals: globals.browser,
|
|
15
|
+
},
|
|
16
|
+
plugins: {
|
|
17
|
+
'react-hooks': reactHooks,
|
|
18
|
+
'react-refresh': reactRefresh,
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
...reactHooks.configs.recommended.rules,
|
|
22
|
+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>{{PROJECT_NAME}}</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Outlet } from 'react-router-dom'
|
|
2
|
+
import { Header } from '@/components/layout/Header'
|
|
3
|
+
import { Footer } from '@/components/layout/Footer'
|
|
4
|
+
|
|
5
|
+
export default function App() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="min-h-screen flex flex-col">
|
|
8
|
+
<Header />
|
|
9
|
+
<main className="flex-1 flex flex-col items-center justify-center gap-8 p-8">
|
|
10
|
+
<Outlet />
|
|
11
|
+
</main>
|
|
12
|
+
<Footer />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function Footer() {
|
|
2
|
+
return (
|
|
3
|
+
<footer className="border-t border-border mt-auto">
|
|
4
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-center">
|
|
5
|
+
<p className="text-sm text-muted-foreground">
|
|
6
|
+
Built with create-my-stack
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
</footer>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom'
|
|
2
|
+
|
|
3
|
+
export function Header() {
|
|
4
|
+
return (
|
|
5
|
+
<header className="border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50">
|
|
6
|
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-14 flex items-center justify-between">
|
|
7
|
+
<Link to="/" className="font-semibold text-foreground hover:text-foreground/80 transition-colors">
|
|
8
|
+
{{PROJECT_NAME}}
|
|
9
|
+
</Link>
|
|
10
|
+
<nav className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
11
|
+
<Link to="/" className="hover:text-foreground transition-colors">Home</Link>
|
|
12
|
+
</nav>
|
|
13
|
+
</div>
|
|
14
|
+
</header>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 aria-invalid:border-destructive cursor-pointer',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
|
12
|
+
destructive:
|
|
13
|
+
'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20',
|
|
14
|
+
outline:
|
|
15
|
+
'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
|
|
16
|
+
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
17
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
18
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
22
|
+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
23
|
+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
24
|
+
icon: 'size-9',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
function Button({
|
|
35
|
+
className,
|
|
36
|
+
variant,
|
|
37
|
+
size,
|
|
38
|
+
asChild = false,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<'button'> &
|
|
41
|
+
VariantProps<typeof buttonVariants> & {
|
|
42
|
+
asChild?: boolean
|
|
43
|
+
}) {
|
|
44
|
+
const Comp = asChild ? Slot : 'button'
|
|
45
|
+
return (
|
|
46
|
+
<Comp
|
|
47
|
+
data-slot="button"
|
|
48
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { Button, buttonVariants }
|