refactor(members): introduce UserProvider to eliminate prop drilling for member profile popovers
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 1m4s
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 1m4s
This commit is contained in:
parent
e2985c53a2
commit
3a119c5891
9 changed files with 87 additions and 120 deletions
|
|
@ -13,7 +13,6 @@ import { ChatInput } from "@/components/chat/ChatInput";
|
||||||
import { ChatMessages } from "@/components/chat/ChatMessages";
|
import { ChatMessages } from "@/components/chat/ChatMessages";
|
||||||
import type { MessageWithMember } from "@/components/chat/ChatItem";
|
import type { MessageWithMember } from "@/components/chat/ChatItem";
|
||||||
import { isValidUuid } from "@/lib/utils";
|
import { isValidUuid } from "@/lib/utils";
|
||||||
import { getUserFriendships } from "@/lib/services/friends.service";
|
|
||||||
import AppFooter from "@/components/layout/AppFooter";
|
import AppFooter from "@/components/layout/AppFooter";
|
||||||
|
|
||||||
/** Renders the direct message conversation page with header, message history, and input field. */
|
/** Renders the direct message conversation page with header, message history, and input field. */
|
||||||
|
|
@ -35,8 +34,8 @@ export default async function DirectMessagePage({
|
||||||
redirect("/login");
|
redirect("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Database Queries
|
// 3. Database Queries (Freundschaften werden nun global im Layout geladen)
|
||||||
const [conversation, rawMessages, friendships] = await Promise.all([
|
const [conversation, rawMessages] = await Promise.all([
|
||||||
db.query.conversations.findFirst({
|
db.query.conversations.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
eq(conversations.id, conversationId),
|
eq(conversations.id, conversationId),
|
||||||
|
|
@ -57,7 +56,6 @@ export default async function DirectMessagePage({
|
||||||
},
|
},
|
||||||
orderBy: (dm, { asc }) => [asc(dm.createdAt)],
|
orderBy: (dm, { asc }) => [asc(dm.createdAt)],
|
||||||
}),
|
}),
|
||||||
getUserFriendships(session.user.id),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
|
|
@ -90,8 +88,6 @@ export default async function DirectMessagePage({
|
||||||
type="dm"
|
type="dm"
|
||||||
name={partner.username}
|
name={partner.username}
|
||||||
initialMessages={initialMessages}
|
initialMessages={initialMessages}
|
||||||
currentUserId={session.user.id}
|
|
||||||
userFriendships={friendships}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatInput
|
<ChatInput
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { MemberSidebar } from "@/components/layout/MemberSidebar";
|
||||||
import { ServerProvider } from "@/lib/context/ServerContext";
|
import { ServerProvider } from "@/lib/context/ServerContext";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getUserFriendships } from "@/lib/services/friends.service";
|
import { getUserFriendships } from "@/lib/services/friends.service";
|
||||||
|
import { UserProvider } from "@/lib/context/UserContext";
|
||||||
|
|
||||||
/** Renders the primary application layout with authentication checks, database fetching, and sidebar structure. */
|
/** Renders the primary application layout with authentication checks, database fetching, and sidebar structure. */
|
||||||
export default async function AppLayout({
|
export default async function AppLayout({
|
||||||
|
|
@ -30,16 +31,7 @@ export default async function AppLayout({
|
||||||
|
|
||||||
// Parallel Loading: User Details, Servers & DM-Conversations
|
// Parallel Loading: User Details, Servers & DM-Conversations
|
||||||
const [[currentUser], userServers, userConversations] = await Promise.all([
|
const [[currentUser], userServers, userConversations] = await Promise.all([
|
||||||
db
|
db.select().from(users).where(eq(users.id, currentUserId)).limit(1),
|
||||||
.select({
|
|
||||||
id: users.id,
|
|
||||||
username: users.username,
|
|
||||||
color: users.color,
|
|
||||||
status: users.status,
|
|
||||||
})
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.id, currentUserId))
|
|
||||||
.limit(1),
|
|
||||||
getUserServers(currentUserId),
|
getUserServers(currentUserId),
|
||||||
db.query.conversations.findMany({
|
db.query.conversations.findMany({
|
||||||
where: or(
|
where: or(
|
||||||
|
|
@ -78,19 +70,18 @@ export default async function AppLayout({
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerProvider>
|
<UserProvider currentUser={currentUser} friendships={friendships}>
|
||||||
<div className="flex h-screen w-full overflow-hidden bg-background text-foreground">
|
<ServerProvider>
|
||||||
<AppSidebar
|
<div className="flex h-screen w-full overflow-hidden bg-background text-foreground">
|
||||||
servers={userServers}
|
<AppSidebar
|
||||||
conversations={formattedConversations}
|
servers={userServers}
|
||||||
user={currentUser}
|
conversations={formattedConversations}
|
||||||
/>
|
user={currentUser}
|
||||||
<div className="flex-1 flex min-w-0">{children}</div>
|
/>
|
||||||
<MemberSidebar
|
<div className="flex-1 flex min-w-0">{children}</div>
|
||||||
currentUserId={currentUserId}
|
<MemberSidebar />
|
||||||
userFriendships={friendships}
|
</div>
|
||||||
/>
|
</ServerProvider>
|
||||||
</div>
|
</UserProvider>
|
||||||
</ServerProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import { getChannelById } from "@/lib/services/channel.service";
|
||||||
import { getChannelMessages } from "@/lib/services/message.service";
|
import { getChannelMessages } from "@/lib/services/message.service";
|
||||||
import { getServerById } from "@/lib/services/server.service";
|
import { getServerById } from "@/lib/services/server.service";
|
||||||
import { isValidUuid } from "@/lib/utils";
|
import { isValidUuid } from "@/lib/utils";
|
||||||
import { getUserFriendships } from "@/lib/services/friends.service";
|
|
||||||
import AppFooter from "@/components/layout/AppFooter";
|
import AppFooter from "@/components/layout/AppFooter";
|
||||||
|
|
||||||
/** Renders the channel chat view by validating parameters, checking user session, fetching channel data, friendships, and displaying headers, messages, and input controls. */
|
/** Renders the channel chat view by validating parameters, checking user session, fetching channel data, friendships, and displaying headers, messages, and input controls. */
|
||||||
|
|
@ -34,11 +33,10 @@ export default async function ChannelPage({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Parallel Loading of Data
|
// 3. Parallel Loading of Data
|
||||||
const [server, channel, channelMessages, friendships] = await Promise.all([
|
const [server, channel, channelMessages] = await Promise.all([
|
||||||
getServerById(serverId),
|
getServerById(serverId),
|
||||||
getChannelById(channelId),
|
getChannelById(channelId),
|
||||||
getChannelMessages(channelId),
|
getChannelMessages(channelId),
|
||||||
getUserFriendships(session.user.id),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!channel || !server) {
|
if (!channel || !server) {
|
||||||
|
|
@ -57,8 +55,6 @@ export default async function ChannelPage({
|
||||||
type="chat"
|
type="chat"
|
||||||
name={channel.name}
|
name={channel.name}
|
||||||
initialMessages={channelMessages}
|
initialMessages={channelMessages}
|
||||||
currentUserId={session.user.id}
|
|
||||||
userFriendships={friendships}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatInput
|
<ChatInput
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { ChatItemActions } from "./ChatItemActions";
|
||||||
import { ChatItemEdit } from "./ChatItemEdit";
|
import { ChatItemEdit } from "./ChatItemEdit";
|
||||||
import { UserProfilePopover } from "../ui/UserProfilePopover";
|
import { UserProfilePopover } from "../ui/UserProfilePopover";
|
||||||
import { useActiveServer } from "@/lib/context/ServerContext";
|
import { useActiveServer } from "@/lib/context/ServerContext";
|
||||||
|
import { useUser } from "@/lib/context/UserContext";
|
||||||
|
|
||||||
/** Composite message type extending base database Message with channel/conversation details and member relation. */
|
/** Composite message type extending base database Message with channel/conversation details and member relation. */
|
||||||
export type MessageWithMember = Omit<Message, "channelId"> & {
|
export type MessageWithMember = Omit<Message, "channelId"> & {
|
||||||
|
|
@ -30,12 +31,6 @@ export type MessageWithMember = Omit<Message, "channelId"> & {
|
||||||
interface ChatItemProps {
|
interface ChatItemProps {
|
||||||
type: "chat" | "dm";
|
type: "chat" | "dm";
|
||||||
message: MessageWithMember;
|
message: MessageWithMember;
|
||||||
userFriendships: Array<{
|
|
||||||
senderId: string;
|
|
||||||
receiverId: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
currentUserId: string;
|
|
||||||
onDeleteSuccess?: (id: string) => void;
|
onDeleteSuccess?: (id: string) => void;
|
||||||
onEditSuccess?: (id: string, newContent: string) => void;
|
onEditSuccess?: (id: string, newContent: string) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -44,13 +39,14 @@ interface ChatItemProps {
|
||||||
export function ChatItem({
|
export function ChatItem({
|
||||||
type,
|
type,
|
||||||
message,
|
message,
|
||||||
currentUserId,
|
|
||||||
userFriendships,
|
|
||||||
onDeleteSuccess,
|
onDeleteSuccess,
|
||||||
onEditSuccess,
|
onEditSuccess,
|
||||||
}: ChatItemProps) {
|
}: ChatItemProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setActiveServer } = useActiveServer();
|
const { setActiveServer } = useActiveServer();
|
||||||
|
const { currentUser, friendships } = useUser();
|
||||||
|
|
||||||
|
const currentUserId = currentUser.id;
|
||||||
|
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
@ -113,15 +109,11 @@ export function ChatItem({
|
||||||
|
|
||||||
/** Determines the friendship status between the current user and the message author. */
|
/** Determines the friendship status between the current user and the message author. */
|
||||||
const getFriendshipStatus = () => {
|
const getFriendshipStatus = () => {
|
||||||
if (
|
if (!user?.id || user.id === currentUserId || !Array.isArray(friendships)) {
|
||||||
!user?.id ||
|
|
||||||
user.id === currentUserId ||
|
|
||||||
!Array.isArray(userFriendships)
|
|
||||||
) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const friendship = userFriendships.find(
|
const friendship = friendships.find(
|
||||||
(f) =>
|
(f) =>
|
||||||
(f.senderId === user.id && f.receiverId === currentUserId) ||
|
(f.senderId === user.id && f.receiverId === currentUserId) ||
|
||||||
(f.receiverId === user.id && f.senderId === currentUserId),
|
(f.receiverId === user.id && f.senderId === currentUserId),
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,6 @@ export interface ChatMessagesProps {
|
||||||
type: "chat" | "dm";
|
type: "chat" | "dm";
|
||||||
name: string;
|
name: string;
|
||||||
initialMessages: MessageWithMember[];
|
initialMessages: MessageWithMember[];
|
||||||
userFriendships: Array<{
|
|
||||||
senderId: string;
|
|
||||||
receiverId: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
currentUserId: string;
|
|
||||||
onDeleteMessage?: (id: string) => void;
|
onDeleteMessage?: (id: string) => void;
|
||||||
onEditMessage?: (id: string, newContent: string) => void;
|
onEditMessage?: (id: string, newContent: string) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -55,8 +49,6 @@ export function ChatMessages({
|
||||||
type,
|
type,
|
||||||
name,
|
name,
|
||||||
initialMessages,
|
initialMessages,
|
||||||
currentUserId,
|
|
||||||
userFriendships,
|
|
||||||
onDeleteMessage,
|
onDeleteMessage,
|
||||||
onEditMessage,
|
onEditMessage,
|
||||||
}: ChatMessagesProps) {
|
}: ChatMessagesProps) {
|
||||||
|
|
@ -156,8 +148,6 @@ export function ChatMessages({
|
||||||
<ChatItem
|
<ChatItem
|
||||||
type={type}
|
type={type}
|
||||||
message={message}
|
message={message}
|
||||||
currentUserId={currentUserId}
|
|
||||||
userFriendships={userFriendships}
|
|
||||||
onDeleteSuccess={handleDeleteMessage}
|
onDeleteSuccess={handleDeleteMessage}
|
||||||
onEditSuccess={handleEditMessage}
|
onEditSuccess={handleEditMessage}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,6 @@ interface MemberHeaderProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Properties for the MemberSidebar component. */
|
|
||||||
interface MemberSidebarProps {
|
|
||||||
currentUserId: string;
|
|
||||||
userFriendships: Array<{
|
|
||||||
senderId: string;
|
|
||||||
receiverId: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Renders the header section of the member sidebar with a title and close button. */
|
/** Renders the header section of the member sidebar with a title and close button. */
|
||||||
function MemberHeader({ title, onClose }: MemberHeaderProps) {
|
function MemberHeader({ title, onClose }: MemberHeaderProps) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -49,10 +39,7 @@ function MemberHeader({ title, onClose }: MemberHeaderProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays the list of members for the active server in a responsive sidebar or drawer layout. */
|
/** Displays the list of members for the active server in a responsive sidebar or drawer layout. */
|
||||||
export function MemberSidebar({
|
export function MemberSidebar() {
|
||||||
currentUserId,
|
|
||||||
userFriendships = [],
|
|
||||||
}: MemberSidebarProps) {
|
|
||||||
const { isMembersOpen, closeMembers } = useSidebarStore();
|
const { isMembersOpen, closeMembers } = useSidebarStore();
|
||||||
const { activeServer } = useActiveServer();
|
const { activeServer } = useActiveServer();
|
||||||
const desktopSidebarRef = useRef<HTMLElement>(null);
|
const desktopSidebarRef = useRef<HTMLElement>(null);
|
||||||
|
|
@ -124,13 +111,7 @@ export function MemberSidebar({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return <MemberList members={members} />;
|
||||||
<MemberList
|
|
||||||
members={members}
|
|
||||||
currentUserId={currentUserId}
|
|
||||||
userFriendships={userFriendships}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -13,29 +13,22 @@ import { UserAvatar } from "../ui/UserAvatar";
|
||||||
import { UserProfilePopover } from "../ui/UserProfilePopover";
|
import { UserProfilePopover } from "../ui/UserProfilePopover";
|
||||||
import { useActiveServer } from "@/lib/context/ServerContext";
|
import { useActiveServer } from "@/lib/context/ServerContext";
|
||||||
import { useSidebarStore } from "@/lib/stores/useSidebarStore";
|
import { useSidebarStore } from "@/lib/stores/useSidebarStore";
|
||||||
|
import { useUser } from "@/lib/context/UserContext";
|
||||||
|
|
||||||
/** Props for the MemberItem component. */
|
/** Props for the MemberItem component. */
|
||||||
interface MemberItemProps {
|
interface MemberItemProps {
|
||||||
member: User;
|
member: User;
|
||||||
currentUserId: string;
|
|
||||||
userFriendships: Array<{
|
|
||||||
senderId: string;
|
|
||||||
receiverId: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
isOffline?: boolean;
|
isOffline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders an individual member item with an interactive user profile popover. */
|
/** Renders an individual member item with an interactive user profile popover. */
|
||||||
export function MemberItem({
|
export function MemberItem({ member, isOffline = false }: MemberItemProps) {
|
||||||
member,
|
|
||||||
currentUserId,
|
|
||||||
userFriendships = [],
|
|
||||||
isOffline = false,
|
|
||||||
}: MemberItemProps) {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { setActiveServer } = useActiveServer();
|
const { setActiveServer } = useActiveServer();
|
||||||
const { closeMembers } = useSidebarStore();
|
const { closeMembers } = useSidebarStore();
|
||||||
|
const { currentUser, friendships } = useUser();
|
||||||
|
|
||||||
|
const currentUserId = currentUser.id;
|
||||||
|
|
||||||
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -71,11 +64,11 @@ export function MemberItem({
|
||||||
|
|
||||||
/** Determines the friendship status between the logged-in user and this member. */
|
/** Determines the friendship status between the logged-in user and this member. */
|
||||||
const getFriendshipStatus = () => {
|
const getFriendshipStatus = () => {
|
||||||
if (!member.id || member.id === currentUserId || !userFriendships) {
|
if (!member.id || member.id === currentUserId || !friendships) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const friendship = userFriendships.find(
|
const friendship = friendships.find(
|
||||||
(f) =>
|
(f) =>
|
||||||
(f.senderId === member.id && f.receiverId === currentUserId) ||
|
(f.senderId === member.id && f.receiverId === currentUserId) ||
|
||||||
(f.receiverId === member.id && f.senderId === currentUserId),
|
(f.receiverId === member.id && f.senderId === currentUserId),
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,18 @@
|
||||||
/**
|
/**
|
||||||
* @file components/members/MemberList.tsx
|
* @file components/members/MemberList.tsx
|
||||||
* @description Renders categorized lists of online and offline members with their friendship statuses.
|
* @description Renders categorized lists of online and offline members using the UserContext.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { MemberItem } from "@/components/members/MemberItem";
|
import { MemberItem } from "@/components/members/MemberItem";
|
||||||
import { User } from "@/db/schema";
|
import { User } from "@/db/schema";
|
||||||
|
|
||||||
/** Props for the MemberList component. */
|
|
||||||
interface MemberListProps {
|
interface MemberListProps {
|
||||||
members: User[];
|
members: User[];
|
||||||
currentUserId: string;
|
|
||||||
userFriendships: Array<{
|
|
||||||
senderId: string;
|
|
||||||
receiverId: string;
|
|
||||||
status: string;
|
|
||||||
}>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders online and offline community members in distinct sections. */
|
/** Renders online and offline community members in distinct sections. */
|
||||||
export function MemberList({
|
export function MemberList({ members = [] }: MemberListProps) {
|
||||||
members = [],
|
|
||||||
currentUserId,
|
|
||||||
userFriendships = [],
|
|
||||||
}: MemberListProps) {
|
|
||||||
const { onlineMembers, offlineMembers } = useMemo(() => {
|
const { onlineMembers, offlineMembers } = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
onlineMembers: members.filter((m) => m.status !== "OFFLINE"),
|
onlineMembers: members.filter((m) => m.status !== "OFFLINE"),
|
||||||
|
|
@ -40,12 +29,7 @@ export function MemberList({
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{onlineMembers.map((member) => (
|
{onlineMembers.map((member) => (
|
||||||
<MemberItem
|
<MemberItem key={member.id} member={member} />
|
||||||
key={member.id}
|
|
||||||
member={member}
|
|
||||||
currentUserId={currentUserId}
|
|
||||||
userFriendships={userFriendships}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
{onlineMembers.length === 0 && (
|
{onlineMembers.length === 0 && (
|
||||||
<p className="text-xs text-muted/60 px-2 italic">
|
<p className="text-xs text-muted/60 px-2 italic">
|
||||||
|
|
@ -62,13 +46,7 @@ export function MemberList({
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{offlineMembers.map((member) => (
|
{offlineMembers.map((member) => (
|
||||||
<MemberItem
|
<MemberItem key={member.id} member={member} isOffline />
|
||||||
key={member.id}
|
|
||||||
member={member}
|
|
||||||
currentUserId={currentUserId}
|
|
||||||
userFriendships={userFriendships}
|
|
||||||
isOffline
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
50
lib/context/UserContext.tsx
Normal file
50
lib/context/UserContext.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* @file lib/context/UserContext.tsx
|
||||||
|
* @description Context provider and hook for managing current user data and friendship states.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, useContext } from "react";
|
||||||
|
import type { User } from "@/db/schema";
|
||||||
|
|
||||||
|
/** Represents a friendship record between two users. */
|
||||||
|
interface Friendship {
|
||||||
|
senderId: string;
|
||||||
|
receiverId: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Shape of the user context value. */
|
||||||
|
interface UserContextType {
|
||||||
|
currentUser: User;
|
||||||
|
friendships: Friendship[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserContext = createContext<UserContextType | null>(null);
|
||||||
|
|
||||||
|
/** Provides user data and friendship states to child components. */
|
||||||
|
export function UserProvider({
|
||||||
|
children,
|
||||||
|
currentUser,
|
||||||
|
friendships,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
currentUser: User;
|
||||||
|
friendships: Friendship[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ currentUser, friendships }}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Custom hook to consume user context data. */
|
||||||
|
export function useUser() {
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useUser must be used within a UserProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue