diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index dc1dbaa..bfa17ed 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -1,13 +1,14 @@
-import React, { useRef } from "react";
+import React from "react";
import { themes } from '@storybook/theming';
import { ThemeProvider } from "../src/app/[lang]/dashboard/components/client/theme-provider/theme-provider";
-import { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
-import { initI18next, LANGS } from "../src/bootstrap/i18n/i18n"
+import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
+import { getI18n, LANGS } from "../src/bootstrap/i18n/i18n"
import { addons } from '@storybook/preview-api';
import { i18n } from "i18next";
import { I18nextProvider } from "react-i18next";
const channel = addons.getChannel();
import "../src/app/globals.css"
+
/**
*
* This function will expand the object with nested properties
@@ -69,7 +70,7 @@ const preview = {
React.useEffect(() => {
(async () => {
- setI18n((await initI18next({ lng: locale })).i18n);
+ setI18n((await getI18n({ lng: locale })).i18n);
})()
}, [])
diff --git a/src/app/[lang]/dashboard/components/client/create-random-invoice/create-random-invoice.tsx b/src/app/[lang]/dashboard/components/client/create-random-invoice/create-random-invoice.tsx
deleted file mode 100644
index 8db5bf3..0000000
--- a/src/app/[lang]/dashboard/components/client/create-random-invoice/create-random-invoice.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-"use client";
-
-import Button from "@/app/components/button/button";
-import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
-import { useDI } from "@/bootstrap/di/di-context";
-import { useRef } from "react";
-
-/**
- * From a parent component Vm and view will be connected together.
- */
-export default function CreateRandomInvoiceContainer() {
- const di = useDI();
- const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM));
-
- return ;
-}
diff --git a/src/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm.ts b/src/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm.ts
index 9ddf695..50c4620 100644
--- a/src/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm.ts
+++ b/src/app/[lang]/dashboard/components/client/nav-links/nav-link.personal-vm.ts
@@ -1,6 +1,4 @@
-import { DocumentIcon } from "@/app/components/icons/document";
import HomeIcon from "@/app/components/icons/home";
-import { UserIcon } from "@/app/components/icons/user";
import { usePathname } from "next/navigation";
import { useRef } from "react";
@@ -23,15 +21,9 @@ export default function navLinkPersonalVM() {
// Depending on the size of the application, this would be stored in a database.
const links = useRef([
{ name: "Home", href: "/dashboard", icon: HomeIcon },
- {
- name: "Invoices",
- href: "/dashboard/invoices",
- icon: DocumentIcon,
- },
- { name: "Customers", href: "/dashboard/customers", icon: UserIcon },
]).current;
return {
links,
- isLinkActive: (link: LinkItem) => pathname === link.href,
+ isLinkActive: (link: LinkItem) => pathname.includes(link.href),
};
}
diff --git a/src/app/[lang]/dashboard/components/server/latest-invoices.tsx b/src/app/[lang]/dashboard/components/server/latest-invoices.tsx
index 4d54d2b..9a0583e 100644
--- a/src/app/[lang]/dashboard/components/server/latest-invoices.tsx
+++ b/src/app/[lang]/dashboard/components/server/latest-invoices.tsx
@@ -1,5 +1,6 @@
-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 CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
+import Button from "@/app/components/button/button";
import { ArrowPathIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { isLeft } from "fp-ts/lib/Either";
@@ -39,6 +40,7 @@ export default async function LatestInvoices() {
));
+
return (
Latest Invoices
@@ -48,7 +50,7 @@ export default async function LatestInvoices() {
Updated just now
-
+
);
diff --git a/src/app/[lang]/dashboard/module/dashboard.app-module.ts b/src/app/[lang]/dashboard/module/dashboard.app-module.ts
index b3ec20f..d87af96 100644
--- a/src/app/[lang]/dashboard/module/dashboard.app-module.ts
+++ b/src/app/[lang]/dashboard/module/dashboard.app-module.ts
@@ -1,3 +1,4 @@
+import createInvoiceController from "@/app/[lang]/dashboard/controller/create-invoice.controller";
import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
import di from "@/bootstrap/di/init-di";
@@ -8,8 +9,12 @@ export default function dashboardAppModule() {
const dashboardDi = di.createChildContainer();
dashboardDi.register(
- CreateRandomInvoiceButtonVM,
+ CreateRandomInvoiceButtonVM.name,
CreateRandomInvoiceButtonVM,
);
+
+ dashboardDi.register(createInvoiceController.name, {
+ useValue: createInvoiceController,
+ });
return dashboardDi;
}
diff --git a/src/app/[lang]/dashboard/page.tsx b/src/app/[lang]/dashboard/page.tsx
index ee88963..792fb14 100644
--- a/src/app/[lang]/dashboard/page.tsx
+++ b/src/app/[lang]/dashboard/page.tsx
@@ -2,27 +2,13 @@ import {
LatestInvoicesSkeleton,
RevenueChartSkeleton,
} from "@/app/[lang]/dashboard/components/server/skeletons";
-import CardWrapper from "@/app/[lang]/dashboard/components/server/cards";
import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart";
import { Suspense } from "react";
-import { getServerTranslation, LANGS } from "@/bootstrap/i18n/i18n";
-import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices";
-export default async function Dashboard(props: {
- params: Promise<{ lang: LANGS }>;
-}) {
- const { params } = props;
- const { lang } = await params;
- const { t } = await getServerTranslation(lang);
+export default async function Dashboard() {
return (
-
- {t(langKey.global.dashboard)}
-
-
-
-
}>
diff --git a/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts b/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts
index 246508d..ccd0622 100644
--- a/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts
+++ b/src/app/[lang]/dashboard/vm/create-random-invoice-button-vm.ts
@@ -1,3 +1,5 @@
+"use client";
+
import createInvoiceController from "@/app/[lang]/dashboard/controller/create-invoice.controller";
import ButtonVm from "@/app/components/button/button.i-vm";
import { useServerAction } from "@/bootstrap/helpers/hooks/use-server-action";
@@ -5,7 +7,6 @@ import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice.param";
-import { CreateInvoiceUsecase } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase";
import { faker } from "@faker-js/faker";
import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
@@ -16,11 +17,11 @@ import { useTranslation } from "react-i18next";
* in this layer.
*/
export default class CreateRandomInvoiceButtonVM extends BaseVM {
- private createInvoice: CreateInvoiceUsecase;
+ private createInvoice: typeof createInvoiceController;
constructor() {
super();
- this.createInvoice = createInvoiceController;
+ this.createInvoice = this.di.resolve(createInvoiceController.name);
}
useVM(): ButtonVm {
diff --git a/src/app/[lang]/page.tsx b/src/app/[lang]/page.tsx
index 1e9c77c..06b634c 100644
--- a/src/app/[lang]/page.tsx
+++ b/src/app/[lang]/page.tsx
@@ -1,4 +1,13 @@
-export default function Home() {
+import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
+import { getServerTranslation, LANGS } from "@/bootstrap/i18n/i18n";
+import Link from "next/link";
+
+export default async function Home(props: {
+ params: Promise<{ lang: LANGS }>;
+}) {
+ const { params } = props;
+ const { lang } = await params;
+ const { t } = await getServerTranslation(lang);
return (
@@ -8,6 +17,12 @@ export default function Home() {
Welcome to Acme. This is the example for the ,
brought to you by Vercel.
+
+ {t(langKey.global.dashboard)}
+
diff --git a/src/app/components/button/stories/Button.stories.tsx b/src/app/components/button/stories/Button.stories.tsx
index a68137b..06bf104 100644
--- a/src/app/components/button/stories/Button.stories.tsx
+++ b/src/app/components/button/stories/Button.stories.tsx
@@ -1,12 +1,7 @@
-import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
import Button from "@/app/components/button/button";
-import { DiContext, useDI } from "@/bootstrap/di/di-context";
-import mockedModuleDi from "@/bootstrap/di/mocked-module-di";
import Story from "@/bootstrap/helpers/view/storybook-base-template-type";
import getArgVM from "@/bootstrap/helpers/view/storybook-with-arg-vm";
-import { createInvoiceUsecaseKey } from "@/feature/core/invoice/domain/usecase/create-invoice/create-invoice.usecase";
import type { Meta } from "@storybook/react";
-import { useRef } from "react";
const meta: Meta = {
title: "general/Button",
@@ -32,36 +27,3 @@ export const Primary: Story = {
return ;
},
};
-
-export const WithVM: Story = {
- decorators: [
- (Story) => {
- const di = useRef(
- mockedModuleDi([
- {
- token: CreateRandomInvoiceButtonVM,
- provider: CreateRandomInvoiceButtonVM,
- },
- {
- token: createInvoiceUsecaseKey,
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, no-console
- provider: (args: any) => console.log("clicked", args),
- },
- ]),
- );
- return (
-
-
-
- );
- },
- ],
- render: () => {
- function Child() {
- const di = useDI();
- const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM));
- return ;
- }
- return ;
- },
-};
diff --git a/src/bootstrap/helpers/view/base-view.tsx b/src/bootstrap/helpers/view/base-view.tsx
index 914c9fc..e68a0fe 100644
--- a/src/bootstrap/helpers/view/base-view.tsx
+++ b/src/bootstrap/helpers/view/base-view.tsx
@@ -1,11 +1,14 @@
-"use client";
-
-// import gdi from "@/bootstrap/di/init-di";
+/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable react/display-name */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/jsx-props-no-spreading */
+
+"use client";
+
+import { useDI } from "@/bootstrap/di/di-context";
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
import { Component, ReactNode, FC, PropsWithChildren, memo } from "react";
+import { InjectionToken } from "tsyringe";
/* -------------------------------------------------------------------------- */
/* Connector Component */
@@ -23,7 +26,6 @@ interface IVvmConnector extends PropsWithChildren {
const VvmConnector = memo(
(props: IVvmConnector) => {
const { View, Vm, restProps, children } = props;
-
const vm = Vm.useVM();
const allProps = {
@@ -45,8 +47,7 @@ const VvmConnector = memo(
type IVMParent = Record;
type IPropParent = Record | undefined;
-type BaseProps = {
- vm: IBaseVM;
+type BaseProps = {
restProps?: PROPS;
/**
* By default it's true.
@@ -57,6 +58,24 @@ type BaseProps = {
children?: ReactNode;
};
+type BasePropsWithVM<
+ IVM extends IVMParent,
+ PROPS extends IPropParent = undefined,
+> = BaseProps & {
+ /**
+ * Directly instantiated vm
+ */
+ vm: IBaseVM;
+};
+
+type BasePropsWithVMKey =
+ BaseProps & {
+ /**
+ * TSyringe key for vm to be injected
+ */
+ vmKey: InjectionToken;
+ };
+
export type BuildProps<
IVM extends IVMParent,
PROPS extends IPropParent = undefined,
@@ -66,6 +85,11 @@ export type BuildProps<
children?: ReactNode;
};
+export type ViewProps<
+ IVM extends IVMParent,
+ PROPS extends IPropParent = undefined,
+> = BasePropsWithVM | BasePropsWithVMKey;
+
/**
* Base view is base component for all views in mvvm architecture which gets
* vm as props and connect it to the view and memoize the component by default
@@ -74,7 +98,23 @@ export type BuildProps<
export default abstract class BaseView<
IVM extends IVMParent,
PROPS extends IPropParent = undefined,
-> extends Component> {
+> extends Component> {
+ private vm: IBaseVM | undefined;
+
+ constructor(props: ViewProps) {
+ super(props);
+ this.vm = this.initVm;
+ }
+
+ private get initVm() {
+ if (Object.hasOwn(this.props, "vmKey")) {
+ const { vmKey } = this.props as BasePropsWithVMKey;
+ const di = useDI();
+ return di.resolve(vmKey) as IBaseVM;
+ }
+ return (this.props as BasePropsWithVM).vm;
+ }
+
protected get componentName() {
return this.constructor.name;
}
@@ -82,9 +122,16 @@ export default abstract class BaseView<
protected abstract Build(props: BuildProps): ReactNode;
render(): ReactNode {
- const { vm, restProps, memoizedByVM, children, ...rest } = this.props;
-
+ const { restProps, memoizedByVM, children, ...rest } = this.props;
VvmConnector.displayName = this.componentName;
+ const vm = memoizedByVM ? this.vm : this.initVm;
+ if (!vm) {
+ const isVmKey = Object.hasOwn(this.props, "vmKey");
+ const message = isVmKey
+ ? "vm is not defined, check your di configuration"
+ : "pass correct vm";
+ throw new Error(`Vm is not defined${message}`);
+ }
return (
);
}
- /* -------------------------------------------------------------------------- */
}