Compare commits
4 Commits
f43770999f
...
124c6a632a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
124c6a632a | ||
|
|
d4acc3ba22 | ||
|
|
28188bb3a6 | ||
|
|
f10a34c505 |
@@ -3,8 +3,9 @@ node_modules
|
||||
.turbo
|
||||
**/.turbo
|
||||
.git
|
||||
*.md
|
||||
.env*
|
||||
!.env.example
|
||||
!.env.local.example
|
||||
.DS_Store
|
||||
apps/e2e
|
||||
apps/dev-tool
|
||||
@@ -16,3 +17,6 @@ apps/dev-tool
|
||||
.github
|
||||
docs
|
||||
**/*.tsbuildinfo
|
||||
**/*.md
|
||||
!**/AGENTS.md
|
||||
!**/CLAUDE.md
|
||||
|
||||
19
.env.local.example
Normal file
19
.env.local.example
Normal file
@@ -0,0 +1,19 @@
|
||||
# =====================================================
|
||||
# MyEasyCMS v2 — Local Development Environment
|
||||
# Copy to .env and run: docker compose -f docker-compose.local.yml up -d
|
||||
# =====================================================
|
||||
|
||||
# --- Database ---
|
||||
POSTGRES_PASSWORD=postgres
|
||||
|
||||
# --- Supabase Auth ---
|
||||
JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
|
||||
|
||||
# --- Supabase Keys (demo keys — safe for local dev only) ---
|
||||
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
|
||||
|
||||
# --- Stripe (test keys) ---
|
||||
# Get your own test keys from https://dashboard.stripe.com/test/apikeys
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_YOUR_KEY
|
||||
STRIPE_SECRET_KEY=sk_test_YOUR_KEY
|
||||
24
Dockerfile
24
Dockerfile
@@ -1,18 +1,15 @@
|
||||
FROM node:22-alpine AS base
|
||||
# node:22-slim (Debian/glibc) is ~2x faster for Next.js builds vs Alpine/musl
|
||||
FROM node:22-slim AS base
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
WORKDIR /app
|
||||
|
||||
# --- Install + Build in one stage ---
|
||||
# --- Install + Build ---
|
||||
FROM base AS builder
|
||||
# CACHE_BUST: change this value to force a full rebuild (busts Docker layer cache)
|
||||
ARG CACHE_BUST=14
|
||||
RUN echo "Cache bust: ${CACHE_BUST}"
|
||||
COPY . .
|
||||
RUN pnpm install --no-frozen-lockfile
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --no-frozen-lockfile --prefer-offline
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# NEXT_PUBLIC_* vars are baked into the Next.js build at compile time.
|
||||
# Pass them as build args so the same Dockerfile works for any environment.
|
||||
ARG NEXT_PUBLIC_CI=false
|
||||
ARG NEXT_PUBLIC_SITE_URL=https://myeasycms.de
|
||||
ARG NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
|
||||
@@ -22,6 +19,7 @@ ARG NEXT_PUBLIC_ENABLE_FISCHEREI=true
|
||||
ARG NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS=true
|
||||
ARG NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG=true
|
||||
ARG NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
||||
ARG NEXT_PUBLIC_BILLING_PROVIDER=stripe
|
||||
ENV NEXT_PUBLIC_CI=${NEXT_PUBLIC_CI}
|
||||
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
|
||||
ENV NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
|
||||
@@ -31,19 +29,19 @@ ENV NEXT_PUBLIC_ENABLE_FISCHEREI=${NEXT_PUBLIC_ENABLE_FISCHEREI}
|
||||
ENV NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS=${NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS}
|
||||
ENV NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG=${NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG}
|
||||
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
|
||||
ENV NEXT_PUBLIC_BILLING_PROVIDER=${NEXT_PUBLIC_BILLING_PROVIDER}
|
||||
RUN pnpm --filter web build
|
||||
|
||||
# --- Run ---
|
||||
FROM base AS runner
|
||||
# --- Run (slim for smaller image than full Debian) ---
|
||||
FROM node:22-slim AS runner
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
COPY --from=builder /app/ ./
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
||||
|
||||
# Ensure Next.js cache directories are writable by the nextjs user
|
||||
RUN groupadd --system --gid 1001 nodejs && useradd --system --uid 1001 nextjs
|
||||
RUN mkdir -p /app/apps/web/.next/cache && chown -R nextjs:nodejs /app/apps/web/.next/cache
|
||||
|
||||
USER nextjs
|
||||
|
||||
@@ -126,7 +126,7 @@ export default async function BookingsPage({
|
||||
icon={<CalendarCheck className="h-5 w-5" />}
|
||||
/>
|
||||
<StatsCard
|
||||
title={t('common.of')}
|
||||
title={t('list.total')}
|
||||
value={total}
|
||||
icon={<Euro className="h-5 w-5" />}
|
||||
/>
|
||||
|
||||
@@ -138,7 +138,7 @@ export default async function EventsPage({ params, searchParams }: PageProps) {
|
||||
{t('capacity')}
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
{t('status')}
|
||||
{t('statusLabel')}
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
{t('registrations')}
|
||||
|
||||
@@ -116,7 +116,7 @@ export default async function EventRegistrationsPage({ params }: PageProps) {
|
||||
{t('eventDate')}
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-left font-medium">
|
||||
{t('status')}
|
||||
{t('statusLabel')}
|
||||
</th>
|
||||
<th scope="col" className="p-3 text-right font-medium">
|
||||
{t('capacity')}
|
||||
|
||||
@@ -144,13 +144,13 @@ export default async function FinancePage({ params, searchParams }: PageProps) {
|
||||
|
||||
{/* Toolbar */}
|
||||
<ListToolbar
|
||||
searchPlaceholder={t('common.showAll')}
|
||||
searchPlaceholder={t('common.searchPlaceholder')}
|
||||
filters={[
|
||||
{
|
||||
param: 'status',
|
||||
label: t('common.status'),
|
||||
options: [
|
||||
{ value: '', label: t('common.noData') },
|
||||
{ value: '', label: t('common.all') },
|
||||
{ value: 'draft', label: t('status.draft') },
|
||||
{ value: 'ready', label: t('sepa.newBatch') },
|
||||
{ value: 'sent', label: t('status.sent') },
|
||||
|
||||
@@ -9,6 +9,8 @@ interface CmsPageShellProps {
|
||||
account: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
/** Override breadcrumb labels for URL path segments (e.g. UUID → name) */
|
||||
breadcrumbValues?: Record<string, string>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
@@ -20,6 +22,7 @@ export function CmsPageShell({
|
||||
account,
|
||||
title,
|
||||
description,
|
||||
breadcrumbValues,
|
||||
children,
|
||||
}: CmsPageShellProps) {
|
||||
return (
|
||||
@@ -28,7 +31,11 @@ export function CmsPageShell({
|
||||
account={account}
|
||||
title={title}
|
||||
description={
|
||||
description !== undefined ? description : <AppBreadcrumbs />
|
||||
description !== undefined ? (
|
||||
description
|
||||
) : (
|
||||
<AppBreadcrumbs values={breadcrumbValues} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export default createBillingSchema({
|
||||
interval: 'month',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_starter_monthly',
|
||||
id: 'price_1THsqKKttnWb7SsFttMu9VzG',
|
||||
name: 'Starter',
|
||||
cost: 29,
|
||||
type: 'flat' as const,
|
||||
@@ -47,7 +47,7 @@ export default createBillingSchema({
|
||||
interval: 'year',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_starter_yearly',
|
||||
id: 'price_1THsqLKttnWb7SsFgvjsKXzs',
|
||||
name: 'Starter',
|
||||
cost: 290,
|
||||
type: 'flat' as const,
|
||||
@@ -82,7 +82,7 @@ export default createBillingSchema({
|
||||
interval: 'month',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_pro_monthly',
|
||||
id: 'price_1THsqLKttnWb7SsFlWPf5IdP',
|
||||
name: 'Pro',
|
||||
cost: 59,
|
||||
type: 'flat' as const,
|
||||
@@ -96,7 +96,7 @@ export default createBillingSchema({
|
||||
interval: 'year',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_pro_yearly',
|
||||
id: 'price_1THsqMKttnWb7SsFZq3A4QkU',
|
||||
name: 'Pro',
|
||||
cost: 590,
|
||||
type: 'flat' as const,
|
||||
@@ -130,7 +130,7 @@ export default createBillingSchema({
|
||||
interval: 'month',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_verband_monthly',
|
||||
id: 'price_1THsqNKttnWb7SsFGv7YskgJ',
|
||||
name: 'Verband',
|
||||
cost: 199,
|
||||
type: 'flat' as const,
|
||||
@@ -144,7 +144,7 @@ export default createBillingSchema({
|
||||
interval: 'year',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_verband_yearly',
|
||||
id: 'price_1THsqNKttnWb7SsFhNl2bVn8',
|
||||
name: 'Verband',
|
||||
cost: 1990,
|
||||
type: 'flat' as const,
|
||||
@@ -178,7 +178,7 @@ export default createBillingSchema({
|
||||
interval: 'month',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_enterprise_monthly',
|
||||
id: 'price_1THsqOKttnWb7SsFlLjfLw72',
|
||||
name: 'Enterprise',
|
||||
cost: 349,
|
||||
type: 'flat' as const,
|
||||
@@ -192,7 +192,7 @@ export default createBillingSchema({
|
||||
interval: 'year',
|
||||
lineItems: [
|
||||
{
|
||||
id: 'price_enterprise_yearly',
|
||||
id: 'price_1THsqOKttnWb7SsF8Sr12isW',
|
||||
name: 'Enterprise',
|
||||
cost: 3490,
|
||||
type: 'flat' as const,
|
||||
|
||||
@@ -304,34 +304,54 @@
|
||||
"paginationNext": "Weiter →"
|
||||
},
|
||||
"permissions": {
|
||||
"modules.read": "Module lesen",
|
||||
"modules.write": "Module bearbeiten",
|
||||
"modules.delete": "Module löschen",
|
||||
"modules.insert": "Datensätze erstellen",
|
||||
"modules.lock": "Datensätze sperren",
|
||||
"modules.import": "Daten importieren",
|
||||
"modules.export": "Daten exportieren",
|
||||
"modules.print": "Drucken",
|
||||
"modules.manage": "Module verwalten",
|
||||
"members.read": "Mitglieder lesen",
|
||||
"members.write": "Mitglieder bearbeiten",
|
||||
"courses.read": "Kurse lesen",
|
||||
"courses.write": "Kurse bearbeiten",
|
||||
"bookings.read": "Buchungen lesen",
|
||||
"bookings.write": "Buchungen bearbeiten",
|
||||
"finance.read": "Finanzen lesen",
|
||||
"finance.write": "Finanzen bearbeiten",
|
||||
"finance.sepa": "SEPA-Einzüge ausführen",
|
||||
"documents.generate": "Dokumente generieren",
|
||||
"newsletter.send": "Newsletter versenden",
|
||||
"fischerei.read": "Fischerei lesen",
|
||||
"fischerei.write": "Fischerei bearbeiten",
|
||||
"meetings.read": "Sitzungsprotokolle lesen",
|
||||
"meetings.write": "Sitzungsprotokolle bearbeiten",
|
||||
"meetings.delete": "Sitzungsprotokolle löschen",
|
||||
"verband.read": "Verbandsverwaltung lesen",
|
||||
"verband.write": "Verbandsverwaltung bearbeiten",
|
||||
"verband.delete": "Verbandsverwaltung löschen"
|
||||
"modules": {
|
||||
"read": "Module lesen",
|
||||
"write": "Module bearbeiten",
|
||||
"delete": "Module löschen",
|
||||
"insert": "Datensätze erstellen",
|
||||
"lock": "Datensätze sperren",
|
||||
"import": "Daten importieren",
|
||||
"export": "Daten exportieren",
|
||||
"print": "Drucken",
|
||||
"manage": "Module verwalten"
|
||||
},
|
||||
"members": {
|
||||
"read": "Mitglieder lesen",
|
||||
"write": "Mitglieder bearbeiten"
|
||||
},
|
||||
"courses": {
|
||||
"read": "Kurse lesen",
|
||||
"write": "Kurse bearbeiten"
|
||||
},
|
||||
"bookings": {
|
||||
"read": "Buchungen lesen",
|
||||
"write": "Buchungen bearbeiten"
|
||||
},
|
||||
"finance": {
|
||||
"read": "Finanzen lesen",
|
||||
"write": "Finanzen bearbeiten",
|
||||
"sepa": "SEPA-Einzüge ausführen"
|
||||
},
|
||||
"documents": {
|
||||
"generate": "Dokumente generieren"
|
||||
},
|
||||
"newsletter": {
|
||||
"send": "Newsletter versenden"
|
||||
},
|
||||
"fischerei": {
|
||||
"read": "Fischerei lesen",
|
||||
"write": "Fischerei bearbeiten"
|
||||
},
|
||||
"meetings": {
|
||||
"read": "Sitzungsprotokolle lesen",
|
||||
"write": "Sitzungsprotokolle bearbeiten",
|
||||
"delete": "Sitzungsprotokolle löschen"
|
||||
},
|
||||
"verband": {
|
||||
"read": "Verbandsverwaltung lesen",
|
||||
"write": "Verbandsverwaltung bearbeiten",
|
||||
"delete": "Verbandsverwaltung löschen"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
"next": "Weiter",
|
||||
"type": "Typ",
|
||||
"date": "Datum",
|
||||
"description": "Beschreibung"
|
||||
"description": "Beschreibung",
|
||||
"searchPlaceholder": "Rechnung suchen...",
|
||||
"all": "Alle"
|
||||
},
|
||||
"status": {
|
||||
"draft": "Entwurf",
|
||||
|
||||
@@ -295,26 +295,40 @@
|
||||
"paginationNext": "Next →"
|
||||
},
|
||||
"permissions": {
|
||||
"modules.read": "Read Modules",
|
||||
"modules.write": "Edit Modules",
|
||||
"modules.delete": "Delete Modules",
|
||||
"modules.insert": "Create Records",
|
||||
"modules.lock": "Lock Records",
|
||||
"modules.import": "Import Data",
|
||||
"modules.export": "Export Data",
|
||||
"modules.print": "Print",
|
||||
"modules.manage": "Manage Modules",
|
||||
"members.read": "Read Members",
|
||||
"members.write": "Edit Members",
|
||||
"courses.read": "Read Courses",
|
||||
"courses.write": "Edit Courses",
|
||||
"bookings.read": "Read Bookings",
|
||||
"bookings.write": "Edit Bookings",
|
||||
"finance.read": "Read Finance",
|
||||
"finance.write": "Edit Finance",
|
||||
"finance.sepa": "Execute SEPA Collections",
|
||||
"documents.generate": "Generate Documents",
|
||||
"newsletter.send": "Send Newsletter",
|
||||
"modules": {
|
||||
"read": "Read Modules",
|
||||
"write": "Edit Modules",
|
||||
"delete": "Delete Modules",
|
||||
"insert": "Create Records",
|
||||
"lock": "Lock Records",
|
||||
"import": "Import Data",
|
||||
"export": "Export Data",
|
||||
"print": "Print",
|
||||
"manage": "Manage Modules"
|
||||
},
|
||||
"members": {
|
||||
"read": "Read Members",
|
||||
"write": "Edit Members"
|
||||
},
|
||||
"courses": {
|
||||
"read": "Read Courses",
|
||||
"write": "Edit Courses"
|
||||
},
|
||||
"bookings": {
|
||||
"read": "Read Bookings",
|
||||
"write": "Edit Bookings"
|
||||
},
|
||||
"finance": {
|
||||
"read": "Read Finance",
|
||||
"write": "Edit Finance",
|
||||
"sepa": "Execute SEPA Collections"
|
||||
},
|
||||
"documents": {
|
||||
"generate": "Generate Documents"
|
||||
},
|
||||
"newsletter": {
|
||||
"send": "Send Newsletter"
|
||||
},
|
||||
"verband": {
|
||||
"delete": "Delete Association Data"
|
||||
}
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
"next": "Next",
|
||||
"type": "Type",
|
||||
"date": "Date",
|
||||
"description": "Description"
|
||||
"description": "Description",
|
||||
"searchPlaceholder": "Search invoices...",
|
||||
"all": "All"
|
||||
},
|
||||
"status": {
|
||||
"draft": "Draft",
|
||||
|
||||
@@ -41,7 +41,6 @@ const INTERNAL_PACKAGES = [
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const config = {
|
||||
output: 'standalone',
|
||||
reactStrictMode: true,
|
||||
/** Enables hot reloading for local packages without a build step */
|
||||
transpilePackages: INTERNAL_PACKAGES,
|
||||
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
image: supabase/postgres:15.8.1.060
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '54322:5432'
|
||||
- '54322:54322'
|
||||
volumes:
|
||||
- supabase-db-data:/var/lib/postgresql/data
|
||||
- ./docker/db/zzz-role-passwords.sh:/docker-entrypoint-initdb.d/zzz-role-passwords.sh:ro
|
||||
@@ -317,7 +317,6 @@ services:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
# NEXT_PUBLIC_CI=true bypasses the HTTPS check during build
|
||||
NEXT_PUBLIC_CI: 'true'
|
||||
NEXT_PUBLIC_SITE_URL: http://localhost:3000
|
||||
NEXT_PUBLIC_SUPABASE_URL: http://localhost:8000
|
||||
|
||||
@@ -330,6 +330,9 @@ services:
|
||||
# Browser-side Supabase URL — goes through external domain (Traefik → Kong)
|
||||
NEXT_PUBLIC_SUPABASE_URL: ${API_EXTERNAL_URL:-http://localhost:8000}
|
||||
NEXT_PUBLIC_SUPABASE_PUBLIC_KEY: ${SUPABASE_ANON_KEY}
|
||||
# Stripe (build-time)
|
||||
NEXT_PUBLIC_BILLING_PROVIDER: stripe
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
supabase-kong:
|
||||
@@ -352,12 +355,17 @@ services:
|
||||
NEXT_PUBLIC_ENABLE_THEME_TOGGLE: 'true'
|
||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS: 'true'
|
||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION: 'true'
|
||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING: 'false'
|
||||
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING: 'false'
|
||||
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING: 'true'
|
||||
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING: 'true'
|
||||
NEXT_PUBLIC_ENABLE_NOTIFICATIONS: 'true'
|
||||
NEXT_PUBLIC_ENABLE_FISCHEREI: 'true'
|
||||
NEXT_PUBLIC_ENABLE_MEETING_PROTOCOLS: 'true'
|
||||
NEXT_PUBLIC_ENABLE_VERBANDSVERWALTUNG: 'true'
|
||||
# Stripe (runtime)
|
||||
NEXT_PUBLIC_BILLING_PROVIDER: stripe
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: ${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}
|
||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
||||
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
|
||||
|
||||
volumes:
|
||||
supabase-db-data:
|
||||
|
||||
@@ -95,7 +95,8 @@ export function ApplicationWorkflow({
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold">Aufnahmeanträge</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{applications.length} Antrag{applications.length !== 1 ? 'e' : ''}
|
||||
{applications.length}{' '}
|
||||
{applications.length === 1 ? 'Antrag' : 'Anträge'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user