Files
re-AstarCupWeb/app/debug/components/RegisterUserCard.tsx
2026-02-05 12:05:12 +08:00

473 lines
20 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 Image from 'next/image';
import { useState, useEffect } from 'react';
import { Season } from '@/app/generated/prisma/enums';
interface UserInfo {
id: number;
osuid: number;
username: string;
avatar_url?: string;
cover_url?: string;
country_code: string;
pp: number;
global_rank: number;
country_rank: number;
seasonal: Season;
userState: string;
approved: boolean;
seed: number;
}
export default function RegisterUserCard() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const [searchType, setSearchType] = useState<'osuid' | 'username'>('osuid');
const [searchValue, setSearchValue] = useState('');
const [updateData, setUpdateData] = useState({
username: '',
avatar_url: '',
cover_url: '',
country_code: '',
pp: 0,
global_rank: 0,
country_rank: 0,
});
// 检查URL参数处理OAuth回调结果并检查当前登录状态
useEffect(() => {
const checkCurrentUser = async () => {
console.log('Checking current user session...');
try {
const response = await fetch('/api/user/me', {
credentials: 'include', // 确保发送 cookie
});
console.log('API response status:', response.status);
if (response.ok) {
const user = await response.json();
console.log('User found:', user.username);
setUserInfo({
id: user.id,
osuid: user.osuid,
username: user.username,
avatar_url: user.avatar_url,
cover_url: user.cover_url,
country_code: user.country_code,
pp: user.pp,
global_rank: user.global_rank,
country_rank: user.country_rank,
seasonal: user.seasonal,
userState: user.userState,
approved: user.approved === 1,
seed: user.seed,
});
setUpdateData({
username: user.username,
avatar_url: user.avatar_url || '',
cover_url: user.cover_url || '',
country_code: user.country_code,
pp: user.pp,
global_rank: user.global_rank,
country_rank: user.country_rank,
});
} else {
console.log('User not authenticated or API error:', response.status);
}
} catch (err) {
// 忽略错误,用户可能未登录
console.log('Error checking user session:', err);
}
// 检查URL参数处理OAuth回调结果
const params = new URLSearchParams(window.location.search);
const successParam = params.get('success');
const usernameParam = params.get('username');
const osuidParam = params.get('osuid');
const errorParam = params.get('error');
if (successParam === 'true' && usernameParam && osuidParam) {
setSuccess(`用户 ${usernameParam} (osuid: ${osuidParam}) 注册/登录成功!`);
// 清空URL参数
const newUrl = window.location.pathname;
window.history.replaceState({}, '', newUrl);
}
if (errorParam) {
setError(decodeURIComponent(errorParam));
// 清空URL参数
const newUrl = window.location.pathname;
window.history.replaceState({}, '', newUrl);
}
};
checkCurrentUser();
}, []);
const handleOsuLogin = () => {
window.location.href = '/api/auth/getAuthUrl';
};
const handleSearchUser = async () => {
if (!searchValue.trim()) {
setError('请输入搜索值');
return;
}
setLoading(true);
setError(null);
setSuccess(null);
try {
const response = await fetch(`/api/user/search?type=${searchType}&value=${encodeURIComponent(searchValue)}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `搜索失败: ${response.status}`);
}
const user = await response.json();
if (user) {
setUserInfo({
id: user.id,
osuid: user.osuid,
username: user.username,
avatar_url: user.avatar_url,
cover_url: user.cover_url,
country_code: user.country_code,
pp: user.pp,
global_rank: user.global_rank,
country_rank: user.country_rank,
seasonal: user.seasonal,
userState: user.userState,
approved: user.approved === 1,
seed: user.seed,
});
setUpdateData({
username: user.username,
avatar_url: user.avatar_url || '',
cover_url: user.cover_url || '',
country_code: user.country_code,
pp: user.pp,
global_rank: user.global_rank,
country_rank: user.country_rank,
});
setSuccess(`找到用户: ${user.username} (ID: ${user.id})`);
} else {
setError('未找到用户');
setUserInfo(null);
}
} catch (err) {
setError('搜索用户失败: ' + (err instanceof Error ? err.message : String(err)));
setUserInfo(null);
} finally {
setLoading(false);
}
};
const handleUpdateProfile = async () => {
if (!userInfo) {
setError('没有用户信息可更新');
return;
}
setLoading(true);
setError(null);
try {
const updatePayload: {
username?: string;
avatar_url?: string;
cover_url?: string;
country_code?: string;
pp?: number;
global_rank?: number;
country_rank?: number;
} = {};
// 只添加有变化的字段
if (updateData.username !== userInfo.username) updatePayload.username = updateData.username;
if (updateData.avatar_url !== userInfo.avatar_url) updatePayload.avatar_url = updateData.avatar_url;
if (updateData.cover_url !== userInfo.cover_url) updatePayload.cover_url = updateData.cover_url;
if (updateData.country_code !== userInfo.country_code) updatePayload.country_code = updateData.country_code;
if (updateData.pp !== userInfo.pp) updatePayload.pp = updateData.pp;
if (updateData.global_rank !== userInfo.global_rank) updatePayload.global_rank = updateData.global_rank;
if (updateData.country_rank !== userInfo.country_rank) updatePayload.country_rank = updateData.country_rank;
// 如果没有变化,直接返回
if (Object.keys(updatePayload).length === 0) {
setSuccess('没有需要更新的信息');
setLoading(false);
return;
}
const response = await fetch('/api/user/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
osuid: userInfo.osuid,
...updatePayload,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `更新失败: ${response.status}`);
}
const updatedUser = await response.json();
setUserInfo({
id: updatedUser.id,
osuid: updatedUser.osuid,
username: updatedUser.username,
avatar_url: updatedUser.avatar_url,
cover_url: updatedUser.cover_url,
country_code: updatedUser.country_code,
pp: updatedUser.pp,
global_rank: updatedUser.global_rank,
country_rank: updatedUser.country_rank,
seasonal: updatedUser.seasonal,
userState: updatedUser.userState,
approved: updatedUser.approved === 1,
seed: updatedUser.seed,
});
setSuccess('用户信息更新成功!');
} catch (err) {
setError('更新用户信息失败: ' + (err instanceof Error ? err.message : String(err)));
} finally {
setLoading(false);
}
};
const handleLogout = async () => {
try {
// 调用 API 清除 cookie
const response = await fetch('/api/user/me', {
method: 'DELETE',
});
if (response.ok) {
setUserInfo(null);
setUpdateData({
username: '',
avatar_url: '',
cover_url: '',
country_code: '',
pp: 0,
global_rank: 0,
country_rank: 0,
});
setSuccess('已退出登录');
setError(null);
} else {
setError('退出登录失败');
}
} catch (err) {
setError('退出登录失败: ' + (err instanceof Error ? err.message : String(err)));
}
};
return (
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
<h2 className="text-2xl font-bold mb-4"></h2>
{error && (
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
{error}
</div>
)}
{success && (
<div className="mb-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded">
{success}
</div>
)}
<div className="mb-6">
<h3 className="text-lg font-semibold mb-2">osu! OAuth </h3>
<p className="text-gray-600 mb-4">使 osu! </p>
<button
onClick={handleOsuLogin}
disabled={loading}
className="px-4 py-2 bg-pink-600 text-white rounded hover:bg-pink-700 disabled:opacity-50"
>
{loading ? '处理中...' : '使用 osu! 账号登录'}
</button>
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-2"></h3>
<div className="flex flex-col md:flex-row gap-2 mb-4">
<div className="flex items-center gap-2">
<label className="flex items-center">
<input
type="radio"
checked={searchType === 'osuid'}
onChange={() => setSearchType('osuid')}
className="mr-2"
/>
osuid
</label>
<label className="flex items-center">
<input
type="radio"
checked={searchType === 'username'}
onChange={() => setSearchType('username')}
className="mr-2"
/>
</label>
</div>
<input
type="text"
value={searchValue}
onChange={(e) => setSearchValue(e.target.value)}
placeholder={searchType === 'osuid' ? '输入 osuid' : '输入用户名'}
className="flex-grow px-3 py-2 border rounded"
/>
<button
onClick={handleSearchUser}
disabled={loading}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{loading ? '搜索中...' : '搜索用户'}
</button>
</div>
</div>
{userInfo && (
<div className="border-t pt-6">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"></h3>
<button
onClick={handleLogout}
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700"
>
退
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div>
<p><strong>ID:</strong> {userInfo.id}</p>
<p><strong>osuid:</strong> {userInfo.osuid}</p>
<p><strong>:</strong> {userInfo.username}</p>
<p><strong>:</strong> {userInfo.country_code}</p>
<p><strong>PP:</strong> {userInfo.pp.toFixed(2)}</p>
</div>
<div>
<p><strong>:</strong> {userInfo.global_rank}</p>
<p><strong>:</strong> {userInfo.country_rank}</p>
<p><strong>:</strong> {userInfo.seasonal}</p>
<p><strong>:</strong> {userInfo.userState}</p>
<p><strong>:</strong> {userInfo.approved ? '是' : '否'}</p>
<p><strong>:</strong> {userInfo.seed}</p>
</div>
</div>
{userInfo.avatar_url && (
<div className="mb-4">
<p className="font-semibold mb-2">:</p>
<Image src={userInfo.avatar_url}
alt="用户头像"
className="w-24 h-24 rounded-full"
width={24}
height={24}
/>
</div>
)}
<div className="mt-6">
<h4 className="text-md font-semibold mb-3"></h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
type="text"
value={updateData.username}
onChange={(e) => setUpdateData({...updateData, username: e.target.value})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
type="text"
value={updateData.country_code}
onChange={(e) => setUpdateData({...updateData, country_code: e.target.value})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">PP</label>
<input
type="number"
value={updateData.pp}
onChange={(e) => setUpdateData({...updateData, pp: parseFloat(e.target.value) || 0})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
type="number"
value={updateData.global_rank}
onChange={(e) => setUpdateData({...updateData, global_rank: parseInt(e.target.value) || 0})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1"></label>
<input
type="number"
value={updateData.country_rank}
onChange={(e) => setUpdateData({...updateData, country_rank: parseInt(e.target.value) || 0})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">URL</label>
<input
type="text"
value={updateData.avatar_url}
onChange={(e) => setUpdateData({...updateData, avatar_url: e.target.value})}
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">URL</label>
<input
type="text"
value={updateData.cover_url}
onChange={(e) => setUpdateData({...updateData, cover_url: e.target.value})}
className="w-full px-3 py-2 border rounded"
/>
</div>
</div>
<button
onClick={handleUpdateProfile}
disabled={loading}
className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50"
>
{loading ? '更新中...' : '更新用户信息'}
</button>
</div>
</div>
)}
</div>
);
}