From 5683b67b6d3e067cb31a81b6f9c620a73f0b0184 Mon Sep 17 00:00:00 2001 From: Gustavo Guichard Date: Thu, 15 Dec 2022 21:17:24 -0300 Subject: [PATCH 1/4] feat: create a config domain-function and use it in the root loader --- src/models/config.ts | 10 ++++++---- src/routes/root.tsx | 10 +++++----- src/types/config.ts | 5 ----- src/utils/responses.ts | 10 ++++++++++ 4 files changed, 21 insertions(+), 14 deletions(-) delete mode 100644 src/types/config.ts create mode 100644 src/utils/responses.ts diff --git a/src/models/config.ts b/src/models/config.ts index 113d2e9..1d843da 100644 --- a/src/models/config.ts +++ b/src/models/config.ts @@ -1,7 +1,9 @@ -export async function getConfig() { - return { +import { makeDomainFunction } from 'domain-functions' + +export const getConfig = makeDomainFunction()(async () => ({ + config: { organizationId: 1, organization: 'blzstore', name: 'Beleza Na Web', - } -} + }, +})) diff --git a/src/routes/root.tsx b/src/routes/root.tsx index 970eca0..653c11a 100644 --- a/src/routes/root.tsx +++ b/src/routes/root.tsx @@ -1,16 +1,16 @@ import { Link, Outlet, useLoaderData } from 'react-router-dom' import { Container } from '../components/container' -import type { Config } from '../types/config' import { getConfig } from '../models/config' +import { loaderResponseOrNotFound } from '../utils/responses' +import { UnpackData } from 'domain-functions' export async function loader() { - return await getConfig() + const result = await getConfig() + return loaderResponseOrNotFound(result) } -type LoaderData = Config - export default function Root() { - const config = useLoaderData() as LoaderData + const { config } = useLoaderData() as UnpackData return ( <> diff --git a/src/types/config.ts b/src/types/config.ts deleted file mode 100644 index d2913f5..0000000 --- a/src/types/config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Config = { - organizationId: number - organization: string - name: string -} diff --git a/src/utils/responses.ts b/src/utils/responses.ts new file mode 100644 index 0000000..6ffbedd --- /dev/null +++ b/src/utils/responses.ts @@ -0,0 +1,10 @@ +import { Result } from 'domain-functions' +import { json } from 'react-router-dom' + +export const loaderResponseOrNotFound = >(result: T, opts?: RequestInit) => { + if (!result.success) { + throw new Response('Not found', { status: 404, ...opts }) + } + + return json(result.data, { status: 200, ...opts }) as Response +} From c897c07bed517478c1992bd7084e12e9b511f406 Mon Sep 17 00:00:00 2001 From: Gustavo Guichard Date: Thu, 15 Dec 2022 21:20:32 -0300 Subject: [PATCH 2/4] feat: create a cart domain-function, parse it with zod and use it in cart route and components --- src/components/cart/cart-item.tsx | 5 +-- src/components/cart/cart-summary.tsx | 7 ++-- src/models/cart.ts | 50 ++++++++++++++++++++++++++-- src/routes/cart.tsx | 20 +++++------ src/types/cart.ts | 37 -------------------- 5 files changed, 62 insertions(+), 57 deletions(-) delete mode 100644 src/types/cart.ts diff --git a/src/components/cart/cart-item.tsx b/src/components/cart/cart-item.tsx index 2b06828..1cf86c0 100644 --- a/src/components/cart/cart-item.tsx +++ b/src/components/cart/cart-item.tsx @@ -1,7 +1,8 @@ -import type { CartItemType } from '../../types/cart' +import { UnpackData } from 'domain-functions' +import { getCart } from '../../models/cart' type CartItemProps = { - item: CartItemType + item: UnpackData['cart']['items'][number] } export function CartItem({ item }: CartItemProps) { return ( diff --git a/src/components/cart/cart-summary.tsx b/src/components/cart/cart-summary.tsx index 7519876..eef3c58 100644 --- a/src/components/cart/cart-summary.tsx +++ b/src/components/cart/cart-summary.tsx @@ -1,9 +1,8 @@ -import type { CartType } from '../../types/cart' +import { UnpackData } from 'domain-functions' +import { getCart } from '../../models/cart' import { pluralize } from '../../utils/pluralize' -type CartSummaryProps = { - cart: CartType -} +type CartSummaryProps = UnpackData export function CartSummary({ cart }: CartSummaryProps) { return ( diff --git a/src/models/cart.ts b/src/models/cart.ts index 61775c6..a19e5e8 100644 --- a/src/models/cart.ts +++ b/src/models/cart.ts @@ -1,3 +1,47 @@ -export async function getCart() { - return await fetch('https://www.mocky.io/v2/5b15c4923100004a006f3c07').then((r) => r.json()) -} +import { makeDomainFunction, pipe } from 'domain-functions' +import * as z from 'zod' + +const imageObjectSchema = z.object({ + featured: z.boolean(), + thumbnail: z.string(), + small: z.string(), + medium: z.string(), + large: z.string(), + extraLarge: z.string(), + valid: z.boolean(), +}) + +const priceSpecificationSchema = z.object({ + sku: z.string(), + price: z.number(), + originalPrice: z.number(), + maxPrice: z.number(), + percent: z.number(), + discount: z.number(), +}) + +const cartItemSchema = z.object({ + quantity: z.number(), + product: z.object({ + sku: z.string(), + name: z.string(), + imageObjects: z.array(imageObjectSchema), + priceSpecification: priceSpecificationSchema, + }), +}) + +const cartSchema = z.object({ + discount: z.number(), + id: z.string(), + items: z.array(cartItemSchema), + shippingTotal: z.number(), + subTotal: z.number(), + total: z.number(), +}) + +const fetchCart = makeDomainFunction()(() => + fetch('https://www.mocky.io/v2/5b15c4923100004a006f3c07').then((r) => r.json()), +) +const normalizeCart = makeDomainFunction(cartSchema)(async (cart) => ({ cart })) + +export const getCart = pipe(fetchCart, normalizeCart) diff --git a/src/routes/cart.tsx b/src/routes/cart.tsx index ba1f5b1..be9d008 100644 --- a/src/routes/cart.tsx +++ b/src/routes/cart.tsx @@ -1,30 +1,28 @@ -import { ActionFunctionArgs, json, Link, useLoaderData, useRouteLoaderData } from 'react-router-dom' +import { ActionFunctionArgs, Link, useLoaderData, useRouteLoaderData } from 'react-router-dom' import { Box } from '../components/box' import { CartItem } from '../components/cart' -import type { CartType } from '../types/cart' import { getCart } from '../models/cart' import { pluralize } from '../utils/pluralize' +import { inputFromForm, UnpackData } from 'domain-functions' +import { loaderResponseOrNotFound } from '../utils/responses' +import { getConfig } from '../models/config' export async function loader() { - const cart = await getCart() - - return json(cart) + const result = await getCart() + return loaderResponseOrNotFound(result) } export async function action({ request }: ActionFunctionArgs) { - const formData = await request.formData() + const formData = await inputFromForm(request) console.log(formData) return null } -type LoaderData = CartType - export default function Cart() { - const cart = useLoaderData() as LoaderData - const config = useRouteLoaderData('root') + const { cart } = useLoaderData() as UnpackData + const { config } = useRouteLoaderData('root') as UnpackData const cartLength = cart.items.length - console.log(config) return (
diff --git a/src/types/cart.ts b/src/types/cart.ts deleted file mode 100644 index 59c6b1c..0000000 --- a/src/types/cart.ts +++ /dev/null @@ -1,37 +0,0 @@ -export type CartItemType = { - quantity: number - product: { - sku: string - name: string - imageObjects: ImageObjectsType[] - priceSpecification: PriceSpecificationType - } -} - -type ImageObjectsType = { - featured: boolean - thumbnail: string - small: string - medium: string - large: string - extraLarge: string - valid: boolean -} - -type PriceSpecificationType = { - sku: string - price: number - originalPrice: number - maxPrice: number - percent: number - discount: number -} - -export type CartType = { - discount: number - id: string - items: CartItemType[] - shippingTotal: number - subTotal: number - total: number -} From 069cc215f29607931c0268d8837a9693977fb876 Mon Sep 17 00:00:00 2001 From: Gustavo Guichard Date: Thu, 15 Dec 2022 21:21:07 -0300 Subject: [PATCH 3/4] feat: create an address domain-function, parse it with zod and use it in address route and components --- src/components/address/address-item.tsx | 5 ++-- src/models/address.ts | 37 +++++++++++++++++++++++-- src/models/compositions.ts | 5 ++++ src/routes/address.tsx | 36 +++++++----------------- src/types/address.ts | 25 ----------------- 5 files changed, 52 insertions(+), 56 deletions(-) create mode 100644 src/models/compositions.ts delete mode 100644 src/types/address.ts diff --git a/src/components/address/address-item.tsx b/src/components/address/address-item.tsx index 7b6bf2f..9da1b37 100644 --- a/src/components/address/address-item.tsx +++ b/src/components/address/address-item.tsx @@ -1,7 +1,8 @@ -import type { AddressItemType } from '../../types/address' +import { UnpackData } from 'domain-functions' +import { getAddress } from '../../models/address' type AddressItemProps = { - item: AddressItemType + item: UnpackData['address'][number] } export function AddressItem({ item }: AddressItemProps) { return ( diff --git a/src/models/address.ts b/src/models/address.ts index eb5a2cc..a18ebbe 100644 --- a/src/models/address.ts +++ b/src/models/address.ts @@ -1,6 +1,37 @@ -export async function getAddress() { - return mockAddress -} +import { makeDomainFunction, pipe } from 'domain-functions' +import * as z from 'zod' + +const addressItemSchema = z.object({ + id: z.number(), + customerId: z.number(), + label: z.string(), + givenName: z.string(), + familyName: z.string(), + streetAddress: z.string(), + number: z.string(), + district: z.string(), + complement: z.string(), + postalCode: z.string(), + addressLocality: z.string(), + addressRegion: z.string(), + referencePoint: z.string().optional(), + localityCode: z.number(), + addressType: z.string().optional(), + main: z.boolean(), + billing: z.boolean(), + updatedAt: z.string(), + geolocation: z.object({ latitude: z.string(), longitude: z.string() }).optional(), + dependencyConnections: z.array(z.unknown()), +}) + +const getAddressItems = makeDomainFunction()(async () => mockAddress) +const normalizeAddresses = makeDomainFunction(z.object({ items: z.array(addressItemSchema) }))( + async ({ items: address }) => ({ + address, + }), +) + +export const getAddress = pipe(getAddressItems, normalizeAddresses) const mockAddress = { items: [ diff --git a/src/models/compositions.ts b/src/models/compositions.ts new file mode 100644 index 0000000..da5c55b --- /dev/null +++ b/src/models/compositions.ts @@ -0,0 +1,5 @@ +import { merge } from 'domain-functions' +import { getAddress } from './address' +import { getCart } from './cart' + +export const getAddressRouteData = merge(getCart, getAddress) diff --git a/src/routes/address.tsx b/src/routes/address.tsx index 8317a03..c08589c 100644 --- a/src/routes/address.tsx +++ b/src/routes/address.tsx @@ -1,43 +1,27 @@ -import { - useLoaderData, - useRouteLoaderData, - json, - Form, - useSubmit, - ActionFunctionArgs, - Link, -} from 'react-router-dom' +import { inputFromForm, UnpackData } from 'domain-functions' +import { useLoaderData, Form, useSubmit, ActionFunctionArgs, Link } from 'react-router-dom' import { AddressItem } from '../components/address' import { Box } from '../components/box' -import type { AddressType } from '../types/address' -import { getAddress } from '../models/address' -import { getCart } from '../models/cart' -import type { CartType } from '../types/cart' +import { getAddressRouteData } from '../models/compositions' +import { loaderResponseOrNotFound } from '../utils/responses' export async function loader() { - const [cart, address] = await Promise.all([getCart(), getAddress()]) - - return json({ cart, address }) + const result = await getAddressRouteData() + return loaderResponseOrNotFound(result) } export async function action({ request }: ActionFunctionArgs) { - const formData = Object.fromEntries(await request.formData()) + const formData = await inputFromForm(request) console.log(formData) // await useAddress(formData.address) return {} // redirect('transacional/pagamento') } -type DataLoader = { - address: AddressType - cart: CartType -} - export default function Address() { - const data = useLoaderData() as DataLoader - const config = useRouteLoaderData('root') - // console.log({ config, data }) + const { address, cart } = useLoaderData() as UnpackData const submit = useSubmit() + console.log(cart) return (
@@ -49,7 +33,7 @@ export default function Address() { submit(event.currentTarget) }} > - {data.address.items.map((item) => ( + {address.map((item) => ( ))} diff --git a/src/types/address.ts b/src/types/address.ts deleted file mode 100644 index 4d9b8ae..0000000 --- a/src/types/address.ts +++ /dev/null @@ -1,25 +0,0 @@ -export type AddressType = { - items: AddressItemType[] -} - -export type AddressItemType = { - id: number - customerId: number - label: string - givenName: string - familyName: string - streetAddress: string - number: string - district: string - complement: string - postalCode: string - addressLocality: string - addressRegion: string - referencePoint: string - localityCode: number - addressType: string - main: false - billing: false - updatedAt: string - dependencyConnections: [] -} From e7993eb4dcb327a45fe948734bd97c2db039f8bf Mon Sep 17 00:00:00 2001 From: Gustavo Guichard Date: Thu, 15 Dec 2022 21:21:39 -0300 Subject: [PATCH 4/4] feat: create a payment domain-function and use it in payment route --- src/models/payment.ts | 18 +++++++++--------- src/routes/payment.tsx | 27 +++++++++------------------ src/types/payment.ts | 5 ----- 3 files changed, 18 insertions(+), 32 deletions(-) delete mode 100644 src/types/payment.ts diff --git a/src/models/payment.ts b/src/models/payment.ts index 69f40c8..da7c159 100644 --- a/src/models/payment.ts +++ b/src/models/payment.ts @@ -1,17 +1,17 @@ +import { makeDomainFunction } from 'domain-functions' import { z } from 'zod' -import { PaymentType } from '../types/payment' -export async function makePayment(payload: PaymentType) { +export const schema = z.object({ + card: z.string().min(18, 'Informe um número de cartão de crédito'), + name: z.string().min(1, 'Informe um títular'), + expire: z.string().min(1), +}) + +export const makePayment = makeDomainFunction(schema)(async (input) => { await new Promise((res) => setTimeout(res, 3000)) // api.post() - console.log(payload) + console.log(input) return true -} - -export const schema = z.object({ - card: z.string().min(18, 'Informe um número de cartão de crédito'), - name: z.string().min(1, 'Informe um títular'), - expire: z.string().min(1), }) diff --git a/src/routes/payment.tsx b/src/routes/payment.tsx index 4caffc7..76cbd64 100644 --- a/src/routes/payment.tsx +++ b/src/routes/payment.tsx @@ -1,37 +1,28 @@ -import { useLoaderData, useRouteLoaderData, json, useNavigation, ActionFunction } from 'react-router-dom' -import { makeDomainFunction } from 'domain-functions' +import { useLoaderData, useNavigation, ActionFunction } from 'react-router-dom' +import { UnpackData } from 'domain-functions' import { formAction, Form } from '../components/form' import { Box } from '../components/box' import { CartSummary } from '../components/cart' -import type { AddressType } from '../types/address' -import { getAddress } from '../models/address' -import type { CartType } from '../types/cart' -import { getCart } from '../models/cart' import { makePayment, schema } from '../models/payment' +import { getAddressRouteData } from '../models/compositions' +import { loaderResponseOrNotFound } from '../utils/responses' export async function loader() { - const [cart, address] = await Promise.all([getCart(), getAddress()]) - return json({ cart, address }) + const result = await getAddressRouteData() + return loaderResponseOrNotFound(result) } -const mutation = makeDomainFunction(schema)(async (values) => await makePayment(values)) - export const action: ActionFunction = async ({ request }) => formAction({ request, schema, - mutation, + mutation: makePayment, successPath: '../transacional/sucesso', }) -type DataLoader = { - address: AddressType - cart: CartType -} - export default function Address() { - const data = useLoaderData() as DataLoader + const { cart } = useLoaderData() as UnpackData const navigation = useNavigation() const isPending = navigation.state === 'submitting' @@ -54,7 +45,7 @@ export default function Address() { - + ) diff --git a/src/types/payment.ts b/src/types/payment.ts deleted file mode 100644 index b49d223..0000000 --- a/src/types/payment.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type PaymentType = { - card?: string - name?: string - expire?: string -}