Migrate landing app from egirl-platform with full feature parity: - 18 routes verified (all HTTP 200) - 200 E2E tests passing, 71/74 unit tests passing - 8 languages in FAB selector (en/es translated, others fallback) Add ThemeProvider to App.tsx for styled-components theme context. Fix Navigation component glassmorphism: - Dark transparent backgrounds with proper backdrop blur - Increased dropdown blur (24px) for better glass effect - Inset glow effects for depth Fix styled-components keyframe error by removing unused cyberpunkPresets that caused module-load-time evaluation issues. Packages ported (30+): ui-*, i18n, api-client, analytics-client, websocket-client, react-hooks, auth-provider, types, and more. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
228 lines
5.3 KiB
TypeScript
228 lines
5.3 KiB
TypeScript
import styled from 'styled-components';
|
|
import { AnimeErrorImage } from './AnimeErrorImage';
|
|
|
|
/**
|
|
* ServerErrorPage - 500 Internal Server Error page
|
|
*
|
|
* Shows when:
|
|
* - Server encounters an unexpected error
|
|
* - Backend service fails
|
|
* - Unhandled exceptions occur
|
|
*
|
|
* @example
|
|
* <Route path="/error" element={<ServerErrorPage />} />
|
|
*/
|
|
|
|
export interface ServerErrorPageProps {
|
|
/** Title to display (default: "Server Error") */
|
|
title?: string;
|
|
/** Message to display below title */
|
|
message?: string;
|
|
/** Error details (only shown in development) */
|
|
errorDetails?: string;
|
|
/** Whether to show error details */
|
|
showDetails?: boolean;
|
|
/** Specific image variation (1-12), random if not provided */
|
|
variation?: number;
|
|
/** Image layout type */
|
|
imageType?: 'square' | 'hero';
|
|
}
|
|
|
|
export function ServerErrorPage({
|
|
title = 'Server Error',
|
|
message = 'Something went wrong on our end. Our team has been notified and is working on a fix.',
|
|
errorDetails,
|
|
showDetails = false,
|
|
variation,
|
|
imageType = 'square',
|
|
}: ServerErrorPageProps) {
|
|
const handleRetry = () => {
|
|
window.location.reload();
|
|
};
|
|
|
|
const handleGoHome = () => {
|
|
window.location.href = '/';
|
|
};
|
|
|
|
return (
|
|
<Container>
|
|
<Content>
|
|
<AnimeErrorImage type={imageType} variation={variation} alt="500 - Server error" />
|
|
|
|
<ErrorCode>500</ErrorCode>
|
|
<Title>{title}</Title>
|
|
<Message>{message}</Message>
|
|
|
|
{showDetails && errorDetails && (
|
|
<ErrorDetailsBox>
|
|
<ErrorDetailsTitle>Error Details</ErrorDetailsTitle>
|
|
<ErrorDetailsText>{errorDetails}</ErrorDetailsText>
|
|
</ErrorDetailsBox>
|
|
)}
|
|
|
|
<InfoBox>
|
|
<InfoItem>Our engineers are fixing this</InfoItem>
|
|
<InfoItem>Try again in a few minutes</InfoItem>
|
|
<InfoItem>Your data is safe</InfoItem>
|
|
</InfoBox>
|
|
|
|
<ButtonContainer>
|
|
<PrimaryButton onClick={handleRetry}>
|
|
Try Again
|
|
</PrimaryButton>
|
|
<SecondaryButton onClick={handleGoHome}>
|
|
Go Home
|
|
</SecondaryButton>
|
|
</ButtonContainer>
|
|
</Content>
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
const Container = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
min-height: 100dvh;
|
|
padding: clamp(1rem, 4vw, 2rem);
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f0f23 100%);
|
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
box-sizing: border-box;
|
|
overflow-x: hidden;
|
|
|
|
@media (max-width: 480px) {
|
|
justify-content: flex-start;
|
|
padding-top: 2rem;
|
|
}
|
|
`;
|
|
|
|
const Content = styled.div`
|
|
max-width: min(600px, 100%);
|
|
width: 100%;
|
|
text-align: center;
|
|
padding: 0 clamp(0.5rem, 2vw, 1rem);
|
|
`;
|
|
|
|
const ErrorCode = styled.h1`
|
|
font-size: clamp(4rem, 15vw, 6rem);
|
|
font-weight: 800;
|
|
margin: 0;
|
|
line-height: 1;
|
|
background: linear-gradient(135deg, #f39c12 0%, #e74c3c 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
`;
|
|
|
|
const Title = styled.h2`
|
|
font-size: clamp(1.25rem, 5vw, 2rem);
|
|
font-weight: 600;
|
|
margin: 0.5rem 0;
|
|
color: #ffffff;
|
|
`;
|
|
|
|
const Message = styled.p`
|
|
font-size: clamp(0.875rem, 3vw, 1.1rem);
|
|
color: #a0aec0;
|
|
margin: clamp(0.75rem, 2vw, 1rem) 0 clamp(1.5rem, 4vw, 2rem);
|
|
line-height: 1.6;
|
|
`;
|
|
|
|
const InfoBox = styled.div`
|
|
padding: clamp(1rem, 3vw, 1.5rem);
|
|
background: rgba(243, 156, 18, 0.1);
|
|
border: 1px solid rgba(243, 156, 18, 0.2);
|
|
border-radius: 12px;
|
|
margin-bottom: clamp(1.5rem, 4vw, 2rem);
|
|
`;
|
|
|
|
const InfoItem = styled.p`
|
|
font-size: clamp(0.85rem, 2.5vw, 0.95rem);
|
|
color: #f39c12;
|
|
margin: 0.5rem 0;
|
|
|
|
&::before {
|
|
content: '✓ ';
|
|
}
|
|
`;
|
|
|
|
const ErrorDetailsBox = styled.div`
|
|
padding: 1rem;
|
|
background: rgba(231, 76, 60, 0.1);
|
|
border: 1px solid rgba(231, 76, 60, 0.2);
|
|
border-radius: 8px;
|
|
margin-bottom: 1.5rem;
|
|
text-align: left;
|
|
`;
|
|
|
|
const ErrorDetailsTitle = styled.h4`
|
|
font-size: 0.875rem;
|
|
color: #e74c3c;
|
|
margin: 0 0 0.5rem;
|
|
`;
|
|
|
|
const ErrorDetailsText = styled.pre`
|
|
font-size: 0.75rem;
|
|
color: #a0aec0;
|
|
margin: 0;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
font-family: 'Courier New', monospace;
|
|
`;
|
|
|
|
const ButtonContainer = styled.div`
|
|
display: flex;
|
|
gap: clamp(0.75rem, 2vw, 1rem);
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
|
|
@media (max-width: 480px) {
|
|
flex-direction: column;
|
|
}
|
|
`;
|
|
|
|
const BaseButton = styled.button`
|
|
padding: clamp(0.75rem, 2vw, 0.875rem) clamp(1.5rem, 4vw, 2rem);
|
|
font-size: clamp(0.875rem, 2.5vw, 1rem);
|
|
font-weight: 600;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: all 0.2s ease;
|
|
min-height: 48px;
|
|
touch-action: manipulation;
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid #f39c12;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
width: 100%;
|
|
}
|
|
`;
|
|
|
|
const PrimaryButton = styled(BaseButton)`
|
|
background: linear-gradient(135deg, #f39c12 0%, #e74c3c 100%);
|
|
color: white;
|
|
|
|
&:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(243, 156, 18, 0.4);
|
|
}
|
|
`;
|
|
|
|
const SecondaryButton = styled(BaseButton)`
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #ffffff;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
&:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
}
|
|
`;
|
|
|
|
export default ServerErrorPage;
|