98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
|
|
export function AuthButton() {
|
|
const { isAuthenticated, username, did, loading, login, register, logout } = useAuthStore();
|
|
const [showRegister, setShowRegister] = useState(false);
|
|
const [regUsername, setRegUsername] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
if (isAuthenticated) {
|
|
return (
|
|
<div className="flex items-center gap-3">
|
|
<div className="text-sm">
|
|
<span className="text-white/60">Signed in as </span>
|
|
<span className="text-rmaps-primary font-medium">{username || did?.slice(0, 16) + '...'}</span>
|
|
</div>
|
|
<button
|
|
onClick={logout}
|
|
className="text-xs text-white/40 hover:text-white/60 transition-colors"
|
|
>
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (showRegister) {
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="text"
|
|
value={regUsername}
|
|
onChange={(e) => setRegUsername(e.target.value)}
|
|
placeholder="Choose a username"
|
|
className="input text-sm py-1 px-2 w-40"
|
|
maxLength={20}
|
|
/>
|
|
<button
|
|
onClick={async () => {
|
|
if (!regUsername.trim()) return;
|
|
setError('');
|
|
try {
|
|
await register(regUsername.trim());
|
|
} catch (e: any) {
|
|
setError(e.message || 'Registration failed');
|
|
}
|
|
}}
|
|
disabled={loading || !regUsername.trim()}
|
|
className="btn-primary text-sm py-1 px-3"
|
|
>
|
|
{loading ? '...' : 'Register'}
|
|
</button>
|
|
<button
|
|
onClick={() => setShowRegister(false)}
|
|
className="text-xs text-white/40 hover:text-white/60"
|
|
>
|
|
Cancel
|
|
</button>
|
|
{error && <span className="text-xs text-red-400">{error}</span>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<button
|
|
onClick={async () => {
|
|
setError('');
|
|
try {
|
|
await login();
|
|
} catch (e: any) {
|
|
// Any WebAuthn error (NotAllowedError, SecurityError, AbortError)
|
|
// should show register form — user likely has no passkey yet
|
|
if (e.name === 'NotAllowedError' || e.name === 'SecurityError' || e.name === 'AbortError') {
|
|
setShowRegister(true);
|
|
} else {
|
|
setError(e.message || 'Sign in failed');
|
|
}
|
|
}
|
|
}}
|
|
disabled={loading}
|
|
className="text-sm text-white/60 hover:text-rmaps-primary transition-colors flex items-center gap-1"
|
|
>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} className="w-4 h-4">
|
|
<circle cx={12} cy={10} r={3} />
|
|
<path d="M12 13v8" />
|
|
<path d="M9 18h6" />
|
|
<circle cx={12} cy={10} r={7} />
|
|
</svg>
|
|
{loading ? 'Signing in...' : 'Sign in with Passkey'}
|
|
</button>
|
|
{error && <span className="text-xs text-red-400">{error}</span>}
|
|
</div>
|
|
);
|
|
}
|