Skip to content

Commit b35bc37

Browse files
authored
Merge pull request #61 from FrostCord/FROS-Role-Assignment
Role Assignment and Other Optimizations
2 parents 9e42cf9 + 79f49fe commit b35bc37

20 files changed

Lines changed: 696 additions & 285 deletions

components/forms/MiniProfile.tsx

Lines changed: 134 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,175 @@
11
import { useSideBarOptionSetter } from '@/context/SideBarOptionCtx';
22
import { getOrCreateDMChannel } from '@/lib/DMChannelHelper';
3-
import { useDMChannels, useSetChannel } from '@/lib/store';
3+
import {
4+
useChannel,
5+
useDMChannels,
6+
useGetAllServerUserProfiles,
7+
useServerRoles,
8+
useServerUserProfileHighestRolePosition,
9+
useServerUserProfilePermissions,
10+
useSetChannel
11+
} from '@/lib/store';
412
import { createMessage } from '@/services/message.service';
5-
import { Role, ServerUser, User } from '@/types/dbtypes';
13+
import { ServerUserProfile } from '@/types/dbtypes';
614
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
7-
import { memo, } from 'react';
815
import UserIcon from '../icons/UserIcon';
916
import { SearchBar } from './Styles';
17+
import { ServerPermissions } from '@/types/permissions';
18+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
19+
import { grantRoleToUser, revokeRoleFromUser } from '@/services/roles.service';
20+
import { toast } from 'react-toastify';
21+
import { XIcon } from '@/components/icons/XIcon';
1022

