diff --git a/src/api/mock-data.ts b/src/api/mock-data.ts index 4644091..e43c121 100644 --- a/src/api/mock-data.ts +++ b/src/api/mock-data.ts @@ -1,9 +1,3 @@ -export const mockUser = { - isAuth: true, - hasAvatar: false, - isAvatarLoading: false -}; - export const mockTopStrengths = [ { number: 1, diff --git a/src/api/useUserApi.ts b/src/api/useUserApi.ts index b556da7..0356de7 100644 --- a/src/api/useUserApi.ts +++ b/src/api/useUserApi.ts @@ -3,17 +3,30 @@ import { UserModel } from "../models/user.model"; import { showToast } from "../utils/toast"; export const getUserByEmail = async (email: string) => { - try { - const { data, error } = await supabase.from("users").select().eq("email", email).single(); - if (error) { - if (error.details === "The result contains 0 rows") { - showToast("error", "Invalid credential."); - } else { - showToast("error", "Failed to get user."); - } - } - return data as UserModel; - } catch (error) { - console.error("Failed to get user:", error); + const { data, error } = await supabase.from("users").select().eq("email", email).single(); + if (error) { + showToast("error", "Invalid credential."); } + return data as UserModel; +}; + +export const getUserById = async (userId: string | null) => { + const { data, error } = await supabase.from("users").select().eq("id", userId).single(); + if (error) { + showToast("error", "Failed to get user."); + } + return data as UserModel; +}; + +export const updateUserAvatarState = async (userId: string | null) => { + const { error: updateError } = await supabase.from("users").update({ hasAvatar: true }).eq("id", userId).single(); + if (updateError) { + showToast("error", "Failed to create avatar."); + } + + const { data, error: getError } = await supabase.from("users").select().eq("id", userId).single(); + if (getError) { + showToast("error", "Failed to get user."); + } + return data as UserModel; }; diff --git a/src/components/layout/avatar-loading.tsx b/src/components/layout/avatar-loading.tsx index 7a43cb1..c03a7c3 100644 --- a/src/components/layout/avatar-loading.tsx +++ b/src/components/layout/avatar-loading.tsx @@ -4,7 +4,7 @@ import { colors } from "../../utils/colors"; export const AvatarLoading = () => { return ( - + diff --git a/src/components/navigation/navbar.tsx b/src/components/navigation/navbar.tsx index b0e898b..603c3e4 100644 --- a/src/components/navigation/navbar.tsx +++ b/src/components/navigation/navbar.tsx @@ -11,7 +11,7 @@ type NavbarProps = { }; export const Navbar = ({ hasSubvisualIcon }: NavbarProps) => { - const { handleLogout } = useContext(AuthContext); + const { handleLogout, userName } = useContext(AuthContext); const [isOpen, setIsOpen] = useState(false); return ( @@ -30,7 +30,7 @@ export const Navbar = ({ hasSubvisualIcon }: NavbarProps) => { - John + {userName} setIsOpen(!isOpen)}> {isOpen ? : } diff --git a/src/contexts/auth.context.tsx b/src/contexts/auth.context.tsx index f0405f2..dd1b5ad 100644 --- a/src/contexts/auth.context.tsx +++ b/src/contexts/auth.context.tsx @@ -1,11 +1,14 @@ import { createContext, ReactNode, useEffect, useState } from "react"; -import { getUserByEmail } from "../api/useUserApi"; +import { getUserByEmail, getUserById, updateUserAvatarState } from "../api/useUserApi"; +import { UserModel } from "../models/user.model"; type AuthContextProps = { - userId: string | null; isAuthenticated: boolean; + userId: string | null; + userName: string | null; hasAvatar: boolean; handleLogin: (email: string) => Promise; + handleHasAvatar: () => Promise; handleLogout: () => Promise; }; @@ -16,41 +19,75 @@ type AuthContextProvider = { export const AuthContext = createContext({} as AuthContextProps); export const AuthContextProvider = ({ children }: AuthContextProvider) => { - const [userId, setUserId] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); + const [userId, setUserId] = useState(null); + const [userName, setUserName] = useState(null); const [hasAvatar, setHasAvatar] = useState(false); useEffect(() => { - const userId = window.localStorage.getItem("userId"); - if (userId) { - setUserId(userId); + const loadUser = async () => { + const storedUserId = localStorage.getItem("userId"); + if (storedUserId) { + try { + const user = await getUserById(storedUserId); + setUserData(user); + } catch (error) { + console.error("Failed to get user:", error); + handleLogout(); + } + } + }; + + loadUser(); + }, []); + + const setUserData = (user?: UserModel | null) => { + if (user) { + setUserId(user.id); + setUserName(user.name); + setHasAvatar(user.hasAvatar); setIsAuthenticated(true); } - }, []); + }; const handleLogin = async (email: string) => { try { const user = await getUserByEmail(email); if (user) { - setUserId(user.id); - setHasAvatar(user.hasAvatar); window.localStorage.setItem("userId", user.id); - setIsAuthenticated(true); + setUserData(user); } } catch (error) { console.error("Login failed:", error); } }; + const handleHasAvatar = async () => { + if (userId && !hasAvatar) { + try { + await new Promise(res => setTimeout(res, 3000)); // this line is to prolong the loading state for testing purposes + const updatedUser = await updateUserAvatarState(userId); + if (updatedUser?.hasAvatar !== undefined) { + setHasAvatar(updatedUser.hasAvatar); + } + } catch (error) { + console.error("Failed to create avatar:", error); + } + } + }; + const handleLogout = async () => { setIsAuthenticated(false); setUserId(null); + setUserName(null); setHasAvatar(false); window.localStorage.removeItem("userId"); }; return ( - + {children} ); diff --git a/src/pages/avatar-create.page.tsx b/src/pages/avatar-create.page.tsx index 2b57d72..2c40a13 100644 --- a/src/pages/avatar-create.page.tsx +++ b/src/pages/avatar-create.page.tsx @@ -1,41 +1,63 @@ import { Link as JoyLink, Stack, Typography } from "@mui/joy"; -import { Link as ReactLink } from "react-router-dom"; +import { useContext, useState } from "react"; import { MockAvatar } from "../api/mock-avatar"; import { ArrowRightOutlined } from "../assets/icons/arrow-right"; +import { AvatarLoading } from "../components/layout/avatar-loading"; +import { Layout } from "../components/shared/containers"; +import { AuthContext } from "../contexts/auth.context"; export const AvatarCreatePage = () => { - return ( - - - - - Your Strength-Based Avatar is Almost Ready! - + const { handleHasAvatar } = useContext(AuthContext); + const [isLoadingAvatar, setIsLoadingAvatar] = useState(false); - - Now, let's customize the core features to bring your Avatar to life. - - + const handleCreateAvatar = async () => { + setIsLoadingAvatar(true); + try { + await handleHasAvatar(); + } catch (error) { + console.error("Failed to create avatar:", error); + } finally { + setIsLoadingAvatar(false); + } + }; + return ( + + {isLoadingAvatar ? ( + + ) : ( - - + + + + Your Strength-Based Avatar is Almost Ready! + + + + Now, let's customize the core features to bring your Avatar to life. + + + + + + - + - - } - textColor="neutral.black" - gap={0.5} - > - Next - + + } + textColor="neutral.black" + gap={0.5} + > + Next + + + - - + )} + ); }; diff --git a/src/pages/avatar-results.page.tsx b/src/pages/avatar-results.page.tsx index d1dbce3..66faded 100644 --- a/src/pages/avatar-results.page.tsx +++ b/src/pages/avatar-results.page.tsx @@ -1,50 +1,40 @@ import { Button, IconButton, Stack, Typography } from "@mui/joy"; import { Link } from "react-router-dom"; import { MockAvatar } from "../api/mock-avatar"; -import { mockUser } from "../api/mock-data"; import { ArrowRotateOutlined } from "../assets/icons/arrow-rotate"; -import { AvatarLoading } from "../components/layout/avatar-loading"; import { CardTopStrengths } from "../components/layout/card-top-strengths"; import { Layout } from "../components/shared/containers"; export const AvatarResultsPage = () => { - const loading = mockUser.isAvatarLoading; - return ( - <> - {loading ? ( - - ) : ( - - - - - - - Your Strengths, brought to life. - - - - Meet your personalized avatar—a reflection of your unique talents and abilities. - - + + + + + + + Your Strengths, brought to life. + - - - - - - - - + + Meet your personalized avatar—a reflection of your unique talents and abilities. + - + + + + + + + - - )} - + + + + + ); }; diff --git a/src/router/app.tsx b/src/router/app.tsx index 3bbf345..766f272 100644 --- a/src/router/app.tsx +++ b/src/router/app.tsx @@ -1,6 +1,5 @@ import { useContext } from "react"; import { Navigate, Route, Routes } from "react-router-dom"; -import { mockUser } from "../api/mock-data"; import { AuthContext } from "../contexts/auth.context"; import { AvatarCreatePage } from "../pages/avatar-create.page"; import { AvatarResultsPage } from "../pages/avatar-results.page"; @@ -10,7 +9,7 @@ import { SignupPage } from "../pages/signup.page"; import { TeamPage } from "../pages/team.page"; export const App = () => { - const { isAuthenticated } = useContext(AuthContext); + const { isAuthenticated, hasAvatar } = useContext(AuthContext); return ( @@ -21,7 +20,7 @@ export const App = () => { ) : ( <> - {!mockUser.hasAvatar ? ( + {!hasAvatar ? ( <> } /> } />