Skip to content

Commit 386f6c7

Browse files
authored
Merge pull request #33 from FrostCord/FROS-Mini-Profiles
Mini Profiles
2 parents fc77617 + 75b7025 commit 386f6c7

17 files changed

Lines changed: 3593 additions & 81 deletions

components/forms/MiniProfile.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { getServerProfileForUser } from '@/services/profile.service';
2+
import { getServerIdFromMessageId } from '@/services/server.service';
3+
import { Role, ServerUser, ServerUserProfile, User } from '@/types/dbtypes';
4+
import { useSupabaseClient } from '@supabase/auth-helpers-react';
5+
import { memo, useEffect, useState } from 'react';
6+
import UserIcon from '../icons/UserIcon';
7+
import { SearchBar } from './Styles';
8+
9+
// TODO: Once DMs are implemented, this component will need to be updated to handle DMs as well.
10+
function WrappedComponent({
11+
profile,
12+
server_user,
13+
roles,
14+
}: {
15+
profile: User;
16+
server_user: ServerUser;
17+
roles: Role[];
18+
}) {
19+
return (
20+
<div className="flex flex-col space-y-2 items-center p-3">
21+
<UserIcon user={profile} className="!w-9 !h-9" />
22+
{server_user.nickname && (
23+
<h2 className="text-lg">{server_user.nickname}</h2>
24+
)}
25+
<h2 className={server_user.nickname ? 'text-base' : 'text-lg'}>
26+
{profile.username}
27+
</h2>
28+
<hr />
29+
<h2 className="text-sm text-gray-400 text-left w-full">Roles</h2>
30+
<div className="flex flex-col w-full items-center space-y-1">
31+
{roles.map((role) => (
32+
<span
33+
key={role.id}
34+
className="text-sm text-gray-400 py-1 px-2 border-2 border-solid w-full rounded-sm"
35+
style={{
36+
border: `1px solid #${!!role.color ? role.color : 'cacacacc'}`,
37+
color: `#${!!role.color ? role.color : 'cacacacc'}`,
38+
}}
39+
>
40+
{role.name}
41+
</span>
42+
))}
43+
</div>
44+
<hr />
45+
<form>
46+
<input
47+
type="text"
48+
className={`${SearchBar('bg-grey-900')}`}
49+
placeholder={`Message @${profile.username}`}
50+
/>
51+
</form>
52+
</div>
53+
);
54+
}
55+
56+
export const MiniProfile = memo(WrappedComponent);

components/forms/Styles.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ ${bg}
1717
flex-1`;
1818
}
1919

20-
export const SearchBar = `w-full
20+
export function SearchBar(bg = 'bg-grey-950 ') {
21+
return `w-full
2122
py-2
22-
pl-6
23+
pl-5
2324
self-start
2425
text-base
2526
font-normal
@@ -30,5 +31,6 @@ transition
3031
ease-in-out
3132
m-0
3233
focus:outline-none
33-
bg-grey-950
34+
${bg}
3435
flex-1`;
36+
}

components/home/Chat.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,20 @@
1-
import { useOnlinePresenceRef } from '@/context/ChatCtx';
21
import ChannelMessageIcon from '../icons/ChannelMessageIcon';
32
import { useRef, useEffect } from 'react';
43
import styles from '@/styles/Chat.module.css';
54
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
65
import MessageInput from './MessageInput';
7-
8-
import { createMessage } from '@/services/message.service';
6+
import type { MessageWithServerProfile, Message as MessageType } from '@/types/dbtypes';
7+
import { createMessage } from '@/services/message.service';
98
import Message from '@/components/home/Message';
10-
11-
import { ChannelPermissions } from '@/types/permissions';
12-
139
import { useChannel, useMessages, useUserPerms } from '@/lib/store';
1410
import { Channel } from '@/types/dbtypes';
1511
import { ChannelMediaIcon } from '@/components/icons/ChannelMediaIcon';
12+
import { ChannelPermissions } from '@/types/permissions';
1613

