create-react-starter-kit 1.0.1 → 1.0.3
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 +0 -66
- package/dist/generator/dependencies.d.ts.map +1 -1
- package/dist/generator/dependencies.js +20 -2
- package/dist/generator/dependencies.js.map +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +10 -2
- package/dist/generator/index.js.map +1 -1
- package/dist/templates/README.md.ejs +23 -29
- package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
- package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
- package/dist/templates/__when_formatting_prettier__/.prettierignore +28 -0
- package/dist/templates/__when_linting_biome__/biome.json +12 -0
- package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
- package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
- package/dist/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
- package/dist/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
- package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
- package/dist/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
- package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
- package/dist/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
- package/dist/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
- package/dist/templates/docs/architecture.md.ejs +32 -34
- package/dist/templates/docs/getting-started.md.ejs +1 -1
- package/{templates/src/app → dist/templates/src}/App.tsx.ejs +34 -1
- package/dist/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
- package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
- package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
- package/dist/templates/src/main.tsx.ejs +2 -2
- package/{templates/src/app → dist/templates/src}/providers.tsx.ejs +1 -2
- package/dist/templates/src/routes/AboutPage.tsx.ejs +11 -10
- package/dist/templates/src/routes/CounterPage.tsx.ejs +17 -0
- package/dist/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
- package/dist/templates/src/routes/HomePage.tsx.ejs +43 -21
- package/dist/templates/src/routes/PostsPage.tsx.ejs +17 -0
- package/dist/templates/src/styles/global.css.ejs +14 -33
- package/package.json +1 -1
- package/templates/README.md.ejs +23 -29
- package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +17 -1
- package/templates/__when_e2eTesting_playwright__/tests/e2e/counter.spec.ts.ejs +25 -0
- package/templates/__when_e2eTesting_playwright__/tests/e2e/feedback.spec.ts.ejs +20 -0
- package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +30 -3
- package/templates/__when_e2eTesting_playwright__/tests/e2e/posts.spec.ts.ejs +17 -0
- package/templates/__when_formatting_prettier__/.prettierignore +28 -0
- package/templates/__when_linting_biome__/biome.json +12 -0
- package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +10 -1
- package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +10 -1
- package/templates/__when_unitTesting_vitest__/tests/counter.test.tsx.ejs +36 -0
- package/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +49 -0
- package/templates/__when_unitTesting_vitest__/tests/pages.test.tsx.ejs +26 -0
- package/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +58 -0
- package/templates/__when_unitTesting_vitest__/tests/test-utils.tsx.ejs +50 -0
- package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +4 -1
- package/templates/__when_visualTesting_storybook__/.storybook/preview.js.ejs +40 -0
- package/templates/__when_visualTesting_storybook__/.storybook/preview.tsx.ejs +40 -0
- package/templates/__when_visualTesting_storybook__/src/components/StackBadge.stories.tsx.ejs +35 -0
- package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +24 -1
- package/templates/__when_visualTesting_storybook__/src/features/counter/CounterPanel.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/features/feedback/FeedbackForm.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/features/posts/PostsPanel.stories.tsx.ejs +25 -0
- package/templates/__when_visualTesting_storybook__/src/routes/Pages.stories.tsx.ejs +42 -0
- package/templates/docs/architecture.md.ejs +32 -34
- package/templates/docs/getting-started.md.ejs +1 -1
- package/{dist/templates/src/app → templates/src}/App.tsx.ejs +34 -1
- package/templates/src/components/layout/AppLayout.tsx.ejs +35 -12
- package/templates/src/features/counter/CounterPanel.tsx.ejs +12 -6
- package/templates/src/features/feedback/FeedbackForm.tsx.ejs +26 -7
- package/templates/src/main.tsx.ejs +2 -2
- package/{dist/templates/src/app → templates/src}/providers.tsx.ejs +1 -2
- package/templates/src/routes/AboutPage.tsx.ejs +11 -10
- package/templates/src/routes/CounterPage.tsx.ejs +17 -0
- package/templates/src/routes/FeedbackPage.tsx.ejs +21 -0
- package/templates/src/routes/HomePage.tsx.ejs +43 -21
- package/templates/src/routes/PostsPage.tsx.ejs +17 -0
- package/templates/src/styles/global.css.ejs +14 -33
- package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
- package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
- package/dist/templates/src/components/ui/Toast.tsx.ejs +0 -61
- package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +0 -16
- package/templates/__when_visualTesting_storybook__/.storybook/preview.js +0 -8
- package/templates/src/components/ui/Toast.tsx.ejs +0 -61
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { HomePage } from '@/routes/HomePage';
|
|
3
|
+
import { AboutPage } from '@/routes/AboutPage';
|
|
4
|
+
import { CounterPage } from '@/routes/CounterPage';
|
|
5
|
+
import { PostsPage } from '@/routes/PostsPage';
|
|
6
|
+
import { FeedbackPage } from '@/routes/FeedbackPage';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'Pages/Routes',
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'fullscreen',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
'Route-level pages composed from feature panels. Useful for visual review of layout and copy.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
} satisfies Meta;
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj;
|
|
23
|
+
|
|
24
|
+
export const Home: Story = {
|
|
25
|
+
render: () => <HomePage />,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const About: Story = {
|
|
29
|
+
render: () => <AboutPage />,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const Counter: Story = {
|
|
33
|
+
render: () => <CounterPage />,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const Posts: Story = {
|
|
37
|
+
render: () => <PostsPage />,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const Feedback: Story = {
|
|
41
|
+
render: () => <FeedbackPage />,
|
|
42
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Why this stack
|
|
4
4
|
|
|
5
|
-
You selected a coherent default-friendly stack during scaffolding. The generator wires providers, scripts, and
|
|
5
|
+
You selected a coherent default-friendly stack during scaffolding. The generator wires providers, scripts, and demo routes so packages are not orphaned in `package.json`.
|
|
6
6
|
|
|
7
7
|
| Concern | Choice | Role |
|
|
8
8
|
|---------|--------|------|
|
|
@@ -19,56 +19,54 @@ You selected a coherent default-friendly stack during scaffolding. The generator
|
|
|
19
19
|
|
|
20
20
|
## Folder organization (feature-based)
|
|
21
21
|
|
|
22
|
-
This scaffold uses a **feature-based / domain-driven** layout — the pattern most large React teams settle on as apps grow (similar in spirit to how companies structure product domains: keep code that changes together close together).
|
|
23
|
-
|
|
24
22
|
```text
|
|
25
23
|
src/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
24
|
+
main.<%= jsxExt %> # bootstrap + render
|
|
25
|
+
providers.<%= jsxExt %>
|
|
26
|
+
App.<%= jsxExt %> # route tree
|
|
27
|
+
assets/
|
|
28
|
+
components/ # shared UI (layout/, ui/)
|
|
29
|
+
config/
|
|
30
|
+
features/ # domain modules
|
|
31
|
+
hooks/
|
|
32
|
+
lib/
|
|
33
|
+
routes/ # / /counter /posts /feedback /about
|
|
34
|
+
services/
|
|
35
|
+
store/
|
|
36
|
+
styles/
|
|
37
|
+
types/
|
|
38
|
+
utils/
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
**Rules of thumb**
|
|
42
42
|
|
|
43
43
|
- Put new product work in `src/features/<domain>/`.
|
|
44
44
|
- Keep `components/` dumb and reusable.
|
|
45
|
-
- Import with the `@/` alias
|
|
45
|
+
- Import with the `@/` alias.
|
|
46
46
|
- Prefer a feature's public surface over deep cross-feature imports.
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Routes
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
| Path | Purpose |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `/` | Home overview |
|
|
53
|
+
| `/counter` | Client state demo |
|
|
54
|
+
| `/posts` | Server state / data fetching |
|
|
55
|
+
| `/feedback` | Forms + validation |
|
|
56
|
+
| `/about` | Orientation |
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```
|
|
58
|
+
## Path aliases
|
|
59
|
+
|
|
60
|
+
`@/*` maps to `src/*` in TypeScript/JS config and in your bundler.
|
|
56
61
|
|
|
57
62
|
## Runtime composition
|
|
58
63
|
|
|
59
|
-
`src/main.<%= jsxExt %>` →
|
|
64
|
+
`src/main.<%= jsxExt %>` → `AppProviders` → `App` (router) → layout → pages.
|
|
60
65
|
|
|
61
|
-
Providers are ordered
|
|
66
|
+
Providers are ordered: client state → server cache → UI theme.
|
|
62
67
|
|
|
63
68
|
## Boundaries
|
|
64
69
|
|
|
65
|
-
- **UI**
|
|
66
|
-
- **Schemas** live in `lib/validation
|
|
70
|
+
- **UI** should call HTTP through `services/`.
|
|
71
|
+
- **Schemas** live in `lib/validation`.
|
|
67
72
|
- **Env access** stays in `config/`, `services/`, and `lib/monitoring`.
|
|
68
|
-
|
|
69
|
-
## Production readiness patterns
|
|
70
|
-
|
|
71
|
-
- Code-split heavy routes when the app grows.
|
|
72
|
-
- Fail closed on missing monitoring DSN (already gated).
|
|
73
|
-
- Keep lint/format/test scripts green in CI.
|
|
74
|
-
- Document public env vars only in `.env.example`.
|
|
@@ -38,7 +38,7 @@ Only variables prefixed for your bundler (e.g. `VITE_`) are exposed to the brows
|
|
|
38
38
|
## Extending the app
|
|
39
39
|
|
|
40
40
|
- Add a domain under `src/features/<name>/`.
|
|
41
|
-
- Register routes in `src/
|
|
41
|
+
- Register routes in `src/App.<%= jsxExt %>`.
|
|
42
42
|
- Put shared UI in `src/components/`.
|
|
43
43
|
- Put HTTP clients in `src/services/`.
|
|
44
44
|
- Import with `@/` (maps to `src/`), e.g. `import { env } from '@/config/env'`.
|
|
@@ -3,6 +3,9 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
|
|
3
3
|
import { AppLayout } from '@/components/layout/AppLayout';
|
|
4
4
|
import { HomePage } from '@/routes/HomePage';
|
|
5
5
|
import { AboutPage } from '@/routes/AboutPage';
|
|
6
|
+
import { CounterPage } from '@/routes/CounterPage';
|
|
7
|
+
import { PostsPage } from '@/routes/PostsPage';
|
|
8
|
+
import { FeedbackPage } from '@/routes/FeedbackPage';
|
|
6
9
|
|
|
7
10
|
export function App() {
|
|
8
11
|
return (
|
|
@@ -11,6 +14,9 @@ export function App() {
|
|
|
11
14
|
<Route element={<AppLayout />}>
|
|
12
15
|
<Route path="/" element={<HomePage />} />
|
|
13
16
|
<Route path="/about" element={<AboutPage />} />
|
|
17
|
+
<Route path="/counter" element={<CounterPage />} />
|
|
18
|
+
<Route path="/posts" element={<PostsPage />} />
|
|
19
|
+
<Route path="/feedback" element={<FeedbackPage />} />
|
|
14
20
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
15
21
|
</Route>
|
|
16
22
|
</Routes>
|
|
@@ -28,6 +34,9 @@ import {
|
|
|
28
34
|
import { AppLayout } from '@/components/layout/AppLayout';
|
|
29
35
|
import { HomePage } from '@/routes/HomePage';
|
|
30
36
|
import { AboutPage } from '@/routes/AboutPage';
|
|
37
|
+
import { CounterPage } from '@/routes/CounterPage';
|
|
38
|
+
import { PostsPage } from '@/routes/PostsPage';
|
|
39
|
+
import { FeedbackPage } from '@/routes/FeedbackPage';
|
|
31
40
|
|
|
32
41
|
function RootComponent() {
|
|
33
42
|
return (
|
|
@@ -53,7 +62,31 @@ const aboutRoute = createRoute({
|
|
|
53
62
|
component: AboutPage,
|
|
54
63
|
});
|
|
55
64
|
|
|
56
|
-
const
|
|
65
|
+
const counterRoute = createRoute({
|
|
66
|
+
getParentRoute: () => rootRoute,
|
|
67
|
+
path: '/counter',
|
|
68
|
+
component: CounterPage,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const postsRoute = createRoute({
|
|
72
|
+
getParentRoute: () => rootRoute,
|
|
73
|
+
path: '/posts',
|
|
74
|
+
component: PostsPage,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const feedbackRoute = createRoute({
|
|
78
|
+
getParentRoute: () => rootRoute,
|
|
79
|
+
path: '/feedback',
|
|
80
|
+
component: FeedbackPage,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const routeTree = rootRoute.addChildren([
|
|
84
|
+
indexRoute,
|
|
85
|
+
aboutRoute,
|
|
86
|
+
counterRoute,
|
|
87
|
+
postsRoute,
|
|
88
|
+
feedbackRoute,
|
|
89
|
+
]);
|
|
57
90
|
|
|
58
91
|
const router = createRouter({ routeTree });
|
|
59
92
|
|
|
@@ -13,6 +13,14 @@ interface AppLayoutProps {
|
|
|
13
13
|
}
|
|
14
14
|
<% } -%>
|
|
15
15
|
|
|
16
|
+
const links = [
|
|
17
|
+
{ to: '/', label: 'Home', end: true },
|
|
18
|
+
{ to: '/counter', label: 'Counter' },
|
|
19
|
+
{ to: '/posts', label: 'Posts' },
|
|
20
|
+
{ to: '/feedback', label: 'Feedback' },
|
|
21
|
+
{ to: '/about', label: 'About' },
|
|
22
|
+
] as const;
|
|
23
|
+
|
|
16
24
|
export function AppLayout(<% if (router === 'tanstack-router') { %>{ children }<% if (isTypeScript) { %>: AppLayoutProps<% } %><% } %>) {
|
|
17
25
|
<% if (router === 'tanstack-router') { -%>
|
|
18
26
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
@@ -30,19 +38,34 @@ export function AppLayout(<% if (router === 'tanstack-router') { %>{ children }<
|
|
|
30
38
|
</div>
|
|
31
39
|
<nav className="app-nav" aria-label="Primary">
|
|
32
40
|
<% if (router === 'react-router') { -%>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
{links.map((link) => (
|
|
42
|
+
<NavLink
|
|
43
|
+
key={link.to}
|
|
44
|
+
to={link.to}
|
|
45
|
+
end={'end' in link ? link.end : false}
|
|
46
|
+
className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
|
|
47
|
+
>
|
|
48
|
+
{link.label}
|
|
49
|
+
</NavLink>
|
|
50
|
+
))}
|
|
39
51
|
<% } else { -%>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
{links.map((link) => (
|
|
53
|
+
<Link
|
|
54
|
+
key={link.to}
|
|
55
|
+
to={link.to}
|
|
56
|
+
className={
|
|
57
|
+
link.to === '/'
|
|
58
|
+
? pathname === '/'
|
|
59
|
+
? 'nav-link active'
|
|
60
|
+
: 'nav-link'
|
|
61
|
+
: pathname.startsWith(link.to)
|
|
62
|
+
? 'nav-link active'
|
|
63
|
+
: 'nav-link'
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
{link.label}
|
|
67
|
+
</Link>
|
|
68
|
+
))}
|
|
46
69
|
<% } -%>
|
|
47
70
|
</nav>
|
|
48
71
|
</header>
|
|
@@ -13,10 +13,8 @@ import { counterAtom } from '@/store';
|
|
|
13
13
|
import { useAppState } from '@/store/context';
|
|
14
14
|
<% } -%>
|
|
15
15
|
import { UiButton } from '@/components/ui/UiButton';
|
|
16
|
-
import { useToast } from '@/components/ui/Toast';
|
|
17
16
|
|
|
18
17
|
export function CounterPanel() {
|
|
19
|
-
const { push } = useToast();
|
|
20
18
|
<% if (stateManagement === 'redux') { -%>
|
|
21
19
|
const value = useSelector((state<% if (isTypeScript) { %>: RootState<% } %>) => state.counter.value);
|
|
22
20
|
const dispatch = useDispatch();
|
|
@@ -48,12 +46,20 @@ export function CounterPanel() {
|
|
|
48
46
|
<p><%= stackLabels.stateManagement %> example — local interactive counter.</p>
|
|
49
47
|
</header>
|
|
50
48
|
<% if (stateManagement === 'none') { -%>
|
|
51
|
-
<p className="muted">
|
|
49
|
+
<p className="muted">
|
|
50
|
+
Client state library was set to None. Add one later under <code>src/store</code>.
|
|
51
|
+
</p>
|
|
52
52
|
<% } else { -%>
|
|
53
53
|
<div className="counter">
|
|
54
|
-
<UiButton onClick={
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
<UiButton aria-label="Decrement" onClick={onDec}>
|
|
55
|
+
−
|
|
56
|
+
</UiButton>
|
|
57
|
+
<strong className="counter-value" data-testid="counter-value" aria-live="polite">
|
|
58
|
+
{value}
|
|
59
|
+
</strong>
|
|
60
|
+
<UiButton aria-label="Increment" onClick={onInc}>
|
|
61
|
+
+
|
|
62
|
+
</UiButton>
|
|
57
63
|
</div>
|
|
58
64
|
<% } -%>
|
|
59
65
|
<p className="panel-note">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
<% if (forms === 'react-hook-form') { -%>
|
|
2
3
|
import { useForm } from 'react-hook-form';
|
|
3
4
|
<% if (validation === 'zod') { -%>
|
|
@@ -25,7 +26,6 @@ import { feedbackSchema } from '@/lib/validation';
|
|
|
25
26
|
import type { FeedbackValues } from '@/lib/validation';
|
|
26
27
|
<% } -%>
|
|
27
28
|
<% } else { -%>
|
|
28
|
-
import { useState } from 'react';
|
|
29
29
|
<% if (validation === 'none') { -%>
|
|
30
30
|
import { validateFeedback<% if (isTypeScript) { %>, type FeedbackValues<% } %> } from '@/lib/validation';
|
|
31
31
|
<% } else { -%>
|
|
@@ -33,7 +33,6 @@ import { feedbackSchema<% if (isTypeScript) { %>, type FeedbackValues<% } %> } f
|
|
|
33
33
|
<% } -%>
|
|
34
34
|
<% } -%>
|
|
35
35
|
import { createFeedback } from '@/services/api';
|
|
36
|
-
import { useToast } from '@/components/ui/Toast';
|
|
37
36
|
import { UiButton } from '@/components/ui/UiButton';
|
|
38
37
|
|
|
39
38
|
const defaults<% if (isTypeScript) { %>: FeedbackValues<% } %> = {
|
|
@@ -43,7 +42,7 @@ const defaults<% if (isTypeScript) { %>: FeedbackValues<% } %> = {
|
|
|
43
42
|
};
|
|
44
43
|
|
|
45
44
|
export function FeedbackForm() {
|
|
46
|
-
const {
|
|
45
|
+
const [status, setStatus] = useState<% if (isTypeScript) { %><string | null><% } %>(null);
|
|
47
46
|
|
|
48
47
|
<% if (forms === 'react-hook-form') { -%>
|
|
49
48
|
const {
|
|
@@ -71,7 +70,7 @@ export function FeedbackForm() {
|
|
|
71
70
|
}
|
|
72
71
|
<% } -%>
|
|
73
72
|
await createFeedback(values);
|
|
74
|
-
|
|
73
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
75
74
|
reset();
|
|
76
75
|
};
|
|
77
76
|
|
|
@@ -107,6 +106,11 @@ export function FeedbackForm() {
|
|
|
107
106
|
{isSubmitting ? 'Sending…' : 'Send feedback'}
|
|
108
107
|
</UiButton>
|
|
109
108
|
</form>
|
|
109
|
+
{status && (
|
|
110
|
+
<p className="success-text" role="status">
|
|
111
|
+
{status}
|
|
112
|
+
</p>
|
|
113
|
+
)}
|
|
110
114
|
<p className="panel-note">
|
|
111
115
|
<strong>When to use:</strong> complex forms with validation and async submit.
|
|
112
116
|
<br />
|
|
@@ -137,7 +141,7 @@ export function FeedbackForm() {
|
|
|
137
141
|
feedbackSchema.parse(values);
|
|
138
142
|
<% } -%>
|
|
139
143
|
await createFeedback(values);
|
|
140
|
-
|
|
144
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
141
145
|
helpers.resetForm();
|
|
142
146
|
}}
|
|
143
147
|
>
|
|
@@ -164,6 +168,11 @@ export function FeedbackForm() {
|
|
|
164
168
|
</Form>
|
|
165
169
|
)}
|
|
166
170
|
</Formik>
|
|
171
|
+
{status && (
|
|
172
|
+
<p className="success-text" role="status">
|
|
173
|
+
{status}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
167
176
|
<p className="panel-note">
|
|
168
177
|
<strong>When to use:</strong> form-heavy screens with Formik's render/props model.
|
|
169
178
|
<br />
|
|
@@ -187,7 +196,7 @@ export function FeedbackForm() {
|
|
|
187
196
|
if (result.error) throw result.error;
|
|
188
197
|
<% } -%>
|
|
189
198
|
await createFeedback(value);
|
|
190
|
-
|
|
199
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
191
200
|
form.reset();
|
|
192
201
|
},
|
|
193
202
|
});
|
|
@@ -255,6 +264,11 @@ export function FeedbackForm() {
|
|
|
255
264
|
</form.Field>
|
|
256
265
|
<UiButton type="submit">Send feedback</UiButton>
|
|
257
266
|
</form>
|
|
267
|
+
{status && (
|
|
268
|
+
<p className="success-text" role="status">
|
|
269
|
+
{status}
|
|
270
|
+
</p>
|
|
271
|
+
)}
|
|
258
272
|
<p className="panel-note">
|
|
259
273
|
<strong>When to use:</strong> headless, type-safe forms with fine-grained reactivity.
|
|
260
274
|
<br />
|
|
@@ -315,7 +329,7 @@ export function FeedbackForm() {
|
|
|
315
329
|
setIsSubmitting(true);
|
|
316
330
|
try {
|
|
317
331
|
await createFeedback(values);
|
|
318
|
-
|
|
332
|
+
setStatus('Feedback submitted successfully (demo API).');
|
|
319
333
|
setValues(defaults);
|
|
320
334
|
} finally {
|
|
321
335
|
setIsSubmitting(false);
|
|
@@ -362,6 +376,11 @@ export function FeedbackForm() {
|
|
|
362
376
|
{isSubmitting ? 'Sending…' : 'Send feedback'}
|
|
363
377
|
</UiButton>
|
|
364
378
|
</form>
|
|
379
|
+
{status && (
|
|
380
|
+
<p className="success-text" role="status">
|
|
381
|
+
{status}
|
|
382
|
+
</p>
|
|
383
|
+
)}
|
|
365
384
|
<p className="panel-note">
|
|
366
385
|
<strong>When to use:</strong> simple forms without a dedicated form library.
|
|
367
386
|
<br />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import { AppProviders } from '@/
|
|
4
|
-
import { App } from '@/
|
|
3
|
+
import { AppProviders } from '@/providers';
|
|
4
|
+
import { App } from '@/App';
|
|
5
5
|
import { initMonitoring } from '@/lib/monitoring';
|
|
6
6
|
import '@/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>';
|
|
7
7
|
|
|
@@ -24,7 +24,6 @@ import { ChakraProvider, defaultSystem } from '@chakra-ui/react';
|
|
|
24
24
|
<% if (uiLibrary === 'mui') { -%>
|
|
25
25
|
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
|
26
26
|
<% } -%>
|
|
27
|
-
import { ToastProvider } from '@/components/ui/Toast';
|
|
28
27
|
|
|
29
28
|
<% if (serverState === 'tanstack-query') { -%>
|
|
30
29
|
const queryClient = new QueryClient({
|
|
@@ -89,5 +88,5 @@ export function AppProviders({ children }<% if (isTypeScript) { %>: AppProviders
|
|
|
89
88
|
tree = <ChakraProvider value={defaultSystem}>{tree}</ChakraProvider>;
|
|
90
89
|
<% } -%>
|
|
91
90
|
|
|
92
|
-
return
|
|
91
|
+
return <>{tree}</>;
|
|
93
92
|
}
|
|
@@ -4,26 +4,27 @@ export function AboutPage() {
|
|
|
4
4
|
<h1>About this starter</h1>
|
|
5
5
|
<p>
|
|
6
6
|
Generated with <strong>create-react-starter-kit</strong> using a scalable{' '}
|
|
7
|
-
<strong>feature-based</strong> layout (domain modules under <code>src/features</code>)
|
|
8
|
-
the same organizational approach used across large production React codebases.
|
|
7
|
+
<strong>feature-based</strong> layout (domain modules under <code>src/features</code>).
|
|
9
8
|
</p>
|
|
10
9
|
<ul className="about-list">
|
|
11
10
|
<li>
|
|
12
|
-
<strong>
|
|
13
|
-
|
|
11
|
+
<strong>Routes:</strong> <code>/</code>, <code>/counter</code>, <code>/posts</code>,{' '}
|
|
12
|
+
<code>/feedback</code>, <code>/about</code>
|
|
14
13
|
</li>
|
|
15
14
|
<li>
|
|
16
|
-
<strong>
|
|
17
|
-
<code>src/lib/</code>. Prefer imports via the <code>@/</code> path alias.
|
|
15
|
+
<strong>Extend features</strong> under <code>src/features/<name>/</code>.
|
|
18
16
|
</li>
|
|
19
17
|
<li>
|
|
20
|
-
<strong>
|
|
21
|
-
|
|
18
|
+
<strong>Shared UI</strong> lives in <code>src/components/</code>. Prefer the{' '}
|
|
19
|
+
<code>@/</code> path alias.
|
|
20
|
+
</li>
|
|
21
|
+
<li>
|
|
22
|
+
<strong>Bootstrap</strong> is in <code>src/main.<%= jsxExt %></code> with providers in{' '}
|
|
23
|
+
<code>src/providers.<%= jsxExt %></code> and routing in <code>src/App.<%= jsxExt %></code>.
|
|
22
24
|
</li>
|
|
23
25
|
</ul>
|
|
24
26
|
<p>
|
|
25
|
-
|
|
26
|
-
<code>docs/selected-stack.md</code> for library-specific guidance.
|
|
27
|
+
Read <code>docs/getting-started.md</code> next, then <code>docs/selected-stack.md</code>.
|
|
27
28
|
</p>
|
|
28
29
|
</div>
|
|
29
30
|
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CounterPanel } from '@/features/counter/CounterPanel';
|
|
2
|
+
|
|
3
|
+
export function CounterPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Client state</p>
|
|
8
|
+
<h1>Counter</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Interactive counter wired to <strong><%= stackLabels.stateManagement %></strong>. Use this
|
|
11
|
+
pattern for UI state that is not server cache.
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
<CounterPanel />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FeedbackForm } from '@/features/feedback/FeedbackForm';
|
|
2
|
+
|
|
3
|
+
export function FeedbackPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Forms</p>
|
|
8
|
+
<h1>Feedback</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Submit demo feedback with <strong><%= stackLabels.forms %></strong>
|
|
11
|
+
<% if (validation !== 'none') { -%>
|
|
12
|
+
{' '}
|
|
13
|
+
and <strong><%= stackLabels.validation %></strong> validation
|
|
14
|
+
<% } -%>
|
|
15
|
+
.
|
|
16
|
+
</p>
|
|
17
|
+
</header>
|
|
18
|
+
<FeedbackForm />
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,29 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
<% if (router === 'react-router') { -%>
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
<% } else { -%>
|
|
4
|
+
import { Link } from '@tanstack/react-router';
|
|
5
|
+
<% } -%>
|
|
4
6
|
import { StackBadge } from '@/components/StackBadge';
|
|
5
|
-
import { useToast } from '@/components/ui/Toast';
|
|
6
|
-
import { UiButton } from '@/components/ui/UiButton';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const demos = [
|
|
9
|
+
{
|
|
10
|
+
to: '/counter',
|
|
11
|
+
title: 'Counter',
|
|
12
|
+
body: 'Client state demo with <%= stackLabels.stateManagement %>.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
to: '/posts',
|
|
16
|
+
title: 'Posts',
|
|
17
|
+
body: 'Server state / data fetching with <%= stackLabels.serverState %>.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
to: '/feedback',
|
|
21
|
+
title: 'Feedback',
|
|
22
|
+
body: 'Forms and validation with <%= stackLabels.forms %><% if (validation !== 'none') { %> + <%= stackLabels.validation %><% } %>.',
|
|
23
|
+
},
|
|
24
|
+
] as const;
|
|
10
25
|
|
|
26
|
+
export function HomePage() {
|
|
11
27
|
return (
|
|
12
28
|
<div className="page home-page">
|
|
13
29
|
<section className="hero">
|
|
14
30
|
<p className="eyebrow">Starter workspace</p>
|
|
15
31
|
<h1><%= projectName %></h1>
|
|
16
32
|
<p className="lede">
|
|
17
|
-
A
|
|
18
|
-
|
|
33
|
+
A production-shaped React starter. Open each demo route to explore client state, server
|
|
34
|
+
data, and forms — each page is a real working example for your selected stack.
|
|
19
35
|
</p>
|
|
20
36
|
<div className="hero-actions">
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
</
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
</
|
|
37
|
+
<Link className="ui-button" to="/counter">
|
|
38
|
+
Open counter demo
|
|
39
|
+
</Link>
|
|
40
|
+
<Link className="text-link" to="/about">
|
|
41
|
+
About this starter →
|
|
42
|
+
</Link>
|
|
27
43
|
</div>
|
|
28
44
|
<div className="stack-row">
|
|
29
45
|
<StackBadge label="Build" value="<%= stackLabels.buildTool %>" />
|
|
@@ -35,12 +51,18 @@ export function HomePage() {
|
|
|
35
51
|
</div>
|
|
36
52
|
</section>
|
|
37
53
|
|
|
38
|
-
<section className="panels">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
<section className="panels" aria-label="Demo routes">
|
|
55
|
+
{demos.map((demo) => (
|
|
56
|
+
<article key={demo.to} className="panel">
|
|
57
|
+
<header className="panel-header">
|
|
58
|
+
<h2>{demo.title}</h2>
|
|
59
|
+
<p>{demo.body}</p>
|
|
60
|
+
</header>
|
|
61
|
+
<Link className="text-link" to={demo.to}>
|
|
62
|
+
Go to {demo.title.toLowerCase()} →
|
|
63
|
+
</Link>
|
|
64
|
+
</article>
|
|
65
|
+
))}
|
|
44
66
|
</section>
|
|
45
67
|
</div>
|
|
46
68
|
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PostsPanel } from '@/features/posts/PostsPanel';
|
|
2
|
+
|
|
3
|
+
export function PostsPage() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="page">
|
|
6
|
+
<header className="page-intro">
|
|
7
|
+
<p className="eyebrow">Server state</p>
|
|
8
|
+
<h1>Posts</h1>
|
|
9
|
+
<p className="lede">
|
|
10
|
+
Fetches sample posts via <strong><%= stackLabels.serverState === 'None' ? stackLabels.apiLayer : stackLabels.serverState %></strong>{' '}
|
|
11
|
+
and <strong><%= stackLabels.apiLayer %></strong>.
|
|
12
|
+
</p>
|
|
13
|
+
</header>
|
|
14
|
+
<PostsPanel />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|