Fix API routes for Cloudflare Workers and enable preview URLs

- Remove unsupported next: { revalidate } option from API routes
- Add Cloudflare-specific cache configuration (cf.cacheTtl)
- Add User-Agent and Referer headers to bypass Plan API 403 errors
- Enable workers_dev and preview_urls in wrangler.jsonc to prevent disabling on deployment
This commit is contained in:
Nicholai 2025-11-09 20:59:46 -07:00
parent d0bd636a43
commit 3c2368d886
13 changed files with 3285 additions and 32 deletions

View File

@ -18,8 +18,6 @@ interface PlanPlayersResponse {
[key: string]: unknown; [key: string]: unknown;
} }
export const runtime = 'edge';
// Extract player name from HTML string // Extract player name from HTML string
function extractPlayerName(nameString: string): string { function extractPlayerName(nameString: string): string {
// Name format: '<a class="link" href="../player/...">PlayerName</a>' // Name format: '<a class="link" href="../player/...">PlayerName</a>'
@ -32,9 +30,14 @@ export async function GET() {
const playersResponse = await fetch( const playersResponse = await fetch(
`${PLAN_BASE_URL}/v1/players?server=${SERVER_NAME}`, `${PLAN_BASE_URL}/v1/players?server=${SERVER_NAME}`,
{ {
next: { revalidate: 300 }, // Cache for 5 minutes
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (compatible; BiohazardVFX/1.0)',
'Referer': PLAN_BASE_URL,
},
cf: {
cacheTtl: 300,
cacheEverything: true,
} }
} }
); );

View File

@ -25,9 +25,14 @@ export async function GET() {
const overviewResponse = await fetch( const overviewResponse = await fetch(
`${PLAN_BASE_URL}/v1/serverOverview?server=${SERVER_NAME}`, `${PLAN_BASE_URL}/v1/serverOverview?server=${SERVER_NAME}`,
{ {
next: { revalidate: 30 }, // Cache for 30 seconds
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (compatible; BiohazardVFX/1.0)',
'Referer': PLAN_BASE_URL,
},
cf: {
cacheTtl: 30,
cacheEverything: true,
} }
} }
); );

View File

