๐ Task TODOLIST
- [x] ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ๋ฉค๋ฒ๋ค ํ๋กํ ์กฐํ
- [x] ์ฑํ ๋ฐฉ์ ์ค์๊ฐ online ๋ฉค๋ฒ ํ์
โจ ๊ฐ๋ฐ ๋ด์ฉ
1. ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ๋ฉค๋ฒ๋ค ํ๋กํ ์กฐํ
Why(์ด์ ) ?
ํ์ฌ ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ๋ฉค๋ฒ๋ค์ด ์ด๋ค ์ฌ๋๋ค์ธ์ง ์ ์ ์์ด์ผ ์ฑํ ์ ํ ๋
"๊ทธ๋์ ์ด ์ฌ๋์ด ์ด๋ค ์ฌ๋์ด๋๋ผ?" ํ ์ ์ ๋ค์ ๋ง์์ ๊ฐ์ ์ด์ ์..
How(๊ณผ์ ) ?
(1) ํ์ฌ ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์จ๋ค.
(2) ์ฑํ ๋ฐฉ ํค๋์ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์ ๋์ดํ๋ค.
(3) ๊ทธ ์ค์์ ์ค์๊ฐ์ผ๋ก ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์ ์ปฌ๋ฌ์ฒ๋ฆฌ, ์ค์๊ฐ ์ฐธ์ฌ ์ค์ด ์๋ ์ฌ๋๋ค์ blur ์ฒ๋ฆฌํด์ผ๊ฒ ๋ค.
what(๊ฒฐ๊ณผ) ?
export const useParticipantsQuery = (roomId: string) => {
const { data: participants } = useSuspenseQuery({
queryKey: [PARTICIPANTS_QUERY_KEY, roomId],
queryFn: () => fetchParticipants(roomId),
return participants;
};
Supabase-realtime์ presence ๊ธฐ๋ฅ์ผ๋ก ํ์ฌ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์ user_id๋ฅผ ์ป์ด๋ด๊ณ ๊ทธ๊ฑธ onlineUsers ๋ผ๋ state๋ก Zustand-Store์ ์ ์ฅํ๋ค.
const ChatPresence = () => {
const { data: user } = useGetUserDataQuery();
const { chatRoomId, onlineUsers, setOnlineUsers } = chatStore((state) => state);
useEffect(() => {
if (chatRoomId) {
const channel = clientSupabase.channel(chatRoomId);
channel
.on('presence', { event: 'sync' }, () => {
const nowUsers = [];
for (const id in channel.presenceState()) {
// @ts-ignore
nowUsers.push(channel.presenceState()[id][0].user_id);
}
setOnlineUsers([...nowUsers]);
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({ online_at: new Date().toISOString(), user_id: user?.user_id });
}
});
}
}, [user, chatRoomId]);
return (
<>
<div className="flex gap-2">
<div className="h-4 w-4 bg-mainColor rounded-full animate-pulse my-auto text-base"></div>
{chatRoomId && <h1>{onlineUsers.length} online</h1>}
</div>
</>
);
};
export default ChatPresence;
๊ทธ๋ฐ ๋ค์, ์ฑํ ๋ฐฉ ํค๋์์ ์ด ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์ Avatar๋ค์ ๋์ดํ๊ณ
์ค์๊ฐ์ผ๋ก ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค(์ดํ onlineUsers)์ ์ปฌ๋ฌ๋ก,
๊ทธ๋ ์ง ์์ ์ฌ๋๋ค์ opacity-30์ผ๋ก ๋ธ๋ฌ์ฒ๋ฆฌ ํ๋ค(Avatar์ Disabled ์์ฑ๋ ์์์ง๋ง ์ด๊ฒ ๋ ๋๋ ทํ์ฌ ์ด ๋ฐฉ๋ฒ ํ.)
const ChatHeader = () => {
return (
<div className="flex gap-5 items-center">
<ChatPresence />
<AvatarGroup isBordered max={8}>
{participants?.map((person) => (
<Popover key={person.user_id} showArrow placement="bottom">
<PopoverTrigger>
<Avatar
as="button"
src={person.users.avatar as string}
className={`w-[32px] h-[32px] ${
!onlineUsers.find((id) => id === person.user_id) ? 'bg-black opacity-30' : ''
}`}
/>
</PopoverTrigger>
<PopoverContent>
<ShowChatMember person={person.users} />
</PopoverContent>
</Popover>
))}
</AvatarGroup>
</div>
)
}
export default ChatHeader;
๐จ TroubleShotting
-
๐ธ ์คํฌ๋ฆฐ์ท
-> ์ค์๊ฐ ์ฐธ์ฌ ์ค์ธ ๋ฉค๋ฒ๋ง ์ปฌ๋ฌ์ฒ๋ฆฌ | -> ์ด ์ฑํ ๋ฐฉ์ ์ฐธ์ฌ ์ค์ธ ์ฌ๋๋ค์(ํ์ฌ 2๋ช ) ํ๋กํ ์กฐํ๊ฐ๋ฅ |
๐ ๋ ํผ๋ฐ์ค
supabase-realtime-presence
Sync and track state
https://supabase.com/docs/guides/realtime/presence#sync-and-track-state