11-
// TODO: Once DMs are implemented, this component will need to be updated to handle DMs as well.
12-
function WrappedComponent({
13-
profile,
14-
server_user,
15-
roles,
16-
}: {
17-
profile: User;
18-
server_user: ServerUser;
19-
roles: Role[];
20-
}) {
23+
24+
export function MiniProfile({ server_user_profile }: { server_user_profile: ServerUserProfile }) {
2125
const user = useUser();
2226
const supabase = useSupabaseClient();
2327
const dmChannels = useDMChannels();
2428
const setChannel = useSetChannel();
2529
const setSideBarOption = useSideBarOptionSetter();
30+
const channel = useChannel();
31+
32+
const serverRoles = useServerRoles(channel!.server_id!);
33+
const userHighestRole = useServerUserProfileHighestRolePosition(
34+
server_user_profile.server_user.server_id,
35+
user!.id
36+
);
37+
const currentUserPerms = useServerUserProfilePermissions(
38+
server_user_profile.server_user.server_id,
39+
user!.id
40+
);
41+
42+
const getAllServerProfiles = useGetAllServerUserProfiles();
43+
44+
const filteredRoles = serverRoles
45+
.filter(
46+
role => (
47+
role.position > userHighestRole
48+
&& role.position !== serverRoles.length - 1
49+
&& !server_user_profile.roles.some(r => r.id === role.id)
50+
)
51+
);
2652

2753
return (
2854
<div className="flex flex-col space-y-2 items-center p-3">
29-
<UserIcon user={profile} className="!w-9 !h-9" />
30-
{server_user.nickname && (
31-
<h2 className="text-lg">{server_user.nickname}</h2>
55+
<UserIcon user={server_user_profile} className="!w-9 !h-9" />
56+
{server_user_profile.server_user.nickname && (
57+
<h2 className="text-lg">{server_user_profile.server_user.nickname}</h2>
3258
)}
33-
<h2 className={server_user.nickname ? 'text-base' : 'text-lg'}>
34-
{profile.username}
59+
<h2 className={server_user_profile.server_user.nickname ? 'text-base' : 'text-lg'}>
60+
{server_user_profile.username}
3561
</h2>
3662
<hr />
3763
<h2 className="text-sm text-gray-400 text-left w-full">Roles</h2>
64+
3865
<div className="flex flex-col w-full items-center space-y-1">
39-
{roles.map((role) => (
66+
{server_user_profile.roles.map((role) => (
4067
<span
4168
key={role.id}
42-
className="text-sm text-gray-400 py-1 px-2 border-2 border-solid w-full rounded-sm"
69+
className="text-sm text-gray-400 py-1 px-2 border-2 border-solid w-full rounded-sm flex flex-row items-center"
4370
style={{
4471
border: `1px solid #${!!role.color ? role.color : 'cacacacc'}`,
4572
color: `#${!!role.color ? role.color : 'cacacacc'}`,
4673
}}
4774
>
48-
{role.name}
75+
<p className="flex-grow">{role.name}</p>
76+
{(
77+
(currentUserPerms & ServerPermissions.MANAGE_ROLES) === ServerPermissions.MANAGE_ROLES
78+
&& role.position > userHighestRole
79+
&& role.position !== serverRoles.length - 1
80+
) && (
81+
<button
82+
type="button"
83+
className="align-middle self-end"
84+
style={{
85+
fill: `#${!!role.color ? role.color : 'cacacacc'}`,
86+
}}
87+
onClick={async () => {
88+
const { error } = await revokeRoleFromUser(
89+
supabase,
90+
role.id,
91+
server_user_profile.server_user.id,
92+
);
93+
94+
if (error) {
95+
console.error(error);
96+
toast.error('Failed to revoke role from user');
97+
return;
98+
}
99+
100+
toast.success('Role revoked from user');
101+
}}
102+
>
103+
<XIcon className="w-4 h-4" />
104+
</button>
105+
)}
49106
</span>
50107
))}
108+
109+
{(
110+
(currentUserPerms & ServerPermissions.MANAGE_ROLES) === ServerPermissions.MANAGE_ROLES
111+
&& filteredRoles.length > 0
112+
) && (
113+
<DropdownMenu.Root>
114+
<DropdownMenu.Trigger
115+
asChild
116+
>
117+
<button
118+
type="button"
119+
className="text-sm text-gray-400 py-1 px-2 border-2 border-solid w-full rounded-sm text-center"
120+
style={{
121+
border: '1px solid #cacacacc'
122+
}}
123+
>
124+
+
125+
</button>
126+
</DropdownMenu.Trigger>
127+
<DropdownMenu.Content
128+
side="right"
129+
className="ContextMenuContent"
130+
>
131+
{filteredRoles.map((role) => (
132+
<DropdownMenu.Item
133+
key={role.id}
134+
className="ContextMenuItem"
135+
style={{
136+
color: `#${!!role.color ? role.color : 'cacacacc'}`,
137+
}}
138+
onSelect={async () => {
139+
const { error } = await grantRoleToUser(
140+
supabase,
141+
role.id,
142+
server_user_profile.server_user.id,
143+
);
144+
145+
if (error) {
146+
console.error(error);
147+
toast.error('Failed to grant role to user');
148+
return;
149+
}
150+
151+
toast.success('Role granted to user');
152+
}}
153+
>
154+
{role.name}
155+
</DropdownMenu.Item>
156+
))}
157+
</DropdownMenu.Content>
158+
</DropdownMenu.Root>
159+
)}
51160
</div>
52161
<hr />
53-
{profile.id !== user?.id && (<form>
162+
{server_user_profile.id !== user?.id && (<form>
54163
<input
55164
type="text"
56165
className={`${SearchBar('bg-grey-900')}`}
57-
placeholder={`Message ${profile.username}`}
166+
placeholder={`Message ${server_user_profile.username}`}
58167
onKeyDown={async (e) => {
59168
if (e.key === 'Enter') {
60169
e.preventDefault();
61170
const dmChannel = await getOrCreateDMChannel(
62171
supabase,
63-
profile,
172+
server_user_profile,
64173
dmChannels
65174
);
66175

@@ -74,6 +183,7 @@ function WrappedComponent({
74183
}
75184
);
76185

186+
getAllServerProfiles(supabase, dmChannel.server_id);
77187
setSideBarOption('friends');
78188
setChannel(dmChannel);
79189
}
@@ -84,5 +194,3 @@ function WrappedComponent({
84194
</div>
85195
);
86196
}
87-
88-
export const MiniProfile = memo(WrappedComponent);

components/forms/RoleEditForm.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { useUserHighestRolePosition, useUserServerPerms } from '@/lib/store';
1+
import { useServerUserProfilePermissions } from '@/lib/store';
22
import { decrementRolePosition, deleteRole, incrementRolePosition, updateRole } from '@/services/roles.service';
33
import { Role } from '@/types/dbtypes';
44
import { ServerPermissions } from '@/types/permissions';
5-
import { useSupabaseClient } from '@supabase/auth-helpers-react';
5+
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
66
import { useForm } from 'react-hook-form';
77
import { toast } from 'react-toastify';
88

@@ -28,10 +28,9 @@ type RoleEditFormResult = {
2828
export function RoleEditForm({role, roles_length, max_role_position }: {role: Role, roles_length: number, max_role_position: number }) {
2929
const { register, handleSubmit, formState: { errors } } = useForm<RoleEditFormResult>();
3030
const supabase = useSupabaseClient();
31-
const userPerms = useUserServerPerms();
32-
const userHighestRolePosition = max_role_position; // useUserHighestRolePosition();
33-
34-
const isFormDisabled = role.is_system || role.position <= userHighestRolePosition;
31+
const user = useUser();
32+
const userPerms = useServerUserProfilePermissions(role.server_id, user?.id!);
33+
const isFormDisabled = role.is_system || role.position <= max_role_position;
3534
const serverPermissions = Object.entries(ServerPermissions).filter(
3635
([key, value]) => typeof value === 'number' && key !== 'NONE' && key !== 'OWNER'
3736
);
@@ -96,7 +95,7 @@ export function RoleEditForm({role, roles_length, max_role_position }: {role: Ro
9695
console.log('up');
9796
await incrementRolePosition(supabase, role.id);
9897
}}
99-
disabled={isFormDisabled || role.position === 1 || role.position - 1 <= userHighestRolePosition}
98+
disabled={isFormDisabled || role.position === 1 || role.position - 1 <= max_role_position}
10099
>
101100
Up
102101
</button>

components/home/Chat.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import type {
99
} from '@/types/dbtypes';
1010
import { createMessage } from '@/services/message.service';
1111
import Message from '@/components/home/Message';
12-
import { useChannel, useMessages, useUserPerms } from '@/lib/store';
12+
import { useChannel, useMessages, useServerUserProfilePermissions, useUserPerms } from '@/lib/store';
1313
import { Channel } from '@/types/dbtypes';
1414
import { ChannelMediaIcon } from '@/components/icons/ChannelMediaIcon';
15-
import { ChannelPermissions } from '@/types/permissions';
15+
import { ChannelPermissions, ServerPermissions } from '@/types/permissions';
1616
import MobileCallControls from './mobile/MobileCallControls';
1717
import { useConnectionState } from '@livekit/components-react';
1818
import { ConnectionState } from 'livekit-client';
@@ -24,6 +24,7 @@ export default function Chat() {
2424
const messages = useMessages();
2525
const channel = useChannel();
2626
const userPerms = useUserPerms();
27+
const serverPermissions = useServerUserProfilePermissions(channel?.server_id!, user?.id!);
2728
const connectionState = useConnectionState();
2829

2930
useEffect(() => {
@@ -60,7 +61,7 @@ export default function Chat() {
6061
{messages &&
6162
messages.map((value, index: number, array) => {
6263
// Get the previous message, if the authors are the same, we don't need to repeat the header (profile picture, name, etc.)
63-
const previousMessage: MessageWithServerProfile | null =
64+
const previousMessage: MessageType | null =
6465
index > 0 ? array[index - 1] : null;
6566

6667
return (
@@ -69,10 +70,10 @@ export default function Chat() {
6970
message={value}
7071
collapse_user={
7172
!!previousMessage &&
72-
previousMessage.profile.id === value.profile.id
73+
previousMessage.profile_id === value.profile_id
7374
}
7475
hasDeletePerms={
75-
(userPerms & ChannelPermissions.MANAGE_MESSAGES) !== 0
76+
(serverPermissions & ServerPermissions.MANAGE_MESSAGES) !== 0
7677
}
7778
/>
7879
);

components/home/DMessageList.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { Channel, DMChannelWithRecipient } from '@/types/dbtypes';
22
import UserIcon from '../icons/UserIcon';
33
import styles from '@/styles/Chat.module.css';
4-
import { useChannel, useConnectionRef, useDMChannels, useSetChannel } from '@/lib/store';
4+
import { useChannel, useConnectionRef, useDMChannels, useSetChannel, useGetAllServerUserProfiles } from '@/lib/store';
55
import SidebarCallControl from '@/components/home/SidebarCallControl';
66
import { SearchBar } from '@/components/forms/Styles';
77
import { useState } from 'react';
8+
import { Database } from '@/types/database.supabase';
9+
import { SupabaseClient } from '@supabase/supabase-js';
10+
import { useSupabaseClient } from '@supabase/auth-helpers-react';
811

912
function mapToComponentArray(
1013
_map: Map<string, DMChannelWithRecipient>,
1114
setChannel: (channel: Channel) => void,
12-
channel: Channel | null
15+
channel: Channel | null,
16+
getAllServerUserProfiles: (supabase: SupabaseClient<Database>, server_id: number) => void,
17+
supabase: SupabaseClient<Database>,
1318
) {
1419
const rv = [];
1520

@@ -23,6 +28,7 @@ function mapToComponentArray(
2328
: 'hover:bg-grey-700'
2429
} flex items-center p-2 w-full rounded-md transition-colors hover:cursor-pointer mt-2`}
2530
onClick={() => {
31+
getAllServerUserProfiles(supabase, value.server_id);
2632
setChannel({
2733
channel_id: value.channel_id,
2834
server_id: value.server_id,
@@ -42,10 +48,12 @@ function mapToComponentArray(
4248
return rv;
4349
}
4450
export default function DMessageList() {
51+
const supabase = useSupabaseClient();
4552
const setChannel = useSetChannel();
4653
const dmChannels = useDMChannels();
4754
const channel = useChannel();
4855
const isInVoice = useConnectionRef();
56+
const getAllServerProfiles = useGetAllServerUserProfiles();
4957
const [ filteredDMs, setFilteredDMs ] = useState(dmChannels);
5058

5159
return (
@@ -78,7 +86,13 @@ export default function DMessageList() {
7886
}}
7987
/>
8088
</div>
81-
{ mapToComponentArray(dmChannels, setChannel, channel) }
89+
{ mapToComponentArray(
90+
filteredDMs,
91+
setChannel,
92+
channel,
93+
getAllServerProfiles,
94+
supabase,
95+
)}
8296
</div>
8397
{ isInVoice && (
8498
<div className="w-full self-end p-4 mb-7">

components/home/DeleteMsgModal.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import { useSupabaseClient } from '@supabase/auth-helpers-react';
66
import styles from '@/styles/Components.module.css';
77
import { useEffect } from 'react';
88
import Modal from '@/components/home/modals/Modal';
9-
import { MessageWithServerProfile } from '@/types/dbtypes';
9+
import { Message, MessageWithServerProfile, ServerUserProfile } from '@/types/dbtypes';
1010

1111
export default function DeleteMsgModal({
1212
showModal,
1313
message,
14+
server_user_profile,
1415
displayTime,
1516
setMessageOptions,
1617
}: {
1718
showModal: boolean;
18-
message: MessageWithServerProfile;
19+
message: Message;
20+
server_user_profile: ServerUserProfile;
1921
displayTime: string;
2022
setMessageOptions: Dispatch<SetStateAction<'delete' | 'edit' | null>>;
2123
}) {
@@ -54,10 +56,10 @@ export default function DeleteMsgModal({
5456
>
5557
<>
5658
<div className="flex-grow flex flex-row">
57-
<UserIcon user={message.profile} />
59+
<UserIcon user={server_user_profile} />
5860
<div className="flex-grow flex items-center">
5961
<div className="text-xl font-semibold tracking-wider mr-2">
60-
{message.profile.username}
62+
{server_user_profile.username}
6163
</div>
6264
<div className="text-xs tracking-wider text-grey-300 mt-1">
6365
{displayTime}{' '}

0 commit comments

Comments
 (0)