@pplancq/react-template 2.8.17 → 2.9.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/_README.md +57 -52
  3. package/mise.toml +2 -2
  4. package/package.json +25 -24
  5. package/scripts/removeDemo.cjs +105 -66
  6. package/src/README.md +81 -0
  7. package/src/app/App.tsx +18 -0
  8. package/src/{bootstrap.tsx → app/AppHTMLElement.tsx} +2 -2
  9. package/src/app/README.md +69 -0
  10. package/src/{__tests__/bootstraps.test.tsx → app/__tests__/AppHTMLElement.test.tsx} +5 -5
  11. package/src/app/config/serviceContainer.ts +6 -0
  12. package/src/app/providers/Providers/Providers.tsx +18 -0
  13. package/src/{providers → app/providers}/QueryClientProvider/QueryClientProvider.tsx +3 -3
  14. package/src/app/providers/ServiceProvider/ServiceContext.ts +8 -0
  15. package/src/app/providers/ServiceProvider/ServiceProvider.tsx +14 -0
  16. package/src/app/routing/Router.tsx +14 -0
  17. package/src/app/routing/appRoutes.ts +3 -0
  18. package/src/app/routing/routes.tsx +9 -0
  19. package/src/demo/README.md +148 -0
  20. package/src/demo/application/dtos/ArchitecturePrinciple.ts +5 -0
  21. package/src/{types/profileTypes.ts → demo/application/dtos/ProfileFormValues.ts} +1 -1
  22. package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCase.ts +11 -0
  23. package/src/demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface.ts +5 -0
  24. package/src/demo/application/use-cases/GetDemoUsersUseCase.ts +11 -0
  25. package/src/demo/application/use-cases/GetDemoUsersUseCaseInterface.ts +5 -0
  26. package/src/demo/domain/entities/DemoUser.ts +7 -0
  27. package/src/demo/domain/repositories/ArchitecturePrincipleRepositoryInterface.d.ts +5 -0
  28. package/src/demo/domain/repositories/DemoUserRepositoryInterface.d.ts +5 -0
  29. package/src/demo/infrastructure/repositories/HttpDemoUserRepository.ts +29 -0
  30. package/src/demo/infrastructure/repositories/StaticArchitecturePrincipleRepository.ts +33 -0
  31. package/src/demo/serviceDemo.ts +39 -0
  32. package/src/demo/serviceIdentifiers.ts +6 -0
  33. package/src/demo/ui/components/DemoFooter/DemoFooter.tsx +17 -0
  34. package/src/{components/Footer → demo/ui/components/DemoFooter}/__tests__/Footer.test.tsx +2 -2
  35. package/src/{components/Footer → demo/ui/components/DemoFooter}/__tests__/__snapshots__/Footer.test.tsx.snap +3 -3
  36. package/src/demo/ui/components/DemoFooter/index.ts +1 -0
  37. package/src/demo/ui/components/DemoHeader/DemoHeader.tsx +25 -0
  38. package/src/{components/Header → demo/ui/components/DemoHeader}/__tests__/Header.test.tsx +2 -2
  39. package/src/demo/ui/components/DemoHeader/__tests__/__snapshots__/Header.test.tsx.snap +64 -0
  40. package/src/demo/ui/components/DemoHeader/index.ts +1 -0
  41. package/src/{ui/molecules/Navigation/Navigation.tsx → demo/ui/components/DemoNavigation/DemoNavigation.tsx} +4 -4
  42. package/src/demo/ui/components/DemoNavigation/index.ts +1 -0
  43. package/src/{components → demo/ui/components}/TextInput/TextInput.tsx +3 -3
  44. package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.tsx +7 -7
  45. package/src/demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery.ts +13 -0
  46. package/src/demo/ui/layouts/DemoLayout/DemoLayout.tsx +12 -0
  47. package/src/demo/ui/layouts/MainTemplate/MainTemplate.module.css +21 -0
  48. package/src/{ui/templates → demo/ui/layouts}/MainTemplate/MainTemplate.tsx +7 -7
  49. package/src/demo/ui/pages/DemoHome/DemoHome.tsx +117 -0
  50. package/src/demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo.tsx +47 -0
  51. package/src/{pages/Demo → demo/ui/pages}/ReactHookFormDemo/ReactHookFormDemo.tsx +7 -7
  52. package/src/demo/ui/pages/ReactQueryDemo/ReactQueryDemo.module.scss +38 -0
  53. package/src/{pages/Demo → demo/ui/pages}/ReactQueryDemo/ReactQueryDemo.tsx +5 -4
  54. package/src/demo/ui/routing/demoAppRoutes.ts +6 -0
  55. package/src/demo/ui/routing/demoRoutes.tsx +31 -0
  56. package/src/main.ts +2 -2
  57. package/src/shared/README.md +53 -0
  58. package/src/shared/application/README.md +49 -0
  59. package/src/shared/domain/README.md +52 -0
  60. package/src/shared/infrastructure/README.md +51 -0
  61. package/src/{api → shared/infrastructure/http}/fetchApi.ts +1 -1
  62. package/src/shared/ui/README.md +50 -0
  63. package/src/shared/ui/hooks/useService/useService.ts +13 -0
  64. package/tsconfig.json +3 -1
  65. package/vitest.config.mts +5 -1
  66. package/src/api/README.md +0 -65
  67. package/src/api/demoApi.ts +0 -10
  68. package/src/assets/README.md +0 -54
  69. package/src/components/App/App.tsx +0 -18
  70. package/src/components/App/index.ts +0 -1
  71. package/src/components/Footer/Footer.tsx +0 -16
  72. package/src/components/Footer/index.ts +0 -1
  73. package/src/components/Header/Header.tsx +0 -17
  74. package/src/components/Header/__tests__/__snapshots__/Header.test.tsx.snap +0 -37
  75. package/src/components/Header/index.ts +0 -1
  76. package/src/components/README.md +0 -33
  77. package/src/config/README.md +0 -63
  78. package/src/config/index.ts +0 -1
  79. package/src/forms/README.md +0 -142
  80. package/src/hooks/README.md +0 -83
  81. package/src/hooks/api/queryKey.ts +0 -1
  82. package/src/hooks/api/useDemoApi.ts +0 -16
  83. package/src/pages/Demo/Demo.tsx +0 -236
  84. package/src/pages/Demo/ReactHookFormDemo/index.ts +0 -1
  85. package/src/pages/Demo/ReactHookFormDemo/routes.tsx +0 -7
  86. package/src/pages/Demo/ReactQueryDemo/index.ts +0 -1
  87. package/src/pages/Demo/ReactQueryDemo/routes.tsx +0 -7
  88. package/src/pages/Demo/__tests__/Demo.test.tsx +0 -15
  89. package/src/pages/Demo/__tests__/__snapshots__/Demo.test.tsx.snap +0 -557
  90. package/src/pages/Demo/index.ts +0 -1
  91. package/src/pages/Demo/routes.tsx +0 -8
  92. package/src/pages/Layout/Layout.tsx +0 -12
  93. package/src/pages/Layout/index.ts +0 -1
  94. package/src/pages/README.md +0 -91
  95. package/src/pages/UnexpectedError/UnexpectedError.module.css +0 -8
  96. package/src/pages/UnexpectedError/UnexpectedError.tsx +0 -22
  97. package/src/pages/UnexpectedError/index.ts +0 -1
  98. package/src/providers/QueryClientProvider/index.ts +0 -1
  99. package/src/providers/README.md +0 -112
  100. package/src/providers/index.ts +0 -1
  101. package/src/providers/withProviders/index.ts +0 -1
  102. package/src/providers/withProviders/withProviders.tsx +0 -18
  103. package/src/routing/README.md +0 -76
  104. package/src/routing/appRoutes.ts +0 -5
  105. package/src/routing/routerFactory.ts +0 -12
  106. package/src/routing/routes.tsx +0 -13
  107. package/src/types/README.md +0 -62
  108. package/src/types/demoApi.ts +0 -25
  109. package/src/ui/README.md +0 -144
  110. package/src/ui/molecules/Navigation/index.ts +0 -1
  111. package/src/ui/organisms/Footer/Footer.module.css +0 -10
  112. package/src/ui/organisms/Footer/Footer.tsx +0 -10
  113. package/src/ui/organisms/Footer/index.ts +0 -1
  114. package/src/ui/organisms/Header/Header.module.css +0 -17
  115. package/src/ui/organisms/Header/Header.tsx +0 -16
  116. package/src/ui/organisms/Header/index.ts +0 -1
  117. package/src/ui/organisms/MainContent/MainContent.module.css +0 -3
  118. package/src/ui/organisms/MainContent/MainContent.tsx +0 -6
  119. package/src/ui/organisms/MainContent/index.ts +0 -1
  120. package/src/ui/templates/MainTemplate/MainTemplate.module.css +0 -5
  121. package/src/utils/README.md +0 -70
  122. /package/src/{assets → app/assets}/css/index.ts +0 -0
  123. /package/src/{assets → app/assets}/css/mainBody.css +0 -0
  124. /package/src/{assets → app/assets}/css/reset.css +0 -0
  125. /package/src/{config → app/config}/queryClientConfig.ts +0 -0
  126. /package/src/{assets → demo/ui/assets}/images/logo.png +0 -0
  127. /package/src/{assets → demo/ui/assets}/images/templatePlugins.png +0 -0
  128. /package/src/{components/Footer/Footer.module.css → demo/ui/components/DemoFooter/DemoFooter.module.css} +0 -0
  129. /package/src/{components/Header/Header.module.css → demo/ui/components/DemoHeader/DemoHeader.module.css} +0 -0
  130. /package/src/{ui/molecules/Navigation/Navigation.module.css → demo/ui/components/DemoNavigation/DemoNavigation.module.css} +0 -0
  131. /package/src/{ui/atoms → demo/ui/components}/Input/Input.module.scss +0 -0
  132. /package/src/{ui/atoms → demo/ui/components}/Input/Input.tsx +0 -0
  133. /package/src/{ui/atoms → demo/ui/components}/Input/index.ts +0 -0
  134. /package/src/{ui/atoms → demo/ui/components}/Logo/Logo.module.css +0 -0
  135. /package/src/{ui/atoms → demo/ui/components}/Logo/Logo.tsx +0 -0
  136. /package/src/{ui/atoms → demo/ui/components}/Logo/__tests__/Logo.test.tsx +0 -0
  137. /package/src/{ui/atoms → demo/ui/components}/Logo/index.ts +0 -0
  138. /package/src/{ui/atoms → demo/ui/components}/NavLink/NavLink.tsx +0 -0
  139. /package/src/{ui/atoms → demo/ui/components}/NavLink/index.ts +0 -0
  140. /package/src/{components → demo/ui/components}/TextInput/index.ts +0 -0
  141. /package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.module.scss +0 -0
  142. /package/src/{forms → demo/ui/forms}/ProfileForm/index.ts +0 -0
  143. /package/src/{ui/templates → demo/ui/layouts}/MainTemplate/index.ts +0 -0
  144. /package/src/{pages/Demo/Demo.module.scss → demo/ui/pages/DemoHome/DemoHome.module.scss} +0 -0
  145. /package/src/{pages/Demo/ReactQueryDemo/ReactQueryDemo.module.scss → demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo.module.scss} +0 -0
  146. /package/src/{pages/Demo/ReactHookFormDemo/ReactHookFormDemo.module.css → demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo.module.scss} +0 -0
  147. /package/src/{api → shared/infrastructure/http}/constant.ts +0 -0
  148. /package/src/{types/api.ts → shared/infrastructure/http/fetchApi.type.d.ts} +0 -0
