From 694f8f54dec2cb60d1284688a6de0dd14530968e Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 10:10:28 -0300 Subject: [PATCH 01/59] Refactor sign-in component to add sign-up link --- .../_components/side-div.tsx | 0 .../_components/terms-and-conditions.tsx | 22 +++++++ .../login/_components/signin-div.tsx | 18 +----- .../src/app/{ => (auth)}/login/page.tsx | 2 +- .../(auth)/signup/_components/signup-div.tsx | 34 +++++++++++ .../src/app/(auth)/signup/page.tsx | 21 +++++++ .../signup/components/email-password-form.tsx | 57 +++++++++++++++++++ .../slices/general/features/signup/index.ts | 1 + .../general/features/signup/signup-form.tsx | 49 ++++++++++++++++ .../general/features/signup/signup.hook.ts | 14 +++++ .../general/features/signup/signup.lib.ts | 25 ++++++++ 11 files changed, 226 insertions(+), 17 deletions(-) rename crazystacknextjs/src/app/{login => (auth)}/_components/side-div.tsx (100%) create mode 100644 crazystacknextjs/src/app/(auth)/_components/terms-and-conditions.tsx rename crazystacknextjs/src/app/{ => (auth)}/login/_components/signin-div.tsx (65%) rename crazystacknextjs/src/app/{ => (auth)}/login/page.tsx (92%) create mode 100644 crazystacknextjs/src/app/(auth)/signup/_components/signup-div.tsx create mode 100644 crazystacknextjs/src/app/(auth)/signup/page.tsx create mode 100644 crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx create mode 100644 crazystacknextjs/src/slices/general/features/signup/index.ts create mode 100644 crazystacknextjs/src/slices/general/features/signup/signup-form.tsx create mode 100644 crazystacknextjs/src/slices/general/features/signup/signup.hook.ts create mode 100644 crazystacknextjs/src/slices/general/features/signup/signup.lib.ts diff --git a/crazystacknextjs/src/app/login/_components/side-div.tsx b/crazystacknextjs/src/app/(auth)/_components/side-div.tsx similarity index 100% rename from crazystacknextjs/src/app/login/_components/side-div.tsx rename to crazystacknextjs/src/app/(auth)/_components/side-div.tsx diff --git a/crazystacknextjs/src/app/(auth)/_components/terms-and-conditions.tsx b/crazystacknextjs/src/app/(auth)/_components/terms-and-conditions.tsx new file mode 100644 index 0000000..a0436e6 --- /dev/null +++ b/crazystacknextjs/src/app/(auth)/_components/terms-and-conditions.tsx @@ -0,0 +1,22 @@ +import Link from "next/link"; + +export const TermsAndConditions = () => { + return ( +

+ Clicando em continuar, você concorda com nossos{" "} + + Termos de serviço + {" "} + e{" "} + + Política de privacidade + +

+ ); +}; diff --git a/crazystacknextjs/src/app/login/_components/signin-div.tsx b/crazystacknextjs/src/app/(auth)/login/_components/signin-div.tsx similarity index 65% rename from crazystacknextjs/src/app/login/_components/signin-div.tsx rename to crazystacknextjs/src/app/(auth)/login/_components/signin-div.tsx index ea6f90c..56ba575 100644 --- a/crazystacknextjs/src/app/login/_components/signin-div.tsx +++ b/crazystacknextjs/src/app/(auth)/login/_components/signin-div.tsx @@ -1,5 +1,6 @@ import { LoginForm } from "@/slices/general/features/login"; import Link from "next/link"; +import { TermsAndConditions } from "../../_components/terms-and-conditions"; export const SignInDiv = () => { return ( @@ -18,22 +19,7 @@ export const SignInDiv = () => { -

- Clicando em continuar, você concorda com nossos{" "} - - Termos de serviço - {" "} - e{" "} - - Política de privacidade - -

+

Ainda não tem uma conta?{" "} { + return ( +

+
+
+

+ Criar conta +

+

+ Digite seu email e senha para criar sua conta +

+
+
+ + +

+ Já tem uma conta?{" "} + + Entre aqui + +

+
+ ); +}; diff --git a/crazystacknextjs/src/app/(auth)/signup/page.tsx b/crazystacknextjs/src/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..51df895 --- /dev/null +++ b/crazystacknextjs/src/app/(auth)/signup/page.tsx @@ -0,0 +1,21 @@ +import type { Metadata } from "next"; +import { whitelabel } from "@/application/whitelabel"; +import { SignUpDiv } from "./_components/signup-div"; +import { SideDiv } from "../_components/side-div"; + +export const metadata: Metadata = { + title: `${whitelabel.systemName} | Entrar`, + description: `Página de login do ${whitelabel.systemName}. Faça login para acessar o sistema.`, +}; +export default function Login() { + return ( +
+ + +
+ ); +} diff --git a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx new file mode 100644 index 0000000..cdd9fa7 --- /dev/null +++ b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx @@ -0,0 +1,57 @@ +import { Button } from "@/components/ui/button"; +import { Icons } from "@/components/ui/icons"; +import { FormField } from "@/shared/ui/atoms/form-field"; +import { FieldValues, FormState, SubmitHandler } from "react-hook-form"; +import { useTranslation } from "react-i18next"; + +interface EmailPasswordFormProps { + formState: FormState; + handleSubmit: ( + callback: SubmitHandler, + ) => (e?: React.BaseSyntheticEvent) => Promise; + register: any; + handleSignUp: any; +} + +export const EmailPasswordForm = ({ + formState, + handleSubmit, + register, + handleSignUp, +}: EmailPasswordFormProps) => { + const { t } = useTranslation(["PAGES"]); + return ( +
+
+ + + +
+
+ ); +}; diff --git a/crazystacknextjs/src/slices/general/features/signup/index.ts b/crazystacknextjs/src/slices/general/features/signup/index.ts new file mode 100644 index 0000000..0c1ce9b --- /dev/null +++ b/crazystacknextjs/src/slices/general/features/signup/index.ts @@ -0,0 +1 @@ +export { SignUpForm } from "./signup-form"; diff --git a/crazystacknextjs/src/slices/general/features/signup/signup-form.tsx b/crazystacknextjs/src/slices/general/features/signup/signup-form.tsx new file mode 100644 index 0000000..b004d81 --- /dev/null +++ b/crazystacknextjs/src/slices/general/features/signup/signup-form.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useTranslation } from "react-i18next"; +import { useSignUp } from "./signup.hook"; +import { cn } from "@/lib/utils"; +import { EmailPasswordForm } from "./components/email-password-form"; +import { SocialButton } from "@/shared/ui/atoms/social-button/social-button"; +import { Icons } from "@/components/ui/icons"; + +interface SignUpFormProps extends React.HTMLAttributes {} + +export function SignUpForm({ className, ...props }: SignUpFormProps) { + const { formState, handleSubmit, register, handleSignUp } = useSignUp(); + + return ( +
+ + +
+ ); +} +const GoogleSignUpSection = ({ formState }: { formState: any }) => { + const { t } = useTranslation(["PAGES"]); + + return ( + <> +
+
+ +
+
+ + {t("PAGES:AUTH_PAGE.orContinueWith", { + defaultValue: "Or continue with", + })} + +
+
+ + Google + + + ); +}; diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts b/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts new file mode 100644 index 0000000..d450785 --- /dev/null +++ b/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts @@ -0,0 +1,14 @@ +import { useAuth } from "@/shared/libs/contexts/AuthContext"; +import { useSignUpLib, type SubmitSignUpHandler } from "./signup.lib"; + +export const useSignUp = () => { + const { signup } = useAuth(); + const { handleSubmit, register, formState } = useSignUpLib(); + const handleSignUp: SubmitSignUpHandler = async (data) => { + await signup({ + ...data, + passwordConfirmation: data.password, + }); + }; + return { handleSubmit, register, formState, handleSignUp }; +}; diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts new file mode 100644 index 0000000..14eeeb3 --- /dev/null +++ b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts @@ -0,0 +1,25 @@ +import * as yup from "yup"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { SubmitHandler, useForm } from "react-hook-form"; + +export interface SignUpFormData { + email: string; + password: string; + name: string; + phone: string; +} +export type SubmitSignUpHandler = SubmitHandler; +export const signupSchema = yup.object({ + email: yup.string().email("Email inválido").required("Email é obrigatório"), + password: yup.string().required("Senha é obrigatória"), + name: yup.string().required("Nome é obrigatório"), + phone: yup.string().required("Telefone é obrigatório"), +}); +export type YupSchema = yup.InferType; + +export const useSignUpLib = () => { + const formProps = useForm({ + resolver: yupResolver(signupSchema), + }); + return formProps; +}; From 08bbea6264ae3ecfe40138df52302d8ea7697008 Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 10:14:20 -0300 Subject: [PATCH 02/59] Refactor sign-in component to update sign-up button text --- .../features/signup/components/email-password-form.tsx | 2 +- .../src/slices/general/features/signup/signup.lib.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx index cdd9fa7..a7a35fc 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx @@ -49,7 +49,7 @@ export const EmailPasswordForm = ({ {formState.isSubmitting && ( )} - {t("PAGES:AUTH_PAGE.signIn", { defaultValue: "Entrar" })} + {t("PAGES:AUTH_PAGE.signUp", { defaultValue: "Criar conta" })} diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts index 14eeeb3..55c0b1d 100644 --- a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts +++ b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts @@ -5,6 +5,7 @@ import { SubmitHandler, useForm } from "react-hook-form"; export interface SignUpFormData { email: string; password: string; + passwordConfirmation: string; name: string; phone: string; } @@ -12,6 +13,10 @@ export type SubmitSignUpHandler = SubmitHandler; export const signupSchema = yup.object({ email: yup.string().email("Email inválido").required("Email é obrigatório"), password: yup.string().required("Senha é obrigatória"), + passwordConfirmation: yup + .string() + .oneOf([yup.ref("password")], "As senhas não correspondem") + .required("Confirmação de senha é obrigatória"), name: yup.string().required("Nome é obrigatório"), phone: yup.string().required("Telefone é obrigatório"), }); From 2cd1f4ef990bad96ffb87841674a463d67f218c5 Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 10:17:09 -0300 Subject: [PATCH 03/59] Refactor sign-in component to update sign-up button text --- .../shared/ui/atoms/form-field/form-field.tsx | 7 +--- .../signup/components/email-password-form.tsx | 37 ++++++++++++++++++- .../general/features/signup/signup-form.tsx | 1 + .../general/features/signup/signup.lib.ts | 3 ++ 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/crazystacknextjs/src/shared/ui/atoms/form-field/form-field.tsx b/crazystacknextjs/src/shared/ui/atoms/form-field/form-field.tsx index d26f70c..a1a96d7 100644 --- a/crazystacknextjs/src/shared/ui/atoms/form-field/form-field.tsx +++ b/crazystacknextjs/src/shared/ui/atoms/form-field/form-field.tsx @@ -1,7 +1,6 @@ import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; -// Molecules interface FormFieldProps { label: string; id: string; @@ -22,15 +21,13 @@ export const FormField = ({ error, }: FormFieldProps) => (
- + -
+
+ + +
); } + const GoogleSignUpSection = ({ formState }: { formState: any }) => { const { t } = useTranslation(["PAGES"]); diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts index 55c0b1d..d4fedef 100644 --- a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts +++ b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts @@ -9,7 +9,9 @@ export interface SignUpFormData { name: string; phone: string; } + export type SubmitSignUpHandler = SubmitHandler; + export const signupSchema = yup.object({ email: yup.string().email("Email inválido").required("Email é obrigatório"), password: yup.string().required("Senha é obrigatória"), @@ -20,6 +22,7 @@ export const signupSchema = yup.object({ name: yup.string().required("Nome é obrigatório"), phone: yup.string().required("Telefone é obrigatório"), }); + export type YupSchema = yup.InferType; export const useSignUpLib = () => { From acecbcf2e38ecfa643c44965793b89d2b752d985 Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 11:25:47 -0300 Subject: [PATCH 04/59] Refactor sign-in component to add phone input --- .../signup/components/email-password-form.tsx | 4 +- .../signup/components/phone-input.tsx | 379 ++++++++++++++++++ 2 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx diff --git a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx index ab1c716..9492dc6 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx @@ -3,6 +3,7 @@ import { Icons } from "@/components/ui/icons"; import { FormField } from "@/shared/ui/atoms/form-field"; import { FieldValues, FormState, SubmitHandler } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { PhoneInput } from "./phone-input"; interface EmailPasswordFormProps { formState: FormState; @@ -45,7 +46,7 @@ export const EmailPasswordForm = ({ register={register} error={formState.errors.email?.message?.toString()} /> - + + + +
+ + setSearchTerm(e.target.value)} + className="border-0 bg-transparent text-white focus-visible:ring-0 focus-visible:ring-offset-0" + /> +
+ + {filteredCountries.map((country) => ( + { + setSelectedCountry(country); + setFormData({ ...formData, phone: "" }); + setPhoneError(""); + setIsPhoneValid(false); + }} + className="flex items-center gap-2 text-white hover:bg-gray-700" + > + + {country.name} + {country.name} (+{country.code}) + + + +{country.phone} + + {selectedCountry.code === country.code && ( + + )} + + ))} + +
+ +
+ + {phoneError && ( +
+ + {phoneError} +
+ )} +
+
+
+ ); +}; From e7336fc9dd4c8b5b03df50065d939ebd10d44d54 Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 11:38:16 -0300 Subject: [PATCH 05/59] Refactor sign-in component to add phone input and update sign-up button text --- .../signup/components/email-password-form.tsx | 3 +++ .../features/signup/components/phone-input.tsx | 14 ++++++++------ .../slices/general/features/signup/signup-form.tsx | 4 +++- .../slices/general/features/signup/signup.hook.ts | 4 ++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx index 9492dc6..60632a1 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/email-password-form.tsx @@ -12,6 +12,7 @@ interface EmailPasswordFormProps { ) => (e?: React.BaseSyntheticEvent) => Promise; register: any; handleSignUp: any; + formProps: any; } export const EmailPasswordForm = ({ @@ -19,6 +20,7 @@ export const EmailPasswordForm = ({ handleSubmit, register, handleSignUp, + formProps, }: EmailPasswordFormProps) => { const { t } = useTranslation(["PAGES"]); return ( @@ -56,6 +58,7 @@ export const EmailPasswordForm = ({ disabled={formState.isSubmitting} register={register} error={formState.errors.phone?.message?.toString()} + formProps={formProps} /> { + console.log({ formProps }); const [formData, setFormData] = useState({ name: "", phone: "", @@ -290,7 +292,7 @@ export const PhoneInput = ({ - -
+ +
setSearchTerm(e.target.value)} - className="border-0 bg-transparent text-white focus-visible:ring-0 focus-visible:ring-offset-0" + className="border-0 bg-transparent text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0" />
@@ -327,7 +329,7 @@ export const PhoneInput = ({ setPhoneError(""); setIsPhoneValid(false); }} - className="flex items-center gap-2 text-white hover:bg-gray-700" + className="flex items-center gap-2 text-muted-foreground hover:bg-background" > {} export function SignUpForm({ className, ...props }: SignUpFormProps) { - const { formState, handleSubmit, register, handleSignUp } = useSignUp(); + const { formState, handleSubmit, register, handleSignUp, formProps } = + useSignUp(); return (
@@ -19,6 +20,7 @@ export function SignUpForm({ className, ...props }: SignUpFormProps) { handleSubmit={handleSubmit} register={register} handleSignUp={handleSignUp} + formProps={formProps} />
diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts b/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts index d450785..e2b5a4c 100644 --- a/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts +++ b/crazystacknextjs/src/slices/general/features/signup/signup.hook.ts @@ -3,12 +3,12 @@ import { useSignUpLib, type SubmitSignUpHandler } from "./signup.lib"; export const useSignUp = () => { const { signup } = useAuth(); - const { handleSubmit, register, formState } = useSignUpLib(); + const { handleSubmit, register, formState, ...formProps } = useSignUpLib(); const handleSignUp: SubmitSignUpHandler = async (data) => { await signup({ ...data, passwordConfirmation: data.password, }); }; - return { handleSubmit, register, formState, handleSignUp }; + return { handleSubmit, register, formState, handleSignUp, formProps }; }; From 75953b8ae00b16656e3ffaa69f5dde6ba72352cb Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 15:37:54 -0300 Subject: [PATCH 06/59] Add phone input and phone number validation to sign-up form --- crazystacknextjs/package.json | 2 + .../signup/components/phone-input.tsx | 172 ++++++++++-------- .../general/features/signup/signup.lib.ts | 20 ++ 3 files changed, 119 insertions(+), 75 deletions(-) diff --git a/crazystacknextjs/package.json b/crazystacknextjs/package.json index cec7b6a..c293b55 100644 --- a/crazystacknextjs/package.json +++ b/crazystacknextjs/package.json @@ -27,6 +27,7 @@ "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.3", + "@react-input/mask": "^1.2.15", "@tanstack/react-query": "^5.59.16", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", @@ -36,6 +37,7 @@ "i18next-chained-backend": "^4.6.2", "i18next-http-backend": "^2.6.2", "i18next-resources-to-backend": "^1.2.1", + "libphonenumber-js": "^1.11.15", "lucide-react": "^0.454.0", "next": "15.0.2", "next-themes": "^0.3.0", diff --git a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx index 5baba23..c4c19cf 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx @@ -16,13 +16,28 @@ import { useEffect, useState } from "react"; import { AlertCircle, Check, ChevronDown, Search } from "lucide-react"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; +import { InputMask, useMask, format } from "@react-input/mask"; +// Opções de configuração para a máscara do input +// const options = { +// mask: "+0 (___) ___-__-__", // Define o formato da máscara +// replacement: { _: /\d/ }, // Define que _ deve ser substituído por dígitos +// }; + +// Componente de exemplo do input com máscara +// export default function App() { +// const inputRef = useMask(options); // Hook para aplicar a máscara +// const defaultValue = format("1234567890", options); // Formata o valor inicial + +// return ; // Renderiza o input com a máscara +// } type Country = { name: string; code: string; phone: string; flag: string; format: string; + mask: string; }; const countries = [ @@ -32,6 +47,7 @@ const countries = [ phone: "55", flag: "🇧🇷", format: "(99) 99999-9999", + mask: "(__) _____-____", }, { name: "United States", @@ -39,6 +55,7 @@ const countries = [ phone: "1", flag: "🇺🇸", format: "(999) 999-9999", + mask: "(__) _____-____", }, { name: "Afghanistan", @@ -46,6 +63,7 @@ const countries = [ phone: "93", flag: "🇦🇫", format: "999 999 999", + mask: "____ _____", }, { name: "Albania", @@ -53,6 +71,7 @@ const countries = [ phone: "355", flag: "🇦🇱", format: "999 999 999", + mask: "_________", }, { name: "Algeria", @@ -60,6 +79,7 @@ const countries = [ phone: "213", flag: "🇩🇿", format: "999 999 999", + mask: "____ _____", }, { name: "Argentina", @@ -67,6 +87,7 @@ const countries = [ phone: "54", flag: "🇦🇷", format: "(999) 999-9999", + mask: "(__) _____-____", }, { name: "Australia", @@ -74,6 +95,7 @@ const countries = [ phone: "61", flag: "🇦🇺", format: "999 999 999", + mask: "(__) _____-____", }, { name: "Austria", @@ -81,6 +103,7 @@ const countries = [ phone: "43", flag: "🇦🇹", format: "999 999 999", + mask: "(__) _____-____", }, { name: "Belgium", @@ -88,6 +111,7 @@ const countries = [ phone: "32", flag: "🇧🇪", format: "999 999 999", + mask: "(__) _____-____", }, { name: "Canada", @@ -95,6 +119,7 @@ const countries = [ phone: "1", flag: "🇨🇦", format: "(999) 999-9999", + mask: "(__) _____-____", }, { name: "China", @@ -102,6 +127,7 @@ const countries = [ phone: "86", flag: "🇨🇳", format: "999 999 9999", + mask: "____ _____", }, { name: "Colombia", @@ -109,6 +135,7 @@ const countries = [ phone: "57", flag: "🇨🇴", format: "999 999 9999", + mask: "____ _____", }, { name: "France", @@ -116,6 +143,7 @@ const countries = [ phone: "33", flag: "🇫🇷", format: "99 99 99 99 99", + mask: "____ _____", }, { name: "Germany", @@ -123,14 +151,23 @@ const countries = [ phone: "49", flag: "🇩🇪", format: "999 999 9999", + mask: "(__) _____-____", + }, + { + name: "India", + code: "IN", + phone: "91", + flag: "🇮🇳", + format: "99999 99999", + mask: "____ _____", }, - { name: "India", code: "IN", phone: "91", flag: "🇮🇳", format: "99999 99999" }, { name: "Italy", code: "IT", phone: "39", flag: "🇮🇹", format: "999 999 9999", + mask: "(__) _____-____", }, { name: "Japan", @@ -138,6 +175,7 @@ const countries = [ phone: "81", flag: "🇯🇵", format: "999 999 9999", + mask: "(__) _____-____", }, { name: "Mexico", @@ -145,6 +183,7 @@ const countries = [ phone: "52", flag: "🇲🇽", format: "999 999 9999", + mask: "(__) _____-____", }, { name: "Portugal", @@ -152,17 +191,37 @@ const countries = [ phone: "351", flag: "🇵🇹", format: "999 999 999", + mask: "(__) _____-____", + }, + { + name: "Spain", + code: "ES", + phone: "34", + flag: "🇪🇸", + format: "999 999 999", + mask: "(__) _____-____", }, - { name: "Spain", code: "ES", phone: "34", flag: "🇪🇸", format: "999 999 999" }, { name: "United Kingdom", code: "GB", phone: "44", flag: "🇬🇧", format: "9999 999999", + mask: "____ _____", }, ] as const as Country[]; +interface PhoneInputProps { + label: string; + id: string; + type: string; + placeholder: string; + disabled: boolean; + register: any; + error?: string; + formProps: any; +} + export const PhoneInput = ({ label, id, @@ -172,46 +231,32 @@ export const PhoneInput = ({ register, error, formProps, -}) => { - console.log({ formProps }); - const [formData, setFormData] = useState({ - name: "", - phone: "", - birthMonth: "", - observation: "", - isSubscriber: false, - }); +}: PhoneInputProps) => { + const { setValue, watch } = formProps; const [selectedCountry, setSelectedCountry] = useState(countries[0]); const [searchTerm, setSearchTerm] = useState(""); const [phoneError, setPhoneError] = useState(""); const [isPhoneValid, setIsPhoneValid] = useState(false); + const phone = watch("phone"); + const filteredCountries = countries.filter( (country) => country.name.toLowerCase().includes(searchTerm.toLowerCase()) || country.code.toLowerCase().includes(searchTerm.toLowerCase()), ); - const validateAndFormatPhone = ( - value: string, - country: (typeof countries)[0], - ) => { + const validateAndFormatPhone = (value: string, country: Country) => { try { - // Remove all non-digit characters const digitsOnly = value.replace(/\D/g, ""); - - // Format the number as you type const formatter = new AsYouType(country.code as CountryCode); const formattedNumber = formatter.input(digitsOnly); - - // Validate the full number const fullNumber = `+${country.phone}${digitsOnly}`; const isValid = isValidPhoneNumber( fullNumber, country.code as CountryCode, ); - // Parse the number to get additional information const phoneNumber = parsePhoneNumberFromString( fullNumber, country.code as CountryCode, @@ -236,56 +281,17 @@ export const PhoneInput = ({ } }; - const handlePhoneChange = (e: React.ChangeEvent) => { - const input = e.target.value; - const { formatted, isValid } = validateAndFormatPhone( - input, - selectedCountry, - ); - - setFormData((prev) => ({ - ...prev, - phone: formatted, - })); - - // Clear error if input is empty - if (!input) { - setPhoneError(""); - setIsPhoneValid(false); - } - }; - - // Validate phone when country changes - useEffect(() => { - if (formData.phone) { - validateAndFormatPhone(formData.phone, selectedCountry); - } - }, [formData.phone, selectedCountry]); - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - // Validate phone before submission - const { isValid, phoneNumber } = validateAndFormatPhone( - formData.phone, - selectedCountry, - ); - - if (!isValid) { - setPhoneError("Por favor, insira um número de telefone válido"); - return; - } - - console.log("Form submitted:", { - ...formData, - countryCode: selectedCountry.code, - fullPhone: phoneNumber?.number, - }); - }; - + // useEffect(() => { + // if (phone) { + // const { formatted } = validateAndFormatPhone(phone, selectedCountry); + // setValue("phone", formatted, { shouldValidate: true }); + // } + // }, [phone, selectedCountry, setValue]); + console.log(selectedCountry); return (
@@ -303,7 +309,7 @@ export const PhoneInput = ({ alt={selectedCountry.name} className="mr-2" /> - {selectedCountry.name} (+{selectedCountry.code}) + (+{selectedCountry.code}) @@ -324,8 +330,12 @@ export const PhoneInput = ({ { + setValue("country", { + code: country.code, + phone: country.phone, + }); setSelectedCountry(country); - setFormData({ ...formData, phone: "" }); + setValue("phone", ""); setPhoneError(""); setIsPhoneValid(false); }} @@ -339,7 +349,7 @@ export const PhoneInput = ({ alt={country.name} className="mr-2" /> - {country.name} (+{country.code}) + (+{country.code}) {country.name} +{country.phone} @@ -353,11 +363,9 @@ export const PhoneInput = ({
- { + console.log(e.target.value); + //setValue("phone", e.target.value); + }} + replacement={ + selectedCountry?.mask?.includes?.("(") + ? { _: /\d/, A: /[a-zA-Z0-9]/, X: /[a-zA-Z]/ } + : { _: /\d/ } + } + defaultValue={""} + {...register("phone")} /> {phoneError && (
diff --git a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts index d4fedef..0835493 100644 --- a/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts +++ b/crazystacknextjs/src/slices/general/features/signup/signup.lib.ts @@ -1,6 +1,12 @@ import * as yup from "yup"; import { yupResolver } from "@hookform/resolvers/yup"; import { SubmitHandler, useForm } from "react-hook-form"; +import { + parsePhoneNumberFromString, + AsYouType, + isValidPhoneNumber, + CountryCode, +} from "libphonenumber-js"; export interface SignUpFormData { email: string; @@ -20,7 +26,21 @@ export const signupSchema = yup.object({ .oneOf([yup.ref("password")], "As senhas não correspondem") .required("Confirmação de senha é obrigatória"), name: yup.string().required("Nome é obrigatório"), + country: yup.object({ + code: yup.string().required("País é obrigatório"), + phone: yup.string().required("DDD é obrigatório"), + }), phone: yup.string().required("Telefone é obrigatório"), + /* .test("phone", "Telefone inválido", function (value) { + const digitsOnly = value?.replace?.(/\D/g, ""); + const country = this.parent.country; + const fullNumber = `+${country.phone}${digitsOnly}`; + const isValid = isValidPhoneNumber( + fullNumber, + country.code as CountryCode, + ); + return isValid; + }),*/ }); export type YupSchema = yup.InferType; From e6596a4a8bdd7119ecc0965d855185f79118b5f8 Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 15:39:59 -0300 Subject: [PATCH 07/59] Refactor phone input component to remove unnecessary code --- .../general/features/signup/components/phone-input.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx index c4c19cf..2d1f7a2 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx @@ -383,9 +383,10 @@ export const PhoneInput = ({ //setValue("phone", e.target.value); }} replacement={ - selectedCountry?.mask?.includes?.("(") - ? { _: /\d/, A: /[a-zA-Z0-9]/, X: /[a-zA-Z]/ } - : { _: /\d/ } + // selectedCountry?.mask?.includes?.("(") + // ? { _: /\d/, A: /[a-zA-Z0-9]/, X: /[a-zA-Z]/ } + // : + { _: /\d/ } } defaultValue={""} {...register("phone")} From 2d23186079343f6bb6665bef638b260c4fb4a4ce Mon Sep 17 00:00:00 2001 From: gustavoaguiar Date: Mon, 25 Nov 2024 16:08:28 -0300 Subject: [PATCH 08/59] Refactor phone input component to update phone number validation and error messages --- .../signup/components/phone-input.tsx | 56 ++++++------------- .../general/features/signup/signup.lib.ts | 10 ++-- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx index 2d1f7a2..95eec6c 100644 --- a/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx +++ b/crazystacknextjs/src/slices/general/features/signup/components/phone-input.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -246,48 +247,34 @@ export const PhoneInput = ({ country.code.toLowerCase().includes(searchTerm.toLowerCase()), ); - const validateAndFormatPhone = (value: string, country: Country) => { + const validateAndFormatPhone = (value: string) => { try { const digitsOnly = value.replace(/\D/g, ""); - const formatter = new AsYouType(country.code as CountryCode); + const formatter = new AsYouType(selectedCountry.code as CountryCode); const formattedNumber = formatter.input(digitsOnly); - const fullNumber = `+${country.phone}${digitsOnly}`; + const fullNumber = `+${selectedCountry.phone}${digitsOnly}`; const isValid = isValidPhoneNumber( fullNumber, - country.code as CountryCode, + selectedCountry.code as CountryCode, ); const phoneNumber = parsePhoneNumberFromString( fullNumber, - country.code as CountryCode, + selectedCountry.code as CountryCode, ); setIsPhoneValid(isValid); - setPhoneError(isValid ? "" : "Número de telefone inválido"); - - return { - formatted: formattedNumber, - isValid, - phoneNumber, - }; + setPhoneError(isValid ? "" : "Invalid phone number"); + setValue("phone", formattedNumber); + return { formatted: formattedNumber, isValid, phoneNumber }; } catch (error) { setIsPhoneValid(false); - setPhoneError("Formato inválido"); - return { - formatted: value, - isValid: false, - phoneNumber: null, - }; + setPhoneError("Invalid format"); + setValue("phone", value); + return { formatted: value, isValid: false, phoneNumber: null }; } }; - // useEffect(() => { - // if (phone) { - // const { formatted } = validateAndFormatPhone(phone, selectedCountry); - // setValue("phone", formatted, { shouldValidate: true }); - // } - // }, [phone, selectedCountry, setValue]); - console.log(selectedCountry); return (