diff --git a/backend/controllers/profileController.js b/backend/controllers/profileController.js index 0ccc4f5..a70dc24 100644 --- a/backend/controllers/profileController.js +++ b/backend/controllers/profileController.js @@ -38,7 +38,8 @@ const updateProfile = async (req, res, next) => { const updateProfilePic = async (req, res, next) => { try { const { id } = req.session.user; - const { profile_pic } = req.body; + // const { profile_pic } = req.body; + const profile_pic = req.file.buffer; const profile = await profileService.updateProfilePic({ id, profile_pic, diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js index d9c6a5a..5ac8360 100644 --- a/backend/controllers/userController.js +++ b/backend/controllers/userController.js @@ -1,6 +1,6 @@ const userService = require('../services/userService'); -const getLikedPokemonsByUserId = async (req, res, next) => { +const getLikedPokemonsByUser = async (req, res, next) => { try { const { id } = req.session.user; const pokemons = await userService.getLikedPokemonsByUserId(id); @@ -10,7 +10,7 @@ const getLikedPokemonsByUserId = async (req, res, next) => { } } -const getDislikedPokemonsByUserId = async (req, res, next) => { +const getDislikedPokemonsByUser = async (req, res, next) => { try { const { id } = req.session.user; const pokemons = await userService.getDislikedPokemonsByUserId(id); @@ -20,6 +20,26 @@ const getDislikedPokemonsByUserId = async (req, res, next) => { } } +const getLikedPokemonsByUserId = async (req, res, next) => { + try { + const { user_id } = req.params + const pokemons = await userService.getLikedPokemonsByUserId(user_id); + res.status(200).json(pokemons); + } catch (error) { + next(error); + } +} + +const getDislikedPokemonsByUserId = async (req, res, next) => { + try { + const { user_id } = req.params; + const pokemons = await userService.getDislikedPokemonsByUserId(user_id); + res.status(200).json(pokemons); + } catch (error) { + next(error); + } +} + const searchUsers = async (req, res, next) => { try { const { username } = req.query; @@ -31,6 +51,8 @@ const searchUsers = async (req, res, next) => { } module.exports = { + getLikedPokemonsByUser, + getDislikedPokemonsByUser, getLikedPokemonsByUserId, getDislikedPokemonsByUserId, searchUsers, diff --git a/backend/database/init.sql b/backend/database/init.sql index 20765cb..3f2da41 100644 --- a/backend/database/init.sql +++ b/backend/database/init.sql @@ -4,7 +4,7 @@ CREATE TYPE relationship_type AS ENUM ('like', 'dislike'); CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(100) UNIQUE NOT NULL, - email VARCHAR(100) UNIQUE NOT NULL, + email VARCHAR(100) UNIQUE, password VARCHAR(100) NOT NULL, -- Encrypted password role VARCHAR(20) DEFAULT 'user', -- 'guest', 'user', 'admin' created_at TIMESTAMP DEFAULT NOW(), diff --git a/backend/models/pokemonModel.js b/backend/models/pokemonModel.js index 72b3ca6..88c8803 100644 --- a/backend/models/pokemonModel.js +++ b/backend/models/pokemonModel.js @@ -18,13 +18,13 @@ const insertLikedPokemon = async (user_id, pokemon_id) => { INSERT INTO user_pokemons (pokemon_id, user_id, relationship) VALUES ($1, $2, 'like') ON CONFLICT (pokemon_id, user_id) - DO UPDATE SET - relationship = CASE - WHEN user_pokemons.relationship = 'dislike' THEN 'like' - ELSE user_pokemons.relationship + DO UPDATE SET + relationship = CASE + WHEN user_pokemons.relationship = 'dislike' THEN 'like' + ELSE user_pokemons.relationship END RETURNING user_id, pokemon_id`; - + const values = [pokemon_id, user_id]; const result = await db.query(text, values); return result; @@ -36,10 +36,10 @@ const insertDislikedPokemon = async (user_id, pokemon_id) => { INSERT INTO user_pokemons (pokemon_id, user_id, relationship) VALUES ($1, $2, 'dislike') ON CONFLICT (pokemon_id, user_id) - DO UPDATE SET - relationship = CASE - WHEN user_pokemons.relationship = 'like' THEN 'dislike' - ELSE user_pokemons.relationship + DO UPDATE SET + relationship = CASE + WHEN user_pokemons.relationship = 'like' THEN 'dislike' + ELSE user_pokemons.relationship END RETURNING user_id, pokemon_id`; const values = [pokemon_id, user_id]; @@ -50,9 +50,6 @@ const insertDislikedPokemon = async (user_id, pokemon_id) => { const likedPokemon = async (user_id, pokemon_id, name) => { await insertPokemon(pokemon_id, name); const result = await insertLikedPokemon(user_id, pokemon_id); -/* if (result.rows.length === 0) { - throw new ValidationError("Pokemon already liked"); - } */ return result.rows[0]; }; @@ -61,18 +58,12 @@ const unlikePokemon = async (user_id, pokemon_id) => { "DELETE FROM user_pokemons WHERE user_id = $1 AND pokemon_id = $2 AND relationship = 'like' RETURNING user_id, pokemon_id"; const values = [user_id, pokemon_id]; const result = await db.query(text, values); -/* if (result.rows.length === 0) { - throw new ValidationError("Pokemon already unliked"); - } */ return result.rows[0]; }; const dislikedPokemon = async (user_id, pokemon_id, name) => { await insertPokemon(pokemon_id, name); const result = await insertDislikedPokemon(user_id, pokemon_id); -/* if (result.rows.length === 0) { - throw new ValidationError("Pokemon already disliked"); - } */ return result.rows[0]; }; @@ -81,9 +72,6 @@ const undislikePokemon = async (user_id, pokemon_id) => { "DELETE FROM user_pokemons WHERE user_id = $1 AND pokemon_id = $2 AND relationship = 'dislike' RETURNING user_id, pokemon_id"; const values = [user_id, pokemon_id]; const result = await db.query(text, values); -/* if (result.rows.length === 0) { - throw new ValidationError("Pokemon already undisliked"); - } */ return result.rows[0]; }; diff --git a/backend/models/profileModel.js b/backend/models/profileModel.js index 23ec9c9..b37dca3 100644 --- a/backend/models/profileModel.js +++ b/backend/models/profileModel.js @@ -23,7 +23,7 @@ const updateProfile = async (id, name, bio, profile_pic) => { const updateProfilePic = async (id, profile_pic) => { const result = await db.query( - "UPDATE profiles SET profile_pic = $1, updated_at = NOW() WHERE _user_id = $2 RETURNING *", + "UPDATE profiles SET profile_pic = $1, updated_at = NOW() WHERE user_id = $2 RETURNING *", [profile_pic, id] ); if (result.rows.length === 0) { diff --git a/backend/models/userModel.js b/backend/models/userModel.js index 36ab026..956be83 100644 --- a/backend/models/userModel.js +++ b/backend/models/userModel.js @@ -44,7 +44,7 @@ const getDislikedPokemonsByUserId = async (userId) => { if (result.rows.length === 0) { throw new NotFoundError("User not found"); } - return result.rows; // Return the array of disliked Pokémon + return result.rows[0]; // Return the array of disliked Pokémon } const searchUsers = async (query) => { diff --git a/backend/routes/profileRoutes.js b/backend/routes/profileRoutes.js index 24af092..2da13c2 100644 --- a/backend/routes/profileRoutes.js +++ b/backend/routes/profileRoutes.js @@ -14,7 +14,7 @@ router.get("/:id", isAuthenticated, profileController.getProfileById); profileController.createProfile ); */ router.put("/me/update", isAuthenticated, profileController.updateProfile); -router.patch( +router.post( "/me/update/profile_pic", upload.single("profile_pic"), profileController.updateProfilePic diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js index b357b1e..d74befc 100644 --- a/backend/routes/userRoutes.js +++ b/backend/routes/userRoutes.js @@ -13,6 +13,9 @@ router.get( isAuthenticated, userController.getDislikedPokemonsByUserId ); + +router.get("/:user_id/liked_pokemons", isAuthenticated, userController.getLikedPokemonsByUserId); +router.get("/:user_id/disliked_pokemons", isAuthenticated, userController.getDislikedPokemonsByUserId); router.get("/search", isAuthenticated, userController.searchUsers); module.exports = router; diff --git a/backend/services/profileService.js b/backend/services/profileService.js index 4a00f00..6a32a94 100644 --- a/backend/services/profileService.js +++ b/backend/services/profileService.js @@ -1,9 +1,9 @@ const userModel = require("../models/profileModel"); -const fs = require('fs'); +const fs = require("fs"); // Read the default profile picture file -const defaultProfilePic = fs.readFileSync('./database/profile_pic.png'); +const defaultProfilePic = fs.readFileSync("./database/profile_pic.png"); // Convert it to a binary format const defaultProfilePicBinary = Buffer.from(defaultProfilePic); @@ -12,8 +12,15 @@ const createProfile = async ({ user_id, name, bio }) => { try { console.log("creating profile"); console.log(user_id, name, bio, defaultProfilePicBinary); - const profile = await userModel.createProfile(user_id, name, bio, defaultProfilePicBinary); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; + const profile = await userModel.createProfile( + user_id, + name, + bio, + defaultProfilePicBinary + ); + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; profile.profile_pic = base64Image; console.log("creating profile successful:", profile); return profile; @@ -28,11 +35,12 @@ const updateProfile = async ({ id, name, bio, profile_pic }) => { console.log("updating profile"); console.log(id, name, bio, profile_pic); const profile = await userModel.updateProfile(id, name, bio, profile_pic); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; profile.profile_pic = base64Image; console.log("updating profile successful:", profile); return profile; - } catch (error) { console.log("error updating profile:", error.message); throw error; @@ -44,7 +52,9 @@ const updateProfilePic = async ({ id, profile_pic }) => { console.log("updating profile pic"); console.log(id, profile_pic); const profile = await userModel.updateProfilePic(id, profile_pic); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; profile.profile_pic = base64Image; console.log("updating profile pic successful:", profile); return profile; @@ -85,7 +95,9 @@ const getProfileById = async (id) => { console.log("fetching profile"); console.log(id); const profile = await userModel.getProfileById(id); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; profile.profile_pic = base64Image; console.log("fetching profile successful:", profile); return profile; @@ -99,8 +111,12 @@ const getProfiles = async () => { try { console.log("fetching profiles"); const profiles = await userModel.getProfiles(); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; - profile.profile_pic = base64Image; + profiles.map((profile) => { + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; + profile.profile_pic = base64Image; + }); console.log("fetching profiles successful:", profiles); return profiles; } catch (error) { @@ -113,16 +129,18 @@ const searchProfiles = async (name) => { try { console.log("searching profiles"); console.log(name); - const profiles = await userModel.searchProfiles(name); - const base64Image = profile.profile_pic ? profile.profile_pic.toString('base64') : null; + const profile = await userModel.searchProfiles(name); + const base64Image = profile.profile_pic + ? profile.profile_pic.toString("base64") + : null; profile.profile_pic = base64Image; - console.log("searching profiles successful:", profiles); + console.log("searching profiles successful:", profile); return profiles; } catch (error) { console.log("error searching profiles:", error.message); throw error; } -} +}; module.exports = { createProfile, diff --git a/frontend/pokedex_app/package-lock.json b/frontend/pokedex_app/package-lock.json index e6fa8ec..6e058a6 100644 --- a/frontend/pokedex_app/package-lock.json +++ b/frontend/pokedex_app/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.0", "dependencies": { "@heroicons/react": "^2.1.5", + "holy-loader": "^2.3.8", + "lucide-react": "^0.453.0", "prettier": "^3.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -3178,6 +3180,15 @@ "node": ">= 0.4" } }, + "node_modules/holy-loader": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/holy-loader/-/holy-loader-2.3.8.tgz", + "integrity": "sha512-EhdAbXSuZtEc5z0Ta1PAgJXr0t/9lfOWt8tl4fjObGl7ObYLESL4218VHR/zPcE+ibyALnUf9JgAIeEDyqKCDw==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3843,6 +3854,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.453.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.453.0.tgz", + "integrity": "sha512-kL+RGZCcJi9BvJtzg2kshO192Ddy9hv3ij+cPrVPWSRzgCWCVazoQJxOjAwgK53NomL07HB7GPHW120FimjNhQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/frontend/pokedex_app/package.json b/frontend/pokedex_app/package.json index 3746f2b..3495078 100644 --- a/frontend/pokedex_app/package.json +++ b/frontend/pokedex_app/package.json @@ -12,6 +12,8 @@ "proxy": "http://localhost:3000", "dependencies": { "@heroicons/react": "^2.1.5", + "holy-loader": "^2.3.8", + "lucide-react": "^0.453.0", "prettier": "^3.3.3", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/pokedex_app/src/App.jsx b/frontend/pokedex_app/src/App.jsx index e904171..3eb63a2 100644 --- a/frontend/pokedex_app/src/App.jsx +++ b/frontend/pokedex_app/src/App.jsx @@ -12,11 +12,12 @@ import PrivacyPolicy from "./pages/PrivacyPolicy"; import TermsOfService from "./pages/TermsOfService"; import ErrorBoundary from "./ErrorBoundary"; import { AuthProvider } from "./AuthContext"; // Import AuthProvider +import Community from "./components/Community"; function App() { return ( - - + +
@@ -35,12 +36,13 @@ function App() { } /> } /> } /> + } />
-
-
+ + ); } diff --git a/frontend/pokedex_app/src/AuthContext.jsx b/frontend/pokedex_app/src/AuthContext.jsx index 8c13b24..818794a 100644 --- a/frontend/pokedex_app/src/AuthContext.jsx +++ b/frontend/pokedex_app/src/AuthContext.jsx @@ -1,4 +1,6 @@ import { createContext, useState, useEffect } from "react"; +import { useNavigate } from 'react-router-dom'; +import HolyLoader from "holy-loader"; // Create Context const AuthContext = createContext(); @@ -8,6 +10,7 @@ export const AuthProvider = ({ children }) => { const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); // To hold user details const [loading, setLoading] = useState(true); // New loading state + const navigate = useNavigate(); useEffect(() => { const checkLoginStatus = async () => { @@ -37,15 +40,19 @@ export const AuthProvider = ({ children }) => { }, []); const logout = async () => { + setLoading(true); await fetch("/api/logout", { method: "POST", credentials: "include" }); setIsAuthenticated(false); setUser(null); + setLoading(false); + navigate("/login"); }; return ( + {loading && } {/* Render the loader when loading */} {children} ); diff --git a/frontend/pokedex_app/src/assets/community_placeholder.json b/frontend/pokedex_app/src/assets/community_placeholder.json new file mode 100644 index 0000000..79478e4 --- /dev/null +++ b/frontend/pokedex_app/src/assets/community_placeholder.json @@ -0,0 +1,102 @@ +{ + "users": [ + { + "username": "Jane Doe", + "bio": "Hi! My name is Jane! I was created only for testing purpoposes", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "ivysaur", "raticate", "flareon", "eevee", "cubone", "seel", "krabby", "kingler", "mankey", "rhydon", "staryu" + ] + }, + { + + "username": "Ash Ketchum", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "Brock", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "Jessie", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "James", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + "username": "Jane Doe 2", + "bio": "Hi! My name is Jane! I was created only for testing purpoposes", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "ivysaur", "raticate", "flareon", "eevee", "cubone", "seel", "krabby", "kingler", "mankey", "rhydon", "staryu" + ] + }, + { + + "username": "Ash Ketchum 2", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "Brock 2", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "Jessie 2", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + }, + { + + "username": "James 2", + "bio": "Hi! My name is Ash! Gotta catch'em all!", + "profile_pic": "https://www.shutterstock.com/shutterstock/photos/2500022355/display_1500/stock-photo-young-latin-woman-in-casual-clothing-in-the-street-looking-at-camera-during-early-morning-2500022355.jpg", + "created_at": "1999-01-08 04:05:06", + "liked_pokemons": [ + "pikachu" + ] + } + ] +} \ No newline at end of file diff --git a/frontend/pokedex_app/src/assets/no_profile_pic.jpg b/frontend/pokedex_app/src/assets/no_profile_pic.jpg new file mode 100644 index 0000000..7135a33 Binary files /dev/null and b/frontend/pokedex_app/src/assets/no_profile_pic.jpg differ diff --git a/frontend/pokedex_app/src/components/Community.jsx b/frontend/pokedex_app/src/components/Community.jsx new file mode 100644 index 0000000..62281f7 --- /dev/null +++ b/frontend/pokedex_app/src/components/Community.jsx @@ -0,0 +1,130 @@ +import React from "react"; +import testProfiles from "../assets/community_placeholder.json"; +import { useState, useEffect } from "react"; + +const Community = () => { + const [userList, setUserList] = useState([]); + const [loading, setLoading] = useState(false); + const [usersPerPage, setUsersPerPage] = useState(8); + const [searchQuery, setSearchQuery] = useState(""); + + let totalPages; + + if (userList.length <= usersPerPage) { + totalPages = 1; + } else { + totalPages = Math.floor(userList.length / usersPerPage); + } + + const [currentPage, setCurrentPage] = useState(totalPages); + + useEffect(() => { + const fetchUsers = async () => { + setLoading(true); + fetch("/api/profile/") + .then((response) => response.json()) + .then((data) => setUserList(data)) + .then(setLoading(false)) + .catch((error) => alert("Error fetching user list")); + }; + fetchUsers(); + }, []); + + const UsersOverview = ({ loading, users, usersPerPage, currentPage }) => { + let shownUsersStart; + + if (currentPage == 1) { + shownUsersStart = 0; + } else { + shownUsersStart = (currentPage - 1) * usersPerPage; + } + + let shownUsersEnd = currentPage * usersPerPage; + + console.log("Users: ", users); + + const currentPageUsers = users.slice(shownUsersStart, shownUsersEnd); + + if (loading === true) { + return