@@ -0,0 +1,69 @@
1
+ # `app`
2
+
3
+ The `app` folder is the **application bootstrap layer**. It is not a bounded context — it has no business
4
+ domain of its own. Its single responsibility is to assemble all the pieces (providers, router, DI container,
5
+ contexts) and expose the application to the browser.
6
+
7
+ Path alias: `@App/*`
8
+
9
+ ## Contents
10
+
11
+ | Subfolder / file | Role |
12
+ | -------------------- | -------------------------------------------------------------------------------- |
13
+ | `App.tsx` | Root React component — composes providers and router |
14
+ | `AppHTMLElement.tsx` | Custom-element bootstrap (`<app-react>`). Mounts/unmounts the React root |
15
+ | `assets/` | Global CSS reset, base styles, and app-wide static assets |
16
+ | `config/` | App-wide configuration and the **composition root** (`serviceContainer.ts`) |
17
+ | `providers/` | React provider components: `ServiceProvider`, `QueryClientProvider`, `Providers` |
18
+ | `routing/` | Top-level router and route aggregation |
19
+
20
+ ## Composition root
21
+
22
+ `config/serviceContainer.ts` is the **composition root** of the dependency injection system. It is the single
23
+ place in the application where the Inversify container is created and all bounded-context service collections
24
+ are loaded:
25
+
26
+ ```ts
27
+ import { serviceDemo } from '@Demo/serviceDemo';
28
+ import { Container } from 'inversify';
29
+
30
+ export const serviceContainer = new Container();
31
+ serviceContainer.load(serviceDemo);
32
+ // add more context modules here as the application grows
33
+ ```
34
+
35
+ The composition root is the **only** place that knows about concrete implementations. Every other layer receives
36
+ dependencies through interfaces and the DI container.
37
+
38
+ ## Provider tree
39
+
40
+ The `Providers` component wraps the application in its provider tree. The order matters:
41
+
42
+ ```
43
+ ServiceProvider ← makes the DI container available via React context
44
+ └── QueryClientProvider ← makes React Query available
45
+ └── {children} ← the rest of the application
46
+ ```
47
+
48
+ `ServiceProvider` must be outermost so that any inner provider can use `useService` to resolve DI services.
49
+
50
+ ## Custom element bootstrap
51
+
52
+ `AppHTMLElement.tsx` registers the application as a
53
+ [custom element](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) (`<app-react>`).
54
+ This allows the React app to be embedded in any HTML host without tight coupling to a specific framework or
55
+ page structure. The custom element mounts a React root in `connectedCallback` and unmounts it in
56
+ `disconnectedCallback`.
57
+
58
+ ## Dependency rules for `app`
59
+
60
+ - ✅ Can import from `@Shared/*` and any bounded context (e.g., `@Demo/*`)
61
+ - ✅ Responsible for high-level orchestration only
62
+ - ❌ Must not contain business rules or feature-specific logic
63
+ - ❌ Generic helpers belong in `shared`, not here
64
+
65
+ ## Avoid
66
+
67
+ - Business rules or domain objects
68
+ - Feature-specific orchestration that belongs to a bounded context
69
+ - Generic helpers that should live in `shared`
@@ -1,11 +1,11 @@
1
- import { App } from '@Front/components/App';
1
+ import { App } from '@App/App';
2
2
  import { waitFor } from '@testing-library/react';
