feat: 用户名密码登录注册系统
- 新增 /api/auth/register 和 /api/auth/login 接口,使用 bcryptjs 哈希密码 - User 模型改为 username + passwordHash,id 自动生成 cuid - 新增 AuthModal 组件(登录/注册双标签页),替换旧的 ProfileSetupModal - 重写 /profile 页面:支持修改用户名、密码、头像、绑定邮箱、退出登录 - /api/user PUT 支持密码修改(需验证当前密码)和用户名唯一性校验 - 游客模式保留,右上角显示"登录"按钮;登录后显示头像和用户名 - 全局 nickname -> username 重命名(types、SwipeDeck、RoomManageModal、buildRoomStatus) - 新增 logout() 清除登录态并重新生成游客 UUID
This commit is contained in:
@@ -11,29 +11,8 @@ import {
|
||||
Crown,
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
|
||||
const AVATARS = [
|
||||
{ emoji: "🐱", bg: "bg-amber-100" },
|
||||
{ emoji: "🐶", bg: "bg-orange-100" },
|
||||
{ emoji: "🦊", bg: "bg-red-100" },
|
||||
{ emoji: "🐰", bg: "bg-pink-100" },
|
||||
{ emoji: "🐼", bg: "bg-zinc-100" },
|
||||
{ emoji: "🐨", bg: "bg-sky-100" },
|
||||
{ emoji: "🦁", bg: "bg-yellow-100" },
|
||||
{ emoji: "🐸", bg: "bg-lime-100" },
|
||||
{ emoji: "🐵", bg: "bg-stone-100" },
|
||||
{ emoji: "🐷", bg: "bg-rose-100" },
|
||||
{ emoji: "🐙", bg: "bg-purple-100" },
|
||||
{ emoji: "🦄", bg: "bg-violet-100" },
|
||||
] as const;
|
||||
|
||||
function getAvatar(uid: string) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < uid.length; i++) {
|
||||
hash = (hash * 31 + uid.charCodeAt(i)) | 0;
|
||||
}
|
||||
return AVATARS[((hash % AVATARS.length) + AVATARS.length) % AVATARS.length];
|
||||
}
|
||||
import { UserProfile } from "@/types";
|
||||
import { getAvatar, getAvatarBg } from "@/lib/avatars";
|
||||
|
||||
interface RoomManageModalProps {
|
||||
open: boolean;
|
||||
@@ -44,6 +23,7 @@ interface RoomManageModalProps {
|
||||
locked: boolean;
|
||||
swipeCounts: Record<string, number>;
|
||||
totalCards: number;
|
||||
userProfiles: Record<string, UserProfile>;
|
||||
onToast: (msg: string) => void;
|
||||
}
|
||||
|
||||
@@ -56,6 +36,7 @@ export default function RoomManageModal({
|
||||
locked,
|
||||
swipeCounts,
|
||||
totalCards,
|
||||
userProfiles,
|
||||
onToast,
|
||||
}: RoomManageModalProps) {
|
||||
const backdropRef = useRef<HTMLDivElement>(null);
|
||||
@@ -172,7 +153,10 @@ export default function RoomManageModal({
|
||||
</h3>
|
||||
<div className="mt-2 flex flex-col gap-1.5">
|
||||
{users.map((uid) => {
|
||||
const avatar = getAvatar(uid);
|
||||
const profile = userProfiles[uid];
|
||||
const emoji = profile?.avatar ?? getAvatar(uid).emoji;
|
||||
const bg = profile ? getAvatarBg(profile.avatar) : getAvatar(uid).bg;
|
||||
const displayName = profile?.username ?? uid.slice(0, 8);
|
||||
const isCreator = uid === userId;
|
||||
const swiped = swipeCounts[uid] ?? 0;
|
||||
const finished = swiped >= totalCards;
|
||||
@@ -183,9 +167,9 @@ export default function RoomManageModal({
|
||||
className="flex items-center gap-2.5 rounded-xl bg-zinc-50 px-3 py-2.5"
|
||||
>
|
||||
<span
|
||||
className={`inline-flex h-8 w-8 items-center justify-center rounded-full text-base ${avatar.bg}`}
|
||||
className={`inline-flex h-8 w-8 items-center justify-center rounded-full text-base ${bg}`}
|
||||
>
|
||||
{avatar.emoji}
|
||||
{emoji}
|
||||
</span>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -196,7 +180,7 @@ export default function RoomManageModal({
|
||||
</span>
|
||||
)}
|
||||
<span className="truncate text-xs font-medium text-zinc-500">
|
||||
{uid.slice(0, 8)}
|
||||
{displayName}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user