Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions pay-flow/src/components/CreateAccountForm/index.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);

const {
register,
reset,
setValue,
handleSubmit,
formState: { errors },
} = useForm<CreateAccountFormData>({
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 (
<form onSubmit={handleSubmit(onSubmit)}>
<Row>
<Col>
<Row>
<Col xs={12}>
<Card title={t("createAccount.titleCreate")}>
<Row>
<Col>
<Input
text=""
label={t("login.name")}
placeholder={t("login.placeholderName")}
error={errors.name?.message}
disabled={codeSent}
{...register("name")}
/>
</Col>
</Row>
<Row>
<Col>
<Input
text=""
label={t("login.email")}
placeholder={t("login.enterEmail")}
uppercase={false}
error={errors.email?.message}
disabled={codeSent}
{...register("email")}
/>
</Col>
</Row>
<Row>
<Col xs={6}>
<Input
text=""
type="password"
label={t("login.password")}
placeholder={t("login.enterPassword")}
uppercase={false}
error={errors.password?.message}
disabled={codeSent}
{...register("password")}
/>
</Col>
<Col xs={6}>
<Input
text=""
type="password"
label={t("resetPassword.confirmPassword")}
placeholder={t("login.confirmPassword")}
uppercase={false}
error={errors.confirmPassword?.message}
disabled={codeSent}
{...register("confirmPassword")}
/>
</Col>
</Row>
{codeSent && (
<Row>
<Col>
<VerificationCode
label={t("createAccount.verificationCode")}
onComplete={(value) =>
setValue("code", value, { shouldValidate: true })
}
error={errors.code?.message}
/>
</Col>
</Row>
)}
</Card>
</Col>
</Row>
<Row>
<Col>
<ActionFooter
confirmText={
codeSent
? t("createAccount.createAccount")
: t("resetPassword.sendCode")
}
onClear={handleClear}
/>
</Col>
</Row>
</Col>
</Row>
</form>
);
}

export default CustomerFormCard;
24 changes: 24 additions & 0 deletions pay-flow/src/components/CreateAccountForm/style.ts
Original file line number Diff line number Diff line change
@@ -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<StyleThemeProps>`
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;
`;
2 changes: 1 addition & 1 deletion pay-flow/src/components/CustomerFormCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ function CustomerFormCard() {
} finally {
handleClear();
}
// reset();
reset();
};

const handleChangeIdentifier = (value: string) => {
Expand Down
8 changes: 7 additions & 1 deletion pay-flow/src/contexts/Auth/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { createContext } from "react";
import type { User } from "./auth.config";
import type {
RegisterAccountPayload,
User,
VerifyEmailPayload,
} from "./auth.config";

interface AuthContextData {
user: User | null;
loading: boolean;
setUser: (user: User | null) => void;
logout: () => Promise<void>;
refreshUser: () => Promise<User | null>;
registerAccount: (payload: RegisterAccountPayload) => Promise<void>;
verifyEmail: (payload: VerifyEmailPayload) => Promise<void>;
}

export const AuthContext = createContext<AuthContextData>(
Expand Down
32 changes: 29 additions & 3 deletions pay-flow/src/contexts/Auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<User | null>(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<User | null> => {
setLoading(true);
try {
Expand Down Expand Up @@ -41,7 +59,15 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {

return (
<AuthContext.Provider
value={{ user, loading, setUser, logout, refreshUser }}
value={{
user,
loading,
setUser,
logout,
refreshUser,
registerAccount,
verifyEmail,
}}
>
{children}
</AuthContext.Provider>
Expand Down
12 changes: 12 additions & 0 deletions pay-flow/src/contexts/Auth/auth.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
18 changes: 17 additions & 1 deletion pay-flow/src/contexts/Auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -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<AuthResponse> => {
const res = await api.post("/auth/login", payload);
Expand All @@ -14,3 +19,14 @@ export const getCurrentUser = async (): Promise<User> => {
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("/user/verify-email", payload);
return res.data;
};
5 changes: 3 additions & 2 deletions pay-flow/src/i18n/locales/pt/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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",
Expand Down
Loading
Loading