@pplancq/react-template 2.8.17 → 2.10.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/CHANGELOG.md +66 -0
- package/_README.md +57 -52
- package/mise.toml +2 -2
- package/mocks/browser.ts +1 -1
- package/mocks/fixtures/user.ts +14 -14
- package/mocks/handlers/userHandlers.ts +3 -3
- package/mocks/server.ts +1 -1
- package/package.json +30 -30
- package/playwright-report/html/index.html +90 -0
- package/playwright-report/sonar-results.xml +20 -0
- package/playwright.config.ts +3 -3
- package/scripts/removeDemo.cjs +105 -66
- package/src/README.md +81 -0
- package/src/app/App.tsx +18 -0
- package/src/{bootstrap.tsx → app/AppHTMLElement.tsx} +5 -5
- package/src/app/README.md +69 -0
- package/src/app/assets/css/index.ts +2 -0
- package/src/{assets → app/assets}/css/reset.css +3 -3
- package/src/app/config/queryClientConfig.ts +3 -0
- package/src/app/config/serviceContainer.ts +6 -0
- package/src/app/providers/Providers/Providers.tsx +18 -0
- package/src/app/providers/QueryClientProvider/QueryClientProvider.tsx +16 -0
- package/src/app/providers/ServiceProvider/ServiceContext.ts +8 -0
- package/src/app/providers/ServiceProvider/ServiceProvider.tsx +14 -0
- package/src/app/routing/Router.tsx +14 -0
- package/src/app/routing/appRoutes.ts +3 -0
- package/src/app/routing/routes.tsx +9 -0
- package/src/demo/README.md +149 -0
- package/src/demo/application/dtos/ArchitecturePrinciple.ts +5 -0
- package/src/{types/profileTypes.ts → demo/application/dtos/ProfileFormValues.ts} +1 -1
- package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCase.ts +11 -0
- package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface.ts +5 -0
- package/src/demo/application/use-cases/GetDemoUsersUseCase.ts +11 -0
- package/src/demo/application/use-cases/GetDemoUsersUseCaseInterface.ts +5 -0
- package/src/demo/domain/entities/DemoUser.ts +7 -0
- package/src/demo/domain/repositories/ArchitecturePrincipleRepositoryInterface.d.ts +5 -0
- package/src/demo/domain/repositories/DemoUserRepositoryInterface.d.ts +5 -0
- package/src/demo/infrastructure/repositories/HttpDemoUserRepository.ts +29 -0
- package/src/demo/infrastructure/repositories/StaticArchitecturePrincipleRepository.ts +33 -0
- package/src/demo/serviceDemo.ts +40 -0
- package/src/demo/serviceIdentifiers.ts +6 -0
- package/src/demo/ui/components/DemoFooter/DemoFooter.tsx +17 -0
- package/src/demo/ui/components/DemoFooter/index.ts +1 -0
- package/src/demo/ui/components/DemoHeader/DemoHeader.tsx +25 -0
- package/src/demo/ui/components/DemoHeader/index.ts +1 -0
- package/src/{ui/molecules/Navigation/Navigation.tsx → demo/ui/components/DemoNavigation/DemoNavigation.tsx} +6 -6
- package/src/demo/ui/components/DemoNavigation/index.ts +1 -0
- package/src/{ui/atoms → demo/ui/components}/Input/Input.tsx +3 -3
- package/src/demo/ui/components/Input/index.ts +1 -0
- package/src/{ui/atoms → demo/ui/components}/Logo/Logo.tsx +2 -2
- package/src/demo/ui/components/Logo/index.ts +1 -0
- package/src/{ui/atoms → demo/ui/components}/NavLink/NavLink.tsx +3 -3
- package/src/demo/ui/components/NavLink/index.ts +1 -0
- package/src/{components → demo/ui/components}/TextInput/TextInput.tsx +3 -3
- package/src/demo/ui/components/TextInput/index.ts +1 -0
- package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.tsx +13 -13
- package/src/demo/ui/forms/ProfileForm/index.ts +1 -0
- package/src/demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery.ts +13 -0
- package/src/demo/ui/layouts/DemoLayout/DemoLayout.tsx +12 -0
- package/src/demo/ui/layouts/MainTemplate/MainTemplate.module.css +21 -0
- package/src/demo/ui/layouts/MainTemplate/MainTemplate.tsx +26 -0
- package/src/demo/ui/layouts/MainTemplate/index.ts +1 -0
- package/src/{pages/Demo/Demo.module.scss → demo/ui/pages/DemoHome/DemoHome.module.scss} +5 -5
- package/src/demo/ui/pages/DemoHome/DemoHome.tsx +117 -0
- package/src/demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo.tsx +47 -0
- package/src/{pages/Demo → demo/ui/pages}/ReactHookFormDemo/ReactHookFormDemo.tsx +8 -8
- package/src/demo/ui/pages/ReactQueryDemo/ReactQueryDemo.module.scss +38 -0
- package/src/{pages/Demo → demo/ui/pages}/ReactQueryDemo/ReactQueryDemo.tsx +6 -5
- package/src/demo/ui/routing/demoAppRoutes.ts +6 -0
- package/src/demo/ui/routing/demoRoutes.tsx +31 -0
- package/src/main.ts +5 -5
- package/src/react-app-env.d.ts +1 -1
- package/src/shared/README.md +53 -0
- package/src/shared/application/README.md +49 -0
- package/src/shared/domain/README.md +52 -0
- package/src/shared/infrastructure/README.md +51 -0
- package/src/shared/infrastructure/http/constant.ts +16 -0
- package/src/{api → shared/infrastructure/http}/fetchApi.ts +3 -3
- package/src/shared/ui/README.md +50 -0
- package/src/shared/ui/hooks/useService/useService.ts +13 -0
- package/test-results/.last-run.json +4 -0
- package/tests/e2e/demo.test.ts +17 -0
- package/tests/e2e/fixture/playwright.fixture.ts +15 -0
- package/tests/{utils → e2e/utils}/createNetworkFixture.ts +2 -2
- package/tests/{utils → e2e/utils}/createTestWithCoverageFixture.ts +8 -8
- package/tests/{utils → e2e/utils}/playwright.globalSetup.ts +4 -4
- package/tests/{utils → e2e/utils}/playwright.globalTeardown.ts +3 -3
- package/{src/__tests__/bootstraps.test.tsx → tests/unit/app/AppHTMLElement.test.tsx} +14 -14
- package/tests/unit/demo/ui/components/DemoFooter/Footer.test.tsx +15 -0
- package/{src/components/Footer/__tests__ → tests/unit/demo/ui/components/DemoFooter}/__snapshots__/Footer.test.tsx.snap +3 -3
- package/tests/unit/demo/ui/components/DemoHeader/Header.test.tsx +15 -0
- package/tests/unit/demo/ui/components/DemoHeader/__snapshots__/Header.test.tsx.snap +64 -0
- package/tests/unit/demo/ui/components/Logo/Logo.test.tsx +12 -0
- package/tsconfig.json +4 -2
- package/vitest.config.mts +6 -2
- package/postcss.config.mjs +0 -5
- package/src/api/README.md +0 -65
- package/src/api/constant.ts +0 -16
- package/src/api/demoApi.ts +0 -10
- package/src/assets/README.md +0 -54
- package/src/assets/css/index.ts +0 -2
- package/src/components/App/App.tsx +0 -18
- package/src/components/App/index.ts +0 -1
- package/src/components/Footer/Footer.tsx +0 -16
- package/src/components/Footer/__tests__/Footer.test.tsx +0 -15
- package/src/components/Footer/index.ts +0 -1
- package/src/components/Header/Header.tsx +0 -17
- package/src/components/Header/__tests__/Header.test.tsx +0 -15
- package/src/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +0 -37
- package/src/components/Header/index.ts +0 -1
- package/src/components/README.md +0 -33
- package/src/components/TextInput/index.ts +0 -1
- package/src/config/README.md +0 -63
- package/src/config/index.ts +0 -1
- package/src/config/queryClientConfig.ts +0 -3
- package/src/forms/ProfileForm/index.ts +0 -1
- package/src/forms/README.md +0 -142
- package/src/hooks/README.md +0 -83
- package/src/hooks/api/queryKey.ts +0 -1
- package/src/hooks/api/useDemoApi.ts +0 -16
- package/src/pages/Demo/Demo.tsx +0 -236
- package/src/pages/Demo/ReactHookFormDemo/index.ts +0 -1
- package/src/pages/Demo/ReactHookFormDemo/routes.tsx +0 -7
- package/src/pages/Demo/ReactQueryDemo/index.ts +0 -1
- package/src/pages/Demo/ReactQueryDemo/routes.tsx +0 -7
- package/src/pages/Demo/__tests__/Demo.test.tsx +0 -15
- package/src/pages/Demo/__tests__/__snapshots__/Demo.test.tsx.snap +0 -557
- package/src/pages/Demo/index.ts +0 -1
- package/src/pages/Demo/routes.tsx +0 -8
- package/src/pages/Layout/Layout.tsx +0 -12
- package/src/pages/Layout/index.ts +0 -1
- package/src/pages/README.md +0 -91
- package/src/pages/UnexpectedError/UnexpectedError.module.css +0 -8
- package/src/pages/UnexpectedError/UnexpectedError.tsx +0 -22
- package/src/pages/UnexpectedError/index.ts +0 -1
- package/src/providers/QueryClientProvider/QueryClientProvider.tsx +0 -16
- package/src/providers/QueryClientProvider/index.ts +0 -1
- package/src/providers/README.md +0 -112
- package/src/providers/index.ts +0 -1
- package/src/providers/withProviders/index.ts +0 -1
- package/src/providers/withProviders/withProviders.tsx +0 -18
- package/src/routing/README.md +0 -76
- package/src/routing/appRoutes.ts +0 -5
- package/src/routing/routerFactory.ts +0 -12
- package/src/routing/routes.tsx +0 -13
- package/src/types/README.md +0 -62
- package/src/types/demoApi.ts +0 -25
- package/src/ui/README.md +0 -144
- package/src/ui/atoms/Input/index.ts +0 -1
- package/src/ui/atoms/Logo/__tests__/Logo.test.tsx +0 -12
- package/src/ui/atoms/Logo/index.ts +0 -1
- package/src/ui/atoms/NavLink/index.ts +0 -1
- package/src/ui/molecules/Navigation/index.ts +0 -1
- package/src/ui/organisms/Footer/Footer.module.css +0 -10
- package/src/ui/organisms/Footer/Footer.tsx +0 -10
- package/src/ui/organisms/Footer/index.ts +0 -1
- package/src/ui/organisms/Header/Header.module.css +0 -17
- package/src/ui/organisms/Header/Header.tsx +0 -16
- package/src/ui/organisms/Header/index.ts +0 -1
- package/src/ui/organisms/MainContent/MainContent.module.css +0 -3
- package/src/ui/organisms/MainContent/MainContent.tsx +0 -6
- package/src/ui/organisms/MainContent/index.ts +0 -1
- package/src/ui/templates/MainTemplate/MainTemplate.module.css +0 -5
- package/src/ui/templates/MainTemplate/MainTemplate.tsx +0 -26
- package/src/ui/templates/MainTemplate/index.ts +0 -1
- package/src/utils/README.md +0 -70
- package/tests/demo.test.ts +0 -17
- package/tests/fixture/playwright.fixture.ts +0 -15
- /package/src/{assets → app/assets}/css/mainBody.css +0 -0
- /package/src/{assets → demo/ui/assets}/images/logo.png +0 -0
- /package/src/{assets → demo/ui/assets}/images/templatePlugins.png +0 -0
- /package/src/{components/Footer/Footer.module.css → demo/ui/components/DemoFooter/DemoFooter.module.css} +0 -0
- /package/src/{components/Header/Header.module.css → demo/ui/components/DemoHeader/DemoHeader.module.css} +0 -0
- /package/src/{ui/molecules/Navigation/Navigation.module.css → demo/ui/components/DemoNavigation/DemoNavigation.module.css} +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Input/Input.module.scss +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Logo/Logo.module.css +0 -0
- /package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.module.scss +0 -0
- /package/src/{pages/Demo/ReactQueryDemo/ReactQueryDemo.module.scss → demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo.module.scss} +0 -0
- /package/src/{pages/Demo/ReactHookFormDemo/ReactHookFormDemo.module.css → demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo.module.scss} +0 -0
- /package/src/{types/api.ts → shared/infrastructure/http/fetchApi.type.d.ts} +0 -0
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
border-top: 1px solid #ccc;
|
|
41
41
|
border-bottom: 1px solid #ccc;
|
|
42
42
|
background-color: #f8f8f8;
|
|
43
|
-
content:
|
|
43
|
+
content: "";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
&::after {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
border-top: 1px solid #ccc;
|
|
50
50
|
border-bottom: 1px solid #ccc;
|
|
51
51
|
background-color: #f8f8f8;
|
|
52
|
-
content:
|
|
52
|
+
content: " ";
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
width: 55px;
|
|
84
84
|
height: 5px;
|
|
85
85
|
background-color: #111;
|
|
86
|
-
content:
|
|
86
|
+
content: "";
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
&::after {
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
max-width: 255px;
|
|
95
95
|
height: 1px;
|
|
96
96
|
background-color: #333;
|
|
97
|
-
content:
|
|
97
|
+
content: "";
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -177,7 +177,7 @@ li {
|
|
|
177
177
|
code {
|
|
178
178
|
padding: 2px 5px;
|
|
179
179
|
border-radius: 4px;
|
|
180
|
-
font-family:
|
|
180
|
+
font-family: "Courier New", monospace;
|
|
181
181
|
font-size: 14px;
|
|
182
182
|
background-color: #f8f9fa;
|
|
183
183
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import image from "@Demo/ui/assets/images/templatePlugins.png";
|
|
2
|
+
import { NavLink } from "@Demo/ui/components/NavLink/NavLink";
|
|
3
|
+
import { demoAppRoutes } from "@Demo/ui/routing/demoAppRoutes";
|
|
4
|
+
import classes from "./DemoHome.module.scss";
|
|
5
|
+
|
|
6
|
+
export const DemoHome = () => {
|
|
7
|
+
return (
|
|
8
|
+
<div className={classes.container}>
|
|
9
|
+
<div className={classes.bigTitleStyle}>
|
|
10
|
+
Welcome to Your React Starter Kit <span>Clean Architecture</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div className={classes.foo}>Vertical slices, shared kernel, and explicit composition.</div>
|
|
14
|
+
|
|
15
|
+
<div className={classes.paragraphContainer}>
|
|
16
|
+
<h1 className={classes.titleStyle}>Overview</h1>
|
|
17
|
+
<div className={classes.infoSection}>
|
|
18
|
+
<span className={classes.centredText}>
|
|
19
|
+
<p className={classes.paragraphStyle}>
|
|
20
|
+
This template now starts from a <span className={classes.highlight}>Clean Architecture</span> inspired
|
|
21
|
+
structure with a dedicated <span className={classes.boldText}>app</span> bootstrap layer, a{" "}
|
|
22
|
+
<span className={classes.boldText}>shared</span> common layer, and a{" "}
|
|
23
|
+
<span className={classes.boldText}>demo</span> bounded context.
|
|
24
|
+
</p>
|
|
25
|
+
<p className={classes.paragraphStyle}>
|
|
26
|
+
The goal is to help you start from explicit architecture choices while still keeping a runnable example
|
|
27
|
+
project that demonstrates routing, form handling, data fetching, and dependency injection.
|
|
28
|
+
</p>
|
|
29
|
+
</span>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<img src={image} alt="Template architecture overview" className={classes.imagePlace} />
|
|
33
|
+
|
|
34
|
+
<div className={classes.paragraphContainer}>
|
|
35
|
+
<h1 className={classes.titleStyle}>Folder Structure</h1>
|
|
36
|
+
<div className={classes.infoSection}>
|
|
37
|
+
<div className={classes.tableWrapper}>
|
|
38
|
+
<table>
|
|
39
|
+
<thead>
|
|
40
|
+
<tr>
|
|
41
|
+
<th>Folder</th>
|
|
42
|
+
<th>Description</th>
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
<tbody>
|
|
46
|
+
<tr>
|
|
47
|
+
<td>
|
|
48
|
+
<span className={classes.boldText}>app</span>
|
|
49
|
+
</td>
|
|
50
|
+
<td>Application bootstrap, routing, providers, config, and global assets.</td>
|
|
51
|
+
</tr>
|
|
52
|
+
<tr>
|
|
53
|
+
<td>
|
|
54
|
+
<span className={classes.boldText}>shared</span>
|
|
55
|
+
</td>
|
|
56
|
+
<td>Reusable common code with no demo-specific responsibility.</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<tr>
|
|
59
|
+
<td>
|
|
60
|
+
<span className={classes.boldText}>demo</span>
|
|
61
|
+
</td>
|
|
62
|
+
<td>Example bounded context split into ui, application, domain, and infrastructure.</td>
|
|
63
|
+
</tr>
|
|
64
|
+
</tbody>
|
|
65
|
+
</table>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div className={classes.paragraphContainer}>
|
|
70
|
+
<h1 className={classes.titleStyle}>Pedagogical Demos</h1>
|
|
71
|
+
<div className={classes.infoSection}>
|
|
72
|
+
<p className={classes.paragraphStyle}>
|
|
73
|
+
Each page below demonstrates a different aspect of the template. The React Query example resolves its use
|
|
74
|
+
case through Inversify, the React Hook Form example stays focused on UI composition, and the dependency
|
|
75
|
+
injection example makes the layer boundaries explicit.
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className={classes.infoSection}>
|
|
80
|
+
<p className={classes.paragraphStyle}>
|
|
81
|
+
<span className={classes.boldText}>React Query demo: </span>
|
|
82
|
+
fetches remote users through a repository and a use case.
|
|
83
|
+
</p>
|
|
84
|
+
<div className={classes.demoStyle}>
|
|
85
|
+
<NavLink to={demoAppRoutes.reactQuery} className={classes.a}>
|
|
86
|
+
Run demo
|
|
87
|
+
</NavLink>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className={classes.infoSection}>
|
|
92
|
+
<p className={classes.paragraphStyle}>
|
|
93
|
+
<span className={classes.boldText}>React Hook Form demo: </span>
|
|
94
|
+
shows a form-first UI example inside the new bounded context.
|
|
95
|
+
</p>
|
|
96
|
+
<div className={classes.demoStyle}>
|
|
97
|
+
<NavLink to={demoAppRoutes.reactHookForm} className={classes.a}>
|
|
98
|
+
Run demo
|
|
99
|
+
</NavLink>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className={classes.infoSection}>
|
|
104
|
+
<p className={classes.paragraphStyle}>
|
|
105
|
+
<span className={classes.boldText}>Dependency injection demo: </span>
|
|
106
|
+
explains how app, shared, and demo collaborate through explicit abstractions.
|
|
107
|
+
</p>
|
|
108
|
+
<div className={classes.demoStyle}>
|
|
109
|
+
<NavLink to={demoAppRoutes.dependencyInjection} className={classes.a}>
|
|
110
|
+
Run demo
|
|
111
|
+
</NavLink>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GetArchitecturePrinciplesUseCaseInterface } from "@Demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface";
|
|
2
|
+
import { DEMO_SERVICES } from "@Demo/serviceIdentifiers";
|
|
3
|
+
import { useService } from "@Shared/ui/hooks/useService/useService";
|
|
4
|
+
import classes from "./DependencyInjectionDemo.module.scss";
|
|
5
|
+
|
|
6
|
+
export const DependencyInjectionDemo = () => {
|
|
7
|
+
const getArchitecturePrinciplesUseCase = useService<GetArchitecturePrinciplesUseCaseInterface>(
|
|
8
|
+
DEMO_SERVICES.GetArchitecturePrinciplesUseCase,
|
|
9
|
+
);
|
|
10
|
+
const architecturePrinciples = getArchitecturePrinciplesUseCase.execute();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className={classes.container}>
|
|
14
|
+
<h1 className={classes.title}>Dependency Injection</h1>
|
|
15
|
+
<hr className={classes.lineStyle} color="#00008f" />
|
|
16
|
+
|
|
17
|
+
<div className={classes.fieldsetStyle}>
|
|
18
|
+
<p className={classes.paragraph}>
|
|
19
|
+
This page is driven by a use case resolved from the Inversify container. The page knows the use case contract,
|
|
20
|
+
but it does not instantiate infrastructure classes directly.
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<h1 className={classes.dataTitle}>Clean Architecture Principles</h1>
|
|
24
|
+
<div className={classes.tableWrapper}>
|
|
25
|
+
<table>
|
|
26
|
+
<thead>
|
|
27
|
+
<tr>
|
|
28
|
+
<th>Layer</th>
|
|
29
|
+
<th>Principle</th>
|
|
30
|
+
<th>Description</th>
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
<tbody>
|
|
34
|
+
{architecturePrinciples.map((principle) => (
|
|
35
|
+
<tr key={`${principle.layer}-${principle.title}`}>
|
|
36
|
+
<td>{principle.layer}</td>
|
|
37
|
+
<td>{principle.title}</td>
|
|
38
|
+
<td>{principle.description}</td>
|
|
39
|
+
</tr>
|
|
40
|
+
))}
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { useState } from
|
|
1
|
+
import type { ProfileFormValues } from "@Demo/application/dtos/ProfileFormValues";
|
|
2
|
+
import { ProfileForm } from "@Demo/ui/forms/ProfileForm/ProfileForm";
|
|
3
|
+
import { useState } from "react";
|
|
4
4
|
|
|
5
|
-
import classes from
|
|
5
|
+
import classes from "./ReactHookFormDemo.module.scss";
|
|
6
6
|
|
|
7
7
|
export const ReactHookFormDemo = () => {
|
|
8
|
-
const [profileData, setProfileData] = useState<
|
|
8
|
+
const [profileData, setProfileData] = useState<ProfileFormValues>();
|
|
9
9
|
|
|
10
|
-
const onSubmit = (data:
|
|
10
|
+
const onSubmit = (data: ProfileFormValues) => {
|
|
11
11
|
setProfileData({ ...data });
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -29,13 +29,13 @@ export const ReactHookFormDemo = () => {
|
|
|
29
29
|
</p>
|
|
30
30
|
<div className={classes.profileSectionStyle}>
|
|
31
31
|
<fieldset>
|
|
32
|
-
<legend>
|
|
32
|
+
<legend>Form</legend>
|
|
33
33
|
<ProfileForm onSubmit={onSubmit} resetForm={resetForm} />
|
|
34
34
|
</fieldset>
|
|
35
35
|
|
|
36
36
|
{profileData ? (
|
|
37
37
|
<fieldset>
|
|
38
|
-
<legend>
|
|
38
|
+
<legend>Result</legend>
|
|
39
39
|
<p>E-mail: {profileData.mail} </p>
|
|
40
40
|
<p>First-name: {profileData.firstName} </p>
|
|
41
41
|
<p>Last-name: {profileData.lastName} </p>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
padding: 32px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.fieldsetStyle {
|
|
6
|
+
margin-top: 24px;
|
|
7
|
+
padding: 16px;
|
|
8
|
+
border: 1px solid black;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.title {
|
|
12
|
+
padding-top: 16px;
|
|
13
|
+
font-size: 20px;
|
|
14
|
+
letter-spacing: 4px;
|
|
15
|
+
text-align: center;
|
|
16
|
+
color: #00008f;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tableWrapper {
|
|
20
|
+
overflow: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dataTitle {
|
|
24
|
+
padding-top: 16px;
|
|
25
|
+
padding-bottom: 16px;
|
|
26
|
+
font-size: 20px;
|
|
27
|
+
letter-spacing: 4px;
|
|
28
|
+
text-align: center;
|
|
29
|
+
color: #00008f;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.lineStyle {
|
|
33
|
+
width: 20%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.paragraph {
|
|
37
|
+
padding-top: 16px;
|
|
38
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import classes from
|
|
1
|
+
import { useDemoUsersQuery } from "@Demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery";
|
|
2
|
+
import classes from "./ReactQueryDemo.module.scss";
|
|
3
3
|
|
|
4
4
|
export const ReactQueryDemo = () => {
|
|
5
|
-
const {
|
|
5
|
+
const { data = [], isLoading, isError } = useDemoUsersQuery();
|
|
6
6
|
|
|
7
7
|
if (isError) {
|
|
8
8
|
return <div>Error fetching data</div>;
|
|
@@ -16,7 +16,8 @@ export const ReactQueryDemo = () => {
|
|
|
16
16
|
<div className={classes.fieldsetStyle}>
|
|
17
17
|
<p className={classes.paragraph}>
|
|
18
18
|
React Query is a popular library for data management in React applications. It simplifies data query
|
|
19
|
-
management, caching, data invalidation, and loading state handling.
|
|
19
|
+
management, caching, data invalidation, and loading state handling. In this template the query calls a use
|
|
20
|
+
case resolved through Inversify, which keeps the UI layer independent from the infrastructure adapter.
|
|
20
21
|
</p>
|
|
21
22
|
{isLoading ? (
|
|
22
23
|
<h1 className={classes.dataTitle}>Loading...</h1>
|
|
@@ -35,7 +36,7 @@ export const ReactQueryDemo = () => {
|
|
|
35
36
|
</tr>
|
|
36
37
|
</thead>
|
|
37
38
|
<tbody>
|
|
38
|
-
{
|
|
39
|
+
{data.map((eachResult) => (
|
|
39
40
|
<tr key={eachResult.id}>
|
|
40
41
|
<td>{eachResult.id}</td>
|
|
41
42
|
<td>{eachResult.name}</td>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DemoLayout } from "@Demo/ui/layouts/DemoLayout/DemoLayout";
|
|
2
|
+
import { DemoHome } from "@Demo/ui/pages/DemoHome/DemoHome";
|
|
3
|
+
import { DependencyInjectionDemo } from "@Demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo";
|
|
4
|
+
import { ReactHookFormDemo } from "@Demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo";
|
|
5
|
+
import { ReactQueryDemo } from "@Demo/ui/pages/ReactQueryDemo/ReactQueryDemo";
|
|
6
|
+
import { demoAppRoutes } from "@Demo/ui/routing/demoAppRoutes";
|
|
7
|
+
import type { RouteObject } from "react-router";
|
|
8
|
+
|
|
9
|
+
const toRelativePath = (path: string) => path.replace(/^\//, "");
|
|
10
|
+
|
|
11
|
+
export const demoRoutes: RouteObject = {
|
|
12
|
+
element: <DemoLayout />,
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
index: true,
|
|
16
|
+
element: <DemoHome />,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: toRelativePath(demoAppRoutes.reactQuery),
|
|
20
|
+
element: <ReactQueryDemo />,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: toRelativePath(demoAppRoutes.reactHookForm),
|
|
24
|
+
element: <ReactHookFormDemo />,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: toRelativePath(demoAppRoutes.dependencyInjection),
|
|
28
|
+
element: <DependencyInjectionDemo />,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
package/src/main.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AppHTMLElement from "@App/AppHTMLElement";
|
|
2
2
|
|
|
3
|
-
if (import.meta.env.FRONT_MOCK_ENABLE ===
|
|
4
|
-
const { worker } = await import(
|
|
3
|
+
if (import.meta.env.FRONT_MOCK_ENABLE === "true") {
|
|
4
|
+
const { worker } = await import("@Mocks/browser");
|
|
5
5
|
await worker.start({
|
|
6
|
-
onUnhandledRequest:
|
|
6
|
+
onUnhandledRequest: "warn",
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
customElements.define(
|
|
10
|
+
customElements.define("app-react", AppHTMLElement);
|
package/src/react-app-env.d.ts
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# `shared`
|
|
2
|
+
|
|
3
|
+
The `shared` folder is the **Shared Kernel** of the application — a DDD term for a small, deliberately
|
|
4
|
+
stable set of code that multiple bounded contexts genuinely need.
|
|
5
|
+
|
|
6
|
+
Path alias: `@Shared/*`
|
|
7
|
+
|
|
8
|
+
## What the Shared Kernel is (and is not)
|
|
9
|
+
|
|
10
|
+
In Domain-Driven Design, a **Shared Kernel** is a portion of the domain model that two or more bounded contexts
|
|
11
|
+
explicitly agree to share. It is held to a higher standard than context-specific code:
|
|
12
|
+
|
|
13
|
+
- **Small by intention.** Every addition to `shared` is available to every context. A mistake here propagates
|
|
14
|
+
everywhere. Err on the side of keeping code inside its bounded context.
|
|
15
|
+
- **Stable by discipline.** Changing `shared` can affect all contexts simultaneously. Any modification should
|
|
16
|
+
be consciously reviewed.
|
|
17
|
+
- **Agnostic by design.** `shared` must contain no feature logic specific to a bounded context. If a file
|
|
18
|
+
references a demo concept, it belongs in `demo`, not here.
|
|
19
|
+
- **Direction is one-way.** Bounded contexts may import from `shared`. `shared` must never import from a
|
|
20
|
+
bounded context (`@Demo/*`, etc.).
|
|
21
|
+
|
|
22
|
+
## What belongs here
|
|
23
|
+
|
|
24
|
+
- Cross-context contracts and types
|
|
25
|
+
- Generic infrastructure helpers (e.g., HTTP client wrappers)
|
|
26
|
+
- Common application-level abstractions
|
|
27
|
+
- Shared UI utilities that are not tied to any specific context experience
|
|
28
|
+
|
|
29
|
+
## What should not go here
|
|
30
|
+
|
|
31
|
+
- Demo pages, forms, layouts, or visual components
|
|
32
|
+
- Feature-specific services that belong inside a bounded context
|
|
33
|
+
- Convenience helpers without clear ownership or a concrete need to share them
|
|
34
|
+
|
|
35
|
+
## Folder map
|
|
36
|
+
|
|
37
|
+
| Sub-folder | Role |
|
|
38
|
+
| ----------------------------------------------- | ----------------------------------------------------------------- |
|
|
39
|
+
| [`domain/`](./domain/README.md) | Shared pure contracts, types, and abstractions. Zero dependencies |
|
|
40
|
+
| [`application/`](./application/README.md) | Shared helpers for orchestration — must not depend on infra or UI |
|
|
41
|
+
| [`infrastructure/`](./infrastructure/README.md) | Shared technical adapters — may depend on external libraries |
|
|
42
|
+
| [`ui/`](./ui/README.md) | Shared React hooks and components — must not depend on infra |
|
|
43
|
+
|
|
44
|
+
## Before you add to `shared`
|
|
45
|
+
|
|
46
|
+
Ask yourself:
|
|
47
|
+
|
|
48
|
+
1. Does more than one bounded context genuinely need this?
|
|
49
|
+
2. Is it stable enough to be shared without frequent modification?
|
|
50
|
+
3. Does it belong to one of the four Clean Architecture layers?
|
|
51
|
+
|
|
52
|
+
If the answer to any of these is "not sure", keep it in its bounded context first and promote it to `shared`
|
|
53
|
+
only when the need is proven.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# `shared/application`
|
|
2
|
+
|
|
3
|
+
The `shared/application` folder contains the **application layer** of the shared kernel — code that
|
|
4
|
+
orchestrates the domain and defines the contracts that infrastructure must fulfill, without doing either
|
|
5
|
+
of those jobs itself.
|
|
6
|
+
|
|
7
|
+
## What the application layer represents
|
|
8
|
+
|
|
9
|
+
In Clean Architecture, the **application layer** sits between the domain and the outside world. Its job is to:
|
|
10
|
+
|
|
11
|
+
1. **Orchestrate use cases.** A use case is a single, named, testable business operation (e.g., "Get the list
|
|
12
|
+
of demo users"). It calls domain objects and repository interfaces; it does not contain business rules
|
|
13
|
+
itself — it delegates those to the domain.
|
|
14
|
+
2. **Define infrastructure contracts.** Use cases depend on repository interfaces that are often declared in
|
|
15
|
+
the domain, but the application layer adds its own interface contracts for services (e.g., caching helpers,
|
|
16
|
+
event buses).
|
|
17
|
+
3. **Carry data across layer boundaries via DTOs.** A **Data Transfer Object (DTO)** is a plain, typed
|
|
18
|
+
structure that carries data from one layer to another without exposing internal domain objects.
|
|
19
|
+
|
|
20
|
+
## Dependency rules
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
shared/application
|
|
24
|
+
✅ can import from: shared/domain
|
|
25
|
+
❌ must NOT import from: shared/infrastructure
|
|
26
|
+
❌ must NOT import from: shared/ui
|
|
27
|
+
❌ must NOT import from: any bounded context
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If application code needs something from infrastructure, it should define an interface that infrastructure
|
|
31
|
+
implements (Dependency Inversion).
|
|
32
|
+
|
|
33
|
+
## What you can put here
|
|
34
|
+
|
|
35
|
+
- Shared use-case helper base classes or abstract factories
|
|
36
|
+
- Cross-context DTO types that carry data across layer boundaries
|
|
37
|
+
- Common abstractions used by multiple application layers (e.g., a `Result<T, E>` type)
|
|
38
|
+
|
|
39
|
+
## What you should not put here
|
|
40
|
+
|
|
41
|
+
- Feature-specific use cases (those belong in their bounded context's `application` folder)
|
|
42
|
+
- React components, hooks, or JSX
|
|
43
|
+
- Infrastructure implementations
|
|
44
|
+
- Domain entities or business rules
|
|
45
|
+
|
|
46
|
+
## Rule of thumb
|
|
47
|
+
|
|
48
|
+
If the code describes _what the application does_ (not _how it stores data_ or _how it looks_) and at least
|
|
49
|
+
two bounded contexts could plausibly depend on it, this folder is the right place.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `shared/domain`
|
|
2
|
+
|
|
3
|
+
The `shared/domain` folder contains the **innermost layer** of the shared kernel — the most protected, most
|
|
4
|
+
stable, and most dependency-free part of the codebase.
|
|
5
|
+
|
|
6
|
+
## What the domain layer represents
|
|
7
|
+
|
|
8
|
+
In Clean Architecture, the **domain layer** is the heart of the application. It captures the business rules and
|
|
9
|
+
invariants that would still be true even if you replaced the database, the HTTP client, or the UI framework.
|
|
10
|
+
|
|
11
|
+
In DDD terms, the domain layer hosts:
|
|
12
|
+
|
|
13
|
+
- **Entities** — objects with a stable identity that persists across state changes (e.g., `User`, `Order`).
|
|
14
|
+
- **Value Objects** — immutable objects defined entirely by their attributes, with no identity (e.g., `Email`,
|
|
15
|
+
`Money`, `DateRange`). Two value objects with the same attributes are equal.
|
|
16
|
+
- **Domain Services** — stateless operations that belong to the domain but do not fit naturally on a single
|
|
17
|
+
entity.
|
|
18
|
+
- **Repository Interfaces** — contracts that define _what_ data access operations exist, without specifying
|
|
19
|
+
_how_ they are implemented. The concrete implementation lives in `infrastructure`.
|
|
20
|
+
- **Domain Errors** — typed, domain-meaningful error cases.
|
|
21
|
+
|
|
22
|
+
## Absolute constraints
|
|
23
|
+
|
|
24
|
+
This layer must have **zero external dependencies**:
|
|
25
|
+
|
|
26
|
+
| Forbidden | Why |
|
|
27
|
+
| ---------------------- | ----------------------------------------------- |
|
|
28
|
+
| React / JSX | Domain must not know how it is rendered |
|
|
29
|
+
| Inversify / decorators | DI is an infrastructure concern |
|
|
30
|
+
| HTTP / fetch / axios | I/O belongs to infrastructure |
|
|
31
|
+
| Browser APIs | Domain must run anywhere (browser, Node, tests) |
|
|
32
|
+
| External NPM packages | Brings in coupling and upgrade risk |
|
|
33
|
+
|
|
34
|
+
Only plain TypeScript (interfaces, types, classes, functions, constants) belongs here.
|
|
35
|
+
|
|
36
|
+
## Difference between `shared/domain` and a context's own domain
|
|
37
|
+
|
|
38
|
+
`shared/domain` is the shared kernel's domain — only types or interfaces that are **genuinely used by more
|
|
39
|
+
than one bounded context**. If a domain concept belongs only to `demo`, it lives in `demo/domain`.
|
|
40
|
+
|
|
41
|
+
## What you can put here
|
|
42
|
+
|
|
43
|
+
- Shared pure interfaces and type aliases
|
|
44
|
+
- Value-object-like primitives reused across contexts
|
|
45
|
+
- Shared domain error types
|
|
46
|
+
- Repository interface contracts shared by multiple contexts (rare)
|
|
47
|
+
|
|
48
|
+
## What you should not put here
|
|
49
|
+
|
|
50
|
+
- Anything with an `import` statement that references React, Inversify, or external libraries
|
|
51
|
+
- Demo-specific entities or repository interfaces
|
|
52
|
+
- Speculative types added "just in case" they might be needed later
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# `shared/infrastructure`
|
|
2
|
+
|
|
3
|
+
The `shared/infrastructure` folder contains **technical adapters and concrete implementations** that can be
|
|
4
|
+
reused across bounded contexts.
|
|
5
|
+
|
|
6
|
+
## What the infrastructure layer represents
|
|
7
|
+
|
|
8
|
+
In Clean Architecture, **infrastructure** is the outermost ring alongside UI. It is the layer that deals with
|
|
9
|
+
the real world: databases, HTTP APIs, browser storage, timers, external SDKs, and so on.
|
|
10
|
+
|
|
11
|
+
The key principle at work here is the **Dependency Inversion Principle (DIP)**:
|
|
12
|
+
|
|
13
|
+
> High-level modules (domain, application) should not depend on low-level modules (infrastructure). Both
|
|
14
|
+
> should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
|
|
15
|
+
|
|
16
|
+
In practice this means:
|
|
17
|
+
|
|
18
|
+
- The **domain** defines a `UserRepositoryInterface` describing what it needs ("give me a user by ID").
|
|
19
|
+
- The **infrastructure** provides the concrete `HttpUserRepository` that knows _how_ to fetch the user.
|
|
20
|
+
- The domain and application layers never import `HttpUserRepository` directly — they work through the
|
|
21
|
+
interface. The DI container wires the concrete class to the interface at startup.
|
|
22
|
+
|
|
23
|
+
This **Ports and Adapters** pattern (also called Hexagonal Architecture) makes the domain fully testable in
|
|
24
|
+
isolation and lets you swap a real HTTP repository for a mock without touching any business logic.
|
|
25
|
+
|
|
26
|
+
## What you can put here
|
|
27
|
+
|
|
28
|
+
- Generic HTTP helpers and API client wrappers (e.g., `fetchApi`)
|
|
29
|
+
- Shared persistence adapters (local storage wrappers, IndexedDB helpers)
|
|
30
|
+
- Generic caching utilities
|
|
31
|
+
- Third-party SDK wrappers that multiple contexts share
|
|
32
|
+
- Base classes for repositories or adapters
|
|
33
|
+
|
|
34
|
+
## What you should not put here
|
|
35
|
+
|
|
36
|
+
- Feature-specific repositories or adapters (those belong in the bounded context's `infrastructure` folder)
|
|
37
|
+
- UI code, React components, or hooks
|
|
38
|
+
- Business rules or domain objects
|
|
39
|
+
|
|
40
|
+
## In this starter
|
|
41
|
+
|
|
42
|
+
The `http/` sub-folder holds a generic `fetchApi` helper used by the demo's HTTP repositories. It lives in
|
|
43
|
+
`shared/infrastructure` (not in `demo`) because the helper itself is context-agnostic — any bounded context
|
|
44
|
+
could use it to call an HTTP API.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
shared/infrastructure/http/
|
|
48
|
+
├── fetchApi.ts ← generic HTTP adapter
|
|
49
|
+
├── fetchApi.type.d.ts ← request/response type definitions
|
|
50
|
+
└── constant.ts ← HTTP-layer constants
|
|
51
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export enum METHODS {
|
|
2
|
+
get = "GET",
|
|
3
|
+
post = "POST",
|
|
4
|
+
put = "PUT",
|
|
5
|
+
patch = "PATCH",
|
|
6
|
+
delete = "DELETE",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export enum HEADERS {
|
|
10
|
+
contentType = "content-type",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum MIME_TYPES {
|
|
14
|
+
json = "application/json",
|
|
15
|
+
text = "plain/text",
|
|
16
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { HEADERS, METHODS, MIME_TYPES } from "./constant";
|
|
2
|
+
import type { Json } from "./fetchApi.type";
|
|
3
3
|
|
|
4
4
|
export type FetchApiError = Error & {
|
|
5
5
|
code?: number;
|
|
@@ -41,7 +41,7 @@ export const fetchApi = async <T extends Json | string>({
|
|
|
41
41
|
throw error;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
if ((response.headers.get(HEADERS.contentType) ??
|
|
44
|
+
if ((response.headers.get(HEADERS.contentType) ?? "").includes("json")) {
|
|
45
45
|
return JSON.parse(content) as T;
|
|
46
46
|
}
|
|
47
47
|
|