删除多个完成报告文件,优化项目结构以提升可维护性。
This commit is contained in:
136
app/view/login/forgot/page.tsx
Normal file
136
app/view/login/forgot/page.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { ChevronLeft, Phone, Hash } from "lucide-react"
|
||||
|
||||
export default function ForgotPasswordPage() {
|
||||
const router = useRouter()
|
||||
const [phone, setPhone] = useState("")
|
||||
const [newPassword, setNewPassword] = useState("")
|
||||
const [confirmPassword, setConfirmPassword] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
const [success, setSuccess] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError("")
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
if (!phone.trim()) {
|
||||
setError("请输入手机号")
|
||||
return
|
||||
}
|
||||
if (!newPassword.trim()) {
|
||||
setError("请输入新密码")
|
||||
return
|
||||
}
|
||||
if (newPassword.trim().length < 6) {
|
||||
setError("密码至少 6 位")
|
||||
return
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
setError("两次输入的密码不一致")
|
||||
return
|
||||
}
|
||||
|
||||
const res = await fetch("/api/auth/reset-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ phone: phone.trim(), newPassword: newPassword.trim() }),
|
||||
})
|
||||
const data = await res.json()
|
||||
|
||||
if (data.success) {
|
||||
setSuccess(true)
|
||||
setTimeout(() => router.push("/view/login"), 2000)
|
||||
} else {
|
||||
setError(data.error || "重置失败")
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white flex flex-col items-center justify-center px-6">
|
||||
<p className="text-[#30d158] text-lg mb-4">密码已重置</p>
|
||||
<p className="text-gray-500 text-sm">请使用新密码登录,正在跳转...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white flex flex-col">
|
||||
<header className="flex items-center px-4 py-3">
|
||||
<Link
|
||||
href="/view/login"
|
||||
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5 text-gray-400" />
|
||||
</Link>
|
||||
<h1 className="flex-1 text-center text-lg font-semibold">找回密码</h1>
|
||||
<div className="w-9" />
|
||||
</header>
|
||||
|
||||
<main className="flex-1 px-6 pt-8">
|
||||
<p className="text-gray-500 text-sm mb-6">
|
||||
请输入注册时使用的手机号和新密码,重置后请使用新密码登录。
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="手机号"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="新密码(至少 6 位)"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="再次输入新密码"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !phone || !newPassword || !confirmPassword}
|
||||
className="w-full py-3.5 bg-[#30d158] text-white font-medium rounded-xl active:scale-[0.98] transition-transform disabled:opacity-50"
|
||||
>
|
||||
{loading ? "提交中..." : "重置密码"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="text-gray-500 text-xs mt-6 text-center">
|
||||
若该手机号未注册,将提示「该手机号未注册」;重置后请使用新密码在登录页登录。
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
183
app/view/login/page.tsx
Normal file
183
app/view/login/page.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import Link from "next/link"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { ChevronLeft, Phone, User, Hash } from "lucide-react"
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter()
|
||||
const { login, register } = useStore()
|
||||
const [mode, setMode] = useState<"login" | "register">("login")
|
||||
const [phone, setPhone] = useState("")
|
||||
const [code, setCode] = useState("")
|
||||
const [nickname, setNickname] = useState("")
|
||||
const [referralCode, setReferralCode] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setError("")
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
// 管理员登录(使用 code 作为密码,调用后台 API 并写 Cookie)
|
||||
if (phone.toLowerCase() === "admin") {
|
||||
try {
|
||||
const res = await fetch("/api/admin", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: phone, password: code }),
|
||||
credentials: "include",
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok && data.success) {
|
||||
router.push("/admin")
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// fallthrough to error
|
||||
}
|
||||
setError("管理员密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
if (mode === "login") {
|
||||
if (!code.trim()) {
|
||||
setError("请输入密码")
|
||||
return
|
||||
}
|
||||
const success = await login(phone, code)
|
||||
if (success) {
|
||||
router.push("/view")
|
||||
} else {
|
||||
setError("密码错误或用户不存在")
|
||||
}
|
||||
} else {
|
||||
if (!nickname.trim()) {
|
||||
setError("请输入昵称")
|
||||
return
|
||||
}
|
||||
if (!code.trim()) {
|
||||
setError("请设置密码(至少 6 位)")
|
||||
return
|
||||
}
|
||||
if (code.trim().length < 6) {
|
||||
setError("密码至少 6 位")
|
||||
return
|
||||
}
|
||||
const success = await register(phone, nickname, code, referralCode || undefined)
|
||||
if (success) {
|
||||
router.push("/view")
|
||||
} else {
|
||||
setError("该手机号已注册")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white flex flex-col">
|
||||
{/* 顶部导航 */}
|
||||
<header className="flex items-center px-4 py-3">
|
||||
<button
|
||||
onClick={() => router.back()}
|
||||
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center"
|
||||
>
|
||||
<ChevronLeft className="w-5 h-5 text-gray-400" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{/* 主内容 */}
|
||||
<main className="flex-1 px-6 pt-8">
|
||||
<h1 className="text-2xl font-bold mb-2">{mode === "login" ? "登录" : "注册"}</h1>
|
||||
<p className="text-gray-500 text-sm mb-8">
|
||||
{mode === "login" ? "登录后查看购买记录和收益" : "注册后开始阅读真实商业故事"}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* 手机号 */}
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="手机号"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 昵称(注册时显示) */}
|
||||
{mode === "register" && (
|
||||
<div className="relative">
|
||||
<User className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
placeholder="昵称"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 密码 */}
|
||||
<div className="relative">
|
||||
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="password"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder={mode === "login" ? "密码" : "设置密码(至少 6 位)"}
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 邀请码(注册时显示) */}
|
||||
{mode === "register" && (
|
||||
<div className="relative">
|
||||
<Hash className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<input
|
||||
type="text"
|
||||
value={referralCode}
|
||||
onChange={(e) => setReferralCode(e.target.value)}
|
||||
placeholder="邀请码(选填)"
|
||||
className="w-full pl-12 pr-4 py-3.5 bg-[#1c1c1e] rounded-xl text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-[#30d158]/50"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 错误提示 */}
|
||||
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !phone}
|
||||
className="w-full py-3.5 bg-[#30d158] text-white font-medium rounded-xl active:scale-[0.98] transition-transform disabled:opacity-50"
|
||||
>
|
||||
{loading ? "处理中..." : mode === "login" ? "登录" : "注册"}
|
||||
</button>
|
||||
|
||||
{/* 忘记密码 / 切换模式 */}
|
||||
<div className="text-center space-y-2">
|
||||
{mode === "login" && (
|
||||
<div>
|
||||
<Link href="/view/login/forgot" className="text-[#30d158] text-sm">
|
||||
忘记密码?
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => setMode(mode === "login" ? "register" : "login")} className="text-[#30d158] text-sm block mx-auto">
|
||||
{mode === "login" ? "没有账号?去注册" : "已有账号?去登录"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user