From b645b7e74a63f2309a27aef47b6d317a98081ed0 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 18 Aug 2026 20:46:09 -0300 Subject: [PATCH 1/2] feat(create-account): implementa criacao de conta --- .../components/CreateAccountForm/index.tsx | 184 ++++++++++++++++++ .../src/components/CreateAccountForm/style.ts | 24 +++ .../src/components/CustomerFormCard/index.tsx | 2 +- pay-flow/src/contexts/Auth/AuthContext.ts | 8 +- pay-flow/src/contexts/Auth/AuthProvider.tsx | 32 ++- pay-flow/src/contexts/Auth/auth.config.ts | 12 ++ pay-flow/src/contexts/Auth/auth.service.ts | 17 +- pay-flow/src/i18n/locales/pt/common.json | 5 +- pay-flow/src/pages/CreateAccount/index.tsx | 119 +---------- pay-flow/src/pages/Customer/index.tsx | 2 +- .../src/validations/createAccountSchema.ts | 14 +- 11 files changed, 289 insertions(+), 130 deletions(-) create mode 100644 pay-flow/src/components/CreateAccountForm/index.tsx create mode 100644 pay-flow/src/components/CreateAccountForm/style.ts diff --git a/pay-flow/src/components/CreateAccountForm/index.tsx b/pay-flow/src/components/CreateAccountForm/index.tsx new file mode 100644 index 0000000..b64520c --- /dev/null +++ b/pay-flow/src/components/CreateAccountForm/index.tsx @@ -0,0 +1,184 @@ +import Card from "../Card"; +import { Row } from "../Row"; +import { Col } from "../Col"; +import Input from "../Input"; +import { ActionFooter } from "../ActionFooter"; +import { useTranslation } from "react-i18next"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { createAccountSchema } from "../../validations/createAccountSchema"; +import { VerificationCode } from "../VerificationCode"; +import { useLoading } from "../../hooks/useLoading"; +import { useAuth } from "../../contexts/Auth/useAuth"; +import i18n from "../../i18n"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; +import { AxiosError } from "axios"; + +export interface CreateAccountFormData { + name: string; + email: string; + password: string; + confirmPassword: string; + code: string; +} + +function CustomerFormCard() { + const { t } = useTranslation(); + const { execute } = useLoading(); + const { registerAccount, verifyEmail } = useAuth(); + + const navigate = useNavigate(); + + const [codeSent, setCodeSent] = useState(false); + + const { + register, + reset, + setValue, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(createAccountSchema(t, codeSent)), + mode: "onTouched", + }); + + const handleClear = () => { + reset({ + name: "", + email: "", + password: "", + confirmPassword: "", + code: "", + }); + setCodeSent(false); + }; + + const onSubmit = async (data: CreateAccountFormData) => { + if (!codeSent) { + try { + await execute(() => + registerAccount({ + name: data.name, + email: data.email, + passwordHash: data.password, + language: i18n.language, + }), + ); + toast.success(t("createAccount.sentEmail")); + setCodeSent(true); + } catch (error) { + const isEmailConflict = + error instanceof AxiosError && error.response?.status === 409; + if (isEmailConflict) { + toast.error(t("createAccount.emailAlreadyExists")); + return; + } + toast.error(t("createAccount.sendCodeError")); + } + return; + } + + try { + await execute(() => verifyEmail({ email: data.email, code: data.code })); + toast.success(t("createAccount.accountCreated")); + navigate("/login", { replace: true }); + } catch { + toast.error(t("createAccount.invalidCode")); + } + }; + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + {codeSent && ( + + + + setValue("code", value, { shouldValidate: true }) + } + error={errors.code?.message} + /> + + + )} + + + + + + + + + + +
+ ); +} + +export default CustomerFormCard; diff --git a/pay-flow/src/components/CreateAccountForm/style.ts b/pay-flow/src/components/CreateAccountForm/style.ts new file mode 100644 index 0000000..d20be9b --- /dev/null +++ b/pay-flow/src/components/CreateAccountForm/style.ts @@ -0,0 +1,24 @@ +import styled from "styled-components"; +import { colors } from "../Style/theme"; +import type { StyleThemeProps } from "../../types/type"; + +export const RowItem = styled.div` + display: flex; + align-items: center; + gap: 8px; + color: ${({ theme }) => + theme === "dark" ? colors.darkText : colors.lightText}; +`; + +export const Label = styled.span` + flex: 1; + font-size: 14px; +`; + +export const Value = styled.span` + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; +`; diff --git a/pay-flow/src/components/CustomerFormCard/index.tsx b/pay-flow/src/components/CustomerFormCard/index.tsx index 8e84568..de2674f 100644 --- a/pay-flow/src/components/CustomerFormCard/index.tsx +++ b/pay-flow/src/components/CustomerFormCard/index.tsx @@ -124,7 +124,7 @@ function CustomerFormCard() { } finally { handleClear(); } - // reset(); + reset(); }; const handleChangeIdentifier = (value: string) => { diff --git a/pay-flow/src/contexts/Auth/AuthContext.ts b/pay-flow/src/contexts/Auth/AuthContext.ts index be5f4bd..5f552e8 100644 --- a/pay-flow/src/contexts/Auth/AuthContext.ts +++ b/pay-flow/src/contexts/Auth/AuthContext.ts @@ -1,5 +1,9 @@ import { createContext } from "react"; -import type { User } from "./auth.config"; +import type { + RegisterAccountPayload, + User, + VerifyEmailPayload, +} from "./auth.config"; interface AuthContextData { user: User | null; @@ -7,6 +11,8 @@ interface AuthContextData { setUser: (user: User | null) => void; logout: () => Promise; refreshUser: () => Promise; + registerAccount: (payload: RegisterAccountPayload) => Promise; + verifyEmail: (payload: VerifyEmailPayload) => Promise; } export const AuthContext = createContext( diff --git a/pay-flow/src/contexts/Auth/AuthProvider.tsx b/pay-flow/src/contexts/Auth/AuthProvider.tsx index 6c5201b..ff5be3d 100644 --- a/pay-flow/src/contexts/Auth/AuthProvider.tsx +++ b/pay-flow/src/contexts/Auth/AuthProvider.tsx @@ -1,12 +1,30 @@ import { useState, useEffect, type ReactNode } from "react"; import { AuthContext } from "./AuthContext"; -import { getCurrentUser, logoutRequest } from "./auth.service"; -import type { User } from "./auth.config"; +import { + getCurrentUser, + logoutRequest, + registerAccountApi, + verifyEmailRequest, +} from "./auth.service"; +import type { + RegisterAccountPayload, + User, + VerifyEmailPayload, +} from "./auth.config"; export const AuthProvider = ({ children }: { children: ReactNode }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); + const registerAccount = async (payload: RegisterAccountPayload) => { + await registerAccountApi(payload); + }; + + const verifyEmail = async (payload: VerifyEmailPayload) => { + await verifyEmailRequest(payload); + await refreshUser(); + }; + const refreshUser = async (): Promise => { setLoading(true); try { @@ -41,7 +59,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { return ( {children} diff --git a/pay-flow/src/contexts/Auth/auth.config.ts b/pay-flow/src/contexts/Auth/auth.config.ts index 4dc63c8..190183e 100644 --- a/pay-flow/src/contexts/Auth/auth.config.ts +++ b/pay-flow/src/contexts/Auth/auth.config.ts @@ -7,3 +7,15 @@ export interface AuthResponse { token: string; user: User; } + +export interface RegisterAccountPayload { + name: string; + email: string; + language: string; + passwordHash: string; +} + +export interface VerifyEmailPayload { + email: string; + code: string; +} diff --git a/pay-flow/src/contexts/Auth/auth.service.ts b/pay-flow/src/contexts/Auth/auth.service.ts index e66d486..673c2fb 100644 --- a/pay-flow/src/contexts/Auth/auth.service.ts +++ b/pay-flow/src/contexts/Auth/auth.service.ts @@ -1,5 +1,10 @@ import { api } from "../../api/api"; -import type { AuthResponse, User } from "./auth.config"; +import type { + AuthResponse, + RegisterAccountPayload, + User, + VerifyEmailPayload, +} from "./auth.config"; export const loginRequest = async (payload: User): Promise => { const res = await api.post("/auth/login", payload); @@ -14,3 +19,13 @@ export const getCurrentUser = async (): Promise => { export const logoutRequest = async () => { await api.post("/auth/logout"); }; + +export const registerAccountApi = async (payload: RegisterAccountPayload) => { + const res = await api.post("/user", payload); + return res.data; +}; + +export const verifyEmailRequest = async (payload: VerifyEmailPayload) => { + const res = await api.post("/auth/verify-email", payload); + return res.data; +}; diff --git a/pay-flow/src/i18n/locales/pt/common.json b/pay-flow/src/i18n/locales/pt/common.json index cdf1a2d..64fb60a 100644 --- a/pay-flow/src/i18n/locales/pt/common.json +++ b/pay-flow/src/i18n/locales/pt/common.json @@ -27,7 +27,7 @@ }, "createAccount": { "createAccount": "Criar conta", - "titleCreate": "Defina sua senha", + "titleCreate": "Nova conta", "titleEmail": "Crie sua conta", "verificationCode": "Código de verificação" }, @@ -102,7 +102,8 @@ "passwordMin": "A senha deve ter pelo menos doze caracteres", "passwordNoSpaces": "A senha não pode conter espaços", "passwordRequired": "Senha é obrigatória", - "signIn": "Fazer login" + "signIn": "Fazer login", + "placeholderName": "Nome completo" }, "payment": { "cash": "Dinheiro", diff --git a/pay-flow/src/pages/CreateAccount/index.tsx b/pay-flow/src/pages/CreateAccount/index.tsx index c98dab2..84c60b1 100644 --- a/pay-flow/src/pages/CreateAccount/index.tsx +++ b/pay-flow/src/pages/CreateAccount/index.tsx @@ -2,60 +2,12 @@ import { useTranslation } from "react-i18next"; import { Body, Page } from "../Login/style"; import HeaderControls from "../../components/HeaderControls"; import { useTheme } from "../../contexts/Theme/useTheme"; -import Input from "../../components/Input"; -import { useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { createAccountSchema } from "../../validations/createAccountSchema"; -import { GridButton, Title } from "../Shipping/style"; -import { useState } from "react"; -import Button from "../../components/Button"; -import { ArrowLeft, Mail, UserPlus } from "lucide-react"; -import { toast } from "react-toastify"; -import { VerificationCode } from "../../components/VerificationCode"; - -type CreateAccountStep = "email" | "create"; - -interface LoginFormData { - name: string; - email: string; - password: string; - confirmPassword: string; - code: string; -} +import CreateAccountForm from "../../components/CreateAccountForm"; function CreateAccount() { const { t } = useTranslation(); const { theme } = useTheme(); - const [step, setStep] = useState("email"); - const [code, setCode] = useState(""); - - const { - register, - trigger, - formState: { errors }, - } = useForm({ - resolver: yupResolver(createAccountSchema(t)), - mode: "onTouched", - }); - - const goToNextStep = async () => { - const isValid = await trigger("email"); - if (!isValid) return; - // API envio do código - toast.success(t("resetPassword.sentEmail")); - setStep("create"); - }; - - const handleResetPassword = async () => { - const isValid = await trigger(["code", "password", "confirmPassword"]); - if (!isValid) return; - console.log(code); - // API validar código - // API criar conta - toast.success(t("resetPassword.passwordChanged")); - }; - return ( - - {step === "email" - ? t("createAccount.titleEmail") - : t("createAccount.titleCreate")} - - {step === "email" && ( - <> - - - - - )} - {step === "create" && ( - <> - setCode(value)} - error={errors.code?.message} - {...register("code")} - /> - - - - - - - - )} + ); diff --git a/pay-flow/src/pages/Customer/index.tsx b/pay-flow/src/pages/Customer/index.tsx index e336f18..0414bef 100644 --- a/pay-flow/src/pages/Customer/index.tsx +++ b/pay-flow/src/pages/Customer/index.tsx @@ -60,7 +60,7 @@ function Customer() { phone: previewCustomer?.phone, email: previewCustomer?.email, country: previewCustomer?.country, - address: previewCustomer?.address, + addresses: previewCustomer?.addresses || [], }); navigate("/checkout"); }; diff --git a/pay-flow/src/validations/createAccountSchema.ts b/pay-flow/src/validations/createAccountSchema.ts index 7a3c7ac..2bf8146 100644 --- a/pay-flow/src/validations/createAccountSchema.ts +++ b/pay-flow/src/validations/createAccountSchema.ts @@ -1,9 +1,9 @@ import type { TFunction } from "i18next"; import * as yup from "yup"; -export const createAccountSchema = (t: TFunction) => +export const createAccountSchema = (t: TFunction, codeSent: boolean) => yup.object({ - name: yup.string().trim().uppercase().required(t("login.emailRequired")), + name: yup.string().trim().uppercase().required(t("login.nameRequired")), email: yup .string() @@ -12,8 +12,6 @@ export const createAccountSchema = (t: TFunction) => .email(t("login.emailInvalid")) .required(t("login.emailRequired")), - code: yup.string().required(t("login.codeRequired")), - password: yup .string() .min(12, t("login.passwordMin")) @@ -24,5 +22,13 @@ export const createAccountSchema = (t: TFunction) => .string() .min(12, t("login.passwordMin")) .matches(/^\S+$/, t("login.passwordNoSpaces")) + .oneOf([yup.ref("password")], t("login.passwordsMustMatch")) .required(t("login.passwordRequired")), + + code: yup + .string() + .default("") + .test("code-required", t("login.codeRequired"), (value) => + codeSent ? !!value : true, + ), }); From a9e6eacfd4f0ddf35fd81750687e46d8a78c1448 Mon Sep 17 00:00:00 2001 From: Bruno Date: Tue, 18 Aug 2026 21:01:48 -0300 Subject: [PATCH 2/2] chore(create-account): resolve conflitos --- pay-flow/src/contexts/Auth/auth.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pay-flow/src/contexts/Auth/auth.service.ts b/pay-flow/src/contexts/Auth/auth.service.ts index 673c2fb..9b2ef7b 100644 --- a/pay-flow/src/contexts/Auth/auth.service.ts +++ b/pay-flow/src/contexts/Auth/auth.service.ts @@ -20,12 +20,13 @@ export const logoutRequest = async () => { await api.post("/auth/logout"); }; +//TODO: criar um context para user export const registerAccountApi = async (payload: RegisterAccountPayload) => { const res = await api.post("/user", payload); return res.data; }; export const verifyEmailRequest = async (payload: VerifyEmailPayload) => { - const res = await api.post("/auth/verify-email", payload); + const res = await api.post("/user/verify-email", payload); return res.data; };