develop #3
@ -1,6 +1,7 @@
|
|||||||
export default interface ButtonVm {
|
export default interface ButtonVm {
|
||||||
props: {
|
props: {
|
||||||
title: string
|
title: string;
|
||||||
|
isDisable: boolean;
|
||||||
}
|
}
|
||||||
onClick(): void
|
onClick(): void
|
||||||
}
|
}
|
@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
40
src/bootstrap/helpers/hooks/use-server-action.ts
Normal file
40
src/bootstrap/helpers/hooks/use-server-action.ts
Normal 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];
|
||||||
|
};
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user