From f86c6814b87236b7bfeccba0d63dc78d73c4562a Mon Sep 17 00:00:00 2001 From: Hy Nguyen Date: Tue, 16 Jan 2024 02:22:01 -0600 Subject: [PATCH] handle auth on client side only. added logout --- app/assets/oauth_providers/facebook-icon.png | Bin 8625 -> 0 bytes app/components/SidePanel.tsx | 22 ++- app/components/SpinnerSVG.tsx | 24 +++ app/root.tsx | 167 +++++++++-------- app/routes/login.tsx | 183 +++++++++---------- app/utils/types/ContextProps.type.ts | 7 + 6 files changed, 226 insertions(+), 177 deletions(-) delete mode 100644 app/assets/oauth_providers/facebook-icon.png create mode 100644 app/components/SpinnerSVG.tsx create mode 100644 app/utils/types/ContextProps.type.ts diff --git a/app/assets/oauth_providers/facebook-icon.png b/app/assets/oauth_providers/facebook-icon.png deleted file mode 100644 index 38b037021fd4d608bb83b0a7b41f1938ff430f75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8625 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 + user: ContextProps["user"]; + supabase: ContextProps["supabase"] }; export default function SidePanel( - {sidePanelIsShown, setSidePanelIsShown} + {sidePanelIsShown, setSidePanelIsShown, user, supabase} : Props){ const [theme, setTheme] = useTheme(); const toggleTheme = () => { setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light")); }; + const logout = async () => { + const {error} = await supabase.auth.signOut(); + if (error) alert("Error while logging out."); + } + return
setSidePanelIsShown(false)}>CLOSE + + {user?
+

Logged in as {user.email}

+ +
: + + } +
} \ No newline at end of file diff --git a/app/components/SpinnerSVG.tsx b/app/components/SpinnerSVG.tsx new file mode 100644 index 0000000..89f4be4 --- /dev/null +++ b/app/components/SpinnerSVG.tsx @@ -0,0 +1,24 @@ + +type Props = { + size: number; +}; + +export default function SidePanel({size} : Props){ + return + + + + + + + + + + + + + + + + ; +} \ No newline at end of file diff --git a/app/root.tsx b/app/root.tsx index 2204da9..ac29365 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,15 +1,16 @@ import type { LinksFunction, LoaderFunction } from "@remix-run/node"; -import type { Database } from '../database.types' +import type { User } from "@supabase/supabase-js"; +import { Database } from "database.types"; import { json } from "@remix-run/node"; import { - Links, - Meta, - Outlet, - Scripts, - useLoaderData, - useRevalidator + Links, + Meta, + Outlet, + Scripts, + useLoaderData, + useRevalidator } from "@remix-run/react"; import { useState, useEffect } from "react"; import { createBrowserClient } from '@supabase/ssr' @@ -25,91 +26,101 @@ import Navbar from "./components/Navbar"; import SidePanel from "./components/SidePanel"; + + export const links: LinksFunction = () => [ - { rel: "stylesheet", href: stylesheet }, + { rel: "stylesheet", href: stylesheet }, ]; export type LoaderData = { - theme: ThemeType | null; - env: { [key: string]: string } + theme: ThemeType | null; + env: { [key: string]: string } }; export const loader: LoaderFunction = async ({ request }) => { - const themeSession = await getThemeSession(request); + const themeSession = await getThemeSession(request); - const data: LoaderData = { - theme: themeSession.getTheme(), - env: { - SUPABASE_URL: process.env.SUPABASE_URL!, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!, - } - }; + const data: LoaderData = { + theme: themeSession.getTheme(), + env: { + SUPABASE_URL: process.env.SUPABASE_URL!, + SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY!, + } + }; - return json(data) + return json(data) } function App() { - const { env } = useLoaderData() - const [supabase] = useState(() => - createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) - ) - - // recalls loaders when authentication state changes - const revalidator = useRevalidator() - useEffect(() => { - const { - data: { subscription } - } = supabase.auth.onAuthStateChange((event, session) => { - revalidator.revalidate() - }) - - return () => { - subscription.unsubscribe() - } - }, [supabase /*, revalidator*/ ]) - - - const [theme] = useTheme(); - const [sidePanelIsShown, setSidePanelIsShown] = useState(false); - - - // hide for specific routes - const location = useLocation(); - const routesToHideNavigation = ['/login', '/signup']; ///// add homepage - const shouldHideNavigation = routesToHideNavigation.includes(location.pathname); - - - - return ( - - - - - - - - - {shouldHideNavigation ? null : <> - - - } - - {/* space for navbar above main page content */} -
- -
- - - - ); + const { env } = useLoaderData() + const [supabase] = useState(() => + createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) + ) + const [user, setUser] = useState(undefined); + + // recalls loaders when authentication state changes + const revalidator = useRevalidator(); + useEffect(() => { + const { + data: { subscription } + } = supabase.auth.onAuthStateChange((event, session) => { + revalidator.revalidate(); + if (event === "SIGNED_IN") { + setUser(session?.user); + } else if (event === "SIGNED_OUT"){ + setUser(undefined); + } + }) + + return () => { + subscription.unsubscribe() + } + }, [supabase /*, revalidator*/]) + + + const [theme] = useTheme(); + const [sidePanelIsShown, setSidePanelIsShown] = useState(false); + + + // hide for specific routes + const location = useLocation(); + const routesToHideNavigation = ['/login', '/signup']; ///// add homepage + const shouldHideNavigation = routesToHideNavigation.includes(location.pathname); + + + + return ( + + + + + + + + + {shouldHideNavigation ? null : <> + + + } + + {/* space for navbar above main page content */} +
+ +
+ + + + ); } export default function AppWithProviders() { - const data = useLoaderData(); - return ( - - - - ); + const data = useLoaderData(); + return ( + + + + ); } \ No newline at end of file diff --git a/app/routes/login.tsx b/app/routes/login.tsx index 6e89f6a..7a2297a 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -1,106 +1,25 @@ -import { Link, Form, useOutletContext, useActionData, redirect } from "@remix-run/react"; -import { json } from "@remix-run/node"; +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 facebookIcon from "~/assets/oauth_providers/facebook-icon.png"; import githubIcon from "~/assets/oauth_providers/github-icon.png"; - -import type { Database } from '../../database.types' -import type { SupabaseClient, Provider } from '@supabase/supabase-js' -import type { ActionFunctionArgs } from "@remix-run/node" - - -export async function action({request}: ActionFunctionArgs) { - const formData = await request.formData(); - - const cookies = parse(request.headers.get('Cookie') ?? '') - const headers = new Headers() - const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { - cookies: { - get(key) { - return cookies[key] - }, - set(key, value, options) { - headers.append('Set-Cookie', serialize(key, value, options)) - }, - remove(key, options) { - headers.append('Set-Cookie', serialize(key, '', options)) - }, - }, - }) - - // LOGIN - if (formData.get("form_type") === "LOGIN"){ - const {error} = await supabase.auth.signInWithPassword({ - email: String(formData.get("email_input")), - password: String(formData.get("password_input")) - }) - if (error) return json({ errorMessage: error.message }); - return redirect("/"); - } - - // REGISTER - else if (formData.get("form_type") === "REGISTER") { - const displayName = String(formData.get("reg_display_name_input")); - const email = String(formData.get("reg_email_input")); - const password = String(formData.get("reg_password_input")); - const passwordConfirm = String(formData.get("reg_confirm_password_input")); - - - let errorMessage: string | null = null; - const validations : [boolean, string][] = [ - [!email.includes("@") || !email.includes("."), "Invalid email address."], - [password.length < 6, "Password should be at least 6 characters."], - [!(/[A-Z]/.test(password)), "Password should contain at least 1 uppercase."], - [!(/[a-z]/.test(password)), "Password should contain at least 1 lowercase."], - [!(/\d/.test(password)), "Password should contain at least 1 number."], - [!(/[!@#$%^&*()_+={}\[\]:;<>,.?~\\|\-]/.test(password)), "Password should contain at least 1 symbol."], - [password !== passwordConfirm, "Confirm password doesn't match."] - ]; - validations.some(vali => { - if (vali[0]){ - errorMessage = vali[1]; - return true; - } else return false; - }); - - // const d = await supabase.auth.getUser() - // console.log(d) - // const s = await supabase.auth.getSession() - // console.log(s) - - if (errorMessage) return json({ errorMessage }); - const {error} = await supabase.auth.signUp({email, password}); - if (error) json({ errorMessage: "Error: Failed to register." }); - - return redirect("/"); - } - - return json({ errorMessage: "Unknown form submitted." }); - } +import type { Provider } from '@supabase/supabase-js' +import { ContextProps } from "~/utils/types/ContextProps.type"; export default function Login(){ - - const actionData = useActionData(); + const navigate = useNavigate(); + const [errorMessage, setErrorMessage] = useState(null); - const resetErrorMessage = () => setErrorMessage(null); - // update errorMessage on form submission - useEffect(()=>{ - if (actionData?.errorMessage){ - setErrorMessage(actionData.errorMessage); - } - }, [actionData]); - + const [isSubmitting, setIsSubmitting] = useState(false); // switch between login & signup const [isAtLogin, setIsAtLogin] = useState(true); - const { supabase } = useOutletContext<{ supabase: SupabaseClient }>() + const { supabase } = useOutletContext() const providerClicked = async (providerName: Provider) => { const { data, error } = await supabase.auth.signInWithOAuth({ @@ -123,6 +42,7 @@ export default function Login(){ hasSymbol: false }); + // revalidate new password useEffect(()=>{ setPassReqs({ hasSixChar: regPassword.length >= 6, @@ -136,7 +56,72 @@ export default function Login(){ const fillDemoAcc = ()=>{ setEmail("hynguyendev@gmail.com"); - setPassword("sup3rs3cur3"); + 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; + } }; @@ -148,10 +133,6 @@ export default function Login(){ onClick={()=>providerClicked("google")}> google -
-
+

Login

@@ -183,7 +164,10 @@ export default function Login(){ type="password" autoComplete="current-password" className="text-input" value={password} onChange={(e) => setPassword(e.target.value)}/> - + {errorMessage}
@@ -192,7 +176,7 @@ export default function Login(){
-
+

Register

@@ -238,7 +222,10 @@ export default function Login(){ className="text-input" required /> {errorMessage} - +
diff --git a/app/utils/types/ContextProps.type.ts b/app/utils/types/ContextProps.type.ts new file mode 100644 index 0000000..82a43dc --- /dev/null +++ b/app/utils/types/ContextProps.type.ts @@ -0,0 +1,7 @@ +import { SupabaseClient, User } from "@supabase/supabase-js" +import { Database } from "database.types" + +export type ContextProps = { + supabase: SupabaseClient, + user: User | undefined +} \ No newline at end of file