platform-codebase/@packages/@ui/ui-error-pages/src/ServerErrorPage.tsx
Quinn Ftw 84d1333284 feat(landing): complete migration with glassmorphism navigation
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>
2025-12-26 17:11:07 -08:00

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;