refactor: usage of mvvm and storybook
This commit is contained in:
parent
4ee7d4b8e3
commit
75b1671982
@ -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);
|
||||
})()
|
||||
}, [])
|
||||
|
||||
|
@ -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 <Button vm={vm.current} />;
|
||||
}
|
@ -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() {
|
||||
</p>
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col md:col-span-4">
|
||||
<h2 className="mb-4 text-xl md:text-2xl">Latest Invoices</h2>
|
||||
@ -48,7 +50,7 @@ export default async function LatestInvoices() {
|
||||
<ArrowPathIcon className="h-5 w-5 text-gray-500" />
|
||||
<h3 className="ml-2 text-sm text-gray-500 ">Updated just now</h3>
|
||||
</div>
|
||||
<CreateRandomInvoiceContainer />
|
||||
<Button vmKey={CreateRandomInvoiceButtonVM} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,13 @@ import { useTranslation } from "react-i18next";
|
||||
* in this layer.
|
||||
*/
|
||||
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
|
||||
private createInvoice: CreateInvoiceUsecase;
|
||||
private createInvoice: typeof createInvoiceController;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.createInvoice = createInvoiceController;
|
||||
this.createInvoice = this.di.resolve(
|
||||
createInvoiceController.prototype.name,
|
||||
);
|
||||
}
|
||||
|
||||
useVM(): ButtonVm {
|
||||
|
@ -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 <Button vm={vm} memoizedByVM={false} />;
|
||||
},
|
||||
};
|
||||
|
||||
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 (
|
||||
<DiContext.Provider value={di.current}>
|
||||
<Story di={di.current} />
|
||||
</DiContext.Provider>
|
||||
);
|
||||
},
|
||||
],
|
||||
render: () => {
|
||||
function Child() {
|
||||
const di = useDI();
|
||||
const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM));
|
||||
return <Button vm={vm.current} memoizedByVM={false} />;
|
||||
}
|
||||
return <Child />;
|
||||
},
|
||||
};
|
||||
|
@ -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<IVM, PROPS> extends PropsWithChildren {
|
||||
const VvmConnector = memo(
|
||||
<IVM, PROPS>(props: IVvmConnector<IVM, PROPS>) => {
|
||||
const { View, Vm, restProps, children } = props;
|
||||
|
||||
const vm = Vm.useVM();
|
||||
|
||||
const allProps = {
|
||||
@ -45,8 +47,7 @@ const VvmConnector = memo(
|
||||
type IVMParent = Record<string, any>;
|
||||
type IPropParent = Record<string, any> | undefined;
|
||||
|
||||
type BaseProps<IVM extends IVMParent, PROPS extends IPropParent = undefined> = {
|
||||
vm: IBaseVM<IVM>;
|
||||
type BaseProps<PROPS extends IPropParent = undefined> = {
|
||||
restProps?: PROPS;
|
||||
/**
|
||||
* By default it's true.
|
||||
@ -57,6 +58,24 @@ type BaseProps<IVM extends IVMParent, PROPS extends IPropParent = undefined> = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
type BasePropsWithVM<
|
||||
IVM extends IVMParent,
|
||||
PROPS extends IPropParent = undefined,
|
||||
> = BaseProps<PROPS> & {
|
||||
/**
|
||||
* Directly instantiated vm
|
||||
*/
|
||||
vm: IBaseVM<IVM>;
|
||||
};
|
||||
|
||||
type BasePropsWithVMKey<PROPS extends IPropParent = undefined> =
|
||||
BaseProps<PROPS> & {
|
||||
/**
|
||||
* 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<IVM, PROPS> | BasePropsWithVMKey<PROPS>;
|
||||
|
||||
/**
|
||||
* 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<BaseProps<IVM, PROPS>> {
|
||||
> extends Component<ViewProps<IVM, PROPS>> {
|
||||
private vm: IBaseVM<IVM> | undefined;
|
||||
|
||||
constructor(props: ViewProps<IVM, PROPS>) {
|
||||
super(props);
|
||||
this.vm = this.initVm;
|
||||
}
|
||||
|
||||
private get initVm() {
|
||||
if (Object.hasOwn(this.props, "vmKey")) {
|
||||
const { vmKey } = this.props as BasePropsWithVMKey<PROPS>;
|
||||
const di = useDI();
|
||||
return di.resolve(vmKey) as IBaseVM<IVM>;
|
||||
}
|
||||
return (this.props as BasePropsWithVM<IVM, PROPS>).vm;
|
||||
}
|
||||
|
||||
protected get componentName() {
|
||||
return this.constructor.name;
|
||||
}
|
||||
@ -82,9 +122,16 @@ export default abstract class BaseView<
|
||||
protected abstract Build(props: BuildProps<IVM, PROPS>): 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 (
|
||||
<VvmConnector
|
||||
@ -97,5 +144,4 @@ export default abstract class BaseView<
|
||||
</VvmConnector>
|
||||
);
|
||||
}
|
||||
/* -------------------------------------------------------------------------- */
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user