diff --git a/app/routes/login.tsx b/app/routes/login.tsx deleted file mode 100644 index 997fb2e..0000000 --- a/app/routes/login.tsx +++ /dev/null @@ -1,366 +0,0 @@ -import { Form, useOutletContext, useNavigate } from "@remix-run/react"; -import { useEffect, useState } from "react"; -import { createServerClient, parse, serialize } from "@supabase/ssr"; - -import SpinnerSVG from "~/components/SpinnerSVG"; -import googleIcon from "~/assets/oauth_providers/google-icon.png"; -import githubIcon from "~/assets/oauth_providers/github-icon.png"; - -import type { Provider } from "@supabase/supabase-js"; -import { ContextProps } from "~/utils/types/ContextProps.type"; - -export default function Login() { - const navigate = useNavigate(); - - const [errorMessage, setErrorMessage] = useState(null); - const resetErrorMessage = () => setErrorMessage(null); - const [isSubmitting, setIsSubmitting] = useState(false); - - // switch between login & signup - const [isAtLogin, setIsAtLogin] = useState(true); - const { supabase } = useOutletContext(); - - const providerClicked = async (providerName: Provider) => { - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: providerName, - }); - if (error) alert("Error while logging in."); - }; - - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [regPassword, setRegPassword] = useState(""); - - const [showPassReqs, setShowPassReqs] = useState(false); - const [passReqs, setPassReqs] = useState({ - hasSixChar: false, - hasUppercase: false, - hasLowercase: false, - hasNumber: false, - hasSymbol: false, - }); - - // revalidate new password - useEffect(() => { - setPassReqs({ - hasSixChar: regPassword.length >= 6, - hasUppercase: /[A-Z]/.test(regPassword), - hasLowercase: /[a-z]/.test(regPassword), - hasNumber: /\d/.test(regPassword), - hasSymbol: /[!@#$%^&*()_+={}\[\]:;<>,.?~\\|\-]/.test(regPassword), - }); - }, [regPassword]); - - const fillDemoAcc = () => { - setEmail("hynguyendev@gmail.com"); - setPassword("123+Ab"); - }; - - const loginOnSubmit: React.FormEventHandler = async ( - event, - ) => { - event.preventDefault(); - resetErrorMessage(); - setIsSubmitting(true); - - const { error, data } = await supabase.auth.signInWithPassword({ - email: email, - password: password, - }); - if (error) { - setIsSubmitting(false); - setErrorMessage(error.message); - return; - } - - return navigate("/"); - }; - - const registerOnSubmit: React.FormEventHandler = async ( - event, - ) => { - event.preventDefault(); - resetErrorMessage(); - setIsSubmitting(true); - - const form = event.currentTarget; - const formValues: { [key: string]: string } = { - displayName: form["reg_display_name_input"].value, - email: form["reg_email_input"].value, - password: form["reg_password_input"].value, - passwordConfirm: form["reg_confirm_password_input"].value, - }; - - const validations: [boolean, string][] = [ - [passReqs.hasSixChar, "Password should be at least 6 characters"], - [ - passReqs.hasUppercase, - "Password should contain at least 1 uppercase", - ], - [ - passReqs.hasLowercase, - "Password should contain at least 1 lowercase", - ], - [passReqs.hasNumber, "Password should contain at least 1 number"], - [passReqs.hasSymbol, "Password should contain at least 1 symbol"], - [ - formValues.password === formValues.passwordConfirm, - "Passwords don't match", - ], - ]; - - const validationsPassed = validations.every((vali) => { - if (!vali[0]) { - setTimeout(() => { - setErrorMessage(vali[1]); - }, 1); - return false; - } else return true; - }); - - if (validationsPassed) { - const { error, data } = await supabase.auth.signUp({ - email: formValues.email, - password: formValues.password, - }); - if (error) { - setIsSubmitting(false); - setErrorMessage(error.message); - return; - } - return navigate("/"); - } else { - setIsSubmitting(false); - return; - } - }; - - return ( -
-
-

Continue with

-
- - -
-
-
OR
- -
- - -
-
-
-

Login

- - - - setEmail(e.target.value)} - /> - - setPassword(e.target.value)} - /> - - - {errorMessage} -
-
- -
- -
-
-
-

- Register -

