diff --git a/app/(invite)/(routes)/invite/[inviteCode]/page.tsx b/app/(invite)/(routes)/invite/[inviteCode]/page.tsx new file mode 100644 index 0000000..0a8c1c7 --- /dev/null +++ b/app/(invite)/(routes)/invite/[inviteCode]/page.tsx @@ -0,0 +1,62 @@ +import { currentProfile } from "@/lib/current-profile"; +import { redirectToSignIn } from "@clerk/nextjs"; +import { redirect } from "next/navigation"; +import { db } from "@/lib/db"; + +interface InviteCodePageProps { + params: { + inviteCode: string; + }; +}; + +const InviteCodePage = async ({ + params +}: InviteCodePageProps) => { + const profile = await currentProfile(); + + if (!profile) { + return redirectToSignIn(); + } + + if (!params?.inviteCode) { + return redirect("/"); + } + + const existingServer = await db.server.findFirst({ + where: { + inviteCode: params.inviteCode, + members: { + some: { + profileId: profile.id, + }, + }, + }, + }); + + if (existingServer) { + return redirect(`/servers/${existingServer.id}`); + } + + const server= await db.server.update({ + where: { + inviteCode: params.inviteCode, + }, + data: { + members: { + create: [ + { + profileId: profile.id, + }, + ], + }, + }, + }); + + if(server) { + return redirect(`/servers/${server.id}`); + } + + return null +} + +export default InviteCodePage; \ No newline at end of file diff --git a/app/api/servers/[serverId]/invite-code/route.ts b/app/api/servers/[serverId]/invite-code/route.ts new file mode 100644 index 0000000..beba2a4 --- /dev/null +++ b/app/api/servers/[serverId]/invite-code/route.ts @@ -0,0 +1,38 @@ +import { v4 as uuidv4 } from "uuid"; +import { NextResponse } from "next/server"; + +import { currentProfile } from "@/lib/current-profile"; +import { db } from "@/lib/db"; + + +export async function PATCH( + req: Request, + { params }: { params: { serverId: string } } +) { + try { + const profile = await currentProfile(); + + if (!profile) { + return new NextResponse("Unauthorized", { status: 401 }); + } + + if (!params.serverId) { + return new NextResponse("Server ID Missing", { status: 400 }); + } + + const server = await db.server.update({ + where: { + id: params.serverId, + profileId: profile.id, + }, + data: { + inviteCode: uuidv4(), + } + }); + + return NextResponse.json(server) + } catch (error) { + console.log("[SERVER_ID]", error); + return new NextResponse("Internal Error", { status: 500}); + } +} \ No newline at end of file diff --git a/components/modals/invite-modal.tsx b/components/modals/invite-modal.tsx new file mode 100644 index 0000000..840c5f9 --- /dev/null +++ b/components/modals/invite-modal.tsx @@ -0,0 +1,90 @@ +"use client"; + +import axios from "axios"; +import { useState } from "react"; +import { Check, Copy, RefreshCw } from "lucide-react"; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useModal } from "@/hooks/use-modal-store"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { useOrigin } from "@/hooks/user-origin"; + + +export const InviteModal = () => { + const { onOpen, isOpen, onClose, type, data } = useModal(); + const origin = useOrigin(); + + const isModalOpen = isOpen && type === "invite"; + const { server } = data; + + const [copied, setCopied] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const inviteUrl = `${origin}/invite/${server?.inviteCode}`; + + const onCopy = () => { + navigator.clipboard.writeText(inviteUrl); + setCopied(true); + + setTimeout(() => { + setCopied(false); + }, 1000); + } + + const onNew = async () => { + try { + setIsLoading(true); + const response = await axios.patch(`/api/servers/${server?.id}/invite-code`); + + onOpen("invite", {server: response.data}); + } catch (error) { + console.log(error); + } finally{ + setIsLoading(false); + } + } + + return ( + + + + + Invite Friends + + +
+ +
+ + +
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/components/providors/modal-providor.tsx b/components/providors/modal-providor.tsx index c73bdb7..76ad0ca 100644 --- a/components/providors/modal-providor.tsx +++ b/components/providors/modal-providor.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import { CreateServerModal } from "@/components/modals/create-server-modal"; +import { InviteModal } from "@/components/modals/invite-modal"; export const ModalProvidor = () => { const [isMounted, setIsMounted] = useState(false); @@ -18,6 +19,7 @@ export const ModalProvidor = () => { return ( <> + ) } \ No newline at end of file diff --git a/components/server/server-header.tsx b/components/server/server-header.tsx index 1e920c5..0a1dda4 100644 --- a/components/server/server-header.tsx +++ b/components/server/server-header.tsx @@ -3,7 +3,8 @@ import { ServerWithMembersWithProfiles } from "@/types"; import { MemberRole } from "@prisma/client"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { ChevronDown, LogOut, LogOutIcon, Plus, PlusCircle, Settings, Trash, Users } from "lucide-react"; +import { ChevronDown, LogOut, Plus, PlusCircle, Settings, Trash, Users } from "lucide-react"; +import { useModal } from "@/hooks/use-modal-store"; interface ServerHeaderProps { server: ServerWithMembersWithProfiles; @@ -11,6 +12,7 @@ interface ServerHeaderProps { } export const SeverHeader = ({server, role}: ServerHeaderProps) => { + const { onOpen } = useModal(); const isAdmin = role === MemberRole.ADMIN; const isModerator = isAdmin || role === MemberRole.MODERATOR; @@ -30,6 +32,7 @@ export const SeverHeader = ({server, role}: ServerHeaderProps) => { > {isModerator && ( onOpen("invite", {server})} className="text-indigo-600 dark:text-indigo-400 px-3 py-2 text-sm cursor-pointer" > Invite People diff --git a/hooks/use-modal-store.ts b/hooks/use-modal-store.ts index 77ff451..9eb292f 100644 --- a/hooks/use-modal-store.ts +++ b/hooks/use-modal-store.ts @@ -1,17 +1,25 @@ + +import { Server } from "@prisma/client"; import { create } from "zustand"; -export type ModalType = "createServer"; +export type ModalType = "createServer" | "invite"; + +interface ModalData { + server?: Server +} interface ModalStore { type: ModalType | null; + data: ModalData; isOpen: boolean; - onOpen: (type: ModalType) => void; + onOpen: (type: ModalType, data?:ModalData) => void; onClose: () => void; } export const useModal = create((set) => ({ type: null, + data: {}, isOpen: false, - onOpen: (type) => set({ isOpen: true, type }), + onOpen: (type, data= {}) => set({ isOpen: true, type, data }), onClose: () => set({ type: null, isOpen: false }), })); \ No newline at end of file diff --git a/hooks/user-origin.ts b/hooks/user-origin.ts new file mode 100644 index 0000000..f4d1a63 --- /dev/null +++ b/hooks/user-origin.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; + +export const useOrigin = () => { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + + }, []); + + const origin = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + + if (!mounted) { + return ""; + } + + return origin; +} \ No newline at end of file