Backup: 2025-06-07 18:32 - Production setup complete

[Restore from backup: vip-coordinator-backup-2025-06-07-18-32-production-setup-complete]
This commit is contained in:
2025-06-07 18:32:00 +02:00
parent aa900505b9
commit ae3702c3b1
32 changed files with 2120 additions and 1494 deletions

View File

@@ -94,101 +94,6 @@
margin: 0;
}
.dev-login-divider {
display: flex;
align-items: center;
gap: 12px;
margin: 24px 0;
color: #94a3b8;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.dev-login-divider::before,
.dev-login-divider::after {
content: '';
flex: 1;
height: 1px;
background: #e2e8f0;
}
.dev-login-form {
display: flex;
flex-direction: column;
gap: 16px;
text-align: left;
}
.dev-login-form h3 {
margin: 0;
color: #1e293b;
font-size: 18px;
font-weight: 600;
}
.dev-login-hint {
margin: 0;
font-size: 13px;
color: #64748b;
line-height: 1.4;
}
.dev-login-form label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 14px;
color: #334155;
}
.dev-login-form input {
width: 100%;
padding: 10px 12px;
border: 1px solid #cbd5f5;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.dev-login-form input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
.dev-login-error {
background: #fee2e2;
border: 1px solid #fecaca;
color: #dc2626;
padding: 10px 12px;
border-radius: 8px;
font-size: 13px;
}
.dev-login-button {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
color: white;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.dev-login-button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(37, 99, 235, 0.2);
}
.dev-login-button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.login-footer {
border-top: 1px solid #eee;
padding-top: 20px;

View File

@@ -3,15 +3,15 @@ import { apiCall } from '../config/api';
import './Login.css';
interface LoginProps {
onLogin: () => void;
errorMessage?: string | null | undefined;
onLogin: (user: any) => void;
}
const Login: React.FC<LoginProps> = ({ onLogin, errorMessage }) => {
const Login: React.FC<LoginProps> = ({ onLogin }) => {
const [setupStatus, setSetupStatus] = useState<any>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check system setup status
apiCall('/auth/setup')
.then(res => res.json())
.then(data => {
@@ -22,7 +22,82 @@ const Login: React.FC<LoginProps> = ({ onLogin, errorMessage }) => {
console.error('Error checking setup status:', error);
setLoading(false);
});
}, []);
// Check for OAuth callback code in URL
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const error = urlParams.get('error');
const token = urlParams.get('token');
if (code && (window.location.pathname === '/auth/google/callback' || window.location.pathname === '/auth/callback')) {
// Exchange code for token
apiCall('/auth/google/exchange', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code })
})
.then(res => {
if (!res.ok) {
throw new Error('Failed to exchange code for token');
}
return res.json();
})
.then(({ token, user }) => {
localStorage.setItem('authToken', token);
onLogin(user);
// Clean up URL and redirect to dashboard
window.history.replaceState({}, document.title, '/');
})
.catch(error => {
console.error('OAuth exchange failed:', error);
alert('Login failed. Please try again.');
// Clean up URL
window.history.replaceState({}, document.title, '/');
});
} else if (token && (window.location.pathname === '/auth/callback' || window.location.pathname === '/auth/google/callback')) {
// Direct token from URL (from backend redirect)
localStorage.setItem('authToken', token);
apiCall('/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(user => {
onLogin(user);
// Clean up URL and redirect to dashboard
window.history.replaceState({}, document.title, '/');
})
.catch(error => {
console.error('Error getting user info:', error);
localStorage.removeItem('authToken');
// Clean up URL
window.history.replaceState({}, document.title, '/');
});
} else if (error) {
console.error('Authentication error:', error);
alert(`Login error: ${error}`);
// Clean up URL
window.history.replaceState({}, document.title, '/');
}
}, [onLogin]);
const handleGoogleLogin = async () => {
try {
// Get OAuth URL from backend
const response = await apiCall('/auth/google/url');
const { url } = await response.json();
// Redirect to Google OAuth
window.location.href = url;
} catch (error) {
console.error('Failed to get OAuth URL:', error);
alert('Login failed. Please try again.');
}
};
if (loading) {
return (
@@ -45,38 +120,55 @@ const Login: React.FC<LoginProps> = ({ onLogin, errorMessage }) => {
{!setupStatus?.firstAdminCreated && (
<div className="setup-notice">
<h3>🚀 First Time Setup</h3>
<p>The first person to sign in will be promoted to administrator automatically.</p>
<p>The first person to log in will become the system administrator.</p>
</div>
)}
<div className="login-content">
<button
<button
className="google-login-btn"
onClick={onLogin}
onClick={handleGoogleLogin}
disabled={false}
>
<svg className="google-icon" viewBox="0 0 24 24">
<path fill="#635dff" d="M22 12.07c0-5.52-4.48-10-10-10s-10 4.48-10 10a9.97 9.97 0 006.85 9.48.73.73 0 00.95-.7v-3.05c-2.79.61-3.38-1.19-3.38-1.19-.46-1.17-1.12-1.49-1.12-1.49-.91-.62.07-.61.07-.61 1 .07 1.53 1.03 1.53 1.03.9 1.53 2.37 1.09 2.96.83.09-.65.35-1.09.63-1.34-2.23-.25-4.57-1.12-4.57-4.96 0-1.1.39-2 1.03-2.7-.1-.25-.45-1.25.1-2.6 0 0 .84-.27 2.75 1.02a9.53 9.53 0 015 0c1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.35.1 2.6.64.7 1.03 1.6 1.03 2.7 0 3.85-2.34 4.71-4.58 4.95.36.31.69.92.69 1.86v2.75c0 .39.27.71.66.79a10 10 0 007.61-9.71z"/>
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Continue with Auth0
Continue with Google
</button>
<div className="login-info">
<p>
{setupStatus?.authProvider === 'auth0'
? 'Sign in with your organisation account. We use Auth0 for secure authentication.'
: 'Authentication service is being configured. Please try again later.'}
{setupStatus?.firstAdminCreated
? "Sign in with your Google account to access the VIP Coordinator."
: "Sign in with Google to set up your administrator account."
}
</p>
</div>
{errorMessage && (
<div className="dev-login-error" style={{ marginTop: '1rem' }}>
{errorMessage}
{setupStatus && !setupStatus.setupCompleted && (
<div style={{
marginTop: '1rem',
padding: '1rem',
backgroundColor: '#fff3cd',
borderRadius: '6px',
border: '1px solid #ffeaa7',
fontSize: '0.9rem'
}}>
<strong> Setup Required:</strong>
<p style={{ margin: '0.5rem 0 0 0' }}>
Google OAuth credentials need to be configured. If the login doesn't work,
please follow the setup guide in <code>GOOGLE_OAUTH_SETUP.md</code> to configure
your Google Cloud Console credentials in the admin dashboard.
</p>
</div>
)}
</div>
<div className="login-footer">
<p>Secure authentication powered by Auth0</p>
<p>Secure authentication powered by Google OAuth</p>
</div>
</div>
</div>