Files
no-whatever/src/app/invite/[id]/page.tsx
T

213 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { motion } from "framer-motion";
import {
Users,
Heart,
Sparkles,
ChevronRight,
} from "lucide-react";
import { getUserId } from "@/lib/userId";
import { joinRoom } from "@/lib/room";
import { Skeleton, SkeletonCircle } from "@/components/Skeleton";
import Button from "@/components/Button";
import { getSceneConfig } from "@/lib/sceneConfig";
import type { SceneType } from "@/types";
export default function InvitePage() {
const params = useParams<{ id: string }>();
const router = useRouter();
const roomId = params.id;
const [status, setStatus] = useState<"loading" | "ready" | "not_found">(
"loading",
);
const [userCount, setUserCount] = useState(0);
const [scene, setScene] = useState<SceneType>("eat");
const [joining, setJoining] = useState(false);
const [joinError, setJoinError] = useState("");
const sceneConfig = getSceneConfig(scene);
useEffect(() => {
fetch(`/api/room/${roomId}`)
.then((res) => {
if (!res.ok) throw new Error();
return res.json();
})
.then((data) => {
setUserCount(data.userCount ?? 0);
if (data.scene) setScene(data.scene);
setStatus("ready");
})
.catch(() => setStatus("not_found"));
}, [roomId]);
const handleJoin = async () => {
setJoining(true);
setJoinError("");
try {
await joinRoom(roomId, getUserId());
router.push(`/room/${roomId}`);
} catch (e) {
setJoinError(e instanceof Error ? e.message : "加入失败,请重试");
setJoining(false);
}
};
if (status === "loading") {
return (
<div className="flex min-h-dvh flex-col items-center justify-center bg-background px-6">
<Skeleton className="h-16 w-16 rounded-2xl" />
<Skeleton className="mt-5 h-8 w-40" />
<Skeleton className="mt-2 h-4 w-24" />
<div className="mt-8 w-full max-w-xs rounded-2xl bg-surface px-6 py-5 ring-1 ring-border">
<div className="flex flex-col items-center gap-3">
<Skeleton className="h-5 w-44" />
<Skeleton className="h-7 w-24 rounded-full" />
<Skeleton className="h-4 w-32" />
</div>
</div>
<div className="mt-6 flex gap-3">
<SkeletonCircle className="h-10 w-10" />
<SkeletonCircle className="h-10 w-10" />
<SkeletonCircle className="h-10 w-10" />
</div>
<Skeleton className="mt-8 h-12 w-full max-w-xs rounded-xl" />
</div>
);
}
if (status === "not_found") {
return (
<div className="flex min-h-dvh flex-col items-center justify-center gap-4 bg-background px-6">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-surface ring-1 ring-border">
<Utensils size={28} className="text-muted" />
</div>
<h1 className="text-xl font-bold text-heading"></h1>
<p className="text-center text-sm text-muted">
</p>
<Button onClick={() => router.push("/")} className="mt-2">
</Button>
</div>
);
}
return (
<div className="flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-12 overflow-y-auto scrollbar-none">
<motion.div
className="flex flex-col items-center"
initial={{ y: -20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
>
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-accent shadow-lg shadow-accent/20">
<span className="text-3xl leading-none">{sceneConfig.emoji}</span>
</div>
<h1 className="mt-5 text-3xl font-black tracking-tight text-heading">
NoWhatever
</h1>
<p className="mt-0.5 text-sm font-medium tracking-widest text-muted">
便
</p>
</motion.div>
<motion.div
className="mt-8 flex w-full max-w-xs flex-col items-center gap-2 rounded-2xl bg-surface px-6 py-5 ring-1 ring-accent/30"
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<p className="text-center text-lg font-bold text-heading">
{sceneConfig.inviteText}
</p>
<div className="flex items-center gap-2 text-sm text-muted">
<span className="rounded-full bg-elevated px-2.5 py-0.5 font-mono text-base font-bold tracking-widest text-accent ring-1 ring-border">
{roomId}
</span>
</div>
{userCount > 0 && (
<div className="flex items-center gap-1 text-xs text-muted">
<Users size={13} />
<span>
<span className="font-semibold text-accent">{userCount}</span>
</span>
</div>
)}
</motion.div>
<motion.div
className="mt-6 flex items-start justify-center gap-3"
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="flex flex-col items-center gap-1.5 w-20">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-accent/15">
<Users size={18} className="text-accent" />
</div>
<span className="text-xs font-semibold text-secondary"></span>
<span className="text-[10px] leading-tight text-muted text-center">
</span>
</div>
<ChevronRight size={14} className="mt-3 shrink-0 text-subtle" />
<div className="flex flex-col items-center gap-1.5 w-20">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-amber-500/15">
<Heart size={18} className="text-amber-400" />
</div>
<span className="text-xs font-semibold text-secondary"></span>
<span className="text-[10px] leading-tight text-muted text-center">
</span>
</div>
<ChevronRight size={14} className="mt-3 shrink-0 text-subtle" />
<div className="flex flex-col items-center gap-1.5 w-20">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-rose-500/15">
<Sparkles size={18} className="text-rose-400" />
</div>
<span className="text-xs font-semibold text-secondary"></span>
<span className="text-[10px] leading-tight text-muted text-center">
</span>
</div>
</motion.div>
<motion.div
className="mt-8 w-full max-w-xs"
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5, delay: 0.3 }}
>
{joinError && (
<motion.p
className="mb-3 text-center text-xs font-medium text-rose-400"
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
>
{joinError}
</motion.p>
)}
<Button
onClick={handleJoin}
size="lg"
fullWidth
loading={joining}
loadingText="加入中..."
>
</Button>
</motion.div>
</div>
);
}