Skip to content
Snippets Groups Projects
Commit d1aeff82 authored by Anders Høvik's avatar Anders Høvik
Browse files

Merge branch 'IntegratingFriends' into 'main'

Integrating friends

See merge request !56
parents a1ff1303 4c1fcb7f
No related branches found
No related tags found
1 merge request!56Integrating friends
Pipeline #281413 passed with warnings
...@@ -596,22 +596,22 @@ ...@@ -596,22 +596,22 @@
"required": true "required": true
}, },
"responses": { "responses": {
"409": { "201": {
"description": "Email already exists", "description": "Successfully signed up",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/ExceptionResponse" "$ref": "#/components/schemas/AuthenticationResponse"
} }
} }
} }
}, },
"201": { "409": {
"description": "Successfully signed up", "description": "Email already exists",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/AuthenticationResponse" "$ref": "#/components/schemas/ExceptionResponse"
} }
} }
} }
...@@ -649,8 +649,8 @@ ...@@ -649,8 +649,8 @@
} }
} }
}, },
"401": { "404": {
"description": "Invalid credentials", "description": "User not found",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
...@@ -659,8 +659,8 @@ ...@@ -659,8 +659,8 @@
} }
} }
}, },
"404": { "401": {
"description": "User not found", "description": "Invalid credentials",
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
...@@ -868,6 +868,49 @@ ...@@ -868,6 +868,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": { "/api/users/me": {
"get": { "get": {
"tags": [ "tags": [
...@@ -1700,6 +1743,9 @@ ...@@ -1700,6 +1743,9 @@
"role": { "role": {
"type": "string" "type": "string"
}, },
"subscriptionLevel": {
"type": "string"
},
"token": { "token": {
"type": "string" "type": "string"
} }
...@@ -1758,6 +1804,9 @@ ...@@ -1758,6 +1804,9 @@
}, },
"role": { "role": {
"type": "string" "type": "string"
},
"subscriptionLevel": {
"type": "string"
} }
} }
}, },
......
...@@ -6,6 +6,7 @@ export type AuthenticationResponse = { ...@@ -6,6 +6,7 @@ export type AuthenticationResponse = {
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
role?: string; role?: string;
subscriptionLevel?: string;
token?: string; token?: string;
}; };
...@@ -10,5 +10,6 @@ export type UserDTO = { ...@@ -10,5 +10,6 @@ export type UserDTO = {
email?: string; email?: string;
createdAt?: string; createdAt?: string;
role?: string; role?: string;
subscriptionLevel?: string;
}; };
...@@ -199,6 +199,28 @@ export class UserService { ...@@ -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 the authenticated user
* Get all user information for 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> <template>
<div class="container"> <div class="container">
<h1>Add Friend</h1> <h1>Add Friend</h1>
<div class="row"> <div class="row">
<form class="col-md-5" id="searchBox" role="search"> <form class="col-md-5" id="searchBox" role="search">
<input class="form-control me-2 custom-border" type="search" placeholder="Search" aria-label="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> <button class="btn btn-success" type="submit">Search</button>
</form> </form>
<div class="col-md-8"> <div class="col-md-8">
<div class="people-nearby"> <div class="people-nearby">
<div class="nearby-user"> <div class="nearby-user">
...@@ -20,7 +31,7 @@ ...@@ -20,7 +31,7 @@
<p class="text-muted">500m away</p> <p class="text-muted">500m away</p>
</div> </div>
<div class="col-md-3 col-sm-3"> <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> </div>
</div> </div>
...@@ -221,6 +232,7 @@ img.profile-photo-lg { ...@@ -221,6 +232,7 @@ img.profile-photo-lg {
} }
.form-control.custom-border { .form-control.custom-border {
border-color: #222223; /* Change to your desired color */ border-color: #222223;
/* Change to your desired color */
} }
</style> </style>
\ No newline at end of file
<template> <template>
<div class="container"> <div class="container">
<h1>Your Friends</h1> <h1>Your Friends</h1>
<button class="btn btn-primary pull-right my-3" @click="addFriend">+ Add Friend</button> <div>
<div class="row"> <button class="btn btn-primary pull-right" @click="addNewFriends">+ Add Friend</button>
<div class="col-lg-3" v-for="friend in friends" :key="friend.id"> <div class="my-3">
<div class="card card-one"> <button class="btn pages" @click="setupFriends">Your Friends</button>
<div class="header"> <button class="btn pages" @click="requestFriend">Friend Requests</button>
<div v-if="friend.profileImage" class="avatar"> </div>
<img :src="'http://localhost:8080/api/images/' + friend.profileImage" alt=""> </div>
</div> <div v-if="showFriends">
<div v-else class="avatar"> <div v-if="elementsInFriends">
<img :src="'../src/assets/userprofile.png'" alt=""> <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>
</div> </div>
<h3><a href="#" class="btn stretched-link" id="profileName" @click="navigateToFriend(friend.id)">{{ friend.firstName }}</a></h3> </div>
<div class="desc">{{ friend.firstName }} {{ friend.lastName }}</div> </div>
<div class="contacts"> <div v-else>No Friends</div>
<a class="text removeFriend" data-bs-toggle="collapse" </div>
:href="'#collapseExample' + friend.id" role="button" aria-expanded="false" :aria-controls="'collapseExample' + friend.id"> <div v-else-if="showRequests" class="row">
See more <div class="content-body">
</a> <div v-if="elementsInFriendRequest" id="requests">
<div class="collapse" :id="'collapseExample' + friend.id"> <div class="request" v-for="(friend) in friendRequests" :key="friend.id">
<button class="btn btn-danger" @click="removeFriend(friend.id)"> <div v-if="friend.profileImage !== null"><img id="profilePicture"
<h5><img src="@/assets/icons/remove-white.svg" style="width: 30px"> Remove friend</h5> :src="'http://localhost:8080/api/images/' + friend.profileImage" alt="user"
</button> 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> </div>
</div> </div>
...@@ -37,14 +116,65 @@ ...@@ -37,14 +116,65 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { FriendService } from '@/api'; import { FriendService, UserService } from '@/api';
import type { UserDTO } from '@/api'; import type { UserDTO } from '@/api';
const router = useRouter(); const router = useRouter();
const friends = ref(); 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() { const elementsInFriendRequest = ref(false);
router.push('/add-friend'); 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) => { const navigateToFriend = (friendID: number) => {
...@@ -53,26 +183,49 @@ const navigateToFriend = (friendID: number) => { ...@@ -53,26 +183,49 @@ const navigateToFriend = (friendID: number) => {
const removeFriend = async (friendID: number) => { const removeFriend = async (friendID: number) => {
try { try {
// Attempt to delete the friend from the backend.
await FriendService.deleteFriendOrFriendRequest({ friendId: friendID }); await FriendService.deleteFriendOrFriendRequest({ friendId: friendID });
// Update the friends list by filtering out the removed friend. const responseFriends = await FriendService.getFriends();
friends.value = friends.value.filter((friend: UserDTO) => friend.id !== friendID); friends.value = responseFriends;
} catch (error) { } catch (error) {
console.error('Failed to remove friend', error); console.error('Failed to remove friend', error);
} }
}; };
const setupFriends = async () => { const setupFriends = async () => {
showFriends.value = true;
showRequests.value = false;
try { try {
const response = await FriendService.getFriends(); const response = await FriendService.getFriends();
friends.value = response; friends.value = response;
elementsInFriends.value = response.length > 0;
console.log(response); console.log(response);
} catch (error) { } catch (error) {
console.error('Failed to fetch friends', 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(() => { onMounted(() => {
setupFriends(); setupFriends();
}); });
...@@ -374,4 +527,39 @@ ul.friend-list .right p { ...@@ -374,4 +527,39 @@ ul.friend-list .right p {
font-weight: 600; font-weight: 600;
width: 100%; 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> </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