diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5f66833..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 4218a49..690cf0b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,12 @@ !.yarn/releases !.yarn/versions +# IDEs +.idea/ +.vscode/ +*.iml + + # testing /coverage diff --git a/components/DoneButton.tsx b/components/DoneButton.tsx new file mode 100644 index 0000000..f217183 --- /dev/null +++ b/components/DoneButton.tsx @@ -0,0 +1,55 @@ +import React from "react"; + +// Define the props for our button component +interface NextStepButtonProps { + onClick?: () => void; + logo?: React.ReactNode; + className?: string; + disabled?: boolean; +} + +const DoneButton: React.FC = ({ + onClick, + logo, + className, + disabled, +}) => { + return ( + + ); +}; + +export default DoneButton; diff --git a/components/InterestCard.tsx b/components/InterestCard.tsx new file mode 100644 index 0000000..8e1c2f8 --- /dev/null +++ b/components/InterestCard.tsx @@ -0,0 +1,34 @@ +import Image from 'next/image'; + +interface InterestCardProps { + name: string; + isSelected: boolean; + onToggle: () => void; +} + +export default function InterestCard({ name, isSelected, onToggle }: InterestCardProps) { + return ( + + ); +} diff --git a/components/LifestylePreferencesCard.tsx b/components/LifestylePreferencesCard.tsx new file mode 100644 index 0000000..93bc284 --- /dev/null +++ b/components/LifestylePreferencesCard.tsx @@ -0,0 +1,37 @@ +import React from "react"; + +interface LifestylePreferencesCardProps { + // define props + title?: string; + imageSrc?: string; + isSelected?: boolean; + onClick?: () => void; +} + +const LifestylePreferencesCard = ({ + title, + imageSrc, + isSelected, + onClick, +}: LifestylePreferencesCardProps) => { + return ( +
+ {/* The circular image */} +
+ +
+

{title}

+ +
+ ); +}; + +export default LifestylePreferencesCard; diff --git a/components/NextStepButton.tsx b/components/NextStepButton.tsx new file mode 100644 index 0000000..0af7dc4 --- /dev/null +++ b/components/NextStepButton.tsx @@ -0,0 +1,55 @@ +import React from "react"; + +// Define the props for our button component +interface NextStepButtonProps { + onClick?: () => void; + logo?: React.ReactNode; + className?: string; + disabled?: boolean; +} + +const NextStepButton: React.FC = ({ + onClick, + logo, + className, + disabled, +}) => { + return ( + + ); +}; + +export default NextStepButton; diff --git a/components/ProgressHeader.tsx b/components/ProgressHeader.tsx new file mode 100644 index 0000000..95bc901 --- /dev/null +++ b/components/ProgressHeader.tsx @@ -0,0 +1,37 @@ +interface ProgressHeaderProps { + title: string; + subtitle: string; + currentStep: number; + totalSteps?: number; +} + +export default function ProgressHeader({ + title, + subtitle, + currentStep, + totalSteps = 5 +}: ProgressHeaderProps) { + const progressPercent = (currentStep / totalSteps) * 100; + + return ( +
+

+ {title} +

+

+ {subtitle} +

