Skip to content

Commit

Permalink
setting up develop branch with new pages and features
Browse files Browse the repository at this point in the history
  • Loading branch information
prince rusweka committed May 5, 2025
1 parent d521555 commit fef9b38
Show file tree
Hide file tree
Showing 18 changed files with 878 additions and 119 deletions.
Binary file modified backend/__pycache__/utils.cpython-311.pyc
Binary file not shown.
103 changes: 102 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -583,6 +583,107 @@ def before_first_request():
"""Run once before the first request to initialize the database."""
init_db()

@app.route("/api/pets/<int:pet_id>/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/<int:pet_id>/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/<int:applicant_id>", 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/<int:applicant_id>/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)

Expand Down
43 changes: 26 additions & 17 deletions backend/utils.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -21,26 +15,41 @@ 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,
password TEXT NOT NULL,
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)
)
""")

Expand Down
24 changes: 24 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ 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 }) {
const isLoggedIn = localStorage.getItem("loggedIn") === "true";
return isLoggedIn ? children : <Navigate to="/profile" />;
}

function AdminRoute({ children }: { children: JSX.Element }) {
const isAdmin = localStorage.getItem("loggedIn") === "true" &&
localStorage.getItem("role") === "admin";
return isAdmin ? children : <Navigate to="/profile" />;
}

export default function App() {
const location = useLocation();
const hideNav = location.pathname === "/profile" || location.pathname === "/more-info";
Expand All @@ -32,6 +44,18 @@ export default function App() {
<Route path="/search" element={<ProtectedRoute><Search /></ProtectedRoute>} />
<Route path="/adopt/:name" element={<ProtectedRoute><AdoptionPage /></ProtectedRoute>} />
<Route path="/my-list" element={<ProtectedRoute><MyList /></ProtectedRoute>} />

{/* Admin-only routes */}
<Route path="/pao" element={<AdminRoute><PAOSystem /></AdminRoute>} />
<Route path="/pao/listings" element={<AdminRoute><PaoListingsPanel /></AdminRoute>} />
<Route path="/pao/pets/:id/edit" element={<AdminRoute><PetEditPage /></AdminRoute>} />
<Route path="/pao/pets/new" element={<AdminRoute><PetCreatePage /></AdminRoute>} />
<Route path="/pao/applicants" element={<AdminRoute><AllApplicantsPage /></AdminRoute>} />
<Route path="/pao/applicants/:id" element={
<AdminRoute>
<ApplicantDetailPage />
</AdminRoute>
} />
</Routes>
</>
);
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ 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);
}, []);

const handleLogout = () => {
localStorage.removeItem("loggedIn");
localStorage.removeItem("role");
setLoggedIn(false);
setIsAdmin(false);
navigate("/profile");
};

Expand All @@ -35,6 +39,12 @@ export default function NavBar() {
<Link to="/search" style={{ color: "white" }}><FaSearch /></Link>
<Link to="/pets" style={{ color: "white" }}>Pets</Link>
<Link to="/my-list" style={{ color: "white" }}>My List</Link>

{isAdmin && (
<Link to="/pao" style={{ color: "white" }}>
PAO System
</Link>
)}
</>
)}

Expand Down
43 changes: 43 additions & 0 deletions frontend/src/components/PAOLayout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div style={{ display: "flex" }}>
<div style={{ width: menuOpen ? "200px" : "50px", transition: "width 0.3s", background: "#333", color: "#fff", height: "100vh", padding: "1rem" }}>
<div
style={{ cursor: "pointer", fontSize: "1.5rem", marginBottom: "1rem" }}
onClick={() => setMenuOpen(!menuOpen)}
>
</div>
{menuOpen && (
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
<button onClick={() => navigate("/pao/dashboard")} style={menuBtn}>User Dashboard</button>
<button onClick={() => navigate("/pao/user/1")} style={menuBtn}>User Profile Page</button>
<button onClick={() => navigate("/pao/pets/new")} style={menuBtn}>New Pet Listing</button>
<button onClick={() => navigate("/pao/pets/1/edit")} style={menuBtn}>Edit Pet Listing</button>
<button onClick={() => navigate("/pao/listings")} style={menuBtn}>PAO Admin Panel</button>
</div>
)}
</div>

<div style={{ flexGrow: 1, padding: "2rem" }}>
<Outlet />
</div>
</div>
);
}

const menuBtn: React.CSSProperties = {
background: "#555",
border: "none",
color: "white",
padding: "0.5rem 1rem",
textAlign: "left",
cursor: "pointer",
borderRadius: "4px"
};
Loading

0 comments on commit fef9b38

Please sign in to comment.