Skip to content

Commit

Permalink
feature: Create relations menu
Browse files Browse the repository at this point in the history
- Correct some CSS stuff
- Make more responsive menu.

Refs: 8697aw9wp
Signed-off-by: Patryk Kłosiński <[email protected]>
  • Loading branch information
JimTheCat committed Jan 6, 2025
1 parent bb86d36 commit 8e94f15
Show file tree
Hide file tree
Showing 8 changed files with 473 additions and 148 deletions.
89 changes: 52 additions & 37 deletions frontend/src/Features/Friends/Friends.tsx
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.
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>
);
}
};
43 changes: 42 additions & 1 deletion frontend/src/Features/Relations/Relations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {Box, Card, Divider, Pagination, ScrollArea, Tabs, Title} from "@mantine/
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);
Expand All @@ -36,6 +38,38 @@ export const Relations = () => {
}
};

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);
Expand Down Expand Up @@ -69,7 +103,14 @@ export const Relations = () => {
<UserGrid emptyMessage="No sent requests" sentRequests={data} isLoading={isLoading}/>
</Tabs.Panel>
<Tabs.Panel value="received">
<UserGrid emptyMessage="No received requests" sentRequests={data} isLoading={isLoading}/>
<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}/>
Expand Down
41 changes: 33 additions & 8 deletions frontend/src/Features/Relations/components/UserGrid/UserGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,56 @@
import {Card, Loader, SimpleGrid, Stack} from "@mantine/core";
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 = ({sentRequests, isLoading, emptyMessage}: UserGridProps) => {
export const UserGrid = (props: UserGridProps) => {

return (
<>
{isLoading &&
{props.isLoading &&
<Stack align="center" justify="center">
<Loader/>
</Stack>
}
{!isLoading && sentRequests.length === 0 &&
{!props.isLoading && props.sentRequests.length === 0 &&
<Stack py={'lg'} align="center" justify="center">
{emptyMessage}
{props.emptyMessage}
</Stack>
}
{!isLoading && sentRequests.length > 0 &&
{!props.isLoading && props.sentRequests.length > 0 &&
<SimpleGrid cols={2} mt={'md'}>
{sentRequests.map((person) => (
{props.sentRequests.map((person) => (
<Card withBorder key={person.id}>
<BasicUser user={person} tagVisible avatarSize={'lg'}/>
<Group justify={'space-between'} align={'center'}>
<BasicUser user={person} tagVisible avatarSize={'lg'}/>
{props.isReceived &&
<Group>
<Tooltip label={'Cancel request'}>
<ActionIcon variant={'subtle'} color={'gray'} size="lg" radius={'lg'} onClick={() => {
props.handleCancelRequest && props.handleCancelRequest(person.login);
}}>
<IconXboxX stroke={0.8}/>
</ActionIcon>
</Tooltip>
<Tooltip label={'Accept request'}>
<ActionIcon variant={'subtle'} color={'gray'} size="lg" radius={'lg'} onClick={() => {
props.handleAcceptRequest && props.handleAcceptRequest(person.login);
}}>
<IconCircleCheck stroke={0.8}/>
</ActionIcon>
</Tooltip>
</Group>
}
</Group>
</Card>
))}
</SimpleGrid>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Features/Root/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
Loading

0 comments on commit 8e94f15

Please sign in to comment.