- - - - - - - - {/* Password requirements popup div */} -
- -
- - - setShowPassReqs(true)} - onBlur={() => setShowPassReqs(false)} - onChange={(e) => setRegPassword(e.target.value)} - value={regPassword} - /> - - - {errorMessage} - - -
-
-
-
-
- ); -} diff --git a/app/routes/login/LoginForm.tsx b/app/routes/login/LoginForm.tsx new file mode 100644 index 0000000..439e92e --- /dev/null +++ b/app/routes/login/LoginForm.tsx @@ -0,0 +1,90 @@ +import { Form } from "@remix-run/react"; + +import SpinnerSVG from "~/components/SpinnerSVG"; + +type Props = { + isAtLogin: boolean; + loginOnSubmit: React.FormEventHandler; + email: string; + setEmail: React.Dispatch>; + password: string; + setPassword: React.Dispatch>; + isSubmitting: boolean; + errorMessage: string | null; +}; + +export default function LoginForm({ + props: { + isAtLogin, + loginOnSubmit, + email, + setEmail, + password, + setPassword, + isSubmitting, + errorMessage, + }, +}: { + props: Props; +}) { + const fillDemoAcc = () => { + setEmail("hynguyendev@gmail.com"); + setPassword("123+Ab"); + }; + + return ( +
+
+
+

Login

+ + + + setEmail(e.target.value)} + /> + + setPassword(e.target.value)} + /> + + + {errorMessage} +
+
+ +
+ ); +} diff --git a/app/routes/login/SignupForm.tsx b/app/routes/login/SignupForm.tsx new file mode 100644 index 0000000..cf196b2 --- /dev/null +++ b/app/routes/login/SignupForm.tsx @@ -0,0 +1,162 @@ +import { Form } from "@remix-run/react"; +import { useEffect, useState } from "react"; + +import SpinnerSVG from "~/components/SpinnerSVG"; + +type Props = { + isAtLogin: boolean; + registerOnSubmit: React.FormEventHandler; + passReqs: { [key: string]: boolean }; + setPassReqs: React.Dispatch< + React.SetStateAction<{ [key: string]: boolean }> + >; + isSubmitting: boolean; + errorMessage: string | null; +}; + +export default function SignupForm({ + props: { + isAtLogin, + registerOnSubmit, + passReqs, + setPassReqs, + isSubmitting, + errorMessage, + }, +}: { + props: Props; +}) { + const [regPassword, setRegPassword] = useState(""); + const [showPassReqs, setShowPassReqs] = useState(false); + + // revalidate new password + useEffect(() => { + setPassReqs({ + hasSixChar: regPassword.length >= 6, + hasUppercase: /[A-Z]/.test(regPassword), + hasLowercase: /[a-z]/.test(regPassword), + hasNumber: /\d/.test(regPassword), + hasSymbol: /[!@#$%^&*()_+={}\[\]:;<>,.?~\\|\-]/.test(regPassword), + }); + }, [regPassword]); + + return ( +
+
+
+

Register

+ + + + + + + + {/* Password requirements popup div */} +
+ +
+ + + setShowPassReqs(true)} + onBlur={() => setShowPassReqs(false)} + onChange={(e) => setRegPassword(e.target.value)} + value={regPassword} + /> + + + {errorMessage} + + +
+
+
+ ); +} diff --git a/app/routes/login/index.tsx b/app/routes/login/index.tsx new file mode 100644 index 0000000..eda3050 --- /dev/null +++ b/app/routes/login/index.tsx @@ -0,0 +1,182 @@ +import { useOutletContext, useNavigate } from "@remix-run/react"; +import { useState } from "react"; + +import googleIcon from "~/assets/oauth_providers/google-icon.png"; +import githubIcon from "~/assets/oauth_providers/github-icon.png"; + +import type { Provider } from "@supabase/supabase-js"; +import { ContextProps } from "~/utils/types/ContextProps.type"; + +import LoginForm from "./LoginForm"; +import SignupForm from "./SignupForm"; + +export default function Login() { + const navigate = useNavigate(); + + const [errorMessage, setErrorMessage] = useState(null); + const resetErrorMessage = () => setErrorMessage(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + // switch between login & signup + const [isAtLogin, setIsAtLogin] = useState(true); + const { supabase } = useOutletContext(); + + const providerClicked = async (providerName: Provider) => { + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: providerName, + }); + if (error) alert("Error while logging in."); + }; + + // LOGIN + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const loginOnSubmit: React.FormEventHandler = async ( + event, + ) => { + event.preventDefault(); + resetErrorMessage(); + setIsSubmitting(true); + + const { error, data } = await supabase.auth.signInWithPassword({ + email: email, + password: password, + }); + if (error) { + setIsSubmitting(false); + setErrorMessage(error.message); + return; + } + + return navigate("/"); + }; + + // SIGNUP + const [passReqs, setPassReqs] = useState<{ [key: string]: boolean }>({ + hasSixChar: false, + hasUppercase: false, + hasLowercase: false, + hasNumber: false, + hasSymbol: false, + }); + + const registerOnSubmit: React.FormEventHandler = async ( + event, + ) => { + event.preventDefault(); + resetErrorMessage(); + setIsSubmitting(true); + + const form = event.currentTarget; + const formValues: { [key: string]: string } = { + displayName: form["reg_display_name_input"].value, + email: form["reg_email_input"].value, + password: form["reg_password_input"].value, + passwordConfirm: form["reg_confirm_password_input"].value, + }; + + const validations: [boolean, string][] = [ + [passReqs.hasSixChar, "Password should be at least 6 characters"], + [ + passReqs.hasUppercase, + "Password should contain at least 1 uppercase", + ], + [ + passReqs.hasLowercase, + "Password should contain at least 1 lowercase", + ], + [passReqs.hasNumber, "Password should contain at least 1 number"], + [passReqs.hasSymbol, "Password should contain at least 1 symbol"], + [ + formValues.password === formValues.passwordConfirm, + "Passwords don't match", + ], + ]; + + const validationsPassed = validations.every((vali) => { + if (!vali[0]) { + setTimeout(() => { + setErrorMessage(vali[1]); + }, 1); + return false; + } else return true; + }); + + if (validationsPassed) { + const { error, data } = await supabase.auth.signUp({ + email: formValues.email, + password: formValues.password, + }); + if (error) { + setIsSubmitting(false); + setErrorMessage(error.message); + return; + } + return navigate("/"); + } else { + setIsSubmitting(false); + return; + } + }; + + return ( +
+
+

Continue with

+
+ + +
+
+
OR
+ +
+ {/* LOGIN/SIGNUP SWITCH */} + + + + + +
+
+ ); +}