Compare commits

..

No commits in common. "4ee7d4b8e33d7c61299c53b18b3a1801b5269475" and "7d9a6e77bd3d47f4551c63ae5b7bc7d6bb7a2d6d" have entirely different histories.

29 changed files with 60 additions and 120 deletions

View File

@ -7,9 +7,6 @@ import {
/** /**
* Controllers are bridge between feature layer and application layer. * Controllers are bridge between feature layer and application layer.
* They decide, feature layer will be cached or not, where to run in client or server
* Or connect multiple usecases and run them, handle their failure, hydrate and store data in
* client state managements.
*/ */
export default function cardController(props: { export default function cardController(props: {
type: "invoices" | "customers" | "pending" | "collected"; type: "invoices" | "customers" | "pending" | "collected";

View File

@ -1,4 +1,4 @@
import cardController from "@/app/[lang]/dashboard/controller/card.controller"; import cardController from "@/app/[lang]/dashboard/components/server/card/card.controller";
export function Card({ export function Card({
title, title,

View File

@ -3,9 +3,6 @@ import { connection } from "next/server";
/** /**
* Controllers are bridge between feature layer and application layer. * Controllers are bridge between feature layer and application layer.
* They decide, feature layer will be cached or not, where to run in client or server
* Or connect multiple usecases and run them, handle their failure, hydrate and store data in
* client state managements.
*/ */
export default function cardsController() { export default function cardsController() {
connection(); connection();

View File

@ -1,5 +1,5 @@
import { Card } from "@/app/[lang]/dashboard/components/server/card"; import { Card } from "@/app/[lang]/dashboard/components/server/card/card";
import cardsController from "@/app/[lang]/dashboard/controller/cards.controller"; import cardsController from "@/app/[lang]/dashboard/components/server/cards/cards.controller";
export default async function CardWrapper() { export default async function CardWrapper() {
const { customersNumber, invoicesNumber, invoicesSummary } = const { customersNumber, invoicesNumber, invoicesSummary } =

View File

@ -3,9 +3,6 @@ import { connection } from "next/server";
/** /**
* Controllers are bridge between feature layer and application layer. * Controllers are bridge between feature layer and application layer.
* They decide, feature layer will be cached or not, where to run in client or server
* Or connect multiple usecases and run them, handle their failure, hydrate and store data in
* client state managements.
*/ */
export default function latestInvoicesController() { export default function latestInvoicesController() {
connection(); connection();

View File

@ -1,5 +1,5 @@
import CreateRandomInvoiceContainer from "@/app/[lang]/dashboard/components/client/create-random-invoice/create-random-invoice"; import CreateRandomInvoiceContainer from "@/app/[lang]/dashboard/components/client/create-random-invoice/create-random-invoice";
import latestInvoicesController from "@/app/[lang]/dashboard/controller/latest-invoices.controller"; import latestInvoicesController from "@/app/[lang]/dashboard/components/server/latest-invoices/latest-invoices.controller";
import { ArrowPathIcon } from "@heroicons/react/24/outline"; import { ArrowPathIcon } from "@heroicons/react/24/outline";
import clsx from "clsx"; import clsx from "clsx";
import { isLeft } from "fp-ts/lib/Either"; import { isLeft } from "fp-ts/lib/Either";

View File

@ -3,9 +3,6 @@ import fetchRevenuesUsecase from "@/feature/core/revenue/domain/usecase/fetch-re
/** /**
* Controllers are bridge between feature layer and application layer. * Controllers are bridge between feature layer and application layer.
* They decide, feature layer will be cached or not, where to run in client or server
* Or connect multiple usecases and run them, handle their failure, hydrate and store data in
* client state managements.
*/ */
export default async function revenueChartController() { export default async function revenueChartController() {
const revenue = await fetchRevenuesUsecase(); const revenue = await fetchRevenuesUsecase();

View File

@ -1,4 +1,4 @@
import revenueChartController from "@/app/[lang]/dashboard/controller/revenue-chart.controller"; import revenueChartController from "@/app/[lang]/dashboard/components/server/revenue-chart/revenue-chart.controller";
import { CalendarIcon } from "@heroicons/react/24/outline"; import { CalendarIcon } from "@heroicons/react/24/outline";
export default async function RevenueChart() { export default async function RevenueChart() {

View File

@ -1,27 +0,0 @@
"use server";
import { ApiEither } from "@/feature/common/data/api-task";
import serverDi from "@/feature/common/server.di";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
import {
CreateInvoiceUsecase,
createInvoiceUsecaseKey,
} from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase";
import { invoiceModuleKey } from "@/feature/core/invoice/invoice.module-key";
import { connection } from "next/server";
/**
* Controllers are bridge between feature layer and application layer.
* They decide, feature layer will be cached or not, where to run in client or server
* Or connect multiple usecases and run them, handle their failure, hydrate and store data in
* client state managements.
*/
export default async function createInvoiceController(
params: InvoiceParam,
): Promise<ApiEither<string>> {
connection();
const usecase = serverDi(invoiceModuleKey).resolve<CreateInvoiceUsecase>(
createInvoiceUsecaseKey,
);
return usecase(params);
}

View File

@ -1,4 +1,4 @@
import DashboardSkeleton from "@/app/[lang]/dashboard/components/server/skeletons"; import DashboardSkeleton from "@/app/[lang]/dashboard/components/server/skeletons/skeletons";
export default function Loading() { export default function Loading() {
return <DashboardSkeleton />; return <DashboardSkeleton />;

View File

@ -1,5 +1,6 @@
import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm"; import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
import di from "@/bootstrap/di/init-di"; import di from "@/bootstrap/di/init-di";
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice.usecase";
/** /**
* Each page can have its own di to connect all vms, usecases or controllers * Each page can have its own di to connect all vms, usecases or controllers
@ -7,6 +8,9 @@ import di from "@/bootstrap/di/init-di";
export default function dashboardAppModule() { export default function dashboardAppModule() {
const dashboardDi = di.createChildContainer(); const dashboardDi = di.createChildContainer();
dashboardDi.register(createInvoiceUsecase.name, {
useValue: createInvoiceUsecase,
});
dashboardDi.register( dashboardDi.register(
CreateRandomInvoiceButtonVM, CreateRandomInvoiceButtonVM,
CreateRandomInvoiceButtonVM, CreateRandomInvoiceButtonVM,

View File

@ -1,13 +1,13 @@
import { import {
LatestInvoicesSkeleton, LatestInvoicesSkeleton,
RevenueChartSkeleton, RevenueChartSkeleton,
} from "@/app/[lang]/dashboard/components/server/skeletons"; } from "@/app/[lang]/dashboard/components/server/skeletons/skeletons";
import CardWrapper from "@/app/[lang]/dashboard/components/server/cards"; import CardWrapper from "@/app/[lang]/dashboard/components/server/cards/cards";
import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart"; import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices/latest-invoices";
import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart/revenue-chart";
import { Suspense } from "react"; import { Suspense } from "react";
import { getServerTranslation, LANGS } from "@/bootstrap/i18n/i18n"; import { getServerTranslation, LANGS } from "@/bootstrap/i18n/i18n";
import langKey from "@/bootstrap/i18n/dictionaries/lang-key"; import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices";
export default async function Dashboard(props: { export default async function Dashboard(props: {
params: Promise<{ lang: LANGS }>; params: Promise<{ lang: LANGS }>;

View File

@ -1,11 +1,10 @@
import createInvoiceController from "@/app/[lang]/dashboard/controller/create-invoice.controller";
import ButtonVm from "@/app/components/button/button.i-vm"; import ButtonVm from "@/app/components/button/button.i-vm";
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action"; import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle"; import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
import BaseVM from "@/bootstrap/helpers/vm/base-vm"; import BaseVM from "@/bootstrap/helpers/vm/base-vm";
import langKey from "@/bootstrap/i18n/dictionaries/lang-key"; import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param"; import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
import { CreateInvoiceUsecase } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase"; import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice.usecase";
import { faker } from "@faker-js/faker"; import { faker } from "@faker-js/faker";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -16,11 +15,11 @@ import { useTranslation } from "react-i18next";
* in this layer. * in this layer.
*/ */
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> { export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
private createInvoice: CreateInvoiceUsecase; private createInvoice: typeof createInvoiceUsecase;
constructor() { constructor() {
super(); super();
this.createInvoice = createInvoiceController; this.createInvoice = this.di.resolve(createInvoiceUsecase.name);
} }
useVM(): ButtonVm { useVM(): ButtonVm {

View File

@ -20,7 +20,7 @@ export default async function layout(
) { ) {
const { params, children } = props; const { params, children } = props;
const { lang } = await params; const { lang } = await params;
await getI18n({ lng: lang }); const { resources } = await getI18n({ lng: lang });
return ( return (
<html lang={lang} suppressHydrationWarning> <html lang={lang} suppressHydrationWarning>
<body <body
@ -32,7 +32,9 @@ export default async function layout(
enableSystem enableSystem
disableTransitionOnChange disableTransitionOnChange
> >
<TranslationsProvider lng={lang}>{children}</TranslationsProvider> <TranslationsProvider lng={lang} resources={resources}>
{children}
</TranslationsProvider>
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>

View File

@ -4,7 +4,7 @@ import { DiContext, useDI } from "@/bootstrap/di/di-context";
import mockedModuleDi from "@/bootstrap/di/mocked-module-di"; import mockedModuleDi from "@/bootstrap/di/mocked-module-di";
import Story from "@/bootstrap/helpers/view/storybook-base-template-type"; import Story from "@/bootstrap/helpers/view/storybook-base-template-type";
import getArgVM from "@/bootstrap/helpers/view/storybook-with-arg-vm"; import getArgVM from "@/bootstrap/helpers/view/storybook-with-arg-vm";
import { createInvoiceUsecaseKey } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase"; import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice.usecase";
import type { Meta } from "@storybook/react"; import type { Meta } from "@storybook/react";
import { useRef } from "react"; import { useRef } from "react";
@ -36,32 +36,30 @@ export const Primary: Story = {
export const WithVM: Story = { export const WithVM: Story = {
decorators: [ decorators: [
(Story) => { (Story) => {
const di = useRef( const di = mockedModuleDi([
mockedModuleDi([
{ {
token: CreateRandomInvoiceButtonVM, token: CreateRandomInvoiceButtonVM,
provider: CreateRandomInvoiceButtonVM, provider: CreateRandomInvoiceButtonVM,
}, },
{ {
token: createInvoiceUsecaseKey, token: createInvoiceUsecase.name,
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-console // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-console
provider: (args: any) => console.log("clicked", args), provider: (args: any) => console.log("clicked", args),
}, },
]), ]);
); return <Story di={di} />;
return (
<DiContext.Provider value={di.current}>
<Story di={di.current} />
</DiContext.Provider>
);
}, },
], ],
render: () => { render: (_, globalProps) => {
function Child() { function Child() {
const di = useDI(); const di = useDI();
const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM)); const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM));
return <Button vm={vm.current} memoizedByVM={false} />; return <Button vm={vm.current} memoizedByVM={false} />;
} }
return <Child />; return (
<DiContext.Provider value={globalProps.di}>
<Child />
</DiContext.Provider>
);
}, },
}; };

View File

@ -1,24 +1,17 @@
"use client"; "use client";
import { I18nextProvider } from "react-i18next"; import { I18nextProvider } from "react-i18next";
import { getI18n, LANGS } from "@/bootstrap/i18n/i18n"; import { i18nInstance, getI18n, LANGS } from "@/bootstrap/i18n/i18n";
import { PropsWithChildren, useEffect, useState } from "react"; import { Resource } from "i18next";
import { i18n } from "i18next"; import { PropsWithChildren } from "react";
import storeLang from "@/bootstrap/i18n/store-lang-action";
export default function TranslationsProvider({ export default function TranslationsProvider({
children, children,
lng, lng,
}: PropsWithChildren & { lng: LANGS }) { resources,
const [i18n, setI18n] = useState<i18n>(); }: PropsWithChildren & { lng: LANGS; resources: Resource }) {
if (!resources) return children;
getI18n({ lng, resources });
useEffect(() => { return <I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider>;
(async () => {
storeLang(lng);
setI18n((await getI18n({ lng })).i18n);
})();
}, [lng]);
if (!i18n) return null;
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
} }

View File

@ -1,9 +0,0 @@
"use server";
import { LANGS } from "@/bootstrap/i18n/i18n";
import { cookieName } from "@/bootstrap/i18n/settings";
import { cookies } from "next/headers";
export default async function storeLang(lng: LANGS) {
(await cookies()).set(cookieName, lng);
}

View File

@ -1,3 +1,4 @@
import "server-only";
import { ApiEither } from "@/feature/common/data/api-task"; import { ApiEither } from "@/feature/common/data/api-task";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice.entity"; import CustomerInvoice from "@/feature/core/customer-invoice/domain/entity/customer-invoice.entity";

View File

@ -1,3 +1,4 @@
import "server-only";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import { customerKey } from "@/feature/core/customer/customer-key"; import { customerKey } from "@/feature/core/customer/customer-key";
import CustomerRepo, { import CustomerRepo, {

View File

@ -1,3 +1,4 @@
import "server-only";
import { ApiEither } from "@/feature/common/data/api-task"; import { ApiEither } from "@/feature/common/data/api-task";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import { customerKey } from "@/feature/core/customer/customer-key"; import { customerKey } from "@/feature/core/customer/customer-key";

View File

@ -1,16 +1,11 @@
import di from "@/bootstrap/di/init-di"; import di from "@/bootstrap/di/init-di";
import invoiceDbRepo from "@/feature/core/invoice/data/repo/invoice-db.repo"; import invoiceDbRepo from "@/feature/core/invoice/data/repo/invoice-db.repo";
import { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice.i-repo"; import { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice.i-repo";
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice-impl.usecase";
import { createInvoiceUsecaseKey } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase";
import { DependencyContainer } from "tsyringe"; import { DependencyContainer } from "tsyringe";
export default function getInvoiceDi(): DependencyContainer { export default function getInvoiceDi(): DependencyContainer {
const invoiceDi = di.createChildContainer(); const invoiceDi = di.createChildContainer();
invoiceDi.register(invoiceRepoKey, invoiceDbRepo); invoiceDi.register(invoiceRepoKey, invoiceDbRepo);
invoiceDi.register(createInvoiceUsecaseKey, {
useValue: createInvoiceUsecase,
});
return invoiceDi; return invoiceDi;
} }

View File

@ -1,4 +1,3 @@
import "server-only";
import { sql } from "@/bootstrap/boundaries/db/db"; import { sql } from "@/bootstrap/boundaries/db/db";
import ApiTask from "@/feature/common/data/api-task"; import ApiTask from "@/feature/common/data/api-task";
import { failureOr } from "@/feature/common/failures/failure-helpers"; import { failureOr } from "@/feature/common/failures/failure-helpers";

View File

@ -1,3 +1,5 @@
"use server";
import { ApiEither } from "@/feature/common/data/api-task"; import { ApiEither } from "@/feature/common/data/api-task";
import ParamsFailure from "@/feature/common/failures/params.failure"; import ParamsFailure from "@/feature/common/failures/params.failure";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
@ -9,13 +11,12 @@ import {
invoiceSchema, invoiceSchema,
} from "@/feature/core/invoice/domain/param/invoice.param"; } from "@/feature/core/invoice/domain/param/invoice.param";
import { invoiceModuleKey } from "@/feature/core/invoice/invoice.module-key"; import { invoiceModuleKey } from "@/feature/core/invoice/invoice.module-key";
import { CreateInvoiceUsecase } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase";
import { pipe } from "fp-ts/lib/function"; import { pipe } from "fp-ts/lib/function";
import { chain, fromNullable, left, map, right } from "fp-ts/lib/TaskEither"; import { chain, fromNullable, left, map, right } from "fp-ts/lib/TaskEither";
const createInvoiceUsecase: CreateInvoiceUsecase = async ( export default async function createInvoiceUsecase(
params: InvoiceParam, params: InvoiceParam,
): Promise<ApiEither<string>> => { ): Promise<ApiEither<string>> {
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey); const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey);
return pipe( return pipe(
@ -28,6 +29,4 @@ const createInvoiceUsecase: CreateInvoiceUsecase = async (
}), }),
chain((params) => repo.createInvoice(params)), chain((params) => repo.createInvoice(params)),
)(); )();
}; }
export default createInvoiceUsecase;

View File

@ -1,8 +0,0 @@
import { ApiEither } from "@/feature/common/data/api-task";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
export type CreateInvoiceUsecase = (
param: InvoiceParam,
) => Promise<ApiEither<string>>;
export const createInvoiceUsecaseKey = "createInvoiceUsecaseKey";

View File

@ -1,3 +1,4 @@
import "server-only";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import InvoiceRepo, { import InvoiceRepo, {
invoiceRepoKey, invoiceRepoKey,

View File

@ -1,3 +1,4 @@
import "server-only";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import InvoiceRepo, { import InvoiceRepo, {
invoiceRepoKey, invoiceRepoKey,

View File

@ -1,3 +1,4 @@
import "server-only";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import Revenue from "@/feature/core/revenue/domain/entity/revenue.entity"; import Revenue from "@/feature/core/revenue/domain/entity/revenue.entity";
import RevenueRepo, { import RevenueRepo, {

View File

@ -1,3 +1,4 @@
import "server-only";
import serverDi from "@/feature/common/server.di"; import serverDi from "@/feature/common/server.di";
import fetchCustomersAmountUsecase from "@/feature/core/customer/domain/usecase/fetch-customers-amount-usecase"; import fetchCustomersAmountUsecase from "@/feature/core/customer/domain/usecase/fetch-customers-amount-usecase";
import fetchAllInvoicesAmountUsecase from "@/feature/core/invoice/domain/usecase/fetch-all-invoices-amount.usecase"; import fetchAllInvoicesAmountUsecase from "@/feature/core/invoice/domain/usecase/fetch-all-invoices-amount.usecase";