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) => ( + + ))} + + ) : ( + + No friends yet + + 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} + + + + + + + + + + Manage + } + onClick={() => + ModificationModal({ + handleAction: handleRemoval, + title: "Remove friend", + buttonConfirmText: "Remove", + buttonConfirmColor: "red", + childrenContent: ( + + Are you sure that you want to remove your friend {friend.login}? + + ), + }) + } + > + Remove friend + + + ); -} \ 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 ( + + + + + + + + Manage + } + onClick={() => ModificationModal({ + handleAction: handleRemove, + title: 'Remove friend', + buttonConfirmText: 'Remove', + buttonConfirmColor: 'red', + childrenContent: Are you sure that you want to remove your friend {user.login}? + })} + > + Remove friend + + + + ); + 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