Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Commit

Permalink
Merge remote-tracking branch 'origin/feature/friends' into feature/fr…
Browse files Browse the repository at this point in the history
…iends
  • Loading branch information
Ivan-Sai committed Mar 4, 2024
2 parents 169012d + d8fe188 commit c2262bb
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { IUserResponse } from '@teameights/types';
export const useGetUserByName = (username: string): { data: IUserResponse | undefined } => {
const users = useGetUsers(JSON.stringify({ username: username }));

return { data: users?.data?.pages[0]?.data[0] ?? undefined };
return { data: users?.data?.pages[0]?.data[0] };
};
1 change: 0 additions & 1 deletion client/src/app/(main)/[username]/profile/ui/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@ interface CardProps {

export const Card = ({ children, className }: CardProps) => {
const cls = clsx(styles.card, className);
console.log(cls);
return <div className={cls}>{children}</div>;
};
7 changes: 3 additions & 4 deletions client/src/app/(main)/[username]/profile/ui/fields/skills.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,25 @@ export const Skills = () => {
const { data: user } = useGetUserByName(username as string);
const skills = {
coreTools: {
badge: BadgeIcon,
badge: ({ data }: { data: string }) => <BadgeIcon data={data} isActive={true} />,
title: 'Core Tools',
},
additionalTools: {
badge: BadgeText,
title: 'Additional Tools',
},
};

return (
<Flex gap='24px' direction='column'>
{user?.skills &&
{user!.skills &&
Object.entries(skills).map(skill => {
const skillName = skill[0] as keyof typeof skills;
const Badge = skills[skillName].badge;
return (
<Flex key={skillName} direction='column' gap='8px'>
<Typography>{skills[skillName].title}</Typography>
<Flex wrap='wrap' gap='8px'>
{user?.skills?.coreTools.map((lang: string) => <Badge key={lang} data={lang} />)}
{user?.skills![skillName]?.map((lang: string) => <Badge key={lang} data={lang} />)}
</Flex>
</Flex>
);
Expand Down
23 changes: 16 additions & 7 deletions client/src/app/(main)/[username]/profile/ui/friends/friends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ArrowRightIcon } from '@/shared/assets';
import { useState } from 'react';
import layoutStyles from '../../layout.module.scss';
import { FriendsModal } from './friends-modal';
import { IUserBase } from '@teameights/types';

export const Friends = () => {
const { username } = useParams();
Expand All @@ -26,12 +27,20 @@ export const Friends = () => {
</Typography>
);
if (friendshipList.length) {
const noun = friendshipList.length === 1 ? 'friend' : 'friends';
const friendsList = friendshipList.map(friendship => {
const { receiver, creator } = friendship;
if (receiver.id !== user?.id) return receiver;
return creator;
});
const friendsList = friendshipList.reduce<IUserBase[]>((accumulator, friendship) => {
if (friendship.status === 'accepted') {
const { receiver, creator } = friendship;
const friend = receiver.id !== user?.id ? receiver : creator;

const existingFriendIndex = accumulator.findIndex(item => item?.id === friend.id);
// filter out duplicates, since there can be two similar friendships with different creators
if (existingFriendIndex === -1) {
accumulator.push(friend);
}
}
return accumulator;
}, []);
const noun = friendsList.length === 1 ? 'friend' : 'friends';
friendsContainer = (
<Flex direction='column'>
<FriendsModal
Expand All @@ -41,7 +50,7 @@ export const Friends = () => {
/>
<Flex align='center' justify='space-between' margin='0 0 21px 0'>
<Flex gap='5px'>
<Typography size='body_m'>{friendshipList.length}</Typography>
<Typography size='body_m'>{friendsList.length}</Typography>
<Typography size='body_m'>{noun}</Typography>
</Flex>
<button onClick={() => setFriendsModal(true)}>
Expand Down
33 changes: 4 additions & 29 deletions client/src/app/(main)/[username]/profile/ui/header/header.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
'use client';
import styles from './header.module.scss';
import { useGetFriends, useGetMe } from '@/entities/session';
import { ChatCircleDotsIcon, PlusIcon, UserPlusIcon } from '@/shared/assets';
import { useGetMe } from '@/entities/session';
import { ChatCircleDotsIcon, PlusIcon } from '@/shared/assets';
import { Button, CardSkeleton, Flex, ImageLoader, Typography } from '@/shared/ui';
import { useParams } from 'next/navigation';
import { useGetUserByName } from '../../lib/useGetUserByName';
import { useContext } from 'react';
import { ProfileContext } from '@/app/(main)/[username]/profile/lib/profile-context';
import { useAddFriend } from '@/entities/session/api/useAddFriend';
import { useRemoveFriend } from '@/entities/session/api/useRemoveFriend';
import { FriendButton } from '@/features/friend-button';
export const Header = () => {
const { username } = useParams();
const { data: me } = useGetMe();
const { data: user } = useGetUserByName(username as string);
const isMyProfile = useContext(ProfileContext);
const { mutate: addFriend } = useAddFriend(String(me?.id), String(user!.id));
const { mutate: removeFriend } = useRemoveFriend(String(user!.id));
const { data: friendships } = useGetFriends(user!.id);

const isMyFriend =
me &&
friendships?.data.some(
friendship => friendship.creator.id === me.id || friendship.receiver.id === me.id
);

if (!user) {
return <CardSkeleton width={'100%'} height={'248px'} borderRadius={15} />;
Expand All @@ -35,29 +25,14 @@ export const Header = () => {
</Button>
);

let friendButton = (
<Button onClick={() => addFriend()} size={'m'}>
Add friend
<UserPlusIcon />
</Button>
);

if (isMyFriend) {
friendButton = (
<Button onClick={() => removeFriend()} size={'m'} typeBtn='danger'>
Remove friend
</Button>
);
}

if (!isMyProfile) {
interactions = (
<Flex gap={'8px'}>
<Button typeBtn={'secondary'} size={'m'}>
Message
<ChatCircleDotsIcon />
</Button>
{friendButton}
<FriendButton myId={me?.id} userId={user.id} />
</Flex>
);
}
Expand Down
17 changes: 0 additions & 17 deletions client/src/entities/session/api/useAcceptFriend.tsx

This file was deleted.

9 changes: 7 additions & 2 deletions client/src/entities/session/api/useAddFriend.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API } from '@/shared/api';
import { API_FRIENDSHIP } from '@/shared/constant';
import { toast } from 'sonner';

export const useAddFriend = (userId: string, receiverId: string) => {
export const useAddFriend = (userId: number | undefined, receiverId: number) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () =>
await API.post(`${API_FRIENDSHIP}/${receiverId}?user={"id":"${userId}"}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['useGetFriends'] });
queryClient.invalidateQueries({ queryKey: ['useGetFriendshipStatus', receiverId] });

toast('Request is sent');
},
onError: err => {
console.log(err);
toast(`Error occurred: ${err}`);
},
});
Expand Down
2 changes: 1 addition & 1 deletion client/src/entities/session/api/useGetFriends.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const useGetFriends = (userId: number) => {
queryKey: ['useGetFriends', userId],
queryFn: async () => {
const { data } = await API.get<{ data: Array<IFriendshipResponse> }>(
`${API_FRIENDSHIP}/${userId}?filters={"status":"accepted"}`
`${API_FRIENDSHIP}/${userId}`
);
return data;
},
Expand Down
22 changes: 22 additions & 0 deletions client/src/entities/session/api/useGetFriendshipStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { API } from '@/shared/api';
import { API_FRIENDSHIP } from '@/shared/constant';

interface IFriendshipStatus {
status: 'none' | 'friends' | 'requested' | 'toRespond';
}

export const useGetFriendshipStatus = (id: number) => {
return useQuery({
queryKey: ['useGetFriendshipStatus', id],
queryFn: async () => {
const { data } = await API.get<IFriendshipStatus>(`${API_FRIENDSHIP}/status/${id}`);
return data;
},
refetchOnMount: false,
refetchOnWindowFocus: false,
retry: 1,
retryDelay: 5000,
});
};
30 changes: 30 additions & 0 deletions client/src/entities/session/api/useHandleFriendshipRequest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API } from '@/shared/api';
import { API_FRIENDSHIP } from '@/shared/constant';
import { toast } from 'sonner';

export const useHandleFriendshipRequest = (
userId: number | undefined,
receiverId: number,
status: 'rejected' | 'accepted'
) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () =>
await API.patch(`${API_FRIENDSHIP}/${receiverId}?user={"id":"${userId}"}`, {
status,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['useGetFriends'] });
queryClient.invalidateQueries({ queryKey: ['useGetFriendshipStatus', receiverId] });
if (status === 'accepted') {
toast('New friend added');
} else {
toast('Friend request declined');
}
},
onError: err => {
toast(`Error occurred: ${err}`);
},
});
};
7 changes: 5 additions & 2 deletions client/src/entities/session/api/useRemoveFriend.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API } from '@/shared/api';
import { API_FRIENDSHIP } from '@/shared/constant';
import { toast } from 'sonner';

export const useRemoveFriend = (userId: string) => {
export const useRemoveFriend = (userId: number) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => await API.delete(`${API_FRIENDSHIP}/${userId}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['useGetFriends'] });
queryClient.invalidateQueries({ queryKey: ['useGetFriendshipStatus', userId] });
toast('User is removed from the friends list');
},
onError: err => {
Expand Down
79 changes: 79 additions & 0 deletions client/src/features/friend-button/friend-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useAddFriend } from '@/entities/session/api/useAddFriend';
import { useRemoveFriend } from '@/entities/session/api/useRemoveFriend';
import { Button } from '@/shared/ui';
import { UserPlusIcon } from '@/shared/assets';
import { useHandleFriendshipRequest } from '@/entities/session/api/useHandleFriendshipRequest';
import { useGetFriendshipStatus } from '@/entities/session/api/useGetFriendshipStatus';

interface FriendButtonProps {
myId?: number;
userId: number;
short?: boolean;
size?: 'm' | 'l' | 's';
width?: string;
}

function getText(text: string, short: boolean) {
if (short) return text;
return text + ' friend';
}

export const FriendButton = ({
myId,
userId,
short = false,
size = 'm',
width,
}: FriendButtonProps) => {
const { mutate: addFriend } = useAddFriend(myId, userId);
const { mutate: removeFriend } = useRemoveFriend(userId);
const { mutate: declineFriend } = useHandleFriendshipRequest(myId, userId, 'rejected');
const { mutate: acceptFriend } = useHandleFriendshipRequest(myId, userId, 'accepted');
const isMyProfile = myId === userId;
const { data } = useGetFriendshipStatus(userId);

const friendStatus = data?.status;

if (!myId || isMyProfile) {
return null; // Hide friend button if user not logged in or it's their profile
}

switch (friendStatus) {
case 'none': {
return (
<Button width={width} onClick={() => addFriend()} size={size}>
{getText('Add', short)}
<UserPlusIcon />
</Button>
);
}
case 'requested': {
return (
<Button width={width} size={size} typeBtn='secondary'>
Pending
</Button>
);
}
case 'toRespond': {
return (
<>
<Button width={width} onClick={() => acceptFriend()} size={size} typeBtn='primary'>
{getText('Accept', short)}
</Button>
<Button width={width} onClick={() => declineFriend()} size={size} typeBtn='danger'>
{getText('Reject', short)}
</Button>
</>
);
}
case 'friends': {
return (
<Button width={width} onClick={() => removeFriend()} size={size} typeBtn='danger'>
{getText('Remove', short)}
</Button>
);
}
default:
return null;
}
};
1 change: 1 addition & 0 deletions client/src/features/friend-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FriendButton } from './friend-button';
Loading

0 comments on commit c2262bb

Please sign in to comment.