@pplancq/react-template 2.8.16 → 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.
- package/CHANGELOG.md +43 -0
- package/_README.md +57 -52
- package/mise.toml +2 -2
- package/package.json +28 -27
- 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} +2 -2
- package/src/app/README.md +69 -0
- package/src/{__tests__/bootstraps.test.tsx → app/__tests__/AppHTMLElement.test.tsx} +5 -5
- package/src/app/config/serviceContainer.ts +6 -0
- package/src/app/providers/Providers/Providers.tsx +18 -0
- package/src/{providers → app/providers}/QueryClientProvider/QueryClientProvider.tsx +3 -3
- 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 +148 -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 +39 -0
- package/src/demo/serviceIdentifiers.ts +6 -0
- package/src/demo/ui/components/DemoFooter/DemoFooter.tsx +17 -0
- package/src/{components/Footer → demo/ui/components/DemoFooter}/__tests__/Footer.test.tsx +2 -2
- package/src/{components/Footer → demo/ui/components/DemoFooter}/__tests__/__snapshots__/Footer.test.tsx.snap +3 -3
- package/src/demo/ui/components/DemoFooter/index.ts +1 -0
- package/src/demo/ui/components/DemoHeader/DemoHeader.tsx +25 -0
- package/src/{components/Header → demo/ui/components/DemoHeader}/__tests__/Header.test.tsx +2 -2
- package/src/demo/ui/components/DemoHeader/__tests__/__snapshots__/Header.test.tsx.snap +64 -0
- package/src/demo/ui/components/DemoHeader/index.ts +1 -0
- package/src/{ui/molecules/Navigation/Navigation.tsx → demo/ui/components/DemoNavigation/DemoNavigation.tsx} +4 -4
- package/src/demo/ui/components/DemoNavigation/index.ts +1 -0
- package/src/{components → demo/ui/components}/TextInput/TextInput.tsx +3 -3
- package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.tsx +7 -7
- 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/{ui/templates → demo/ui/layouts}/MainTemplate/MainTemplate.tsx +7 -7
- 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 +7 -7
- package/src/demo/ui/pages/ReactQueryDemo/ReactQueryDemo.module.scss +38 -0
- package/src/{pages/Demo → demo/ui/pages}/ReactQueryDemo/ReactQueryDemo.tsx +5 -4
- package/src/demo/ui/routing/demoAppRoutes.ts +6 -0
- package/src/demo/ui/routing/demoRoutes.tsx +31 -0
- package/src/main.ts +2 -2
- 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/{api → shared/infrastructure/http}/fetchApi.ts +1 -1
- package/src/shared/ui/README.md +50 -0
- package/src/shared/ui/hooks/useService/useService.ts +13 -0
- package/tsconfig.json +3 -1
- package/vitest.config.mts +5 -1
- package/src/api/README.md +0 -65
- package/src/api/demoApi.ts +0 -10
- package/src/assets/README.md +0 -54
- 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/index.ts +0 -1
- package/src/components/Header/Header.tsx +0 -17
- 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/config/README.md +0 -63
- package/src/config/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/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/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/utils/README.md +0 -70
- /package/src/{assets → app/assets}/css/index.ts +0 -0
- /package/src/{assets → app/assets}/css/mainBody.css +0 -0
- /package/src/{assets → app/assets}/css/reset.css +0 -0
- /package/src/{config → app/config}/queryClientConfig.ts +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}/Input/Input.tsx +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Input/index.ts +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Logo/Logo.module.css +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Logo/Logo.tsx +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Logo/__tests__/Logo.test.tsx +0 -0
- /package/src/{ui/atoms → demo/ui/components}/Logo/index.ts +0 -0
- /package/src/{ui/atoms → demo/ui/components}/NavLink/NavLink.tsx +0 -0
- /package/src/{ui/atoms → demo/ui/components}/NavLink/index.ts +0 -0
- /package/src/{components → demo/ui/components}/TextInput/index.ts +0 -0
- /package/src/{forms → demo/ui/forms}/ProfileForm/ProfileForm.module.scss +0 -0
- /package/src/{forms → demo/ui/forms}/ProfileForm/index.ts +0 -0
- /package/src/{ui/templates → demo/ui/layouts}/MainTemplate/index.ts +0 -0
- /package/src/{pages/Demo/Demo.module.scss → demo/ui/pages/DemoHome/DemoHome.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/{api → shared/infrastructure/http}/constant.ts +0 -0
- /package/src/{types/api.ts → shared/infrastructure/http/fetchApi.type.d.ts} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`Header Component > should render the Header component correctly 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="container"
|
|
7
|
+
>
|
|
8
|
+
<a
|
|
9
|
+
data-discover="true"
|
|
10
|
+
href="/"
|
|
11
|
+
>
|
|
12
|
+
<img
|
|
13
|
+
alt="logo"
|
|
14
|
+
class="logoStyle"
|
|
15
|
+
src="/src/demo/ui/assets/images/logo.png"
|
|
16
|
+
style="width: 50px;"
|
|
17
|
+
/>
|
|
18
|
+
</a>
|
|
19
|
+
<nav>
|
|
20
|
+
<ul
|
|
21
|
+
class="navList"
|
|
22
|
+
>
|
|
23
|
+
<li>
|
|
24
|
+
<a
|
|
25
|
+
aria-current="page"
|
|
26
|
+
class="a active"
|
|
27
|
+
data-discover="true"
|
|
28
|
+
href="/"
|
|
29
|
+
>
|
|
30
|
+
Overview
|
|
31
|
+
</a>
|
|
32
|
+
</li>
|
|
33
|
+
<li>
|
|
34
|
+
<a
|
|
35
|
+
class="a"
|
|
36
|
+
data-discover="true"
|
|
37
|
+
href="/react-query"
|
|
38
|
+
>
|
|
39
|
+
React Query
|
|
40
|
+
</a>
|
|
41
|
+
</li>
|
|
42
|
+
<li>
|
|
43
|
+
<a
|
|
44
|
+
class="a"
|
|
45
|
+
data-discover="true"
|
|
46
|
+
href="/react-hook-form"
|
|
47
|
+
>
|
|
48
|
+
React Hook Form
|
|
49
|
+
</a>
|
|
50
|
+
</li>
|
|
51
|
+
<li>
|
|
52
|
+
<a
|
|
53
|
+
class="a"
|
|
54
|
+
data-discover="true"
|
|
55
|
+
href="/dependency-injection"
|
|
56
|
+
>
|
|
57
|
+
Dependency Injection
|
|
58
|
+
</a>
|
|
59
|
+
</li>
|
|
60
|
+
</ul>
|
|
61
|
+
</nav>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DemoHeader } from './DemoHeader';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { NavLink } from '@
|
|
1
|
+
import { NavLink } from '@Demo/ui/components/NavLink/NavLink';
|
|
2
2
|
import { useId } from 'react';
|
|
3
|
-
import classes from './
|
|
3
|
+
import classes from './DemoNavigation.module.css';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type DemoNavigationProps = {
|
|
6
6
|
links: { label: string; url: string }[];
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const DemoNavigation = ({ links }: DemoNavigationProps) => {
|
|
10
10
|
const id = useId();
|
|
11
11
|
|
|
12
12
|
return (
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DemoNavigation } from './DemoNavigation';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Input } from '@
|
|
2
|
-
import { InputHTMLAttributes } from 'react';
|
|
3
|
-
import { FieldError, useFormContext } from 'react-hook-form';
|
|
1
|
+
import { Input } from '@Demo/ui/components/Input/Input';
|
|
2
|
+
import type { InputHTMLAttributes } from 'react';
|
|
3
|
+
import { type FieldError, useFormContext } from 'react-hook-form';
|
|
4
4
|
|
|
5
5
|
interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
6
6
|
name: string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { ProfileFormValues } from '@Demo/application/dtos/ProfileFormValues';
|
|
2
|
+
import { TextInput } from '@Demo/ui/components/TextInput/TextInput';
|
|
3
3
|
import { yupResolver } from '@hookform/resolvers/yup';
|
|
4
|
-
import { FormProvider, SubmitHandler, useForm } from 'react-hook-form';
|
|
4
|
+
import { FormProvider, type SubmitHandler, useForm } from 'react-hook-form';
|
|
5
5
|
import * as yup from 'yup';
|
|
6
6
|
import classes from './ProfileForm.module.scss';
|
|
7
7
|
|
|
8
8
|
type ProfileFormProps = {
|
|
9
|
-
onSubmit: SubmitHandler<
|
|
9
|
+
onSubmit: SubmitHandler<ProfileFormValues>;
|
|
10
10
|
resetForm: () => void;
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -17,15 +17,15 @@ const schema = yup.object().shape({
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
export const ProfileForm = ({ onSubmit, resetForm }: ProfileFormProps) => {
|
|
20
|
-
const methods = useForm<
|
|
20
|
+
const methods = useForm<ProfileFormValues>({
|
|
21
21
|
resolver: yupResolver(schema),
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<FormProvider {...methods}>
|
|
26
26
|
<form onSubmit={methods.handleSubmit(onSubmit)}>
|
|
27
|
-
<p>The form
|
|
28
|
-
<TextInput label="E-mail" type="email" name="mail" placeholder="
|
|
27
|
+
<p>The form has been submitted {methods.formState.submitCount} times.</p>
|
|
28
|
+
<TextInput label="E-mail" type="email" name="mail" placeholder="example@gmail.com" />
|
|
29
29
|
|
|
30
30
|
<TextInput label="First Name" name="firstName" placeholder="First Name" />
|
|
31
31
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GetDemoUsersUseCaseInterface } from '@Demo/application/use-cases/GetDemoUsersUseCaseInterface';
|
|
2
|
+
import { DEMO_SERVICES } from '@Demo/serviceIdentifiers';
|
|
3
|
+
import { useService } from '@Shared/ui/hooks/useService/useService';
|
|
4
|
+
import { useQuery } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
export const useDemoUsersQuery = () => {
|
|
7
|
+
const getDemoUsersUseCase = useService<GetDemoUsersUseCaseInterface>(DEMO_SERVICES.GetDemoUsersUseCase);
|
|
8
|
+
|
|
9
|
+
return useQuery({
|
|
10
|
+
queryKey: ['demo-users'],
|
|
11
|
+
queryFn: () => getDemoUsersUseCase.execute(),
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DemoFooter } from '@Demo/ui/components/DemoFooter/DemoFooter';
|
|
2
|
+
import { DemoHeader } from '@Demo/ui/components/DemoHeader/DemoHeader';
|
|
3
|
+
import { MainTemplate } from '@Demo/ui/layouts/MainTemplate/MainTemplate';
|
|
4
|
+
import { Outlet } from 'react-router';
|
|
5
|
+
|
|
6
|
+
export const DemoLayout = () => {
|
|
7
|
+
return (
|
|
8
|
+
<MainTemplate headerProps={{ leftComponents: <DemoHeader /> }} footerProps={{ footerContent: <DemoFooter /> }}>
|
|
9
|
+
<Outlet />
|
|
10
|
+
</MainTemplate>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.mainTemplateStyle {
|
|
2
|
+
display: flex;
|
|
3
|
+
min-height: 100%;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.header {
|
|
8
|
+
display: flex;
|
|
9
|
+
padding: 16px;
|
|
10
|
+
align-items: center;
|
|
11
|
+
justify-content: space-between;
|
|
12
|
+
gap: 16px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.main {
|
|
16
|
+
flex: 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.footer {
|
|
20
|
+
padding: 16px;
|
|
21
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Header } from '@Front/ui/organisms/Header';
|
|
3
|
-
import { MainContent } from '@Front/ui/organisms/MainContent';
|
|
4
|
-
import { PropsWithChildren, ReactNode } from 'react';
|
|
1
|
+
import type { PropsWithChildren, ReactNode } from 'react';
|
|
5
2
|
|
|
6
3
|
import classes from './MainTemplate.module.css';
|
|
7
4
|
|
|
@@ -18,9 +15,12 @@ type MainTemplateProps = {
|
|
|
18
15
|
export const MainTemplate = ({ headerProps, footerProps, children }: PropsWithChildren<MainTemplateProps>) => {
|
|
19
16
|
return (
|
|
20
17
|
<div className={classes.mainTemplateStyle}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
<header className={classes.header}>
|
|
19
|
+
{headerProps.leftComponents}
|
|
20
|
+
{headerProps.rightComponents}
|
|
21
|
+
</header>
|
|
22
|
+
<main className={classes.main}>{children}</main>
|
|
23
|
+
<footer className={classes.footer}>{footerProps.footerContent}</footer>
|
|
24
24
|
</div>
|
|
25
25
|
);
|
|
26
26
|
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import image from '@Demo/ui/assets/images/templatePlugins.png';
|
|
2
|
+
import { demoAppRoutes } from '@Demo/ui/routing/demoAppRoutes';
|
|
3
|
+
import { NavLink } from '@Demo/ui/components/NavLink/NavLink';
|
|
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 {
|
|
1
|
+
import type { ProfileFormValues } from '@Demo/application/dtos/ProfileFormValues';
|
|
2
|
+
import { ProfileForm } from '@Demo/ui/forms/ProfileForm/ProfileForm';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
|
|
5
|
-
import classes from './ReactHookFormDemo.module.
|
|
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 {
|
|
1
|
+
import { useDemoUsersQuery } from '@Demo/ui/hooks/useDemoUsersQuery/useDemoUsersQuery';
|
|
2
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 { demoAppRoutes } from '@Demo/ui/routing/demoAppRoutes';
|
|
2
|
+
import { DemoLayout } from '@Demo/ui/layouts/DemoLayout/DemoLayout';
|
|
3
|
+
import { DemoHome } from '@Demo/ui/pages/DemoHome/DemoHome';
|
|
4
|
+
import { DependencyInjectionDemo } from '@Demo/ui/pages/DependencyInjectionDemo/DependencyInjectionDemo';
|
|
5
|
+
import { ReactHookFormDemo } from '@Demo/ui/pages/ReactHookFormDemo/ReactHookFormDemo';
|
|
6
|
+
import { ReactQueryDemo } from '@Demo/ui/pages/ReactQueryDemo/ReactQueryDemo';
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import AppHTMLElement from '@App/AppHTMLElement';
|
|
2
2
|
|
|
3
3
|
if (import.meta.env.FRONT_MOCK_ENABLE === 'true') {
|
|
4
4
|
const { worker } = await import('@Mocks/browser');
|
|
@@ -7,4 +7,4 @@ if (import.meta.env.FRONT_MOCK_ENABLE === 'true') {
|
|
|
7
7
|
});
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
customElements.define('app-react',
|
|
10
|
+
customElements.define('app-react', AppHTMLElement);
|
|
@@ -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.
|