+ + {/* Progress Bar */} +
+
+
+
+ ); +} diff --git a/components/ScreenBlocker.tsx b/components/ScreenBlocker.tsx index 1423d72..d7793dd 100644 --- a/components/ScreenBlocker.tsx +++ b/components/ScreenBlocker.tsx @@ -16,7 +16,7 @@ export default function ScreenBlocker() {
MeteorMate Star Icon {/* Main Footer Content */} @@ -21,7 +21,7 @@ export default function ContactUs() {
MeteorMate Logo
diff --git a/components/landing/HeroSection.tsx b/components/landing/HeroSection.tsx index de10c47..349395a 100644 --- a/components/landing/HeroSection.tsx +++ b/components/landing/HeroSection.tsx @@ -23,7 +23,7 @@ export default function HeroSection() {
{/* spacing for fixed navbar */}
@@ -76,7 +76,7 @@ export default function HeroSection() {
Laptop showing MeteorMate interface

Fast Solution and Best Matches diff --git a/components/landing/Navbar.tsx b/components/landing/Navbar.tsx index 3d152d5..93bd22c 100644 --- a/components/landing/Navbar.tsx +++ b/components/landing/Navbar.tsx @@ -47,7 +47,7 @@ export default function Navbar() {
router.push("/")}>
MeteorMate Logo + {/* Back arrow */} -
- {/* OR line */} -
+
+ + {/* OR line */} +

OR
-
+
+ {/* Login form */}
diff --git a/src/app/authentication/verifyEmail/page.tsx b/src/app/authentication/verifyEmail/page.tsx index 5301ca4..974fcdb 100644 --- a/src/app/authentication/verifyEmail/page.tsx +++ b/src/app/authentication/verifyEmail/page.tsx @@ -92,8 +92,32 @@ export default function VerifyEmailPage() { } }; + const resendCode = async () => { + const email = localStorage.getItem("verificationEmail"); + if (!email) { + setError("No email found. Please log in again."); + return; + } + + try { + const res = await fetch("http://localhost:8000/api/auth/send-verification-code", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ email, purpose: "verify" }), + }); + + if (!res.ok) { + const data = await res.json().catch(() => ({})); + setError(data.detail || "Failed to resend code."); + } + } catch { + setError("Failed to resend code. Please try again."); + } + }; + + return ( - +

Verify Email @@ -142,6 +166,14 @@ export default function VerifyEmailPage() { {isVerifying && } {isVerifying ? "Verifying..." : "Verify Email"} + + +

); diff --git a/src/app/authentication/verifyPassword/page.tsx b/src/app/authentication/verifyPassword/page.tsx index 50c5c10..d464eb2 100644 --- a/src/app/authentication/verifyPassword/page.tsx +++ b/src/app/authentication/verifyPassword/page.tsx @@ -111,7 +111,7 @@ export default function VerifyPassword() { }; return ( - +

Verify Password diff --git a/src/app/onboarding/createProfile/page.tsx b/src/app/onboarding/createProfile/page.tsx new file mode 100644 index 0000000..b638ff8 --- /dev/null +++ b/src/app/onboarding/createProfile/page.tsx @@ -0,0 +1,293 @@ +"use client"; +import React from "react"; +import NextStepButton from "../../../../components/NextStepButton"; +import { useRouter } from "next/navigation"; +import ProgressHeader from "../../../../components/ProgressHeader"; +import { useRef, useState } from "react"; // mostly only for the profile picture + +export default function CreateProfilePage() { + const router = useRouter(); + const [age, setAge] = React.useState(""); + const [firstName, setFirstName] = React.useState(""); + const [lastName, setLastName] = React.useState(""); + const [major, setMajor] = React.useState(""); + const [year, setYear] = React.useState(""); + const [gender, setGender] = React.useState(""); + + //for the profile picture + const fileInputRef = useRef(null); + const [preview, setPreview] = useState(null); + + //to make sure before moving ahead that their whole thing is filled or not + const isFormValid = + firstName.trim() !== "" && + lastName.trim() !== "" && + major !== "" && + year !== "" && + gender !== "" && + age !== "" && + Number(age) > 0; + + + // const PeechiDuo = require("../../../public/images/peechi_duo.webp"); + + const handleGenderChange = (e: React.ChangeEvent) => { + setGender(e.target.value); + }; + + const handleAgeChange = (e: React.ChangeEvent) => { + setAge(Number(e.target.value)); + }; + + const handleFirstNameChange = (e: React.ChangeEvent) => { + setFirstName(e.target.value); + }; + + const handleLastNameChange = (e: React.ChangeEvent) => { + setLastName(e.target.value); + }; + + const handleMajorChange = (e: React.ChangeEvent) => { + setMajor(e.target.value); + }; + + const handleYearChange = (e: React.ChangeEvent) => { + setYear(e.target.value); + }; + + const handleNextStep = () => { + // Logic to handle the next step action + if (!isFormValid) { + alert("Please fill out all required fields."); + return; + } + console.log({ age, firstName, lastName, major, year, gender }); + router.push("/onboarding/lifestylePreferences"); + }; + + + const handleImageClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // to preview the image in the icon + const imageUrl = URL.createObjectURL(file); + setPreview(imageUrl); + }; + + return ( +
+ +
+ {/* profile picture */} +
+
+ +
+ {preview ? ( + Profile + ) : ( + Click to upload + )} +
+ + {/* Hidden file input */} + +
+ +
+ +
+ {/* first name */} +
+

First Name

+
+ +
+
+ {/* last name */} +
+

Last Name

+
+ +
+
+ {/* major */} +
+

Major

+ +
+ {/* year */} +
+

Year

+ +
+ {/* age */} +
+

Age

+
+ +
+
+ {/* Gender */} +
+

Gender

+ +
+ {/* next step button */} + {/*

Gender

*/} +
+
+ } + onClick={handleNextStep} + disabled={!isFormValid} + /> +
+
+
+ ); +} diff --git a/src/app/onboarding/housing/page.tsx b/src/app/onboarding/housing/page.tsx new file mode 100644 index 0000000..34b083c --- /dev/null +++ b/src/app/onboarding/housing/page.tsx @@ -0,0 +1,241 @@ +"use client"; + +import { useState } from "react"; +import LifestylePreferencesCard from "../../../../components/LifestylePreferencesCard"; +import DoneButton from "../../../../components/DoneButton"; +import ProgressHeader from "../../../../components/ProgressHeader"; +import React, { useMemo } from "react"; +import { useSearchParams, useRouter } from "next/navigation"; + + + +function OnCampusUI() { + const router = useRouter(); + const handleNextStep = () => { + router.push("/onboarding/dashboard"); + }; + + const [selectedLocation, setSelectedLocation] = useState( + null + ); + const [selectedHonorsStatus, setSelectedHonorsStatus] = useState( + null + ); + const [selectedLLCPreference, setSelectedLLCPreference] = useState< + string | null + >(null); + const handleToggle = ( + currentValue: string | null, + setValue: (val: string | null) => void, + newValue: string + ) => { + if (currentValue === newValue) { + setValue(null); + } else { + setValue(newValue); + } + } + + return ( +
+ +
+

+ Location: UV or Freshmen Dorms? +

+ {/*grid for the 3 options*/} +
+ handleToggle(selectedLocation, setSelectedLocation, "UV")} + /> + handleToggle(selectedLocation, setSelectedLocation, "FD")} + /> + +
+ +

