Files
gridpilot.gg/apps/companion/renderer/components/LoginPrompt.tsx
2025-12-11 21:06:25 +01:00

294 lines
8.6 KiB
TypeScript

import React from 'react';
type LoginStatus = 'idle' | 'waiting' | 'success' | 'error';
interface LoginPromptProps {
authState: string;
errorMessage: string | undefined;
onLogin: () => void;
onRetry: () => void;
loginStatus?: LoginStatus;
}
export function LoginPrompt({
authState,
errorMessage,
onLogin,
onRetry,
loginStatus = 'idle'
}: LoginPromptProps) {
// Show success state when login completed
if (loginStatus === 'success') {
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
backgroundColor: '#1a1a1a',
padding: '2rem',
}}>
<div style={{
maxWidth: '500px',
width: '100%',
padding: '2rem',
backgroundColor: '#252525',
borderRadius: '12px',
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.3)',
textAlign: 'center',
}}>
<div style={{
width: '80px',
height: '80px',
margin: '0 auto 1.5rem',
backgroundColor: '#1a472a',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '2.5rem',
color: '#4ade80',
animation: 'scaleIn 0.3s ease-out',
}}>
</div>
<style>{`
@keyframes scaleIn {
from { transform: scale(0); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
<h1 style={{
color: '#4ade80',
fontSize: '1.75rem',
marginBottom: '0.5rem',
fontWeight: 600,
animation: 'fadeIn 0.4s ease-out 0.1s both',
}}>
Login Successful!
</h1>
<p style={{
color: '#aaa',
fontSize: '1rem',
marginBottom: '1rem',
lineHeight: 1.5,
animation: 'fadeIn 0.4s ease-out 0.2s both',
}}>
You're now connected to iRacing.
</p>
<p style={{
color: '#666',
fontSize: '0.9rem',
animation: 'fadeIn 0.4s ease-out 0.3s both',
}}>
Redirecting...
</p>
</div>
</div>
);
}
const getStateMessage = () => {
switch (authState) {
case 'EXPIRED':
return 'Your iRacing session has expired. Please log in again to continue.';
case 'LOGGED_OUT':
return 'You have been logged out. Please log in to use GridPilot.';
case 'UNKNOWN':
return errorMessage
? `Unable to verify authentication: ${errorMessage}`
: 'Unable to verify your authentication status.';
default:
return null; // Will show explanation instead
}
};
const stateMessage = getStateMessage();
const isWaiting = loginStatus === 'waiting';
return (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
backgroundColor: '#1a1a1a',
padding: '2rem',
}}>
<div style={{
maxWidth: '500px',
width: '100%',
padding: '2rem',
backgroundColor: '#252525',
borderRadius: '12px',
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.3)',
textAlign: 'center',
}}>
<div style={{
width: '80px',
height: '80px',
margin: '0 auto 1.5rem',
backgroundColor: '#333',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '2.5rem',
}}>
{isWaiting ? '' : '🔐'}
</div>
<h1 style={{
color: '#fff',
fontSize: '1.75rem',
marginBottom: '0.5rem',
fontWeight: 600,
}}>
{isWaiting ? 'Waiting for Login...' : 'iRacing Login Required'}
</h1>
{stateMessage ? (
<p style={{
color: '#aaa',
fontSize: '1rem',
marginBottom: '2rem',
lineHeight: 1.5,
}}>
{stateMessage}
</p>
) : (
<div style={{
color: '#aaa',
fontSize: '1rem',
marginBottom: '2rem',
lineHeight: 1.6,
textAlign: 'left',
}}>
<p style={{ marginBottom: '1rem' }}>
<strong style={{ color: '#fff' }}>Why do I need to log in?</strong>
</p>
<p style={{ marginBottom: '0.75rem' }}>
GridPilot needs to access your iRacing account to create and manage hosted sessions on your behalf. This requires authentication with iRacing's website.
</p>
<ul style={{
margin: '0.75rem 0',
paddingLeft: '1.25rem',
color: '#888',
}}>
<li style={{ marginBottom: '0.5rem' }}> Your credentials are entered directly on iRacing.com</li>
<li style={{ marginBottom: '0.5rem' }}> GridPilot never sees or stores your password</li>
<li> Session cookies are stored locally for convenience</li>
</ul>
</div>
)}
{isWaiting ? (
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '1rem',
}}>
<div style={{
width: '40px',
height: '40px',
border: '3px solid #333',
borderTopColor: '#007bff',
borderRadius: '50%',
animation: 'spin 1s linear infinite',
}} />
<style>{`
@keyframes spin {
to { transform: rotate(360deg); }
}
`}</style>
<p style={{
color: '#aaa',
fontSize: '0.95rem',
}}>
A browser window has opened. Please log in to iRacing.
</p>
<p style={{
color: '#666',
fontSize: '0.85rem',
}}>
This window will update automatically when login is detected.
</p>
</div>
) : (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '0.75rem',
}}>
<button
onClick={onLogin}
style={{
padding: '1rem 2rem',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '1.1rem',
fontWeight: 600,
transition: 'background-color 0.2s',
}}
onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#0056b3'}
onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#007bff'}
>
Log in to iRacing
</button>
{(authState === 'UNKNOWN' || errorMessage) && (
<button
onClick={onRetry}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: 'transparent',
color: '#aaa',
border: '1px solid #555',
borderRadius: '8px',
cursor: 'pointer',
fontSize: '0.95rem',
transition: 'all 0.2s',
}}
onMouseOver={(e) => {
e.currentTarget.style.borderColor = '#777';
e.currentTarget.style.color = '#fff';
}}
onMouseOut={(e) => {
e.currentTarget.style.borderColor = '#555';
e.currentTarget.style.color = '#aaa';
}}
>
Retry Connection
</button>
)}
</div>
)}
{!isWaiting && (
<p style={{
color: '#666',
fontSize: '0.85rem',
marginTop: '2rem',
lineHeight: 1.4,
}}>
A browser window will open for you to log in securely to iRacing.
The window will close automatically once login is complete.
</p>
)}
</div>
</div>
);
}