Skip to content

Commit

Permalink
Merge pull request #96 from JimTheCat/CU-8697aw9wp_Integrate-relation…
Browse files Browse the repository at this point in the history
…s-with-backend_Patryk-Kosiski

Cu 8697aw9wp integrate relations with backend patryk kosiski
  • Loading branch information
JimTheCat authored Jan 6, 2025
2 parents 7102085 + 8e94f15 commit f07c3ee
Show file tree
Hide file tree
Showing 17 changed files with 640 additions and 161 deletions.
16 changes: 0 additions & 16 deletions frontend/src/Features/Following/Following.tsx

This file was deleted.

1 change: 0 additions & 1 deletion frontend/src/Features/Following/index.tsx

This file was deleted.

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>
);
}
};
2 changes: 1 addition & 1 deletion frontend/src/Features/MainPage/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
141 changes: 141 additions & 0 deletions frontend/src/Features/Relations/Relations.tsx
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>
);
};
Loading

0 comments on commit f07c3ee

Please sign in to comment.