+ Are you an Honors student? +

+
+ handleToggle(selectedHonorsStatus, setSelectedHonorsStatus, "yes")} + /> + handleToggle(selectedHonorsStatus, setSelectedHonorsStatus, "no")} + /> + +
+ +

+ Are you interested in being part of the Living Learning Community? +

+
+ handleToggle(selectedLLCPreference, setSelectedLLCPreference, "Yes")} + /> + handleToggle(selectedLLCPreference, setSelectedLLCPreference, "No")} + /> + +
+
+
+ } + onClick={handleNextStep} + disabled = {!selectedLLCPreference || !selectedHonorsStatus || !selectedLocation} + + /> +
+
+ ); +} + +function OffCampusUI() { + const router = useRouter(); + const handleNextStep = () => { + router.push("/onboarding/dashboard"); + }; + + const [selectedLeaseStatus, setSelectedLeaseStatus] = useState( + null + ); + const [selectedFindingPreference, setSelectedFindingPreference] = useState< + string | null + >(null); + + const handleToggle = ( + currentValue: string | null, + setValue: (val: string | null) => void, + newValue: string + ) => { + if (currentValue === newValue) { + setValue(null); + } else { + setValue(newValue); + } + } + return ( +
+ +
+

Do you have a lease?

+

+ This will help us pair you with the right people. +

+ {/*grid for the 3 options*/} +
+ handleToggle(selectedLeaseStatus, setSelectedLeaseStatus, "yes")} + /> + handleToggle(selectedLeaseStatus, setSelectedLeaseStatus, "no")} + /> + +
+ +

Have a lease:

+

+ Pick the option that best matches how long the roommate would stay. +

