diff --git a/apps/shared-app/src/client/button.tsx b/apps/shared-app/src/client/button.tsx index bc2e68f..99879b2 100644 --- a/apps/shared-app/src/client/button.tsx +++ b/apps/shared-app/src/client/button.tsx @@ -1,17 +1,18 @@ 'use client'; import * as React from 'react'; +import * as ReactDOM from 'react-dom'; import {trackClick} from '../server/track-click.js'; -export type ButtonProps = React.PropsWithChildren<{ - readonly disabled?: boolean; -}>; +export type ButtonProps = React.PropsWithChildren<{}>; + +export function Button({children}: ButtonProps): JSX.Element { + const {pending} = ReactDOM.useFormStatus(); -export function Button({children, disabled}: ButtonProps): JSX.Element { return ( - {message} + + {result && ( + + {result.status === `success` ? ( +

+ Bought {result.quantity} + {` `} + {result.quantity === 1 ? `item` : `items`}. +

+ ) : ( +

{result.message}

+ )} +

+ Total items bought: {result.totalQuantityInSession} +

+
+ )} ); } diff --git a/apps/shared-app/src/client/use-ephemeral-state.ts b/apps/shared-app/src/client/use-ephemeral-state.ts deleted file mode 100644 index ace74b2..0000000 --- a/apps/shared-app/src/client/use-ephemeral-state.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; - -export function useEphemeralState( - initialState: S | undefined | (() => S | undefined), - lifetimeInMilliseconds: number, -): [S | undefined, React.Dispatch>] { - const [state, setState] = React.useState(initialState); - const timeoutRef = React.useRef(); - - const setEphemeralState = React.useCallback< - React.Dispatch> - >((value) => { - window.clearTimeout(timeoutRef.current); - - timeoutRef.current = window.setTimeout( - () => setState(undefined), - lifetimeInMilliseconds, - ); - - setState(value); - }, []); - - React.useEffect(() => () => window.clearTimeout(timeoutRef.current), []); - - return [state, setEphemeralState]; -} diff --git a/apps/shared-app/src/server/buy.tsx b/apps/shared-app/src/server/buy.ts similarity index 50% rename from apps/shared-app/src/server/buy.tsx rename to apps/shared-app/src/server/buy.ts index f3ceebc..7e3e750 100644 --- a/apps/shared-app/src/server/buy.tsx +++ b/apps/shared-app/src/server/buy.ts @@ -1,12 +1,27 @@ 'use server'; -import * as React from 'react'; import 'server-only'; import {z} from 'zod'; -import {Notification} from '../shared/notification.js'; import {wait} from './wait.js'; -type BuyFieldErrors = z.inferFlattenedErrors['fieldErrors']; +export type BuyResult = BuySuccessResult | BuyErrorResult; + +export interface BuySuccessResult { + readonly status: 'success'; + readonly quantity: number; + readonly totalQuantityInSession: number; +} + +export interface BuyErrorResult { + readonly status: 'error'; + readonly message: string; + readonly fieldErrors?: BuyFieldErrors; + readonly totalQuantityInSession: number; +} + +export type BuyFieldErrors = z.inferFlattenedErrors< + typeof BuyFormData +>['fieldErrors']; const FormDataFields = z.instanceof(FormData).transform((formData) => { const fields: Record = {}; @@ -37,14 +52,20 @@ async function fetchAvailableProductCount(): Promise { } export async function buy( + prevResult: BuyResult | undefined, formData: FormData, -): Promise<[React.ReactNode] | [React.ReactNode, BuyFieldErrors]> { +): Promise { const parsedFormData = FormDataFields.safeParse(formData); + const totalQuantityInSession = prevResult?.totalQuantityInSession ?? 0; if (!parsedFormData.success) { - return [ - An unexpected error occured., - ]; + console.error(parsedFormData.error); + + return { + status: `error`, + message: `An unexpected error occured.`, + totalQuantityInSession, + }; } const result = await BuyFormData.safeParseAsync(parsedFormData.data); @@ -52,19 +73,21 @@ export async function buy( if (!result.success) { const {fieldErrors} = result.error.formErrors; - return [ - - {Object.values(fieldErrors).flat().join(` `)} - , + return { + status: `error`, + message: Object.values(fieldErrors).flat().join(` `), fieldErrors, - ]; + totalQuantityInSession, + }; } const {quantity} = result.data; - return [ - - Bought {quantity} {quantity === 1 ? `item` : `items`}. - , - ]; + // Buy quantity number of items ... + + return { + status: `success`, + quantity, + totalQuantityInSession: totalQuantityInSession + quantity, + }; } diff --git a/apps/shared-app/src/server/home-page.tsx b/apps/shared-app/src/server/home-page.tsx index bd71689..1791ab0 100644 --- a/apps/shared-app/src/server/home-page.tsx +++ b/apps/shared-app/src/server/home-page.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import {Product} from '../client/product.js'; import {Main} from '../shared/main.js'; -import {buy} from './buy.js'; import {Hello} from './hello.js'; import {Suspended} from './suspended.js'; @@ -13,7 +12,7 @@ export function HomePage(): JSX.Element { - + );