added invitation popup
This commit is contained in:
parent
2c29ae11e7
commit
e3075627a4
62
app/(invite)/(routes)/invite/[inviteCode]/page.tsx
Normal file
62
app/(invite)/(routes)/invite/[inviteCode]/page.tsx
Normal 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;
|
38
app/api/servers/[serverId]/invite-code/route.ts
Normal file
38
app/api/servers/[serverId]/invite-code/route.ts
Normal 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});
|
||||||
|
}
|
||||||
|
}
|
90
components/modals/invite-modal.tsx
Normal file
90
components/modals/invite-modal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -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
|
||||||
|
@ -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
18
hooks/user-origin.ts
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user