backstory/frontend/src/pages/BetaPage.tsx
2025-06-18 14:26:07 -07:00

327 lines
9.2 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import {
Box,
Container,
Typography,
Paper,
Grid,
Button,
alpha,
GlobalStyles,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import ConstructionIcon from "@mui/icons-material/Construction";
import RocketLaunchIcon from "@mui/icons-material/RocketLaunch";
import { Beta } from "../components/ui/Beta";
interface BetaPageProps {
children?: React.ReactNode;
title?: string;
subtitle?: string;
returnPath?: string;
returnLabel?: string;
onReturn?: () => void;
}
const BetaPage: React.FC<BetaPageProps> = ({
children,
title = "Coming Soon",
subtitle = "This page is currently in development",
returnPath = "/",
returnLabel = "Return to Backstory",
onReturn,
}) => {
const theme = useTheme();
const [showSparkle, setShowSparkle] = useState<boolean>(false);
const navigate = useNavigate();
const location = useLocation();
if (!children) {
children = (
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}>
<Typography>
The page you requested (<b>{location.pathname.replace(/^\//, "")}</b>)
is not yet ready.
</Typography>
</Box>
);
}
// Enhanced sparkle effect for background elements
const [sparkles, setSparkles] = useState<
Array<{
id: number;
x: number;
y: number;
size: number;
opacity: number;
duration: number;
delay: number;
}>
>([]);
useEffect(() => {
// Generate sparkle elements with random properties
const newSparkles = Array.from({ length: 30 }).map((_, index) => ({
id: index,
x: Math.random() * 100,
y: Math.random() * 100,
size: 2 + Math.random() * 5,
opacity: 0.3 + Math.random() * 0.7,
duration: 2 + Math.random() * 4,
delay: Math.random() * 3,
}));
setSparkles(newSparkles);
// Show main sparkle effect after a short delay
const timer = setTimeout(() => {
setShowSparkle(true);
}, 500);
return () => clearTimeout(timer);
}, []);
const handleReturn = () => {
if (onReturn) {
onReturn();
} else if (returnPath) {
navigate(returnPath);
}
};
return (
<Box
sx={{
minHeight: "100%",
width: "100%",
position: "relative",
overflow: "hidden",
bgcolor: theme.palette.background.default,
pt: 8,
pb: 6,
}}
>
{/* Animated background elements */}
<Box
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 0,
overflow: "hidden",
}}
>
{sparkles.map((sparkle) => (
<Box
key={sparkle.id}
sx={{
position: "absolute",
left: `${sparkle.x}%`,
top: `${sparkle.y}%`,
width: sparkle.size,
height: sparkle.size,
borderRadius: "50%",
bgcolor: alpha(theme.palette.primary.main, sparkle.opacity),
boxShadow: `0 0 ${sparkle.size * 2}px ${alpha(
theme.palette.primary.main,
sparkle.opacity
)}`,
animation: `float ${sparkle.duration}s ease-in-out ${sparkle.delay}s infinite alternate`,
}}
/>
))}
</Box>
<Container maxWidth="lg" sx={{ position: "relative", zIndex: 2 }}>
<Grid container spacing={4} direction="column" alignItems="center">
<Grid size={{ xs: 12 }} sx={{ textAlign: "center", mb: 2 }}>
<Typography
variant="h2"
component="h1"
gutterBottom
sx={{
fontWeight: "bold",
color: theme.palette.primary.main,
textShadow: `0 0 10px ${alpha(
theme.palette.primary.main,
0.3
)}`,
animation: showSparkle
? "titleGlow 3s ease-in-out infinite alternate"
: "none",
}}
>
{title}
</Typography>
<Typography
variant="h5"
component="h2"
color="textSecondary"
sx={{ mb: 6 }}
>
{subtitle}
</Typography>
</Grid>
<Grid size={{ xs: 12, md: 10, lg: 8 }} sx={{ mb: 4 }}>
<Paper
elevation={8}
sx={{
p: { xs: 3, md: 5 },
borderRadius: 2,
bgcolor: alpha(theme.palette.background.paper, 0.8),
backdropFilter: "blur(8px)",
boxShadow: `0 8px 32px ${alpha(
theme.palette.primary.main,
0.15
)}`,
border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,
position: "relative",
overflow: "hidden",
}}
>
{/* Construction icon */}
<Box
sx={{
position: "absolute",
top: -15,
right: -15,
bgcolor: theme.palette.warning.main,
color: theme.palette.warning.contrastText,
borderRadius: "50%",
p: 2,
boxShadow: 3,
transform: "rotate(15deg)",
}}
>
<ConstructionIcon fontSize="large" />
</Box>
{/* Content */}
<Box sx={{ mt: 3, mb: 3 }}>
{children || (
<Box sx={{ textAlign: "center", py: 4 }}>
<RocketLaunchIcon
fontSize="large"
color="primary"
sx={{
fontSize: 80,
mb: 2,
animation: "rocketWobble 3s ease-in-out infinite",
}}
/>
<Typography>
We're working hard to bring you this exciting new feature!
</Typography>
<Typography color="textSecondary" sx={{ mt: 1 }}>
Check back soon for updates.
</Typography>
</Box>
)}
<Beta
adaptive={false}
sx={{
opacity: 0.5,
left: "-72px",
"& > div": {
paddingRight: "30px",
background: "gold",
color: "#808080",
},
}}
onClick={() => {
navigate("/docs/beta");
}}
/>
</Box>
{/* Return button */}
<Box sx={{ mt: 4, textAlign: "center" }}>
<Button
variant="contained"
color="primary"
size="large"
onClick={handleReturn}
sx={{
px: 4,
py: 1,
borderRadius: 4,
boxShadow: `0 4px 14px ${alpha(
theme.palette.primary.main,
0.4
)}`,
"&:hover": {
boxShadow: `0 6px 20px ${alpha(
theme.palette.primary.main,
0.6
)}`,
},
}}
>
{returnLabel}
</Button>
</Box>
</Paper>
</Grid>
</Grid>
</Container>
{/* Global styles added with MUI's GlobalStyles component */}
<GlobalStyles
styles={{
"@keyframes float": {
"0%": {
transform: "translateY(0) scale(1)",
},
"100%": {
transform: "translateY(-20px) scale(1.1)",
},
},
"@keyframes sparkleFloat": {
"0%": {
transform: "translateY(0) scale(1)",
opacity: 0.7,
},
"50%": {
opacity: 1,
},
"100%": {
transform: "translateY(-15px) scale(1.2)",
opacity: 0.7,
},
},
"@keyframes titleGlow": {
"0%": {
textShadow: `0 0 10px ${alpha(theme.palette.primary.main, 0.3)}`,
},
"100%": {
textShadow: `0 0 25px ${alpha(
theme.palette.primary.main,
0.7
)}, 0 0 40px ${alpha(theme.palette.primary.main, 0.4)}`,
},
},
"@keyframes rocketWobble": {
"0%": {
transform: "translateY(0) rotate(0deg)",
},
"50%": {
transform: "translateY(-10px) rotate(3deg)",
},
"100%": {
transform: "translateY(0) rotate(-2deg)",
},
},
}}
/>
</Box>
);
};
export { BetaPage };