@ -1,7 +1,5 @@
'use client'; 'use client';
import Link from 'next/link';
import StickyFooter from '@/components/ui/footer';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { MessageCircle } from 'lucide-react'; import { MessageCircle } from 'lucide-react';
import { toast } from 'sonner'; import { toast } from 'sonner';
@ -42,7 +40,7 @@ export function CustomFooter() {
try { try {
await navigator.clipboard.writeText('minecraft.biohazardvfx.com'); await navigator.clipboard.writeText('minecraft.biohazardvfx.com');
toast.success('Server IP copied to clipboard!'); toast.success('Server IP copied to clipboard!');
} catch (error) { } catch {
toast.error('Failed to copy IP address'); toast.error('Failed to copy IP address');
} }
}; };
@ -84,7 +82,7 @@ export function CustomFooter() {
{section.title} {section.title}
</h3> </h3>
{section.links.map((link, linkIndex) => { {section.links.map((link, linkIndex) => {
if (link.external) { if ('external' in link && link.external) {
return ( return (
<motion.a <motion.a
key={linkIndex} key={linkIndex}
@ -105,7 +103,7 @@ export function CustomFooter() {
</span> </span>
</motion.a> </motion.a>
); );
} else if (link.isButton) { } else if ('isButton' in link && link.isButton) {
return ( return (
<motion.button <motion.button
key={linkIndex} key={linkIndex}
@ -124,7 +122,7 @@ export function CustomFooter() {
</span> </span>
</motion.button> </motion.button>
); );
} else if (link.isStatic) { } else if ('isStatic' in link && link.isStatic) {
return ( return (
<span <span
key={linkIndex} key={linkIndex}

View File

@ -76,19 +76,19 @@ export function Hero() {
<span className="block text-primary break-words tracking-tighter text-shadow-md"> <span className="block text-primary break-words tracking-tighter text-shadow-md">
<HoverRollingText <HoverRollingText
text="Build." text="Build."
transition={{ duration: 0.6, delay: 0.05, ease: [0.4, 0, 0.2, 1] }} transition={{ duration: 0.6, delay: 0.05 }}
/> />
</span> </span>
<span className="block text-secondary break-words tracking-normal text-shadow-sm"> <span className="block text-secondary break-words tracking-normal text-shadow-sm">
<HoverRollingText <HoverRollingText
text="Explore." text="Explore."
transition={{ duration: 0.6, delay: 0.05, ease: [0.4, 0, 0.2, 1] }} transition={{ duration: 0.6, delay: 0.05 }}
/> />
</span> </span>
<span className="block text-accent break-words tracking-normal text-shadow-sm"> <span className="block text-accent break-words tracking-normal text-shadow-sm">
<HoverRollingText <HoverRollingText
text="Survive." text="Survive."
transition={{ duration: 0.6, delay: 0.05, ease: [0.4, 0, 0.2, 1] }} transition={{ duration: 0.6, delay: 0.05 }}
/> />
</span> </span>
</motion.h1> </motion.h1>

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from 'react';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Trophy, Clock, Calendar } from 'lucide-react'; import { Trophy, Clock } from 'lucide-react';
import { PlayerTooltip } from '@/components/player-tooltip'; import { PlayerTooltip } from '@/components/player-tooltip';
interface LeaderboardPlayer { interface LeaderboardPlayer {

View File

@ -5,7 +5,6 @@ import { useTheme } from 'next-themes';
export function ThemeTransition() { export function ThemeTransition() {
const { theme, resolvedTheme } = useTheme(); const { theme, resolvedTheme } = useTheme();
const [isTransitioning, setIsTransitioning] = useState(false);
const [prevTheme, setPrevTheme] = useState<string | undefined>(); const [prevTheme, setPrevTheme] = useState<string | undefined>();
const [capturedImage, setCapturedImage] = useState<string | null>(null); const [capturedImage, setCapturedImage] = useState<string | null>(null);
const overlayRef = useRef<HTMLDivElement>(null); const overlayRef = useRef<HTMLDivElement>(null);
@ -26,8 +25,6 @@ export function ThemeTransition() {
useEffect(() => { useEffect(() => {
const currentTheme = resolvedTheme || theme; const currentTheme = resolvedTheme || theme;
if (prevTheme && prevTheme !== currentTheme && currentTheme && overlayRef.current && capturedImage) { if (prevTheme && prevTheme !== currentTheme && currentTheme && overlayRef.current && capturedImage) {
setIsTransitioning(true);
// Set the captured image as background // Set the captured image as background
overlayRef.current.style.backgroundImage = `url(${capturedImage})`; overlayRef.current.style.backgroundImage = `url(${capturedImage})`;
overlayRef.current.style.backgroundSize = 'cover'; overlayRef.current.style.backgroundSize = 'cover';
@ -36,7 +33,6 @@ export function ThemeTransition() {
overlayRef.current.classList.add('theme-wipe-active'); overlayRef.current.classList.add('theme-wipe-active');
const timer = setTimeout(() => { const timer = setTimeout(() => {
setIsTransitioning(false);
if (overlayRef.current) { if (overlayRef.current) {
overlayRef.current.classList.remove('theme-wipe-active'); overlayRef.current.classList.remove('theme-wipe-active');
overlayRef.current.style.opacity = '0'; overlayRef.current.style.opacity = '0';

View File

@ -9,7 +9,6 @@ const containerVariants = {
y: 0, y: 0,
transition: { transition: {
duration: 0.8, duration: 0.8,
ease: "easeOut",
staggerChildren: 0.1, staggerChildren: 0.1,
}, },
}, },
@ -20,7 +19,7 @@ const itemVariants = {
visible: { visible: {
opacity: 1, opacity: 1,
x: 0, x: 0,
transition: { duration: 0.6, ease: "easeOut" }, transition: { duration: 0.6 },
}, },
} }
@ -29,7 +28,7 @@ const linkVariants = {
visible: { visible: {
opacity: 1, opacity: 1,
y: 0, y: 0,
transition: { duration: 0.4, ease: "easeOut" }, transition: { duration: 0.4 },
}, },
} }
@ -39,7 +38,7 @@ const socialVariants = {
opacity: 1, opacity: 1,
scale: 1, scale: 1,
transition: { transition: {
type: "spring", type: "spring" as const,
stiffness: 200, stiffness: 200,
damping: 10, damping: 10,
}, },
@ -53,7 +52,6 @@ const backgroundVariants = {
scale: 1, scale: 1,
transition: { transition: {
duration: 2, duration: 2,
ease: "easeOut",
}, },
}, },
} }

View File

@ -5,7 +5,7 @@ import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
const transition = { const transition = {
type: "spring", type: "spring" as const,
mass: 0.5, mass: 0.5,
damping: 11.5, damping: 11.5,
stiffness: 100, stiffness: 100,
@ -109,7 +109,7 @@ export const ProductItem = ({
); );
}; };
export const HoveredLink = ({ children, ...rest }: any) => { export const HoveredLink = ({ children, ...rest }: React.ComponentProps<typeof Link>) => {
return ( return (
<Link <Link
{...rest} {...rest}

View File

@ -35,20 +35,21 @@ export const AnimatedTooltip = ({ items }: AnimatedTooltipProps) => {
springConfig, springConfig,
); );
const handleMouseMove = (event: any) => { const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
if (animationFrameRef.current) { if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current); cancelAnimationFrame(animationFrameRef.current);
} }
animationFrameRef.current = requestAnimationFrame(() => { animationFrameRef.current = requestAnimationFrame(() => {
const halfWidth = event.target.offsetWidth / 2; const target = event.target as HTMLElement;
const halfWidth = target.offsetWidth / 2;
x.set(event.nativeEvent.offsetX - halfWidth); x.set(event.nativeEvent.offsetX - halfWidth);
}); });
}; };
return ( return (
<> <>
{items.map((item, idx) => ( {items.map((item) => (
<div <div
className="group relative -mr-4" className="group relative -mr-4"
key={item.name} key={item.name}

View File

@ -1,5 +1,4 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useState } from "react";
export const Component = () => { export const Component = () => {
return ( return (

View File

@ -1,5 +1,3 @@
import { MessageCircle } from "lucide-react";
export interface NavItem { export interface NavItem {
label: string; label: string;
href: string; href: string;
@ -29,6 +27,5 @@ export const navigationItems: Array<NavItem | NavDropdown> = [
label: "Community", label: "Community",
href: "https://discord.gg/invite", href: "https://discord.gg/invite",
external: true, external: true,
icon: <MessageCircle className="h-4 w-4" />,
}, },
]; ];

View File

@ -21,6 +21,8 @@
"zone_name": "biohazardvfx.com" "zone_name": "biohazardvfx.com"
} }
], ],
"workers_dev": true,
"preview_urls": true,
"observability": { "observability": {
"enabled": true "enabled": true
} }