Loading...

; + } + return ( + + ); + }; + + const handleSearch = () => {}; + + const SearchUser = () => { + return ( +
+

Search users by name:

+ +
+ ); + }; + + const Pagination = ({ usersPerPage, length, currentPage }) => { + const paginationNumbers = []; + + for (let i = 1; i <= Math.ceil(length / usersPerPage); i++) { + paginationNumbers.push(i); + } + + const handlePagination = ({ pageNumber }) => { + setCurrentPage(pageNumber); + }; + + return ( +
+ {paginationNumbers.map((pageNumber) => ( + + ))} +
+ ); + }; + + return ( +
+
+

+ Our community +

+ +
+ +
+ +
+
+ ); +}; + +export default Community; diff --git a/frontend/pokedex_app/src/components/Navbar/Navbar.jsx b/frontend/pokedex_app/src/components/Navbar/Navbar.jsx index 731e0d0..0edd3cb 100644 --- a/frontend/pokedex_app/src/components/Navbar/Navbar.jsx +++ b/frontend/pokedex_app/src/components/Navbar/Navbar.jsx @@ -24,6 +24,9 @@ const Navbar = () => {
My Profile Pokedex + + Community + {!isAuthenticated && ( diff --git a/frontend/pokedex_app/src/components/RegistrationForm.jsx b/frontend/pokedex_app/src/components/RegistrationForm.jsx index 3cc5891..09b1874 100644 --- a/frontend/pokedex_app/src/components/RegistrationForm.jsx +++ b/frontend/pokedex_app/src/components/RegistrationForm.jsx @@ -16,7 +16,6 @@ const RegistrationForm = (props) => { const handleUsername = (event) => { setUsername(event.target.value); }; - const attemptRegistration = (event) => { event.preventDefault(); const newUser = { diff --git a/frontend/pokedex_app/src/pages/pokedex.jsx b/frontend/pokedex_app/src/pages/pokedex.jsx index 06a89f0..fe85a89 100644 --- a/frontend/pokedex_app/src/pages/pokedex.jsx +++ b/frontend/pokedex_app/src/pages/pokedex.jsx @@ -1,6 +1,7 @@ import { useState, useEffect, useRef } from "react"; import logo from '../../img/logo.png'; import './pokedex.css'; +import { RefreshCw } from 'lucide-react'; const Pokedex = () => { const [pokemonList, setPokemonList] = useState([]); @@ -188,21 +189,24 @@ const Sort = ({ sortOrder, onSort }) => ( - +
); // Shuffle button const Shuffle = ({ isFetching, onShuffle }) => ( +
+
); // Pokémon List component (non-search) diff --git a/frontend/pokedex_app/src/pages/pokemon_profile.jsx b/frontend/pokedex_app/src/pages/pokemon_profile.jsx index 3ff79c6..029582f 100644 --- a/frontend/pokedex_app/src/pages/pokemon_profile.jsx +++ b/frontend/pokedex_app/src/pages/pokemon_profile.jsx @@ -8,6 +8,8 @@ const PokemonProfile = () => { const [pokemonInfo, setPokemonInfo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [like, setLike] = useState(false); + const [dislike, setDislike] = useState(false); useEffect(() => { const fetchPokemonInfo = async () => { @@ -31,6 +33,92 @@ const PokemonProfile = () => { if (loading) return
Loading...
; if (error) return
{error}
; + const handleLike = async () => { + try { + if (like) { + // If already liked, unlike the Pokémon + const unlikeResponse = await fetch(`/api/pokemon/unlike/${pokemonInfo.id}`, { + method: 'DELETE', + }); + if (!unlikeResponse.ok) { + throw new Error('Failed to unlike Pokémon'); + } + setLike(false); // Update state to reflect unliking + } else { + setLike(true); + setDislike(false); + const response = await fetch(`/api/pokemon/liked`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // Define the content type + }, + body: JSON.stringify({ + pokemon_id: pokemonInfo.id, // Send the appropriate data + pokemon_name: pokemonInfo.name, + }), + }); + if (!response.ok) { + throw new Error('Failed to like Pokémon'); + } + + if (dislike) { + const undislikeResponse = await fetch(`/api/pokemon/undislike/${pokemonInfo.id}`, { + method: 'DELETE', + }); + if (!undislikeResponse.ok) { + throw new Error('Failed to undislike Pokémon'); + } + setDislike(false); + } + } + } catch(error){ + console.error('Error in liking pokemons:', error); + } + } + + const handleDislike = async () => { + try { + if (dislike) { + // If already disliked, undis like the Pokémon + const undislikeResponse = await fetch(`/api/pokemon/undislike/${pokemonInfo.id}`, { + method: 'DELETE', + }); + if (!undislikeResponse.ok) { + throw new Error('Failed to undislike Pokémon'); + } + setDislike(false); // Update state to reflect unliking + } else { + setLike(false); + setDislike(true); + const response = await fetch(`/api/pokemon/disliked`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // Define the content type + }, + body: JSON.stringify({ + pokemon_id: pokemonInfo.id, // Send the appropriate data + pokemon_name: pokemonInfo.name, + }), + }); + if (!response.ok) { + throw new Error('Failed to dislike Pokémon'); + } + + if (like) { + const undislikeResponse = await fetch(`/api/pokemon/unlike/${pokemonInfo.id}`, { + method: 'DELETE', + }); + if (!undislikeResponse.ok) { + throw new Error('Failed to unlike Pokémon'); + } + setLike(false); + } + } + }catch(error){ + console.error('Error in disliking pokemons:', error); + } + } + return (

{pokemonInfo.name }

@@ -42,19 +130,19 @@ const PokemonProfile = () => { />
- {/* Height */} -

Height:

-
- {pokemonInfo.height * 10} - cm -
- - {/* Weight */} -

Weight:

-
- {pokemonInfo.weight * 0.1} - kg -
+ {/* Height */} +

Height:

+
+ {(pokemonInfo.height * 10).toFixed(2)} + cm +
+ + {/* Weight */} +

Weight:

+
+ {(pokemonInfo.weight * 0.1).toFixed(2)} + kg +
{/* Type */}

Type:

@@ -83,16 +171,23 @@ const PokemonProfile = () => { {/* Like and Dislike Buttons */}
- {/* Like Button */} - - - {/* Dislike Button */} - + {/* Like Button */} + + + {/* Dislike Button */} +
+
/* Add more fields as needed */ ); diff --git a/frontend/pokedex_app/src/pages/profile.jsx b/frontend/pokedex_app/src/pages/profile.jsx index 396a148..9cb8bc5 100644 --- a/frontend/pokedex_app/src/pages/profile.jsx +++ b/frontend/pokedex_app/src/pages/profile.jsx @@ -1,85 +1,223 @@ import React from "react"; -import testData from '../assets/profile_placeholder.json'; -import background from "../assets/profile_bg.png" -import UserLikedPokemon from "../components/UserLikedPokemon" -import { useState, useContext, useEffect } from "react"; +import testData from "../assets/profile_placeholder.json"; +import background from "../assets/profile_bg.png"; +import UserLikedPokemon from "../components/UserLikedPokemon"; +import { useState, useContext, useEffect, useRef } from "react"; +import defaultProfilePic from "../assets/no_profile_pic.jpg"; -import { useNavigate } from 'react-router-dom'; -import AuthContext from '../AuthContext'; +import { useNavigate } from "react-router-dom"; +import AuthContext from "../AuthContext"; const Profile = () => { + const [editingBio, setEditingBio] = useState(false); + const [bio, setBio] = useState("Loading..."); + const [ownData, setOwnData] = useState({ + id: 1, + name: "Loading...", + bio: "loading...", + profile_pic: null, + }); - const [editingBio, setEditingBio] = useState(false); - const [bio, setBio] = useState(testData.bio) + const { isAuthenticated, user, loading } = useContext(AuthContext); + const navigate = useNavigate(); + const fileInputRef = useRef(); - const { isAuthenticated , user, loading} = useContext(AuthContext); - const navigate = useNavigate(); + const handleSubmit = async (event) => { + event.preventDefault(); // Prevent the default form submission - console.log("isAuthenticated:", isAuthenticated); - useEffect(() => { - if (!isAuthenticated && !loading) { - navigate('/login'); // Redirect to login if not authenticated + const formData = new FormData(); + const file = fileInputRef.current.files[0]; // Get the file from the input + if (file) { + formData.append("profile_pic", file); // Append the file to FormData + console.log("form data: ", formData); + + try { + const response = await fetch("/api/profile/me/update/profile_pic", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); } - }, [isAuthenticated, navigate]); - const updateBio = (event) => { - setBio(event.target.value) + // Handle success, e.g., update the UI or show a message + alert("Profile picture uploaded successfully!"); + } catch (error) { + console.error("Error uploading profile picture:", error); + alert("Error uploading profile picture"); + } } + }; - const handleBio = () => { + const fetchProfile = () => { + console.log("Fetching profile"); + fetch("/api/profile/me") + .then((response) => response.json()) + .then((data) => { + console.log("data: ", data); + setOwnData(data); + }) + .catch((error) => alert("Error fetching user list")); + }; - console.log("Handling bio") - - if (editingBio === false) - return (

{bio}

) - else - { - return ( -
-