feat: MyEasyCMS v2 — Full SaaS rebuild
Some checks failed
Workflow / ⚫️ Test (push) Has been cancelled
Workflow / ʦ TypeScript (push) Has been cancelled

Complete rebuild of 22-year-old PHP CMS as modern SaaS:

Database (15 migrations, 42+ tables):
- Foundation: account_settings, audit_log, GDPR register, cms_files
- Module Engine: modules, fields, records, permissions, relations + RPC
- Members: 45+ field member profiles, departments, roles, honors, SEPA mandates
- Courses: courses, sessions, categories, instructors, locations, attendance
- Bookings: rooms, guests, bookings with availability
- Events: events, registrations, holiday passes
- Finance: SEPA batches/items (pain.008/001 XML), invoices
- Newsletter: campaigns, templates, recipients, subscriptions
- Site Builder: site_pages (Puck JSON), site_settings, cms_posts
- Portal Auth: member_portal_invitations, user linking

Feature Packages (9):
- @kit/module-builder — dynamic low-code CRUD engine
- @kit/member-management — 31 API methods, 21 actions, 8 components
- @kit/course-management, @kit/booking-management, @kit/event-management
- @kit/finance — SEPA XML generator + IBAN validator
- @kit/newsletter — campaigns + dispatch
- @kit/document-generator — PDF/Excel/Word
- @kit/site-builder — Puck visual editor, 15 blocks, public rendering

Pages (60+):
- Dashboard with real stats from all APIs
- Full CRUD for all 8 domains with react-hook-form + Zod
- Recharts statistics
- German i18n throughout
- Member portal with auth + invitation system
- Public club websites via Puck at /club/[slug]

Infrastructure:
- Dockerfile (multi-stage, standalone output)
- docker-compose.yml (Supabase self-hosted + Next.js)
- Kong API gateway config
- .env.production.example
This commit is contained in:
Zaid Marzguioui
2026-03-29 23:17:38 +02:00
parent 61ff48cb73
commit 1294caa7fa
120 changed files with 11013 additions and 1858 deletions

View File

