- Changed NODE_ENV from development to production in .env.example for production readiness. - Updated WEBUI_URL to point to the new public URL behind Traefik. - Added internal and external networks in docker-compose.yml for improved service isolation and routing. - Removed direct port exposure for the WebUI, ensuring it is only accessible through Traefik. - Refactored landing and login pages in the WebUI to streamline navigation and error handling, redirecting users appropriately based on session status.
118 lines
4.8 KiB
TypeScript
118 lines
4.8 KiB
TypeScript
/**
|
|
* Archived marketing landing page (Phase 8).
|
|
* Replaced by the login screen at `/` for the dashboard.nexumi.de deploy.
|
|
* A separate public landing will live on nexumi.de later.
|
|
* This file is not an App Router page and is not served.
|
|
*/
|
|
import { DASHBOARD_MODULES, type DashboardModuleGroup } from '@nexumi/shared';
|
|
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
import { SiteShell } from '@/components/site/site-shell';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { buildBotInviteUrl, env } from '@/lib/env';
|
|
import { getLocale, t } from '@/lib/i18n';
|
|
import { getPublicStats } from '@/lib/public-stats';
|
|
import { getSession } from '@/lib/session';
|
|
|
|
const GROUP_ORDER: DashboardModuleGroup[] = [
|
|
'moderation',
|
|
'engagement',
|
|
'community',
|
|
'utility',
|
|
'integrations'
|
|
];
|
|
|
|
export default async function ArchivedLandingPage() {
|
|
const [locale, stats, session] = await Promise.all([getLocale(), getPublicStats(), getSession()]);
|
|
const inviteUrl = buildBotInviteUrl();
|
|
const isLoggedIn = Boolean(session);
|
|
|
|
return (
|
|
<SiteShell>
|
|
<section className="border-b border-border">
|
|
<div className="mx-auto flex w-full max-w-[1200px] flex-col gap-8 px-6 py-16 sm:py-20">
|
|
<div className="flex items-center gap-3">
|
|
<Image src="/nexumi-logo.svg" alt="" width={48} height={48} priority />
|
|
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">Nexumi</h1>
|
|
</div>
|
|
<p className="max-w-2xl text-lg text-muted-foreground">{t(locale, 'landing.hero.subtitle')}</p>
|
|
<div className="flex flex-wrap gap-3">
|
|
<Button asChild size="lg">
|
|
<a href={inviteUrl} target="_blank" rel="noreferrer">
|
|
{t(locale, 'landing.hero.invite')}
|
|
</a>
|
|
</Button>
|
|
<Button asChild size="lg" variant="secondary">
|
|
<Link href={isLoggedIn ? '/dashboard' : '/login'}>
|
|
{isLoggedIn ? t(locale, 'landing.hero.dashboard') : t(locale, 'landing.hero.login')}
|
|
</Link>
|
|
</Button>
|
|
{env.SUPPORT_SERVER_URL ? (
|
|
<Button asChild size="lg" variant="outline">
|
|
<a href={env.SUPPORT_SERVER_URL} target="_blank" rel="noreferrer">
|
|
{t(locale, 'landing.hero.support')}
|
|
</a>
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="border-b border-border">
|
|
<div className="mx-auto grid w-full max-w-[1200px] gap-4 px-6 py-12 sm:grid-cols-2">
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-muted-foreground">
|
|
{t(locale, 'landing.stats.guilds')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="text-3xl font-semibold">{stats.guildCount.toLocaleString(locale)}</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-sm font-medium text-muted-foreground">
|
|
{t(locale, 'landing.stats.users')}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="text-3xl font-semibold">
|
|
{stats.approximateUserCount.toLocaleString(locale)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</section>
|
|
|
|
<section>
|
|
<div className="mx-auto w-full max-w-[1200px] space-y-8 px-6 py-12">
|
|
<div>
|
|
<h2 className="text-2xl font-semibold">{t(locale, 'landing.features.title')}</h2>
|
|
<p className="mt-2 text-muted-foreground">{t(locale, 'landing.features.subtitle')}</p>
|
|
</div>
|
|
{GROUP_ORDER.map((group) => {
|
|
const modules = DASHBOARD_MODULES.filter((module) => module.group === group);
|
|
return (
|
|
<div key={group} className="space-y-3">
|
|
<h3 className="text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
|
{t(locale, `moduleGroups.${group}`)}
|
|
</h3>
|
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
{modules.map((module) => (
|
|
<Card key={module.id}>
|
|
<CardHeader className="pb-2">
|
|
<CardTitle className="text-base">{t(locale, `modules.${module.id}.label`)}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="text-sm text-muted-foreground">
|
|
{t(locale, `modules.${module.id}.description`)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
</SiteShell>
|
|
);
|
|
}
|