+
+ handleToggle(selectedFindingPreference, setSelectedFindingPreference, "temp")} + /> + handleToggle(selectedFindingPreference, setSelectedFindingPreference, "LongTerm")} + /> + +
+ + +
+
+ } + onClick={handleNextStep} + disabled = {!selectedFindingPreference || !selectedLeaseStatus} + + /> +
+
+ ); +} + +export default function HousingPage() { + + const router = useRouter(); + const searchParams = useSearchParams(); + + const living = useMemo( + () => searchParams.get("living") ?? "", + [searchParams] + ); + + + return ( +
+ {living === "onCampus" ? ( + + ) : living === "offCampus" ? ( + + ) : ( +
+

+ No living preference passed. Please go back and select one. +

+ +
+ + + )} + +
+ ); +} diff --git a/src/app/onboarding/interests/page.tsx b/src/app/onboarding/interests/page.tsx new file mode 100644 index 0000000..b04ec2a --- /dev/null +++ b/src/app/onboarding/interests/page.tsx @@ -0,0 +1,77 @@ +"use client"; +import React, { useState } from "react"; +import NextStepButton from "../../../../components/NextStepButton"; +import { useRouter } from "next/navigation"; +import InterestCard from '../../../../components/InterestCard'; +import ProgressHeader from "../../../../components/ProgressHeader"; + +const INTEREST_ROWS = [ + ['Climbing', 'Anime', 'Running', 'Instruments', 'Reading', 'Gaming'], + ['Travel', 'Blogging', 'Movies', 'Singing', 'Shopping', 'Cooking', 'Art'], + ['Organized', 'Photos', 'Basketball', 'Music', 'EDM', 'Coding'], + ['Bollywood', 'Sleeping', 'Scrapbook', 'Legos', 'D&D', 'Soccer', 'Pickleball'], + ['Chess', 'Concerts', 'K-Pop', 'Dancing', 'Languages', 'Badminton'] +]; + +const MAX_SELECTIONS = 6; + +export default function InterestsPage() { + const [selectedInterests, setSelectedInterests] = useState([]); + const router = useRouter(); + + const handleNextStep = () => { + router.push("/onboarding/lifestylePersonality"); + console.log(selectedInterests) + } + + const handleToggle = (interest: string) => { + setSelectedInterests(prev => { + if (prev.includes(interest)) { + return prev.filter(i => i !== interest); + } + if (prev.length >= MAX_SELECTIONS) { + return prev; + } + return [...prev, interest]; + }); + }; + + return ( +
+ +
+
+
+ {INTEREST_ROWS.map((row, rowIndex) => ( +
+ {row.map(interest => ( + handleToggle(interest)} + /> + ))} +
+ ))} +
+
+
+ } + onClick={handleNextStep} + /> +
+ ); +} \ No newline at end of file diff --git a/src/app/onboarding/layout.tsx b/src/app/onboarding/layout.tsx new file mode 100644 index 0000000..e8d751e --- /dev/null +++ b/src/app/onboarding/layout.tsx @@ -0,0 +1,17 @@ +import React from "react"; +export default function CreateProfilePageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
{children}
+
+ ); +} diff --git a/src/app/onboarding/lifestylePersonality/page.tsx b/src/app/onboarding/lifestylePersonality/page.tsx new file mode 100644 index 0000000..e13cd4a --- /dev/null +++ b/src/app/onboarding/lifestylePersonality/page.tsx @@ -0,0 +1,198 @@ +"use client"; +import React from "react"; +import { useState } from "react"; +import LifestylePreferencesCard from "../../../../components/LifestylePreferencesCard"; +import NextStepButton from "../../../../components/NextStepButton"; +import { useRouter } from "next/navigation"; +import ProgressHeader from "../../../../components/ProgressHeader"; + +export default function LifestylePersonalityPage() { + const router = useRouter(); + + const [selectedCookingPreference, setselectedCookingPreference] = useState( + null + ); + const [selectedPetPreferences, setselectedPetPreferences] = useState( + null + ); + const [selectedLivingPreference, setselectedLivingPreference] = useState< + string | null + >(null); + const [selectedGuestsPreference, setSelectedGuestsPreferences] = useState< + string | null + >(null); + const [selectedRoommatePreference, setSelectedRoommatePreference] = useState< + string | null + >(null); + + + + const handleToggle = ( + currentValue: string | null, + setValue: (val: string | null) => void, + newValue: string + ) => { + if (currentValue === newValue) { + setValue(null); + } else { + setValue(newValue); + } + } + + + + const handleNextStep = () => { + // Logic to handle the next step action + console.log("handling next step"); + console.log({ + selectedCookingPreference, + selectedPetPreferences, + selectedLivingPreference, + }); + + router.push(`/onboarding/housing?living=${encodeURIComponent(selectedLivingPreference ?? "")}`); + }; + return ( +
+ +
+

+ How often do you cook? +

+ {/*grid for the 3 options*/} +
+ handleToggle(selectedCookingPreference, setselectedCookingPreference, "Never")} + /> + handleToggle(selectedCookingPreference, setselectedCookingPreference, "Rarely")} + /> + handleToggle(selectedCookingPreference, setselectedCookingPreference, "Often")} + /> +
+ +

+ Pet preferences? +

