working companion prototype
This commit is contained in:
294
apps/companion/renderer/components/LoginPrompt.tsx
Normal file
294
apps/companion/renderer/components/LoginPrompt.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import React from 'react';
|
||||
|
||||
type LoginStatus = 'idle' | 'waiting' | 'success' | 'error';
|
||||
|
||||
interface LoginPromptProps {
|
||||
authState: string;
|
||||
errorMessage?: string;
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user