diff --git a/frontend/src/Features/Following/Following.tsx b/frontend/src/Features/Following/Following.tsx
deleted file mode 100644
index 408873f..0000000
--- a/frontend/src/Features/Following/Following.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import {Card, Center, Title} from "@mantine/core";
-
-export const Following = () => {
- return (
-
-
-
- Following
-
-
- {/* Here will be the list of followed users after PR-63*/}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/src/Features/Following/index.tsx b/frontend/src/Features/Following/index.tsx
deleted file mode 100644
index bb43b76..0000000
--- a/frontend/src/Features/Following/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./Following";
\ No newline at end of file
diff --git a/frontend/src/Features/Friends/Friends.tsx b/frontend/src/Features/Friends/Friends.tsx
index ff93e9a..8a33468 100644
--- a/frontend/src/Features/Friends/Friends.tsx
+++ b/frontend/src/Features/Friends/Friends.tsx
@@ -1,46 +1,61 @@
-import {Card, Center, Group, SimpleGrid, Text, Title} from "@mantine/core";
+import {Card, Center, Divider, Group, Image, SimpleGrid, Stack, Text, Title} from "@mantine/core";
import {FriendDetailed} from "./components/FriendDetailed";
+import {BasicUserInfo} from "../shared/types";
+import {useEffect, useState} from "react";
+import api from "../shared/services/api.ts";
+import cat_no_friends from "./assets/cat_no_friends.png";
-type SuggestedUser = {
- id: number;
- name: string;
- avatar: string;
- tag: string;
-};
+export const Friends = () => {
+ const [friends, setFriends] = useState([]);
-const dummyUsers: SuggestedUser[] = [
- {id: 1, name: "Alice Johnson", avatar: "https://via.placeholder.com/40", tag: "@alicejohnson"},
- {id: 2, name: "Bob Smith", avatar: "https://via.placeholder.com/40", tag: "@bobsmith"},
- {id: 3, name: "Charlie Brown", avatar: "https://via.placeholder.com/40", tag: "@charliebrown"},
- {id: 4, name: "David Johnson", avatar: "https://via.placeholder.com/40", tag: "@davidjohnson"},
- {id: 5, name: "Eve Johnson", avatar: "https://via.placeholder.com/40", tag: "@evejohnson"},
- {id: 6, name: "Frank Johnson", avatar: "https://via.placeholder.com/40", tag: "@frankjohnson"},
- {id: 7, name: "Grace Johnson", avatar: "https://via.placeholder.com/40", tag: "@gracejohnson"},
- {id: 8, name: "Hannah Johnson", avatar: "https://via.placeholder.com/40", tag: "@hannahjohnson"},
- {id: 9, name: "Isaac Johnson", avatar: "https://via.placeholder.com/40", tag: "@isaacjohnson"},
- {id: 10, name: "Jack Johnson", avatar: "https://via.placeholder.com/40", tag: "@jackjohnson"},
- {id: 11, name: "Katie Johnson", avatar: "https://via.placeholder.com/40", tag: "@katiejohnson"},
-];
+ const fetchFriends = () => {
+ api.get('/api/relations/friends').then((response) => {
+ setFriends(response.data.content);
+ });
+ };
+
+ useEffect(() => {
+ fetchFriends();
+ }, []);
-export const Friends = () => {
return (
-
-
-
- List of friends
-
-
- Total friends: {dummyUsers.length}
-
+
+
+ List of friends
+ Total friends: {friends.length}
- {/*List of friends*/}
-
- {dummyUsers.map((user) => (
-
- ))}
-
+
+
+ {/* Friends list */}
+ {friends.length > 0 ? (
+
+ {friends.map((user) => (
+
+ ))}
+
+ ) : (
+
+
+
+ You don’t have any friends yet. 🐾
+
+
+ Start connecting with people and build your social circle!
+
+
+ )}
- )
-}
\ No newline at end of file
+ );
+};
diff --git a/frontend/src/Features/Friends/assets/cat_no_friends.png b/frontend/src/Features/Friends/assets/cat_no_friends.png
new file mode 100644
index 0000000..4703dd2
Binary files /dev/null and b/frontend/src/Features/Friends/assets/cat_no_friends.png differ
diff --git a/frontend/src/Features/Friends/components/FriendDetailed/FriendDetailed.tsx b/frontend/src/Features/Friends/components/FriendDetailed/FriendDetailed.tsx
index c886216..2d48d08 100644
--- a/frontend/src/Features/Friends/components/FriendDetailed/FriendDetailed.tsx
+++ b/frontend/src/Features/Friends/components/FriendDetailed/FriendDetailed.tsx
@@ -1,26 +1,76 @@
-import {Avatar, Card, Group, Stack, Text} from "@mantine/core";
+import {ActionIcon, Avatar, Card, Group, Menu, rem, Stack, Text} from "@mantine/core";
import {useNavigate} from "react-router-dom";
+import {BasicUserInfo} from "../../../shared/types";
+import {IconDots, IconUsersMinus} from "@tabler/icons-react";
+import {ModificationModal} from "../../../shared/components/ModificationModal";
+import api from "../../../shared/services/api.ts";
+import {useAlert} from "../../../../Providers/AlertProvider.tsx";
-export const FriendDetailed = (props: { friend: any }) => {
-
+export const FriendDetailed = ({
+ friend,
+ onRemove,
+ }: {
+ friend: BasicUserInfo;
+ onRemove: () => void; // Function to refresh friends list
+}) => {
const navigate = useNavigate();
+ const alert = useAlert();
+
+ const handleRemoval = () => {
+ api.post(`/api/relations/${friend.login}/delete-friend`).then((response) => {
+ if (response.status === 200) {
+ alert.showError({
+ title: "Success",
+ message: "Friend removed",
+ level: "INFO",
+ timestamp: new Date().toISOString(),
+ });
+ onRemove();
+ }
+ });
+ };
return (
- navigate(`/profile/${props.friend.tag}`)}
- style={{cursor: "pointer"}}
- >
+
{/*Friend detailed view*/}
-
-
-
- {props.friend.name}
- {props.friend.tag}
-
+
+ navigate(`/profile/@${friend.login}`)}>
+
+
+ {friend.name}
+ @{friend.login}
+
+
+
);
-}
\ No newline at end of file
+};
diff --git a/frontend/src/Features/MainPage/MainPage.tsx b/frontend/src/Features/MainPage/MainPage.tsx
index d6b7858..7fc92f0 100644
--- a/frontend/src/Features/MainPage/MainPage.tsx
+++ b/frontend/src/Features/MainPage/MainPage.tsx
@@ -17,7 +17,7 @@ export const MainPage = () => {
setLoading(true);
try {
const response = await api.get(`/api/posts`, {
- params: {pageNo: page, pageSize: 3},
+ params: {pageNo: page, pageSize: 10},
});
const data = response.data;
diff --git a/frontend/src/Features/Relations/Relations.tsx b/frontend/src/Features/Relations/Relations.tsx
new file mode 100644
index 0000000..3321231
--- /dev/null
+++ b/frontend/src/Features/Relations/Relations.tsx
@@ -0,0 +1,141 @@
+import {useEffect, useState} from "react";
+import {CenterContainer} from "../shared/components/CenterContainer";
+import {Box, Card, Divider, Pagination, ScrollArea, Tabs, Title} from "@mantine/core";
+import {IconArrowDownLeft, IconCircleX, IconSend} from "@tabler/icons-react";
+import api from "../shared/services/api";
+import {UserGrid} from "./components/UserGrid";
+import {useAlert} from "../../Providers/AlertProvider.tsx";
+
+export const Relations = () => {
+ const [activeTab, setActiveTab] = useState("sent");
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPages, setTotalPages] = useState(0);
+ const [data, setData] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const alert = useAlert();
+
+ const fetchData = async (page: number) => {
+ setIsLoading(true);
+ let endpoint = "";
+
+ if (activeTab === "sent") {
+ endpoint = `/api/relations/pending?page=${page - 1}`;
+ } else if (activeTab === "received") {
+ endpoint = `/api/relations/received?page=${page - 1}`;
+ } else if (activeTab === "rejected") {
+ endpoint = `/api/relations/rejected?page=${page - 1}`;
+ }
+
+ try {
+ const response = await api.get(endpoint);
+ setData(response.data?.content || []);
+ setTotalPages(response.data?.totalPages || 0);
+ } catch (error) {
+ setData([]);
+ setTotalPages(0);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleAcceptRequest = (login: string) => {
+ api.post(`/api/relations/${login}/accept`).then(r => {
+ if (r.status === 200) {
+ fetchData(currentPage).then(r => r);
+ alert.showError(
+ {
+ title: 'Success',
+ message: 'Friend request accepted',
+ level: 'INFO',
+ timestamp: new Date().toISOString()
+ }
+ );
+ }
+ });
+ }
+
+ const handleCancelRequest = (login: string) => {
+ api.post(`/api/relations/${login}/reject`).then(r => {
+ if (r.status === 200) {
+ fetchData(currentPage).then(r => r);
+ alert.showError(
+ {
+ title: 'Success',
+ message: 'Friend request rejected',
+ level: 'INFO',
+ timestamp: new Date().toISOString()
+ }
+ );
+ }
+ });
+ }
+
+ // Fetch data when activeTab or currentPage changes
+ useEffect(() => {
+ fetchData(currentPage).then(r => r);
+ }, [activeTab, currentPage]);
+
+ return (
+
+
+
+ 1 ? 'calc(90dvh - 64px)' : ''}>
+ Relations
+
+
+
+ }>
+ Sent
+
+ }>
+ Received
+
+ }>
+ Rejected
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {totalPages > 1 &&
+
+
+
+ }
+
+
+ );
+};
diff --git a/frontend/src/Features/Relations/components/UserGrid/UserGrid.tsx b/frontend/src/Features/Relations/components/UserGrid/UserGrid.tsx
new file mode 100644
index 0000000..7c5b38c
--- /dev/null
+++ b/frontend/src/Features/Relations/components/UserGrid/UserGrid.tsx
@@ -0,0 +1,60 @@
+import {ActionIcon, Card, Group, Loader, SimpleGrid, Stack, Tooltip} from "@mantine/core";
+import {BasicUser} from "../../../shared/components/Cards/BasicUser";
+import {BasicUserInfo} from "../../../shared/types";
+import {IconCircleCheck, IconXboxX} from "@tabler/icons-react";
+
+type UserGridProps = {
+ sentRequests: BasicUserInfo[];
+ isLoading: boolean;
+ emptyMessage?: string;
+ isReceived?: boolean;
+ handleAcceptRequest?: (login: string) => void;
+ handleCancelRequest?: (login: string) => void;
+};
+
+export const UserGrid = (props: UserGridProps) => {
+
+ return (
+ <>
+ {props.isLoading &&
+
+
+
+ }
+ {!props.isLoading && props.sentRequests.length === 0 &&
+
+ {props.emptyMessage}
+
+ }
+ {!props.isLoading && props.sentRequests.length > 0 &&
+
+ {props.sentRequests.map((person) => (
+
+
+
+ {props.isReceived &&
+
+
+ {
+ props.handleCancelRequest && props.handleCancelRequest(person.login);
+ }}>
+
+
+
+
+ {
+ props.handleAcceptRequest && props.handleAcceptRequest(person.login);
+ }}>
+
+
+
+
+ }
+
+
+ ))}
+
+ }
+ >
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/Features/Relations/components/UserGrid/index.tsx b/frontend/src/Features/Relations/components/UserGrid/index.tsx
new file mode 100644
index 0000000..cc97914
--- /dev/null
+++ b/frontend/src/Features/Relations/components/UserGrid/index.tsx
@@ -0,0 +1 @@
+export {UserGrid} from './UserGrid';
\ No newline at end of file
diff --git a/frontend/src/Features/Relations/index.tsx b/frontend/src/Features/Relations/index.tsx
new file mode 100644
index 0000000..63de5dc
--- /dev/null
+++ b/frontend/src/Features/Relations/index.tsx
@@ -0,0 +1 @@
+export {Relations} from './Relations';
\ No newline at end of file
diff --git a/frontend/src/Features/Root/Root.tsx b/frontend/src/Features/Root/Root.tsx
index af7896e..1f11424 100644
--- a/frontend/src/Features/Root/Root.tsx
+++ b/frontend/src/Features/Root/Root.tsx
@@ -7,7 +7,6 @@ import {NotFound} from "../NotFound";
import {Recovery} from "../Recovery";
import {Profile} from "../Profile";
import {Search} from "../Search";
-import {Following} from "../Following";
import {Groups} from "../Groups";
import {Friends} from "../Friends";
import {Settings} from "../Settings";
@@ -15,6 +14,7 @@ import {Messenger} from "../Messenger";
import {ProtectedRoute} from "./components/ProtectedRoute";
import {PublicRoute} from "./components/PublicRoute";
import {useAuthStore} from "../shared/services/authStore.ts";
+import {Relations} from "../Relations";
import {Matching} from "../Matching";
import {MatchProfile} from "../Matching/components/Profile";
import {MatchFilters} from "../Matching/components/Filters";
@@ -41,7 +41,7 @@ export const Root = () => {
}/>
}/>
}/>
- }/>
+ }/>
}/>
}/>
}/>
diff --git a/frontend/src/Features/Root/components/Navbar/Navbar.tsx b/frontend/src/Features/Root/components/Navbar/Navbar.tsx
index ca22811..9f57ca4 100644
--- a/frontend/src/Features/Root/components/Navbar/Navbar.tsx
+++ b/frontend/src/Features/Root/components/Navbar/Navbar.tsx
@@ -5,8 +5,8 @@ import {
IconHome,
IconMail,
IconSettings,
+ IconTopologyFull,
IconUserHeart,
- IconUserPlus,
IconUsers,
IconUsersGroup,
IconZoom
@@ -15,7 +15,7 @@ import {LogOut} from "../Buttons/LogOut";
import {useAuthStore} from "../../../shared/services/authStore.ts";
import {useNavigate} from "react-router-dom";
import {CreatePost} from "../../../CreatePost";
-import {BasicUserInfo} from "../../../shared/types/User.tsx";
+import {BasicUserInfo} from "../../../shared/types";
import {useEffect, useState} from "react";
import api from "../../../shared/services/api.ts";
@@ -75,7 +75,7 @@ export const Navbar = () => {
} text={"Znajomi"} href={"/friends"}/>
} text={"Grupy"} href={"/groups"}/>
- } text={"Obserwowani"} href={"/following"}/>
+ } text={"Relacje"} href={"/relations"}/>
} text={"Matching"} href={"/matching"}/>
} text={"Wiadomości"} href={"/messages"}/>
diff --git a/frontend/src/Features/Search/Search.tsx b/frontend/src/Features/Search/Search.tsx
index a401294..6aa3a33 100644
--- a/frontend/src/Features/Search/Search.tsx
+++ b/frontend/src/Features/Search/Search.tsx
@@ -9,66 +9,92 @@ import {
Stack,
Text,
} from '@mantine/core';
-import {useState} from 'react';
+import {useEffect, useState} from 'react';
import {IconSearch, IconX} from "@tabler/icons-react";
import {SuggestedUsers} from "./components/SuggestedUsers";
import {useNavigate} from "react-router-dom";
-import {BasicUserInfo} from "../shared/types/User.tsx";
+import {BasicUserInfo} from "../shared/types";
import {SearchResults} from "./components/SearchResults";
-
-// Mocked users data
-const users: BasicUserInfo[] = [
- {id: '1', name: 'Emily', surname: 'Johnson', profilePicture: 'https://via.placeholder.com/40', login: 'emily'},
- {id: '2', name: 'Ava', surname: 'Rodriguez', profilePicture: 'https://via.placeholder.com/40', login: 'ava'},
- {id: '3', name: 'Olivia', surname: 'Chen', profilePicture: 'https://via.placeholder.com/40', login: 'olivia'},
- {id: '4', name: 'Ethan', surname: 'Barnes', profilePicture: 'https://via.placeholder.com/40', login: 'ethan'},
- {id: '5', name: 'Mason', surname: 'Taylor', profilePicture: 'https://via.placeholder.com/40', login: 'mason'},
- {id: '6', name: "Jan", surname: 'Kowalski', profilePicture: 'https://via.placeholder.com/40', login: 'johndoe'},
-];
-
-const usersData = users.reduce((acc, user) => {
- acc[`${user.name} ${user.surname}`] = {profilePicture: user.profilePicture, tag: `@${user.login}`};
- return acc;
-}, {} as Record);
-
-const renderAutocompleteOption: AutocompleteProps['renderOption'] = ({option}) => {
- if (option.value.startsWith("search:")) {
- const searchText = option.value.replace("search:", "");
- return (
-
- Wyszukaj "{searchText}"
-
- );
- }
-
- return (
-
-
-
- {option.value}
-
- {usersData[option.value]?.tag}
-
-
-
- );
-};
+import api from "../shared/services/api.ts";
export const Search = () => {
const [query, setQuery] = useState('');
const [dropdownOpened, setDropdownOpened] = useState(false);
+ const [userOptions, setUserOptions] = useState([]);
+ const [usersData, setUsersData] = useState>({});
const navigate = useNavigate();
const itemsNumber = 5;
- const convertedData = users
- .map((user) => ({
- value: `${user.name} ${user.surname}`,
- label: `${user.name} ${user.surname}`,
- tag: `@${user.login}`,
- profilePicture: user.profilePicture,
- }));
+ const renderAutocompleteOption: AutocompleteProps['renderOption'] = ({option}) => {
+
+ console.log('option', option);
+
+ if (option.value.startsWith("search:")) {
+ const searchText = option.value.replace("search:", "");
+ return (
+
+ Wyszukaj "{searchText}"
+
+ );
+ }
+
+ return (
+
+
+
+ {option.value}
+
+ {usersData[option.value].tag}
+
+
+
+ );
+ };
+
+ const fetchUsers = async (searchQuery = '') => {
+ try {
+ const response = await api.get('/api/users/search', {
+ params: {
+ query: searchQuery,
+ size: itemsNumber,
+ },
+
+ });
+
+ const content = response.data.content as BasicUserInfo[];
+
+ const users = content.map((user: BasicUserInfo) => ({
+ value: `${user.name} ${user.surname}`,
+ label: `${user.name} ${user.surname}`,
+ tag: `@${user.login}`,
+ profilePicture: user.profilePicture,
+ }));
+
+ // Map user data
+ const userDataMap = content.reduce((acc, user) => {
+ acc[`${user.name} ${user.surname}`] = {profilePicture: user.profilePicture, tag: `@${user.login}`};
+ return acc;
+ }, {} as Record);
+
+ setUserOptions(users);
+ setUsersData(userDataMap);
+ } catch (error) {
+ console.error('Error fetching users:', error);
+ }
+ };
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ fetchUsers(query);
+ }, 500);
+
+ return () => clearTimeout(handler);
+ }, [query]);
+
+ useEffect(() => {
+ fetchUsers();
+ }, []);
- // Data for autocomplete options
const optionsFilter: OptionsFilter = ({options, search}) => {
if (!search) {
return options;
@@ -76,10 +102,9 @@ export const Search = () => {
const filteredOptions = (options as ComboboxItem[]).filter((option) => {
const searchLower = search.toLowerCase();
- return option.value.toLowerCase().includes(searchLower) || usersData[option.value]?.tag.toLowerCase().includes(searchLower);
+ return option.label.toLowerCase().includes(searchLower) || usersData[option.value].tag.toLowerCase().includes(searchLower);
});
- // Add dynamic option for searching users
return [
{
value: `search:${search}`,
@@ -89,7 +114,6 @@ export const Search = () => {
].slice(0, itemsNumber);
};
-
const handleSearchSubmit = () => {
if (query) {
navigate(`/search?query=${encodeURIComponent(query)}`);
@@ -101,14 +125,11 @@ export const Search = () => {
setDropdownOpened(false);
};
- //TODO: Get random {amount} users and display them in SuggestedUsers component
- //TODO: Get filtered users based on query and display them in SearchResults component
-
return (
}
rightSection={query ? : null}
renderOption={renderAutocompleteOption}
@@ -129,7 +150,10 @@ export const Search = () => {
navigate(`/search?query=${encodeURIComponent(searchText)}`);
handleClear();
} else {
- navigate(`/profile/${usersData[value]?.tag}`);
+ const selectedUser = userOptions.find((user) => user.value === value);
+ if (selectedUser) {
+ navigate(`/profile/${usersData[value]?.tag}`);
+ }
}
}}
onKeyDown={(event) => {
@@ -143,6 +167,5 @@ export const Search = () => {
-
);
};
diff --git a/frontend/src/Features/Search/components/SearchResults/SearchResults.tsx b/frontend/src/Features/Search/components/SearchResults/SearchResults.tsx
index 8f5ee2f..b4f2673 100644
--- a/frontend/src/Features/Search/components/SearchResults/SearchResults.tsx
+++ b/frontend/src/Features/Search/components/SearchResults/SearchResults.tsx
@@ -1,41 +1,221 @@
-import {ActionIcon, Avatar, Card, Divider, Group, Stack, Text} from '@mantine/core';
-import {useSearchParams} from "react-router-dom";
-import {BasicUserInfo} from "../../../shared/types/User.tsx";
-import {IconPlus, IconUserPlus} from "@tabler/icons-react";
+import {ActionIcon, Avatar, Card, Divider, Group, Menu, rem, Stack, Text, Tooltip} from '@mantine/core';
+import {useNavigate, useSearchParams} from "react-router-dom";
+import {BasicUserInfo} from "../../../shared/types";
+import {IconDots, IconSend, IconUserCheck, IconUserPlus, IconUsersMinus, IconUserX} from "@tabler/icons-react";
+import {useEffect, useState} from "react";
+import api from "../../../shared/services/api.ts";
+import {useAuthStore} from "../../../shared/services/authStore.ts";
+import {useAlert} from "../../../../Providers/AlertProvider.tsx";
+import {ModificationModal} from "../../../shared/components/ModificationModal";
+
+type RelationStatus = 'none' | 'friends' | 'pendingSent' | 'pendingReceived';
+
+const fetchPaginatedResults = async (url: string) => {
+ const results: BasicUserInfo[] = [];
+ let page = 0;
+ let hasMore = true;
+
+ while (hasMore) {
+ const response = await api.get(url, {params: {page}});
+ const {content, last} = response.data;
+ results.push(...content);
+ hasMore = !last;
+ page++;
+ }
+
+ return results;
+};
export const SearchResults = () => {
const [searchParams] = useSearchParams();
- const query = searchParams.get('query') || '';
+ const query = searchParams.get('query') ?? '';
+ const [filteredUsers, setFilteredUsers] = useState([]);
+ const [relations, setRelations] = useState>({});
+ const loggedInUser = useAuthStore();
+ const navigate = useNavigate();
+ const alert = useAlert();
+
+ useEffect(() => {
+ if (query) {
+ // Fetch users matching the query
+ api.get('/api/users/search', {
+ params: {query},
+ }).then((response) => {
+ const users = response.data.content;
+ setFilteredUsers(users);
+
+ // Fetch relations status for each user
+ fetchRelations(users.map((user: BasicUserInfo) => user.login));
+ });
+ }
+ }, [query]);
+
+ const fetchRelations = async (logins: string[]) => {
+ try {
+ // Fetch list of friends (paginated)
+ const friends = await fetchPaginatedResults('/api/relations/friends');
+
+ // Fetch list of pending relations (paginated)
+ const pendingSent = await fetchPaginatedResults('/api/relations/pending');
+ const pendingReceived = await fetchPaginatedResults('/api/relations/received');
+
+ // Update relations status
+ const newRelations: Record = {};
+ logins.forEach((login) => {
+ if (friends.some((friend) => friend.login === login)) {
+ newRelations[login] = 'friends';
+ } else if (pendingSent.some((user) => user.login === login)) {
+ newRelations[login] = 'pendingSent';
+ } else if (pendingReceived.some((user) => user.login === login)) {
+ newRelations[login] = 'pendingReceived';
+ } else {
+ newRelations[login] = 'none';
+ }
+ });
- // Mocked users data
- const filteredUsers: BasicUserInfo[] = [
- {id: '1', name: 'Emily', surname: 'Johnson', profilePicture: 'https://via.placeholder.com/40', login: 'emily'},
- {id: '2', name: 'Ava', surname: 'Rodriguez', profilePicture: 'https://via.placeholder.com/40', login: 'ava'},
- {id: '3', name: 'Olivia', surname: 'Chen', profilePicture: 'https://via.placeholder.com/40', login: 'olivia'},
- ];
+ setRelations(newRelations);
+ } catch (error) {
+ console.error("Error fetching relations:", error);
+ }
+ };
+
+ const handleSendFriendRequest = (login: string) => {
+ api.post(`/api/relations/${login}/send`).then(() => {
+ setRelations((prev) => ({...prev, [login]: 'pendingSent'}));
+ alert.showError({
+ title: 'Success',
+ message: 'Friend request sent',
+ level: 'INFO',
+ timestamp: new Date().toISOString(),
+ });
+ });
+ };
+
+ const handleCancelFriendRequest = (login: string) => {
+ api.post(`/api/relations/${login}/reject`).then(() => {
+ setRelations((prev) => ({...prev, [login]: 'none'}));
+ alert.showError({
+ title: 'Cancelled',
+ message: 'Friend request cancelled',
+ level: 'INFO',
+ timestamp: new Date().toISOString(),
+ });
+ });
+ };
+
+ const handleAcceptFriendRequest = (login: string) => {
+ api.post(`/api/relations/${login}/accept`).then(() => {
+ setRelations((prev) => ({...prev, [login]: 'friends'}));
+ alert.showError({
+ title: 'Accepted',
+ message: 'Friend request accepted',
+ level: 'INFO',
+ timestamp: new Date().toISOString(),
+ });
+ });
+ };
+
+ const handleRemoveFriend = (login: string) => {
+ api.post(`/api/relations/${login}/delete-friend`).then(() => {
+ setRelations((prev) => ({...prev, [login]: 'none'}));
+ alert.showError({
+ title: 'Removed',
+ message: 'Friend removed',
+ level: 'INFO',
+ timestamp: new Date().toISOString(),
+ });
+ });
+ };
+
+ const renderActionButton = (user: BasicUserInfo) => {
+ const status = relations[user.login] || 'none';
+
+ const handleRemove = () => {
+ handleRemoveFriend(user.login);
+ }
+
+ switch (status) {
+ case 'none':
+ return (
+ handleSendFriendRequest(user.login)}>
+
+
+ );
+ case 'pendingSent':
+ return (
+
+
+
+
+
+ );
+ case 'pendingReceived':
+ return (
+
+ handleAcceptFriendRequest(user.login)}>
+
+
+ handleCancelFriendRequest(user.login)}>
+
+
+
+ );
+ case 'friends':
+ return (
+
+ );
+ default:
+ return null;
+ }
+ };
if (!query) {
return null;
}
- //TODO: Get filtered users based on query
-
return (
Wyniki wyszukiwania:
-
- {filteredUsers.length} results
-
+ {filteredUsers.length > 0 &&
+
+ {filteredUsers.length} results
+
+ }
{filteredUsers.length > 0 ? (
filteredUsers.map((user) => (
-
+ navigate(`/profile/@${user.login}`)} style={{cursor: 'pointer'}}>
{user.name} {user.surname}
@@ -44,16 +224,7 @@ export const SearchResults = () => {
-
- {
- }}>
-
-
- {
- }}>
-
-
-
+ {loggedInUser.user?.login !== user.login && renderActionButton(user)}
))
) : (
diff --git a/frontend/src/Features/shared/components/Cards/BasicUser/BasicUser.tsx b/frontend/src/Features/shared/components/Cards/BasicUser/BasicUser.tsx
new file mode 100644
index 0000000..a8e052d
--- /dev/null
+++ b/frontend/src/Features/shared/components/Cards/BasicUser/BasicUser.tsx
@@ -0,0 +1,32 @@
+import {BasicUserInfo} from "../../../types";
+import {Avatar, Group, MantineSize, Stack, Text} from "@mantine/core";
+
+type BasicUserType = {
+ user: BasicUserInfo,
+ avatarSize?: MantineSize
+ tagVisible?: boolean
+}
+
+export const BasicUser = (props: BasicUserType) => {
+
+ const avatarSize = props.avatarSize ? props.avatarSize : 'xl';
+ const tagVisible = props.tagVisible ? props.tagVisible : true;
+
+ return (
+
+
+
+
+
+ {props.user.name} {props.user.surname}
+ {tagVisible &&
+ @{props.user.login}
+ }
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/Features/shared/components/Cards/BasicUser/index.tsx b/frontend/src/Features/shared/components/Cards/BasicUser/index.tsx
new file mode 100644
index 0000000..69c5574
--- /dev/null
+++ b/frontend/src/Features/shared/components/Cards/BasicUser/index.tsx
@@ -0,0 +1 @@
+export {BasicUser} from './BasicUser.tsx';
\ No newline at end of file
diff --git a/frontend/src/Features/shared/types/index.tsx b/frontend/src/Features/shared/types/index.tsx
new file mode 100644
index 0000000..0329e43
--- /dev/null
+++ b/frontend/src/Features/shared/types/index.tsx
@@ -0,0 +1 @@
+export type {User, BasicUserInfo} from './User';
\ No newline at end of file