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/components/Button.jsx b/src/components/Button.jsx index fdda22f..c077c74 100644 --- a/src/components/Button.jsx +++ b/src/components/Button.jsx @@ -2,53 +2,68 @@ import { useNavigate } from 'react-router-dom'; import { tv } from 'tailwind-variants/lite'; const buttonVariants = tv({ - // base styles for all buttons - base: 'font-poppins flex items-center justify-center drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]', - // all button variants - variants: { - size: { - sm: 'h-12 w-[162px] text-base font-semibold rounded-lg lg:h-14 lg:w-[220px] lg:text-xl', - md: 'h-11 w-[218px] text-base font-semibold rounded-lg lg:h-14 lg:w-[286px] lg:text-xl xl:h-16 xl:w-[327px] xl:text-2xl', - lg: 'h-11 w-[345px] text-base font-medium rounded-md lg:w-[501px]', - circ: 'h-11 w-11 text-2xl rounded-full' - }, - color: { - primary: 'bg-eerie text-white border-1 border-eerie', - secondary: 'bg-white text-eerie border-1 border-eerie', - gradient: 'bg-gradient-to-b from-electricgreen to-persianblue text-white' - } + // base styles for all buttons + base: 'font-poppins flex items-center justify-center drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]', + // all button variants + variants: { + size: { + sm: 'h-12 w-[162px] text-base font-semibold rounded-lg lg:h-14 lg:w-[220px] lg:text-xl', + md: 'h-11 w-[218px] text-base font-semibold rounded-lg lg:h-14 lg:w-[286px] lg:text-xl xl:h-16 xl:w-[327px] xl:text-2xl', + lg: 'h-11 w-[345px] text-base font-medium rounded-md lg:w-[501px]', + circ: 'h-11 w-11 text-2xl rounded-full', }, - // default button styles if no specified props - defaultVariants: { - size: 'sm', - color: 'primary' + color: { + primary: 'bg-eerie text-white border-1 border-eerie', + secondary: 'bg-white text-eerie border-1 border-eerie', + gradient: 'bg-gradient-to-b from-electricgreen to-persianblue text-white', }, - // conditional style cases for specific prop combinations - compoundVariants: [ - // remove drop shadow and lower font weight for user selection button - { - color: 'secondary', - size: 'md', - className: 'drop-shadow-none !font-normal' - } - ] + }, + // default button styles if no specified props + defaultVariants: { + size: 'sm', + color: 'primary', + }, + // conditional style cases for specific prop combinations + compoundVariants: [ + // remove drop shadow and lower font weight for user selection button + { + color: 'secondary', + size: 'md', + className: 'drop-shadow-none !font-normal', + }, + ], }); -export default function Button({ size, color, label, onClick, isActive, to, ...props }) { - let navigate = useNavigate(); +export default function Button({ + size, + color, + label, + onClick, + isActive, + to, + ...props +}) { + let navigate = useNavigate(); - function handleClick(){ - if (to){ - navigate(to); - } else { - onClick(); - } + function handleClick() { + if (to) { + navigate(to); + } else { + onClick(); } + } - return ( - - ); -} \ No newline at end of file + return ( + + ); +} 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 -
  • +
  • - +
  • }, { path: 'journal', element: }, { path: 'logout', element: }, + { path: 'profile', element: }, { path: 'error', element: }, { path: 'challenges', element: }, - ], + ], }, { path: '*', @@ -42,4 +44,4 @@ createRoot(document.getElementById('root')).render( -); \ No newline at end of file +); diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 3861d2e..fdd4c0a 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -3,6 +3,8 @@ 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 { auth } from '../firebase'; function Login() { const navigate = useNavigate(); @@ -10,20 +12,40 @@ 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 })); }; - 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 { user } = await signInWithEmailAndPassword( + auth, + formValues.loginEmail, + formValues.loginPassword + ); + console.log('Logged in user is: ', user.uid); + localStorage.setItem('authToken', user.uid); + 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); } }; @@ -84,7 +106,9 @@ function Login() {

    )}
    -
    New user?{' '} @@ -100,4 +124,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..f10e6f1 100644 --- a/src/pages/Onboarding.jsx +++ b/src/pages/Onboarding.jsx @@ -1,14 +1,45 @@ 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 +51,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 +70,58 @@ export default function Onboarding() {
    {/* button quiz grid */}
    -
    {/* next and skip buttons */}
    -
    diff --git a/src/pages/Profile.jsx b/src/pages/Profile.jsx new file mode 100644 index 0000000..8a337c7 --- /dev/null +++ b/src/pages/Profile.jsx @@ -0,0 +1,107 @@ +import Button from '../components/Button'; +import Baseinput from '../components/Baseinput'; +import { useState, useEffect } from 'react'; +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 navigate = useNavigate(); + let inputClassName = + 'rounded-none border-0 border-b border-eerie bg-transparent focus:border-stone-600 focus:ring-0'; + useEffect(() => { + 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()); + setLoading(false); + } else { + setProfileData(null); + setLoading(false); + navigate('/logout'); + } + }); + return () => unsubscribe(); + }, []); + const handleLogout = async () => { + await signOut(auth); + localStorage.removeItem('authToken'); + navigate('/logout'); + }; + if (loading) return

    Loading

    ; + return ( +
    +
    +

    + Profile +

    +
    + +
    +
    + +
    +
    + 0 + ? profileData.onboardingConcerns + .map((item) => `#${item}`) + .join('\u00A0\u00A0') + : 'No concerns were selected' + } + type="text" + name="profile-onboarding-choices" + required={false} + label="Onboarding Selections" + inputClassName={inputClassName} + ariaInvalid={undefined} + ariaDescribedBy={undefined} + disabled={true} + /> +
    + +
    + ); +} + +export default Profile; diff --git a/src/pages/Signup.jsx b/src/pages/Signup.jsx index a7adab9..80b2649 100644 --- a/src/pages/Signup.jsx +++ b/src/pages/Signup.jsx @@ -3,6 +3,9 @@ import Passwordinput from '../components/PasswordInput'; import Button from '../components/Button'; import { useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; +import { createUserWithEmailAndPassword } from 'firebase/auth'; +import { auth, db } from '../firebase'; +import { doc, setDoc } from 'firebase/firestore'; function Signup() { const navigate = useNavigate(); @@ -10,20 +13,35 @@ 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 })); }; - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); + if (!isValidEmail || !isValidPassword) { setFormSubmitMessage('Fill form properly'); } else { + const userCredential = await createUserWithEmailAndPassword( + auth, + formValues.signupEmail, + formValues.signupPassword + ); + const uid = userCredential.user.uid; + const signupFormData = new FormData(); for (const key in formValues) { signupFormData.append(key, formValues[key]); } console.log('Submitting Signup Data:', signupFormData); - localStorage.setItem('authToken', 'my-auth-token'); + await setDoc(doc(db, 'users', uid), { + email: formValues.signupEmail, + password: formValues.signupPassword, + onboardingConcerns: [], + }); + localStorage.setItem('authToken', uid); navigate('/onboarding'); } }; @@ -75,7 +93,9 @@ function Signup() {

    )}
    -