From fef9b383714cb8f57e43dd553560c5db88e708cd Mon Sep 17 00:00:00 2001 From: prince rusweka Date: Sun, 4 May 2025 22:12:33 -0400 Subject: [PATCH] setting up develop branch with new pages and features --- backend/__pycache__/utils.cpython-311.pyc | Bin 1593 -> 2100 bytes backend/main.py | 103 +++++++++++++++++- backend/utils.py | 43 +++++--- frontend/src/App.tsx | 24 +++++ frontend/src/components/NavBar.tsx | 10 ++ frontend/src/components/PAOLayout.tsx | 43 ++++++++ frontend/src/pages/AdoptionPage.tsx | 76 ++++++++++++-- frontend/src/pages/AllApplicantsPage.tsx | 75 ++++++++++++++ frontend/src/pages/ApplicantDetailPage.tsx | 115 +++++++++++++++++++++ frontend/src/pages/ApplicantsPage.tsx | 35 +++++++ frontend/src/pages/MoreInfo.tsx | 65 ++++-------- frontend/src/pages/MyList.tsx | 84 +++++++-------- frontend/src/pages/PAOSystem.tsx | 51 +++++++++ frontend/src/pages/PaoDashboard.tsx | 8 ++ frontend/src/pages/PaoListingPanel.tsx | 75 ++++++++++++++ frontend/src/pages/PetCreatePage.tsx | 72 +++++++++++++ frontend/src/pages/PetEditPage.tsx | 104 +++++++++++++++++++ frontend/src/pages/Profile.tsx | 14 ++- 18 files changed, 878 insertions(+), 119 deletions(-) create mode 100644 frontend/src/components/PAOLayout.tsx create mode 100644 frontend/src/pages/AllApplicantsPage.tsx create mode 100644 frontend/src/pages/ApplicantDetailPage.tsx create mode 100644 frontend/src/pages/ApplicantsPage.tsx create mode 100644 frontend/src/pages/PAOSystem.tsx create mode 100644 frontend/src/pages/PaoDashboard.tsx create mode 100644 frontend/src/pages/PaoListingPanel.tsx create mode 100644 frontend/src/pages/PetCreatePage.tsx create mode 100644 frontend/src/pages/PetEditPage.tsx diff --git a/backend/__pycache__/utils.cpython-311.pyc b/backend/__pycache__/utils.cpython-311.pyc index 6a825b36f3722ac157b66d74eacab6d7a2113c2c..ce4b1ebb9173f8dfe7b437c49f1245e2e55b9487 100644 GIT binary patch delta 543 zcmZXQy-OoO7{=e(oz;z+kkg2If^HGefYmi3_(8<=gIrcID~KREW*1#aV%(3~4+QJ* zih_lu$AO*KTy=lKwOWY?)+@0PL2)KVEPRLg&HM5^!#vF2mOt^?U(sk2@Y?L^+?a0w zxD&%ik$O{Y;mrwHZ~#kSksPF?{D&qia(!1{E6W#^n@&P2PeUqjpJK2x{3iEb9+Qeh z{^2%B;xui?jQqV}ph@F}0Zrj49g*0hFskKp!O63dk7M$9tZe%$j-|~Qraq<5vDHuLaCYNvVT3MCXZT~9#w(g6GQQAU zT;=~D{>3}u*o7-xRT4iSmQWucrbHeE!WuMCnh%YABLr25st`H&$dSNvrFW;U$Aumj zI!9PT5|qecWoMvAy1z;AFP{BRpNp4H*vmK7B2QMZ|owQN1S31vb^ OC=HU<5WYg(C-4Sty@+oB delta 405 zcmdlYu#+ctIWI340}uoq63JM=!octt#DM`ODC4sTkTIPhg&~D8harj~g{g%hiZO*T zm_d{IB}j>1G9y$ah+>A~&sQe;ubHgMsA0rQt}n@@=Fv_D>92q^b+SyzQCffxt4Jv6VrOGNzC$-jaft| zn=y+q>2OWXV9}gBmBo_LZ}M?A6-JiHZ`gvvIDxja0&#IUkZ53dz$@4v-4)##+Y{U1 z_JD<}-LJ{-0}~%7$46!XR<E8+!mL19v?4kSJ>GcqzhVBl*2 Q!w+m)j6xq6FbS|h0Hv^DCIA2c diff --git a/backend/main.py b/backend/main.py index 75b87b3..5f30a65 100644 --- a/backend/main.py +++ b/backend/main.py @@ -9,7 +9,7 @@ app = Flask(__name__, static_folder="images", static_url_path="/images") app.secret_key = "intro_to_swe" -CORS(app) +CORS(app, resources={r"/api/*": {"origins": "*"}}) Swagger(app) DATABASE = "animal_shelter.db" @@ -583,6 +583,107 @@ def before_first_request(): """Run once before the first request to initialize the database.""" init_db() +@app.route("/api/pets//applicants", methods=["GET"]) +def get_applicants(pet_id): + """Retrieve all applicants for a specific pet.""" + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute("SELECT * FROM applicants WHERE pet_id = ?", (pet_id,)) + applicants = cursor.fetchall() + applicant_list = [dict(app) for app in applicants] + conn.close() + return jsonify(applicant_list), 200 + except sqlite3.Error as e: + print(f"An error occurred: {e}") + return jsonify({"error": "Internal Server Error"}), 500 + +@app.route("/api/applicants", methods=["GET"]) +def get_all_applicants(): + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute("SELECT * FROM applicants") + applicants = cursor.fetchall() + return jsonify([dict(row) for row in applicants]), 200 + except sqlite3.Error as e: + print(f"An error occurred: {e}") + return jsonify({"error": "Internal Server Error"}), 500 + +@app.route("/api/pets//applicants", methods=["POST"]) +def submit_applicant(pet_id): + """Submit a new applicant for a specific pet.""" + try: + data = request.get_json() + name = data.get("name") + email = data.get("email") + location = data.get("location") + username = data.get("username") + password = data.get("password") + reason = data.get("reason") + care = data.get("care") + home = data.get("home") + + if not name or not reason: + return jsonify({"error": "Missing name or reason"}), 400 + + conn = get_db() + cursor = conn.cursor() + cursor.execute(""" + INSERT INTO applicants (pet_id, name, email, location, username, password, reason, care, home) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, (pet_id, name, email, location, username, password, reason, care, home)) + conn.commit() + conn.close() + + return jsonify({"message": "Application submitted"}), 201 + + except Exception as e: + print(f"Application submission error: {e}") + return jsonify({"error": "Internal Server Error"}), 500 + +@app.route("/api/applicants/", methods=["GET"]) +def get_applicant_detail(applicant_id): + """Return full detail for a specific applicant by ID.""" + try: + conn = get_db() + cursor = conn.cursor() + cursor.execute("SELECT * FROM applicants WHERE id = ?", (applicant_id,)) + applicant = cursor.fetchone() + conn.close() + + if applicant: + return jsonify(dict(applicant)), 200 + else: + return jsonify({"error": "Applicant not found"}), 404 + except Exception as e: + print(f"Error fetching applicant: {e}") + return jsonify({"error": "Internal server error"}), 500 + +@app.route("/api/applicants//status", methods=["PATCH"]) +def update_applicant_status(applicant_id): + """Update the status of an applicant (approved/denied).""" + try: + data = request.get_json() + new_status = data.get("status") + + if new_status not in ["approved", "denied"]: + return jsonify({"error": "Invalid status"}), 400 + + conn = get_db() + cursor = conn.cursor() + cursor.execute(""" + UPDATE applicants SET status = ? + WHERE id = ? + """, (new_status, applicant_id)) + conn.commit() + conn.close() + + return jsonify({"message": f"Applicant status updated to {new_status}"}), 200 + except Exception as e: + print(f"Status update error: {e}") + return jsonify({"error": "Internal Server Error"}), 500 + app.before_request_funcs.setdefault(None, []).append(before_first_request) diff --git a/backend/utils.py b/backend/utils.py index 90fc8a6..7545742 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -1,13 +1,7 @@ -# utils.py -import sqlite3 - - def create_tables(conn): - """Create tables for the database if they do not exist.""" cursor = conn.cursor() - cursor.execute( - """ + cursor.execute(""" CREATE TABLE IF NOT EXISTS animals ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, @@ -21,11 +15,9 @@ def create_tables(conn): location TEXT, history TEXT ) - """ - ) + """) - cursor.execute( - """ + cursor.execute(""" CREATE TABLE IF NOT EXISTS adopters ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, @@ -33,14 +25,31 @@ def create_tables(conn): email TEXT UNIQUE, join_date TEXT DEFAULT CURRENT_TIMESTAMP ) - """ - ) + """) + + cursor.execute(""" + CREATE TABLE IF NOT EXISTS saved_pets ( + id INTEGER PRIMARY KEY, + pet_id INTEGER NOT NULL, + FOREIGN KEY(pet_id) REFERENCES animals(id) + ) + """) cursor.execute(""" - CREATE TABLE IF NOT EXISTS saved_pets ( - id INTEGER PRIMARY KEY, - pet_id INTEGER NOT NULL, - FOREIGN KEY(pet_id) REFERENCES animals(id) + CREATE TABLE IF NOT EXISTS applicants ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + pet_id INTEGER, + name TEXT, + location TEXT, + email TEXT, + username TEXT, + password TEXT, + reason TEXT, + care TEXT, + home TEXT, + status TEXT DEFAULT 'pending', + submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(pet_id) REFERENCES animals(id) ) """) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fada179..8c985ba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,6 +8,12 @@ import MyList from "./pages/MyList"; import MoreInfo from "./pages/MoreInfo"; import AdoptionPage from "./pages/AdoptionPage"; import NavBar from "./components/NavBar"; +import PAOSystem from "./pages/PAOSystem"; +import PaoListingsPanel from "./pages/PaoListingPanel"; +import PetEditPage from "./pages/PetEditPage"; +import AllApplicantsPage from "./pages/AllApplicantsPage"; +import PetCreatePage from "./pages/PetCreatePage"; +import ApplicantDetailPage from "./pages/ApplicantDetailPage"; import { JSX } from "react"; function ProtectedRoute({ children }: { children: JSX.Element }) { @@ -15,6 +21,12 @@ function ProtectedRoute({ children }: { children: JSX.Element }) { return isLoggedIn ? children : ; } +function AdminRoute({ children }: { children: JSX.Element }) { + const isAdmin = localStorage.getItem("loggedIn") === "true" && + localStorage.getItem("role") === "admin"; + return isAdmin ? children : ; +} + export default function App() { const location = useLocation(); const hideNav = location.pathname === "/profile" || location.pathname === "/more-info"; @@ -32,6 +44,18 @@ export default function App() { } /> } /> } /> + + {/* Admin-only routes */} + } /> + } /> + } /> + } /> + } /> + + + + } /> ); diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index bc06bee..3519693 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -4,11 +4,13 @@ import { useState, useEffect } from "react"; export default function NavBar() { const [loggedIn, setLoggedIn] = useState(localStorage.getItem("loggedIn") === "true"); + const [isAdmin, setIsAdmin] = useState(localStorage.getItem("role") === "admin"); const navigate = useNavigate(); useEffect(() => { const handleStorageChange = () => { setLoggedIn(localStorage.getItem("loggedIn") === "true"); + setIsAdmin(localStorage.getItem("role") === "admin"); }; window.addEventListener("storage", handleStorageChange); return () => window.removeEventListener("storage", handleStorageChange); @@ -16,7 +18,9 @@ export default function NavBar() { const handleLogout = () => { localStorage.removeItem("loggedIn"); + localStorage.removeItem("role"); setLoggedIn(false); + setIsAdmin(false); navigate("/profile"); }; @@ -35,6 +39,12 @@ export default function NavBar() { Pets My List + + {isAdmin && ( + + PAO System + + )} )} diff --git a/frontend/src/components/PAOLayout.tsx b/frontend/src/components/PAOLayout.tsx new file mode 100644 index 0000000..587ff8c --- /dev/null +++ b/frontend/src/components/PAOLayout.tsx @@ -0,0 +1,43 @@ +import React, { useState } from "react"; +import { Outlet, useNavigate } from "react-router-dom"; + +export default function PAOLayout() { + const navigate = useNavigate(); + const [menuOpen, setMenuOpen] = useState(true); + + return ( +
+
+
setMenuOpen(!menuOpen)} + > + ☰ +
+ {menuOpen && ( +
+ + + + + +
+ )} +
+ +
+ +
+
+ ); +} + +const menuBtn: React.CSSProperties = { + background: "#555", + border: "none", + color: "white", + padding: "0.5rem 1rem", + textAlign: "left", + cursor: "pointer", + borderRadius: "4px" +}; diff --git a/frontend/src/pages/AdoptionPage.tsx b/frontend/src/pages/AdoptionPage.tsx index 5df4ade..1673da1 100644 --- a/frontend/src/pages/AdoptionPage.tsx +++ b/frontend/src/pages/AdoptionPage.tsx @@ -1,5 +1,5 @@ -import { useState, CSSProperties } from "react"; -import { useParams } from "react-router-dom"; +import { useState, CSSProperties, useEffect } from "react"; +import { useNavigate, useParams } 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"; @@ -8,12 +8,66 @@ import menuLogo from "../assets/menu_logo.png"; export default function AdoptionForm() { const { name } = useParams<{ name: string }>(); + const navigate = useNavigate(); + const [responses, setResponses] = useState({ care: "", reason: "", home: "", }); + const [petId, setPetId] = useState(null); + + useEffect(() => { + fetch("http://localhost:5000/api/pets") + .then(res => res.json()) + .then(data => { + const match = data.find((p: any) => p.name.toLowerCase() === name?.toLowerCase()); + if (match) setPetId(match.id); + }); + }, [name]); + + const handleSubmit = () => { + if (!petId) { + alert("Could not find pet ID."); + return; + } + + const applicantName = localStorage.getItem("applicantName") || "Anonymous"; + const applicantLocation = localStorage.getItem("applicantLocation") || "Unknown"; + const applicantEmail = localStorage.getItem("applicantEmail") || "N/A"; + const applicantUsername = localStorage.getItem("username") || "N/A"; + const applicantPassword = localStorage.getItem("password") || "N/A"; + + fetch(`http://localhost:5000/api/pets/${petId}/applicants`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: applicantName, + email: applicantEmail, + location: applicantLocation, + username: applicantUsername, + password: applicantPassword, + reason: responses.reason, + care: responses.care, + home: responses.home, + }), + }) + + .then(res => { + if (res.ok) { + alert("Application submitted!"); + navigate("/"); + } else { + alert("Failed to submit."); + } + }) + .catch(err => { + console.error("Submit error:", err); + alert("Error submitting form."); + }); + }; + return (
@@ -34,7 +88,7 @@ export default function AdoptionForm() { />