-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #96 from JimTheCat/CU-8697aw9wp_Integrate-relation…
…s-with-backend_Patryk-Kosiski Cu 8697aw9wp integrate relations with backend patryk kosiski
- Loading branch information
Showing
17 changed files
with
640 additions
and
161 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<BasicUserInfo[]>([]); | ||
|
||
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 ( | ||
<Center> | ||
<Card shadow="sm" padding="lg" m={"md"} radius="md" w={"fit-content"} withBorder> | ||
<Group justify={"space-between"} align={"flex-start"}> | ||
<Title mb={"md"} order={2}> | ||
List of friends | ||
</Title> | ||
<Text> | ||
Total friends: {dummyUsers.length} | ||
</Text> | ||
<Card shadow="sm" padding="lg" m={"md"} radius="md" withBorder> | ||
<Group justify={"space-between"}> | ||
<Title order={2}>List of friends</Title> | ||
<Text>Total friends: {friends.length}</Text> | ||
</Group> | ||
{/*List of friends*/} | ||
<SimpleGrid cols={3} spacing="lg"> | ||
{dummyUsers.map((user) => ( | ||
<FriendDetailed key={user.id} friend={user}/> | ||
))} | ||
</SimpleGrid> | ||
|
||
<Divider mt={"md"}/> | ||
{/* Friends list */} | ||
{friends.length > 0 ? ( | ||
<SimpleGrid mt={"md"} cols={3} spacing="lg"> | ||
{friends.map((user) => ( | ||
<FriendDetailed | ||
key={user.id} | ||
friend={user} | ||
onRemove={fetchFriends} // Refresh friends list after removing a friend | ||
/> | ||
))} | ||
</SimpleGrid> | ||
) : ( | ||
<Stack align="center" mt="xl"> | ||
<Image | ||
src={cat_no_friends} | ||
radius={"md"} | ||
alt="No friends yet" | ||
width={200} | ||
height={200} | ||
/> | ||
<Text size="lg" c="dimmed" mt="md" ta="center"> | ||
You don’t have any friends yet. 🐾 | ||
</Text> | ||
<Text size="sm" c="dimmed" ta="center"> | ||
Start connecting with people and build your social circle! | ||
</Text> | ||
</Stack> | ||
)} | ||
</Card> | ||
</Center> | ||
) | ||
} | ||
); | ||
}; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 67 additions & 17 deletions
84
frontend/src/Features/Friends/components/FriendDetailed/FriendDetailed.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Card | ||
p="lg" | ||
withBorder | ||
shadow={"lg"} | ||
onClick={() => navigate(`/profile/${props.friend.tag}`)} | ||
style={{cursor: "pointer"}} | ||
> | ||
<Card p="lg" withBorder shadow={"lg"} style={{cursor: "pointer"}}> | ||
{/*Friend detailed view*/} | ||
<Group justify="space-between"> | ||
<Avatar src={props.friend.avatar} size={"lg"} radius={180}/> | ||
<Stack gap={0}> | ||
<Text>{props.friend.name}</Text> | ||
<Text>{props.friend.tag}</Text> | ||
</Stack> | ||
<Group justify={"space-between"}> | ||
<Group onClick={() => navigate(`/profile/@${friend.login}`)}> | ||
<Avatar src={friend.profilePicture} size={"lg"} radius={180}/> | ||
<Stack gap={0}> | ||
<Text>{friend.name}</Text> | ||
<Text>@{friend.login}</Text> | ||
</Stack> | ||
</Group> | ||
<Menu radius={"sm"} shadow="xl" width={"auto"} closeOnItemClick> | ||
<Menu.Target> | ||
<ActionIcon size="lg" color="gray" radius={"xl"} variant={"subtle"}> | ||
<IconDots stroke={1.5}/> | ||
</ActionIcon> | ||
</Menu.Target> | ||
<Menu.Dropdown> | ||
<Menu.Label>Manage</Menu.Label> | ||
<Menu.Item | ||
color="red" | ||
leftSection={<IconUsersMinus style={{width: rem(14), height: rem(14)}}/>} | ||
onClick={() => | ||
ModificationModal({ | ||
handleAction: handleRemoval, | ||
title: "Remove friend", | ||
buttonConfirmText: "Remove", | ||
buttonConfirmColor: "red", | ||
childrenContent: ( | ||
<Text> | ||
Are you sure that you want to remove your friend {friend.login}? | ||
</Text> | ||
), | ||
}) | ||
} | ||
> | ||
Remove friend | ||
</Menu.Item> | ||
</Menu.Dropdown> | ||
</Menu> | ||
</Group> | ||
</Card> | ||
); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string | null>("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 ( | ||
<CenterContainer> | ||
<Card mih="90vh" w="50vw" component={ScrollArea}> | ||
|
||
<Box mah={totalPages > 1 ? 'calc(90dvh - 64px)' : ''}> | ||
<Title order={2}>Relations</Title> | ||
<Divider my="md"/> | ||
<Tabs | ||
value={activeTab} | ||
onChange={setActiveTab} | ||
> | ||
<Tabs.List> | ||
<Tabs.Tab value="sent" leftSection={<IconSend/>}> | ||
Sent | ||
</Tabs.Tab> | ||
<Tabs.Tab value="received" leftSection={<IconArrowDownLeft/>}> | ||
Received | ||
</Tabs.Tab> | ||
<Tabs.Tab value="rejected" leftSection={<IconCircleX/>}> | ||
Rejected | ||
</Tabs.Tab> | ||
</Tabs.List> | ||
|
||
<Box mb={'xl'}> | ||
<Tabs.Panel value="sent"> | ||
<UserGrid emptyMessage="No sent requests" sentRequests={data} isLoading={isLoading}/> | ||
</Tabs.Panel> | ||
<Tabs.Panel value="received"> | ||
<UserGrid | ||
emptyMessage="No received requests" | ||
isReceived | ||
sentRequests={data} | ||
isLoading={isLoading} | ||
handleAcceptRequest={handleAcceptRequest} | ||
handleCancelRequest={handleCancelRequest} | ||
/> | ||
</Tabs.Panel> | ||
<Tabs.Panel value="rejected"> | ||
<UserGrid emptyMessage="No rejected requests" sentRequests={data} isLoading={isLoading}/> | ||
</Tabs.Panel> | ||
</Box> | ||
</Tabs> | ||
</Box> | ||
|
||
{totalPages > 1 && | ||
<Box | ||
pos={'absolute'} | ||
bottom={0} | ||
left={0} | ||
right={0} | ||
py={'10px'} | ||
display={'flex'} | ||
style={{ | ||
zIndex: 10, | ||
justifyContent: "center", | ||
}} | ||
> | ||
<Pagination total={totalPages} value={currentPage} onChange={setCurrentPage}/> | ||
</Box> | ||
} | ||
</Card> | ||
</CenterContainer> | ||
); | ||
}; |
Oops, something went wrong.