From c3507d7365d53178e01feff3c01ccf103f4d84f6 Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 5 Aug 2026 21:13:37 -0300 Subject: [PATCH] feat: implementa cadastro do cliente --- .../src/components/ActionFooter/index.tsx | 44 +- pay-flow/src/components/ActionFooter/style.ts | 16 + pay-flow/src/components/Breadcrumb/style.ts | 2 +- pay-flow/src/components/Button/index.tsx | 13 +- pay-flow/src/components/Button/style.ts | 32 +- pay-flow/src/components/Card/index.tsx | 24 +- pay-flow/src/components/CashierCard/style.ts | 2 +- .../src/components/CashierFormCard/style.ts | 2 +- .../src/components/CustomerCard/index.tsx | 12 +- pay-flow/src/components/CustomerCard/style.ts | 2 +- .../src/components/CustomerFormCard/index.tsx | 432 +++++++++++------- .../src/components/CustomerFormCard/style.ts | 2 +- pay-flow/src/components/DiscountCard/style.ts | 2 +- pay-flow/src/components/FileInput/style.ts | 15 +- pay-flow/src/components/Input/style.ts | 2 + pay-flow/src/components/InputButton/style.ts | 2 +- pay-flow/src/components/PaymentCard/style.ts | 2 +- .../src/components/ProductFormCard/style.ts | 2 +- pay-flow/src/components/Row/index.tsx | 4 +- pay-flow/src/components/Row/style.ts | 1 + pay-flow/src/components/Select/style.ts | 12 +- pay-flow/src/components/ShippingCard/style.ts | 2 +- .../src/components/ShippingFormCard/style.ts | 2 +- pay-flow/src/components/Style/theme.ts | 3 +- .../src/contexts/Address/AddressContext.ts | 11 + .../src/contexts/Address/AddressProvider.tsx | 43 ++ .../src/contexts/Address/address.service.ts | 15 + pay-flow/src/contexts/Address/useAddress.ts | 6 + .../src/contexts/Customer/customer.service.ts | 37 +- .../src/contexts/Product/product.service.ts | 11 +- pay-flow/src/contexts/Providers.tsx | 29 +- pay-flow/src/domain/constants.ts | 2 + pay-flow/src/i18n/locales/pt/common.json | 22 +- pay-flow/src/pages/Checkout/style.ts | 2 +- pay-flow/src/pages/Login/style.ts | 20 +- pay-flow/src/pages/Payment/style.ts | 2 +- pay-flow/src/pages/Shipping/style.ts | 2 +- pay-flow/src/types/api-response.ts | 14 + pay-flow/src/{type => types}/type.ts | 0 pay-flow/src/utils/mask.ts | 26 ++ pay-flow/src/utils/phone.ts | 21 - pay-flow/src/validations/newCustomerSchema.ts | 45 +- 42 files changed, 650 insertions(+), 290 deletions(-) create mode 100644 pay-flow/src/components/ActionFooter/style.ts create mode 100644 pay-flow/src/contexts/Address/AddressContext.ts create mode 100644 pay-flow/src/contexts/Address/AddressProvider.tsx create mode 100644 pay-flow/src/contexts/Address/address.service.ts create mode 100644 pay-flow/src/contexts/Address/useAddress.ts create mode 100644 pay-flow/src/types/api-response.ts rename pay-flow/src/{type => types}/type.ts (100%) delete mode 100644 pay-flow/src/utils/phone.ts diff --git a/pay-flow/src/components/ActionFooter/index.tsx b/pay-flow/src/components/ActionFooter/index.tsx index 741f64d..97e1a2e 100644 --- a/pay-flow/src/components/ActionFooter/index.tsx +++ b/pay-flow/src/components/ActionFooter/index.tsx @@ -1,11 +1,10 @@ import { useNavigate } from "react-router-dom"; -import { CheckCircle, Trash2, Undo } from "lucide-react"; +import { CheckCircle, Eraser, Undo } from "lucide-react"; import { useTranslation } from "react-i18next"; import Button from "../Button"; import { colors } from "../Style/theme"; -import { Row } from "../Row"; import { Col } from "../Col"; -import { toast } from "react-toastify"; +import { ButtonGroup, FooterWrapper } from "./style"; interface ActionFooterProps { confirmText: string; @@ -24,40 +23,37 @@ export function ActionFooter({ const handleClear = (event: React.MouseEvent) => { event.stopPropagation(); onClear(); - navigate("/checkout"); - toast.success("Sucesso"); }; return ( - + + + - - + + + + + reset()} + /> + + + + + ); } diff --git a/pay-flow/src/components/CustomerFormCard/style.ts b/pay-flow/src/components/CustomerFormCard/style.ts index 0297633..d20be9b 100644 --- a/pay-flow/src/components/CustomerFormCard/style.ts +++ b/pay-flow/src/components/CustomerFormCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/DiscountCard/style.ts b/pay-flow/src/components/DiscountCard/style.ts index bfc4273..f475465 100644 --- a/pay-flow/src/components/DiscountCard/style.ts +++ b/pay-flow/src/components/DiscountCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/FileInput/style.ts b/pay-flow/src/components/FileInput/style.ts index 4069d7c..da08850 100644 --- a/pay-flow/src/components/FileInput/style.ts +++ b/pay-flow/src/components/FileInput/style.ts @@ -31,6 +31,17 @@ export const UploadArea = styled.label` width: 100%; height: 220px; + background: ${({ theme }) => + theme === "dark" ? colors.darkComponentBg : colors.lightComponentBg}; + + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + + box-shadow: ${({ theme }) => + theme === "dark" + ? "0 12px 32px rgba(0,0,0,0.45)" + : "0 12px 32px rgba(0,0,0,0.12)"}; + border: 1px solid ${({ hasError, theme }) => hasError @@ -39,7 +50,7 @@ export const UploadArea = styled.label` ? colors.darkBorder : colors.lightBorder}; - border-radius: 8px; + border-radius: 16px; display: flex; justify-content: center; @@ -99,4 +110,4 @@ export const Placeholder = styled.div<{ svg { color: inherit; } -`; +`; \ No newline at end of file diff --git a/pay-flow/src/components/Input/style.ts b/pay-flow/src/components/Input/style.ts index 7676fc7..fd4625d 100644 --- a/pay-flow/src/components/Input/style.ts +++ b/pay-flow/src/components/Input/style.ts @@ -61,12 +61,14 @@ export const StyledInput = styled.input` &::placeholder { color: ${({ theme }) => theme === "dark" ? colors.darkText : colors.lightText}; + opacity: ${({ theme }) => (theme === "dark" ? 0.45 : 0.5)}; } text-transform: ${({ uppercase }) => (uppercase ? "uppercase" : "none")}; &::placeholder { text-transform: none; + opacity: ${({ theme }) => (theme === "dark" ? 0.45 : 0.5)}; } &:focus { diff --git a/pay-flow/src/components/InputButton/style.ts b/pay-flow/src/components/InputButton/style.ts index e9060ac..4de7e50 100644 --- a/pay-flow/src/components/InputButton/style.ts +++ b/pay-flow/src/components/InputButton/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const InputWrapper = styled.div` position: relative; diff --git a/pay-flow/src/components/PaymentCard/style.ts b/pay-flow/src/components/PaymentCard/style.ts index bfc4273..f475465 100644 --- a/pay-flow/src/components/PaymentCard/style.ts +++ b/pay-flow/src/components/PaymentCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/ProductFormCard/style.ts b/pay-flow/src/components/ProductFormCard/style.ts index 0297633..d20be9b 100644 --- a/pay-flow/src/components/ProductFormCard/style.ts +++ b/pay-flow/src/components/ProductFormCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/Row/index.tsx b/pay-flow/src/components/Row/index.tsx index 2c4934a..9d0c5e4 100644 --- a/pay-flow/src/components/Row/index.tsx +++ b/pay-flow/src/components/Row/index.tsx @@ -5,15 +5,17 @@ export interface RowProps { children: ReactNode; align?: "flex-start" | "center" | "flex-end" | "stretch"; justify?: "flex-start" | "center" | "flex-end" | "space-between"; + className?: string; } export function Row({ children, align = "stretch", justify = "flex-start", + className, }: RowProps) { return ( - + {children} ); diff --git a/pay-flow/src/components/Row/style.ts b/pay-flow/src/components/Row/style.ts index 5e57e14..3249372 100644 --- a/pay-flow/src/components/Row/style.ts +++ b/pay-flow/src/components/Row/style.ts @@ -9,6 +9,7 @@ export const StyledRow = styled.div` display: flex; width: 100%; flex-wrap: wrap; + box-sizing: border-box; margin-bottom: 16px; diff --git a/pay-flow/src/components/Select/style.ts b/pay-flow/src/components/Select/style.ts index fe989ad..8611c48 100644 --- a/pay-flow/src/components/Select/style.ts +++ b/pay-flow/src/components/Select/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const Container = styled.div` width: 100%; @@ -22,7 +22,10 @@ export const StyledSelect = styled.select` border-radius: 14px; font-size: 15px; - font-family: system-ui, -apple-system, BlinkMacSystemFont; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont; appearance: none; -webkit-appearance: none; @@ -81,7 +84,10 @@ export const StyledLabel = styled.label` margin-bottom: 10px; font-size: 16px; font-weight: 600; - font-family: system-ui, -apple-system, BlinkMacSystemFont; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont; color: ${({ theme }) => theme === "dark" ? colors.darkText : colors.lightText}; diff --git a/pay-flow/src/components/ShippingCard/style.ts b/pay-flow/src/components/ShippingCard/style.ts index 749a79e..48b358b 100644 --- a/pay-flow/src/components/ShippingCard/style.ts +++ b/pay-flow/src/components/ShippingCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/ShippingFormCard/style.ts b/pay-flow/src/components/ShippingFormCard/style.ts index 0297633..d20be9b 100644 --- a/pay-flow/src/components/ShippingFormCard/style.ts +++ b/pay-flow/src/components/ShippingFormCard/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const RowItem = styled.div` display: flex; diff --git a/pay-flow/src/components/Style/theme.ts b/pay-flow/src/components/Style/theme.ts index 3a65000..91987bc 100644 --- a/pay-flow/src/components/Style/theme.ts +++ b/pay-flow/src/components/Style/theme.ts @@ -21,6 +21,7 @@ export const colors = { // White white: "#ffffff", - // Error + // Status colors red: "#ef4444", + green: "#22c55e", }; diff --git a/pay-flow/src/contexts/Address/AddressContext.ts b/pay-flow/src/contexts/Address/AddressContext.ts new file mode 100644 index 0000000..6d7937f --- /dev/null +++ b/pay-flow/src/contexts/Address/AddressContext.ts @@ -0,0 +1,11 @@ +import { createContext } from "react"; +import type { PostalCode } from "./AddressProvider"; + +export interface AddressContextData { + loading: boolean; + getAddress: (postalCode: string) => Promise; +} + +export const AddressContext = createContext( + {} as AddressContextData, +); diff --git a/pay-flow/src/contexts/Address/AddressProvider.tsx b/pay-flow/src/contexts/Address/AddressProvider.tsx new file mode 100644 index 0000000..3f53270 --- /dev/null +++ b/pay-flow/src/contexts/Address/AddressProvider.tsx @@ -0,0 +1,43 @@ +import { useState, type ReactNode } from "react"; +import { toast } from "react-toastify"; +import { useTranslation } from "react-i18next"; +import { AddressContext } from "./AddressContext"; +import { getAddressByPostalCode } from "./address.service"; + +export interface PostalCode { + street: string; + complement: string; + neighborhood: string; + city: string; + uf: string; +} + +export function AddressProvider({ children }: { children: ReactNode }) { + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + + const getAddress = async ( + postalCode: string, + ): Promise => { + try { + const address = await getAddressByPostalCode(postalCode); + return address; + } catch { + toast.error(t("address.errorGetAddress")); + } finally { + setLoading(false); + } + }; + + return ( + + {children} + + ); +} diff --git a/pay-flow/src/contexts/Address/address.service.ts b/pay-flow/src/contexts/Address/address.service.ts new file mode 100644 index 0000000..2303b3d --- /dev/null +++ b/pay-flow/src/contexts/Address/address.service.ts @@ -0,0 +1,15 @@ +import { api } from "../../api/api"; +import type { ApiResponse } from "../../types/api-response"; +import type { PostalCode } from "./AddressProvider"; + +export const getAddressByPostalCode = async ( + postalCode: string, +): Promise => { + const response = await api.post>( + `/address/postal-code`, + { + postalCode, + }, + ); + return response.data.data; +}; diff --git a/pay-flow/src/contexts/Address/useAddress.ts b/pay-flow/src/contexts/Address/useAddress.ts new file mode 100644 index 0000000..2a04152 --- /dev/null +++ b/pay-flow/src/contexts/Address/useAddress.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { AddressContext } from "./AddressContext"; + +export function useAddress() { + return useContext(AddressContext); +} diff --git a/pay-flow/src/contexts/Customer/customer.service.ts b/pay-flow/src/contexts/Customer/customer.service.ts index cebf021..fb237e7 100644 --- a/pay-flow/src/contexts/Customer/customer.service.ts +++ b/pay-flow/src/contexts/Customer/customer.service.ts @@ -15,10 +15,39 @@ export const getCustomerByIdentifier = async ( export const postCustomer = async ( customer: CustomerFormData, ): Promise => { - const response = await api.post("/customer/add", { - ...customer, - identifier: onlyNumbers(customer.identifier), - phone: onlyNumbers(customer.phone), + const formData = new FormData(); + + formData.append("identifier", onlyNumbers(customer.identifier)); + formData.append("name", customer.name.toUpperCase()); + formData.append("phone", onlyNumbers(customer.phone)); + formData.append("email", customer.email); + + customer.address.forEach((address, index) => { + formData.append(`addresses[${index}].street`, address.street.toUpperCase()); + formData.append(`addresses[${index}].number`, String(address.number)); + formData.append( + `addresses[${index}].complement`, + address.complement?.toUpperCase() || "", + ); + formData.append( + `addresses[${index}].neighborhood`, + address.neighborhood?.toUpperCase() || "", + ); + formData.append(`addresses[${index}].city`, address.city.toUpperCase()); + formData.append( + `addresses[${index}].postalCode`, + onlyNumbers(address.postalCode), + ); + formData.append(`addresses[${index}].uf`, address.uf.toUpperCase()); + }); + + if (customer.photo) formData.append("photo", customer.photo); + + const response = await api.post("/customer", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, }); + return response.data; }; diff --git a/pay-flow/src/contexts/Product/product.service.ts b/pay-flow/src/contexts/Product/product.service.ts index 5d7e9e7..a70b44a 100644 --- a/pay-flow/src/contexts/Product/product.service.ts +++ b/pay-flow/src/contexts/Product/product.service.ts @@ -1,10 +1,13 @@ import { api } from "../../api/api"; import type { ProductFormData } from "../../components/ProductFormCard"; +import type { ApiResponse } from "../../types/api-response"; import type { Product } from "./ProductProvider"; -export const getProductByItem = async (item: string): Promise => { - const response = await api.get(`/product/${item}`); - return response.data; +export const getProductByItem = async ( + item: string, +): Promise => { + const response = await api.get>(`/product/${item}`); + return response.data.data; }; export const postProduct = async (product: ProductFormData) => { @@ -18,7 +21,7 @@ export const postProduct = async (product: ProductFormData) => { if (product.image) formData.append("image", product.image); - const response = await api.post("product/add", formData, { + const response = await api.post("product", formData, { headers: { "Content-Type": "multipart/form-data", }, diff --git a/pay-flow/src/contexts/Providers.tsx b/pay-flow/src/contexts/Providers.tsx index 604c5ab..8e511f7 100644 --- a/pay-flow/src/contexts/Providers.tsx +++ b/pay-flow/src/contexts/Providers.tsx @@ -11,6 +11,7 @@ import { CashierProvider } from "./Cashier/CashierProvider"; import { CustomerProvider } from "./Customer/CustomerProvider"; import { AuthProvider } from "./Auth/AuthProvider"; import { ProductProvider } from "./Product/ProductProvider"; +import { AddressProvider } from "./Address/AddressProvider"; export function Providers({ children }: { children: ReactNode }) { return ( @@ -18,19 +19,21 @@ export function Providers({ children }: { children: ReactNode }) { - - - - - - - {children} - - - - - - + + + + + + + + {children} + + + + + + + diff --git a/pay-flow/src/domain/constants.ts b/pay-flow/src/domain/constants.ts index e026479..6539628 100644 --- a/pay-flow/src/domain/constants.ts +++ b/pay-flow/src/domain/constants.ts @@ -4,3 +4,5 @@ export const PRODUCT_CODE_LENGTH: number = 8; export const PRODUCT_BARCODE_LENGTH: number = 13; export const CPF_CODE_LENGTH: number = 14; export const CNPJ_CODE_LENGTH: number = 18; +export const PHONE_LENGTH: number = 15; +export const POSTAL_CODE_LENGTH: number = 9; diff --git a/pay-flow/src/i18n/locales/pt/common.json b/pay-flow/src/i18n/locales/pt/common.json index add888c..85c6fb1 100644 --- a/pay-flow/src/i18n/locales/pt/common.json +++ b/pay-flow/src/i18n/locales/pt/common.json @@ -38,14 +38,21 @@ "customer": "Cliente", "email": "E-mail", "errorGetCustomer": "Erro ao buscar as informações do cliente", - "identifier": "Identificador", + "identifier": "CPF / CNPJ", "identifierInvalid": "Identificador inválido", "identifierRequired": "Identificador é obrigatório", "name": "Nome", "phone": "Telefone", - "successAddCustomer": "Cliente salvo com sucesso" + "successAddCustomer": "Cliente salvo com sucesso", + "uploadPhoto": "Carregar foto", + "placeholderName": "Nome completo", + "defaultIdentifier": "000.000.000-00", + "defaultPhone": "(00) 00000-0000", + "defaultEmail": "exemplo@dominio.com", + "personalInformation": "Informações pessoais" }, "address": { + "address": "Endereço", "street": "Rua", "city": "Cidade", "complement": "Complemento", @@ -54,7 +61,16 @@ "state": "Estado", "postalCode": "CEP", "uf": "UF", - "country": "País" + "country": "País", + "addAddress": "Adicionar endereço", + "placeholderPostalCode": "00000-000", + "placeholderStreet": "Nome da rua", + "placeholderNumber": "123", + "placeholderComplement": "Apto, Bloco, Casa, etc.", + "placeholderNeighborhood": "Nome do bairro", + "placeholderCity": "Nome da cidade", + "placeholderState": "Nome do estado", + "placeholderUf": "UF" }, "discount": { "coupon": "Cupom", diff --git a/pay-flow/src/pages/Checkout/style.ts b/pay-flow/src/pages/Checkout/style.ts index 2e6925c..9317d6c 100644 --- a/pay-flow/src/pages/Checkout/style.ts +++ b/pay-flow/src/pages/Checkout/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../../components/Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const CardProduct = styled.div` display: flex; diff --git a/pay-flow/src/pages/Login/style.ts b/pay-flow/src/pages/Login/style.ts index 4f3e33f..d92742b 100644 --- a/pay-flow/src/pages/Login/style.ts +++ b/pay-flow/src/pages/Login/style.ts @@ -1,7 +1,7 @@ import styled from "styled-components"; import { colors } from "../../components/Style/theme"; import { Link } from "react-router-dom"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const Page = styled.div` min-height: 100vh; @@ -13,7 +13,10 @@ export const Page = styled.div` background: ${({ theme }) => theme === "dark" ? colors.darkBackground : colors.lightBackground}; - font-family: system-ui, -apple-system, BlinkMacSystemFont; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont; `; export const StyledLink = styled(Link)` @@ -34,13 +37,18 @@ export const RightContainer = styled.div` export const Body = styled.div` flex: 1; + width: 100%; + max-width: 1400px; + + margin: 0 auto; + + padding: 24px; + + box-sizing: border-box; + display: flex; flex-direction: column; - justify-content: center; - align-items: center; - width: 400px; - margin: 0 auto; gap: 10px; `; diff --git a/pay-flow/src/pages/Payment/style.ts b/pay-flow/src/pages/Payment/style.ts index 89d6caa..84bd1ff 100644 --- a/pay-flow/src/pages/Payment/style.ts +++ b/pay-flow/src/pages/Payment/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../../components/Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const SummaryLabel = styled.label` margin-top: 12px; diff --git a/pay-flow/src/pages/Shipping/style.ts b/pay-flow/src/pages/Shipping/style.ts index 9767bcb..6bd3b81 100644 --- a/pay-flow/src/pages/Shipping/style.ts +++ b/pay-flow/src/pages/Shipping/style.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; import { colors } from "../../components/Style/theme"; -import type { StyleThemeProps } from "../../type/type"; +import type { StyleThemeProps } from "../../types/type"; export const GridButton = styled.div` display: flex; diff --git a/pay-flow/src/types/api-response.ts b/pay-flow/src/types/api-response.ts new file mode 100644 index 0000000..137ce4d --- /dev/null +++ b/pay-flow/src/types/api-response.ts @@ -0,0 +1,14 @@ +export interface ApiError { + code: string; + message: string; + field?: string; +} + +export interface ApiResponse { + success: boolean; + message: string; + data: T | null; + errors: ApiError[] | null; + timestamp: string; + traceId?: string; +} diff --git a/pay-flow/src/type/type.ts b/pay-flow/src/types/type.ts similarity index 100% rename from pay-flow/src/type/type.ts rename to pay-flow/src/types/type.ts diff --git a/pay-flow/src/utils/mask.ts b/pay-flow/src/utils/mask.ts index d3ed4aa..de872a4 100644 --- a/pay-flow/src/utils/mask.ts +++ b/pay-flow/src/utils/mask.ts @@ -16,3 +16,29 @@ export function maskCpfCnpj(identifier?: string, fallback = ""): string { .replace(/\.(\d{3})(\d)/, ".$1/$2") .replace(/(\d{4})(\d{2})$/, "$1-$2"); } + +export function maskPostalCode(postalCode?: string, fallback = ""): string { + if (!postalCode) return fallback; + + const digits = postalCode.replace(/\D/g, ""); + + return digits.replace(/^(\d{5})(\d)/, "$1-$2").slice(0, 9); +} + +export function maskPhone(phone?: string, fallback = ""): string { + if (!phone) return fallback; + + const digits = phone.replace(/\D/g, ""); + + if (digits.length <= 10) { + return digits + .replace(/^(\d{2})(\d)/, "($1) $2") + .replace(/(\d{4})(\d)/, "$1-$2") + .slice(0, 14); + } + + return digits + .replace(/^(\d{2})(\d)/, "($1) $2") + .replace(/(\d{5})(\d)/, "$1-$2") + .slice(0, 15); +} diff --git a/pay-flow/src/utils/phone.ts b/pay-flow/src/utils/phone.ts deleted file mode 100644 index 50baa45..0000000 --- a/pay-flow/src/utils/phone.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - parsePhoneNumberFromString, - type CountryCode, -} from "libphonenumber-js"; - -export function formatPhoneInternational( - phone?: string, - defaultCountry: CountryCode = "BR", - fallback = "", -) { - if (!phone) return fallback; - - const normalizedPhone = phone.replace(/\D/g, ""); - - const phoneNumber = parsePhoneNumberFromString( - normalizedPhone, - defaultCountry, - ); - - return phoneNumber?.formatNational() ?? phone; -} diff --git a/pay-flow/src/validations/newCustomerSchema.ts b/pay-flow/src/validations/newCustomerSchema.ts index 385835b..19c9e68 100644 --- a/pay-flow/src/validations/newCustomerSchema.ts +++ b/pay-flow/src/validations/newCustomerSchema.ts @@ -3,7 +3,14 @@ import * as yup from "yup"; export const newCustomerSchema = (t: TFunction) => yup.object({ - identifier: yup.string().required(t("customer.identifierRequired")), + identifier: yup + .string() + .required(t("customer.identifierRequired")) + .test("cpf-cnpj", t("customer.identifierInvalid"), (value) => { + if (!value) return false; + const digits = value.replace(/\D/g, ""); + return digits.length === 11 || digits.length === 14; + }), name: yup.string().required(t("customer.nameRequired")), phone: yup .string() @@ -13,18 +20,26 @@ export const newCustomerSchema = (t: TFunction) => .string() .required(t("customer.emailRequired")) .email(t("customer.emailInvalid")), - address: yup.object({ - street: yup.string().required(t("address.streetRequired")), - city: yup.string().required(t("address.cityRequired")), - postalCode: yup - .string() - .required(t("address.postalCodeRequired")) - .matches(/^\d{8}$/, t("address.postalCodeInvalid")), - state: yup.string().required(t("address.stateRequired")), - uf: yup - .string() - .required(t("address.ufRequired")) - .length(2, t("address.ufInvalid")), - country: yup.string().required(t("address.countryRequired")), - }), + address: yup + .array() + .of( + yup.object({ + street: yup.string().required(t("address.streetRequired")), + number: yup.string().optional(), + complement: yup.string().optional(), + neighborhood: yup.string().optional(), + city: yup.string().required(t("address.cityRequired")), + postalCode: yup + .string() + .required(t("address.postalCodeRequired")) + .matches(/^\d{5}-\d{3}$/, t("address.postalCodeInvalid")), + country: yup.string().optional(), + uf: yup + .string() + .required(t("address.ufRequired")) + .matches(/^[A-Z]{2}$/, t("address.ufInvalid")), + }), + ) + .min(1, t("address.addressRequired")) + .required(), });