Fix organization setup flow: redirect to onboarding for incomplete setup
This commit is contained in:
266
pages/Login.tsx
Normal file
266
pages/Login.tsx
Normal file
@@ -0,0 +1,266 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useOrg } from '../contexts/OrgContext';
|
||||
import { Card, Button } from '../components/UiKit';
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { inviteCode: routeInviteCode } = useParams<{ inviteCode: string }>();
|
||||
const [email, setEmail] = useState('demo@auditly.com');
|
||||
const [password, setPassword] = useState('demo123');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [inviteCode, setInviteCode] = useState<string | null>(null);
|
||||
const { signInWithGoogle, signInWithEmail, signUpWithEmail, user, loading } = useAuth();
|
||||
const { consumeInvite, org } = useOrg();
|
||||
|
||||
useEffect(() => {
|
||||
// Check for invite code in route params first, then fallback to query params
|
||||
if (routeInviteCode) {
|
||||
console.log('Invite code from route params:', routeInviteCode);
|
||||
setInviteCode(routeInviteCode);
|
||||
// Clear demo credentials for invite flow
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
} else {
|
||||
// Extract query params from hash-based URL
|
||||
const hashSearch = location.hash.includes('?') ? location.hash.split('?')[1] : '';
|
||||
const searchParams = new URLSearchParams(hashSearch);
|
||||
const queryInvite = searchParams.get('invite');
|
||||
if (queryInvite) {
|
||||
console.log('Invite code from query params:', queryInvite);
|
||||
setInviteCode(queryInvite);
|
||||
// Clear demo credentials for invite flow
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
}
|
||||
}
|
||||
}, [routeInviteCode, location]);
|
||||
|
||||
const handleSuccessfulLogin = async () => {
|
||||
if (inviteCode) {
|
||||
// Invite flow - redirect to org selection with invite code
|
||||
navigate(`/org-selection?invite=${inviteCode}`, { replace: true });
|
||||
} else {
|
||||
// Regular login - redirect to org selection to choose/create org
|
||||
navigate('/org-selection', { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user && !loading) {
|
||||
handleSuccessfulLogin();
|
||||
}
|
||||
}, [user, loading]);
|
||||
|
||||
const handleEmailLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
if (inviteCode) {
|
||||
// For invites, try to create account first since they're new users
|
||||
console.log('Invite flow: attempting to create account for', email);
|
||||
await signUpWithEmail(email, password, email.split('@')[0]);
|
||||
} else {
|
||||
// Regular login
|
||||
await signInWithEmail(email, password);
|
||||
}
|
||||
// Success will be handled by the useEffect hook
|
||||
} catch (error) {
|
||||
console.error('Auth failed:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
|
||||
if (inviteCode) {
|
||||
// For invite flow, if account creation failed, try login instead
|
||||
if (errorMessage.includes('User already exists') || errorMessage.includes('already-exists')) {
|
||||
try {
|
||||
console.log('Account exists, trying login instead...');
|
||||
await signInWithEmail(email, password);
|
||||
} catch (loginError) {
|
||||
console.error('Login also failed:', loginError);
|
||||
setError(`Account exists but password is incorrect. Please check your password or contact your administrator.`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
setError(`Failed to create account: ${errorMessage}. Please try a different email or contact your administrator.`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
// Regular login flow - try signup if user not found
|
||||
if (errorMessage.includes('User not found')) {
|
||||
try {
|
||||
console.log('User not found, attempting sign-up...');
|
||||
await signUpWithEmail(email, password, email.split('@')[0]);
|
||||
// Success will be handled by the useEffect hook
|
||||
} catch (signUpError) {
|
||||
console.error('Sign-up also failed:', signUpError);
|
||||
setError(`Failed to create account: ${signUpError instanceof Error ? signUpError.message : 'Unknown error'}`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
setError(`Login failed: ${errorMessage}`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoogleLogin = async () => {
|
||||
setIsLoading(true);
|
||||
setError('');
|
||||
try {
|
||||
await signInWithGoogle();
|
||||
// Success will be handled by the useEffect hook
|
||||
} catch (error) {
|
||||
console.error('Google login failed:', error);
|
||||
setError(`Google login failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-[--background-primary] py-12 px-4 sm:px-6 lg:px-8">
|
||||
<Card className="max-w-md w-full space-y-8" padding="lg">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4">
|
||||
A
|
||||
</div>
|
||||
<h2 className="text-3xl font-bold text-[--text-primary]">Welcome to Auditly</h2>
|
||||
<p className="text-[--text-secondary] mt-2">
|
||||
{inviteCode ? 'Complete your profile to join the team survey' : 'Sign in to your account'}
|
||||
</p>
|
||||
{inviteCode && (
|
||||
<div className="mt-3 p-3 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200">
|
||||
<EFBFBD> <strong>Employee Survey Invitation</strong><br />
|
||||
No account needed! Just create a password to secure your responses and start the questionnaire.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="mt-3 p-3 bg-red-100 dark:bg-red-900 rounded-lg">
|
||||
<p className="text-sm text-red-800 dark:text-red-200">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleEmailLogin} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Email {inviteCode && <span className="text-gray-500 dark:text-gray-400">(use your work email)</span>}
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Password {inviteCode && <span className="text-gray-500 dark:text-gray-400">(create a new password)</span>}
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
|
||||
required
|
||||
/>
|
||||
{inviteCode && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Choose a secure password for your new account
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Processing...' : (inviteCode ? 'Create Account & Join Team' : 'Sign In')}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">Or continue with</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="w-full"
|
||||
onClick={handleGoogleLogin}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Sign in with Google
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Manual invite code entry - only show if no invite code in URL */}
|
||||
{!inviteCode && (
|
||||
<div className="border-t border-[--border-color] pt-6">
|
||||
<div className="text-center mb-4">
|
||||
<h3 className="text-sm font-medium text-[--text-primary] mb-2">Employee? Use Your Invite Code</h3>
|
||||
<p className="text-xs text-[--text-secondary]">
|
||||
Skip account creation - employees can go directly to their questionnaire
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your invite code"
|
||||
className="flex-1 px-3 py-2 border border-[--input-border] rounded-lg bg-[--input-bg] text-[--text-primary] placeholder-[--input-placeholder] focus:outline-none focus:ring-2 focus:ring-[--accent] focus:border-[--accent]"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const code = (e.target as HTMLInputElement).value.trim();
|
||||
if (code) {
|
||||
window.location.href = `#/invite/${code}`;
|
||||
} else {
|
||||
alert('Please enter an invite code');
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
const input = document.querySelector('input[placeholder="Enter your invite code"]') as HTMLInputElement;
|
||||
const code = input?.value.trim();
|
||||
if (code) {
|
||||
window.location.href = `#/invite/${code}`;
|
||||
} else {
|
||||
alert('Please enter an invite code');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Start Survey
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-[--text-secondary] mt-2 text-center">
|
||||
No account needed - just answer questions and submit
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-xs text-[--text-secondary]">
|
||||
{inviteCode ?
|
||||
'Demo mode: Enter any email and password to create your account.' :
|
||||
'Demo mode: No Firebase configuration detected.\nUse any email/password to continue.'
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
Reference in New Issue
Block a user