From 8a812eefd7c670cb845405f9bbc269ef6090b745 Mon Sep 17 00:00:00 2001 From: nkabembo <71999441+nkabembo@users.noreply.github.com> Date: Fri, 28 Nov 2025 01:12:44 +0200 Subject: [PATCH 1/4] Profile page ui and part of contexts for profile --- src/App.css | 8 ++-- src/components/Baseinput.jsx | 2 +- src/contexts/ProfileContext.js | 5 ++ src/contexts/ProfileProvider.jsx | 38 +++++++++++++++ src/main.jsx | 11 +++-- src/pages/Profile.jsx | 82 ++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 src/contexts/ProfileContext.js create mode 100644 src/contexts/ProfileProvider.jsx create mode 100644 src/pages/Profile.jsx diff --git a/src/App.css b/src/App.css index c829a2b..7f31b0a 100644 --- a/src/App.css +++ b/src/App.css @@ -1,13 +1,13 @@ -button:not(.toggle-btn):focus-visible { +button:not(.toggle-btn):not(.delete-btn):focus-visible { outline: solid 4px var(--color-persianblue); outline-offset: 8px; } -button:not(.toggle-btn):hover { +button:not(.toggle-btn):not(.delete-btn):hover { background-color: var(--color-zinc); color: white; cursor: pointer; } -button:not(.toggle-btn):active { +button:not(.toggle-btn):not(.delete-btn):active { background-color: var(--color-persianblue); color: white; } @@ -17,4 +17,4 @@ button.active { } button.active:hover { background-color: var(--color-persianblue); -} \ No newline at end of file +} diff --git a/src/components/Baseinput.jsx b/src/components/Baseinput.jsx index cb5ecbf..5a388fc 100644 --- a/src/components/Baseinput.jsx +++ b/src/components/Baseinput.jsx @@ -33,7 +33,7 @@ function Baseinput({ block w-full h-11 px-4 py-2 bg-white text-black ${inputClassName} - disabled:bg-gray-100 disabled:text-gray-500`} + disabled:bg-white disabled:text-eerie`} aria-invalid={ariaInvalid} aria-describedby={ariaDescribedBy} disabled={disabled} diff --git a/src/contexts/ProfileContext.js b/src/contexts/ProfileContext.js new file mode 100644 index 0000000..9a6eede --- /dev/null +++ b/src/contexts/ProfileContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const ProfileContext = createContext(); + +export default ProfileContext; diff --git a/src/contexts/ProfileProvider.jsx b/src/contexts/ProfileProvider.jsx new file mode 100644 index 0000000..ec10f6c --- /dev/null +++ b/src/contexts/ProfileProvider.jsx @@ -0,0 +1,38 @@ +import { useState } from 'react'; +import ProfileContext from './ProfileContext'; + +export function ProfileProvider({ children }) { + const [user, setUser] = useState(null); // login info + const [extraData, setExtraData] = useState([]); // other component data + + const handleLogout = () => { + setUser(null); + setExtraData([]); + localStorage.removeItem('user'); + localStorage.removeItem('extraData'); + }; + + const handleDeleteAccount = () => { + setUser(null); + setExtraData([]); + localStorage.removeItem('user'); + localStorage.removeItem('extraData'); + }; + + return ( + + {children} + + ); +} diff --git a/src/main.jsx b/src/main.jsx index 1eadd95..90cf314 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './index.css'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { ProfileProvider } from './contexts/ProfileContext'; import Home from './pages/Home'; import Onboarding from './pages/Onboarding'; import Achievements from './pages/Achievements'; @@ -12,6 +13,7 @@ import Login from './pages/Login'; import Signup from './pages/Signup'; import Journal from './pages/journal'; import Logout from './pages/Logout'; +import Profile from './pages/Profile'; import App from './App'; const router = createBrowserRouter([ @@ -28,8 +30,9 @@ const router = createBrowserRouter([ { path: 'achievements', element: }, { path: 'journal', element: }, { path: 'logout', element: }, + { path: 'profile', element: }, { path: 'error', element: }, - ], + ], }, { path: '*', @@ -38,6 +41,8 @@ const router = createBrowserRouter([ ]); createRoot(document.getElementById('root')).render( - + + + -); \ No newline at end of file +); diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx new file mode 100644 index 0000000..5659ea4 --- /dev/null +++ b/src/pages/Profile.jsx @@ -0,0 +1,82 @@ +import Button from '../components/Button'; +import Baseinput from '../components/Baseinput'; +import { useContext } from 'react'; +import { ProfileContext } from '../contexts/ProfileContext'; +function Profile() { + let inputClassName = + 'rounded-none border-0 border-b border-eerie bg-transparent focus:border-stone-600 focus:ring-0'; + const { handleLogout, handleDeleteAccount } = useContext(ProfileContext); + return ( +
+
+

+ Profile +

+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ ); +} + +export default Profile; From fc65754986f491e310016c2cb3e482f029eb1524 Mon Sep 17 00:00:00 2001 From: nkabembo <71999441+nkabembo@users.noreply.github.com> Date: Sat, 29 Nov 2025 15:42:59 +0200 Subject: [PATCH 2/4] profile logout, display onboarding and email data --- src/components/Navbar.jsx | 21 +++++--- src/firebase.js | 11 ++-- src/pages/Login.jsx | 26 ++++++++-- src/pages/Onboarding.jsx | 103 +++++++++++++++++++++++++------------- src/pages/Profile.jsx | 60 +++++++++++++++++----- src/pages/Signup.jsx | 20 +++++++- 6 files changed, 177 insertions(+), 64 deletions(-) diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index f67b214..eb185bf 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -9,6 +9,10 @@ import { HiMenu } from 'react-icons/hi'; //components import MobileMenu from './MobileMenu'; +//firebase +import { auth } from '../firebase'; +import { signOut } from 'firebase/auth'; + function Navbar() { const [isMenuOpen, setIsMenuOpen] = useState(false); // mobile menu state const [isAuthenticated, setIsAuthenticated] = useState(false); // user authentication state @@ -27,10 +31,15 @@ function Navbar() { const toggleMenu = () => setIsMenuOpen(!isMenuOpen); - const handleLogout = () => { - localStorage.removeItem('authToken'); - setIsAuthenticated(false); - navigate('/logout'); + const handleLogout = async () => { + try { + await signOut(auth); + localStorage.removeItem('authToken'); + setIsAuthenticated(false); + navigate('/logout'); + } catch (error) { + console.error('Logout failed:', error); + } }; // navbar for NON-authenticated user @@ -123,7 +132,7 @@ function Navbar() { Detox Challenge -
  • +
  • - +
  • { setFormValues((prevValue) => ({ ...prevValue, [fieldName]: value })); }; - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); if (!isValidEmail || !isValidPassword) { setFormSubmitMessage('Fill form properly'); } else { - localStorage.setItem('authToken', 'my-auth-token'); + //localStorage.setItem('authToken', 'my-auth-token'); const loginFormData = new FormData(); for (const key in formValues) { loginFormData.append(key, formValues[key]); } - navigate('/dashboard'); + try { + const userCredential = await signinWithEmailAndPassWord( + auth, + formValues.loginEmail, + formValues.loginPassword + ); + navigate('/dashboard'); + } catch (error) { + if (error.code === 'auth/user-not-found') { + setFormSubmitMessage('No user found with this email'); + } else if (error.code === 'auth/wrong-password') { + setFormSubmitMessage('No usr found with this email'); + } else { + setFormSubmitMessage('Login failed. Try again.'); + } + } + console.log('Login Submission Data:', loginFormData); } }; @@ -100,4 +118,4 @@ function Login() { ); } -export default Login; \ No newline at end of file +export default Login; diff --git a/src/pages/Onboarding.jsx b/src/pages/Onboarding.jsx index dc68c7c..1d24090 100644 --- a/src/pages/Onboarding.jsx +++ b/src/pages/Onboarding.jsx @@ -1,14 +1,44 @@ import Button from '../components/Button'; import { useEffect, useState } from 'react'; - +import { doc, setDoc } from 'firebase/firestore'; +import { auth, db } from '../firebase'; +import { useNavigate } from 'react-router-dom'; export default function Onboarding() { const [selectedConcerns, setSelectedConcerns] = useState([]); - + const navigate = useNavigate(); // log the selectedConcerns after the array changes to update automatically useEffect(() => { console.log('Updated Concerns:', selectedConcerns); }, [selectedConcerns]); + async function handleNext() { + if (!auth.currentUser) { + return; + } + const uid = auth.currentUser.uid; + await setDoc( + doc(db, 'users', uid), + { onboardingConcerns: selectedConcerns }, + { merge: true } + ) + .then(() => { + console.log('Onboarding data for profile is saved'); + navigate('/dashboard'); + }) + .catch((error) => console.error('Error saving data:', error)); + } + async function handleSkip() { + const uid = auth.currentUser.uid; + await setDoc( + doc(db, 'users', uid), + { + onboardingConcerns: [], + }, + { merge: true } + ); + navigate('/dashboard'); + } + // function for toggling active state and storing the user's selected concern function toggleConcern(concern) { // if the clicked concern is not selectedConcerns, setSelectedConcerns @@ -20,7 +50,7 @@ export default function Onboarding() { } else { // log the deselection console.log(`${concern} button deselected!`); - setSelectedConcerns(selectedConcerns.filter(item => item !== concern)); + setSelectedConcerns(selectedConcerns.filter((item) => item !== concern)); } } @@ -39,53 +69,58 @@ export default function Onboarding() { {/* button quiz grid */}
    -
    {/* next and skip buttons */}
    -
    diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index 5659ea4..040304c 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -1,11 +1,47 @@ import Button from '../components/Button'; import Baseinput from '../components/Baseinput'; -import { useContext } from 'react'; -import { ProfileContext } from '../contexts/ProfileContext'; +import { useState, useEffect } from 'react'; +//import { useContext } from 'react'; +import { signOut } from 'firebase/auth'; +import { auth, db } from '../firebase'; +import { doc, getDoc } from 'firebase/firestore'; +import { useNavigate } from 'react-router-dom'; +//import { ProfileContext } from '../contexts/ProfileContext'; function Profile() { + const { profileData, setProfileData } = useState(null); + const { loading, setLoading } = useState(true); + const navigate = useNavigate(); let inputClassName = 'rounded-none border-0 border-b border-eerie bg-transparent focus:border-stone-600 focus:ring-0'; - const { handleLogout, handleDeleteAccount } = useContext(ProfileContext); + //const { handleLogout, handleDeleteAccount } = useContext(ProfileContext); + useEffect(() => { + async function fetchProfileData() { + try { + const uid = auth.currentUser.uid; + + const docRef = doc(db, 'users', uid); + const docSnap = await getDoc(docRef); + + if (docSnap.exists()) { + setProfileData(docSnap.data()); + } else { + console.log('No user document found'); + } + } catch (error) { + console.error('Error fetching user data:', error); + } finally { + setLoading(false); + } + } + + fetchProfileData(); + }, []); + const handleLogout = async () => { + await signOut(auth); + localStorage.removeItem('authToken'); + navigate('/logout'); + }; + if (loading) return

    Loading

    ; return (
    @@ -15,7 +51,7 @@ function Profile() {
    0 + ? profileData.onboardingConcerns + .map((item) => `#${item}`) + .join('\u00A0\u00A0') + : 'No concerns were selected' + } type="password" name="profile-password" required={false} @@ -54,14 +96,6 @@ function Profile() { disabled={true} />
    -
    - -
    - ); -} \ No newline at end of file + return ( + + ); +} diff --git a/src/main.jsx b/src/main.jsx index dae0ee3..c3ad357 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -13,6 +13,7 @@ import Signup from './pages/Signup'; import Journal from './pages/journal'; import Logout from './pages/Logout'; import Challenges from './pages/Challenges'; +import Profile from './pages/Profile'; import App from './App'; const router = createBrowserRouter([ diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 8b6ded6..fdd4c0a 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -3,7 +3,7 @@ import Passwordinput from '../components/PasswordInput'; import Button from '../components/Button'; import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import { signinWithEmailAndPassWord } from 'firebase/auth'; +import { signInWithEmailAndPassword } from 'firebase/auth'; import { auth } from '../firebase'; function Login() { @@ -12,6 +12,8 @@ function Login() { const [isValidEmail, setisValidEmail] = useState(false); const [isValidPassword, setisValidPassword] = useState(false); const [formSubmitMessage, setFormSubmitMessage] = useState(''); + const buttonStyles = + 'h-11 w-[345px] text-base font-medium rounded-md lg:w-[501px] bg-eerie text-white border-1 border-eerie'; const setFormValue = (fieldName, value) => { setFormValues((prevValue) => ({ ...prevValue, [fieldName]: value })); }; @@ -26,7 +28,7 @@ function Login() { loginFormData.append(key, formValues[key]); } try { - const { user } = await signinWithEmailAndPassWord( + const { user } = await signInWithEmailAndPassword( auth, formValues.loginEmail, formValues.loginPassword @@ -104,7 +106,9 @@ function Login() {

    )}
    -
    New user?{' '} diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx index 71a1062..8a337c7 100644 --- a/src/pages/Profile.jsx +++ b/src/pages/Profile.jsx @@ -1,38 +1,31 @@ import Button from '../components/Button'; import Baseinput from '../components/Baseinput'; import { useState, useEffect } from 'react'; -import { signOut } from 'firebase/auth'; +import { signOut, onAuthStateChanged } from 'firebase/auth'; import { auth, db } from '../firebase'; import { doc, getDoc } from 'firebase/firestore'; import { useNavigate } from 'react-router-dom'; function Profile() { - const { profileData, setProfileData } = useState(null); - const { loading, setLoading } = useState(true); + const [profileData, setProfileData] = useState(null); + const [loading, setLoading] = useState(true); const navigate = useNavigate(); let inputClassName = 'rounded-none border-0 border-b border-eerie bg-transparent focus:border-stone-600 focus:ring-0'; useEffect(() => { - async function fetchProfileData() { - try { - const uid = auth.currentUser.uid; - - const docRef = doc(db, 'users', uid); + const unsubscribe = onAuthStateChanged(auth, async (user) => { + if (user) { + const docRef = doc(db, 'users', user.uid); const docSnap = await getDoc(docRef); - - if (docSnap.exists()) { - setProfileData(docSnap.data()); - } else { - console.log('No user document found'); - } - } catch (error) { - console.error('Error fetching user data:', error); - } finally { + if (docSnap.exists()) setProfileData(docSnap.data()); setLoading(false); + } else { + setProfileData(null); + setLoading(false); + navigate('/logout'); } - } - - fetchProfileData(); + }); + return () => unsubscribe(); }, []); const handleLogout = async () => { await signOut(auth); @@ -49,7 +42,7 @@ function Profile() {
    0 + profileData?.onboardingConcerns?.length > 0 ? profileData.onboardingConcerns .map((item) => `#${item}`) .join('\u00A0\u00A0') diff --git a/src/pages/Signup.jsx b/src/pages/Signup.jsx index 2309346..80b2649 100644 --- a/src/pages/Signup.jsx +++ b/src/pages/Signup.jsx @@ -13,6 +13,8 @@ function Signup() { const [isValidEmail, setisValidEmail] = useState(false); const [isValidPassword, setisValidPassword] = useState(false); const [formSubmitMessage, setFormSubmitMessage] = useState(''); + const buttonStyles = + 'h-11 w-[345px] text-base font-medium rounded-md lg:w-[501px] bg-eerie text-white border-1 border-eerie'; const setFormValue = (fieldName, value) => { setFormValues((prevValue) => ({ ...prevValue, [fieldName]: value })); }; @@ -91,7 +93,9 @@ function Signup() {

    )}
    -