feature/research-di #1

Merged
behnam merged 37 commits from feature/research-di into develop 2024-11-21 15:50:19 +00:00
6 changed files with 56 additions and 12 deletions
Showing only changes of commit 324e7314e3 - Show all commits

View File

@ -1,6 +1,7 @@
export default interface ButtonVm { export default interface ButtonVm {
props: { props: {
title: string title: string;
isDisable: boolean;
} }
onClick(): void onClick(): void
} }

View File

@ -11,7 +11,7 @@ export default class Button extends BaseView<ButtonVm> {
protected Build(props: BuildProps<ButtonVm>): ReactNode { protected Build(props: BuildProps<ButtonVm>): ReactNode {
const {vm} = props const {vm} = props
return <ButtonUi onClick={vm.onClick} >{vm.props.title}</ButtonUi> return <ButtonUi disabled={vm.props.isDisable} onClick={vm.onClick} >{vm.props.title}</ButtonUi>
} }
} }

View File

@ -1,10 +1,11 @@
import ButtonVm from "@/app/components/button/button-vm"; import ButtonVm from "@/app/components/button/button-vm";
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 { 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-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";
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> { export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
private createInvoice: typeof createInvoiceUsecase private createInvoice: typeof createInvoiceUsecase
@ -14,16 +15,20 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
} }
useVM(): ButtonVm { useVM(): ButtonVm {
const throttledOnClick = useThrottle(this.onClickHandler.bind(this), 5000) const router = useRouter()
const [action, isPending] = useServerAction(() => this.onClickHandler(router.refresh))
const throttledOnClick = useThrottle(action, 5000)
return { return {
props: { props: {
title: "Create Random Invoice" title: isPending ? "Loading" : "Create Random Invoice",
isDisable: isPending ? true : false
}, },
onClick: throttledOnClick onClick: throttledOnClick.bind(this)
} }
} }
onClickHandler() { async onClickHandler(refreshPage: () => void) {
const fakedParams: InvoiceParam = { const fakedParams: InvoiceParam = {
amount: faker.number.int({ amount: faker.number.int({
min: 1, min: 1,
@ -31,6 +36,7 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
}), }),
status: "paid" status: "paid"
} }
this.createInvoice(fakedParams) await this.createInvoice(fakedParams)
refreshPage()
} }
} }

View File

@ -0,0 +1,40 @@
import { useState, useEffect, useTransition, useRef } from 'react';
/**
*
* @param action Main server action to run
* @param onFinished Callback to run after action
* @returns transitioned action to run and is pending variable
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useServerAction = <P extends any[], R>(
action: (...args: P) => Promise<R>,
onFinished?: (_: R | undefined) => void,
): [(...args: P) => Promise<R | undefined>, boolean] => {
const [isPending, startTransition] = useTransition();
const [result, setResult] = useState<R>();
const [finished, setFinished] = useState(false);
const resolver = useRef<(value?: R | PromiseLike<R>) => void>();
useEffect(() => {
if (!finished) return;
if (onFinished) onFinished(result);
resolver.current?.(result);
}, [result, finished, onFinished]);
const runAction = async (...args: P): Promise<R | undefined> => {
startTransition(() => {
action(...args).then((data) => {
setResult(data);
setFinished(true);
});
});
return new Promise((resolve) => {
resolver.current = resolve;
});
};
return [runAction, isPending];
};

View File

@ -16,7 +16,7 @@ export default class InvoiceDbRepo implements InvoiceRepo {
async createInvoice(params: InvoiceParam): Promise<string> { async createInvoice(params: InvoiceParam): Promise<string> {
const firstCustomerIdDb = await sql`SELECT const firstCustomerIdDb = await sql`SELECT
id FROM customers id FROM customers
ORDER BY id ASC ORDER BY id DESC
LIMIT 1 LIMIT 1
` `
const customerId = firstCustomerIdDb.at(0)?.id const customerId = firstCustomerIdDb.at(0)?.id

View File

@ -3,8 +3,6 @@ import serverDi from "@/feature/common/server-di";
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo"; import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param"; import { InvoiceParam, invoiceSchema } 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 { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default async function createInvoiceUsecase(params: InvoiceParam): Promise<string | {errorMessage: string}> { export default async function createInvoiceUsecase(params: InvoiceParam): Promise<string | {errorMessage: string}> {
const isParamsValid = invoiceSchema.safeParse(params) const isParamsValid = invoiceSchema.safeParse(params)
@ -16,7 +14,6 @@ export default async function createInvoiceUsecase(params: InvoiceParam): Promis
} }
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey) const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey)
revalidatePath("/dashboard")
return repo.createInvoice(params) return repo.createInvoice(params)
} }