Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propositions #144

Merged
merged 11 commits into from
Feb 28, 2024
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
38 changes: 37 additions & 1 deletion backend/src/routes/usersFriendsRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Router, Request, Response } from "express";
import { Session } from "neo4j-driver";
import driver from "../driver/driver";
import User from "../models/User";
import { OkErrorResponse, FriendsErrorResponse } from "../types/userResponse";
import {
OkErrorResponse,
FriendsErrorResponse,
UsersErrorResponse,
} from "../types/userResponse";

const friendshipRouter = Router();

Expand Down Expand Up @@ -85,6 +89,38 @@ friendshipRouter.get(
},
);

friendshipRouter.get(
"/:userId/friend-suggestions",
async (req: Request, res: UsersErrorResponse) => {
try {
const session: Session = driver.session();
const userId: string = req.params.userId;

const user = await userExists(session, res, userId);
if ("json" in user) {
await session.close();
return res;
}

const friendSuggestionsQuery = await session.run(
`MATCH (u:User {id: $userId})-[:IS_FRIENDS_WITH]->(friend:User)-[:IS_FRIENDS_WITH]->(suggested:User)
WHERE NOT (u)-[:IS_FRIENDS_WITH]->(suggested) AND suggested.id <> $userId
RETURN DISTINCT suggested`,
{ userId },
);
await session.close();

const users: User[] = friendSuggestionsQuery.records
.map((record) => record.get("suggested").properties)
.slice(0, 15);
return res.json({ status: "ok", users });
} catch (err) {
console.log("Error:", err);
return res.status(404).json({ status: "error", errors: err as object });
}
},
);

