create-softeneers-app 0.1.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 +31 -0
- package/dist/args.js +96 -0
- package/dist/args.js.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.js +31 -0
- package/dist/prompts.js.map +1 -0
- package/dist/scaffold.js +130 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/templates.js +40 -0
- package/dist/templates.js.map +1 -0
- package/package.json +41 -0
- package/templates/next-fullstack/.env.example +18 -0
- package/templates/next-fullstack/README.md +54 -0
- package/templates/next-fullstack/apps/server/.env.example +8 -0
- package/templates/next-fullstack/apps/server/README.md +64 -0
- package/templates/next-fullstack/apps/server/config/config.cjs +24 -0
- package/templates/next-fullstack/apps/server/controller/carController.ts +62 -0
- package/templates/next-fullstack/apps/server/index.ts +50 -0
- package/templates/next-fullstack/apps/server/migrations/20260414160000-create-cars-table.js +39 -0
- package/templates/next-fullstack/apps/server/models/car.ts +70 -0
- package/templates/next-fullstack/apps/server/package.json +34 -0
- package/templates/next-fullstack/apps/server/routes/carRoutes.ts +18 -0
- package/templates/next-fullstack/apps/server/seeders/20260414160500-seed-cars.js +34 -0
- package/templates/next-fullstack/apps/server/tsconfig.json +14 -0
- package/templates/next-fullstack/apps/web/README.md +38 -0
- package/templates/next-fullstack/apps/web/app/components/Footer.tsx +19 -0
- package/templates/next-fullstack/apps/web/app/components/Navbar.tsx +34 -0
- package/templates/next-fullstack/apps/web/app/docs/page.tsx +170 -0
- package/templates/next-fullstack/apps/web/app/favicon.ico +0 -0
- package/templates/next-fullstack/apps/web/app/globals.css +1 -0
- package/templates/next-fullstack/apps/web/app/layout.tsx +29 -0
- package/templates/next-fullstack/apps/web/app/masini/page.tsx +269 -0
- package/templates/next-fullstack/apps/web/app/page.tsx +88 -0
- package/templates/next-fullstack/apps/web/eslint.config.mjs +18 -0
- package/templates/next-fullstack/apps/web/next.config.ts +7 -0
- package/templates/next-fullstack/apps/web/package.json +26 -0
- package/templates/next-fullstack/apps/web/postcss.config.mjs +7 -0
- package/templates/next-fullstack/apps/web/public/file.svg +1 -0
- package/templates/next-fullstack/apps/web/public/globe.svg +1 -0
- package/templates/next-fullstack/apps/web/public/next.svg +1 -0
- package/templates/next-fullstack/apps/web/public/vercel.svg +1 -0
- package/templates/next-fullstack/apps/web/public/window.svg +1 -0
- package/templates/next-fullstack/apps/web/tsconfig.json +34 -0
- package/templates/next-fullstack/docker-compose.yml +23 -0
- package/templates/next-fullstack/package.json +25 -0
- package/templates/next-fullstack/pnpm-workspace.yaml +3 -0
- package/templates/next-fullstack/turbo.json +17 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
type Car = {
|
|
6
|
+
id: number;
|
|
7
|
+
brand: string;
|
|
8
|
+
model: string;
|
|
9
|
+
year: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
|
|
13
|
+
|
|
14
|
+
export default function MasiniPage() {
|
|
15
|
+
const [cars, setCars] = useState<Car[]>([]);
|
|
16
|
+
const [brand, setBrand] = useState("");
|
|
17
|
+
const [model, setModel] = useState("");
|
|
18
|
+
const [year, setYear] = useState("");
|
|
19
|
+
const [selectedId, setSelectedId] = useState<number | null>(null);
|
|
20
|
+
const [statusMessage, setStatusMessage] = useState("Ready");
|
|
21
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
22
|
+
|
|
23
|
+
const endpoint = useMemo(() => `${apiBaseUrl}/api/cars`, []);
|
|
24
|
+
|
|
25
|
+
const loadCars = useCallback(async () => {
|
|
26
|
+
setIsLoading(true);
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(endpoint, { cache: "no-store" });
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error("Nu am putut incarca masinile.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const data = (await response.json()) as Car[];
|
|
34
|
+
setCars(data);
|
|
35
|
+
setStatusMessage("Masinile au fost incarcate.");
|
|
36
|
+
} catch (error) {
|
|
37
|
+
setStatusMessage(error instanceof Error ? error.message : "Eroare la incarcare.");
|
|
38
|
+
} finally {
|
|
39
|
+
setIsLoading(false);
|
|
40
|
+
}
|
|
41
|
+
}, [endpoint]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
void loadCars();
|
|
45
|
+
}, [loadCars]);
|
|
46
|
+
|
|
47
|
+
const resetForm = () => {
|
|
48
|
+
setBrand("");
|
|
49
|
+
setModel("");
|
|
50
|
+
setYear("");
|
|
51
|
+
setSelectedId(null);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleCreate = async (event: FormEvent) => {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(endpoint, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ brand, model, year: Number(year) }),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error("Crearea masinii a esuat.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
setStatusMessage("Masina a fost adaugata.");
|
|
68
|
+
resetForm();
|
|
69
|
+
await loadCars();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
setStatusMessage(error instanceof Error ? error.message : "Eroare la creare.");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleSelectCar = (car: Car) => {
|
|
76
|
+
setSelectedId(car.id);
|
|
77
|
+
setBrand(car.brand);
|
|
78
|
+
setModel(car.model);
|
|
79
|
+
setYear(String(car.year));
|
|
80
|
+
setStatusMessage(`Masina #${car.id} este selectata pentru editare.`);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleUpdate = async () => {
|
|
84
|
+
if (!selectedId) {
|
|
85
|
+
setStatusMessage("Selecteaza o masina din tabel pentru update.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const response = await fetch(`${endpoint}/${selectedId}`, {
|
|
91
|
+
method: "PUT",
|
|
92
|
+
headers: { "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify({ brand, model, year: Number(year) }),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error("Actualizarea masinii a esuat.");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setStatusMessage(`Masina #${selectedId} a fost actualizata.`);
|
|
101
|
+
resetForm();
|
|
102
|
+
await loadCars();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
setStatusMessage(error instanceof Error ? error.message : "Eroare la update.");
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleDelete = async (carId: number) => {
|
|
109
|
+
try {
|
|
110
|
+
const response = await fetch(`${endpoint}/${carId}`, { method: "DELETE" });
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error("Stergerea masinii a esuat.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
setStatusMessage(`Masina #${carId} a fost stearsa.`);
|
|
116
|
+
if (selectedId === carId) {
|
|
117
|
+
resetForm();
|
|
118
|
+
}
|
|
119
|
+
await loadCars();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
setStatusMessage(error instanceof Error ? error.message : "Eroare la stergere.");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const handleReadOne = async () => {
|
|
126
|
+
if (!selectedId) {
|
|
127
|
+
setStatusMessage("Selecteaza o masina pentru GET by id.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(`${endpoint}/${selectedId}`);
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error("Nu am gasit masina selectata.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const car = (await response.json()) as Car;
|
|
138
|
+
setStatusMessage(
|
|
139
|
+
`GET by id: #${car.id} ${car.brand} ${car.model} (${car.year})`
|
|
140
|
+
);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
setStatusMessage(error instanceof Error ? error.message : "Eroare la get by id.");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<section className="mx-auto flex w-full max-w-5xl flex-col gap-6 px-6 py-10">
|
|
148
|
+
<h1 className="text-3xl font-bold">CRUD Masini</h1>
|
|
149
|
+
|
|
150
|
+
<p className="rounded-md border border-zinc-200 px-4 py-2 text-sm dark:border-zinc-700">
|
|
151
|
+
Status: {isLoading ? "Loading..." : statusMessage}
|
|
152
|
+
</p>
|
|
153
|
+
|
|
154
|
+
<form onSubmit={handleCreate} className="grid grid-cols-1 gap-3 md:grid-cols-4">
|
|
155
|
+
<input
|
|
156
|
+
value={brand}
|
|
157
|
+
onChange={(event) => setBrand(event.target.value)}
|
|
158
|
+
placeholder="Brand"
|
|
159
|
+
className="rounded-md border border-zinc-300 px-3 py-2"
|
|
160
|
+
required
|
|
161
|
+
/>
|
|
162
|
+
<input
|
|
163
|
+
value={model}
|
|
164
|
+
onChange={(event) => setModel(event.target.value)}
|
|
165
|
+
placeholder="Model"
|
|
166
|
+
className="rounded-md border border-zinc-300 px-3 py-2"
|
|
167
|
+
required
|
|
168
|
+
/>
|
|
169
|
+
<input
|
|
170
|
+
value={year}
|
|
171
|
+
onChange={(event) => setYear(event.target.value)}
|
|
172
|
+
placeholder="An"
|
|
173
|
+
type="number"
|
|
174
|
+
className="rounded-md border border-zinc-300 px-3 py-2"
|
|
175
|
+
required
|
|
176
|
+
/>
|
|
177
|
+
<button
|
|
178
|
+
type="submit"
|
|
179
|
+
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
|
|
180
|
+
>
|
|
181
|
+
Create
|
|
182
|
+
</button>
|
|
183
|
+
</form>
|
|
184
|
+
|
|
185
|
+
<div className="flex flex-wrap gap-3">
|
|
186
|
+
<button
|
|
187
|
+
type="button"
|
|
188
|
+
onClick={handleReadOne}
|
|
189
|
+
className="rounded-md bg-zinc-800 px-4 py-2 text-white hover:bg-zinc-700"
|
|
190
|
+
>
|
|
191
|
+
Read one
|
|
192
|
+
</button>
|
|
193
|
+
<button
|
|
194
|
+
type="button"
|
|
195
|
+
onClick={handleUpdate}
|
|
196
|
+
className="rounded-md bg-emerald-600 px-4 py-2 text-white hover:bg-emerald-700"
|
|
197
|
+
>
|
|
198
|
+
Update selected
|
|
199
|
+
</button>
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={loadCars}
|
|
203
|
+
className="rounded-md bg-indigo-600 px-4 py-2 text-white hover:bg-indigo-700"
|
|
204
|
+
>
|
|
205
|
+
Refresh (Read all)
|
|
206
|
+
</button>
|
|
207
|
+
<button
|
|
208
|
+
type="button"
|
|
209
|
+
onClick={resetForm}
|
|
210
|
+
className="rounded-md bg-zinc-500 px-4 py-2 text-white hover:bg-zinc-600"
|
|
211
|
+
>
|
|
212
|
+
Clear selection
|
|
213
|
+
</button>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div className="overflow-x-auto">
|
|
217
|
+
<table className="w-full border-collapse border border-zinc-300 text-left text-sm">
|
|
218
|
+
<thead className="bg-zinc-100 dark:bg-zinc-800">
|
|
219
|
+
<tr>
|
|
220
|
+
<th className="border border-zinc-300 px-3 py-2">ID</th>
|
|
221
|
+
<th className="border border-zinc-300 px-3 py-2">Brand</th>
|
|
222
|
+
<th className="border border-zinc-300 px-3 py-2">Model</th>
|
|
223
|
+
<th className="border border-zinc-300 px-3 py-2">Year</th>
|
|
224
|
+
<th className="border border-zinc-300 px-3 py-2">Actions</th>
|
|
225
|
+
</tr>
|
|
226
|
+
</thead>
|
|
227
|
+
<tbody>
|
|
228
|
+
{cars.map((car) => (
|
|
229
|
+
<tr
|
|
230
|
+
key={car.id}
|
|
231
|
+
className={selectedId === car.id ? "bg-yellow-100 text-black" : ""}
|
|
232
|
+
>
|
|
233
|
+
<td className="border border-zinc-300 px-3 py-2">{car.id}</td>
|
|
234
|
+
<td className="border border-zinc-300 px-3 py-2">{car.brand}</td>
|
|
235
|
+
<td className="border border-zinc-300 px-3 py-2">{car.model}</td>
|
|
236
|
+
<td className="border border-zinc-300 px-3 py-2">{car.year}</td>
|
|
237
|
+
<td className="border border-zinc-300 px-3 py-2">
|
|
238
|
+
<div className="flex gap-2">
|
|
239
|
+
<button
|
|
240
|
+
type="button"
|
|
241
|
+
onClick={() => handleSelectCar(car)}
|
|
242
|
+
className="rounded bg-amber-500 px-2 py-1 text-white hover:bg-amber-600"
|
|
243
|
+
>
|
|
244
|
+
Select
|
|
245
|
+
</button>
|
|
246
|
+
<button
|
|
247
|
+
type="button"
|
|
248
|
+
onClick={() => handleDelete(car.id)}
|
|
249
|
+
className="rounded bg-red-600 px-2 py-1 text-white hover:bg-red-700"
|
|
250
|
+
>
|
|
251
|
+
Delete
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
</td>
|
|
255
|
+
</tr>
|
|
256
|
+
))}
|
|
257
|
+
{cars.length === 0 && (
|
|
258
|
+
<tr>
|
|
259
|
+
<td className="border border-zinc-300 px-3 py-2" colSpan={5}>
|
|
260
|
+
Nu exista masini in baza de date.
|
|
261
|
+
</td>
|
|
262
|
+
</tr>
|
|
263
|
+
)}
|
|
264
|
+
</tbody>
|
|
265
|
+
</table>
|
|
266
|
+
</div>
|
|
267
|
+
</section>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export default function Home() {
|
|
2
|
+
return (
|
|
3
|
+
<section className="mx-auto flex w-full max-w-5xl flex-col gap-8 px-6 py-10">
|
|
4
|
+
<header className="space-y-3">
|
|
5
|
+
<h1 className="text-4xl font-bold">Monolith Demo App</h1>
|
|
6
|
+
<p className="text-zinc-700 dark:text-zinc-300">
|
|
7
|
+
Acesta este un demo full-stack foarte simplu pentru CRUD, facut ca
|
|
8
|
+
punct de pornire pentru invatare si pentru proiecte web mici.
|
|
9
|
+
</p>
|
|
10
|
+
</header>
|
|
11
|
+
|
|
12
|
+
<article className="space-y-4 rounded-lg border border-zinc-200 p-5 dark:border-zinc-700">
|
|
13
|
+
<h2 className="text-2xl font-semibold">Tehnologii folosite</h2>
|
|
14
|
+
<ul className="list-disc space-y-2 pl-6 text-zinc-700 dark:text-zinc-300">
|
|
15
|
+
<li>
|
|
16
|
+
<strong>Next.js 16 + React 19 (frontend)</strong>: pentru pagini,
|
|
17
|
+
UI si navigare.
|
|
18
|
+
</li>
|
|
19
|
+
<li>
|
|
20
|
+
<strong>TypeScript</strong>: pentru tipuri clare si cod mai sigur.
|
|
21
|
+
</li>
|
|
22
|
+
<li>
|
|
23
|
+
<strong>Tailwind CSS</strong>: pentru styling rapid in componente.
|
|
24
|
+
</li>
|
|
25
|
+
<li>
|
|
26
|
+
<strong>Express</strong>: server HTTP simplu pentru API REST.
|
|
27
|
+
</li>
|
|
28
|
+
<li>
|
|
29
|
+
<strong>Sequelize + sequelize-cli</strong>: ORM + migration/seed.
|
|
30
|
+
</li>
|
|
31
|
+
<li>
|
|
32
|
+
<strong>MySQL + mysql2</strong>: baza de date relationala.
|
|
33
|
+
</li>
|
|
34
|
+
</ul>
|
|
35
|
+
</article>
|
|
36
|
+
|
|
37
|
+
<article className="space-y-4 rounded-lg border border-zinc-200 p-5 dark:border-zinc-700">
|
|
38
|
+
<h2 className="text-2xl font-semibold">Cum este impartita aplicatia</h2>
|
|
39
|
+
<div className="space-y-3 text-zinc-700 dark:text-zinc-300">
|
|
40
|
+
<p>
|
|
41
|
+
<strong>Frontend (`frontend/app`)</strong>: afiseaza pagini, inclusiv
|
|
42
|
+
`masini`, unde rulezi operatiile CRUD din browser.
|
|
43
|
+
</p>
|
|
44
|
+
<p>
|
|
45
|
+
<strong>Backend (`backend`)</strong>: expune endpoint-uri API pentru
|
|
46
|
+
masini (`/api/cars`) si raspunde cu JSON.
|
|
47
|
+
</p>
|
|
48
|
+
<p>
|
|
49
|
+
<strong>Baza de date (MySQL)</strong>: pastreaza datele in tabela
|
|
50
|
+
`cars`, creata prin migration.
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
</article>
|
|
54
|
+
|
|
55
|
+
<article className="space-y-4 rounded-lg border border-zinc-200 p-5 dark:border-zinc-700">
|
|
56
|
+
<h2 className="text-2xl font-semibold">De ce fiecare tehnologie</h2>
|
|
57
|
+
<ul className="list-disc space-y-2 pl-6 text-zinc-700 dark:text-zinc-300">
|
|
58
|
+
<li>
|
|
59
|
+
Next.js simplifica structura pe pagini si integrarea cu React.
|
|
60
|
+
</li>
|
|
61
|
+
<li>
|
|
62
|
+
Express tine backend-ul mic si usor de inteles pentru incepatori.
|
|
63
|
+
</li>
|
|
64
|
+
<li>
|
|
65
|
+
Sequelize permite CRUD fara SQL manual in majoritatea cazurilor.
|
|
66
|
+
</li>
|
|
67
|
+
<li>
|
|
68
|
+
Migration + seed fac setup-ul repetabil pe orice calculator.
|
|
69
|
+
</li>
|
|
70
|
+
<li>
|
|
71
|
+
CORS permite frontend-ului sa consume API-ul backend local.
|
|
72
|
+
</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</article>
|
|
75
|
+
|
|
76
|
+
<article className="space-y-4 rounded-lg border border-zinc-200 p-5 dark:border-zinc-700">
|
|
77
|
+
<h2 className="text-2xl font-semibold">Fluxul CRUD pe scurt</h2>
|
|
78
|
+
<ol className="list-decimal space-y-2 pl-6 text-zinc-700 dark:text-zinc-300">
|
|
79
|
+
<li>Utilizatorul apasa Create/Read/Update/Delete in `frontend/app/masini/page.tsx`.</li>
|
|
80
|
+
<li>Frontend-ul face request catre `http://localhost:4000/api/cars`.</li>
|
|
81
|
+
<li>Route-ul din backend trimite request-ul in controller.</li>
|
|
82
|
+
<li>Controller-ul foloseste modelul Sequelize `Car`.</li>
|
|
83
|
+
<li>Modelul executa query in MySQL si backend-ul returneaza JSON.</li>
|
|
84
|
+
</ol>
|
|
85
|
+
</article>
|
|
86
|
+
</section>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"next-env.d.ts",
|
|
15
|
+
]),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"next": "16.2.3",
|
|
13
|
+
"react": "19.2.4",
|
|
14
|
+
"react-dom": "19.2.4"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@tailwindcss/postcss": "^4",
|
|
18
|
+
"@types/node": "^20",
|
|
19
|
+
"@types/react": "^19",
|
|
20
|
+
"@types/react-dom": "^19",
|
|
21
|
+
"eslint": "^9",
|
|
22
|
+
"eslint-config-next": "16.2.3",
|
|
23
|
+
"tailwindcss": "^4",
|
|
24
|
+
"typescript": "^5"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
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": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Local MySQL for the generated project.
|
|
2
|
+
# Credentials must match .env (DB_*). Defaults below mirror .env.example.
|
|
3
|
+
services:
|
|
4
|
+
db:
|
|
5
|
+
image: mysql:8
|
|
6
|
+
restart: unless-stopped
|
|
7
|
+
ports:
|
|
8
|
+
- "${DB_PORT:-3306}:3306"
|
|
9
|
+
environment:
|
|
10
|
+
# Empty root password to match the .env.example default. Set a real
|
|
11
|
+
# password in production and update DB_PASSWORD accordingly.
|
|
12
|
+
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
|
|
13
|
+
MYSQL_DATABASE: "${DB_NAME:-app_dev}"
|
|
14
|
+
volumes:
|
|
15
|
+
- db_data:/var/lib/mysql
|
|
16
|
+
healthcheck:
|
|
17
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
18
|
+
interval: 5s
|
|
19
|
+
timeout: 5s
|
|
20
|
+
retries: 10
|
|
21
|
+
|
|
22
|
+
volumes:
|
|
23
|
+
db_data:
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next-fullstack",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Next.js + Express + Sequelize + MySQL fullstack app generated by create-softeneers-app.",
|
|
6
|
+
"packageManager": "npm@11.5.2",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"workspaces": [
|
|
11
|
+
"apps/*",
|
|
12
|
+
"packages/*"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "turbo run dev",
|
|
16
|
+
"build": "turbo run build",
|
|
17
|
+
"lint": "turbo run lint",
|
|
18
|
+
"db:migrate": "turbo run db:migrate",
|
|
19
|
+
"db:seed": "turbo run db:seed",
|
|
20
|
+
"db:reset": "turbo run db:reset"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"turbo": "^2.1.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://turbo.build/schema.json",
|
|
3
|
+
"tasks": {
|
|
4
|
+
"build": {
|
|
5
|
+
"dependsOn": ["^build"],
|
|
6
|
+
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
|
|
7
|
+
},
|
|
8
|
+
"dev": {
|
|
9
|
+
"cache": false,
|
|
10
|
+
"persistent": true
|
|
11
|
+
},
|
|
12
|
+
"lint": {},
|
|
13
|
+
"db:migrate": { "cache": false },
|
|
14
|
+
"db:seed": { "cache": false },
|
|
15
|
+
"db:reset": { "cache": false }
|
|
16
|
+
}
|
|
17
|
+
}
|