mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat: 更新登录和注册表单以支持手机号登录
This commit is contained in:
parent
a88c5f0d62
commit
2da57de786
@ -1,18 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from '@remix-run/react';
|
||||
import { validatePhoneNumber } from '~/utils/validation';
|
||||
|
||||
export function Login() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [phoneError, setPhoneError] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!validatePhoneNumber(phone)) {
|
||||
setPhoneError('请输入有效的手机号码');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
body: JSON.stringify({ phone, password }),
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = (await response.json()) as { token: string };
|
||||
@ -27,22 +33,45 @@ export function Login() {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="邮箱"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="密码"
|
||||
required
|
||||
/>
|
||||
<button type="submit">登录</button>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||
手机号
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => {
|
||||
setPhone(e.target.value);
|
||||
setPhoneError('');
|
||||
}}
|
||||
required
|
||||
className="mt-1 block w-full px-3 py-2 bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor rounded-md shadow-sm focus:outline-none focus:ring-bolt-elements-button-primary-background focus:border-bolt-elements-button-primary-background"
|
||||
/>
|
||||
{phoneError && <p className="mt-2 text-sm text-red-600">{phoneError}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="mt-1 block w-full px-3 py-2 bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor rounded-md shadow-sm focus:outline-none focus:ring-bolt-elements-button-primary-background focus:border-bolt-elements-button-primary-background"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-bolt-elements-button-primary-text bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background"
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,19 +1,29 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from '@remix-run/react';
|
||||
import { validatePhoneNumber } from '~/utils/validation';
|
||||
|
||||
export function Register() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [phone, setPhone] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [phoneError, setPhoneError] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!validatePhoneNumber(phone)) {
|
||||
setPhoneError('请输入有效的手机号码');
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
alert('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, email, password }),
|
||||
body: JSON.stringify({ phone, password }),
|
||||
});
|
||||
if (response.ok) {
|
||||
navigate('/login');
|
||||
@ -26,29 +36,58 @@ export function Register() {
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="用户名"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="邮箱"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="密码"
|
||||
required
|
||||
/>
|
||||
<button type="submit">注册</button>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||
手机号
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => {
|
||||
setPhone(e.target.value);
|
||||
setPhoneError('');
|
||||
}}
|
||||
required
|
||||
className="mt-1 block w-full px-3 py-2 bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor rounded-md shadow-sm focus:outline-none focus:ring-bolt-elements-button-primary-background focus:border-bolt-elements-button-primary-background"
|
||||
/>
|
||||
{phoneError && <p className="mt-2 text-sm text-red-600">{phoneError}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||
密码
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="mt-1 block w-full px-3 py-2 bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor rounded-md shadow-sm focus:outline-none focus:ring-bolt-elements-button-primary-background focus:border-bolt-elements-button-primary-background"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||
确认密码
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
className="mt-1 block w-full px-3 py-2 bg-bolt-elements-background-depth-1 border border-bolt-elements-borderColor rounded-md shadow-sm focus:outline-none focus:ring-bolt-elements-button-primary-background focus:border-bolt-elements-button-primary-background"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-bolt-elements-button-primary-text bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background"
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@ -44,13 +44,13 @@ export function Header() {
|
||||
)}
|
||||
<button
|
||||
onClick={() => setIsLoginOpen(true)}
|
||||
className="text-bolt-elements-textPrimary hover:text-bolt-elements-textSecondary transition-colors"
|
||||
className="px-4 py-2 text-sm font-medium text-bolt-elements-button-secondary-text bg-bolt-elements-button-secondary-background hover:bg-bolt-elements-button-secondary-backgroundHover rounded-md transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-secondary-background"
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsRegisterOpen(true)}
|
||||
className="text-bolt-elements-textPrimary hover:text-bolt-elements-textSecondary transition-colors"
|
||||
className="px-4 py-2 text-sm font-medium text-bolt-elements-button-primary-text bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover rounded-md transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background"
|
||||
>
|
||||
注册
|
||||
</button>
|
||||
|
||||
@ -1,15 +1,25 @@
|
||||
import { json } from '@remix-run/cloudflare';
|
||||
import { type ActionFunction } from '@remix-run/node';
|
||||
import { json } from '@remix-run/node';
|
||||
import type { ActionFunction } from '@remix-run/node';
|
||||
import { validatePhoneNumber } from '~/utils/validation';
|
||||
import { verifyLogin, createToken } from '~/utils/auth.server';
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const { email, password } = (await request.json()) as { email: string; password: string };
|
||||
|
||||
const user = await verifyLogin(email, password);
|
||||
if (!user) {
|
||||
return json({ success: false, error: 'Invalid credentials' }, { status: 400 });
|
||||
const { phone, password } = await request.json() as { phone: string, password: string };
|
||||
|
||||
if (!validatePhoneNumber(phone)) {
|
||||
return json({ error: '无效的手机号码' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await verifyLogin(phone, password);
|
||||
if (!user) {
|
||||
return json({ error: '手机号或密码不正确' }, { status: 401 });
|
||||
}
|
||||
|
||||
const token = createToken(user._id.toString());
|
||||
return json({ token });
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return json({ error: '登录失败,请稍后再试' }, { status: 500 });
|
||||
}
|
||||
|
||||
const token = createToken(user._id.toString());
|
||||
return json({ success: true, token });
|
||||
};
|
||||
|
||||
@ -1,20 +1,31 @@
|
||||
import { type ActionFunction, json } from '@remix-run/cloudflare';
|
||||
import { json } from '@remix-run/node';
|
||||
import type { ActionFunction } from '@remix-run/node';
|
||||
import { validatePhoneNumber } from '~/utils/validation';
|
||||
import { db } from '~/utils/db.server';
|
||||
import { hashPassword } from '~/utils/auth.server';
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const { username, email, password } = (await request.json()) as { username: string; email: string; password: string };
|
||||
|
||||
const { phone, password } = await request.json() as { phone: string, password: string };
|
||||
|
||||
if (!validatePhoneNumber(phone)) {
|
||||
return json({ error: '无效的手机号码' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const existingUser = await db('users').where({ phone }).first();
|
||||
if (existingUser) {
|
||||
return json({ error: '该手机号已被注册' }, { status: 400 });
|
||||
}
|
||||
|
||||
const hashedPassword = await hashPassword(password);
|
||||
const [userId] = await db('users').insert({
|
||||
username,
|
||||
email,
|
||||
phone,
|
||||
password: hashedPassword
|
||||
});
|
||||
|
||||
return json({ success: true, userId });
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
return json({ success: false, error: 'Registration failed' }, { status: 400 });
|
||||
return json({ error: '注册失败,请稍后再试' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
6
app/utils/validation.ts
Normal file
6
app/utils/validation.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function validatePhoneNumber(phone: string): boolean {
|
||||
// 这里使用一个简单的中国大陆手机号码验证规则
|
||||
// 你可能需要根据具体需求调整这个正则表达式
|
||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
||||
return phoneRegex.test(phone);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user