1714
export default function Chat() {
1815
const supabase = useSupabaseClient();
1916
const user = useUser();
2017
const newestMessageRef = useRef<null | HTMLDivElement>(null);
21-
const realtimeRef = useOnlinePresenceRef();
22-
2318
const messages = useMessages();
2419
const channel = useChannel();
2520
const userPerms = useUserPerms();
@@ -58,16 +53,15 @@ export default function Chat() {
5853
{messages &&
5954
messages.map((value, index: number, array) => {
6055
// Get the previous message, if the authors are the same, we don't need to repeat the header (profile picture, name, etc.)
61-
const previousMessage: ChatMessageWithUser | null =
62-
index > 0 ? array[index - 1] : null;
56+
const previousMessage: MessageWithServerProfile | null = index > 0 ? array[index - 1] : null;
6357

6458
return (
6559
<Message
6660
key={value.id}
6761
message={value}
6862
collapse_user={
6963
!!previousMessage &&
70-
previousMessage.profiles.id === value.profiles.id
64+
previousMessage.profile.id === value.profile.id
7165
}
7266
hasDeletePerms={
7367
(userPerms & ChannelPermissions.MANAGE_MESSAGES) !== 0

components/home/DeleteMsgModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ 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/Modal';
9-
import { ChatMessageWithUser } from '@/types/dbtypes';
9+
import { MessageWithServerProfile } from '@/types/dbtypes';
1010

1111
export default function DeleteMsgModal({
1212
showModal,
@@ -15,7 +15,7 @@ export default function DeleteMsgModal({
1515
setMessageOptions,
1616
}: {
1717
showModal: boolean;
18-
message: ChatMessageWithUser;
18+
message: MessageWithServerProfile;
1919
displayTime: string;
2020
setMessageOptions: Dispatch<SetStateAction<'delete' | 'edit' | null>>;
2121
}) {
@@ -54,10 +54,10 @@ export default function DeleteMsgModal({
5454
>
5555
<>
5656
<div className="flex-grow flex flex-row">
57-
<UserIcon user={message.profiles} />
57+
<UserIcon user={message.profile} />
5858
<div className="flex-grow flex items-center">
5959
<div className="text-xl font-semibold tracking-wider mr-2">
60-
{message.profiles.username}
60+
{message.profile.username}
6161
</div>
6262
<div className="text-xs tracking-wider text-grey-300 mt-1">
6363
{displayTime}{' '}

components/home/Message.tsx

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import { Tooltip } from 'react-tooltip';
44
import { useEffect, useRef, useState, KeyboardEvent } from 'react';
55
import TrashIcon from '@/components/icons/TrashIcon';
66
import EditIcon from '@/components/icons/EditIcon';
7-
import { editMessage, deleteMessage } from '@/services/message.service';
7+
import { editMessage } from '@/services/message.service';
88
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react';
99
import MessageContent from './MessageContent';
1010
import DeleteMsgModal from '@/components/home/DeleteMsgModal';
11-
import { ChatMessageWithUser } from '@/types/dbtypes';
11+
import { MessageWithServerProfile } from '@/types/dbtypes';
12+
import { MessageHeader } from './MessageHeader';
1213

1314
export default function Message({
1415
message,
1516
collapse_user,
1617
hasDeletePerms = false,
1718
}: {
18-
message: ChatMessageWithUser;
19+
message: MessageWithServerProfile;
1920
collapse_user: boolean;
2021
hasDeletePerms?: boolean;
2122
}) {
@@ -30,19 +31,23 @@ export default function Message({
3031

3132
const supabase = useSupabaseClient();
3233
const user = useUser();
33-
34+
const [ messageColor, setMessageColor ] = useState<string>('white');
3435
const [showOptions, setShowOptions] = useState<'show' | 'hide'>('hide');
35-
const [messageOptions, setMessageOptions] = useState<
36-
null | 'delete' | 'edit'
37-
>(null);
36+
const [messageOptions, setMessageOptions] = useState<null | 'delete' | 'edit'>(null);
3837

3938
const chatMessage = useRef<HTMLInputElement>(null);
4039

4140
useEffect(() => {
4241
if (messageOptions == 'edit') {
4342
chatMessage.current?.focus();
4443
}
45-
}, [messageOptions]);
44+
45+
const highestRoleWithColor = message.roles.find((role) => !!role.color)?.color;
46+
47+
if (highestRoleWithColor) {
48+
setMessageColor(`#${highestRoleWithColor}`);
49+
}
50+
}, [messageOptions, message.roles]);
4651

4752
function handleKeyPress(e: KeyboardEvent<HTMLInputElement>) {
4853
if (e.key === 'Escape') {
@@ -64,20 +69,15 @@ export default function Message({
6469
<>
6570
<div className="px-2 pt-1 pb-1 flex flex-col">
6671
{!collapse_user && (
67-
<div className="flex-grow flex flex-row">
68-
<UserIcon user={message.profiles} />
69-
<div className="flex-grow flex items-center">
70-
<div className="text-xl font-semibold tracking-wider mr-2">
71-
{message.profiles.username}
72-
</div>
73-
<div className="text-xs tracking-wider text-grey-300 mt-1">
74-
{displayTime}{' '}
75-
{message.is_edited && (
76-
<span className="text-gray-400">(edited)</span>
77-
)}
78-
</div>
79-
</div>
80-
</div>
72+
<MessageHeader
73+
profile={message.profile}
74+
server_user={message.author}
75+
roles={message.roles}
76+
message_id={message.id}
77+
message_color={messageColor}
78+
display_time={displayTime}
79+
edited={message.is_edited}
80+
/>
8181
)}
8282

8383
{/* TODO: figure out how to close modal on blur (cant use onBlur on the dialog cuz it takes up the entire screen meaning you can never focus off of it) */}
@@ -92,7 +92,7 @@ export default function Message({
9292
<div
9393
className="font-light tracking-wide ml-8 -mt-2 hover:bg-grey-900 rounded-lg p-1 transition-colors break-all relative flex flex-col items-start"
9494
onMouseEnter={() => {
95-
if ((user && user.id == message.profiles.id) || hasDeletePerms)
95+
if ((user && user.id == message.profile.id) || hasDeletePerms)
9696
setShowOptions('show');
9797
}}
9898
onMouseLeave={() => setShowOptions('hide')}
@@ -102,7 +102,7 @@ export default function Message({
102102
showOptions == 'hide' ? 'hidden' : ''
103103
} absolute left-[90%] bottom-4 bg-grey-925 px-2 py-1 rounded-lg z-10 flex `}
104104
>
105-
{user && user.id == message.profiles.id ? (
105+
{user && user.id == message.profile.id ? (
106106
<span onClick={() => setMessageOptions('edit')}>
107107
<EditIcon styles="mr-1 hover:bg-grey-600 rounded-lg hover:cursor-pointer" />
108108
</span>

components/home/MessageHeader.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Role, ServerUser, User } from '@/types/dbtypes';
2+
import { Tooltip } from 'react-tooltip';
3+
import { MiniProfile } from '../forms/MiniProfile';
4+
import UserIcon from '../icons/UserIcon';
5+
6+
export function MessageHeader({
7+
profile,
8+
server_user,
9+
message_id,
10+
message_color,
11+
display_time,
12+
edited,
13+
roles,
14+
}: {
15+
profile: User;
16+
server_user: ServerUser;
17+
message_id: number;
18+
message_color: string;
19+
display_time: string;
20+
edited: boolean;
21+
roles: Role[];
22+
}) {
23+
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>}
53+
</div>
54+
</div>
55+
</div>
56+
);
57+
}

components/home/RenderDesktopView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ export default function RenderDesktopView() {
1616
const [sideBarView, mainView] = renderContent(sideBarOption, channel);
1717

1818
return (
19-
<div className={`${styles.container} `}>
19+
<div className={`${styles.container}`}>
2020
<div className="col-start-1 col-end-2 bg-grey-950 flex-col justify-center ">
2121
<NavBar type="vertical" />
2222
</div>
23-
<div className="col-start-2 col-end-4 flex-col bg-grey-900 ">
23+
<div className="col-start-2 col-end-4 flex-col bg-grey-900 h-screen overflow-y-scroll overflow-x-hidden">
2424
{sideBarView}
2525
</div>
2626
<div className="col-start-4 col-end-13 flex flex-col h-screen">

components/home/ServerList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default function ServerList() {
6969
<div className="pt-4 pb-4">
7070
<input
7171
type="text"
72-
className={`${SearchBar}`}
72+
className={`${SearchBar()}`}
7373
placeholder="Search"
7474
></input>
7575
</div>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function DefaultUserIcon({ className }: { className?: string }) {
2+
return (
3+
<div className={`flex-none bg-grey-900 rounded-full mr-3 h-7 ${className}`}>
4+
<svg
5+
xmlns='http://www.w3.org/2000/svg'
6+
fill='none'
7+
viewBox='0 0 24 24'
8+
strokeWidth={1.5}
9+
stroke='currentColor'
10+
className={`w-7 p-2 ${className}`}
11+
>
12+
<path
13+
strokeLinecap='round'
14+
strokeLinejoin='round'
15+
d='M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z'
16+
/>
17+
</svg>
18+
</div>
19+
);
20+
}

components/icons/UserIcon.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { User } from '@/types/dbtypes';
2-
import Image from 'next/image';
32
import { PresenceIndicator } from '../home/PresenceIndicator';
3+
import { DefaultUserIcon } from './DefaultUserIcon';
44

5-
export default function UserIcon({ user }: { user: User }) {
5+
export default function UserIcon({ user, className }: { user: User, className?: string }) {
66

77
return (
88
<div>
@@ -11,26 +11,9 @@ export default function UserIcon({ user }: { user: User }) {
1111
<img
1212
src={user.avatar_url!}
1313
alt={`${user.username}'s avatar`}
14-
className='flex-none w-7 h-7 mr-2 rounded-full'
14+
className={`flex-none w-7 h-7 mr-2 rounded-full ${className}`}
1515
/>
16-
) : (
17-
<div className="flex-none bg-grey-900 rounded-full mr-3 h-7">
18-
<svg
19-
xmlns='http://www.w3.org/2000/svg'
20-
fill='none'
21-
viewBox='0 0 24 24'
22-
strokeWidth={1.5}
23-
stroke='currentColor'
24-
className='w-7 h-7 p-2 '
25-
>
26-
<path
27-
strokeLinecap='round'
28-
strokeLinejoin='round'
29-
d='M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z'
30-
/>
31-
</svg>
32-
</div>
33-
)
16+
) : <DefaultUserIcon className={className} />
3417
}
3518
<PresenceIndicator userId={user.id} />
3619
</div>

0 commit comments

Comments
 (0)