friendshipRouter.delete(
"/:userId1/remove/:userId2",
async (req: Request, res: OkErrorResponse) => {
Expand Down
77 changes: 77 additions & 0 deletions frontend/src/components/Friend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
faUserMinus,
faVideo,
faCommentAlt,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import User from "../models/User";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import Modal from "./Modal";

export interface FriendProps {
friend: User;
joinMeeting: (friendId: string) => Promise<string | void>;
handleDeclineRequest: (friend: User) => Promise<void>;
}

function Friend(props: FriendProps) {
const navigate = useNavigate();

const [showDeleteModal, setShowDeleteModal] = useState(false);

const friend: User = props.friend;
const joinMeeting = props.joinMeeting;
const handleDeclineRequest = props.handleDeclineRequest;

return (
<li key={friend.id} className="flex flex-row mt-5">
<img
src={friend.profile_picture}
className="rounded-full w-28 h-28 border-my-orange border-2 object-cover"
/>
<div className=" ml-5 flex flex-col justify-evenly">
<p className="font-semibold text-2xl">
<span className="">
{" "}
{friend.first_name} {friend.last_name}{" "}
</span>
<button
className={` text-my-red text-sm my-2 p-2 rounded-md transition hover:scale-110 hover:bg-my-red hover:text-my-light active:translate-x-2`}
onClick={() => {
setShowDeleteModal(true);
}}
>
<FontAwesomeIcon icon={faUserMinus} />
</button>
</p>
<div className="flex flex-col xl:flex-row">
<button
className={`btn small bg-my-orange text-xs my-2`}
onClick={() => joinMeeting(friend.id)}
>
<FontAwesomeIcon icon={faVideo} />
</button>
<button
className={`btn small bg-my-purple text-xs my-2`}
onClick={() => navigate(`/messages/${friend.id}`)}
>
<FontAwesomeIcon icon={faCommentAlt} />
</button>
</div>
</div>
{showDeleteModal && (
<Modal
text={`Are you sure that you want remove ${friend.first_name} ${friend.last_name} from your friends ?`}
handleYes={() => {
handleDeclineRequest(friend);
setShowDeleteModal(false);
}}
handleNo={() => setShowDeleteModal(false)}
></Modal>
)}
</li>
);
}

export default Friend;
72 changes: 72 additions & 0 deletions frontend/src/components/Paginator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { faArrowRight, faArrowLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import User from "../models/User";

interface PaginatorProps {
users: User[];
itemsPerPage: number;
renderItem: (user: User) => React.ReactNode;
}

function Paginator(props: PaginatorProps) {
const [currentPageNum, setCurrentPageNum] = useState<number>(1);

const indexOfLastItem: number = currentPageNum * props.itemsPerPage;
const indexOfFirstItem: number = indexOfLastItem - props.itemsPerPage;

const totalPages: number = Math.ceil(props.users.length / props.itemsPerPage);
const currentItems: User[] = props.users.slice(
indexOfFirstItem,
indexOfLastItem,
);

const nextPage = () => {
if (currentPageNum + 1 <= totalPages) {
setCurrentPageNum(currentPageNum + 1);
}
};

const previousPage = () => {
if (currentPageNum - 1 > 0) {
setCurrentPageNum(currentPageNum - 1);
}
};

return (
<div id="paginator">
<ul>
{currentItems.map((user) => (
<li key={user.id}>{props.renderItem(user)}</li>
))}
</ul>

<div
id="pagin-buttons"
className=" bg-my-orange rounded-md flex flex-row justify-evenly text-lg p-2 align-middle"
>
<button
onClick={previousPage}
className=" rounded-lg bg-my-dark text-my-light p-2 transition duration-250 ease-in-out hover:bg-my-darker active:translate-y-1"
>
<FontAwesomeIcon icon={faArrowLeft} />
</button>

<div className=" flex flex-col justify-center">
<p>
Page {currentPageNum} of {totalPages}
</p>
</div>

<button
onClick={nextPage}
className=" rounded-lg bg-my-dark text-my-light p-2 transition duration-250 ease-in-out hover:bg-my-darker active:translate-y-1"
>
<FontAwesomeIcon icon={faArrowRight} />
</button>
</div>
</div>
);
}

export default Paginator;
113 changes: 56 additions & 57 deletions frontend/src/pages/FriendsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { useUser } from "../helpers/UserProvider";

import { faUserMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
import { faVideo } from "@fortawesome/free-solid-svg-icons";
import { faCommentAlt } from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

import Footer from "../components/Footer";
import FriendRequest from "../components/FriendRequest";
import Modal from "../components/Modal";
import Navbar from "../components/Navbar";
import User from "../models/User";
import setUserFriends from "../redux/actions/setUserFriends";
import dataService from "../services/data";
import Transition from "../components/Transition";
import FoundUser from "../components/FoundUser";
import Friend from "../components/Friend";
import Paginator from "../components/Paginator";

function FriendsPage() {
const navigate = useNavigate();
Expand All @@ -26,8 +26,7 @@ function FriendsPage() {
const [friendsRequests, setFriendsRequests] = useState([]);
const [refresh, setRefresh] = useState(false);

const [showDeleteModal, setShowDeleteModal] = useState(false);
const [friendToDelete, setFriendToDelete] = useState<User | null>(null);
const [friendSuggestions, setFriendSuggestions] = useState([]);

const [showAnimation, setShowAnim] = useState(false);
const [showContent, setShowContent] = useState(false);
Expand All @@ -43,6 +42,20 @@ function FriendsPage() {
}, 100);
}, []);

useEffect(() => {
const fetchFriendSuggestions = async () => {
if (user) {
const friendsRequestsResponse = await dataService.fetchData(
`/users/${user.id}/friend-suggestions`,
"GET",
{},
);
setFriendSuggestions(friendsRequestsResponse.users);
}
};
fetchFriendSuggestions();
}, []);

useEffect(() => {
const fetchFriendRequests = async () => {
if (user) {
Expand Down Expand Up @@ -129,60 +142,25 @@ function FriendsPage() {
<h1 className="text-3xl font-bold">Friends:</h1>
<hr className="text-my-orange"></hr>
<ul className="">
{friends.map((friend: User) => (
<li key={friend.id} className="flex flex-row mt-5">
<img
src={friend.profile_picture}
className="rounded-full w-28 h-28 border-my-orange border-2 object-cover"
/>
<div className=" ml-5 flex flex-col justify-evenly">
<p className="font-semibold text-2xl">
<span className="">
{" "}
{friend.first_name} {friend.last_name}{" "}
</span>
<button
className={` text-my-red text-sm my-2 p-2 rounded-md transition hover:scale-110 hover:bg-my-red hover:text-my-light active:translate-x-2`}
onClick={() => {
setShowDeleteModal(true);
setFriendToDelete(friend);
}}
>
<FontAwesomeIcon icon={faUserMinus} />
</button>
</p>
<div className="flex flex-col xl:flex-row">
<button
className={`btn small bg-my-orange text-xs my-2`}
onClick={() => joinMeeting(friend.id)}
>
<FontAwesomeIcon icon={faVideo} />
</button>
<button
className={`btn small bg-my-purple text-xs my-2`}
onClick={() => navigate(`/messages/${friend.id}`)}
>
<FontAwesomeIcon icon={faCommentAlt} />
</button>
</div>
</div>
</li>
))}
{friends && friends.length > 0 ? (
<Paginator
users={friends}
itemsPerPage={5}
renderItem={(user) => (
<Friend
friend={user}
handleDeclineRequest={handleDeclineRequest}
joinMeeting={joinMeeting}
/>
)}
/>
) : (
<p>You don't have any friends.</p>
)}
</ul>
</div>

{showDeleteModal && friendToDelete && (
<Modal
text={`Are you sure that you want remove ${friendToDelete.first_name} ${friendToDelete.last_name} from your friends ?`}
handleYes={() => {
handleDeclineRequest(friendToDelete);
setFriendToDelete(null);
}}
handleNo={() => setShowDeleteModal(false)}
></Modal>
)}

<div id="friend-requests" className="">
<div id="friend-requests">
<div className="p-10 rounded-xl bg-my-dark">
<h1 className="text-3xl font-bold">Friend requests:</h1>
<hr className="text-my-orange"></hr>
Expand All @@ -205,6 +183,27 @@ function FriendsPage() {
</div>
</div>
</section>
<section id="suggestions" className=" mt-8">
<div>
<h1 className="text-3xl font-bold">Friends Suggestions:</h1>
{user && friendSuggestions && friendSuggestions.length > 0 ? (
<Paginator
users={friendSuggestions}
itemsPerPage={3}
renderItem={(user) => (
<FoundUser
user={user}
key={String(1)}
currentId={user.id}
isFriend={false}
/>
)}
/>
) : (
"You need to add more friends to show valid suggestions."
)}
</div>
</section>
</div>
<Footer />
</>
Expand Down
Loading
Loading