start-vibing-stacks 2.2.0 → 2.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.
@@ -0,0 +1,342 @@
1
+ # Inertia.js + React Integration
2
+
3
+ ## Architecture Overview
4
+
5
+ Inertia.js acts as a bridge between Laravel (backend) and React (frontend). There is NO separate API layer — controllers return Inertia responses that render React page components with server-side data as props.
6
+
7
+ ```
8
+ Request Flow:
9
+ Browser → Laravel Router → Controller → Inertia::render('Page', $props)
10
+
11
+ HandleInertiaRequests (middleware)
12
+
13
+ InertiaShare (shared props: auth, translations, menu)
14
+
15
+ React Page Component (receives all props)
16
+ ```
17
+
18
+ ## Backend: Middleware
19
+
20
+ ### HandleInertiaRequests
21
+
22
+ The Inertia middleware merges shared props (auth, translations, flash messages) into every response:
23
+
24
+ ```php
25
+ namespace App\Http\Middleware;
26
+
27
+ use Inertia\Middleware;
28
+ use App\Support\InertiaShare;
29
+
30
+ class HandleInertiaRequests extends Middleware
31
+ {
32
+ protected $rootView = 'app';
33
+
34
+ public function version(Request $request): ?string
35
+ {
36
+ return parent::version($request);
37
+ }
38
+
39
+ public function share(Request $request): array
40
+ {
41
+ return array_merge(
42
+ parent::share($request),
43
+ InertiaShare::getProps($request),
44
+ );
45
+ }
46
+ }
47
+ ```
48
+
49
+ **Rules:**
50
+ - Keep `share()` lean — delegate to `InertiaShare` helper
51
+ - `parent::share()` provides validation errors automatically
52
+ - `version()` triggers full page reload on asset changes
53
+
54
+ ### InertiaShare Support Class
55
+
56
+ Centralize all shared props in `App\Support\InertiaShare`:
57
+
58
+ ```php
59
+ namespace App\Support;
60
+
61
+ class InertiaShare
62
+ {
63
+ public static function getProps(Request $request): array
64
+ {
65
+ $locale = App::getLocale();
66
+
67
+ return [
68
+ 'auth' => [
69
+ 'user' => $request->user() ? [
70
+ 'id' => $request->user()->id,
71
+ 'name' => $request->user()->name,
72
+ 'role' => $request->user()->role,
73
+ 'email' => $request->user()->email,
74
+ 'timezone' => $request->user()->timezone,
75
+ ] : null,
76
+ ],
77
+ 'locale' => $locale,
78
+ 'translations' => static::getTranslations(
79
+ $locale,
80
+ $request->route()->uri,
81
+ ),
82
+ 'flash' => $request->hasSession() ? [
83
+ 'success' => Session::get('success'),
84
+ 'error' => Session::get('error'),
85
+ ] : [],
86
+ ];
87
+ }
88
+ }
89
+ ```
90
+
91
+ **Rules:**
92
+ - Auth data: expose only necessary fields (never passwords, tokens)
93
+ - Translations loaded on-demand per page (not all at once)
94
+ - Flash messages via Laravel session
95
+ - Menu structure loaded from DB, filtered by permissions, cached per user
96
+
97
+ ## Backend: Controllers with Inertia
98
+
99
+ Controllers render React page components via `Inertia::render()`:
100
+
101
+ ```php
102
+ use Inertia\Inertia;
103
+ use Inertia\Response as InertiaResponse;
104
+
105
+ class DashboardController extends Controller
106
+ {
107
+ public function __construct(
108
+ private readonly DashboardService $service,
109
+ ) {}
110
+
111
+ public function index(Request $request): InertiaResponse
112
+ {
113
+ return Inertia::render('Dashboard/Index', [
114
+ 'stats' => $this->service->getStats($request->user()),
115
+ 'recentOrders' => fn () => OrderResource::collection(
116
+ $request->user()->orders()->latest()->limit(10)->get()
117
+ ),
118
+ ]);
119
+ }
120
+
121
+ public function show(Order $order): InertiaResponse
122
+ {
123
+ return Inertia::render('Orders/Show', [
124
+ 'order' => OrderResource::make($order->load('items')),
125
+ ]);
126
+ }
127
+ }
128
+ ```
129
+
130
+ **Rules:**
131
+ - Return type: `Inertia\Response` (not `JsonResponse`)
132
+ - Page component path maps to: `resources/js/Pages/{path}`
133
+ - Use lazy props with `fn ()` for data not needed on first render
134
+ - Use API Resources to format complex data before passing as props
135
+ - Keep controller thin — delegate to services
136
+
137
+ ### Inertia Redirects
138
+
139
+ ```php
140
+ // After mutations, redirect (Inertia handles SPA navigation)
141
+ public function store(StoreOrderRequest $request): RedirectResponse
142
+ {
143
+ $order = $this->service->create($request->validated());
144
+
145
+ return redirect()
146
+ ->route('orders.show', $order)
147
+ ->with('success', __('orders.created'));
148
+ }
149
+
150
+ public function destroy(Order $order): RedirectResponse
151
+ {
152
+ $this->service->delete($order);
153
+
154
+ return redirect()
155
+ ->route('orders.index')
156
+ ->with('success', __('orders.deleted'));
157
+ }
158
+ ```
159
+
160
+ **Rule:** POST/PUT/DELETE actions return `redirect()` with flash messages, never `Inertia::render()`.
161
+
162
+ ## Backend: Translations On-Demand
163
+
164
+ Translations are loaded per-page via a config file that maps routes to translation files:
165
+
166
+ ```php
167
+ // config/translations_inertia.php
168
+ return [
169
+ 'global' => ['common', 'errors', 'validation'],
170
+
171
+ 'pages' => [
172
+ 'dashboard' => ['dashboard'],
173
+ 'orders/*' => ['orders', 'products'],
174
+ 'settings/*' => ['settings'],
175
+ ],
176
+ ];
177
+ ```
178
+
179
+ The `InertiaShare::getTranslations()` method:
180
+ 1. Loads global translation files (always sent)
181
+ 2. Loads page-specific files based on route URI
182
+ 3. Merges PHP files (`lang/{locale}/*.php`) + JSON file (`lang/{locale}.json`)
183
+ 4. Caches result per locale + page combination
184
+
185
+ **Rules:**
186
+ - Global files: `common`, `errors`, `validation` (always loaded)
187
+ - Page files: only load what the page needs
188
+ - Cache invalidation: clear on deploy (`php artisan cache:clear`)
189
+ - Store translations in `lang/en/*.php` and `lang/pt/*.php`
190
+
191
+ ## Frontend: Translation Helper
192
+
193
+ The `__()` function resolves translation keys from Inertia shared props:
194
+
195
+ ```js
196
+ // resources/js/Utils/translate.js
197
+ import { usePage } from '@inertiajs/react';
198
+
199
+ export default function __(key, replacements = {}, pageProps = null) {
200
+ const propsSource = pageProps ? { props: pageProps } : usePage();
201
+ const translations = propsSource.props.translations || {};
202
+
203
+ let translation = key.split('.').reduce((obj, part) => {
204
+ return obj && typeof obj[part] !== 'undefined' ? obj[part] : null;
205
+ }, translations);
206
+
207
+ if (translation === null) {
208
+ return key;
209
+ }
210
+
211
+ if (typeof translation === 'string' && Object.keys(replacements).length > 0) {
212
+ Object.keys(replacements).forEach((placeholder) => {
213
+ translation = translation.replace(
214
+ new RegExp(`:${placeholder}`, 'g'),
215
+ replacements[placeholder],
216
+ );
217
+ });
218
+ }
219
+
220
+ return translation;
221
+ }
222
+ ```
223
+
224
+ ### Usage in React Components
225
+
226
+ ```tsx
227
+ import __ from '@/Utils/translate';
228
+
229
+ // CORRECT: Define translations as CONST before hooks
230
+ const LABELS = {
231
+ title: __('dashboard.title'),
232
+ welcome: __('dashboard.welcome', { name: 'User' }),
233
+ save: __('common.save'),
234
+ };
235
+
236
+ export default function Dashboard({ stats }) {
237
+ const [loading, setLoading] = useState(false);
238
+
239
+ return (
240
+ <div>
241
+ <h1>{LABELS.title}</h1>
242
+ <p>{LABELS.welcome}</p>
243
+ </div>
244
+ );
245
+ }
246
+
247
+ // WRONG: Calling __() inside JSX (React Hook violation)
248
+ return <h1>{__('dashboard.title')}</h1>; // NEVER
249
+ ```
250
+
251
+ **Rules:**
252
+ - ALWAYS define translations as `CONST` at the top of the component, BEFORE hooks
253
+ - NEVER call `__()` inside JSX or render methods
254
+ - New strings must be added to both `lang/en/*.php` and `lang/pt/*.php`
255
+ - Error strings centralized in `lang/*/errors.php`
256
+ - Use replacements for dynamic values: `__('greeting', { name: userName })`
257
+
258
+ ## Frontend: Page Component Structure
259
+
260
+ ```
261
+ resources/js/
262
+ ├── Pages/
263
+ │ ├── Dashboard/
264
+ │ │ └── Index.jsx
265
+ │ ├── Orders/
266
+ │ │ ├── Index.jsx
267
+ │ │ ├── Show.jsx
268
+ │ │ └── _components/ # Page-specific components
269
+ │ │ ├── OrderTable.jsx
270
+ │ │ └── OrderFilters.jsx
271
+ │ ├── Auth/
272
+ │ │ ├── Login.jsx
273
+ │ │ └── Register.jsx
274
+ │ └── Users/
275
+ │ └── Admin/
276
+ │ └── Dashboard.jsx
277
+ ├── Components/
278
+ │ ├── UI/ # Reusable UI primitives
279
+ │ ├── Layout/ # Header, Sidebar, Footer
280
+ │ └── Shared/ # Cross-feature components
281
+ ├── Icons/
282
+ │ ├── index.js # Barrel export
283
+ │ ├── CheckIcon.svg
284
+ │ └── AlertIcon.svg
285
+ ├── Layouts/
286
+ │ ├── AuthenticatedLayout.jsx
287
+ │ └── GuestLayout.jsx
288
+ └── Utils/
289
+ └── translate.js # __() helper
290
+ ```
291
+
292
+ **Rules:**
293
+ - Pages map 1:1 to `Inertia::render('Path/Component')`
294
+ - Page-specific components in `_components/` folder
295
+ - Shared components in `Components/`
296
+ - Icons as separate `.svg` files, imported with `?react` suffix
297
+
298
+ ## Frontend: Inertia Hooks
299
+
300
+ ```tsx
301
+ import { usePage, useForm, router, Link } from '@inertiajs/react';
302
+
303
+ // Access shared props
304
+ const { auth, flash, locale } = usePage().props;
305
+
306
+ // Form handling with Inertia
307
+ const { data, setData, post, processing, errors } = useForm({
308
+ name: '',
309
+ email: '',
310
+ });
311
+
312
+ const handleSubmit = (e) => {
313
+ e.preventDefault();
314
+ post(route('users.store'));
315
+ };
316
+
317
+ // Programmatic navigation
318
+ router.visit(route('dashboard'));
319
+ router.reload({ only: ['stats'] }); // Partial reload
320
+
321
+ // Links (SPA navigation, no full page reload)
322
+ <Link href={route('orders.index')}>Orders</Link>
323
+ ```
324
+
325
+ **Rules:**
326
+ - Use `useForm` for all form submissions (handles CSRF, errors, loading state)
327
+ - Use `router.reload({ only: [...] })` for partial page updates
328
+ - Use `<Link>` instead of `<a>` for SPA navigation
329
+ - Access shared props via `usePage().props`
330
+ - `processing` boolean from `useForm` for button loading states
331
+
332
+ ## Forbidden Patterns
333
+
334
+ | Pattern | Reason | Use Instead |
335
+ |---------|--------|-------------|
336
+ | `fetch()` / `axios` for page data | Bypasses Inertia | `Inertia::render()` with props |
337
+ | `__()` inside JSX | React Hook violation | CONST at top of component |
338
+ | Inline SVGs in JSX | Bloats components | SVG files with `?react` import |
339
+ | `<a href>` for internal links | Full page reload | `<Link href>` |
340
+ | `window.location` for navigation | Full page reload | `router.visit()` |
341
+ | `Inertia::render()` after POST | Breaks Inertia protocol | `redirect()->route()` |
342
+ | Loading all translations globally | Performance waste | On-demand per page route |
@@ -0,0 +1,267 @@
1
+ # React 19+ Standards (with Inertia.js)
2
+
3
+ ## Version Requirements
4
+
5
+ - **ReactJS >= 19** — MANDATORY
6
+ - **TailwindCSS >= 4** — MANDATORY
7
+ - **Inertia.js >= 2** — MANDATORY
8
+
9
+ ## Translation Pattern (via Inertia shared props)
10
+
11
+ ```tsx
12
+ import __ from '@/Utils/translate';
13
+
14
+ // CORRECT: Translations as CONST at the top, BEFORE hooks
15
+ const LABELS = {
16
+ title: __('dashboard.title'),
17
+ save: __('common.save'),
18
+ cancel: __('common.cancel'),
19
+ errorRequired: __('errors.field_required'),
20
+ welcome: __('dashboard.welcome', { name: 'User' }),
21
+ };
22
+
23
+ export default function Dashboard() {
24
+ const [data, setData] = useState(null);
25
+
26
+ return <h1>{LABELS.title}</h1>;
27
+ }
28
+
29
+ // WRONG: __() inside JSX (Hook violation — usePage() is called internally)
30
+ return <h1>{__('dashboard.title')}</h1>; // NEVER
31
+ ```
32
+
33
+ **Rules:**
34
+ - Translations in `CONST` variables before state hooks
35
+ - New strings must be added to `lang/en/*.php` AND `lang/pt/*.php`
36
+ - Error strings centralized in `lang/*/errors.php`
37
+ - Use replacements for dynamic values: `__('key', { name: value })`
38
+
39
+ ## Debug Logging
40
+
41
+ ```tsx
42
+ const ENABLE_DASHBOARD_DEBUG = false;
43
+
44
+ const debugLog = (...args: unknown[]) => {
45
+ if (ENABLE_DASHBOARD_DEBUG) console.log('[Dashboard]', ...args);
46
+ };
47
+
48
+ export default function Dashboard() {
49
+ debugLog('Rendering with data:', data);
50
+ }
51
+ ```
52
+
53
+ **Rule:** Never leave raw `console.log`. Always use controlled debug pattern.
54
+
55
+ ## TailwindCSS Class Organization
56
+
57
+ ```tsx
58
+ // CORRECT: Classes as CONST — clean JSX
59
+ const STYLES = {
60
+ container: 'flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm',
61
+ title: 'text-2xl font-bold text-gray-900',
62
+ button: 'px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition',
63
+ grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6',
64
+ };
65
+
66
+ export default function Dashboard() {
67
+ return (
68
+ <div className={STYLES.container}>
69
+ <h1 className={STYLES.title}>{LABELS.title}</h1>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ // WRONG: Inline class soup
75
+ <div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm"> // NEVER
76
+ ```
77
+
78
+ ## SVG Icons
79
+
80
+ ```tsx
81
+ // CORRECT: Separate files, import with ?react
82
+ import CheckIcon from '@/Icons/CheckIcon.svg?react';
83
+ import { CheckIcon, AlertIcon } from '@/Icons';
84
+
85
+ // WRONG: Inline SVG (bloats JSX)
86
+ <svg viewBox="0 0 24 24">...</svg> // NEVER
87
+ ```
88
+
89
+ **Structure:**
90
+ ```
91
+ resources/js/Icons/
92
+ ├── index.js # Barrel export
93
+ ├── CheckIcon.svg
94
+ ├── AlertIcon.svg
95
+ └── SpinnerIcon.svg
96
+ ```
97
+
98
+ ## Inertia.js Hooks & Navigation
99
+
100
+ ### Accessing Shared Props
101
+
102
+ ```tsx
103
+ import { usePage } from '@inertiajs/react';
104
+
105
+ export default function Header() {
106
+ const { auth, locale, flash } = usePage().props;
107
+
108
+ return (
109
+ <nav>
110
+ <span>{auth.user?.name}</span>
111
+ {flash.success && <Alert>{flash.success}</Alert>}
112
+ </nav>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ### Forms with useForm
118
+
119
+ ```tsx
120
+ import { useForm } from '@inertiajs/react';
121
+
122
+ export default function CreateOrder() {
123
+ const { data, setData, post, processing, errors } = useForm({
124
+ product_id: '',
125
+ quantity: 1,
126
+ notes: '',
127
+ });
128
+
129
+ const handleSubmit = (e) => {
130
+ e.preventDefault();
131
+ post(route('orders.store'));
132
+ };
133
+
134
+ return (
135
+ <form onSubmit={handleSubmit}>
136
+ <Input
137
+ value={data.product_id}
138
+ onChange={(e) => setData('product_id', e.target.value)}
139
+ error={errors.product_id}
140
+ />
141
+ <Button type="submit" loading={processing}>
142
+ {processing ? <LoadingSpinner /> : LABELS.save}
143
+ </Button>
144
+ </form>
145
+ );
146
+ }
147
+ ```
148
+
149
+ **Rules:**
150
+ - Use `useForm` for ALL form submissions (handles CSRF, errors, loading)
151
+ - `processing` boolean for button loading states
152
+ - `errors` object maps to Form Request validation errors
153
+ - Never use `fetch()` or `axios` for form submissions
154
+
155
+ ### Navigation
156
+
157
+ ```tsx
158
+ import { Link, router } from '@inertiajs/react';
159
+
160
+ // SPA links (no full page reload)
161
+ <Link href={route('orders.index')}>Orders</Link>
162
+
163
+ // Programmatic navigation
164
+ router.visit(route('dashboard'));
165
+
166
+ // Partial reload (only refresh specific props)
167
+ router.reload({ only: ['stats', 'recentOrders'] });
168
+ ```
169
+
170
+ ## Loading States
171
+
172
+ ```tsx
173
+ // CORRECT: Always show loading feedback
174
+ export default function DataTable() {
175
+ const [loading, setLoading] = useState(true);
176
+
177
+ if (loading) {
178
+ return <SectionLoader />;
179
+ }
180
+
181
+ return <Table data={data} />;
182
+ }
183
+
184
+ // Button loading (from useForm)
185
+ <Button onClick={handleSave} disabled={processing}>
186
+ {processing ? <LoadingSpinner /> : LABELS.save}
187
+ </Button>
188
+ ```
189
+
190
+ **Rule:** Every data-heavy section needs a loading state.
191
+
192
+ ## Modal Data Flow
193
+
194
+ ```tsx
195
+ interface EditModalProps {
196
+ item: Item;
197
+ isOpen: boolean;
198
+ onClose: () => void;
199
+ onUpdated: () => void;
200
+ }
201
+
202
+ function EditModal({ item, isOpen, onClose, onUpdated }: EditModalProps) {
203
+ const { data, setData, put, processing } = useForm({
204
+ name: item.name,
205
+ });
206
+
207
+ const handleSave = (e) => {
208
+ e.preventDefault();
209
+ put(route('items.update', item.id), {
210
+ onSuccess: () => {
211
+ onUpdated();
212
+ onClose();
213
+ },
214
+ });
215
+ };
216
+ }
217
+
218
+ // Parent: use router.reload for refresh after modal mutation
219
+ <EditModal
220
+ item={selectedItem}
221
+ isOpen={showModal}
222
+ onClose={() => setShowModal(false)}
223
+ onUpdated={() => router.reload({ only: ['items'] })}
224
+ />
225
+ ```
226
+
227
+ ## Third-Party Libraries (Charts)
228
+
229
+ ```tsx
230
+ // CORRECT: Let React handle re-rendering
231
+ {chartData && (
232
+ <ApexChart
233
+ key={JSON.stringify(chartData)}
234
+ options={chartOptions}
235
+ series={chartData}
236
+ type="area"
237
+ />
238
+ )}
239
+
240
+ // WRONG: Manual DOM manipulation
241
+ const chartRef = useRef(null);
242
+ chartRef.current.updateSeries(newData); // NEVER
243
+
244
+ // Memoize expensive computations
245
+ const processedData = useMemo(() => {
246
+ return heavyTransform(rawData);
247
+ }, [rawData]);
248
+ ```
249
+
250
+ **Rules:**
251
+ - No `useRef` for updating third-party components
252
+ - Conditional rendering: `data && <Component />`
253
+ - `useMemo` for expensive computations
254
+ - Loading states before rendering charts
255
+
256
+ ## Forbidden Patterns
257
+
258
+ | Pattern | Reason | Use Instead |
259
+ |---------|--------|-------------|
260
+ | `fetch()` / `axios` for pages | Bypasses Inertia | `Inertia::render()` props |
261
+ | `__()` inside JSX | Hook violation | CONST at top |
262
+ | Inline SVGs | Bloats components | SVG files + `?react` |
263
+ | `<a href>` for internal links | Full reload | `<Link href>` |
264
+ | `window.location` | Full reload | `router.visit()` |
265
+ | Raw `console.log` | Uncontrolled | Debug constant pattern |
266
+ | Inline Tailwind soup | Unreadable | STYLES const object |
267
+ | `axios.post()` for forms | No CSRF/errors | `useForm().post()` |