profit-planet-frontend/src/app/page.tsx
2026-03-15 18:34:19 +01:00

205 lines
8.4 KiB
TypeScript

'use client';
import { useRef, useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import PageLayout from './components/PageLayout';
import Crosshair from './components/Crosshair';
import Waves from './components/background/waves';
import { usePublicDashboardPlatforms } from './hooks/usePublicDashboardPlatforms';
import {
LinkIcon,
ShoppingBagIcon,
UserCircleIcon,
UsersIcon,
} from '@heroicons/react/24/outline';
import type { ComponentType, SVGProps } from 'react';
import type { DashboardPlatformIconName } from './utils/dashboardPlatforms';
export default function HomePage() {
const containerRef = useRef<HTMLDivElement | null>(null);
const [isMobile, setIsMobile] = useState(() => {
if (typeof window === 'undefined') return false;
return window.matchMedia('(max-width: 768px)').matches;
});
const router = useRouter();
const isShopEnabled = process.env.NEXT_PUBLIC_SHOW_SHOP !== 'false';
const { platforms, loading, error } = usePublicDashboardPlatforms();
// Keep breakpoint updated (resize/orientation)
useEffect(() => {
const mq = window.matchMedia('(max-width: 768px)');
const apply = () => setIsMobile(mq.matches);
mq.addEventListener?.('change', apply);
window.addEventListener('resize', apply, { passive: true });
return () => {
mq.removeEventListener?.('change', apply);
window.removeEventListener('resize', apply);
};
}, []);
const icons: Record<DashboardPlatformIconName, ComponentType<SVGProps<SVGSVGElement>>> = {
ShoppingBagIcon,
LinkIcon,
UsersIcon,
UserCircleIcon,
};
const goTo = (href: string) => {
const trimmed = (href || '').trim();
if (!trimmed) return;
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
window.location.href = trimmed;
return;
}
router.push(trimmed);
};
return (
<PageLayout>
<div
ref={containerRef}
className="min-h-screen relative overflow-hidden bg-transparent text-gray-900"
>
{/* Waves background */}
<Waves
className="pointer-events-none"
lineColor="#0f172a"
backgroundColor="rgba(245, 245, 240, 1)"
waveSpeedX={0.02}
waveSpeedY={0.01}
waveAmpX={40}
waveAmpY={20}
friction={0.9}
tension={0.01}
maxCursorMove={120}
xGap={12}
yGap={36}
animate={!isMobile}
interactive={!isMobile}
/>
<div className="relative z-10 min-h-screen flex items-center px-4 py-10 sm:py-14">
<div className="w-full max-w-7xl mx-auto">
<div className="flex flex-col gap-6">
<div className="rounded-3xl bg-white/70 backdrop-blur-md border border-white/60 shadow-lg p-6 sm:p-8">
<div className="flex flex-col sm:flex-row sm:items-center gap-5 sm:gap-6">
<img
src="/images/logos/PP_Logo_BW_round.png"
alt="Profit Planet"
className="h-16 w-16 sm:h-20 sm:w-20 object-contain"
/>
<div className="min-w-0 flex-1">
<div className="inline-flex items-center rounded-full border border-gray-200 bg-white/60 px-3 py-1 text-xs font-semibold text-gray-700">
Welcome
</div>
<h1 className="mt-3 text-5xl sm:text-6xl md:text-7xl font-black tracking-tight leading-none text-transparent bg-clip-text bg-gradient-to-r from-gray-900 via-gray-700 to-amber-700">
Profit Planet
</h1>
<p className="mt-3 text-sm sm:text-base text-gray-700">
Pick a platform to continue.
</p>
</div>
</div>
</div>
<div className="rounded-3xl bg-white/70 backdrop-blur-md border border-white/60 shadow-lg p-6 sm:p-8">
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-xl sm:text-2xl font-bold text-gray-900">Platforms</h2>
<p className="mt-1 text-sm text-gray-700">Navigation shortcuts</p>
</div>
</div>
<div className="mt-6">
{loading && (
<div className="rounded-2xl border border-gray-200 bg-white p-6 text-center text-sm text-gray-600">
Loading
</div>
)}
{error && (
<div className="rounded-2xl border border-red-200 bg-red-50 p-6 text-center text-sm text-red-800">
{error}
</div>
)}
{!loading && !error && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{platforms.map((platform) => {
const Icon = icons[platform.icon] || LinkIcon;
const disabledByEnv = platform.href === '/shop' && !isShopEnabled;
const isDisabled = Boolean(platform.disabled) || disabledByEnv;
const disabledText = disabledByEnv ? 'This is currently disabled.' : platform.disabledText;
return (
<button
key={platform.id}
onClick={() => {
if (!isDisabled) {
goTo(platform.href);
}
}}
disabled={isDisabled}
className={`rounded-2xl border text-left p-5 transition-all duration-200 ${
isDisabled
? 'border-gray-200 bg-white opacity-60 cursor-not-allowed'
: 'group border-gray-200 bg-white shadow-sm hover:shadow-md hover:-translate-y-0.5'
}`}
>
<div className="flex items-start gap-4">
<div
className={`${platform.color} rounded-xl p-3 ${
isDisabled
? 'grayscale'
: 'transition-transform group-hover:scale-105'
}`}
>
<Icon className="h-6 w-6 text-white" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-3">
<h3
className={`text-base font-semibold transition-colors ${
isDisabled
? 'text-gray-500'
: 'text-gray-900 hover:text-amber-700'
}`}
>
{platform.title}
</h3>
</div>
<p className="mt-1 text-sm text-gray-600">
{platform.description}
</p>
{isDisabled && disabledText && (
<p className="mt-3 text-xs font-medium text-amber-700">
{disabledText}
</p>
)}
</div>
</div>
</button>
);
})}
{platforms.length === 0 && (
<div className="sm:col-span-2 lg:col-span-3 rounded-2xl border border-gray-200 bg-white p-8 text-center text-sm text-gray-600">
No platforms available.
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* No parallax/crosshair on mobile */}
{!isMobile && <Crosshair containerRef={containerRef} color="#0f172a" />}
</div>
</PageLayout>
);
}