Skip to content
Snippets Groups Projects
Commit 16cbcd78 authored by VIktorGrev's avatar VIktorGrev
Browse files

feat: Integrating friends endpoint into UserFriends

parent 49fbecec
No related branches found
No related tags found
1 merge request!56Integrating friends
......@@ -585,22 +585,22 @@
"required": true
},
"responses": {
"409": {
"description": "Email already exists",
"201": {
"description": "Successfully signed up",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ExceptionResponse"
"$ref": "#/components/schemas/AuthenticationResponse"
}
}
}
},
"201": {
"description": "Successfully signed up",
"409": {
"description": "Email already exists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AuthenticationResponse"
"$ref": "#/components/schemas/ExceptionResponse"
}
}
}
......@@ -638,8 +638,8 @@
}
}
},
"401": {
"description": "Invalid credentials",
"404": {
"description": "User not found",
"content": {
"application/json": {
"schema": {
......@@ -648,8 +648,8 @@
}
}
},
"404": {
"description": "User not found",
"401": {
"description": "Invalid credentials",
"content": {
"application/json": {
"schema": {
......@@ -889,6 +889,49 @@
}
}
},
"/api/users/search/{searchTerm}/{filter}": {
"get": {
"tags": [
"User"
],
"summary": "Search for users by name and filter",
"description": "Returns a list of users whose names contain the specified search term and match the filter.",
"operationId": "getUsersByNameAndFilter",
"parameters": [
{
"name": "searchTerm",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "filter",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successfully retrieved list of users",
"content": {
"*/*": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/UserDTO"
}
}
}
}
}
}
}
},
"/api/users/me": {
"get": {
"tags": [
......@@ -1719,6 +1762,9 @@
"role": {
"type": "string"
},
"subscriptionLevel": {
"type": "string"
},
"token": {
"type": "string"
}
......@@ -1782,6 +1828,9 @@
},
"role": {
"type": "string"
},
"subscriptionLevel": {
"type": "string"
}
}
},
......
......@@ -6,6 +6,7 @@ export type AuthenticationResponse = {
firstName?: string;
lastName?: string;
role?: string;
subscriptionLevel?: string;
token?: string;
};
......@@ -10,5 +10,6 @@ export type UserDTO = {
email?: string;
createdAt?: string;
role?: string;
subscriptionLevel?: string;
};
......@@ -199,6 +199,28 @@ export class UserService {
},
});
}
/**
* Search for users by name and filter
* Returns a list of users whose names contain the specified search term and match the filter.
* @returns UserDTO Successfully retrieved list of users
* @throws ApiError
*/
public static getUsersByNameAndFilter({
searchTerm,
filter,
}: {
searchTerm: string,
filter: string,
}): CancelablePromise<Array<UserDTO>> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/users/search/{searchTerm}/{filter}',
path: {
'searchTerm': searchTerm,
'filter': filter,
},
});
}
/**
* Get the authenticated user
* Get all user information for the authenticated user
......
<script setup lang="ts">
import { ref } from 'vue'
import { FriendService } from '@/api';
async function addFriend(friendID: number) {
const response = await FriendService.addFriendRequest({ userId: friendID });
console.log(response);
}
</script>
<template>
<div class="container">
<h1>Add Friend</h1>
<div class="row">
<form class="col-md-5" id="searchBox" role="search">
<input class="form-control me-2 custom-border" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-success" type="submit">Search</button>
</form>
<input class="form-control me-2 custom-border" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-success" type="submit">Search</button>
</form>
<div class="col-md-8">
<div class="people-nearby">
<div class="nearby-user">
......@@ -20,7 +31,7 @@
<p class="text-muted">500m away</p>
</div>
<div class="col-md-3 col-sm-3">
<button class="btn btn-primary pull-right">Add Friend</button>
<button class="btn btn-primary pull-right" @click="addFriend(1)">Add Friend</button>
</div>
</div>
</div>
......@@ -221,6 +232,7 @@ img.profile-photo-lg {
}
.form-control.custom-border {
border-color: #222223; /* Change to your desired color */
border-color: #222223;
/* Change to your desired color */
}
</style>
\ No newline at end of file
<template>
<div class="container">
<h1>Your Friends</h1>
<button class="btn btn-primary pull-right my-3" @click="addFriend">+ Add Friend</button>
<div class="row">
<div class="col-lg-3" v-for="friend in friends" :key="friend.id">
<div class="card card-one">
<div class="header">
<div v-if="friend.profileImage" class="avatar">
<img :src="'http://localhost:8080/api/images/' + friend.profileImage" alt="">
</div>
<div v-else class="avatar">
<img :src="'../src/assets/userprofile.png'" alt="">
<div>
<button class="btn btn-primary pull-right" @click="addNewFriends">+ Add Friend</button>
<div class="my-3">
<button class="btn pages" @click="setupFriends">Your Friends</button>
<button class="btn pages" @click="requestFriend">Friend Requests</button>
</div>
</div>
<div v-if="showFriends">
<div v-if="elementsInFriends">
<div class="row">
<div class="col-lg-3" v-for="friend in friends" :key="friend.id">
<div class="card card-one">
<div class="header">
<div v-if="friend.profileImage" class="avatar">
<img :src="'http://localhost:8080/api/images/' + friend.profileImage" alt="">
</div>
<div v-else class="avatar">
<img :src="'../src/assets/userprofile.png'" alt="">
</div>
</div>
<h3><a href="#" class="btn stretched-link" id="profileName"
@click="navigateToFriend(friend.id)">{{
friend.firstName }}</a></h3>
<div class="desc">{{ friend.firstName }} {{ friend.lastName }}</div>
<div class="contacts">
<a class="text removeFriend" data-bs-toggle="collapse"
:href="'#collapseExample' + friend.id" role="button" aria-expanded="false"
:aria-controls="'collapseExample' + friend.id">
See more
</a>
<div class="collapse" :id="'collapseExample' + friend.id">
<button class="btn btn-danger" @click="removeFriend(friend.id)">
<h5><img src="@/assets/icons/remove-white.svg" style="width: 30px"> Remove
friend
</h5>
</button>
</div>
</div>
</div>
</div>
<h3><a href="#" class="btn stretched-link" id="profileName" @click="navigateToFriend(friend.id)">{{ friend.firstName }}</a></h3>
<div class="desc">{{ friend.firstName }} {{ friend.lastName }}</div>
<div class="contacts">
<a class="text removeFriend" data-bs-toggle="collapse"
:href="'#collapseExample' + friend.id" role="button" aria-expanded="false" :aria-controls="'collapseExample' + friend.id">
See more
</a>
<div class="collapse" :id="'collapseExample' + friend.id">
<button class="btn btn-danger" @click="removeFriend(friend.id)">
<h5><img src="@/assets/icons/remove-white.svg" style="width: 30px"> Remove friend</h5>
</button>
</div>
</div>
<div v-else>No Friends</div>
</div>
<div v-else-if="showRequests" class="row">
<div class="content-body">
<div v-if="elementsInFriendRequest" id="requests">
<div class="request" v-for="(friend) in friendRequests" :key="friend.id">
<div v-if="friend.profileImage !== null"><img id="profilePicture"
:src="'http://localhost:8080/api/images/' + friend.profileImage" alt="user"
class="profile-photo-lg"></div>
<div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'" alt="user"
class="profile-photo-lg"></div>
<h2>{{ friend.firstName }}</h2> - <button class="btn btn-success mx-2"
@click="acceptRequest(friend.id)">Accept</button>
<button class="btn btn-danger" @click="rejectRequest(friend.id)">Reject</button>
</div>
</div>
<div v-else>No friend requests</div>
</div>
</div>
<div v-if="showAddFriend" class="modal" tabindex="-1" role="dialog"
style="display:block; background-color: rgba(0,0,0,0.5);">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Friend</h5>
<button type="button" class="close" @click="showAddFriend = false">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body d-flex justify-content-center align-items-center flex-column">
<form class="col-md-10 d-flex justify-content-center align-items-center flex-row my-4"
id="searchBox" role="search" @submit.prevent="searchProfile(searchWord)">
<input class="form-control me-2 custom-border" type="search" placeholder="Search"
aria-label="Search" v-model="searchWord">
<button class="btn btn-success" type="submit">Search</button>
</form>
<div class="col-md-12">
<div class="people-nearby">
<div v-for="user in searchedUsers" :key="user.id" class="nearby-user">
<div class="row d-flex align-items-center">
<div class="col-md-2 col-sm-2">
<div v-if="user.profileImage !== null"><img id="profilePicture"
:src="'http://localhost:8080/api/images/' + user.profileImage"
alt="user" class="profile-photo-lg"></div>
<div v-else><img id="profilePicture" :src="'../src/assets/userprofile.png'"
alt="user" class="profile-photo-lg"></div>
</div>
<div class="col-md-7 col-sm-7">
<h5><a href="#" class="profile-link" @click="toUserProfile(user.id)">{{
user.firstName }}</a>
</h5>
</div>
<div class="col-md-3 col-sm-3">
<button class="btn btn-primary pull-right" @click="addFriend(user.id)">Add
Friend</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
......@@ -37,14 +116,65 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { FriendService } from '@/api';
import { FriendService, UserService } from '@/api';
import type { UserDTO } from '@/api';
const router = useRouter();
const friends = ref();
const showFriends = ref(true);
const showRequests = ref(false);
const showAddFriend = ref(false);
const friendRequests = ref([] as any);
const addFriends = ref([] as any);
const searchedUsers = ref([] as any);
const searchWord = ref("");
function addFriend() {
router.push('/add-friend');
const elementsInFriendRequest = ref(false);
const elementsInFriends = ref(false);
const toUserProfile = (userId: number) => {
router.push('/profile/' + userId);
};
const searchProfile = async (searchTerm: string) => {
const userPayload = {
searchTerm: searchTerm as string,
filter: 'NON_FRIENDS' as string,
};
try {
const response = await UserService.getUsersByNameAndFilter(userPayload);
searchedUsers.value = response;
console.log(response);
} catch (error) {
console.error('Failed to search for profile', error);
}
};
const addNewFriends = async () => {
try {
//const response = await FriendService.();
showAddFriend.value = true;
} catch (error) {
console.error('Failed to add friend', error);
}
};
async function addFriend(friendID: number) {
const response = await FriendService.addFriendRequest({ userId: friendID });
}
async function requestFriend() {
showRequests.value = true;
showFriends.value = false;
try {
const response = await FriendService.getFriendRequests();
friendRequests.value = response;
elementsInFriendRequest.value = response.length > 0;
console.log("Friend requests: " + response);
} catch (error) {
console.error('Failed to fetch friend requests', error);
}
}
const navigateToFriend = (friendID: number) => {
......@@ -53,26 +183,49 @@ const navigateToFriend = (friendID: number) => {
const removeFriend = async (friendID: number) => {
try {
// Attempt to delete the friend from the backend.
await FriendService.deleteFriendOrFriendRequest({ friendId: friendID });
// Update the friends list by filtering out the removed friend.
friends.value = friends.value.filter((friend: UserDTO) => friend.id !== friendID);
const responseFriends = await FriendService.getFriends();
friends.value = responseFriends;
} catch (error) {
console.error('Failed to remove friend', error);
}
};
const setupFriends = async () => {
showFriends.value = true;
showRequests.value = false;
try {
const response = await FriendService.getFriends();
friends.value = response;
elementsInFriends.value = response.length > 0;
console.log(response);
} catch (error) {
console.error('Failed to fetch friends', error);
}
};
const acceptRequest = async (requestID: number) => {
try {
await FriendService.acceptFriendRequest({ friendId: requestID });
const responseRequest = await FriendService.getFriendRequests();
friendRequests.value = responseRequest;
const responseFriends = await FriendService.getFriends();
friends.value = responseFriends;
} catch (error) {
console.error('Failed to accept friend request', error);
}
};
const rejectRequest = async (requestID: number) => {
try {
await FriendService.deleteFriendOrFriendRequest({ friendId: requestID });
const response = await FriendService.getFriendRequests();
friendRequests.value = response;
} catch (error) {
console.error('Failed to reject friend request', error);
}
};
onMounted(() => {
setupFriends();
});
......@@ -374,4 +527,39 @@ ul.friend-list .right p {
font-weight: 600;
width: 100%;
}
#requests {
display: flex;
flex-direction: column;
align-items: center;
}
.request {
display: flex;
justify-content: center;
align-items: center;
margin: 1rem;
}
#profilePicture {
width: 70px;
height: 70px;
border-radius: 50%;
margin-right: 1rem;
border: 2px solid #000;
}
.modal-content {
padding: 1rem;
}
.modal-header {
margin-bottom: 5px;
}
.pages {
border-bottom: 1px solid #000;
border-radius: 0px;
margin: 0px 5px;
}
</style>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment