diff --git a/src/app/components/button/button-vm.ts b/src/app/components/button/button-vm.ts index f9e8754..ef0d7af 100644 --- a/src/app/components/button/button-vm.ts +++ b/src/app/components/button/button-vm.ts @@ -1,6 +1,7 @@ export default interface ButtonVm { props: { - title: string + title: string; + isDisable: boolean; } onClick(): void } \ No newline at end of file diff --git a/src/app/components/button/button.tsx b/src/app/components/button/button.tsx index 5245285..df0de5b 100644 --- a/src/app/components/button/button.tsx +++ b/src/app/components/button/button.tsx @@ -11,7 +11,7 @@ export default class Button extends BaseView { protected Build(props: BuildProps): ReactNode { const {vm} = props - return {vm.props.title} + return {vm.props.title} } } diff --git a/src/app/dashboard/vm/create-random-invoice-button-vm.ts b/src/app/dashboard/vm/create-random-invoice-button-vm.ts index 11a258e..c5ceb13 100644 --- a/src/app/dashboard/vm/create-random-invoice-button-vm.ts +++ b/src/app/dashboard/vm/create-random-invoice-button-vm.ts @@ -1,10 +1,11 @@ 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 BaseVM from "@/bootstrap/helpers/vm/base-vm"; import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"; import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase"; import { faker } from "@faker-js/faker"; - +import { useRouter } from "next/navigation"; export default class CreateRandomInvoiceButtonVM extends BaseVM { private createInvoice: typeof createInvoiceUsecase @@ -14,16 +15,20 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM { } 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 { 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 = { amount: faker.number.int({ min: 1, @@ -31,6 +36,7 @@ export default class CreateRandomInvoiceButtonVM extends BaseVM { }), status: "paid" } - this.createInvoice(fakedParams) + await this.createInvoice(fakedParams) + refreshPage() } } \ No newline at end of file diff --git a/src/bootstrap/helpers/hooks/use-server-action.ts b/src/bootstrap/helpers/hooks/use-server-action.ts new file mode 100644 index 0000000..abb7516 --- /dev/null +++ b/src/bootstrap/helpers/hooks/use-server-action.ts @@ -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 =

( + action: (...args: P) => Promise, + onFinished?: (_: R | undefined) => void, +): [(...args: P) => Promise, boolean] => { + const [isPending, startTransition] = useTransition(); + const [result, setResult] = useState(); + const [finished, setFinished] = useState(false); + const resolver = useRef<(value?: R | PromiseLike) => void>(); + + useEffect(() => { + if (!finished) return; + + if (onFinished) onFinished(result); + resolver.current?.(result); + }, [result, finished, onFinished]); + + const runAction = async (...args: P): Promise => { + startTransition(() => { + action(...args).then((data) => { + setResult(data); + setFinished(true); + }); + }); + + return new Promise((resolve) => { + resolver.current = resolve; + }); + }; + + return [runAction, isPending]; +}; \ No newline at end of file diff --git a/src/feature/core/invoice/data/repo/invoice-db-repo.ts b/src/feature/core/invoice/data/repo/invoice-db-repo.ts index fd2219c..ea60b0f 100644 --- a/src/feature/core/invoice/data/repo/invoice-db-repo.ts +++ b/src/feature/core/invoice/data/repo/invoice-db-repo.ts @@ -16,7 +16,7 @@ export default class InvoiceDbRepo implements InvoiceRepo { async createInvoice(params: InvoiceParam): Promise { const firstCustomerIdDb = await sql`SELECT id FROM customers - ORDER BY id ASC + ORDER BY id DESC LIMIT 1 ` const customerId = firstCustomerIdDb.at(0)?.id diff --git a/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts index 1b3ea6a..73a0106 100644 --- a/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts +++ b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts @@ -3,8 +3,6 @@ import serverDi from "@/feature/common/server-di"; import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo"; import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param"; 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 { const isParamsValid = invoiceSchema.safeParse(params) @@ -16,7 +14,6 @@ export default async function createInvoiceUsecase(params: InvoiceParam): Promis } const repo = serverDi(invoiceModuleKey).resolve(invoiceRepoKey) - revalidatePath("/dashboard") return repo.createInvoice(params) } \ No newline at end of file