feat(invite): show App download page after phone-invite registration

Phone-invited users are mobile App users, not web admin users.
After accepting a phone invitation, display App download QR + APK link
instead of redirecting to /dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-09 08:01:42 -07:00
parent d73f07d688
commit 1ccdbc0526
1 changed files with 46 additions and 1 deletions

View File

@ -3,6 +3,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useRouter, useParams } from 'next/navigation'; import { useRouter, useParams } 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';
@ -36,6 +37,8 @@ export default function AcceptInvitePage() {
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null); const [submitError, setSubmitError] = useState<string | null>(null);
const [registered, setRegistered] = useState(false);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
async function validateInvite() { async function validateInvite() {
@ -78,7 +81,17 @@ export default function AcceptInvitePage() {
} }
} catch { /* ignore */ } } catch { /* ignore */ }
if (invite?.phone) {
// Phone invite = mobile App user, show App download page
const url = await fetch('/api/app/version/check?platform=android&current_version_code=0')
.then((r) => r.json())
.then((d) => d?.downloadUrl || null)
.catch(() => null);
setDownloadUrl(url);
setRegistered(true);
} else {
router.push('/dashboard'); router.push('/dashboard');
}
} catch (err) { } catch (err) {
setSubmitError(err instanceof Error ? err.message : 'Failed to accept invitation'); setSubmitError(err instanceof Error ? err.message : 'Failed to accept invitation');
} finally { } finally {
@ -109,6 +122,38 @@ export default function AcceptInvitePage() {
); );
} }
if (registered) {
return (
<div className="w-full max-w-md p-8 space-y-6 bg-card rounded-lg border text-center">
<img src="/icons/logo.svg" alt="iAgent" className="w-16 h-16 mx-auto" />
<div>
<h1 className="text-2xl font-bold text-green-500"></h1>
<p className="text-muted-foreground mt-2 text-sm">
<span className="font-medium">{invite?.tenantName}</span> App 使
</p>
</div>
{downloadUrl ? (
<div className="space-y-3">
<div className="flex justify-center">
<div className="bg-white p-3 rounded-lg inline-block">
<QRCode value={downloadUrl} size={160} />
</div>
</div>
<p className="text-xs text-muted-foreground"> Android App</p>
<a
href={downloadUrl}
className="inline-block px-5 py-2.5 bg-primary text-primary-foreground rounded-md text-sm font-medium hover:opacity-90"
>
APK
</a>
</div>
) : (
<p className="text-sm text-muted-foreground"> App </p>
)}
</div>
);
}
return ( return (
<div className="w-full max-w-md p-8 space-y-6 bg-card rounded-lg border"> <div className="w-full max-w-md p-8 space-y-6 bg-card rounded-lg border">
<div className="text-center"> <div className="text-center">