3
3
  import { StrictMode } from 'react';
4
4
  import { createRoot } from 'react-dom/client';
5
5
  import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
6
- import bootstrap from '../bootstrap';
6
+ import AppHTMLElement from '../AppHTMLElement';
7
7
 
8
- vi.mock('@Front/components/App', () => ({
8
+ vi.mock('@App/App', () => ({
9
9
  App: () => <div data-testid="app-mock">AppMock</div>,
10
10
  }));
11
11
 
@@ -13,7 +13,7 @@ vi.mock('react-dom/client', () => ({
13
13
  createRoot: vi.fn(),
14
14
  }));
15
15
 
16
- describe('bootstrap', () => {
16
+ describe('AppHTMLElement', () => {
17
17
  let container: HTMLElement;
18
18
  const render = vi.fn();
19
19
  const unmount = vi.fn();
@@ -22,7 +22,7 @@ describe('bootstrap', () => {
22
22
  unmount,
23
23
  }));
24
24
 
25
- customElements.define('bootstrap-html-element', bootstrap);
25
+ customElements.define('bootstrap-html-element', AppHTMLElement);
26
26
 
27
27
  beforeEach(() => {
28
28
  container = document.createElement('bootstrap-html-element');
@@ -0,0 +1,6 @@
1
+ import { serviceDemo } from '@Demo/serviceDemo';
2
+ import { Container } from 'inversify';
3
+
4
+ export const serviceContainer = new Container();
5
+
6
+ serviceContainer.load(serviceDemo);
@@ -0,0 +1,18 @@
1
+ import { QueryClientProvider } from '@App/providers/QueryClientProvider/QueryClientProvider';
2
+ import { ServiceProvider } from '@App/providers/ServiceProvider/ServiceProvider';
3
+ import type { QueryClient } from '@tanstack/react-query';
4
+ import type { Container } from 'inversify';
5
+ import type { PropsWithChildren } from 'react';
6
+
7
+ type ProvidersProps = PropsWithChildren<{
8
+ queryClient: QueryClient;
9
+ container: Container;
10
+ }>;
11
+
12
+ export const Providers = ({ queryClient, container, children }: ProvidersProps) => {
13
+ return (
14
+ <ServiceProvider container={container}>
15
+ <QueryClientProvider queryClient={queryClient}>{children}</QueryClientProvider>
16
+ </ServiceProvider>
17
+ );
18
+ };
@@ -3,12 +3,12 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
3
3
  import type { PropsWithChildren } from 'react';
4
4
 
5
5
  type ClientProviderProps = {
6
- client: QueryClient;
6
+ queryClient: QueryClient;
7
7
  };
8
8
 
9
- export const QueryClientProvider = ({ children, client }: PropsWithChildren<ClientProviderProps>) => {
9
+ export const QueryClientProvider = ({ children, queryClient }: PropsWithChildren<ClientProviderProps>) => {
10
10
  return (
11
- <ReactQueryClientProvider client={client}>
11
+ <ReactQueryClientProvider client={queryClient}>
12
12
  {children}
13
13
  <ReactQueryDevtools initialIsOpen={false} />
14
14
  </ReactQueryClientProvider>
@@ -0,0 +1,8 @@
1
+ import type { Container } from 'inversify';
2
+ import { createContext } from 'react';
3
+
4
+ export type ServiceContextValue = {
5
+ container: Container;
6
+ };
7
+
8
+ export const ServiceContext = createContext<ServiceContextValue | null>(null);
@@ -0,0 +1,14 @@
1
+ import { ServiceContext } from '@App/providers/ServiceProvider/ServiceContext';
2
+ import type { Container } from 'inversify';
3
+ import type { PropsWithChildren } from 'react';
4
+ import { useRef } from 'react';
5
+
6
+ export type ServiceProviderProps = PropsWithChildren<{
7
+ container: Container;
8
+ }>;
9
+
10
+ export const ServiceProvider = ({ container, children }: ServiceProviderProps) => {
11
+ const containerRef = useRef({ container });
12
+
13
+ return <ServiceContext.Provider value={containerRef.current}>{children}</ServiceContext.Provider>;
14
+ };
@@ -0,0 +1,14 @@
1
+ import { RouterProvider, createBrowserRouter } from 'react-router';
2
+ import { routeObject } from './routes';
3
+
4
+ type RouterProps = {
5
+ basename?: string;
6
+ };
7
+
8
+ export const Router = ({ basename }: RouterProps = {}) => {
9
+ const router = createBrowserRouter(routeObject, {
10
+ basename,
11
+ });
12
+
13
+ return <RouterProvider router={router} />;
14
+ };
@@ -0,0 +1,3 @@
1
+ export const appRoutes = Object.freeze({
2
+ home: '/',
3
+ });
@@ -0,0 +1,9 @@
1
+ import { demoRoutes } from '@Demo/ui/routing/demoRoutes';
2
+ import type { RouteObject } from 'react-router';
3
+
4
+ export const routeObject: RouteObject[] = [
5
+ {
6
+ path: '/',
7
+ children: [demoRoutes],
8
+ },
9
+ ];
@@ -0,0 +1,148 @@
1
+ # `demo`
2
+
3
+ The `demo` folder is the template's **example Bounded Context**.
4
+
5
+ Path alias: `@Demo/*`
6
+
7
+ ## What is a Bounded Context?
8
+
9
+ In Domain-Driven Design (DDD), a **Bounded Context** is an isolated, autonomous slice of the application that
10
+ owns its own domain model, its own ubiquitous language, and its own internal implementation. It communicates
11
+ with the outside world (other contexts, the app layer) through well-defined entry points only.
12
+
13
+ Practical rules:
14
+
15
+ - A bounded context **never imports directly from another bounded context**. Cross-context communication goes
16
+ through clearly defined contracts (events, DTOs, or a shared kernel type).
17
+ - A bounded context **owns its full stack**: from the domain model all the way to the UI.
18
+ - Every concept inside a context is named consistently — this is the **Ubiquitous Language** principle. The
19
+ same word must mean the same thing everywhere inside the context.
20
+
21
+ ## Layer structure
22
+
23
+ The `demo` context follows Clean Architecture's four-layer model:
24
+
25
+ ```
26
+ demo/
27
+ ├── domain/ ← pure business contracts, entities, repository interfaces
28
+ ├── application/ ← use cases, DTOs, orchestration
29
+ ├── infrastructure/ ← concrete implementations, adapters
30
+ ├── ui/ ← React pages, components, hooks, routing
31
+ ├── serviceDemo.ts ← Inversify ContainerModule that wires this context's DI
32
+ └── serviceIdentifiers.ts ← unique Symbols for each service in this context
33
+ ```
34
+
35
+ ### `domain/`
36
+
37
+ The heart of the context. Contains:
38
+
39
+ - **Repository interfaces** — describe the data access operations the domain needs (e.g.,
40
+ `DemoUserRepositoryInterface`, `ArchitecturePrincipleRepositoryInterface`). These are TypeScript interfaces;
41
+ the implementation lives in `infrastructure/`.
42
+ - **Entities** — objects with identity and lifecycle (e.g., `DemoUser`).
43
+ - **Value Objects** — immutable objects defined by their values (no identity).
44
+
45
+ Zero external dependencies. No React, no Inversify, no HTTP.
46
+
47
+ ### `application/`
48
+
49
+ The orchestration layer. Contains:
50
+
51
+ - **Use cases** — named, testable business operations (e.g., `GetDemoUsersUseCase`). Each use case implements
52
+ an interface (`GetDemoUsersUseCaseInterface`) so it can be replaced by a test double without modifying the UI.
53
+ - **DTOs** — plain, typed structures that carry data across layer boundaries (e.g., `ArchitecturePrinciple`,
54
+ `ProfileFormValues`).
55
+
56
+ Can import from `domain`. Must not import from `infrastructure` or `ui`.
57
+
58
+ ### `infrastructure/`
59
+
60
+ The adapter layer. Contains:
61
+
62
+ - **Concrete repository implementations** (e.g., `HttpDemoUserRepository`, `StaticArchitecturePrincipleRepository`).
63
+ Each class implements an interface defined in `domain/`, satisfying the **Dependency Inversion Principle**.
64
+
65
+ Can import from `domain` (the interfaces it implements) and from `@Shared/infrastructure` (shared adapters).
66
+ Must not import from `application` or `ui`.
67
+
68
+ ### `ui/`
69
+
70
+ The presentation layer. Contains:
71
+
72
+ - **Pages** — top-level route components
73
+ - **Components** — context-specific visual components
74
+ - **Layouts** — page-level layout wrappers
75
+ - **Forms** — form composition (React Hook Form)
76
+ - **Hooks** — React Query hooks that bridge use cases to the React component tree
77
+ - **Routing** — route declarations exported to `@App/routing`
78
+
79
+ The UI layer uses `useService` from `@Shared/ui/hooks/useService` to resolve use cases from the DI container.
80
+ It calls use cases; it does not call repositories or infrastructure directly.
81
+
82
+ ## Dependency direction within the context
83
+
84
+ ```
85
+ ui → application → domain
86
+ infrastructure -.→ domain (implements its interfaces)
87
+ ```
88
+
89
+ The arrows show allowed import direction. Reversing them is a Clean Architecture violation.
90
+
91
+ ## DI wiring
92
+
93
+ ### `serviceIdentifiers.ts`
94
+
95
+ Declares unique `Symbol` identifiers for every service in the context:
96
+
97
+ ```ts
98
+ export const DEMO_SERVICES = Object.freeze({
99
+ DemoUserRepository: Symbol.for('Demo.DemoUserRepository'),
100
+ GetDemoUsersUseCase: Symbol.for('Demo.GetDemoUsersUseCase'),
101
+ // ...
102
+ });
103
+ ```
104
+
105
+ Using `Symbol.for` with a namespaced string (`Demo.*`) avoids collisions with other contexts.
106
+
107
+ ### `serviceDemo.ts`
108
+
109
+ A `ContainerModule` that binds each service identifier to its concrete implementation using Inversify's
110
+ `toDynamicValue`. This pattern avoids TypeScript decorators entirely, keeping the domain and application layers
111
+ free of framework metadata:
112
+
113
+ ```ts
114
+ export const serviceDemo: ContainerModule = new ContainerModule(options => {
115
+ options
116
+ .bind<DemoUserRepositoryInterface>(DEMO_SERVICES.DemoUserRepository)
117
+ .toDynamicValue(() => new HttpDemoUserRepository())
118
+ .inSingletonScope();
119
+
120
+ options
121
+ .bind<GetDemoUsersUseCaseInterface>(DEMO_SERVICES.GetDemoUsersUseCase)
122
+ .toDynamicValue(
123
+ services => new GetDemoUsersUseCase(services.get<DemoUserRepositoryInterface>(DEMO_SERVICES.DemoUserRepository)),
124
+ )
125
+ .inSingletonScope();
126
+ });
127
+ ```
128
+
129
+ `serviceDemo` is loaded into the global container in `@App/config/serviceContainer.ts`.
130
+
131
+ ## UI organization principle
132
+
133
+ The UI layer uses practical, readable sub-folders (`components`, `layouts`, `pages`, `forms`, `hooks`,
134
+ `routing`). Atomic Design can still guide how you think about component responsibility — keeping components
135
+ small and composable — but strict `atoms/molecules/organisms/templates` directories add indirection without
136
+ real value in most projects.
137
+
138
+ ## Important: demo as a model, not a constraint
139
+
140
+ The `demo` context exists to show the path. When you start your real project:
141
+
142
+ 1. Rename or replace `demo` with your first real bounded context.
143
+ 2. Add its alias to `tsconfig.json` (e.g., `"@Orders/*": ["src/orders/*"]`).
144
+ 3. Create its `serviceIdentifiers.ts` and `service[ContextName].ts` (e.g., `serviceHome.ts`).
145
+ 4. Register its container module in `app/config/serviceContainer.ts`.
146
+
147
+ Keep visual components in their context's `ui/` folder until there is a proven, concrete reason to move them
148
+ to `shared`.
@@ -0,0 +1,5 @@
1
+ export type ArchitecturePrinciple = {
2
+ title: string;
3
+ description: string;
4
+ layer: 'domain' | 'application' | 'infrastructure' | 'ui';
5
+ };
@@ -1,4 +1,4 @@
1
- export type ProfileType = {
1
+ export type ProfileFormValues = {
2
2
  mail: string;
3
3
  firstName: string;
4
4
  lastName: string;
@@ -0,0 +1,11 @@
1
+ import type { ArchitecturePrinciple } from '@Demo/application/dtos/ArchitecturePrinciple';
2
+ import type { GetArchitecturePrinciplesUseCaseInterface } from '@Demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface';
3
+ import type { ArchitecturePrincipleRepositoryInterface } from '@Demo/domain/repositories/ArchitecturePrincipleRepositoryInterface';
4
+
5
+ export class GetArchitecturePrinciplesUseCase implements GetArchitecturePrinciplesUseCaseInterface {
6
+ public constructor(private readonly architecturePrincipleRepository: ArchitecturePrincipleRepositoryInterface) {}
7
+
8
+ public readonly execute = (): ArchitecturePrinciple[] => {
9
+ return this.architecturePrincipleRepository.getAll();
10
+ };
11
+ }
@@ -0,0 +1,5 @@
1
+ import type { ArchitecturePrinciple } from '@Demo/application/dtos/ArchitecturePrinciple';
2
+
3
+ export interface GetArchitecturePrinciplesUseCaseInterface {
4
+ execute: () => ArchitecturePrinciple[];
5
+ }
@@ -0,0 +1,11 @@
1
+ import type { GetDemoUsersUseCaseInterface } from '@Demo/application/use-cases/GetDemoUsersUseCaseInterface';
2
+ import type { DemoUser } from '@Demo/domain/entities/DemoUser';
3
+ import type { DemoUserRepositoryInterface } from '@Demo/domain/repositories/DemoUserRepositoryInterface';
4
+
5
+ export class GetDemoUsersUseCase implements GetDemoUsersUseCaseInterface {
6
+ public constructor(private readonly demoUserRepository: DemoUserRepositoryInterface) {}
7
+
8
+ public readonly execute = async (): Promise<DemoUser[]> => {
9
+ return this.demoUserRepository.getAll();
10
+ };
11
+ }
@@ -0,0 +1,5 @@
1
+ import type { DemoUser } from '@Demo/domain/entities/DemoUser';
2
+
3
+ export interface GetDemoUsersUseCaseInterface {
4
+ execute: () => Promise<DemoUser[]>;
5
+ }
@@ -0,0 +1,7 @@
1
+ export type DemoUser = {
2
+ id: number;
3
+ name: string;
4
+ username: string;
5
+ email: string;
6
+ website: string;
7
+ };
@@ -0,0 +1,5 @@
1
+ import type { ArchitecturePrinciple } from '@Demo/application/dtos/ArchitecturePrinciple';
2
+
3
+ export interface ArchitecturePrincipleRepositoryInterface {
4
+ getAll: () => ArchitecturePrinciple[];
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { DemoUser } from '@Demo/domain/entities/DemoUser';
2
+
3
+ export interface DemoUserRepositoryInterface {
4
+ getAll: () => Promise<DemoUser[]>;
5
+ }
@@ -0,0 +1,29 @@
1
+ import type { DemoUser } from '@Demo/domain/entities/DemoUser';
2
+ import type { DemoUserRepositoryInterface } from '@Demo/domain/repositories/DemoUserRepositoryInterface';
3
+ import { fetchApi } from '@Shared/infrastructure/http/fetchApi';
4
+
5
+ type DemoUserApiResponse = {
6
+ id: number;
7
+ name: string;
8
+ username: string;
9
+ email: string;
10
+ website: string;
11
+ };
12
+
13
+ export class HttpDemoUserRepository implements DemoUserRepositoryInterface {
14
+ private readonly apiPath = 'https://jsonplaceholder.typicode.com/users';
15
+
16
+ public readonly getAll = async (): Promise<DemoUser[]> => {
17
+ const users = await fetchApi<DemoUserApiResponse[]>({
18
+ path: this.apiPath,
19
+ });
20
+
21
+ return users.map(user => ({
22
+ id: user.id,
23
+ name: user.name,
24
+ username: user.username.toUpperCase(),
25
+ email: user.email.toLowerCase(),
26
+ website: user.website,
27
+ }));
28
+ };
29
+ }
@@ -0,0 +1,33 @@
1
+ import type { ArchitecturePrinciple } from '@Demo/application/dtos/ArchitecturePrinciple';
2
+ import type { ArchitecturePrincipleRepositoryInterface } from '@Demo/domain/repositories/ArchitecturePrincipleRepositoryInterface';
3
+
4
+ export class StaticArchitecturePrincipleRepository implements ArchitecturePrincipleRepositoryInterface {
5
+ private readonly architecturePrinciples: ArchitecturePrinciple[] = [
6
+ {
7
+ title: 'Domain stays framework-free',
8
+ description: 'The demo domain only defines contracts and pure data structures. React and Inversify stay outside.',
9
+ layer: 'domain',
10
+ },
11
+ {
12
+ title: 'Application orchestrates use cases',
13
+ description: 'Use cases expose intent-driven APIs such as GetDemoUsersUseCase instead of leaking infrastructure.',
14
+ layer: 'application',
15
+ },
16
+ {
17
+ title: 'Infrastructure implements contracts',
18
+ description:
19
+ 'Concrete adapters, such as the HTTP repository, live in infrastructure and are selected by the container.',
20
+ layer: 'infrastructure',
21
+ },
22
+ {
23
+ title: 'UI depends on abstractions',
24
+ description:
25
+ 'React pages use hooks and useService to resolve use cases without importing infrastructure directly.',
26
+ layer: 'ui',
27
+ },
28
+ ];
29
+
30
+ public readonly getAll = (): ArchitecturePrinciple[] => {
31
+ return this.architecturePrinciples;
32
+ };
33
+ }
@@ -0,0 +1,39 @@
1
+ import type { ArchitecturePrincipleRepositoryInterface } from '@Demo/domain/repositories/ArchitecturePrincipleRepositoryInterface';
2
+ import type { DemoUserRepositoryInterface } from '@Demo/domain/repositories/DemoUserRepositoryInterface';
3
+ import { StaticArchitecturePrincipleRepository } from '@Demo/infrastructure/repositories/StaticArchitecturePrincipleRepository';
4
+ import { HttpDemoUserRepository } from '@Demo/infrastructure/repositories/HttpDemoUserRepository';
5
+ import { GetArchitecturePrinciplesUseCase } from '@Demo/application/use-cases/GetArchitecturePrinciplesUseCase';
6
+ import type { GetArchitecturePrinciplesUseCaseInterface } from '@Demo/application/use-cases/GetArchitecturePrinciplesUseCaseInterface';
7
+ import { GetDemoUsersUseCase } from '@Demo/application/use-cases/GetDemoUsersUseCase';
8
+ import type { GetDemoUsersUseCaseInterface } from '@Demo/application/use-cases/GetDemoUsersUseCaseInterface';
9
+ import { ContainerModule } from 'inversify';
10
+ import { DEMO_SERVICES } from './serviceIdentifiers';
11
+
12
+ export const serviceDemo: ContainerModule = new ContainerModule(options => {
13
+ options
14
+ .bind<DemoUserRepositoryInterface>(DEMO_SERVICES.DemoUserRepository)
15
+ .toDynamicValue(() => new HttpDemoUserRepository())
16
+ .inSingletonScope();
17
+
18
+ options
19
+ .bind<GetDemoUsersUseCaseInterface>(DEMO_SERVICES.GetDemoUsersUseCase)
20
+ .toDynamicValue(
21
+ services => new GetDemoUsersUseCase(services.get<DemoUserRepositoryInterface>(DEMO_SERVICES.DemoUserRepository)),
22
+ )
23
+ .inSingletonScope();
24
+
25
+ options
26
+ .bind<ArchitecturePrincipleRepositoryInterface>(DEMO_SERVICES.ArchitecturePrincipleRepository)
27
+ .toDynamicValue(() => new StaticArchitecturePrincipleRepository())
28
+ .inSingletonScope();
29
+
30
+ options
31
+ .bind<GetArchitecturePrinciplesUseCaseInterface>(DEMO_SERVICES.GetArchitecturePrinciplesUseCase)
32
+ .toDynamicValue(
33
+ services =>
34
+ new GetArchitecturePrinciplesUseCase(
35
+ services.get<ArchitecturePrincipleRepositoryInterface>(DEMO_SERVICES.ArchitecturePrincipleRepository),
36
+ ),
37
+ )
38
+ .inSingletonScope();
39
+ });
@@ -0,0 +1,6 @@
1
+ export const DEMO_SERVICES = Object.freeze({
2
+ DemoUserRepository: Symbol.for('Demo.DemoUserRepository'),
3
+ GetDemoUsersUseCase: Symbol.for('Demo.GetDemoUsersUseCase'),
4
+ ArchitecturePrincipleRepository: Symbol.for('Demo.ArchitecturePrincipleRepository'),
5
+ GetArchitecturePrinciplesUseCase: Symbol.for('Demo.GetArchitecturePrinciplesUseCase'),
6
+ });
@@ -0,0 +1,17 @@
1
+ import logo from '@Demo/ui/assets/images/logo.png';
2
+ import { demoAppRoutes } from '@Demo/ui/routing/demoAppRoutes';
3
+ import { Logo } from '@Demo/ui/components/Logo/Logo';
4
+ import { Link } from 'react-router';
5
+
6
+ import classes from './DemoFooter.module.css';
7
+
8
+ export const DemoFooter = () => {
9
+ return (
10
+ <div className={classes.container}>
11
+ <hr />
12
+ <Link to={demoAppRoutes.home}>
13
+ <Logo size="50px" src={logo} alt="logo" />
14
+ </Link>
15
+ </div>
16
+ );
17
+ };
@@ -1,12 +1,12 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import { BrowserRouter as Router } from 'react-router';
3
- import { Footer } from '../Footer';
3
+ import { DemoFooter } from '../DemoFooter';
4
4
 
5
5
  describe('Footer Component', () => {
6
6
  it('should render the Footer component correctly', () => {
7
7
  const { container } = render(
8
8
  <Router>
9
- <Footer />
9
+ <DemoFooter />
10
10
  </Router>,
11
11
  );
12
12
 
@@ -3,7 +3,7 @@
3
3
  exports[`Footer Component > should render the Footer component correctly 1`] = `
4
4
  <div>
5
5
  <div
6
- class="_container_acfd2c"
6
+ class="container"
7
7
  >
8
8
  <hr />
9
9
  <a
@@ -12,8 +12,8 @@ exports[`Footer Component > should render the Footer component correctly 1`] = `
12
12
  >
13
13
  <img
14
14
  alt="logo"
15
- class="_logoStyle_9e86a7"
16
- src="/src/assets/images/logo.png"
15
+ class="logoStyle"
16
+ src="/src/demo/ui/assets/images/logo.png"
17
17
  style="width: 50px;"
18
18
  />
19
19
  </a>
@@ -0,0 +1 @@
1
+ export { DemoFooter } from './DemoFooter';
@@ -0,0 +1,25 @@
1
+ import logo from '@Demo/ui/assets/images/logo.png';
2
+ import { DemoNavigation } from '@Demo/ui/components/DemoNavigation/DemoNavigation';
3
+ import { demoAppRoutes } from '@Demo/ui/routing/demoAppRoutes';
4
+ import { Logo } from '@Demo/ui/components/Logo/Logo';
5
+ import { Link } from 'react-router';
6
+
7
+ import classes from './DemoHeader.module.css';
8
+
9
+ export const DemoHeader = () => {
10
+ return (
11
+ <div className={classes.container}>
12
+ <Link to={demoAppRoutes.home}>
13
+ <Logo size="50px" src={logo} alt="logo" />
14
+ </Link>
15
+ <DemoNavigation
16
+ links={[
17
+ { label: 'Overview', url: demoAppRoutes.home },
18
+ { label: 'React Query', url: demoAppRoutes.reactQuery },
19
+ { label: 'React Hook Form', url: demoAppRoutes.reactHookForm },
20
+ { label: 'Dependency Injection', url: demoAppRoutes.dependencyInjection },
21
+ ]}
22
+ />
23
+ </div>
24
+ );
25
+ };
@@ -1,12 +1,12 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import { BrowserRouter as Router } from 'react-router';
3
- import { Header } from '../Header';
3
+ import { DemoHeader } from '../DemoHeader';
4
4
 
5
5
  describe('Header Component', () => {
6
6
  it('should render the Header component correctly', () => {
7
7
  const { container } = render(
8
8
  <Router>
9
- <Header />
9
+ <DemoHeader />
10
10
  </Router>,
11
11
  );
12
12