feat(register): move app download banner to top with QR code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-09 06:18:16 -07:00
parent 146b396427
commit 7555f1ad5a
1 changed files with 28 additions and 62 deletions

View File

@ -3,6 +3,7 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Link from 'next/link'; import Link from 'next/link';
import QRCode from 'react-qr-code';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { apiClient } from '@/infrastructure/api/api-client'; import { apiClient } from '@/infrastructure/api/api-client';
@ -32,15 +33,12 @@ export default function RegisterPage() {
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [appDownloadUrl, setAppDownloadUrl] = useState<{ android?: string; ios?: string }>({}); const [androidDownloadUrl, setAndroidDownloadUrl] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
// Try to fetch latest app version for download links
fetch('/api/app/version/check?platform=android&current_version_code=0') fetch('/api/app/version/check?platform=android&current_version_code=0')
.then((r) => r.json()) .then((r) => r.json())
.then((d) => { .then((d) => { if (d?.downloadUrl) setAndroidDownloadUrl(d.downloadUrl); })
if (d?.downloadUrl) setAppDownloadUrl((prev) => ({ ...prev, android: d.downloadUrl }));
})
.catch(() => {}); .catch(() => {});
}, []); }, []);
@ -67,28 +65,15 @@ export default function RegisterPage() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (password !== confirmPassword) { if (password !== confirmPassword) { setError(tc('passwordsNoMatch')); return; }
setError(tc('passwordsNoMatch')); if (password.length < 6) { setError('密码至少 6 位'); return; }
return; if (!companyName.trim()) { setError('请填写企业名称'); return; }
}
if (password.length < 6) {
setError('密码至少 6 位');
return;
}
if (!companyName.trim()) {
setError('请填写企业名称');
return;
}
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
try { try {
const body: Record<string, string> = { const body: Record<string, string> = { password, name, companyName };
password,
name,
companyName,
};
if (loginMethod === 'email') { if (loginMethod === 'email') {
body.email = email; body.email = email;
} else { } else {
@ -97,10 +82,7 @@ export default function RegisterPage() {
body.smsCode = smsCode; body.smsCode = smsCode;
} }
const data = await apiClient<RegisterResponse>('/api/v1/auth/register', { const data = await apiClient<RegisterResponse>('/api/v1/auth/register', { method: 'POST', body });
method: 'POST',
body,
});
localStorage.setItem('access_token', data.accessToken); localStorage.setItem('access_token', data.accessToken);
localStorage.setItem('refresh_token', data.refreshToken); localStorage.setItem('refresh_token', data.refreshToken);
@ -108,9 +90,7 @@ export default function RegisterPage() {
try { try {
const payload = JSON.parse(atob(data.accessToken.split('.')[1])); const payload = JSON.parse(atob(data.accessToken.split('.')[1]));
if (payload.tenantId) { if (payload.tenantId) localStorage.setItem('current_tenant', JSON.stringify({ id: payload.tenantId }));
localStorage.setItem('current_tenant', JSON.stringify({ id: payload.tenantId }));
}
} catch { /* ignore */ } } catch { /* ignore */ }
router.push('/dashboard'); router.push('/dashboard');
@ -123,6 +103,25 @@ export default function RegisterPage() {
return ( return (
<div className="w-full max-w-lg space-y-6"> <div className="w-full max-w-lg space-y-6">
{/* App download banner — top of page */}
{androidDownloadUrl && (
<div className="flex items-center gap-5 p-4 bg-card border rounded-lg">
<div className="bg-white p-2 rounded-md shrink-0">
<QRCode value={androidDownloadUrl} size={80} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-semibold"> IT0 App</p>
<p className="text-xs text-muted-foreground mt-0.5 mb-2"> Android </p>
<a
href={androidDownloadUrl}
className="inline-block px-3 py-1.5 text-xs font-medium bg-primary text-primary-foreground rounded-md hover:opacity-90"
>
APK
</a>
</div>
</div>
)}
{/* Header */} {/* Header */}
<div className="text-center"> <div className="text-center">
<img src="/icons/logo.svg" alt="iAgent" className="w-16 h-16 mx-auto mb-3" /> <img src="/icons/logo.svg" alt="iAgent" className="w-16 h-16 mx-auto mb-3" />
@ -162,7 +161,6 @@ export default function RegisterPage() {
</div> </div>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
{/* Identifier field */}
{loginMethod === 'email' ? ( {loginMethod === 'email' ? (
<div> <div>
<label className="block text-sm font-medium mb-1">{t('email')}</label> <label className="block text-sm font-medium mb-1">{t('email')}</label>
@ -213,7 +211,6 @@ export default function RegisterPage() {
</> </>
)} )}
{/* Full name */}
<div> <div>
<label className="block text-sm font-medium mb-1">{t('fullName')}</label> <label className="block text-sm font-medium mb-1">{t('fullName')}</label>
<input <input
@ -226,7 +223,6 @@ export default function RegisterPage() {
/> />
</div> </div>
{/* Company name — required for enterprise */}
<div> <div>
<label className="block text-sm font-medium mb-1">{t('organizationName')}</label> <label className="block text-sm font-medium mb-1">{t('organizationName')}</label>
<input <input
@ -240,7 +236,6 @@ export default function RegisterPage() {
<p className="text-xs text-muted-foreground mt-1">{t('organizationHint')}</p> <p className="text-xs text-muted-foreground mt-1">{t('organizationHint')}</p>
</div> </div>
{/* Password */}
<div> <div>
<label className="block text-sm font-medium mb-1">{t('password')}</label> <label className="block text-sm font-medium mb-1">{t('password')}</label>
<input <input
@ -281,35 +276,6 @@ export default function RegisterPage() {
</Link> </Link>
</p> </p>
</div> </div>
{/* App download section */}
<div className="p-5 bg-muted/50 rounded-lg border border-dashed space-y-3">
<div className="flex items-center gap-2">
<span className="text-lg">📱</span>
<h3 className="text-sm font-semibold">{t('downloadApp')}</h3>
</div>
<p className="text-xs text-muted-foreground leading-relaxed">{t('downloadAppDesc')}</p>
<div className="flex gap-3">
{appDownloadUrl.android ? (
<a
href={appDownloadUrl.android}
className="flex-1 py-2 text-center text-xs font-medium bg-card border rounded-md hover:bg-accent transition-colors"
>
{t('downloadAndroid')}
</a>
) : (
<Link
href="/app-versions"
className="flex-1 py-2 text-center text-xs font-medium bg-card border rounded-md hover:bg-accent transition-colors"
>
{t('downloadAndroid')}
</Link>
)}
<span className="flex-1 py-2 text-center text-xs font-medium bg-card border rounded-md text-muted-foreground cursor-not-allowed">
{t('downloadIOS')} (线)
</span>
</div>
</div>
</div> </div>
); );
} }