diff --git a/src/interfaces/Cities.tsx b/src/interfaces/Cities.tsx index 1781619..90796bb 100644 --- a/src/interfaces/Cities.tsx +++ b/src/interfaces/Cities.tsx @@ -9,6 +9,7 @@ export type City = { name: string; isActive: boolean; state: State; + label?: string; }; export type RemoteCity = { diff --git a/src/interfaces/Person.tsx b/src/interfaces/Person.tsx new file mode 100644 index 0000000..a5c15a4 --- /dev/null +++ b/src/interfaces/Person.tsx @@ -0,0 +1,28 @@ +export type Person = { + cpf: string; + password: string; + passwordConfirm: string; + name: string; + phoneNumber: string; + emergencyPhone: string; + emergencyContact: string; + birthDate: string; + avatar: string; + city: string; + lgpdAcceptance: boolean; +}; + +export type RemotePerson = { + cpf: string; + password?: string; + password_confirm?: string; + name: string; + phone: string; + emergency_phone: string; + emergency_contact: string; + birth_date: string; + avatar?: string; + city_id: number; + lgpd_acceptance?: boolean; + validation_uuid?: string; +}; diff --git a/src/pages/RideOfferPage/RideOffer.tsx b/src/pages/RideOfferPage/RideOffer.tsx index 9e5ac96..8f8c294 100644 --- a/src/pages/RideOfferPage/RideOffer.tsx +++ b/src/pages/RideOfferPage/RideOffer.tsx @@ -2,7 +2,15 @@ import { useEffect } from 'react'; import { FieldValues, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { Button, Input, Item, ItemList, PageHeader, Select } from '@components'; +import { + Button, + Input, + InputTextArea, + Item, + ItemList, + PageHeader, + Select, +} from '@components'; import { useAuthContext } from '@contexts/AuthProvider'; export default function RideOfferPage() { @@ -25,7 +33,7 @@ export default function RideOfferPage() { meetingPoint: string; carSpaces: number; departureDate: Date; - returnDate: string; + notes: string; }) => { console.log('form submit'); }) as SubmitHandler; @@ -95,17 +103,17 @@ export default function RideOfferPage() { className="mb-s-200" form={register('departureDate', { required: 'Obrigatório' })} label="Ida" - type="datetime-local" + type="date" error={!!errors.departureDate} caption={errors.departureDate?.message as string} /> -
diff --git a/src/pages/SignupPage/Signup.tsx b/src/pages/SignupPage/Signup.tsx index 81ac137..c5ecc02 100644 --- a/src/pages/SignupPage/Signup.tsx +++ b/src/pages/SignupPage/Signup.tsx @@ -1,40 +1,137 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { FieldValues, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; -import { Button, FileUpload, Input, PageHeader, Select } from '@components'; +import { toast } from 'react-toastify'; + +import { + Button, + Checkbox, + FileUpload, + Input, + PageHeader, + PasswordInput, + Select, +} from '@components'; + +import { formCpfPattern } from '@validations/cpf'; + +import { City } from 'interfaces/Cities'; import { useAuthContext } from '@contexts/AuthProvider'; import { handleErrorForm } from '@services/api'; +import { RemotePerson } from 'interfaces/Person'; +import PersonAPICaller from '@services/api/person'; + +import CitiesAPICaller from '@services/api/cities'; +import { convertToBase64 } from '@utils/file/file'; export default function SignupPage() { const { setError, register, handleSubmit, + watch, + setValue, + getValues, formState: { errors, isSubmitting }, } = useForm(); - const { login, user, isLoadingRequest } = useAuthContext(); + const { user } = useAuthContext(); const navigate = useNavigate(); + const [personData, setPersonData] = useState( + {} as RemotePerson + ); + const [step, setStep] = useState(1); + const [cities, setCities] = useState([]); + const [validationUuid, setValidationUuid] = useState(''); + const [avatar, setAvatar] = useState(); useEffect(() => { if (!!user) navigate('/home'); return; }, [user, navigate]); + useEffect(() => { + CitiesAPICaller.loadCities() + .then((data) => { + setCities(data); + }) + .catch(() => { + handleErrorForm(setError); + }); + }, [setError]); + const onSubmit = ((data: { - password: string; - fullName: string; - birthDate: string; cpf: string; + password: string; + passwordConfirm: string; + name: string; phoneNumber: string; - state: string; - city: string; - emergencyContactName: string; - emergencyContactPhone: string; + emergencyPhone: string; + emergencyContact: string; + birthDate: string; + city: { value: number; label: string }; + lgpdAcceptance: boolean; }) => { - login(data.cpf.toLowerCase(), data.password).catch( - handleErrorForm(setError) - ); + if (!avatar) { + toast.error('Selecione uma foto de perfil'); + return; + } + + if (step === 1) { + setPersonData({ + cpf: data.cpf, + name: data.name, + phone: data.phoneNumber, + emergency_phone: data.emergencyPhone, + emergency_contact: data.emergencyContact, + birth_date: data.birthDate, + city_id: data.city.value, + }); + + convertToBase64(avatar).then((avatarBase64) => { + setPersonData((prevState) => ({ ...prevState, avatar: avatarBase64 })); + }); + setStep(2); + } else if (step === 3) { + PersonAPICaller.register({ + ...personData, + validation_uuid: validationUuid, + password: data.password, + password_confirm: data.passwordConfirm, + lgpd_acceptance: data.lgpdAcceptance, + }) + .then(() => { + toast.success('Cadastro realizado com sucesso'); + navigate('/login'); + }) + .catch((error) => { + console.log(error); + }); + + return; + } + }) as SubmitHandler; + + useEffect(() => { + if (step === 2) { + PersonAPICaller.sendCode(personData.phone).then((data) => { + setValidationUuid(data.validationUuid); + setPersonData((prevState) => ({ + ...prevState, + validation_uuid: data.validationUuid, + })); + }); + } + }, [personData.phone, step]); + + const onConfirmCode = ((data: { validationCode: string }) => { + PersonAPICaller.checkCode( + personData.phone, + data.validationCode, + validationUuid + ).then(() => { + setStep(3); + }); }) as SubmitHandler; return ( @@ -53,120 +150,266 @@ export default function SignupPage() { className="form-max-height w-100" onSubmit={handleSubmit(onSubmit)} > - - - - - - - - -
- -
-
+ {step === 1 && ( + <> + { + if (file) { + setAvatar(file); + } + }} + accept="image/*" + /> + + + + + + + + + -
-
- -
-
+
+ +
+
+ +
+ +
+ +
+
+ + )} + + {step === 3 && ( + <> + + + + +
+ +
+
+ +
+ +
+ +
+
+ + )} diff --git a/src/services/api/cities/calls.ts b/src/services/api/cities/calls.ts index 829d3d1..a294cc5 100644 --- a/src/services/api/cities/calls.ts +++ b/src/services/api/cities/calls.ts @@ -1,9 +1,9 @@ import { api } from '..'; export const list = async () => { - return api.get('/cities'); + return api.get('/cities/'); }; export const retrieve = async (id: string) => { - return api.get(`/cities/${id}`); + return api.get(`/cities/${id}/`); }; diff --git a/src/services/api/cities/index.ts b/src/services/api/cities/index.ts index 6c320de..43a6a31 100644 --- a/src/services/api/cities/index.ts +++ b/src/services/api/cities/index.ts @@ -9,6 +9,7 @@ export default class CitiesAPICaller { return cities.data.map((city) => ({ ...city, isActive: city.is_active, + label: `${city.name} - ${city.state.code}`, })); }; diff --git a/src/services/api/person/calls.ts b/src/services/api/person/calls.ts new file mode 100644 index 0000000..22d01e2 --- /dev/null +++ b/src/services/api/person/calls.ts @@ -0,0 +1,22 @@ +import { RemotePerson } from 'interfaces/Person'; +import { api } from '..'; + +export const register = async (params: RemotePerson) => { + return api.post('/person/register/', { ...params }); +}; + +export const sendCode = async (phone: string) => { + return api.post('/validate_phone/send_code/', { phone }); +}; + +export const checkCode = async ( + phone: string, + code: string, + validationUuid: string +) => { + return api.post('/validate_phone/check_code/', { + phone, + code, + validation_uuid: validationUuid, + }); +}; diff --git a/src/services/api/person/cities.spec.ts b/src/services/api/person/cities.spec.ts new file mode 100644 index 0000000..c55803a --- /dev/null +++ b/src/services/api/person/cities.spec.ts @@ -0,0 +1,9 @@ +import { api } from '..'; + +import { register } from '.'; + +describe('Person API Caller', () => { + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/src/services/api/person/index.ts b/src/services/api/person/index.ts new file mode 100644 index 0000000..151fbb5 --- /dev/null +++ b/src/services/api/person/index.ts @@ -0,0 +1,29 @@ +import { checkCode, register, sendCode } from './calls'; +import { RemotePerson } from 'interfaces/Person'; +export * from './calls'; + +export default class PersonAPICaller { + static register = async (remotePerson: RemotePerson) => { + const person = await register(remotePerson); + console.log(person); + }; + + static sendCode = async (phone: string) => { + const response = await sendCode(phone); + + return { + message: response.data.message, + validationUuid: response.data.validation_uuid, + }; + }; + + static checkCode = async ( + phone: string, + code: string, + validationUuid: string + ) => { + const response = await checkCode(phone, code, validationUuid); + + return response.data; + }; +} diff --git a/src/utils/file/file.ts b/src/utils/file/file.ts new file mode 100644 index 0000000..8a6b237 --- /dev/null +++ b/src/utils/file/file.ts @@ -0,0 +1,8 @@ +export function convertToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = (error) => reject(error); + reader.readAsDataURL(file); + }); +}