@@ -1,19 +1,34 @@
import Image from 'next/image';
import Link from 'next/link';
import { ArrowRightIcon, LayoutDashboard } from 'lucide-react';
import {
ArrowRightIcon,
BookOpenIcon,
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,
FeatureCard,
FeatureGrid,
FeatureShowcase,
FeatureShowcaseIconContainer,
Hero,
Pill,
PillActionButton,
SecondaryHero,
} from '@kit/ui/marketing';
import { Trans } from '@kit/ui/trans';
@@ -23,31 +38,25 @@ import pathsConfig from '~/config/paths.config';
function Home() {
return (
<div className={'mt-4 flex flex-col space-y-24 py-14'}>
<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={'New'}>
<span>The SaaS Starter Kit for ambitious developers</span>
<PillActionButton
render={
<Link href={'/auth/sign-up'}>
<ArrowRightIcon className={'h-4 w-4'} />
</Link>
}
/>
<Pill label={'Neu'}>
<span>
<Trans i18nKey={'marketing.heroPill'} />
</span>
</Pill>
}
title={
<span className="text-secondary-foreground">
<span>Ship a SaaS faster than ever.</span>
<Trans i18nKey={'marketing.heroTitle'} />
</span>
}
subtitle={
<span>
Makerkit gives you a production-ready boilerplate to build your
SaaS faster than ever before with the next-gen SaaS Starter Kit.
Get started in minutes.
<Trans i18nKey={'marketing.heroSubtitle'} />
</span>
}
cta={<MainCallToActionButton />}
@@ -55,95 +64,227 @@ function Home() {
<Image
priority
className={
'dark:border-primary/10 w-full rounded-lg border border-gray-200'
'dark:border-primary/10 w-full rounded-2xl border border-gray-200 shadow-2xl'
}
width={3558}
height={2222}
src={`/images/dashboard.webp`}
alt={`App Image`}
alt={`MyEasyCMS Dashboard`}
/>
}
/>
</div>
{/* Trust Indicators */}
<div className={'container mx-auto'}>
<div className="flex flex-col items-center gap-8">
<p className="text-muted-foreground text-sm font-medium uppercase tracking-widest">
<Trans i18nKey={'marketing.trustedBy'} />
</p>
<div className="flex flex-wrap items-center justify-center gap-x-12 gap-y-6">
<TrustItem icon={UsersIcon} label="marketing.trustAssociations" />
<TrustItem
icon={GraduationCapIcon}
label="marketing.trustSchools"
/>
<TrustItem icon={BookOpenIcon} label="marketing.trustClubs" />
<TrustItem
icon={GlobeIcon}
label="marketing.trustOrganizations"
/>
</div>
</div>
</div>
{/* Core Modules Feature Grid */}
<div className={'container mx-auto'}>
<div className={'py-4 xl:py-8'}>
<FeatureShowcase
heading={
<>
<b className="font-medium tracking-tight dark:text-white">
The ultimate SaaS Starter Kit
<Trans i18nKey={'marketing.featuresHeading'} />
</b>
.{' '}
<span className="text-secondary-foreground/70 block font-normal tracking-tight">
Unleash your creativity and build your SaaS faster than ever
with Makerkit.
<Trans i18nKey={'marketing.featuresSubheading'} />
</span>
</>
}
icon={
<FeatureShowcaseIconContainer>
<LayoutDashboard className="h-4 w-4" />
<span>All-in-one solution</span>
<LayoutDashboardIcon className="h-4 w-4" />
<span>
<Trans i18nKey={'marketing.featuresLabel'} />
</span>
</FeatureShowcaseIconContainer>
}
>
<FeatureGrid>
<FeatureCard
className={'relative col-span-1 overflow-hidden'}
label={'Beautiful Dashboard'}
description={`Makerkit provides a beautiful dashboard to manage your SaaS business.`}
></FeatureCard>
<FeatureCard
className={'relative col-span-1 w-full overflow-hidden'}
label={'Authentication'}
description={`Makerkit provides a variety of providers to allow your users to sign in.`}
></FeatureCard>
<FeatureCard
className={'relative col-span-1 overflow-hidden'}
label={'Multi Tenancy'}
description={`Multi tenant memberships for your SaaS business.`}
<div className="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"
/>
<FeatureCard
className={'relative col-span-1 overflow-hidden'}
label={'Billing'}
description={`Makerkit supports multiple payment gateways to charge your customers.`}
<IconFeatureCard
icon={GraduationCapIcon}
titleKey="marketing.featureCoursesTitle"
descKey="marketing.featureCoursesDesc"
/>
<FeatureCard
className={'relative col-span-1 overflow-hidden'}
label={'Plugins'}
description={`Extend your SaaS with plugins that you can install using the CLI.`}
<IconFeatureCard
icon={BedDoubleIcon}
titleKey="marketing.featureBookingsTitle"
descKey="marketing.featureBookingsDesc"
/>
<FeatureCard
className={'relative col-span-1 overflow-hidden'}
label={'Documentation'}
description={`Makerkit provides a comprehensive documentation to help you get started.`}
<IconFeatureCard
icon={CalendarIcon}
titleKey="marketing.featureEventsTitle"
descKey="marketing.featureEventsDesc"
/>
</FeatureGrid>
<IconFeatureCard
icon={WalletIcon}
titleKey="marketing.featureFinanceTitle"
descKey="marketing.featureFinanceDesc"
/>
<IconFeatureCard
icon={MailIcon}
titleKey="marketing.featureNewsletterTitle"
descKey="marketing.featureNewsletterDesc"
/>
</div>
</FeatureShowcase>
</div>
</div>
{/* Dashboard Showcase */}
<div className={'container mx-auto'}>
<EcosystemShowcase
heading="The ultimate SaaS Starter Kit for founders."
description="Unleash your creativity and build your SaaS faster than ever with Makerkit. Get started in minutes and ship your SaaS in no time."
heading={<Trans i18nKey={'marketing.showcaseHeading'} />}
description={<Trans i18nKey={'marketing.showcaseDescription'} />}
>
<Image
className="rounded-md"
src={'/images/sign-in.webp'}
alt="Sign in"
width={1000}
height={1000}
className="rounded-lg shadow-lg"
src={'/images/dashboard.webp'}
alt="MyEasyCMS Dashboard"
width={1200}
height={800}
/>
</EcosystemShowcase>
</div>
{/* Additional Features Row */}
<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="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"
/>
<IconFeatureCard
icon={GlobeIcon}
titleKey="marketing.featureSiteBuilderTitle"
descKey="marketing.featureSiteBuilderDesc"
/>
<IconFeatureCard
icon={LayoutDashboardIcon}
titleKey="marketing.featureModulesTitle"
descKey="marketing.featureModulesDesc"
/>
</div>
</FeatureShowcase>
</div>
</div>
{/* Why Choose Us Section */}
<div className={'container mx-auto'}>
<EcosystemShowcase
heading={<Trans i18nKey={'marketing.whyChooseHeading'} />}
description={<Trans i18nKey={'marketing.whyChooseDescription'} />}
textPosition="right"
>
<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>
{/* How It Works */}
<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 dark:text-white xl:text-5xl">
<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="grid w-full grid-cols-1 gap-8 md:grid-cols-3">
<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>
{/* Pricing Section */}
<div className={'container mx-auto'}>
<div
className={
@@ -151,9 +292,13 @@ function Home() {
}
>
<SecondaryHero
pill={<Pill label="Start for free">No credit card required.</Pill>}
heading="Fair pricing for all types of businesses"
subheading="Get started on our free plan and upgrade when you are ready."
pill={
<Pill label={<Trans i18nKey={'marketing.pricingPillLabel'} />}>
<Trans i18nKey={'marketing.pricingPillText'} />
</Pill>
}
heading={<Trans i18nKey={'marketing.pricingHeading'} />}
subheading={<Trans i18nKey={'marketing.pricingSubheading'} />}
/>
<div className={'w-full'}>
@@ -167,6 +312,37 @@ function Home() {
</div>
</div>
</div>
{/* Final CTA */}
<div className="container mx-auto">
<div className="bg-primary/5 flex flex-col items-center gap-8 rounded-2xl border p-12 text-center lg:p-16">
<h2 className="max-w-3xl text-3xl font-medium tracking-tight dark:text-white xl:text-5xl">
<Trans i18nKey={'marketing.ctaHeading'} />
</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-12 px-8 text-base">
<Link href={'/auth/sign-up'}>
<span className="flex items-center gap-2">
<Trans i18nKey={'marketing.ctaButtonPrimary'} />
<ArrowRightIcon className="h-4 w-4" />
</span>
</Link>
</CtaButton>
<CtaButton variant={'outline'} className="h-12 px-8 text-base">
<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>
</div>
);
}
@@ -201,3 +377,73 @@ function MainCallToActionButton() {
</div>
);
}
function IconFeatureCard(props: {
icon: React.ComponentType<{ className?: string }>;
titleKey: string;
descKey: string;
}) {
return (
<div className="bg-muted/50 flex flex-col gap-3 rounded p-6">
<div className="bg-primary/10 flex h-10 w-10 items-center justify-center rounded-lg">
<props.icon className="text-primary h-5 w-5" />
</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 TrustItem(props: {
icon: React.ComponentType<{ className?: string }>;
label: string;
}) {
return (
<div className="text-muted-foreground flex items-center gap-2.5">
<props.icon className="h-5 w-5" />
<span className="text-sm font-medium">
<Trans i18nKey={props.label} />
</span>
</div>
);
}
function WhyItem(props: {
icon: React.ComponentType<{ className?: string }>;
titleKey: string;
descKey: string;
}) {
return (
<div className="flex gap-4">
<div className="bg-primary/10 flex h-10 w-10 shrink-0 items-center justify-center rounded-lg">
<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="bg-muted/50 relative flex flex-col gap-4 rounded-lg p-6">
<span className="text-primary/20 text-6xl font-bold">{props.step}</span>
<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>
);
}