added invitation popup

This commit is contained in:
Bob Burningham 2023-10-14 00:06:14 -07:00
parent 2c29ae11e7
commit e3075627a4
7 changed files with 225 additions and 4 deletions

View File

@ -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;

View File

@ -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});
}
}

View File

@ -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 (
<Dialog open={isModalOpen} onOpenChange={onClose}>
<DialogContent className="bg-white text-black p-0 overflow-hidden">
<DialogHeader className="pt-8 px-6">
<DialogTitle className="text-2xl text-center font-bold">
Invite Friends
</DialogTitle>
</DialogHeader>
<div className="p-6">
<Label className="uppercase text-xs font-bold text-zinc-500 dark:text-secondary/70">
Server invite link
</Label>
<div className="flex items-center mt-2 gap-x-2">
<Input className="bg-zinc-300/50 border-0 focus-visible:ring-0 text-black focus-visible:ring-offset-0 "
disabled={isLoading}
value={inviteUrl}
/>
<Button disabled={isLoading} onClick={onCopy} size="icon">
{copied ? <Check className="w-4 h-4"/> : <Copy className="w-4 h-4"/>}
</Button>
</div>
<Button
onClick={onNew}
disabled={isLoading}
variant="link"
size="sm"
className="text-xs text-zinc-500 mt-4"
>
Generate a new link
<RefreshCw className="w-4 h-4 ml-2"/>
</Button>
</div>
</DialogContent>
</Dialog>
)
}

View File

@ -3,6 +3,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { CreateServerModal } from "@/components/modals/create-server-modal"; import { CreateServerModal } from "@/components/modals/create-server-modal";
import { InviteModal } from "@/components/modals/invite-modal";
export const ModalProvidor = () => { export const ModalProvidor = () => {
const [isMounted, setIsMounted] = useState(false); const [isMounted, setIsMounted] = useState(false);
@ -18,6 +19,7 @@ export const ModalProvidor = () => {
return ( return (
<> <>
<CreateServerModal /> <CreateServerModal />
<InviteModal />
</> </>
) )
} }

View File

@ -3,7 +3,8 @@
import { ServerWithMembersWithProfiles } from "@/types"; import { ServerWithMembersWithProfiles } from "@/types";
import { MemberRole } from "@prisma/client"; import { MemberRole } from "@prisma/client";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 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 { interface ServerHeaderProps {
server: ServerWithMembersWithProfiles; server: ServerWithMembersWithProfiles;
@ -11,6 +12,7 @@ interface ServerHeaderProps {
} }
export const SeverHeader = ({server, role}: ServerHeaderProps) => { export const SeverHeader = ({server, role}: ServerHeaderProps) => {
const { onOpen } = useModal();
const isAdmin = role === MemberRole.ADMIN; const isAdmin = role === MemberRole.ADMIN;
const isModerator = isAdmin || role === MemberRole.MODERATOR; const isModerator = isAdmin || role === MemberRole.MODERATOR;
@ -30,6 +32,7 @@ export const SeverHeader = ({server, role}: ServerHeaderProps) => {
> >
{isModerator && ( {isModerator && (
<DropdownMenuItem <DropdownMenuItem
onClick={() => onOpen("invite", {server})}
className="text-indigo-600 dark:text-indigo-400 px-3 py-2 text-sm cursor-pointer" className="text-indigo-600 dark:text-indigo-400 px-3 py-2 text-sm cursor-pointer"
> >
Invite People Invite People

View File

@ -1,17 +1,25 @@
import { Server } from "@prisma/client";
import { create } from "zustand"; import { create } from "zustand";
export type ModalType = "createServer"; export type ModalType = "createServer" | "invite";
interface ModalData {
server?: Server
}
interface ModalStore { interface ModalStore {
type: ModalType | null; type: ModalType | null;
data: ModalData;
isOpen: boolean; isOpen: boolean;
onOpen: (type: ModalType) => void; onOpen: (type: ModalType, data?:ModalData) => void;
onClose: () => void; onClose: () => void;
} }
export const useModal = create<ModalStore>((set) => ({ export const useModal = create<ModalStore>((set) => ({
type: null, type: null,
data: {},
isOpen: false, isOpen: false,
onOpen: (type) => set({ isOpen: true, type }), onOpen: (type, data= {}) => set({ isOpen: true, type, data }),
onClose: () => set({ type: null, isOpen: false }), onClose: () => set({ type: null, isOpen: false }),
})); }));

18
hooks/user-origin.ts Normal file
View File

@ -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;
}