Skip to content

setting up develop branch with new pages and features #30

Merged
merged 1 commit into from
May 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading