Commits all remaining uncommitted local work: - apps/web: fischerei, verband, modules, members-cms, documents, newsletter, meetings, site-builder, courses, bookings, events, finance pages and components - apps/web: marketing page updates, layout, paths config, next.config.mjs, styles/makerkit.css - apps/web/i18n: documents, fischerei, marketing, verband (de+en) - packages/features: finance, fischerei, member-management, module-builder, newsletter, sitzungsprotokolle, verbandsverwaltung server APIs and components - packages/ui: button.tsx updates - pnpm-lock.yaml
545 lines
19 KiB
TypeScript
545 lines
19 KiB
TypeScript
import Image from 'next/image';
|
|
import Link from 'next/link';
|
|
|
|
import {
|
|
ArrowRightIcon,
|
|
CalendarIcon,
|
|
FileTextIcon,
|
|
GraduationCapIcon,
|
|
LayoutDashboardIcon,
|
|
MailIcon,
|
|
ShieldCheckIcon,
|
|
UsersIcon,
|
|
WalletIcon,
|
|
BedDoubleIcon,
|
|
GlobeIcon,
|
|
ZapIcon,
|
|
HeadsetIcon,
|
|
LockIcon,
|
|
SmartphoneIcon,
|
|
CheckIcon,
|
|
} from 'lucide-react';
|
|
|
|
import { PricingTable } from '@kit/billing-gateway/marketing';
|
|
import {
|
|
CtaButton,
|
|
EcosystemShowcase,
|
|
FeatureShowcase,
|
|
FeatureShowcaseIconContainer,
|
|
GradientText,
|
|
Hero,
|
|
Pill,
|
|
SecondaryHero,
|
|
} from '@kit/ui/marketing';
|
|
import { Trans } from '@kit/ui/trans';
|
|
import { cn } from '@kit/ui/utils';
|
|
|
|
import billingConfig from '~/config/billing.config';
|
|
import pathsConfig from '~/config/paths.config';
|
|
|
|
import { AnimateOnScroll } from './_components/animate-on-scroll';
|
|
|
|
function Home() {
|
|
return (
|
|
<div className={'mt-4 flex flex-col space-y-24 py-14 lg:space-y-36'}>
|
|
{/* Hero Section */}
|
|
<div className={'mx-auto'}>
|
|
<Hero
|
|
pill={
|
|
<Pill label={'Neu'}>
|
|
<span>
|
|
<Trans i18nKey={'marketing.heroPill'} />
|
|
</span>
|
|
</Pill>
|
|
}
|
|
title={
|
|
<span className="text-secondary-foreground">
|
|
<Trans i18nKey={'marketing.heroTitleLine1'} />{' '}
|
|
<GradientText className="from-primary to-primary/60">
|
|
<Trans i18nKey={'marketing.heroTitleLine2'} />
|
|
</GradientText>
|
|
</span>
|
|
}
|
|
subtitle={
|
|
<span>
|
|
<Trans i18nKey={'marketing.heroSubtitle'} />
|
|
</span>
|
|
}
|
|
cta={<MainCallToActionButton />}
|
|
image={
|
|
<div className="relative">
|
|
<div
|
|
className="bg-primary/10 absolute inset-0 -z-10 mx-auto max-w-3xl rounded-full blur-3xl"
|
|
aria-hidden="true"
|
|
/>
|
|
<Image
|
|
priority
|
|
className={
|
|
'dark:border-primary/10 w-full rounded-2xl border border-gray-200 shadow-2xl'
|
|
}
|
|
width={3558}
|
|
height={2222}
|
|
src={`/images/dashboard.webp`}
|
|
alt={`MyEasyCMS Dashboard`}
|
|
/>
|
|
</div>
|
|
}
|
|
/>
|
|
</div>
|
|
|
|
{/* Stats Bar */}
|
|
<AnimateOnScroll>
|
|
<div className={'container mx-auto'}>
|
|
<div className="border-border border-y py-8">
|
|
<p className="text-muted-foreground mb-6 text-center text-sm font-medium tracking-widest uppercase">
|
|
<Trans i18nKey={'marketing.trustedBy'} />
|
|
</p>
|
|
<div className="reveal-stagger divide-border flex flex-wrap items-center justify-center divide-x">
|
|
<StatItem value="69,000+" labelKey="marketing.statMembers" />
|
|
<StatItem value="90+" labelKey="marketing.statOrganizations" />
|
|
<StatItem value="22" labelKey="marketing.statYears" />
|
|
<StatItem value="3" labelKey="marketing.statFederations" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Core Modules Feature Grid */}
|
|
<AnimateOnScroll>
|
|
<div className={'container mx-auto'}>
|
|
<div className={'py-4 xl:py-8'}>
|
|
<FeatureShowcase
|
|
heading={
|
|
<>
|
|
<b className="font-medium tracking-tight dark:text-white">
|
|
<Trans i18nKey={'marketing.featuresHeading'} />
|
|
</b>
|
|
.{' '}
|
|
<span className="text-secondary-foreground/70 block font-normal tracking-tight">
|
|
<Trans i18nKey={'marketing.featuresSubheading'} />
|
|
</span>
|
|
</>
|
|
}
|
|
icon={
|
|
<FeatureShowcaseIconContainer>
|
|
<LayoutDashboardIcon className="h-4 w-4" />
|
|
<span>
|
|
<Trans i18nKey={'marketing.featuresLabel'} />
|
|
</span>
|
|
</FeatureShowcaseIconContainer>
|
|
}
|
|
>
|
|
<div className="reveal-stagger mt-2 grid w-full grid-cols-1 gap-4 md:mt-6 md:grid-cols-2 lg:grid-cols-3">
|
|
<IconFeatureCard
|
|
icon={UsersIcon}
|
|
titleKey="marketing.featureMembersTitle"
|
|
descKey="marketing.featureMembersDesc"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={GraduationCapIcon}
|
|
titleKey="marketing.featureCoursesTitle"
|
|
descKey="marketing.featureCoursesDesc"
|
|
accentBg="bg-chart-1/10"
|
|
accentText="text-chart-1"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={BedDoubleIcon}
|
|
titleKey="marketing.featureBookingsTitle"
|
|
descKey="marketing.featureBookingsDesc"
|
|
accentBg="bg-chart-2/10"
|
|
accentText="text-chart-2"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={CalendarIcon}
|
|
titleKey="marketing.featureEventsTitle"
|
|
descKey="marketing.featureEventsDesc"
|
|
accentBg="bg-chart-3/10"
|
|
accentText="text-chart-3"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={WalletIcon}
|
|
titleKey="marketing.featureFinanceTitle"
|
|
descKey="marketing.featureFinanceDesc"
|
|
accentBg="bg-chart-4/10"
|
|
accentText="text-chart-4"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={MailIcon}
|
|
titleKey="marketing.featureNewsletterTitle"
|
|
descKey="marketing.featureNewsletterDesc"
|
|
accentBg="bg-chart-5/10"
|
|
accentText="text-chart-5"
|
|
/>
|
|
</div>
|
|
</FeatureShowcase>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Testimonials */}
|
|
<AnimateOnScroll>
|
|
<div className="container mx-auto">
|
|
<div className="flex flex-col items-center gap-12">
|
|
<div className="text-center">
|
|
<h2 className="text-3xl font-medium tracking-tight xl:text-5xl dark:text-white">
|
|
<Trans i18nKey={'marketing.testimonialsHeading'} />
|
|
</h2>
|
|
<p className="text-secondary-foreground/70 mx-auto mt-4 max-w-2xl text-xl font-medium tracking-tight">
|
|
<Trans i18nKey={'marketing.testimonialsSubheading'} />
|
|
</p>
|
|
</div>
|
|
<div className="reveal-stagger grid w-full grid-cols-1 gap-6 md:grid-cols-3">
|
|
<TestimonialCard
|
|
quoteKey="marketing.testimonial1Quote"
|
|
nameKey="marketing.testimonial1Name"
|
|
roleKey="marketing.testimonial1Role"
|
|
/>
|
|
<TestimonialCard
|
|
quoteKey="marketing.testimonial2Quote"
|
|
nameKey="marketing.testimonial2Name"
|
|
roleKey="marketing.testimonial2Role"
|
|
/>
|
|
<TestimonialCard
|
|
quoteKey="marketing.testimonial3Quote"
|
|
nameKey="marketing.testimonial3Name"
|
|
roleKey="marketing.testimonial3Role"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Additional Features Row */}
|
|
<AnimateOnScroll>
|
|
<div className={'container mx-auto'}>
|
|
<div className={'py-4 xl:py-8'}>
|
|
<FeatureShowcase
|
|
heading={
|
|
<>
|
|
<b className="font-medium tracking-tight dark:text-white">
|
|
<Trans i18nKey={'marketing.additionalFeaturesHeading'} />
|
|
</b>
|
|
.{' '}
|
|
<span className="text-secondary-foreground/70 block font-normal tracking-tight">
|
|
<Trans i18nKey={'marketing.additionalFeaturesSubheading'} />
|
|
</span>
|
|
</>
|
|
}
|
|
icon={
|
|
<FeatureShowcaseIconContainer>
|
|
<ZapIcon className="h-4 w-4" />
|
|
<span>
|
|
<Trans i18nKey={'marketing.additionalFeaturesLabel'} />
|
|
</span>
|
|
</FeatureShowcaseIconContainer>
|
|
}
|
|
>
|
|
<div className="reveal-stagger mt-2 grid w-full grid-cols-1 gap-4 md:mt-6 md:grid-cols-2 lg:grid-cols-3">
|
|
<IconFeatureCard
|
|
icon={FileTextIcon}
|
|
titleKey="marketing.featureDocumentsTitle"
|
|
descKey="marketing.featureDocumentsDesc"
|
|
accentBg="bg-chart-1/10"
|
|
accentText="text-chart-1"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={GlobeIcon}
|
|
titleKey="marketing.featureSiteBuilderTitle"
|
|
descKey="marketing.featureSiteBuilderDesc"
|
|
accentBg="bg-chart-2/10"
|
|
accentText="text-chart-2"
|
|
/>
|
|
<IconFeatureCard
|
|
icon={LayoutDashboardIcon}
|
|
titleKey="marketing.featureModulesTitle"
|
|
descKey="marketing.featureModulesDesc"
|
|
accentBg="bg-chart-3/10"
|
|
accentText="text-chart-3"
|
|
/>
|
|
</div>
|
|
</FeatureShowcase>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Why Choose Us Section */}
|
|
<AnimateOnScroll>
|
|
<div className={'container mx-auto'}>
|
|
<EcosystemShowcase
|
|
heading={<Trans i18nKey={'marketing.whyChooseHeading'} />}
|
|
description={<Trans i18nKey={'marketing.whyChooseDescription'} />}
|
|
textPosition="right"
|
|
className="border-primary/10 rounded-xl border"
|
|
>
|
|
<div className="flex flex-col gap-6">
|
|
<WhyItem
|
|
icon={SmartphoneIcon}
|
|
titleKey="marketing.whyResponsiveTitle"
|
|
descKey="marketing.whyResponsiveDesc"
|
|
/>
|
|
<WhyItem
|
|
icon={LockIcon}
|
|
titleKey="marketing.whySecureTitle"
|
|
descKey="marketing.whySecureDesc"
|
|
/>
|
|
<WhyItem
|
|
icon={HeadsetIcon}
|
|
titleKey="marketing.whySupportTitle"
|
|
descKey="marketing.whySupportDesc"
|
|
/>
|
|
<WhyItem
|
|
icon={ShieldCheckIcon}
|
|
titleKey="marketing.whyGdprTitle"
|
|
descKey="marketing.whyGdprDesc"
|
|
/>
|
|
</div>
|
|
</EcosystemShowcase>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* How It Works */}
|
|
<AnimateOnScroll>
|
|
<div className="container mx-auto">
|
|
<div className="flex flex-col items-center gap-12">
|
|
<div className="text-center">
|
|
<h2 className="text-3xl font-medium tracking-tight xl:text-5xl dark:text-white">
|
|
<Trans i18nKey={'marketing.howItWorksHeading'} />
|
|
</h2>
|
|
<p className="text-secondary-foreground/70 mx-auto mt-4 max-w-2xl text-xl font-medium tracking-tight">
|
|
<Trans i18nKey={'marketing.howItWorksSubheading'} />
|
|
</p>
|
|
</div>
|
|
|
|
<div className="relative grid w-full grid-cols-1 gap-8 md:grid-cols-3">
|
|
<div
|
|
className="border-primary/30 absolute top-10 right-[16.67%] left-[16.67%] hidden h-px border-t border-dashed md:block"
|
|
aria-hidden="true"
|
|
/>
|
|
<StepCard
|
|
step="01"
|
|
titleKey="marketing.howStep1Title"
|
|
descKey="marketing.howStep1Desc"
|
|
/>
|
|
<StepCard
|
|
step="02"
|
|
titleKey="marketing.howStep2Title"
|
|
descKey="marketing.howStep2Desc"
|
|
/>
|
|
<StepCard
|
|
step="03"
|
|
titleKey="marketing.howStep3Title"
|
|
descKey="marketing.howStep3Desc"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Pricing Section */}
|
|
<AnimateOnScroll>
|
|
<div className={'container mx-auto'}>
|
|
<div
|
|
className={
|
|
'flex flex-col items-center justify-center space-y-12 py-4 xl:py-8'
|
|
}
|
|
>
|
|
<SecondaryHero
|
|
pill={
|
|
<Pill label={<Trans i18nKey={'marketing.pricingPillLabel'} />}>
|
|
<Trans i18nKey={'marketing.pricingPillText'} />
|
|
</Pill>
|
|
}
|
|
heading={
|
|
<GradientText className="from-primary to-primary/60">
|
|
<Trans i18nKey={'marketing.pricingHeading'} />
|
|
</GradientText>
|
|
}
|
|
subheading={<Trans i18nKey={'marketing.pricingSubheading'} />}
|
|
/>
|
|
|
|
<div className={'w-full'}>
|
|
<PricingTable
|
|
config={billingConfig}
|
|
paths={{
|
|
signUp: pathsConfig.auth.signUp,
|
|
return: pathsConfig.app.home,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
|
|
{/* Final CTA */}
|
|
<AnimateOnScroll>
|
|
<div className="container mx-auto">
|
|
<div className="ring-primary/10 from-primary/10 via-background to-primary/5 flex flex-col items-center gap-8 rounded-2xl border bg-gradient-to-br p-12 text-center ring-1 lg:p-16">
|
|
<h2 className="max-w-3xl text-3xl font-medium tracking-tight xl:text-5xl dark:text-white">
|
|
<GradientText className="from-primary to-primary/60">
|
|
<Trans i18nKey={'marketing.ctaHeading'} />
|
|
</GradientText>
|
|
</h2>
|
|
<p className="text-secondary-foreground/70 max-w-2xl text-lg">
|
|
<Trans i18nKey={'marketing.ctaDescription'} />
|
|
</p>
|
|
<div className="flex flex-col gap-3 sm:flex-row">
|
|
<CtaButton className="h-14 px-10 text-lg">
|
|
<Link href={'/auth/sign-up'}>
|
|
<span className="flex items-center gap-2">
|
|
<Trans i18nKey={'marketing.ctaButtonPrimary'} />
|
|
<ArrowRightIcon className="h-5 w-5" />
|
|
</span>
|
|
</Link>
|
|
</CtaButton>
|
|
<CtaButton variant={'outline'} className="h-14 px-10 text-lg">
|
|
<Link href={'/contact'}>
|
|
<Trans i18nKey={'marketing.ctaButtonSecondary'} />
|
|
</Link>
|
|
</CtaButton>
|
|
</div>
|
|
<p className="text-muted-foreground flex items-center gap-2 text-sm">
|
|
<CheckIcon className="h-4 w-4" />
|
|
<Trans i18nKey={'marketing.ctaNote'} />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</AnimateOnScroll>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Home;
|
|
|
|
function MainCallToActionButton() {
|
|
return (
|
|
<div className={'flex space-x-2.5'}>
|
|
<CtaButton className="h-12 px-8 text-base shadow-lg">
|
|
<Link href={'/auth/sign-up'}>
|
|
<span className={'flex items-center space-x-0.5'}>
|
|
<span>
|
|
<Trans i18nKey={'common.getStarted'} />
|
|
</span>
|
|
|
|
<ArrowRightIcon
|
|
className={
|
|
'animate-in fade-in slide-in-from-left-8 h-4' +
|
|
' zoom-in fill-mode-both delay-1000 duration-1000'
|
|
}
|
|
/>
|
|
</span>
|
|
</Link>
|
|
</CtaButton>
|
|
|
|
<CtaButton variant={'link'} className="h-12 px-8 text-base">
|
|
<Link href={'/pricing'}>
|
|
<Trans i18nKey={'common.pricing'} />
|
|
</Link>
|
|
</CtaButton>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function IconFeatureCard(props: {
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
titleKey: string;
|
|
descKey: string;
|
|
accentBg?: string;
|
|
accentText?: string;
|
|
}) {
|
|
return (
|
|
<div className="reveal bg-muted/50 hover:border-primary/20 flex flex-col gap-3 rounded-xl border border-transparent p-6 transition-all duration-300 hover:-translate-y-1 hover:shadow-md">
|
|
<div
|
|
className={cn(
|
|
'flex h-10 w-10 items-center justify-center rounded-lg',
|
|
props.accentBg ?? 'bg-primary/10',
|
|
)}
|
|
>
|
|
<props.icon
|
|
className={cn('h-5 w-5', props.accentText ?? 'text-primary')}
|
|
/>
|
|
</div>
|
|
<h4 className="text-lg font-medium">
|
|
<Trans i18nKey={props.titleKey} />
|
|
</h4>
|
|
<p className="text-muted-foreground max-w-xs text-sm">
|
|
<Trans i18nKey={props.descKey} />
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StatItem(props: { value: string; labelKey: string }) {
|
|
return (
|
|
<div className="reveal flex flex-col items-center gap-1 px-6 py-4">
|
|
<span className="text-primary text-3xl font-bold tracking-tight lg:text-4xl">
|
|
{props.value}
|
|
</span>
|
|
<span className="text-muted-foreground text-sm font-medium">
|
|
<Trans i18nKey={props.labelKey} />
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TestimonialCard(props: {
|
|
quoteKey: string;
|
|
nameKey: string;
|
|
roleKey: string;
|
|
}) {
|
|
return (
|
|
<div className="reveal border-border bg-card flex flex-col gap-4 rounded-xl border p-6 shadow-sm">
|
|
<p className="text-secondary-foreground text-sm leading-relaxed italic">
|
|
“
|
|
<Trans i18nKey={props.quoteKey} />
|
|
”
|
|
</p>
|
|
<div className="border-border border-t pt-4">
|
|
<p className="text-sm font-medium">
|
|
<Trans i18nKey={props.nameKey} />
|
|
</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
<Trans i18nKey={props.roleKey} />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WhyItem(props: {
|
|
icon: React.ComponentType<{ className?: string }>;
|
|
titleKey: string;
|
|
descKey: string;
|
|
}) {
|
|
return (
|
|
<div className="flex gap-4">
|
|
<div className="ring-primary/20 bg-primary/10 flex h-12 w-12 shrink-0 items-center justify-center rounded-xl ring-1">
|
|
<props.icon className="text-primary h-5 w-5" />
|
|
</div>
|
|
<div>
|
|
<h4 className="text-secondary-foreground font-medium">
|
|
<Trans i18nKey={props.titleKey} />
|
|
</h4>
|
|
<p className="text-muted-foreground mt-1 text-sm">
|
|
<Trans i18nKey={props.descKey} />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function StepCard(props: { step: string; titleKey: string; descKey: string }) {
|
|
return (
|
|
<div className="reveal border-border bg-card relative flex flex-col items-center gap-4 rounded-xl border p-8 text-center shadow-sm transition-all duration-300 hover:-translate-y-1 hover:shadow-md">
|
|
<div className="bg-primary text-primary-foreground shadow-primary/20 relative z-10 flex h-14 w-14 items-center justify-center rounded-full text-xl font-bold shadow-lg">
|
|
{props.step}
|
|
</div>
|
|
<h3 className="text-secondary-foreground text-xl font-medium">
|
|
<Trans i18nKey={props.titleKey} />
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm">
|
|
<Trans i18nKey={props.descKey} />
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|