feat: OAuth 登录后返回来源页 + 登录页清理

- OAuth 流程透传 redirect 参数,登录后回到触发页面而非固定跳 Dashboard
- 服务端校验 redirect 为相对路径,防止 Open Redirect 攻击
- 隐藏 Apple 登录按钮和邮箱注册入口
- Dark Mode 切换改为下拉菜单样式
- 提取 useClickOutside hook 消除重复代码

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 17:56:57 +08:00
parent d1ee0bbad2
commit 49ca1f6e1f
10 changed files with 116 additions and 78 deletions

View File

@@ -1,5 +1,6 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef, useCallback } from 'react';
import { useI18n, type Locale } from '../lib/i18n';
import { useClickOutside } from '../hooks/useClickOutside';
const languages: { locale: Locale; flag: string; label: string }[] = [
{ locale: 'en', flag: '🇺🇸', label: 'English' },
@@ -10,15 +11,7 @@ export default function LanguageToggle() {
const { locale, setLocale } = useI18n();
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const handler = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
};
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [open]);
useClickOutside(ref, useCallback(() => setOpen(false), []), open);
return (
<div ref={ref} className="relative">