+
+ handleToggle(selectedPetPreferences, setselectedPetPreferences, "Okay")} + /> + handleToggle(selectedPetPreferences, setselectedPetPreferences, "NotOkay")} + /> + handleToggle(selectedPetPreferences, setselectedPetPreferences, "HaveAPet")} + /> +
+ +

+ How often do you have guests over? +

+
+ handleToggle(selectedGuestsPreference, setSelectedGuestsPreferences, "Never")} + /> + handleToggle(selectedGuestsPreference, setSelectedGuestsPreferences, "Sometimes")} + /> + handleToggle(selectedGuestsPreference, setSelectedGuestsPreferences, "Often")} + /> +
+ +

+ How close would you like to be with your roommates? +

+ +
+ handleToggle(selectedRoommatePreference, setSelectedRoommatePreference, "NotClose")} + /> + handleToggle(selectedRoommatePreference, setSelectedRoommatePreference, "Friends")} + /> + handleToggle(selectedRoommatePreference, setSelectedRoommatePreference, "CloseFriends")} + /> +
+ +

+ Do you plan on living on-Campus or off-Campus? +

+ +
+ { + handleToggle(selectedLivingPreference, setselectedLivingPreference, "onCampus"); + }} + + /> + { + handleToggle(selectedLivingPreference, setselectedLivingPreference, "offCampus"); + }} + + /> + + +
+
+ } + onClick={handleNextStep} + disabled={!selectedLivingPreference || !selectedRoommatePreference || !selectedCookingPreference || !selectedGuestsPreference || !selectedPetPreferences} + /> +
+
+
+ ); +} diff --git a/src/app/onboarding/lifestylePreferences/page.tsx b/src/app/onboarding/lifestylePreferences/page.tsx new file mode 100644 index 0000000..32b5a6b --- /dev/null +++ b/src/app/onboarding/lifestylePreferences/page.tsx @@ -0,0 +1,138 @@ +"use client"; +import React from "react"; +import { useState } from "react"; +import LifestylePreferencesCard from "../../../../components/LifestylePreferencesCard"; +import NextStepButton from "../../../../components/NextStepButton"; +import { useRouter } from "next/navigation"; +import ProgressHeader from "../../../../components/ProgressHeader"; + +export default function LifestylePreferencesPage() { + const router = useRouter(); + + const [selectedWakeupTime, setSelectedWakeupTime] = useState( + null + ); + const [selectedCleanliness, setSelectedCleanliness] = useState( + null + ); + const [selectedNoiseTolerance, setSelectedNoiseTolerance] = useState< + string | null + >(null); + + const handleToggle = ( + currentValue: string | null, + setValue: (val: string | null) => void, + newValue: string + ) => { + if (currentValue === newValue) { + setValue(null); + } else { + setValue(newValue); + } + } + + + + const handleNextStep = () => { + // Logic to handle the next step action + console.log("handling next step"); + console.log({ + selectedWakeupTime, + selectedCleanliness, + selectedNoiseTolerance, + }); + router.push("/onboarding/interests"); + }; + return ( +
+ +
+

Wake-up Time

+

+ When are you the most active generally? +

+ {/*grid for the 3 options*/} +
+ handleToggle(selectedWakeupTime, setSelectedWakeupTime, "Early Bird")} + /> + handleToggle(selectedWakeupTime, setSelectedWakeupTime, "Flexible")} + /> + handleToggle(selectedWakeupTime, setSelectedWakeupTime, "Night Owl")} + /> +
+

Cleanliness

+

+ What is your preferred level of tidiness? +

+
+ handleToggle(selectedCleanliness, setSelectedCleanliness, "Orderly")} + /> + handleToggle(selectedCleanliness, setSelectedCleanliness, "Tidy")} + /> + handleToggle(selectedCleanliness, setSelectedCleanliness, "Neat Freak")} + /> +
+

Noise Tolerance

+

+ What noise level are you comfortable with? +

+
+ handleToggle(selectedNoiseTolerance, setSelectedNoiseTolerance, "Quiet")} + /> + handleToggle(selectedNoiseTolerance, setSelectedNoiseTolerance, "Moderate")} + /> + handleToggle(selectedNoiseTolerance, setSelectedNoiseTolerance, "Social")} + /> +
+
+ } + onClick={handleNextStep} + disabled = {!selectedCleanliness || !selectedNoiseTolerance || !selectedWakeupTime} + /> +
+
+
+ ); +}