release 0.3.4
This commit is contained in:
28
apps/web/app/invite/[id]/layout.tsx
Normal file
28
apps/web/app/invite/[id]/layout.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import Providers from "@/app/providers";
|
||||
import { siteConfig } from "@/config/site";
|
||||
import "@/styles/globals.css";
|
||||
|
||||
import { Metadata } from "next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Invite | Inke",
|
||||
description: siteConfig.description,
|
||||
keywords: siteConfig.keywords,
|
||||
authors: siteConfig.authors,
|
||||
creator: siteConfig.creator,
|
||||
themeColor: siteConfig.themeColor,
|
||||
icons: siteConfig.icons,
|
||||
metadataBase: siteConfig.metadataBase,
|
||||
openGraph: siteConfig.openGraph,
|
||||
twitter: siteConfig.twitter,
|
||||
manifest: siteConfig.manifest,
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Providers>{children}</Providers>
|
||||
</>
|
||||
);
|
||||
}
|
19
apps/web/app/invite/[id]/page.tsx
Normal file
19
apps/web/app/invite/[id]/page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||
import Nav from "@/ui/layout/nav";
|
||||
import Wrapper from "./wrapper";
|
||||
import Footer from "@/ui/layout/footer";
|
||||
|
||||
export default async function Page({ params }: { params: { id: string } }) {
|
||||
const session = await getServerSession(authOptions);
|
||||
return (
|
||||
<>
|
||||
<div className="pt-16">
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Nav />
|
||||
<Wrapper session={session} id={params.id} />
|
||||
<Footer />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
233
apps/web/app/invite/[id]/wrapper.tsx
Normal file
233
apps/web/app/invite/[id]/wrapper.tsx
Normal file
@@ -0,0 +1,233 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useCollaborationById,
|
||||
useCollaborationByRoomId,
|
||||
useCollaborationInviteCount,
|
||||
} from "@/app/post/[id]/request";
|
||||
import { ContentItem } from "@/lib/types/note";
|
||||
import { IResponse } from "@/lib/types/response";
|
||||
import { fetcher } from "@/lib/utils";
|
||||
import { addNote, noteTable } from "@/store/db.model";
|
||||
import UINotFound from "@/ui/layout/not-found";
|
||||
import { LoadingCircle, LoadingDots } from "@/ui/shared/icons";
|
||||
import { Collaboration, User } from "@prisma/client";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
import { motion } from "framer-motion";
|
||||
import { Shapes, Users } from "lucide-react";
|
||||
import { Session } from "next-auth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast, { Toaster } from "react-hot-toast";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export default function Wrapper({
|
||||
session,
|
||||
id,
|
||||
}: {
|
||||
session: Session | null;
|
||||
id: string;
|
||||
}) {
|
||||
const { room, isLoading } = useCollaborationById(id);
|
||||
const { count } = useCollaborationInviteCount(room?.data?.roomId);
|
||||
|
||||
const router = useRouter();
|
||||
const contents = useLiveQuery<ContentItem[]>(() => noteTable.toArray());
|
||||
|
||||
const [isJoined, setIsJoined] = useState(false);
|
||||
const [isClickJoin, setClickJoin] = useState(false);
|
||||
const [creator, setCreator] = useState<User>();
|
||||
const [firstCreatedRoom, setFirstCreatedRoom] = useState<Collaboration>();
|
||||
|
||||
useEffect(() => {
|
||||
if (room && room.code === 200) {
|
||||
// 查询第一个空间创建者
|
||||
onRequestCreator(room.data.roomId);
|
||||
}
|
||||
}, [room]);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log(room?.data);
|
||||
|
||||
if (room && room.data) {
|
||||
const index = contents.findIndex((item) => item.id === room.data.localId);
|
||||
if (index !== -1) {
|
||||
setIsJoined(true);
|
||||
}
|
||||
}
|
||||
}, [contents, room]);
|
||||
|
||||
const onRequestCreator = async (roomId: string) => {
|
||||
const res = await fetcher<IResponse<Collaboration>>(
|
||||
"/api/collaboration/room",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ roomId }),
|
||||
},
|
||||
);
|
||||
if (res.code === 200) {
|
||||
setFirstCreatedRoom(res.data);
|
||||
const user = await fetcher<User>(`/api/users?id=${res.data.userId}`);
|
||||
if (user) {
|
||||
setCreator(user);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleJoin = async () => {
|
||||
if (firstCreatedRoom && firstCreatedRoom.deletedAt) {
|
||||
toast("Space has been deleted");
|
||||
return;
|
||||
}
|
||||
|
||||
setClickJoin(true);
|
||||
const localId = uuidv4();
|
||||
const res = await fetcher<IResponse<Collaboration | null>>(
|
||||
"/api/collaboration",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
roomId: room.data.roomId,
|
||||
localId,
|
||||
title: room.data.title,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
if (res.code === 200) {
|
||||
toast.success(res.msg, {
|
||||
icon: "🎉",
|
||||
});
|
||||
newPost(localId);
|
||||
router.push(`/post/${localId}?work=${room.data.roomId}`);
|
||||
} else if (res.code === 301) {
|
||||
toast.success(res.msg, {
|
||||
icon: "🎉",
|
||||
});
|
||||
// 其他设备加入了
|
||||
const index = contents.findIndex((item) => item.id === room.data.localId);
|
||||
if (index === -1) {
|
||||
newPost(room.data.localId);
|
||||
router.push(`/post/${room.data.localId}?work=${room.data.roomId}`);
|
||||
} else {
|
||||
router.push(`/post/${localId}?work=${room.data.roomId}`);
|
||||
}
|
||||
} else {
|
||||
toast(res.msg);
|
||||
setClickJoin(false);
|
||||
}
|
||||
};
|
||||
|
||||
const newPost = (localId: string) => {
|
||||
const date = new Date();
|
||||
addNote({
|
||||
id: localId,
|
||||
title: `Untitled-${localId.slice(0, 6)}-${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()}`,
|
||||
content: {},
|
||||
tag: "",
|
||||
created_at: date.getTime(),
|
||||
updated_at: date.getTime(),
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="flex h-screen w-full justify-center px-6 py-6 text-center">
|
||||
<LoadingCircle className="h-6 w-6" />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!id || room.code !== 200) return <UINotFound />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toaster />
|
||||
<div className="mx-auto h-screen max-w-3xl px-6 py-6 text-center">
|
||||
<Shapes className="mx-auto h-12 w-12 text-cyan-500 hover:text-slate-500" />
|
||||
<h1 className="my-4 text-center text-2xl font-semibold">
|
||||
🎉 Invite to Join Collaboration
|
||||
</h1>
|
||||
<p>You are being invited to join the collaboration space</p>
|
||||
|
||||
<motion.div
|
||||
className="mx-auto mb-4 mt-6 w-80 rounded-lg border border-slate-100 p-3 text-sm shadow-md"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<li className="flex items-center justify-between border-b border-slate-100 pb-2">
|
||||
<span>Space name</span>
|
||||
<span className="font-semibold text-cyan-500">
|
||||
{room?.data?.title}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between border-b border-slate-100 py-2">
|
||||
<span>Joined members</span>
|
||||
<span className="font-semibold">{count?.data || "-"}</span>
|
||||
</li>
|
||||
{creator && (
|
||||
<>
|
||||
<li className="flex items-center justify-between border-b border-slate-100 py-2">
|
||||
<span>Space owner</span>
|
||||
<span className="font-semibold">{creator.name}</span>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{firstCreatedRoom && (
|
||||
<>
|
||||
<li className="flex items-center justify-between border-b border-slate-100 py-2">
|
||||
<span>Created at</span>
|
||||
<span className="font-semibold">
|
||||
{firstCreatedRoom.createdAt.toString().slice(0, 10)}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-center justify-between pt-2">
|
||||
<span>Space status</span>
|
||||
<span
|
||||
className={
|
||||
`${
|
||||
firstCreatedRoom.deletedAt
|
||||
? "text-yellow-500"
|
||||
: "text-green-500"
|
||||
}` + " font-semibold"
|
||||
}
|
||||
>
|
||||
{firstCreatedRoom.deletedAt ? "Deleted" : "Active"}
|
||||
</span>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{isJoined ? (
|
||||
<button
|
||||
className="mx-auto mt-6 flex h-10 min-w-[200px] items-center justify-center rounded-md bg-blue-500 px-3 py-2 text-slate-50 shadow-md hover:bg-blue-400"
|
||||
disabled={isClickJoin}
|
||||
onClick={() => {
|
||||
setClickJoin(true);
|
||||
router.push(
|
||||
`/post/${room.data.localId}?work=${room.data.roomId}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{isClickJoin ? (
|
||||
<LoadingDots color="#f6f6f6" />
|
||||
) : (
|
||||
"Joined, click for quick access"
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="mx-auto mt-6 flex h-10 w-64 items-center justify-center rounded-md bg-blue-500 px-3 py-2 text-slate-50 shadow-md hover:bg-blue-400"
|
||||
onClick={handleJoin}
|
||||
disabled={isClickJoin}
|
||||
>
|
||||
{isClickJoin ? <LoadingDots color="#f6f6f6" /> : "Join Now"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user