From d0ddd4b372308711ef053031b69401518ca96911 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 10:38:27 -0400 Subject: [PATCH 1/9] sending message working --- lib/current-profile-pages.ts | 20 ++++++++ pages/api/socket/messages.ts | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 lib/current-profile-pages.ts create mode 100644 pages/api/socket/messages.ts diff --git a/lib/current-profile-pages.ts b/lib/current-profile-pages.ts new file mode 100644 index 0000000..cdb2de3 --- /dev/null +++ b/lib/current-profile-pages.ts @@ -0,0 +1,20 @@ +import { getAuth } from "@clerk/nextjs/server"; + +import { db } from "@/lib/db"; +import { NextApiRequest } from "next"; + +export const currentProfilePages = async (req: NextApiRequest) => { + const { userId } = getAuth(req); + + if (!userId) { + return null; + } + + const profile = await db.profile.findUnique({ + where: { + userId, + }, + }); + + return profile; +}; diff --git a/pages/api/socket/messages.ts b/pages/api/socket/messages.ts new file mode 100644 index 0000000..a866bfd --- /dev/null +++ b/pages/api/socket/messages.ts @@ -0,0 +1,97 @@ +import { currentProfilePages } from "@/lib/current-profile-pages"; +import { NextApiResponseServerIo } from "@/types"; +import { NextApiRequest } from "next"; +import { db } from "@/lib/db"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponseServerIo +) { + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + try { + const profile = await currentProfilePages(req); + const { content, fileUrl } = req.body; + const { serverId, channelId } = req.query; + + if (!profile) { + return res.status(401).json({ message: "Unauthorized" }); + } + + if (!serverId) { + return res.status(400).json({ message: "Server ID is Missing" }); + } + + if (!channelId) { + return res.status(400).json({ message: "Channel ID is Missing" }); + } + + if (!content) { + return res.status(400).json({ message: "Content is Missing" }); + } + + const server = await db.server.findFirst({ + where: { + id: serverId as string, + members: { + some: { + profileId: profile.id, + }, + }, + }, + include: { + members: true, + }, + }); + + if (!server) { + return res.status(404).json({ message: "Server not found" }); + } + + const channel = await db.channel.findFirst({ + where: { + id: channelId as string, + serverId: server.id as string, + }, + }); + + if (!channel) { + return res.status(404).json({ message: "Channel not found" }); + } + + const member = server.members.find( + (member) => member.profileId === profile.id + ); + + if (!member) { + return res.status(404).json({ message: "Member not found" }); + } + + const message = await db.message.create({ + data: { + content, + fileUrl, + channelId: channelId as string, + memberId: member.id, + }, + include: { + member: { + include: { + profile: true, + }, + }, + }, + }); + + const channelKey = `chat:${channelId}:messages`; + + res?.socket?.server?.io?.emit(channelKey, message); + + return res.status(200).json(message); + } catch (error) { + console.log("[MESSGES_POST]", error); + return res.status(500).json({ message: "Internal error" }); + } +} From 192c57a8f7a000618100ed8dcb16b80e8076fffc Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 11:22:22 -0400 Subject: [PATCH 2/9] file upload working --- components/chat/chat-input.tsx | 5 +- components/file-upload.tsx | 24 ++++- components/modals/message-file-modal.tsx | 113 +++++++++++++++++++++++ components/providers/modal-provider.tsx | 2 + hooks/use-modal-store.ts | 5 +- 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 components/modals/message-file-modal.tsx diff --git a/components/chat/chat-input.tsx b/components/chat/chat-input.tsx index 24ef575..6ba752a 100644 --- a/components/chat/chat-input.tsx +++ b/components/chat/chat-input.tsx @@ -8,6 +8,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Plus, SmileIcon } from "lucide-react"; +import { useModal } from "@/hooks/use-modal-store"; interface ChatInputProps { apiUrl: string; @@ -21,6 +22,8 @@ const formSchema = z.object({ }); export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => { + const { onOpen } = useModal(); + const form = useForm>({ defaultValues: { content: "", @@ -54,7 +57,7 @@ export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => {
+
+ ); + } + return ( { + const { isOpen, onClose, type, data } = useModal(); + + const router = useRouter(); + + const isModalOpen = isOpen && type === "messageFile"; + + const { apiUrl, query } = data; + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + fileUrl: "", + }, + }); + + const handleClose = () => { + form.reset(); + onClose(); + }; + + const isLoading = form.formState.isSubmitting; + + const onSubmit = async (values: z.infer) => { + try { + const url = qs.stringifyUrl({ + url: apiUrl || "", + query, + }); + + await axios.post(url, { ...values, content: values.fileUrl }); + form.reset(); + router.refresh(); + handleClose(); + } catch (error) { + console.log(error); + } + }; + + return ( + + + + + Add an attachment + + + Send a file as a message + + +
+ +
+
+ ( + + + + + + )} + /> +
+
+ + + +
+ +
+
+ ); +}; + +export default MessageFileModal; diff --git a/components/providers/modal-provider.tsx b/components/providers/modal-provider.tsx index a460c08..4771f16 100644 --- a/components/providers/modal-provider.tsx +++ b/components/providers/modal-provider.tsx @@ -10,6 +10,7 @@ import { LeaveServerModal } from "@/components/modals/leave-server-modal"; import { DeleteServerModal } from "@/components/modals/delete-server-modal"; import { DeleteChannelModal } from "@/components/modals/delete-channel-modal"; import { EditChannelModal } from "@/components/modals/edit-channel-modal"; +import MessageFileModal from "@/components/modals/message-file-modal"; export const ModalProvider = () => { const [isMounted, setIsMounted] = useState(false); @@ -33,6 +34,7 @@ export const ModalProvider = () => { + ); }; diff --git a/hooks/use-modal-store.ts b/hooks/use-modal-store.ts index 2990b4b..b058716 100644 --- a/hooks/use-modal-store.ts +++ b/hooks/use-modal-store.ts @@ -11,12 +11,15 @@ export type ModalType = | "leaveServer" | "deleteServer" | "deleteChannel" - | "editChannel"; + | "editChannel" + | "messageFile"; interface ModalData { server?: Server; channel?: Channel; channelType?: ChannelType; + apiUrl?: string; + query?: Record; } interface ModalStore { From 6ab0235a56edf8203fec80f779cd4eb27ff76e6e Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 11:38:56 -0400 Subject: [PATCH 3/9] emoji sending working --- components/chat/chat-input.tsx | 34 ++++++++---- components/emoji-picker.tsx | 38 +++++++++++++ components/ui/popover.tsx | 31 +++++++++++ package-lock.json | 99 ++++++++++++++++++++++++++++++++++ package.json | 4 ++ 5 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 components/emoji-picker.tsx create mode 100644 components/ui/popover.tsx diff --git a/components/chat/chat-input.tsx b/components/chat/chat-input.tsx index 6ba752a..0053789 100644 --- a/components/chat/chat-input.tsx +++ b/components/chat/chat-input.tsx @@ -1,20 +1,23 @@ "use client"; -import { useForm } from "react-hook-form"; + import * as z from "zod"; import axios from "axios"; import qs from "query-string"; +import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import { Plus } from "lucide-react"; +import { useRouter } from "next/navigation"; import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { Plus, SmileIcon } from "lucide-react"; import { useModal } from "@/hooks/use-modal-store"; +import { EmojiPicker } from "@/components/emoji-picker"; interface ChatInputProps { apiUrl: string; query: Record; name: string; - type: "channel" | "conversation"; + type: "conversation" | "channel"; } const formSchema = z.object({ @@ -23,12 +26,13 @@ const formSchema = z.object({ export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => { const { onOpen } = useModal(); + const router = useRouter(); const form = useForm>({ + resolver: zodResolver(formSchema), defaultValues: { content: "", }, - resolver: zodResolver(formSchema), }); const isLoading = form.formState.isSubmitting; @@ -39,9 +43,13 @@ export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => { url: apiUrl, query, }); + await axios.post(url, values); - } catch (e) { - console.log(e); + + form.reset(); + router.refresh(); + } catch (error) { + console.log(error); } }; @@ -56,7 +64,7 @@ export const ChatInput = ({ apiUrl, query, name, type }: ChatInputProps) => {
- + + field.onChange(`${field.value} ${emoji}`) + } + />
diff --git a/components/emoji-picker.tsx b/components/emoji-picker.tsx new file mode 100644 index 0000000..3428c07 --- /dev/null +++ b/components/emoji-picker.tsx @@ -0,0 +1,38 @@ +"use client"; +import { Smile } from "lucide-react"; +import Picker from "@emoji-mart/react"; +import data from "@emoji-mart/data"; +import { useTheme } from "next-themes"; + +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +interface EmojiPickerProps { + onChange: (value: string) => void; +} + +export const EmojiPicker = ({ onChange }: EmojiPickerProps) => { + const { resolvedTheme } = useTheme(); + + return ( + + + + + + onChange(emoji.native)} + /> + + + ); +}; diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx new file mode 100644 index 0000000..a0ec48b --- /dev/null +++ b/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/package-lock.json b/package-lock.json index 8ebdb2a..29db426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,15 @@ "version": "0.1.0", "dependencies": { "@clerk/nextjs": "^4.23.5", + "@emoji-mart/data": "^1.1.2", + "@emoji-mart/react": "^1.1.1", "@hookform/resolvers": "^3.3.1", "@prisma/client": "^5.3.0", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-separator": "^1.0.3", @@ -30,6 +33,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "emoji-mart": "^5.5.2", "eslint": "8.49.0", "eslint-config-next": "13.4.19", "lucide-react": "^0.277.0", @@ -225,6 +229,20 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "node_modules/@emoji-mart/data": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.1.2.tgz", + "integrity": "sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==" + }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -997,6 +1015,43 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz", + "integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.4", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.3", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.2", + "@radix-ui/react-portal": "1.0.3", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz", @@ -2809,6 +2864,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz", "integrity": "sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g==" }, + "node_modules/emoji-mart": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz", + "integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -6514,6 +6574,17 @@ } } }, + "@emoji-mart/data": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.1.2.tgz", + "integrity": "sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==" + }, + "@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "requires": {} + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -6963,6 +7034,29 @@ "react-remove-scroll": "2.5.5" } }, + "@radix-ui/react-popover": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz", + "integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.4", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.3", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.2", + "@radix-ui/react-portal": "1.0.3", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + } + }, "@radix-ui/react-popper": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz", @@ -8185,6 +8279,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.520.tgz", "integrity": "sha512-Frfus2VpYADsrh1lB3v/ft/WVFlVzOIm+Q0p7U7VqHI6qr7NWHYKe+Wif3W50n7JAFoBsWVsoU0+qDks6WQ60g==" }, + "emoji-mart": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.5.2.tgz", + "integrity": "sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==" + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", diff --git a/package.json b/package.json index f900ada..e9d685d 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ }, "dependencies": { "@clerk/nextjs": "^4.23.5", + "@emoji-mart/data": "^1.1.2", + "@emoji-mart/react": "^1.1.1", "@hookform/resolvers": "^3.3.1", "@prisma/client": "^5.3.0", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.5", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-scroll-area": "^1.0.4", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-separator": "^1.0.3", @@ -31,6 +34,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "emoji-mart": "^5.5.2", "eslint": "8.49.0", "eslint-config-next": "13.4.19", "lucide-react": "^0.277.0", From 1e372302add058e95c2241e92086c3eb1143ced5 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 12:30:17 -0400 Subject: [PATCH 4/9] adding chat item to display --- .../[serverId]/channels/[channelId]/page.tsx | 16 +++- app/api/messages/route.ts | 80 +++++++++++++++++++ app/layout.tsx | 3 +- components/chat/chat-messages.tsx | 76 ++++++++++++++++++ components/chat/chat-welcome.tsx | 27 +++++++ components/providers/query-provider.tsx | 10 +++ hooks/use-chat-query.ts | 51 ++++++++++++ package-lock.json | 50 ++++++++++++ package.json | 1 + 9 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 app/api/messages/route.ts create mode 100644 components/chat/chat-messages.tsx create mode 100644 components/chat/chat-welcome.tsx create mode 100644 components/providers/query-provider.tsx create mode 100644 hooks/use-chat-query.ts diff --git a/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx b/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx index 8db9c20..7f99b78 100644 --- a/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx +++ b/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx @@ -1,5 +1,6 @@ import { ChatHeader } from "@/components/chat/chat-header"; import { ChatInput } from "@/components/chat/chat-input"; +import { ChatMessages } from "@/components/chat/chat-messages"; import { currentProfile } from "@/lib/current-profile"; import { db } from "@/lib/db"; import { redirectToSignIn } from "@clerk/nextjs"; @@ -43,7 +44,20 @@ const channelIdPage = async ({ params }: channelIdPageProps) => { serverId={channel.serverId} type="channel" /> -
Future Messages
+ - {children} + {children} diff --git a/components/chat/chat-messages.tsx b/components/chat/chat-messages.tsx new file mode 100644 index 0000000..50e0524 --- /dev/null +++ b/components/chat/chat-messages.tsx @@ -0,0 +1,76 @@ +"use client"; + +import { Member, Message, Profile } from "@prisma/client"; +import { ChatWelcome } from "./chat-welcome"; +import { useChatQuery } from "@/hooks/use-chat-query"; +import { Loader2, ServerCrash } from "lucide-react"; +import { Fragment } from "react"; + +type MessageWithMemberWithProfile = Message & { + member: Member & { profile: Profile }; +}; + +interface ChatMessagesProps { + name: string; + member: Member; + chatId: string; + apiUrl: string; + socketUrl: string; + socketQuery: Record; + paramKey: "channelId" | "conversationId"; + paramValue: string; + type: "channel" | "conversation"; +} + +export const ChatMessages = ({ + name, + member, + chatId, + apiUrl, + socketUrl, + socketQuery, + paramKey, + paramValue, + type, +}: ChatMessagesProps) => { + const queryKey = `chat:${chatId}`; + const { data, fetchMessages, hasNextPage, isFetchingNextPage, status } = + useChatQuery({ queryKey, apiUrl, paramKey, paramValue }); + + if (status === "loading") { + return ( +
+ +

+ Loading messages... +

+
+ ); + } + + if (status === "error") { + return ( +
+ +

+ Something went wrong! +

+
+ ); + } + return ( +
+
+ +
+ {data?.pages.map((group, i) => ( + + {group.items.map((message: MessageWithMemberWithProfile) => ( +
{message.content}
+ ))} +
+ ))} +
+
+ ); +}; diff --git a/components/chat/chat-welcome.tsx b/components/chat/chat-welcome.tsx new file mode 100644 index 0000000..54b7613 --- /dev/null +++ b/components/chat/chat-welcome.tsx @@ -0,0 +1,27 @@ +import { Hash } from "lucide-react"; + +interface ChatWelcomeProps { + name: string; + type: "channel" | "conversation"; +} + +export const ChatWelcome = ({ name, type }: ChatWelcomeProps) => { + return ( +
+ {type === "channel" && ( +
+ +
+ )} +

+ {type === "channel" ? "Welcome to #" : ""} + {name} +

+

+ {type === "channel" + ? `This is the start of the #${name} channel.` + : `This is the start of your conversation with ${name}.`} +

+
+ ); +}; diff --git a/components/providers/query-provider.tsx b/components/providers/query-provider.tsx new file mode 100644 index 0000000..a94220b --- /dev/null +++ b/components/providers/query-provider.tsx @@ -0,0 +1,10 @@ +"use client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { useState } from "react"; + +export const QueryProvider = ({ children }: { children: React.ReactNode }) => { + const [queryClient] = useState(() => new QueryClient()); + return ( + {children} + ); +}; diff --git a/hooks/use-chat-query.ts b/hooks/use-chat-query.ts new file mode 100644 index 0000000..ecfbd28 --- /dev/null +++ b/hooks/use-chat-query.ts @@ -0,0 +1,51 @@ +import qs from "query-string"; +import { useInfiniteQuery } from "@tanstack/react-query"; + +import { useSocket } from "@/components/providers/socket-provider"; + +interface ChatQueryProps { + queryKey: string; + apiUrl: string; + paramKey: "channelId" | "conversationId"; + paramValue: string; +} + +export const useChatQuery = ({ + queryKey, + apiUrl, + paramKey, + paramValue, +}: ChatQueryProps) => { + const { isConnected } = useSocket(); + + const fetchMessages = async ({ pageParam = undefined }) => { + const url = qs.stringifyUrl( + { + url: apiUrl, + query: { + cursor: pageParam, + [paramKey]: paramValue, + }, + }, + { skipNull: true } + ); + const res = await fetch(url); + return res.json(); + }; + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = + useInfiniteQuery({ + queryKey: [queryKey], + queryFn: fetchMessages, + getNextPageParam: (lastPage) => lastPage?.nextCursor, + refetchInterval: isConnected ? false : 1000, + }); + + return { + data, + fetchMessages, + hasNextPage, + isFetchingNextPage, + status, + }; +}; diff --git a/package-lock.json b/package-lock.json index 29db426..8fea084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.6", + "@tanstack/react-query": "^4.35.3", "@types/node": "20.6.0", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", @@ -1506,6 +1507,41 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "4.35.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.35.3.tgz", + "integrity": "sha512-PS+WEjd9wzKTyNjjQymvcOe1yg8f3wYc6mD+vb6CKyZAKvu4sIJwryfqfBULITKCla7P9C4l5e9RXePHvZOZeQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "4.35.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.35.3.tgz", + "integrity": "sha512-UgTPioip/rGG3EQilXfA2j4BJkhEQsR+KAbF+KIuvQ7j4MkgnTCJF01SfRpIRNtQTlEfz/+IL7+jP8WA8bFbsw==", + "dependencies": { + "@tanstack/query-core": "4.35.3", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -7299,6 +7335,20 @@ "tslib": "^2.4.0" } }, + "@tanstack/query-core": { + "version": "4.35.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.35.3.tgz", + "integrity": "sha512-PS+WEjd9wzKTyNjjQymvcOe1yg8f3wYc6mD+vb6CKyZAKvu4sIJwryfqfBULITKCla7P9C4l5e9RXePHvZOZeQ==" + }, + "@tanstack/react-query": { + "version": "4.35.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.35.3.tgz", + "integrity": "sha512-UgTPioip/rGG3EQilXfA2j4BJkhEQsR+KAbF+KIuvQ7j4MkgnTCJF01SfRpIRNtQTlEfz/+IL7+jP8WA8bFbsw==", + "requires": { + "@tanstack/query-core": "4.35.3", + "use-sync-external-store": "^1.2.0" + } + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", diff --git a/package.json b/package.json index e9d685d..2930e83 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.6", + "@tanstack/react-query": "^4.35.3", "@types/node": "20.6.0", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", From 070d10bd5dc1bbd355d413f426470016468af796 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 14:08:10 -0400 Subject: [PATCH 5/9] edit message working --- .../[serverId]/channels/[channelId]/page.tsx | 2 +- components/action-tooltip.tsx | 2 +- components/chat/chat-item.tsx | 230 ++++++++++++++++++ components/chat/chat-messages.tsx | 18 +- package-lock.json | 24 ++ package.json | 1 + pages/api/socket/messages/[messageId].ts | 132 ++++++++++ .../socket/{messages.ts => messages/index.ts} | 0 8 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 components/chat/chat-item.tsx create mode 100644 pages/api/socket/messages/[messageId].ts rename pages/api/socket/{messages.ts => messages/index.ts} (100%) diff --git a/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx b/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx index 7f99b78..de66292 100644 --- a/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx +++ b/app/(main)/(routes)/server/[serverId]/channels/[channelId]/page.tsx @@ -50,7 +50,7 @@ const channelIdPage = async ({ params }: channelIdPageProps) => { chatId={channel.id} type="channel" apiUrl="/api/messages" - socketUrl="/api/socket/messagse" + socketUrl="/api/socket/messages" socketQuery={{ channelId: channel.id, serverId: channel.serverId, diff --git a/components/action-tooltip.tsx b/components/action-tooltip.tsx index 98246e5..0f95721 100644 --- a/components/action-tooltip.tsx +++ b/components/action-tooltip.tsx @@ -18,7 +18,7 @@ export const ActionTooltip = ({ label, children, side, - align, + align = "start", }: ActionTooltipProps) => { return ( diff --git a/components/chat/chat-item.tsx b/components/chat/chat-item.tsx new file mode 100644 index 0000000..e2b15fa --- /dev/null +++ b/components/chat/chat-item.tsx @@ -0,0 +1,230 @@ +"use client"; + +import { Member, MemberRole, Profile } from "@prisma/client"; +import { UserAvatar } from "@/components/user-avatar"; +import { ActionTooltip } from "../action-tooltip"; +import { Edit, FileIcon, ShieldAlert, ShieldCheck, Trash } from "lucide-react"; +import Image from "next/image"; +import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; +import * as z from "zod"; +import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; +import axios from "axios"; +import qs from "query-string"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { eventNames } from "process"; + +interface ChatItemProps { + id: string; + content: string; + member: Member & { + profile: Profile; + }; + timestamp: string; + fileUrl: string | null; + deleted: boolean; + currentMember: Member; + isUpdate: boolean; + socketUrl: string; + socketQuery: Record; +} + +const roleIconMap = { + GUEST: null, + MODERATOR: , + ADMIN: , +}; + +const formSchema = z.object({ + content: z.string().min(1), +}); + +export const ChatItem = ({ + id, + content, + member, + timestamp, + fileUrl, + deleted, + currentMember, + isUpdate, + socketUrl, + socketQuery, +}: ChatItemProps) => { + const [isEditing, setIsEditing] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + content: content, + }, + }); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape" || e.keyCode === 27) { + setIsEditing(false); + } + }; + window.addEventListener("keydown", handleKeyDown); + + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); + + useEffect(() => { + form.reset({ + content: content, + }); + }, [form, content]); + + const isLoading = form.formState.isSubmitting; + + const onSubmit = async (values: z.infer) => { + try { + const url = qs.stringifyUrl({ + url: `${socketUrl}/${id}`, + query: socketQuery, + }); + await axios.patch(url, values); + + form.reset(); + setIsEditing(false); + } catch (error) { + console.log(error); + } + }; + + const fileType = fileUrl?.split(".").pop(); + + const isAdmin = currentMember.role === MemberRole.ADMIN; + const isModerator = currentMember.role === MemberRole.MODERATOR; + const isOwner = currentMember.id === member.id; + + const canDeleteMessage = !deleted && (isAdmin || isModerator || isOwner); + + const canEditMessage = !deleted && isOwner && !fileUrl; + + const isPDF = fileType === "pdf" && fileUrl; + const isImage = !isPDF && fileUrl; + + return ( +
+
+
+ +
+
+
+
+

+ {member.profile.name} +

+ + {roleIconMap[member.role]} + +
+ + {timestamp} + +
+ {isImage && ( + + {content} + + )} + {isPDF && ( + + )} + {!fileUrl && !isEditing && ( +

+ {content} + {isUpdate && !deleted && ( + + (edited) + + )} +

+ )} + {!fileUrl && isEditing && ( +
+ + ( + + +
+ +
+
+
+ )} + /> + + + + Press ESC to cancel, enter to save + + + )} +
+
+ {canDeleteMessage && ( +
+ {canEditMessage && ( + + setIsEditing(true)} + className="w-4 h-4 ml-auto transition cursor-pointer text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300" + /> + + )} + + + +
+ )} +
+ ); +}; diff --git a/components/chat/chat-messages.tsx b/components/chat/chat-messages.tsx index 50e0524..ad2c6b3 100644 --- a/components/chat/chat-messages.tsx +++ b/components/chat/chat-messages.tsx @@ -5,6 +5,10 @@ import { ChatWelcome } from "./chat-welcome"; import { useChatQuery } from "@/hooks/use-chat-query"; import { Loader2, ServerCrash } from "lucide-react"; import { Fragment } from "react"; +import { ChatItem } from "./chat-item"; +import { format } from "date-fns"; + +const DATE_FORMAT = "d MMM yyyy, HH:mm"; type MessageWithMemberWithProfile = Message & { member: Member & { profile: Profile }; @@ -66,7 +70,19 @@ export const ChatMessages = ({ {data?.pages.map((group, i) => ( {group.items.map((message: MessageWithMemberWithProfile) => ( -
{message.content}
+ ))}
))} diff --git a/package-lock.json b/package-lock.json index 8fea084..2ee3712 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "date-fns": "^2.30.0", "emoji-mart": "^5.5.2", "eslint": "8.49.0", "eslint-config-next": "13.4.19", @@ -2767,6 +2768,21 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8231,6 +8247,14 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 2930e83..01015d3 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", + "date-fns": "^2.30.0", "emoji-mart": "^5.5.2", "eslint": "8.49.0", "eslint-config-next": "13.4.19", diff --git a/pages/api/socket/messages/[messageId].ts b/pages/api/socket/messages/[messageId].ts new file mode 100644 index 0000000..813a9dd --- /dev/null +++ b/pages/api/socket/messages/[messageId].ts @@ -0,0 +1,132 @@ +import { currentProfilePages } from "@/lib/current-profile-pages"; +import { db } from "@/lib/db"; +import { NextApiResponseServerIo } from "@/types"; +import { MemberRole } from "@prisma/client"; +import { NextApiRequest } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponseServerIo +) { + if (req.method !== "DELETE" && req.method !== "PATCH") { + return res.status(405).json({ message: "Method not allowed" }); + } + try { + const profile = await currentProfilePages(req); + const { messageId, serverId, channelId } = req.query; + const { content } = req.body; + + if (!profile) return res.status(401).json({ message: "Unauthorized" }); + + if (!serverId) + return res.status(400).json({ message: "Server ID Missing" }); + + if (!channelId) + return res.status(400).json({ message: "Channel ID Missing" }); + + const server = await db.server.findFirst({ + where: { + id: serverId as string, + members: { + some: { + profileId: profile.id, + }, + }, + }, + include: { + members: true, + }, + }); + + if (!server) return res.status(404).json({ message: "Server not found" }); + + const channel = await db.channel.findFirst({ + where: { + id: channelId as string, + serverId: serverId as string, + }, + }); + + if (!channel) return res.status(404).json({ message: "Channel not found" }); + + const member = server.members.find( + (member) => member.profileId === profile.id + ); + + if (!member) return res.status(404).json({ message: "Member not found" }); + + let message = await db.message.findFirst({ + where: { + id: messageId as string, + channelId: channelId as string, + }, + include: { + member: { + include: { + profile: true, + }, + }, + }, + }); + + if (!message || message.deleted) + return res.status(404).json({ message: "Message not found" }); + + const isMessageOwner = message.memberId === member.id; + const isAdmin = member.role === MemberRole.ADMIN; + const isModerator = member.role === MemberRole.MODERATOR; + const canModify = isMessageOwner || isAdmin || isModerator; + + if (!canModify) return res.status(401).json({ message: "Unauthorized" }); + + if (req.method === "DELETE") { + message = await db.message.update({ + where: { + id: messageId as string, + }, + data: { + fileUrl: null, + content: "This message has been deleted", + deleted: true, + }, + include: { + member: { + include: { + profile: true, + }, + }, + }, + }); + } + + if (req.method === "PATCH") { + if (!isMessageOwner) + return res.status(401).json({ message: "Unauthorized" }); + + message = await db.message.update({ + where: { + id: messageId as string, + }, + data: { + content: content, + }, + include: { + member: { + include: { + profile: true, + }, + }, + }, + }); + } + + const updateKey = `chat:${channelId}:messages:update`; + + res?.socket?.server?.io?.emit(updateKey, message); + + return res.status(200).json(message); + } catch (error) { + console.log("[MESSAGE_ID]", error); + return res.status(500).json({ message: "Internal error" }); + } +} diff --git a/pages/api/socket/messages.ts b/pages/api/socket/messages/index.ts similarity index 100% rename from pages/api/socket/messages.ts rename to pages/api/socket/messages/index.ts From ce4158a9d239d6b218aa965f664b2090d63d61b2 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 14:20:55 -0400 Subject: [PATCH 6/9] message delete working --- components/chat/chat-item.tsx | 13 +++- components/modals/delete-message-modal.tsx | 70 ++++++++++++++++++++++ components/providers/modal-provider.tsx | 2 + hooks/use-modal-store.ts | 3 +- 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 components/modals/delete-message-modal.tsx diff --git a/components/chat/chat-item.tsx b/components/chat/chat-item.tsx index e2b15fa..a555d1a 100644 --- a/components/chat/chat-item.tsx +++ b/components/chat/chat-item.tsx @@ -16,6 +16,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { eventNames } from "process"; +import { useModal } from "@/hooks/use-modal-store"; interface ChatItemProps { id: string; @@ -55,7 +56,7 @@ export const ChatItem = ({ socketQuery, }: ChatItemProps) => { const [isEditing, setIsEditing] = useState(false); - const [isDeleting, setIsDeleting] = useState(false); + const { onOpen } = useModal(); const form = useForm>({ resolver: zodResolver(formSchema), @@ -221,7 +222,15 @@ export const ChatItem = ({ )} - + + onOpen("deleteMessage", { + apiUrl: `${socketUrl}/${id}`, + query: socketQuery, + }) + } + className="w-4 h-4 ml-auto transition cursor-pointer text-zinc-500 hover:text-zinc-600 dark:hover:text-zinc-300" + />
)} diff --git a/components/modals/delete-message-modal.tsx b/components/modals/delete-message-modal.tsx new file mode 100644 index 0000000..029292f --- /dev/null +++ b/components/modals/delete-message-modal.tsx @@ -0,0 +1,70 @@ +"use client"; + +import axios from "axios"; +import { useState } from "react"; +import qs from "query-string"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useModal } from "@/hooks/use-modal-store"; +import { Button } from "@/components/ui/button"; + +export const DeleteMessageModal = () => { + const { isOpen, onClose, type, data } = useModal(); + + const isModalOpen = isOpen && type === "deleteMessage"; + const { apiUrl, query } = data; + + const [isLoading, setIsLoading] = useState(false); + + const onClick = async () => { + try { + setIsLoading(true); + const url = qs.stringifyUrl({ + url: apiUrl || "", + query, + }); + + await axios.delete(url); + + onClose(); + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + + Delete Message + + + Are you sure you want to do this? +
+ The message will be deleted permanently. +
+
+ +
+ + +
+
+
+
+ ); +}; diff --git a/components/providers/modal-provider.tsx b/components/providers/modal-provider.tsx index 4771f16..cbc0e32 100644 --- a/components/providers/modal-provider.tsx +++ b/components/providers/modal-provider.tsx @@ -11,6 +11,7 @@ import { DeleteServerModal } from "@/components/modals/delete-server-modal"; import { DeleteChannelModal } from "@/components/modals/delete-channel-modal"; import { EditChannelModal } from "@/components/modals/edit-channel-modal"; import MessageFileModal from "@/components/modals/message-file-modal"; +import { DeleteMessageModal } from "@/components/modals/delete-message-modal"; export const ModalProvider = () => { const [isMounted, setIsMounted] = useState(false); @@ -35,6 +36,7 @@ export const ModalProvider = () => { + ); }; diff --git a/hooks/use-modal-store.ts b/hooks/use-modal-store.ts index b058716..0b0f3cf 100644 --- a/hooks/use-modal-store.ts +++ b/hooks/use-modal-store.ts @@ -12,7 +12,8 @@ export type ModalType = | "deleteServer" | "deleteChannel" | "editChannel" - | "messageFile"; + | "messageFile" + | "deleteMessage"; interface ModalData { server?: Server; From 07773dd3bcebe9b18c575d7c997d1437e72105a4 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 15:44:45 -0400 Subject: [PATCH 7/9] bug fix and link to use message working --- components/chat/chat-item.tsx | 21 +- next.config.js | 8 + package-lock.json | 403 ++++++++++++++++++---------------- package.json | 4 +- tsconfig.json | 24 +- 5 files changed, 258 insertions(+), 202 deletions(-) diff --git a/components/chat/chat-item.tsx b/components/chat/chat-item.tsx index a555d1a..1e73b3d 100644 --- a/components/chat/chat-item.tsx +++ b/components/chat/chat-item.tsx @@ -15,7 +15,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { eventNames } from "process"; +import { useRouter, useParams } from "next/navigation"; import { useModal } from "@/hooks/use-modal-store"; interface ChatItemProps { @@ -58,6 +58,15 @@ export const ChatItem = ({ const [isEditing, setIsEditing] = useState(false); const { onOpen } = useModal(); + const params = useParams(); + const router = useRouter(); + + const onMemberClick = () => { + if (member.id === currentMember.id) return; + + router.push(`/server/${params?.serverId}/conversations/${member.id}`); + }; + const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -115,13 +124,19 @@ export const ChatItem = ({ return (
-
+
-

+

{member.profile.name}

diff --git a/next.config.js b/next.config.js index 7808557..490fcac 100644 --- a/next.config.js +++ b/next.config.js @@ -1,5 +1,13 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + webpack: (config) => { + config.externals.push({ + "utf-8-validate": "commonjs utf-8-validate", + bufferutil: "commonjs bufferutil", + }); + + return config; + }, images: { domains: ["utfs.io"], }, diff --git a/package-lock.json b/package-lock.json index 2ee3712..c0aef84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,9 +37,9 @@ "date-fns": "^2.30.0", "emoji-mart": "^5.5.2", "eslint": "8.49.0", - "eslint-config-next": "13.4.19", + "eslint-config-next": "13.4.12", "lucide-react": "^0.277.0", - "next": "13.4.19", + "next": "13.4.12", "next-themes": "^0.2.1", "postcss": "8.4.29", "query-string": "^8.1.0", @@ -413,22 +413,22 @@ } }, "node_modules/@next/env": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", - "integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==" + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.12.tgz", + "integrity": "sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", - "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.12.tgz", + "integrity": "sha512-6rhK9CdxEgj/j1qvXIyLTWEaeFv7zOK8yJMulz3Owel0uek0U9MJCGzmKgYxM3aAUBo3gKeywCZKyQnJKto60A==", "dependencies": { "glob": "7.1.7" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", - "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.12.tgz", + "integrity": "sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==", "cpu": [ "arm64" ], @@ -441,9 +441,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", - "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", + "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", "cpu": [ "x64" ], @@ -456,9 +456,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", - "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", + "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", "cpu": [ "arm64" ], @@ -471,9 +471,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", - "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", + "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", "cpu": [ "arm64" ], @@ -486,9 +486,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", - "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", + "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", "cpu": [ "x64" ], @@ -501,9 +501,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", - "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", + "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", "cpu": [ "x64" ], @@ -516,9 +516,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", - "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", + "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", - "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", + "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", "cpu": [ "ia32" ], @@ -546,9 +546,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", - "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", + "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", "cpu": [ "x64" ], @@ -1703,25 +1703,24 @@ "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==" }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.0.tgz", - "integrity": "sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==", - "dependencies": { - "@typescript-eslint/scope-manager": "6.7.0", - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/typescript-estree": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1730,15 +1729,15 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", - "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dependencies": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1746,11 +1745,11 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", - "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1758,20 +1757,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", - "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dependencies": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.3.7", + "tsutils": "^3.21.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -1784,15 +1783,15 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", - "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dependencies": { - "@typescript-eslint/types": "6.7.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "type": "opencollective", @@ -3169,19 +3168,19 @@ } }, "node_modules/eslint-config-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", - "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.12.tgz", + "integrity": "sha512-ZF0r5vxKaVazyZH/37Au/XItiG7qUOBw+HaH3PeyXltIMwXorsn6bdrl0Nn9N5v5v9spc+6GM2ryjugbjF6X2g==", "dependencies": { - "@next/eslint-plugin-next": "13.4.19", + "@next/eslint-plugin-next": "13.4.12", "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + "eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0", @@ -3383,9 +3382,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", "engines": { "node": ">=10" }, @@ -4637,11 +4636,11 @@ } }, "node_modules/next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", - "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.12.tgz", + "integrity": "sha512-eHfnru9x6NRmTMcjQp6Nz0J4XH9OubmzOa7CkWL+AUrUxpibub3vWwttjduu9No16dug1kq04hiUUpo7J3m3Xw==", "dependencies": { - "@next/env": "13.4.19", + "@next/env": "13.4.12", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -4657,18 +4656,19 @@ "node": ">=16.8.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.19", - "@next/swc-darwin-x64": "13.4.19", - "@next/swc-linux-arm64-gnu": "13.4.19", - "@next/swc-linux-arm64-musl": "13.4.19", - "@next/swc-linux-x64-gnu": "13.4.19", - "@next/swc-linux-x64-musl": "13.4.19", - "@next/swc-win32-arm64-msvc": "13.4.19", - "@next/swc-win32-ia32-msvc": "13.4.19", - "@next/swc-win32-x64-msvc": "13.4.19" + "@next/swc-darwin-arm64": "13.4.12", + "@next/swc-darwin-x64": "13.4.12", + "@next/swc-linux-arm64-gnu": "13.4.12", + "@next/swc-linux-arm64-musl": "13.4.12", + "@next/swc-linux-x64-gnu": "13.4.12", + "@next/swc-linux-x64-musl": "13.4.12", + "@next/swc-win32-arm64-msvc": "13.4.12", + "@next/swc-win32-ia32-msvc": "13.4.12", + "@next/swc-win32-x64-msvc": "13.4.12" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "fibers": ">= 3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -4677,6 +4677,9 @@ "@opentelemetry/api": { "optional": true }, + "fibers": { + "optional": true + }, "sass": { "optional": true } @@ -6023,17 +6026,6 @@ "to-no-case": "^1.0.0" } }, - "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "engines": { - "node": ">=16.13.0" - }, - "peerDependencies": { - "typescript": ">=4.2.0" - } - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -6055,6 +6047,25 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6762,70 +6773,70 @@ } }, "@next/env": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", - "integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==" + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.12.tgz", + "integrity": "sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==" }, "@next/eslint-plugin-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", - "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.12.tgz", + "integrity": "sha512-6rhK9CdxEgj/j1qvXIyLTWEaeFv7zOK8yJMulz3Owel0uek0U9MJCGzmKgYxM3aAUBo3gKeywCZKyQnJKto60A==", "requires": { "glob": "7.1.7" } }, "@next/swc-darwin-arm64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", - "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.12.tgz", + "integrity": "sha512-deUrbCXTMZ6ZhbOoloqecnUeNpUOupi8SE2tx4jPfNS9uyUR9zK4iXBvH65opVcA/9F5I/p8vDXSYbUlbmBjZg==", "optional": true }, "@next/swc-darwin-x64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", - "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.12.tgz", + "integrity": "sha512-WRvH7RxgRHlC1yb5oG0ZLx8F7uci9AivM5/HGGv9ZyG2Als8Ij64GC3d+mQ5sJhWjusyU6T6V1WKTUoTmOB0zQ==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", - "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.12.tgz", + "integrity": "sha512-YEKracAWuxp54tKiAvvq73PUs9lok57cc8meYRibTWe/VdPB2vLgkTVWFcw31YDuRXdEhdX0fWS6Q+ESBhnEig==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", - "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.12.tgz", + "integrity": "sha512-LhJR7/RAjdHJ2Isl2pgc/JaoxNk0KtBgkVpiDJPVExVWA1c6gzY57+3zWuxuyWzTG+fhLZo2Y80pLXgIJv7g3g==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", - "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.12.tgz", + "integrity": "sha512-1DWLL/B9nBNiQRng+1aqs3OaZcxC16Nf+mOnpcrZZSdyKHek3WQh6j/fkbukObgNGwmCoVevLUa/p3UFTTqgqg==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", - "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.12.tgz", + "integrity": "sha512-kEAJmgYFhp0VL+eRWmUkVxLVunn7oL9Mdue/FS8yzRBVj7Z0AnIrHpTIeIUl1bbdQq1VaoOztnKicAjfkLTRCQ==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", - "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.12.tgz", + "integrity": "sha512-GMLuL/loR6yIIRTnPRY6UGbLL9MBdw2anxkOnANxvLvsml4F0HNIgvnU3Ej4BjbqMTNjD4hcPFdlEow4XHPdZA==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", - "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.12.tgz", + "integrity": "sha512-PhgNqN2Vnkm7XaMdRmmX0ZSwZXQAtamBVSa9A/V1dfKQCV1rjIZeiy/dbBnVYGdj63ANfsOR/30XpxP71W0eww==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", - "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.12.tgz", + "integrity": "sha512-Z+56e/Ljt0bUs+T+jPjhFyxYBcdY2RIq9ELFU+qAMQMteHo7ymbV7CKmlcX59RI9C4YzN8PgMgLyAoi916b5HA==", "optional": true }, "@nodelib/fs.scandir": { @@ -7525,52 +7536,51 @@ "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==" }, "@typescript-eslint/parser": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.0.tgz", - "integrity": "sha512-jZKYwqNpNm5kzPVP5z1JXAuxjtl2uG+5NpaMocFPTNC2EdYIgbXIPImObOkhbONxtFTTdoZstLZefbaK+wXZng==", - "requires": { - "@typescript-eslint/scope-manager": "6.7.0", - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/typescript-estree": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "requires": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.0.tgz", - "integrity": "sha512-lAT1Uau20lQyjoLUQ5FUMSX/dS07qux9rYd5FGzKz/Kf8W8ccuvMyldb8hadHdK/qOI7aikvQWqulnEq2nCEYA==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "requires": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" } }, "@typescript-eslint/types": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.0.tgz", - "integrity": "sha512-ihPfvOp7pOcN/ysoj0RpBPOx3HQTJTrIN8UZK+WFd3/iDeFHHqeyYxa4hQk4rMhsz9H9mXpR61IzwlBVGXtl9Q==" + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==" }, "@typescript-eslint/typescript-estree": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.0.tgz", - "integrity": "sha512-dPvkXj3n6e9yd/0LfojNU8VMUGHWiLuBZvbM6V6QYD+2qxqInE7J+J/ieY2iGwR9ivf/R/haWGkIj04WVUeiSQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "requires": { - "@typescript-eslint/types": "6.7.0", - "@typescript-eslint/visitor-keys": "6.7.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.3.7", + "tsutils": "^3.21.0" } }, "@typescript-eslint/visitor-keys": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.0.tgz", - "integrity": "sha512-/C1RVgKFDmGMcVGeD8HjKv2bd72oI1KxQDeY8uc66gw9R0OK0eMq48cA+jv9/2Ag6cdrsUGySm1yzYmfz0hxwQ==", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "requires": { - "@typescript-eslint/types": "6.7.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" } }, "@uploadthing/mime-types": { @@ -8563,19 +8573,19 @@ } }, "eslint-config-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", - "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.12.tgz", + "integrity": "sha512-ZF0r5vxKaVazyZH/37Au/XItiG7qUOBw+HaH3PeyXltIMwXorsn6bdrl0Nn9N5v5v9spc+6GM2ryjugbjF6X2g==", "requires": { - "@next/eslint-plugin-next": "13.4.19", + "@next/eslint-plugin-next": "13.4.12", "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "@typescript-eslint/parser": "^5.42.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + "eslint-plugin-react-hooks": "5.0.0-canary-7118f5dd7-20230705" } }, "eslint-import-resolver-node": { @@ -8756,9 +8766,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", "requires": {} }, "eslint-scope": { @@ -9597,20 +9607,20 @@ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", - "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", - "requires": { - "@next/env": "13.4.19", - "@next/swc-darwin-arm64": "13.4.19", - "@next/swc-darwin-x64": "13.4.19", - "@next/swc-linux-arm64-gnu": "13.4.19", - "@next/swc-linux-arm64-musl": "13.4.19", - "@next/swc-linux-x64-gnu": "13.4.19", - "@next/swc-linux-x64-musl": "13.4.19", - "@next/swc-win32-arm64-msvc": "13.4.19", - "@next/swc-win32-ia32-msvc": "13.4.19", - "@next/swc-win32-x64-msvc": "13.4.19", + "version": "13.4.12", + "resolved": "https://registry.npmjs.org/next/-/next-13.4.12.tgz", + "integrity": "sha512-eHfnru9x6NRmTMcjQp6Nz0J4XH9OubmzOa7CkWL+AUrUxpibub3vWwttjduu9No16dug1kq04hiUUpo7J3m3Xw==", + "requires": { + "@next/env": "13.4.12", + "@next/swc-darwin-arm64": "13.4.12", + "@next/swc-darwin-x64": "13.4.12", + "@next/swc-linux-arm64-gnu": "13.4.12", + "@next/swc-linux-arm64-musl": "13.4.12", + "@next/swc-linux-x64-gnu": "13.4.12", + "@next/swc-linux-x64-musl": "13.4.12", + "@next/swc-win32-arm64-msvc": "13.4.12", + "@next/swc-win32-ia32-msvc": "13.4.12", + "@next/swc-win32-x64-msvc": "13.4.12", "@swc/helpers": "0.5.1", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -10503,12 +10513,6 @@ "to-no-case": "^1.0.0" } }, - "ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", - "requires": {} - }, "ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -10530,6 +10534,21 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 01015d3..90e62bb 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,9 @@ "date-fns": "^2.30.0", "emoji-mart": "^5.5.2", "eslint": "8.49.0", - "eslint-config-next": "13.4.19", + "eslint-config-next": "13.4.12", "lucide-react": "^0.277.0", - "next": "13.4.19", + "next": "13.4.12", "next-themes": "^0.2.1", "postcss": "8.4.29", "query-string": "^8.1.0", diff --git a/tsconfig.json b/tsconfig.json index c714696..7cded7b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,9 +23,19 @@ } ], "paths": { - "@/*": ["./*"] - } + "@/*": [ + "./*" + ] + }, + "forceConsistentCasingInFileNames": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } From e42ceff066acb3e0a541f273eee121058d7bb51b Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 16:03:39 -0400 Subject: [PATCH 8/9] messageing working --- components/chat/chat-messages.tsx | 6 +++ hooks/use-chat-socket.ts | 88 +++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 hooks/use-chat-socket.ts diff --git a/components/chat/chat-messages.tsx b/components/chat/chat-messages.tsx index ad2c6b3..1765b94 100644 --- a/components/chat/chat-messages.tsx +++ b/components/chat/chat-messages.tsx @@ -7,6 +7,7 @@ import { Loader2, ServerCrash } from "lucide-react"; import { Fragment } from "react"; import { ChatItem } from "./chat-item"; import { format } from "date-fns"; +import { useChatSocket } from "@/hooks/use-chat-socket"; const DATE_FORMAT = "d MMM yyyy, HH:mm"; @@ -38,9 +39,14 @@ export const ChatMessages = ({ type, }: ChatMessagesProps) => { const queryKey = `chat:${chatId}`; + const addKey = `chat:${chatId}:messages`; + const updateKey = `chat:${chatId}:messages:update`; + const { data, fetchMessages, hasNextPage, isFetchingNextPage, status } = useChatQuery({ queryKey, apiUrl, paramKey, paramValue }); + useChatSocket({ queryKey, addKey, updateKey }); + if (status === "loading") { return (
diff --git a/hooks/use-chat-socket.ts b/hooks/use-chat-socket.ts new file mode 100644 index 0000000..1b54372 --- /dev/null +++ b/hooks/use-chat-socket.ts @@ -0,0 +1,88 @@ +import { useEffect } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { Member, Message, Profile } from "@prisma/client"; + +import { useSocket } from "@/components/providers/socket-provider"; + +type ChatSocketProps = { + addKey: string; + updateKey: string; + queryKey: string; +}; + +type MessageWithMemberWithProfile = Message & { + member: Member & { + profile: Profile; + }; +}; + +export const useChatSocket = ({ + addKey, + updateKey, + queryKey, +}: ChatSocketProps) => { + const { socket } = useSocket(); + const queryClient = useQueryClient(); + + useEffect(() => { + if (!socket) { + return; + } + + socket.on(updateKey, (message: MessageWithMemberWithProfile) => { + queryClient.setQueryData([queryKey], (oldData: any) => { + if (!oldData || !oldData.pages || oldData.pages.length === 0) { + return oldData; + } + + const newData = oldData.pages.map((page: any) => { + return { + ...page, + items: page.items.map((item: MessageWithMemberWithProfile) => { + if (item.id === message.id) { + return message; + } + return item; + }), + }; + }); + + return { + ...oldData, + pages: newData, + }; + }); + }); + + socket.on(addKey, (message: MessageWithMemberWithProfile) => { + queryClient.setQueryData([queryKey], (oldData: any) => { + if (!oldData || !oldData.pages || oldData.pages.length === 0) { + return { + pages: [ + { + items: [message], + }, + ], + }; + } + + const newData = [...oldData.pages]; + + newData[0] = { + ...newData[0], + items: [message, ...newData[0].items], + }; + + return { + ...oldData, + pages: newData, + }; + }); + }); + + return () => { + socket.off(addKey); + socket.off(updateKey); + }; + }, [queryClient, addKey, queryKey, socket, updateKey]); +}; From 0e361757906f6ee361556281bc3890317d9e5505 Mon Sep 17 00:00:00 2001 From: YubaNeupane Date: Thu, 21 Sep 2023 16:29:03 -0400 Subject: [PATCH 9/9] message sending working --- components/chat/chat-messages.tsx | 61 ++++++++++++++++++++++------- hooks/use-chat-query.ts | 2 +- hooks/use-chat-scroll.ts | 64 +++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 hooks/use-chat-scroll.ts diff --git a/components/chat/chat-messages.tsx b/components/chat/chat-messages.tsx index 1765b94..01d04e0 100644 --- a/components/chat/chat-messages.tsx +++ b/components/chat/chat-messages.tsx @@ -1,18 +1,23 @@ "use client"; +import { Fragment, useRef, ElementRef } from "react"; +import { format } from "date-fns"; import { Member, Message, Profile } from "@prisma/client"; -import { ChatWelcome } from "./chat-welcome"; -import { useChatQuery } from "@/hooks/use-chat-query"; import { Loader2, ServerCrash } from "lucide-react"; -import { Fragment } from "react"; -import { ChatItem } from "./chat-item"; -import { format } from "date-fns"; + +import { useChatQuery } from "@/hooks/use-chat-query"; import { useChatSocket } from "@/hooks/use-chat-socket"; +import { useChatScroll } from "@/hooks/use-chat-scroll"; + +import { ChatWelcome } from "./chat-welcome"; +import { ChatItem } from "./chat-item"; const DATE_FORMAT = "d MMM yyyy, HH:mm"; type MessageWithMemberWithProfile = Message & { - member: Member & { profile: Profile }; + member: Member & { + profile: Profile; + }; }; interface ChatMessagesProps { @@ -42,10 +47,24 @@ export const ChatMessages = ({ const addKey = `chat:${chatId}:messages`; const updateKey = `chat:${chatId}:messages:update`; - const { data, fetchMessages, hasNextPage, isFetchingNextPage, status } = - useChatQuery({ queryKey, apiUrl, paramKey, paramValue }); + const chatRef = useRef>(null); + const bottomRef = useRef>(null); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = + useChatQuery({ + queryKey, + apiUrl, + paramKey, + paramValue, + }); useChatSocket({ queryKey, addKey, updateKey }); + useChatScroll({ + chatRef, + bottomRef, + loadMore: fetchNextPage, + shouldLoadMore: !isFetchingNextPage && !!hasNextPage, + count: data?.pages?.[0]?.items?.length ?? 0, + }); if (status === "loading") { return ( @@ -68,19 +87,34 @@ export const ChatMessages = ({
); } + return ( -
-
- +
+ {!hasNextPage &&
} + {!hasNextPage && } + {hasNextPage && ( +
+ {isFetchingNextPage ? ( + + ) : ( + + )} +
+ )}
- {data?.pages.map((group, i) => ( + {data?.pages?.map((group, i) => ( {group.items.map((message: MessageWithMemberWithProfile) => ( ))}
+
); }; diff --git a/hooks/use-chat-query.ts b/hooks/use-chat-query.ts index ecfbd28..5ba2b19 100644 --- a/hooks/use-chat-query.ts +++ b/hooks/use-chat-query.ts @@ -43,7 +43,7 @@ export const useChatQuery = ({ return { data, - fetchMessages, + fetchNextPage, hasNextPage, isFetchingNextPage, status, diff --git a/hooks/use-chat-scroll.ts b/hooks/use-chat-scroll.ts new file mode 100644 index 0000000..3d8c81a --- /dev/null +++ b/hooks/use-chat-scroll.ts @@ -0,0 +1,64 @@ +import { useEffect, useState } from "react"; + +type ChatScrollProps = { + chatRef: React.RefObject; + bottomRef: React.RefObject; + shouldLoadMore: boolean; + loadMore: () => void; + count: number; +}; + +export const useChatScroll = ({ + chatRef, + bottomRef, + shouldLoadMore, + loadMore, + count, +}: ChatScrollProps) => { + const [hasInitialized, setHasInitialized] = useState(false); + + useEffect(() => { + const topDiv = chatRef?.current; + + const handleScroll = () => { + const scrollTop = topDiv?.scrollTop; + + if (scrollTop === 0 && shouldLoadMore) { + loadMore(); + } + }; + + topDiv?.addEventListener("scroll", handleScroll); + + return () => { + topDiv?.removeEventListener("scroll", handleScroll); + }; + }, [shouldLoadMore, loadMore, chatRef]); + + useEffect(() => { + const bottomDiv = bottomRef?.current; + const topDiv = chatRef.current; + const shouldAutoScroll = () => { + if (!hasInitialized && bottomDiv) { + setHasInitialized(true); + return true; + } + + if (!topDiv) { + return false; + } + + const distanceFromBottom = + topDiv.scrollHeight - topDiv.scrollTop - topDiv.clientHeight; + return distanceFromBottom <= 100; + }; + + if (shouldAutoScroll()) { + setTimeout(() => { + bottomRef.current?.scrollIntoView({ + behavior: "smooth", + }); + }, 100); + } + }, [bottomRef, chatRef, count, hasInitialized]); +};