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 (
+
+ {currentPageUsers.map((user) => (
+ -
+ {user.name}
+
+
{" "}
+
+ ))}
+
+ );
+ };
+
+ 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 (
-
- )
- }
+ console.log("isAuthenticated:", isAuthenticated);
+ useEffect(() => {
+ if (!isAuthenticated && !loading) {
+ navigate("/login"); // Redirect to login if not authenticated
+ } else {
+ fetchProfile();
}
+ }, [isAuthenticated, navigate, loading]);
- const handleBioEditing = (event) => {
- setEditingBio(true);
- }
+ // useEffect(() => {
+ // console.log("Fetching profile");
+ // fetchProfile();
+ // }, []);
+
+ console.log("own data: ", ownData);
+
+ const updateBio = (event) => {
+ setBio(event.target.value);
+ };
- const handleBioSaving = (event) => {
- setEditingBio(false);
- //we would send the new bio data to the server here
+ const handleBio = () => {
+ console.log("Handling bio");
+
+ if (editingBio === false)
+ return
{ownData.bio}
;
+ else {
+ return (
+
+ );
}
+ };
+
+ const RenderProfilePic = () => {
+ console.log("rendering profile pic");
+ return (
+

+ );
+ /* if (ownData.profile_pic === null) {
+ return (
+

+ );
+ } else {
+ return (
+

+ );
+ } */
+ };
+
+ const handleBioEditing = (event) => {
+ setEditingBio(true);
+ };
+
+ const handleBioSaving = (event) => {
+ setEditingBio(false);
+ const newBio = { bio: bio };
+ fetch("api/profile/me/update/bio", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(newBio),
+ })
+ .then(fetchProfile())
+ .catch((error) => alert("Error updating bio"));
+ };
+
+ const handleNewProfilePic = (event) => {
+ fetch;
+ };
+
+ return (
+
+
+
+

+
+
+
+ {ownData.name}
+
+
+
+
+ {handleBio()}
+
+
+
+
+
+
+
+
+
+
+
+
+ Pokemon that I like:
+
+
+
+
+ );
+};
- return (
-
-
-
-

-
-

-
- {testData.username}
-
-
-
{handleBio()}
-
-
-
-
-
-
-
-
-
-
- Pokemon that I like:
-
-
-
- )
-}
-
-export default Profile;
\ No newline at end of file
+export default Profile;