diff --git a/backend/__pycache__/utils.cpython-311.pyc b/backend/__pycache__/utils.cpython-311.pyc index 93954d7..6a825b3 100644 Binary files a/backend/__pycache__/utils.cpython-311.pyc and b/backend/__pycache__/utils.cpython-311.pyc differ diff --git a/backend/images/biscuit.jpg b/backend/images/biscuit.jpg new file mode 100644 index 0000000..2f62d94 Binary files /dev/null and b/backend/images/biscuit.jpg differ diff --git a/backend/images/churro.jpg b/backend/images/churro.jpg new file mode 100644 index 0000000..43df41b Binary files /dev/null and b/backend/images/churro.jpg differ diff --git a/backend/images/cleo.jpg b/backend/images/cleo.jpg new file mode 100644 index 0000000..4995fca Binary files /dev/null and b/backend/images/cleo.jpg differ diff --git a/backend/images/coco.jpg b/backend/images/coco.jpg new file mode 100644 index 0000000..cac0bbe Binary files /dev/null and b/backend/images/coco.jpg differ diff --git a/backend/images/daisy.jpg b/backend/images/daisy.jpg new file mode 100644 index 0000000..a6f69dc Binary files /dev/null and b/backend/images/daisy.jpg differ diff --git a/backend/images/dusty.jpg b/backend/images/dusty.jpg new file mode 100644 index 0000000..29df50f Binary files /dev/null and b/backend/images/dusty.jpg differ diff --git a/backend/images/fang.jpg b/backend/images/fang.jpg new file mode 100644 index 0000000..ce1c628 Binary files /dev/null and b/backend/images/fang.jpg differ diff --git a/backend/images/fluffy.jpg b/backend/images/fluffy.jpg new file mode 100644 index 0000000..6e7945c Binary files /dev/null and b/backend/images/fluffy.jpg differ diff --git a/backend/images/gizmo.jpg b/backend/images/gizmo.jpg new file mode 100644 index 0000000..d99a63e Binary files /dev/null and b/backend/images/gizmo.jpg differ diff --git a/backend/images/hazel.jpg b/backend/images/hazel.jpg new file mode 100644 index 0000000..62412b2 Binary files /dev/null and b/backend/images/hazel.jpg differ diff --git a/backend/images/luna.png b/backend/images/luna.png new file mode 100644 index 0000000..b5d6b2e Binary files /dev/null and b/backend/images/luna.png differ diff --git a/backend/images/maple.jpg b/backend/images/maple.jpg new file mode 100644 index 0000000..d925f3d Binary files /dev/null and b/backend/images/maple.jpg differ diff --git a/backend/images/marshmallow.jpg b/backend/images/marshmallow.jpg new file mode 100644 index 0000000..26da6f8 Binary files /dev/null and b/backend/images/marshmallow.jpg differ diff --git a/backend/images/milo.jpg b/backend/images/milo.jpg new file mode 100644 index 0000000..fb7ee6a Binary files /dev/null and b/backend/images/milo.jpg differ diff --git a/backend/images/oreo.jpg b/backend/images/oreo.jpg new file mode 100644 index 0000000..143877d Binary files /dev/null and b/backend/images/oreo.jpg differ diff --git a/backend/images/peanut.jpg b/backend/images/peanut.jpg new file mode 100644 index 0000000..823b21b Binary files /dev/null and b/backend/images/peanut.jpg differ diff --git a/backend/images/rio.jpg b/backend/images/rio.jpg new file mode 100644 index 0000000..599e5c4 Binary files /dev/null and b/backend/images/rio.jpg differ diff --git a/backend/images/sky.jpg b/backend/images/sky.jpg new file mode 100644 index 0000000..53de961 Binary files /dev/null and b/backend/images/sky.jpg differ diff --git a/backend/images/spike.jpg b/backend/images/spike.jpg new file mode 100644 index 0000000..3d75068 Binary files /dev/null and b/backend/images/spike.jpg differ diff --git a/backend/images/tank.jpg b/backend/images/tank.jpg new file mode 100644 index 0000000..da32e58 Binary files /dev/null and b/backend/images/tank.jpg differ diff --git a/backend/images/thumper.jpg b/backend/images/thumper.jpg new file mode 100644 index 0000000..e1e4841 Binary files /dev/null and b/backend/images/thumper.jpg differ diff --git a/backend/images/tinkerbell.jpg b/backend/images/tinkerbell.jpg new file mode 100644 index 0000000..b0e29db Binary files /dev/null and b/backend/images/tinkerbell.jpg differ diff --git a/backend/images/tweety.jpg b/backend/images/tweety.jpg new file mode 100644 index 0000000..43c8439 Binary files /dev/null and b/backend/images/tweety.jpg differ diff --git a/backend/images/zazu.jpg b/backend/images/zazu.jpg new file mode 100644 index 0000000..ed7d135 Binary files /dev/null and b/backend/images/zazu.jpg differ diff --git a/backend/main.py b/backend/main.py index 653856d..75b87b3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -8,6 +8,7 @@ from utils import create_tables app = Flask(__name__, static_folder="images", static_url_path="/images") +app.secret_key = "intro_to_swe" CORS(app) Swagger(app) @@ -27,64 +28,390 @@ def init_db(): conn = get_db() create_tables(conn) - pets = [ - ("Buddy", - "Dog", - "Golden Retriever", - 3, - "Friendly", - "/images/buddy.jpg", - "Available", - ), - ("Whiskers", - "Cat", - "Siamese", - 2, - "Independent", - "/images/whiskers.jpg", - "Available", - ), - ("Rex", - "Dog", - "German Shepherd", - 4, - "Protective", - "/images/rex.jpg", - "Available", - ), - ("Max", - "Dog", - "Labrador Retriever", - 5, - "Playful", - "/images/max.jpg", - "Adopted", - ), - ("Mittens", - "Cat", - "Persian", - 1, - "Lazy", - "/images/mittens.jpg", - "Available", - ), - ("Bella", - "Dog", - "Bulldog", - 2, - "Gentle", - "/images/bella.jpg", - "Available"), + pets = [ + ( + "Buddy", + "Dog", + "Golden Retriever", + 3, + "Friendly", + "/images/buddy.jpg", + "Available", + "Large", + "Connecticut", + "Buddy was rescued from a rural shelter. He enjoys long walks and playing fetch with children. He's great with other dogs and loves water. Buddy is ready for a forever home with an active family." + ), + ( + "Whiskers", + "Cat", + "Siamese", + 2, + "Independent", + "/images/whiskers.jpg", + "Available", + "Small", + "New York", + "Whiskers grew up in a small city apartment. She enjoys napping in the sun and people-watching. Though independent, she warms up quickly with gentle petting. She would do best in a quiet home." + ), + ( + "Rex", + "Dog", + "German Shepherd", + 4, + "Protective", + "/images/rex.jpg", + "Available", + "Large", + "New Jersey", + "Rex received basic police dog training but was retired early due to sensitivity to loud noises. He is intelligent and eager to please. He bonds quickly with his handler. A calm environment will help Rex thrive." + ), + ( + "Max", + "Dog", + "Labrador Retriever", + 5, + "Playful", + "/images/max.jpg", + "Adopted", + "Large", + "Boston", + "Max was raised in a family with children and loves being around people. He has high energy and enjoys daily playtime. He knows basic commands and is house-trained. Max recently found his forever family." + ), + ( + "Mittens", + "Cat", + "Persian", + 1, + "Lazy", + "/images/mittens.jpg", + "Available", + "Small", + "Vermont", + "Mittens prefers the indoors and warm blankets. She was surrendered by an elderly owner. Her calm demeanor makes her ideal for apartment living. She is litter trained and easygoing." + ), + ( + "Bella", + "Dog", + "Bulldog", + 2, + "Gentle", + "/images/bella.jpg", + "Available", + "Medium", + "Connecticut", + "Bella came from a home with multiple pets and got along with all of them. She enjoys slow-paced walks and cuddles. She is calm and easy to groom. Bella is looking for a loving and peaceful home." + ), + ( + "Shadow", + "Dog", + "Husky", + 3, + "Energetic", + "/images/shadow.jpg", + "Available", + "Large", + "Maine", + "Shadow came to us recently and has been adjusting well. They are curious about new environments. They enjoy gentle interaction and thrive on companionship. They are great with people once they warm up. A stable and loving home will help them flourish." + ), + ( + "Luna", + "Cat", + "Maine Coon", + 3, + "Affectionate", + "/images/luna.png", + "Available", + "Medium", + "Massachusetts", + "Luna grew up with other animals and enjoys companionship. She's curious and loves exploring high shelves. Her favorite hobby is bird watching from the window. Luna will thrive in a home with enrichment." + ), + ( + "Tank", + "Turtle", + "Box Turtle", + 10, + "Calm", + "/images/tank.jpg", + "Available", + "Small", + "Rhode Island", + "Tank the turtle was found in a backyard pond. He enjoys basking and is surprisingly social during feeding time. He requires a clean habitat and proper UV light. Spike is a low-maintenance, peaceful companion." + ), + ( + "Thumper", + "Rabbit", + "Dutch", + 2, + "Shy", + "/images/thumper.jpg", + "Available", + "Small", + "New Hampshire", + "Thumper is a rabbit who loves cuddling in warm blankets. He's vocal and excited at mealtime. Kids love his gentle temperament. Patches is perfect for first-time pet owners." + ), + ( + "Coco", + "Rabbit", + "Lop", + 1, + "Playful", + "/images/coco.jpg", + "Available", + "Small", + "Connecticut", + "Coco is a fluffy rabbit with a playful attitude. She enjoys cardboard mazes and gentle grooming. Coco was born in a rescue shelter and has been well-socialized. She prefers quiet, calm spaces." + ), + ( + "Fluffy", + "Rabbit", + "Angora", + 4, + "Gentle", + "/images/fluffy.jpg", + "Available", + "Small", + "Massachusetts", + "Fluffy the rabbit was part of a college experiment. After the term ended, she was rehomed to the shelter. She's easy to care for and enjoys running garden laps. Fluffy is best for beginner pet-owners." + ), + ( + "Hazel", + "Rabbit", + "Mini Rex", + 3, + "Curious", + "/images/hazel.jpg", + "Available", + "Small", + "Vermont", + "Hazel was raised in a peaceful backyard hutch with her littermates. She enjoys tunneling under blankets and nibbling on fresh herbs. She's curious but settles down in a lap quickly. Hazel loves routine and would thrive in a calm environment." + ), + ( + "Daisy", + "Rabbit", + "Lionhead", + 2, + "Timid", + "/images/daisy.jpg", + "Available", + "Small", + "New York", + "Daisy was surrendered due to housing restrictions. She's shy at first but enjoys gentle petting and head rubs. Her favorite treat is dandelion greens. Daisy is ready for a patient home where she can blossom." + ), + ( + "Tweety", + "Bird", + "Canary", + 1, + "Cheerful", + "/images/tweety.jpg", + "Available", + "Tiny", + "Connecticut", + "Tweety greets the morning with cheerful songs. She was part of a small aviary in a classroom. She's friendly and easy to care for. Tweety adds sunshine to any space." + ), + ( + "Sky", + "Bird", + "Parakeet", + 2, + "Social", + "/images/sky.jpg", + "Available", + "Tiny", + "Rhode Island", + "Sky was raised with a small flock and thrives with companionship. She chirps happily when she sees her humans. Sky enjoys millet and mirrors. She's best placed with another bird or a chatty home." + ), + ( + "Zazu", + "Bird", + "Lovebird", + 3, + "Loyal", + "/images/zazu.jpg", + "Available", + "Tiny", + "New Jersey", + "Zazu is bonded with another lovebird and shares everything, including food. He whistles and mimics sounds throughout the day. Zazu was brought in after his owner moved abroad. He brings joy wherever he goes." + ), + ( + "Cleo", + "Bird", + "Cockatoo", + 6, + "Talkative", + "/images/cleo.jpg", + "Available", + "Medium", + "Massachusetts", + "Cleo can mimic a handful of phrases and laughs when she's excited. She enjoys music and bopping to beats. Cleo needs mental stimulation and interaction. She's very social and loves attention." + ), + ( + "Rio", + "Bird", + "Macaw", + 5, + "Smart", + "/images/rio.jpg", + "Available", + "Large", + "Maine", + "Rio is a majestic macaw who loves fruit snacks. He was previously part of a parrot sanctuary. Rio needs lots of space and frequent interaction. He's clever and learns new tricks quickly." + ), + ( + "Gizmo", + "Guinea Pig", + "American", + 2, + "Vocal", + "/images/gizmo.jpg", + "Available", + "Small", + "Connecticut", + "Gizmo wheeks excitedly every time the fridge opens. He enjoys lap time and munching on lettuce. Gizmo was raised with kids and loves being held. He is full of energy and curiosity." + ), + ( + "Peanut", + "Guinea Pig", + "Abyssinian", + 3, + "Curious", + "/images/peanut.jpg", + "Available", + "Small", + "Massachusetts", + "Peanut loves burrowing through tunnels and hay piles. He's curious and greets visitors with little squeaks. Peanut does best with another guinea pig. He enjoys exploring new enrichment toys." + ), + ( + "Maple", + "Guinea Pig", + "Peruvian", + 1, + "Calm", + "/images/maple.jpg", + "Available", + "Small", + "New York", + "Maple is calm and content sitting on a lap for hours. She was surrendered after her owner moved overseas. Maple loves gentle grooming and quiet time. She prefers soft bedding and a slow pace." + ), + ( + "Biscuit", + "Guinea Pig", + "Teddy", + 2, + "Playful", + "/images/biscuit.jpg", + "Available", + "Small", + "New Jersey", + "Biscuit chirps happily when being hand-fed treats. He's a playful guinea pig who races around during floor time. He loves chasing balls and cuddling in cozy pouches. Biscuit makes a fun companion." + ), + ( + "Oreo", + "Guinea Pig", + "Silkie", + 4, + "Timid", + "/images/oreo.jpg", + "Available", + "Small", + "Connecticut", + "Oreo recently lost his bonded friend and is looking for companionship. He's a bit shy but warms up with patience. Oreo enjoys hiding tunnels and chew toys. He thrives with consistent care." + ), + ( + "Spike", + "Reptile", + "Bearded Dragon", + 5, + "Chill", + "/images/spike.jpg", + "Available", + "Medium", + "Connecticut", + "Spike basks under his heat lamp most of the day. He enjoys eating mealworms and lounging on rocks. Spike is used to handling and rarely gets stressed. He's calm and great for beginners." + ), + ( + "Fang", + "Reptile", + "Corn Snake", + 3, + "Docile", + "/images/fang.jpg", + "Available", + "Medium", + "Rhode Island", + "Fang was bred in captivity and is easy to handle. He slithers slowly and prefers calm spaces. Fang loves hiding under warm rocks. He's docile and well-suited for new reptile owners." + ), + ( + "Churro", + "Ferret", + "Standard", + 2, + "Energetic", + "/images/churro.jpg", + "Available", + "Small", + "Massachusetts", + "Churro zooms through tunnels and leaps between hammocks. He's an energetic ferret who plays all day. Churro loves socks and hoarding toys. He needs regular interaction to stay happy." + ), + ( + "Marshmallow", + "Hamster", + "Syrian", + 1, + "Adventurous", + "/images/marshmallow.jpg", + "Available", + "Tiny", + "New Hampshire", + "Marshmallow runs endlessly on her exercise wheel. She's curious and often peeks out to greet visitors. Marshmallow was adopted from a classroom pet program. She's a delight to watch in action." + ), + ( + "Dusty", + "Chinchilla", + "Standard", + 4, + "Fast", + "/images/dusty.jpg", + "Available", + "Small", + "Maine", + "Dusty spins in circles when it's bath time. He loves his dust baths and running up ramps. Dusty is fast and agile, often bouncing from shelf to shelf. He prefers cool, quiet spaces." + ), + ( + "Milo", + "Dog", + "Beagle", + 3, + "Loyal", + "/images/milo.jpg", + "Available", + "Medium", + "Connecticut", + "Milo has a nose for adventure and is always sniffing around. He enjoys tracking scents and playing fetch. Milo was surrendered due to his family moving. He thrives in an active home." + ), + ( + "Tinkerbell", + "Cat", + "Ragdoll", + 4, + "Laid-back", + "/images/tinkerbell.jpg", + "Available", + "Medium", + "New York", + "Tinkerbell melts into your lap and loves snuggling for hours. She's soft-spoken and follows people quietly. Tinkerbell was raised with gentle children.She's a perfect lap cat." + ) ] + cursor = conn.cursor() cursor.executemany( """ - INSERT INTO animals (name, species, breed, age, personality, image_path, adoption_status) - VALUES (?, ?, ?, ?, ?, ?, ?)""", + INSERT INTO animals (name, species, breed, age, personality, image_path, adoption_status, size, location, history) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, pets, ) - conn.commit() conn.close() @@ -235,6 +562,22 @@ def remove_saved_pet(pet_id): except sqlite3.Error as e: print(f"An error occurred: {e}") return jsonify({"error": "Internal Server Error"}), 500 + +@app.route("/api/login", methods=["POST"]) +def login(): + data = request.get_json() + username = data.get("username") + password = data.get("password") + + conn = get_db() + cursor = conn.cursor() + cursor.execute("SELECT * FROM adopters WHERE username = ? AND password = ?", (username, password)) + user = cursor.fetchone() + conn.close() + + if user: + return jsonify({"message": "Logged in successfully."}), 200 + return jsonify({"error": "Invalid credentials"}), 401 def before_first_request(): """Run once before the first request to initialize the database.""" diff --git a/backend/utils.py b/backend/utils.py index f5c697b..90fc8a6 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -16,7 +16,10 @@ def create_tables(conn): age INTEGER, personality TEXT, image_path TEXT, - adoption_status TEXT DEFAULT 'Available' + adoption_status TEXT DEFAULT 'Available', + size TEXT, + location TEXT, + history TEXT ) """ ) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5ea889f..fada179 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,31 +1,38 @@ -import { Routes, Route } from 'react-router-dom'; -import NavBar from './components/NavBar'; -import Home from './pages/Home'; -import Search from './pages/Search'; -import MoreInfo from "./pages/MoreInfo"; -import PetProfile from './pages/PetProfile'; -import Profile from './pages/Profile'; +import { Routes, Route, Navigate, useLocation } from "react-router-dom"; +import Home from "./pages/Home"; +import Profile from "./pages/Profile"; +import PetProfile from "./pages/PetPage"; import PetDetails from "./pages/PetDetails"; +import Search from "./pages/Search"; +import MyList from "./pages/MyList"; +import MoreInfo from "./pages/MoreInfo"; import AdoptionPage from "./pages/AdoptionPage"; -import MyList from './pages/MyList'; +import NavBar from "./components/NavBar"; +import { JSX } from "react"; + +function ProtectedRoute({ children }: { children: JSX.Element }) { + const isLoggedIn = localStorage.getItem("loggedIn") === "true"; + return isLoggedIn ? children : ; +} +export default function App() { + const location = useLocation(); + const hideNav = location.pathname === "/profile" || location.pathname === "/more-info"; -function App() { return ( <> - + {!hideNav && } + } /> - } /> - } /> - } /> } /> } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> ); } - -export default App; \ No newline at end of file diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..af442a8 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,11 @@ +import { Outlet } from "react-router-dom"; +import Navbar from "./NavBar"; + +export default function Layout() { + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index 1ae7d6b..bc06bee 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -1,7 +1,25 @@ -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { FaHome, FaSearch, FaUser } from "react-icons/fa"; +import { useState, useEffect } from "react"; export default function NavBar() { + const [loggedIn, setLoggedIn] = useState(localStorage.getItem("loggedIn") === "true"); + const navigate = useNavigate(); + + useEffect(() => { + const handleStorageChange = () => { + setLoggedIn(localStorage.getItem("loggedIn") === "true"); + }; + window.addEventListener("storage", handleStorageChange); + return () => window.removeEventListener("storage", handleStorageChange); + }, []); + + const handleLogout = () => { + localStorage.removeItem("loggedIn"); + setLoggedIn(false); + navigate("/profile"); + }; + return ( ); -} \ No newline at end of file +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bdbcb9f..b63d567 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,13 +1,13 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; -import './index.css'; import { BrowserRouter } from 'react-router-dom'; +import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( - , -); + +); \ No newline at end of file diff --git a/frontend/src/pages/MoreInfo.tsx b/frontend/src/pages/MoreInfo.tsx index 1a35315..ba711f1 100644 --- a/frontend/src/pages/MoreInfo.tsx +++ b/frontend/src/pages/MoreInfo.tsx @@ -59,7 +59,7 @@ export default function MoreInfo() { style={buttonStyle} onClick={() => { if (agree) { - navigate("/dashboard"); + navigate("/"); } else { alert("Please agree to continue."); } diff --git a/frontend/src/pages/PetProfile.tsx b/frontend/src/pages/PetPage.tsx similarity index 100% rename from frontend/src/pages/PetProfile.tsx rename to frontend/src/pages/PetPage.tsx diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx index 807b615..8060bbf 100644 --- a/frontend/src/pages/Profile.tsx +++ b/frontend/src/pages/Profile.tsx @@ -1,9 +1,5 @@ import { useState, CSSProperties } from "react"; -import { Link } from "react-router-dom"; import { useNavigate } from "react-router-dom"; - - - import dogImage from "../assets/dog-profile.png"; import instagramLogo from "../assets/instagram_logo.png"; import facebookLogo from "../assets/facebook_logo.png"; @@ -13,7 +9,37 @@ import menuLogo from "../assets/menu_logo.png"; export default function Profile() { const [isSignUp, setIsSignUp] = useState(false); - const navigate = useNavigate() + const [loginUsername, setLoginUsername] = useState(""); + const [loginPassword, setLoginPassword] = useState(""); + const navigate = useNavigate(); + + const handleLogin = () => { + fetch("http://localhost:5000/api/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: loginUsername, + password: loginPassword, + }), + }) + .then((res) => { + if (res.ok) { + localStorage.setItem("loggedIn", "true"); + navigate("/"); + } else { + alert("Invalid credentials"); + } + }) + .catch((err) => { + console.error("Login error:", err); + alert("Something went wrong."); + }); + }; + + const handleSignup = () => { + localStorage.setItem("loggedIn", "true"); + navigate("/more-info"); + }; return (
@@ -22,14 +48,18 @@ export default function Profile() {

{isSignUp ? "Sign Up" : "Sign In"}

setLoginUsername(e.target.value)} /> setLoginPassword(e.target.value)} /> {isSignUp && ( @@ -39,22 +69,7 @@ export default function Profile() { )} - {!isSignUp && ( -
- Forgot Password? -
- )} - - @@ -87,9 +102,7 @@ export default function Profile() { Twitter YouTube
-
- Menu -
+ Menu ); @@ -99,15 +112,15 @@ const containerStyle: CSSProperties = { display: "flex", flexDirection: "column", minHeight: "100vh", - backgroundColor: "rgb(180, 164, 143)" + backgroundColor: "rgb(180, 164, 143)", }; const contentWrapperStyle: CSSProperties = { display: "flex", flex: 1, - flexWrap: "wrap", + flexWrap: "wrap", justifyContent: "center", - alignItems: "stretch" + alignItems: "stretch", }; const formBoxStyle: CSSProperties = { @@ -122,7 +135,6 @@ const formBoxStyle: CSSProperties = { flexDirection: "column", alignItems: "center", color: "white", - boxSizing: "border-box" }; const inputStyle: CSSProperties = { @@ -132,12 +144,7 @@ const inputStyle: CSSProperties = { border: "none", margin: "0.5rem 0", fontSize: "1rem", - backgroundColor: "#e0d9d9" -}; - -const headerStyle: CSSProperties = { - fontSize: "2rem", - marginBottom: "1rem" + backgroundColor: "#e0d9d9", }; const buttonStyle: CSSProperties = { @@ -148,26 +155,25 @@ const buttonStyle: CSSProperties = { padding: "0.75rem 2rem", fontSize: "1rem", cursor: "pointer", - margin: "1rem 0" + margin: "1rem 0", }; -const forgotStyle: CSSProperties = { - width: "100%", - textAlign: "right", - fontSize: "0.85rem" +const headerStyle: CSSProperties = { + fontSize: "2rem", + marginBottom: "1rem", }; const footerTextStyle: CSSProperties = { fontSize: "0.9rem", textAlign: "center", - marginTop: "1rem" + marginTop: "1rem", }; const requirementsStyle: CSSProperties = { textAlign: "left", fontSize: "0.85rem", margin: "0.5rem 0 1rem", - color: "#ddd" + color: "#ddd", }; const sideImageStyle: CSSProperties = { @@ -176,7 +182,7 @@ const sideImageStyle: CSSProperties = { maxWidth: "600px", height: "100vh", objectFit: "cover", - display: "block" + display: "block", }; const footerStyle: CSSProperties = { @@ -187,23 +193,22 @@ const footerStyle: CSSProperties = { display: "flex", justifyContent: "space-between", alignItems: "center", - flexWrap: "wrap", - boxSizing: "border-box" + boxSizing: "border-box", }; const footerLeftStyle: CSSProperties = { display: "flex", gap: "1.5rem", - flexWrap: "wrap" + flexWrap: "wrap", }; const iconStyle: CSSProperties = { width: "36px", - height: "36px" + height: "36px", }; const linkStyle: CSSProperties = { color: "#fff", textDecoration: "underline", - cursor: "pointer" + cursor: "pointer", }; diff --git a/frontend/src/pages/Search.tsx b/frontend/src/pages/Search.tsx index 7c8273f..dffbfd9 100644 --- a/frontend/src/pages/Search.tsx +++ b/frontend/src/pages/Search.tsx @@ -1,89 +1,125 @@ -import { CSSProperties, useState } from "react"; +import { CSSProperties, useEffect, useState } from "react"; +import { Link } from "react-router-dom"; import instagramLogo from "../assets/instagram_logo.png"; import facebookLogo from "../assets/facebook_logo.png"; import twitterLogo from "../assets/twitter_logo.png"; import youtubeLogo from "../assets/youtube_logo.png"; import menuLogo from "../assets/menu_logo.png"; +type Pet = { + id: number; + name: string; + species: string; + breed: string; + age: number; + personality: string; + image_path: string; + size: string; + location: string; +}; + export default function Search() { - const [location, setLocation] = useState(""); - const [radius, setRadius] = useState(500); + const [pets, setPets] = useState([]); + const [filters, setFilters] = useState({ + species: "", + breed: "", + age: "", + personality: "", + size: "", + location: "" + }); + const [filterOptions, setFilterOptions] = useState({}); + + useEffect(() => { + fetch("http://localhost:5000/api/pets") + .then(res => res.json()) + .then(data => { + setPets(data); + // Extract unique values for each filter + const options = { + species: [...new Set(data.map((p: Pet) => p.species))], + breed: [...new Set(data.map((p: Pet) => p.breed))], + age: [...new Set(data.map((p: Pet) => p.age.toString()))], + personality: [...new Set(data.map((p: Pet) => p.personality))], + size: [...new Set(data.map((p: Pet) => p.size))], + location: [...new Set(data.map((p: Pet) => p.location))] + }; + setFilterOptions(options); + }) + .catch(err => console.error("Error fetching pets:", err)); + }, []); + + const handleFilterChange = (key: string, value: string) => { + setFilters(prev => ({ ...prev, [key]: value })); + }; + + const filteredPets = pets.filter(pet => + Object.entries(filters).every(([key, val]) => + val === "" ? true : (pet as any)[key].toString() === val + ) + ); return (

- Ready for pet adoption, or just looking around? -
- Get started by searching by criteria. + Find a pet by choosing characteristics below!

-
-

Pet Age

- -
-
-

Breed

- -
-
-

Size

- -
-
-

Temperament

- -
+ {Object.entries(filterOptions).map(([key, values]) => ( +
+

{capitalize(key)}

+ +
+ ))}
-
-
-

Location

- setLocation(e.target.value)} - style={inputStyle} - placeholder="Enter location" - /> -
-
-

Search Radius

- setRadius(Number(e.target.value))} - style={{ width: "100%" }} - /> -

{radius} mi

-
+
+

+ Showing {filteredPets.length} result(s) +

-
- +
+ {filteredPets.map(pet => ( + + {pet.name} +

{pet.name}

+

{pet.breed}

+ + ))}
@@ -100,6 +136,8 @@ export default function Search() { ); } +const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); + // === Styles === const pageWrapperStyle: CSSProperties = { @@ -150,31 +188,6 @@ const dropdownStyle: CSSProperties = { border: "1px solid #999" }; -const inputStyle: CSSProperties = { - width: "100%", - padding: "0.5rem", - borderRadius: "30px", - border: "none", - fontSize: "1rem" -}; - -const searchOptionsRow: CSSProperties = { - display: "flex", - gap: "1.5rem", - flexWrap: "wrap", - marginBottom: "1rem" -}; - -const searchButton: CSSProperties = { - padding: "0.75rem 2rem", - fontSize: "1.1rem", - borderRadius: "1rem", - backgroundColor: "black", - color: "white", - border: "none", - cursor: "pointer" -}; - const footerStyle: CSSProperties = { backgroundColor: "black", color: "white", @@ -193,3 +206,11 @@ const iconStyle: CSSProperties = { width: "36px", height: "36px" }; + +const resultImageStyle: CSSProperties = { + width: "100%", + maxHeight: "300px", + objectFit: "contain", + borderRadius: "12px", + backgroundColor: "#a89c8a" +}; \ No newline at end of file diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json index db0becc..ab616b3 100644 --- a/frontend/tsconfig.node.json +++ b/frontend/tsconfig.node.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "jsx": "react-jsx", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "target": "ES2022", "lib": ["ES2023"],