Fix organization setup flow: redirect to onboarding for incomplete setup

This commit is contained in:
Ra
2025-08-18 10:33:45 -07:00
commit 557b113196
60 changed files with 16246 additions and 0 deletions

266
pages/Login.tsx Normal file
View 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;