Skip to content

Commit 525c4a1

Browse files
authored
Merge pull request #50 from FrostCord/FROS-Friend-System
Friend System
2 parents 410d39b + 149a38d commit 525c4a1

20 files changed

Lines changed: 712 additions & 85 deletions

components/home/DMessageList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export default function DMessageList() {
2-
return <>Coming soon...</>;
2+
return <>DM List...</>;
33
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { acceptFriendRequest, removeFriendOrRequest } from '@/services/friends.service';
2+
import { DetailedProfileRelation } from '@/types/dbtypes';
3+
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
4+
import ChannelMessageIcon from '../icons/ChannelMessageIcon';
5+
import { CheckIcon } from '../icons/CheckIcon';
6+
import UserIcon from '../icons/UserIcon';
7+
import VerticalSettingsIcon from '../icons/VerticalSettingsIcon';
8+
import { XIcon } from '../icons/XIcon';
9+
10+
export function FriendRequestItem({ relation }: { relation: DetailedProfileRelation }) {
11+
const user = useUser();
12+
const supabase = useSupabaseClient();
13+
14+
return (
15+
<div key={relation.id} className="flex flex-row items-center space-x-3 p-2 w-full hover:bg-grey-900 rounded-md transition-colors">
16+
<UserIcon user={relation.target_profile} />
17+
<h1 className="text-xl font-semibold tracking-wide flex-grow">
18+
{relation.target_profile.username}
19+
</h1>
20+
<div className='flex flex-row space-x-2'>
21+
{relation.initiator_profile_id !== user?.id && (
22+
<button
23+
className="rounded-md p-2 border-2 border-gray-500 hover:bg-gray-500"
24+
onClick={async () => {
25+
const { error } = await acceptFriendRequest(supabase, relation.id);
26+
if (error) {
27+
console.error(error);
28+
}
29+
}}
30+
>
31+
<CheckIcon className='fill-green-400' />
32+
</button>
33+
)}
34+
<button
35+
className="rounded-md p-2 border-2 border-gray-500 hover:bg-gray-500"
36+
onClick={async () => {
37+
const { error } = await removeFriendOrRequest(supabase, relation.id);
38+
39+
if (error) {
40+
console.error(error);
41+
}
42+
}}
43+
>
44+
<XIcon className='fill-red-400' />
45+
</button>
46+
</div>
47+
</div>
48+
);
49+
}

components/home/FriendsList.tsx

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,71 @@
1+
import { useRelations } from '@/lib/store';
2+
import styles from '@/styles/Chat.module.css';
3+
import { DetailedProfileRelation, ProfileRelationshipType } from '@/types/dbtypes';
4+
import { useSupabaseClient } from '@supabase/auth-helpers-react';
5+
import { useEffect, useState } from 'react';
6+
import { FriendRequestItem } from './FriendRequestItem';
7+
import { FriendsListItem } from './FriendsListItem';
8+
19
export default function FriendsList() {
2-
//TODO: when user clicks on friend, set viewCtx to chat, set chatCtx to api route of the requested chat
3-
return <>Coming soon...</>;
10+
const [ activeCategory, setActiveCategory ] = useState<ProfileRelationshipType>('friends');
11+
const relationships = useRelations();
12+
const [ dispRelations, setDispRelations ] = useState<DetailedProfileRelation[]>([]);
13+
14+
useEffect(() => {
15+
console.log(activeCategory);
16+
setDispRelations(
17+
relationships.filter(
18+
(relation) => relation.relationship === activeCategory
19+
)
20+
);
21+
}, [activeCategory, relationships]);
22+
23+
return (
24+
<>
25+
<div className={`${styles.chatHeader} px-5 pt-5 mb-3`}>
26+
<div className="flex flex-row items-center space-x-3">
27+
<h1 className="text-3xl font-semibold tracking-wide">
28+
Friends
29+
</h1>
30+
<h1 className='text-gray-500 text-2xl'>|</h1>
31+
<button
32+
className="rounded-md p-1 px-3 border-2 border-gray-500 hover:bg-gray-500"
33+
onClick={() => setActiveCategory('friends')}
34+
style={{
35+
backgroundColor: activeCategory === 'friends' ? '#8a94a6' : '',
36+
borderColor: activeCategory === 'friends' ? '#8a94a6' : '',
37+
}}
38+
>
39+
All
40+
</button>
41+
<button
42+
className="rounded-md p-1 px-3 border-2 border-gray-500 hover:bg-gray-500"
43+
onClick={() => setActiveCategory('friend_requested')}
44+
style={{
45+
backgroundColor: activeCategory === 'friend_requested' ? '#8a94a6' : '',
46+
borderColor: activeCategory === 'friend_requested' ? '#8a94a6' : '',
47+
}}
48+
>
49+
Pending
50+
</button>
51+
{/* <button
52+
className="rounded-md p-1 px-3 border-2 border-gray-500 hover:bg-gray-500"
53+
onClick={() => setActiveCategory('blocked')}
54+
style={{
55+
backgroundColor: activeCategory === 'blocked' ? '#8a94a6' : '',
56+
borderColor: activeCategory === 'blocked' ? '#8a94a6' : '',
57+
}}
58+
>
59+
Blocked
60+
</button> */}
61+
</div>
62+
</div>
63+
<div className="border-t-2 mx-5 border-grey-700 flex flex-col">
64+
{dispRelations.map((relation) => (
65+
relation.relationship === 'friends' ?
66+
<FriendsListItem key={relation.id} relation={relation} /> : <FriendRequestItem key={relation.id} relation={relation} />
67+
))}
68+
</div>
69+
</>
70+
);
471
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { DetailedProfileRelation } from '@/types/dbtypes';
2+
import ChannelMessageIcon from '../icons/ChannelMessageIcon';
3+
import UserIcon from '../icons/UserIcon';
4+
import VerticalSettingsIcon from '../icons/VerticalSettingsIcon';
5+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
6+
import { removeFriendOrRequest } from '@/services/friends.service';
7+
import { useSupabaseClient } from '@supabase/auth-helpers-react';
8+
9+
export function FriendsListItem({ relation }: { relation: DetailedProfileRelation }) {
10+
const supabase = useSupabaseClient();
11+
return (
12+
<div key={relation.id} className="flex flex-row items-center space-x-3 p-2 w-full hover:bg-grey-900 rounded-md transition-colors">
13+
<UserIcon user={relation.target_profile} />
14+
<h1 className="text-xl font-semibold tracking-wide flex-grow">
15+
{relation.target_profile.username}
16+
</h1>
17+
<div className='flex flex-row space-x-2'>
18+
<button
19+
className="rounded-md p-3 border-2 border-gray-500 hover:bg-gray-500"
20+
onClick={() => {
21+
console.log('DM');
22+
}}
23+
>
24+
<ChannelMessageIcon />
25+
</button>
26+
<DropdownMenu.Root>
27+
<DropdownMenu.Trigger asChild>
28+
<button
29+
className="rounded-md p-2 border-2 border-gray-500 hover:bg-gray-500"
30+
onClick={() => {
31+
console.log('Options');
32+
}}
33+
>
34+
<VerticalSettingsIcon />
35+
</button>
36+
</DropdownMenu.Trigger>
37+
<DropdownMenu.Content className='ContextMenuContent' side='left'>
38+
<DropdownMenu.Item
39+
className='ContextMenuItem'
40+
onClick={async () => await removeFriendOrRequest(supabase, relation.id)}
41+
>
42+
Remove friend
43+
</DropdownMenu.Item>
44+
</DropdownMenu.Content>
45+
</DropdownMenu.Root>
46+
</div>
47+
</div>
48+
);
49+
}

components/home/MessageHeader.tsx

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { Role, ServerUser, User } from '@/types/dbtypes';
22
import { Tooltip } from 'react-tooltip';
33
import { MiniProfile } from '../forms/MiniProfile';
44
import UserIcon from '../icons/UserIcon';
5+
import * as ContextMenu from '@radix-ui/react-context-menu';
6+
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
7+
import { acceptFriendRequest, removeFriendOrRequest, sendFriendRequest } from '@/services/friends.service';
8+
import { useRelations } from '@/lib/store';
59

610
export function MessageHeader({
711
profile,
@@ -20,38 +24,83 @@ export function MessageHeader({
2024
edited: boolean;
2125
roles: Role[];
2226
}) {
27+
const currentUser = useUser();
28+
const supabase = useSupabaseClient();
29+
const relation = useRelations().find((relation) => relation.target_profile.id === profile.id);
30+
2331
return (
24-
<div className="flex-grow flex flex-row">
25-
<Tooltip
26-
id={profile.id}
27-
className="z-20 !w-12 !opacity-100 "
28-
style={{ backgroundColor: '#21282b', borderRadius: '0.5rem' }}
29-
clickable
30-
noArrow
31-
>
32-
<MiniProfile
33-
profile={profile}
34-
server_user={server_user}
35-
roles={roles}
36-
/>
37-
</Tooltip>
38-
<UserIcon user={profile} />
39-
<div className="flex-grow flex items-center">
40-
<div
41-
className="text-xl font-semibold tracking-wider mr-2"
42-
data-tooltip-id={profile.id}
43-
style={{
44-
// Check for the first role that has a non-null color and use that
45-
color: message_color,
46-
}}
47-
>
48-
{server_user.nickname || profile.username}
49-
</div>
50-
<div className="text-xs tracking-wider text-grey-300 mt-1">
51-
{display_time}{' '}
52-
{edited && <span className="text-gray-400">(edited)</span>}
32+
<ContextMenu.Root>
33+
<ContextMenu.Trigger asChild>
34+
<div className="flex-grow flex flex-row">
35+
<Tooltip
36+
id={profile.id}
37+
className="z-20 !w-12 !opacity-100 "
38+
style={{ backgroundColor: '#21282b', borderRadius: '0.5rem' }}
39+
clickable
40+
noArrow
41+
>
42+
<MiniProfile
43+
profile={profile}
44+
server_user={server_user}
45+
roles={roles}
46+
/>
47+
</Tooltip>
48+
<UserIcon user={profile} />
49+
<div className="flex-grow flex items-center">
50+
<div
51+
className="text-xl font-semibold tracking-wider mr-2"
52+
data-tooltip-id={profile.id}
53+
style={{
54+
// Check for the first role that has a non-null color and use that
55+
color: message_color,
56+
}}
57+
>
58+
{server_user.nickname || profile.username}
59+
</div>
60+
<div className="text-xs tracking-wider text-grey-300 mt-1">
61+
{display_time}{' '}
62+
{edited && <span className="text-gray-400">(edited)</span>}
63+
</div>
64+
</div>
5365
</div>
54-
</div>
55-
</div>
66+
</ContextMenu.Trigger>
67+
{ currentUser?.id !== profile.id && (
68+
<ContextMenu.Content className='ContextMenuContent'>
69+
<ContextMenu.Item
70+
className='ContextMenuItem'
71+
onClick={() => {
72+
sendFriendRequest(supabase, profile.id);
73+
}}
74+
hidden={!!relation}
75+
>
76+
Send friend request
77+
</ContextMenu.Item>
78+
<ContextMenu.Item
79+
className='ContextMenuItem'
80+
onClick={() => {
81+
// NOTE: Since this is only shown if a relation exists, therefore we can assume relation is not undefined
82+
acceptFriendRequest(supabase, relation!.id);
83+
}}
84+
hidden={!relation || relation.relationship !== 'friend_requested' || relation.initiator_profile_id === currentUser?.id}
85+
>
86+
Accept friend request
87+
</ContextMenu.Item>
88+
<ContextMenu.Item
89+
className='ContextMenuItem'
90+
onClick={() => {
91+
// NOTE: Since this is only shown if a relation exists, therefore we can assume relation is not undefined
92+
removeFriendOrRequest(supabase, relation!.id);
93+
}}
94+
hidden={!relation}
95+
>
96+
{
97+
relation && relation.relationship === 'friend_requested' ?
98+
(relation.initiator_profile_id === currentUser?.id ? 'Cancel friend request' : 'Reject friend request')
99+
: 'Remove friend'
100+
}
101+
</ContextMenu.Item>
102+
</ContextMenu.Content>
103+
)}
104+
</ContextMenu.Root>
56105
);
57106
}

components/home/PresenceIndicator.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { useOnlinePresenceRef } from '@/context/ChatCtx';
1+
import { useOnlineUsers } from '@/lib/store';
22

33
export function PresenceIndicator({ userId }: { userId: string }) {
4-
const channel = useOnlinePresenceRef()!;
4+
const onlineUsers = useOnlineUsers();
55

66
return (
77
<div
@@ -12,7 +12,7 @@ export function PresenceIndicator({ userId }: { userId: string }) {
1212
h-4
1313
rounded-full
1414
z-10
15-
${channel.presenceState()[userId] ? 'bg-green-600' : 'bg-slate-500'}
15+
${onlineUsers[userId] ? 'bg-green-600' : 'bg-slate-500'}
1616
border-2
1717
-mt-3
1818
mr-2

components/home/RenderDesktopView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export function renderContent(
3636
) {
3737
switch (sideBarOption) {
3838
case 'friends':
39-
if (channel) return [<FriendsList key={1} />, <Chat key={2} />];
40-
return [<FriendsList key={1} />, <DefaultTest key={2} />];
39+
if (channel) return [<DMessageList key={1} />, <Chat key={2} />];
40+
return [<DMessageList key={1} />, <FriendsList key={2} />];
4141
case 'servers':
4242
if (channel) return [<ServerList key={1} />, <Chat key={2} />];
4343
return [<ServerList key={1} />, <DefaultTest key={2} />];

components/home/Server.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
useEffect,
88
useState,
99
} from 'react';
10-
import { useOnlinePresenceRef } from '@/context/ChatCtx';
1110
import { getChannelsInServer } from '@/services/channels.service';
1211
import ServersIcon from '../icons/ServersIcon';
1312
import { useSupabaseClient } from '@supabase/auth-helpers-react';
@@ -32,15 +31,7 @@ export default function Server({
3231
}) {
3332
const expand = expanded == server.id;
3433
const supabase = useSupabaseClient();
35-
const [memberCount, setMemberCount] = useState(0);
36-
const [onlineCount, setOnlineCount] = useState(0);
37-
const onlinePresenceChannel = useOnlinePresenceRef();
38-
39-
const [isChannelHovered, setIsChannelHovered] = useState(false);
40-
const [isServerHovered, setIsServerHovered] = useState(false);
4134
const [isSettingsHovered, setIsSettingsHovered] = useState(false);
42-
const [isSettingsClicked, setIsSettingsClicked] = useState(false);
43-
4435
const setChannel = useSetChannel();
4536
const [channels, setChannels] = useState<Channel[]>([]);
4637

components/home/ServerMemberStats.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useOnlinePresenceRef } from '@/context/ChatCtx';
1+
import { useOnlineUsers } from '@/lib/store';
22
import { getServerMemberCount, getUsersInServer } from '@/services/server.service';
33
import styles from '@/styles/Servers.module.css';
44
import { Server } from '@/types/dbtypes';
@@ -8,8 +8,8 @@ import { useEffect, useState } from 'react';
88
export function ServerMemberStats({ server, flexStyle }: { server: Server, flexStyle?: string }) {
99
const [ memberCount, setMemberCount ] = useState(0);
1010
const [ onlineCount, setOnlineCount ] = useState(0);
11-
const onlinePresenceChannel = useOnlinePresenceRef();
1211
const supabase = useSupabaseClient();
12+
const onlineUsers = useOnlineUsers();
1313

1414
useEffect(() => {
1515
async function handleAsync() {
@@ -19,19 +19,19 @@ export function ServerMemberStats({ server, flexStyle }: { server: Server, flexS
1919
// Now we need to get the online count
2020
const { data: onlineData, error } = await getUsersInServer(supabase, server.id);
2121

22-
let onlineUsers = 0;
22+
let amtOnlineUsers = 0;
2323
if (!error) {
2424
for (const profile of onlineData) {
25-
if (onlinePresenceChannel.presenceState()[profile.id] !== undefined) {
26-
onlineUsers++;
25+
if (onlineUsers[profile.id] !== undefined) {
26+
amtOnlineUsers++;
2727
}
2828
}
29-
setOnlineCount(onlineUsers);
29+
setOnlineCount(amtOnlineUsers);
3030
}
3131
}
3232

3333
handleAsync();
34-
}, [supabase, onlinePresenceChannel, server.id]);
34+
}, [supabase, onlineUsers, server.id]);
3535

3636
return (
3737
<div className={`text-xs tracking-wide text-grey-300 ${flexStyle}`}>

0 commit comments

Comments
 (0)