From 1294caa7fa5375e2d32beac6e0ccae50d70b8182 Mon Sep 17 00:00:00 2001 From: Zaid Marzguioui Date: Sun, 29 Mar 2026 23:17:38 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20MyEasyCMS=20v2=20=E2=80=94=20Full=20Saa?= =?UTF-8?q?S=20rebuild?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .claude/skills/gitnexus/gitnexus-cli/SKILL.md | 82 ++ .../gitnexus/gitnexus-debugging/SKILL.md | 89 ++ .../gitnexus/gitnexus-exploring/SKILL.md | 78 ++ .../skills/gitnexus/gitnexus-guide/SKILL.md | 64 ++ .../gitnexus-impact-analysis/SKILL.md | 97 +++ .../gitnexus/gitnexus-refactoring/SKILL.md | 121 +++ .dockerignore | 12 + .env.production.example | 48 ++ .gitignore | 3 +- AGENTS.md | 102 +++ CLAUDE.md | 104 ++- Dockerfile | 50 ++ apps/web/app/[locale]/(marketing)/page.tsx | 382 +++++++-- .../[locale]/club/[slug]/[...page]/page.tsx | 31 + .../club/[slug]/newsletter/subscribe/page.tsx | 41 + .../[slug]/newsletter/unsubscribe/page.tsx | 37 + apps/web/app/[locale]/club/[slug]/page.tsx | 34 + .../club/[slug]/portal/documents/page.tsx | 100 +++ .../club/[slug]/portal/invite/page.tsx | 125 +++ .../app/[locale]/club/[slug]/portal/page.tsx | 100 +++ .../club/[slug]/portal/profile/page.tsx | 131 +++ .../home/[account]/bookings/new/page.tsx | 233 +----- .../home/[account]/courses/new/page.tsx | 191 +---- .../[account]/courses/statistics/page.tsx | 103 +-- .../home/[account]/events/new/page.tsx | 295 +------ .../[account]/finance/invoices/new/page.tsx | 206 +---- .../home/[account]/finance/sepa/new/page.tsx | 100 +-- .../members-cms/[memberId]/edit/page.tsx | 25 + .../[account]/members-cms/[memberId]/page.tsx | 164 +--- .../members-cms/applications/page.tsx | 105 +-- .../home/[account]/members-cms/cards/page.tsx | 81 ++ .../members-cms/departments/page.tsx | 54 ++ .../home/[account]/members-cms/dues/page.tsx | 83 +- .../[account]/members-cms/import/page.tsx | 20 + .../home/[account]/members-cms/new/page.tsx | 191 +---- .../home/[account]/members-cms/page.tsx | 68 +- .../[account]/members-cms/statistics/page.tsx | 88 +- .../home/[account]/newsletter/new/page.tsx | 119 +-- .../site-builder/[pageId]/edit/page.tsx | 18 + .../home/[account]/site-builder/new/page.tsx | 20 + .../home/[account]/site-builder/page.tsx | 95 +++ .../[account]/site-builder/posts/new/page.tsx | 18 + .../[account]/site-builder/posts/page.tsx | 53 ++ .../[account]/site-builder/settings/page.tsx | 22 + apps/web/app/api/club/accept-invite/route.ts | 75 ++ apps/web/app/api/club/contact/route.ts | 40 + apps/web/app/api/club/newsletter/route.ts | 42 + apps/web/components/stats-charts.tsx | 71 ++ apps/web/config/feature-flags.config.ts | 5 + apps/web/config/paths.config.ts | 2 + .../config/team-account-navigation.config.tsx | 7 +- apps/web/i18n/messages/de/common.json | 3 +- apps/web/i18n/messages/de/marketing.json | 76 +- apps/web/i18n/messages/en/common.json | 1 + apps/web/i18n/messages/en/marketing.json | 76 +- apps/web/lib/database.types.ts | 777 ++++++++++++++++++ apps/web/package.json | 1 + ...0260409000001_member_management_parity.sql | 204 +++++ .../20260410000001_site_builder.sql | 176 ++++ .../migrations/20260410000002_anon_grants.sql | 5 + .../20260411000001_member_portal_auth.sql | 93 +++ docker-compose.yml | 147 ++++ docker/kong.yml | 58 ++ package.json | 3 + .../features/booking-management/package.json | 5 +- .../src/components/create-booking-form.tsx | 130 +++ .../src/components/index.ts | 2 +- .../src/server/actions/booking-actions.ts | 69 ++ .../booking-management/src/server/api.ts | 10 + .../features/course-management/package.json | 5 +- .../src/components/create-course-form.tsx | 147 ++++ .../course-management/src/components/index.ts | 2 +- .../src/server/actions/course-actions.ts | 129 +++ .../course-management/src/server/api.ts | 29 + .../features/event-management/package.json | 5 +- .../src/components/create-event-form.tsx | 158 ++++ .../event-management/src/components/index.ts | 2 +- .../src/server/actions/event-actions.ts | 51 ++ .../event-management/src/server/api.ts | 10 + packages/features/finance/package.json | 5 +- .../src/components/create-invoice-form.tsx | 190 +++++ .../src/components/create-sepa-batch-form.tsx | 103 +++ .../features/finance/src/components/index.ts | 3 +- .../src/server/actions/finance-actions.ts | 93 +++ packages/features/finance/src/server/api.ts | 53 ++ .../features/member-management/package.json | 9 +- .../src/components/application-workflow.tsx | 200 +++++ .../src/components/create-member-form.tsx | 243 ++++++ .../src/components/dues-category-manager.tsx | 258 ++++++ .../src/components/edit-member-form.tsx | 228 +++++ .../member-management/src/components/index.ts | 11 +- .../src/components/mandate-manager.tsx | 309 +++++++ .../src/components/member-detail-view.tsx | 227 +++++ .../src/components/member-import-wizard.tsx | 281 +++++++ .../src/components/members-data-table.tsx | 230 ++++++ .../member-management/src/lib/member-utils.ts | 69 ++ .../src/schema/member.schema.ts | 101 +++ .../src/server/actions/member-actions.ts | 302 +++++++ .../member-management/src/server/api.ts | 313 +++++++ .../server/services/member-card-generator.ts | 79 ++ packages/features/newsletter/package.json | 5 +- .../src/components/create-newsletter-form.tsx | 100 +++ .../newsletter/src/components/index.ts | 2 +- .../src/server/actions/newsletter-actions.ts | 91 ++ packages/features/site-builder/package.json | 41 + .../src/components/create-page-form.tsx | 96 +++ .../src/components/create-post-form.tsx | 82 ++ .../site-builder/src/components/index.ts | 6 + .../src/components/portal-login-form.tsx | 113 +++ .../src/components/site-editor.tsx | 35 + .../src/components/site-renderer.tsx | 12 + .../src/components/site-settings-form.tsx | 121 +++ .../site-builder/src/config/puck-config.tsx | 360 ++++++++ .../site-builder/src/schema/site.schema.ts | 66 ++ .../server/actions/site-builder-actions.ts | 80 ++ .../features/site-builder/src/server/api.ts | 136 +++ packages/features/site-builder/tsconfig.json | 6 + packages/supabase/src/database.types.ts | 777 ++++++++++++++++++ pnpm-lock.yaml | 633 ++++++++++++++ pnpm-workspace.yaml | 3 + 120 files changed, 11013 insertions(+), 1858 deletions(-) create mode 100644 .claude/skills/gitnexus/gitnexus-cli/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-debugging/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-exploring/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-guide/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md create mode 100644 .dockerignore create mode 100644 .env.production.example create mode 100644 Dockerfile create mode 100644 apps/web/app/[locale]/club/[slug]/[...page]/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/newsletter/subscribe/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/newsletter/unsubscribe/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/portal/page.tsx create mode 100644 apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/members-cms/[memberId]/edit/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/members-cms/cards/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/members-cms/departments/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/members-cms/import/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/[pageId]/edit/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/new/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/posts/new/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/posts/page.tsx create mode 100644 apps/web/app/[locale]/home/[account]/site-builder/settings/page.tsx create mode 100644 apps/web/app/api/club/accept-invite/route.ts create mode 100644 apps/web/app/api/club/contact/route.ts create mode 100644 apps/web/app/api/club/newsletter/route.ts create mode 100644 apps/web/components/stats-charts.tsx create mode 100644 apps/web/supabase/migrations/20260409000001_member_management_parity.sql create mode 100644 apps/web/supabase/migrations/20260410000001_site_builder.sql create mode 100644 apps/web/supabase/migrations/20260410000002_anon_grants.sql create mode 100644 apps/web/supabase/migrations/20260411000001_member_portal_auth.sql create mode 100644 docker-compose.yml create mode 100644 docker/kong.yml create mode 100644 packages/features/booking-management/src/components/create-booking-form.tsx create mode 100644 packages/features/booking-management/src/server/actions/booking-actions.ts create mode 100644 packages/features/course-management/src/components/create-course-form.tsx create mode 100644 packages/features/course-management/src/server/actions/course-actions.ts create mode 100644 packages/features/event-management/src/components/create-event-form.tsx create mode 100644 packages/features/event-management/src/server/actions/event-actions.ts create mode 100644 packages/features/finance/src/components/create-invoice-form.tsx create mode 100644 packages/features/finance/src/components/create-sepa-batch-form.tsx create mode 100644 packages/features/finance/src/server/actions/finance-actions.ts create mode 100644 packages/features/member-management/src/components/application-workflow.tsx create mode 100644 packages/features/member-management/src/components/create-member-form.tsx create mode 100644 packages/features/member-management/src/components/dues-category-manager.tsx create mode 100644 packages/features/member-management/src/components/edit-member-form.tsx create mode 100644 packages/features/member-management/src/components/mandate-manager.tsx create mode 100644 packages/features/member-management/src/components/member-detail-view.tsx create mode 100644 packages/features/member-management/src/components/member-import-wizard.tsx create mode 100644 packages/features/member-management/src/components/members-data-table.tsx create mode 100644 packages/features/member-management/src/lib/member-utils.ts create mode 100644 packages/features/member-management/src/server/actions/member-actions.ts create mode 100644 packages/features/member-management/src/server/services/member-card-generator.ts create mode 100644 packages/features/newsletter/src/components/create-newsletter-form.tsx create mode 100644 packages/features/newsletter/src/server/actions/newsletter-actions.ts create mode 100644 packages/features/site-builder/package.json create mode 100644 packages/features/site-builder/src/components/create-page-form.tsx create mode 100644 packages/features/site-builder/src/components/create-post-form.tsx create mode 100644 packages/features/site-builder/src/components/index.ts create mode 100644 packages/features/site-builder/src/components/portal-login-form.tsx create mode 100644 packages/features/site-builder/src/components/site-editor.tsx create mode 100644 packages/features/site-builder/src/components/site-renderer.tsx create mode 100644 packages/features/site-builder/src/components/site-settings-form.tsx create mode 100644 packages/features/site-builder/src/config/puck-config.tsx create mode 100644 packages/features/site-builder/src/schema/site.schema.ts create mode 100644 packages/features/site-builder/src/server/actions/site-builder-actions.ts create mode 100644 packages/features/site-builder/src/server/api.ts create mode 100644 packages/features/site-builder/tsconfig.json diff --git a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md new file mode 100644 index 000000000..c9e0af341 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md @@ -0,0 +1,82 @@ +--- +name: gitnexus-cli +description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\"" +--- + +# GitNexus CLI Commands + +All commands work via `npx` — no global install required. + +## Commands + +### analyze — Build or refresh the index + +```bash +npx gitnexus analyze +``` + +Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files. + +| Flag | Effect | +| -------------- | ---------------------------------------------------------------- | +| `--force` | Force full re-index even if up to date | +| `--embeddings` | Enable embedding generation for semantic search (off by default) | + +**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated. + +### status — Check index freshness + +```bash +npx gitnexus status +``` + +Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed. + +### clean — Delete the index + +```bash +npx gitnexus clean +``` + +Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project. + +| Flag | Effect | +| --------- | ------------------------------------------------- | +| `--force` | Skip confirmation prompt | +| `--all` | Clean all indexed repos, not just the current one | + +### wiki — Generate documentation from the graph + +```bash +npx gitnexus wiki +``` + +Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use). + +| Flag | Effect | +| ------------------- | ----------------------------------------- | +| `--force` | Force full regeneration | +| `--model ` | LLM model (default: minimax/minimax-m2.5) | +| `--base-url ` | LLM API base URL | +| `--api-key ` | LLM API key | +| `--concurrency ` | Parallel LLM calls (default: 3) | +| `--gist` | Publish wiki as a public GitHub Gist | + +### list — Show all indexed repos + +```bash +npx gitnexus list +``` + +Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information. + +## After Indexing + +1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded +2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task + +## Troubleshooting + +- **"Not inside a git repository"**: Run from a directory inside a git repo +- **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server +- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding diff --git a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md new file mode 100644 index 000000000..9510b97ac --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md @@ -0,0 +1,89 @@ +--- +name: gitnexus-debugging +description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\"" +--- + +# Debugging with GitNexus + +## When to Use + +- "Why is this function failing?" +- "Trace where this error comes from" +- "Who calls this method?" +- "This endpoint returns 500" +- Investigating bugs, errors, or unexpected behavior + +## Workflow + +``` +1. gitnexus_query({query: ""}) → Find related execution flows +2. gitnexus_context({name: ""}) → See callers/callees/processes +3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow +4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] Understand the symptom (error message, unexpected behavior) +- [ ] gitnexus_query for error text or related code +- [ ] Identify the suspect function from returned processes +- [ ] gitnexus_context to see callers and callees +- [ ] Trace execution flow via process resource if applicable +- [ ] gitnexus_cypher for custom call chain traces if needed +- [ ] Read source files to confirm root cause +``` + +## Debugging Patterns + +| Symptom | GitNexus Approach | +| -------------------- | ---------------------------------------------------------- | +| Error message | `gitnexus_query` for error text → `context` on throw sites | +| Wrong return value | `context` on the function → trace callees for data flow | +| Intermittent failure | `context` → look for external calls, async deps | +| Performance issue | `context` → find symbols with many callers (hot paths) | +| Recent regression | `detect_changes` to see what your changes affect | + +## Tools + +**gitnexus_query** — find code related to error: + +``` +gitnexus_query({query: "payment validation error"}) +→ Processes: CheckoutFlow, ErrorHandling +→ Symbols: validatePayment, handlePaymentError, PaymentException +``` + +**gitnexus_context** — full context for a suspect: + +``` +gitnexus_context({name: "validatePayment"}) +→ Incoming calls: processCheckout, webhookHandler +→ Outgoing calls: verifyCard, fetchRates (external API!) +→ Processes: CheckoutFlow (step 3/7) +``` + +**gitnexus_cypher** — custom call chain traces: + +```cypher +MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"}) +RETURN [n IN nodes(path) | n.name] AS chain +``` + +## Example: "Payment endpoint returns 500 intermittently" + +``` +1. gitnexus_query({query: "payment error handling"}) + → Processes: CheckoutFlow, ErrorHandling + → Symbols: validatePayment, handlePaymentError + +2. gitnexus_context({name: "validatePayment"}) + → Outgoing calls: verifyCard, fetchRates (external API!) + +3. READ gitnexus://repo/my-app/process/CheckoutFlow + → Step 3: validatePayment → calls fetchRates (external) + +4. Root cause: fetchRates calls external API without proper timeout +``` diff --git a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md new file mode 100644 index 000000000..927a4e4b6 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md @@ -0,0 +1,78 @@ +--- +name: gitnexus-exploring +description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\"" +--- + +# Exploring Codebases with GitNexus + +## When to Use + +- "How does authentication work?" +- "What's the project structure?" +- "Show me the main components" +- "Where is the database logic?" +- Understanding code you haven't seen before + +## Workflow + +``` +1. READ gitnexus://repos → Discover indexed repos +2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness +3. gitnexus_query({query: ""}) → Find related execution flows +4. gitnexus_context({name: ""}) → Deep dive on specific symbol +5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow +``` + +> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] READ gitnexus://repo/{name}/context +- [ ] gitnexus_query for the concept you want to understand +- [ ] Review returned processes (execution flows) +- [ ] gitnexus_context on key symbols for callers/callees +- [ ] READ process resource for full execution traces +- [ ] Read source files for implementation details +``` + +## Resources + +| Resource | What you get | +| --------------------------------------- | ------------------------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) | +| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) | +| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) | + +## Tools + +**gitnexus_query** — find execution flows related to a concept: + +``` +gitnexus_query({query: "payment processing"}) +→ Processes: CheckoutFlow, RefundFlow, WebhookHandler +→ Symbols grouped by flow with file locations +``` + +**gitnexus_context** — 360-degree view of a symbol: + +``` +gitnexus_context({name: "validateUser"}) +→ Incoming calls: loginHandler, apiMiddleware +→ Outgoing calls: checkToken, getUserById +→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3) +``` + +## Example: "How does payment processing work?" + +``` +1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes +2. gitnexus_query({query: "payment processing"}) + → CheckoutFlow: processPayment → validateCard → chargeStripe + → RefundFlow: initiateRefund → calculateRefund → processRefund +3. gitnexus_context({name: "processPayment"}) + → Incoming: checkoutHandler, webhookHandler + → Outgoing: validateCard, chargeStripe, saveTransaction +4. Read src/payments/processor.ts for implementation details +``` diff --git a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md new file mode 100644 index 000000000..937ac73d1 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md @@ -0,0 +1,64 @@ +--- +name: gitnexus-guide +description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\"" +--- + +# GitNexus Guide + +Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema. + +## Always Start Here + +For any task involving code understanding, debugging, impact analysis, or refactoring: + +1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness +2. **Match your task to a skill below** and **read that skill file** +3. **Follow the skill's workflow and checklist** + +> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first. + +## Skills + +| Task | Skill to read | +| -------------------------------------------- | ------------------- | +| Understand architecture / "How does X work?" | `gitnexus-exploring` | +| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` | +| Trace bugs / "Why is X failing?" | `gitnexus-debugging` | +| Rename / extract / split / refactor | `gitnexus-refactoring` | +| Tools, resources, schema reference | `gitnexus-guide` (this file) | +| Index, status, clean, wiki CLI commands | `gitnexus-cli` | + +## Tools Reference + +| Tool | What it gives you | +| ---------------- | ------------------------------------------------------------------------ | +| `query` | Process-grouped code intelligence — execution flows related to a concept | +| `context` | 360-degree symbol view — categorized refs, processes it participates in | +| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence | +| `detect_changes` | Git-diff impact — what do your current changes affect | +| `rename` | Multi-file coordinated rename with confidence-tagged edits | +| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) | +| `list_repos` | Discover indexed repos | + +## Resources Reference + +Lightweight reads (~100-500 tokens) for navigation: + +| Resource | Content | +| ---------------------------------------------- | ----------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness check | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores | +| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members | +| `gitnexus://repo/{name}/processes` | All execution flows | +| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace | +| `gitnexus://repo/{name}/schema` | Graph schema for Cypher | + +## Graph Schema + +**Nodes:** File, Function, Class, Interface, Method, Community, Process +**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"}) +RETURN caller.name, caller.filePath +``` diff --git a/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md new file mode 100644 index 000000000..e19af280c --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md @@ -0,0 +1,97 @@ +--- +name: gitnexus-impact-analysis +description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\"" +--- + +# Impact Analysis with GitNexus + +## When to Use + +- "Is it safe to change this function?" +- "What will break if I modify X?" +- "Show me the blast radius" +- "Who uses this code?" +- Before making non-trivial code changes +- Before committing — to understand what your changes affect + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this +2. READ gitnexus://repo/{name}/processes → Check affected execution flows +3. gitnexus_detect_changes() → Map current git changes to affected flows +4. Assess risk and report to user +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents +- [ ] Review d=1 items first (these WILL BREAK) +- [ ] Check high-confidence (>0.8) dependencies +- [ ] READ processes to check affected execution flows +- [ ] gitnexus_detect_changes() for pre-commit check +- [ ] Assess risk level and report to user +``` + +## Understanding Output + +| Depth | Risk Level | Meaning | +| ----- | ---------------- | ------------------------ | +| d=1 | **WILL BREAK** | Direct callers/importers | +| d=2 | LIKELY AFFECTED | Indirect dependencies | +| d=3 | MAY NEED TESTING | Transitive effects | + +## Risk Assessment + +| Affected | Risk | +| ------------------------------ | -------- | +| <5 symbols, few processes | LOW | +| 5-15 symbols, 2-5 processes | MEDIUM | +| >15 symbols or many processes | HIGH | +| Critical path (auth, payments) | CRITICAL | + +## Tools + +**gitnexus_impact** — the primary tool for symbol blast radius: + +``` +gitnexus_impact({ + target: "validateUser", + direction: "upstream", + minConfidence: 0.8, + maxDepth: 3 +}) + +→ d=1 (WILL BREAK): + - loginHandler (src/auth/login.ts:42) [CALLS, 100%] + - apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%] + +→ d=2 (LIKELY AFFECTED): + - authRouter (src/routes/auth.ts:22) [CALLS, 95%] +``` + +**gitnexus_detect_changes** — git-diff based impact analysis: + +``` +gitnexus_detect_changes({scope: "staged"}) + +→ Changed: 5 symbols in 3 files +→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline +→ Risk: MEDIUM +``` + +## Example: "What breaks if I change validateUser?" + +``` +1. gitnexus_impact({target: "validateUser", direction: "upstream"}) + → d=1: loginHandler, apiMiddleware (WILL BREAK) + → d=2: authRouter, sessionManager (LIKELY AFFECTED) + +2. READ gitnexus://repo/my-app/processes + → LoginFlow and TokenRefresh touch validateUser + +3. Risk: 2 direct callers, 2 processes = MEDIUM +``` diff --git a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md new file mode 100644 index 000000000..f48cc01bd --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md @@ -0,0 +1,121 @@ +--- +name: gitnexus-refactoring +description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\"" +--- + +# Refactoring with GitNexus + +## When to Use + +- "Rename this function safely" +- "Extract this into a module" +- "Split this service" +- "Move this to a new file" +- Any task involving renaming, extracting, splitting, or restructuring code + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents +2. gitnexus_query({query: "X"}) → Find execution flows involving X +3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs +4. Plan update order: interfaces → implementations → callers → tests +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklists + +### Rename Symbol + +``` +- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits +- [ ] Review graph edits (high confidence) and ast_search edits (review carefully) +- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits +- [ ] gitnexus_detect_changes() — verify only expected files changed +- [ ] Run tests for affected processes +``` + +### Extract Module + +``` +- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs +- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers +- [ ] Define new module interface +- [ ] Extract code, update imports +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +### Split Function/Service + +``` +- [ ] gitnexus_context({name: target}) — understand all callees +- [ ] Group callees by responsibility +- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update +- [ ] Create new functions/services +- [ ] Update callers +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +## Tools + +**gitnexus_rename** — automated multi-file rename: + +``` +gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) +→ 12 edits across 8 files +→ 10 graph edits (high confidence), 2 ast_search edits (review) +→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}] +``` + +**gitnexus_impact** — map all dependents first: + +``` +gitnexus_impact({target: "validateUser", direction: "upstream"}) +→ d=1: loginHandler, apiMiddleware, testUtils +→ Affected Processes: LoginFlow, TokenRefresh +``` + +**gitnexus_detect_changes** — verify your changes after refactoring: + +``` +gitnexus_detect_changes({scope: "all"}) +→ Changed: 8 files, 12 symbols +→ Affected processes: LoginFlow, TokenRefresh +→ Risk: MEDIUM +``` + +**gitnexus_cypher** — custom reference queries: + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"}) +RETURN caller.name, caller.filePath ORDER BY caller.filePath +``` + +## Risk Rules + +| Risk Factor | Mitigation | +| ------------------- | ----------------------------------------- | +| Many callers (>5) | Use gitnexus_rename for automated updates | +| Cross-area refs | Use detect_changes after to verify scope | +| String/dynamic refs | gitnexus_query to find them | +| External/public API | Version and deprecate properly | + +## Example: Rename `validateUser` to `authenticateUser` + +``` +1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) + → 12 edits: 10 graph (safe), 2 ast_search (review) + → Files: validator.ts, login.ts, middleware.ts, config.json... + +2. Review ast_search edits (config.json: dynamic reference!) + +3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) + → Applied 12 edits across 8 files + +4. gitnexus_detect_changes({scope: "all"}) + → Affected: LoginFlow, TokenRefresh + → Risk: MEDIUM — run tests for these flows +``` diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d26dfabb5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.next +.turbo +.git +*.md +.env* +.DS_Store +apps/e2e +apps/dev-tool +.gitnexus +.gsd +.claude diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 000000000..f801bc32b --- /dev/null +++ b/.env.production.example @@ -0,0 +1,48 @@ +# ===================================================== +# MyEasyCMS v2 — Environment Variables +# Copy to .env and fill in your values +# ===================================================== + +# --- Supabase --- +POSTGRES_PASSWORD=change-me-to-a-strong-password +JWT_SECRET=change-me-to-at-least-32-characters-long-secret + +# Generate these with: npx supabase gen keys +SUPABASE_ANON_KEY=your-anon-key-here +SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here + +# --- App --- +SITE_URL=https://myeasycms.de +APP_PORT=3000 + +# --- Kong --- +KONG_HTTP_PORT=8000 +KONG_HTTPS_PORT=8443 +API_EXTERNAL_URL=https://api.myeasycms.de + +# --- Email (SMTP) --- +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USER=noreply@myeasycms.de +SMTP_PASS=your-smtp-password +SMTP_ADMIN_EMAIL=admin@myeasycms.de + +# --- Auth --- +ENABLE_EMAIL_AUTOCONFIRM=false +DISABLE_SIGNUP=false +JWT_EXPIRY=3600 +ADDITIONAL_REDIRECT_URLS= + +# --- Webhooks --- +DB_WEBHOOK_SECRET=your-webhook-secret + +# --- Feature Flags --- +# All default to true, set to false to disable +# NEXT_PUBLIC_ENABLE_MODULE_BUILDER=true +# NEXT_PUBLIC_ENABLE_MEMBER_MANAGEMENT=true +# NEXT_PUBLIC_ENABLE_COURSE_MANAGEMENT=true +# NEXT_PUBLIC_ENABLE_BOOKING_MANAGEMENT=false +# NEXT_PUBLIC_ENABLE_SEPA_PAYMENTS=true +# NEXT_PUBLIC_ENABLE_DOCUMENT_GENERATION=true +# NEXT_PUBLIC_ENABLE_NEWSLETTER=true +# NEXT_PUBLIC_ENABLE_SITE_BUILDER=true diff --git a/.gitignore b/.gitignore index 3c3c8d420..161be2abb 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,5 @@ yarn-error.log* node-compile-cache/ # prds -.prds/ \ No newline at end of file +.prds/ +.gitnexus diff --git a/AGENTS.md b/AGENTS.md index d1dc564cc..26ca9df84 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,3 +68,105 @@ After implementation, always run: 2. `pnpm lint:fix` 3. `pnpm format:fix` 4. Run code quality reviewer agent + + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **myeasycms-v2** (5424 symbols, 14434 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. + +## When Debugging + +1. `gitnexus_query({query: ""})` — find execution flows related to the issue +2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation +3. `READ gitnexus://repo/myeasycms-v2/process/{processName}` — trace the full execution flow step by step +4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed + +## When Refactoring + +- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. +- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. +- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. + +## Never Do + +- NEVER edit a function, class, or method without first running `gitnexus_impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. +- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. + +## Tools Quick Reference + +| Tool | When to use | Command | +|------|-------------|---------| +| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | +| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | +| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | +| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | +| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | +| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | + +## Impact Risk Levels + +| Depth | Meaning | Action | +|-------|---------|--------| +| d=1 | WILL BREAK — direct callers/importers | MUST update these | +| d=2 | LIKELY AFFECTED — indirect deps | Should test | +| d=3 | MAY NEED TESTING — transitive | Test if critical path | + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/myeasycms-v2/context` | Codebase overview, check index freshness | +| `gitnexus://repo/myeasycms-v2/clusters` | All functional areas | +| `gitnexus://repo/myeasycms-v2/processes` | All execution flows | +| `gitnexus://repo/myeasycms-v2/process/{name}` | Step-by-step execution trace | + +## Self-Check Before Finishing + +Before completing any code modification task, verify: +1. `gitnexus_impact` was run for all modified symbols +2. No HIGH/CRITICAL risk warnings were ignored +3. `gitnexus_detect_changes()` confirms changes match expected scope +4. All d=1 (WILL BREAK) dependents were updated + +## Keeping the Index Fresh + +After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: + +```bash +npx gitnexus analyze +``` + +If the index previously included embeddings, preserve them by adding `--embeddings`: + +```bash +npx gitnexus analyze --embeddings +``` + +To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** + +> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + diff --git a/CLAUDE.md b/CLAUDE.md index eef4bd20c..f0862be7c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,103 @@ -@AGENTS.md \ No newline at end of file +@AGENTS.md + + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **myeasycms-v2** (5424 symbols, 14434 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. + +## When Debugging + +1. `gitnexus_query({query: ""})` — find execution flows related to the issue +2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation +3. `READ gitnexus://repo/myeasycms-v2/process/{processName}` — trace the full execution flow step by step +4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed + +## When Refactoring + +- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. +- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. +- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. + +## Never Do + +- NEVER edit a function, class, or method without first running `gitnexus_impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. +- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. + +## Tools Quick Reference + +| Tool | When to use | Command | +|------|-------------|---------| +| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | +| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | +| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | +| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | +| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | +| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | + +## Impact Risk Levels + +| Depth | Meaning | Action | +|-------|---------|--------| +| d=1 | WILL BREAK — direct callers/importers | MUST update these | +| d=2 | LIKELY AFFECTED — indirect deps | Should test | +| d=3 | MAY NEED TESTING — transitive | Test if critical path | + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/myeasycms-v2/context` | Codebase overview, check index freshness | +| `gitnexus://repo/myeasycms-v2/clusters` | All functional areas | +| `gitnexus://repo/myeasycms-v2/processes` | All execution flows | +| `gitnexus://repo/myeasycms-v2/process/{name}` | Step-by-step execution trace | + +## Self-Check Before Finishing + +Before completing any code modification task, verify: +1. `gitnexus_impact` was run for all modified symbols +2. No HIGH/CRITICAL risk warnings were ignored +3. `gitnexus_detect_changes()` confirms changes match expected scope +4. All d=1 (WILL BREAK) dependents were updated + +## Keeping the Index Fresh + +After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: + +```bash +npx gitnexus analyze +``` + +If the index previously included embeddings, preserve them by adding `--embeddings`: + +```bash +npx gitnexus analyze --embeddings +``` + +To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** + +> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..7ff1863a1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +# ===================================================== +# MyEasyCMS v2 — Production Dockerfile +# Multi-stage build for Next.js with pnpm +# ===================================================== + +FROM node:22-alpine AS base +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app + +# --- Dependencies stage --- +FROM base AS deps +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/web/package.json ./apps/web/package.json +COPY packages/ ./packages/ +COPY tooling/ ./tooling/ +RUN pnpm install --frozen-lockfile --prod=false + +# --- Build stage --- +FROM base AS builder +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/packages/ ./packages/ +COPY . . + +# Set build-time env vars +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NEXT_PUBLIC_SITE_URL=https://myeasycms.de + +RUN pnpm --filter web build + +# --- Production stage --- +FROM node:22-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy built app +COPY --from=builder /app/apps/web/.next/standalone ./ +COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=builder /app/apps/web/public ./apps/web/public + +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "apps/web/server.js"] diff --git a/apps/web/app/[locale]/(marketing)/page.tsx b/apps/web/app/[locale]/(marketing)/page.tsx index ab320ca08..7d7f36de9 100644 --- a/apps/web/app/[locale]/(marketing)/page.tsx +++ b/apps/web/app/[locale]/(marketing)/page.tsx @@ -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 ( -
+
+ {/* Hero Section */}
- The SaaS Starter Kit for ambitious developers - - - - } - /> + + + + } title={ - Ship a SaaS faster than ever. + } subtitle={ - 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. + } cta={} @@ -55,95 +64,227 @@ function Home() { {`App } />
+ {/* Trust Indicators */} +
+
+

+ +

+ +
+ + + + +
+
+
+ + {/* Core Modules Feature Grid */}
- The ultimate SaaS Starter Kit + .{' '} - Unleash your creativity and build your SaaS faster than ever - with Makerkit. + } icon={ - - All-in-one solution + + + + } > - - - - - - + - - - - - - - + + +
+ {/* Dashboard Showcase */}
} + description={} > Sign in
+ {/* Additional Features Row */} +
+
+ + + + + .{' '} + + + + + } + icon={ + + + + + + + } + > +
+ + + +
+
+
+
+ + {/* Why Choose Us Section */} +
+ } + description={} + textPosition="right" + > +
+ + + + +
+
+
+ + {/* How It Works */} +
+
+
+

+ +

+

+ +

+
+ +
+ + + +
+
+
+ + {/* Pricing Section */}
No credit card required.} - heading="Fair pricing for all types of businesses" - subheading="Get started on our free plan and upgrade when you are ready." + pill={ + }> + + + } + heading={} + subheading={} />
@@ -167,6 +312,37 @@ function Home() {
+ + {/* Final CTA */} +
+
+

+ +

+

+ +

+
+ + + + + + + + + + + + + +
+

+ + +

+
+
); } @@ -201,3 +377,73 @@ function MainCallToActionButton() { ); } + +function IconFeatureCard(props: { + icon: React.ComponentType<{ className?: string }>; + titleKey: string; + descKey: string; +}) { + return ( +
+
+ +
+

+ +

+

+ +

+
+ ); +} + +function TrustItem(props: { + icon: React.ComponentType<{ className?: string }>; + label: string; +}) { + return ( +
+ + + + +
+ ); +} + +function WhyItem(props: { + icon: React.ComponentType<{ className?: string }>; + titleKey: string; + descKey: string; +}) { + return ( +
+
+ +
+
+

+ +

+

+ +

+
+
+ ); +} + +function StepCard(props: { step: string; titleKey: string; descKey: string }) { + return ( +
+ {props.step} +

+ +

+

+ +

+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx b/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx new file mode 100644 index 000000000..1756eb8d4 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/[...page]/page.tsx @@ -0,0 +1,31 @@ +import { createClient } from '@supabase/supabase-js'; +import { notFound } from 'next/navigation'; +import { SiteRenderer } from '@kit/site-builder/components'; + +interface Props { params: Promise<{ slug: string; page: string[] }> } + +export default async function ClubSubPage({ params }: Props) { + const { slug, page: pagePath } = await params; + const pageSlug = pagePath.join('/'); + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + const { data: account } = await supabase.from('accounts').select('id').eq('slug', slug).single(); + if (!account) notFound(); + + const { data: settings } = await supabase.from('site_settings').select('*').eq('account_id', account.id).eq('is_public', true).maybeSingle(); + if (!settings) notFound(); + + const { data: sitePageData } = await supabase.from('site_pages').select('*') + .eq('account_id', account.id).eq('slug', pageSlug).eq('is_published', true).maybeSingle(); + if (!sitePageData) notFound(); + + return ( +
+ } /> +
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/newsletter/subscribe/page.tsx b/apps/web/app/[locale]/club/[slug]/newsletter/subscribe/page.tsx new file mode 100644 index 000000000..312880318 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/newsletter/subscribe/page.tsx @@ -0,0 +1,41 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Input } from '@kit/ui/input'; +import { Label } from '@kit/ui/label'; +import { Button } from '@kit/ui/button'; +import { Mail } from 'lucide-react'; + +interface Props { params: Promise<{ slug: string }> } + +export default async function NewsletterSubscribePage({ params }: Props) { + const { slug } = await params; + + return ( +
+ + +
+ +
+ Newsletter abonnieren +

Bleiben Sie über Neuigkeiten informiert.

+
+ +
+
+ + +
+
+ + +
+ +

+ Sie können sich jederzeit abmelden. Wir senden Ihnen eine Bestätigungs-E-Mail. +

+
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/newsletter/unsubscribe/page.tsx b/apps/web/app/[locale]/club/[slug]/newsletter/unsubscribe/page.tsx new file mode 100644 index 000000000..deb842f65 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/newsletter/unsubscribe/page.tsx @@ -0,0 +1,37 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Button } from '@kit/ui/button'; +import { MailX } from 'lucide-react'; +import Link from 'next/link'; + +interface Props { params: Promise<{ slug: string }>; searchParams: Promise<{ token?: string }> } + +export default async function NewsletterUnsubscribePage({ params, searchParams }: Props) { + const { slug } = await params; + const { token } = await searchParams; + + return ( +
+ + +
+ +
+ Newsletter abbestellen +
+ + {token ? ( + <> +

Möchten Sie den Newsletter wirklich abbestellen?

+ + + ) : ( +

Kein gültiger Abmeldelink. Bitte verwenden Sie den Link aus der Newsletter-E-Mail.

+ )} + + + +
+
+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/page.tsx b/apps/web/app/[locale]/club/[slug]/page.tsx new file mode 100644 index 000000000..d5c3eb0e7 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/page.tsx @@ -0,0 +1,34 @@ +import { createClient } from '@supabase/supabase-js'; +import { notFound } from 'next/navigation'; +import { SiteRenderer } from '@kit/site-builder/components'; + +interface Props { params: Promise<{ slug: string }> } + +export default async function ClubHomePage({ params }: Props) { + const { slug } = await params; + + // Use anon client for public access + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + // Resolve slug → account + const { data: account } = await supabase.from('accounts').select('id, name').eq('slug', slug).single(); + if (!account) notFound(); + + // Check site is public + const { data: settings } = await supabase.from('site_settings').select('*').eq('account_id', account.id).eq('is_public', true).maybeSingle(); + if (!settings) notFound(); + + // Get homepage + const { data: page } = await supabase.from('site_pages').select('*') + .eq('account_id', account.id).eq('is_homepage', true).eq('is_published', true).maybeSingle(); + if (!page) notFound(); + + return ( +
+ } /> +
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx new file mode 100644 index 000000000..d9553a275 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/portal/documents/page.tsx @@ -0,0 +1,100 @@ +import { createClient } from '@supabase/supabase-js'; +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Button } from '@kit/ui/button'; +import { Badge } from '@kit/ui/badge'; +import { FileText, Download, Shield, Receipt, FileCheck } from 'lucide-react'; +import Link from 'next/link'; + +interface Props { + params: Promise<{ slug: string }>; +} + +export default async function PortalDocumentsPage({ params }: Props) { + const { slug } = await params; + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + const { data: account } = await supabase.from('accounts').select('id, name').eq('slug', slug).single(); + if (!account) return
Organisation nicht gefunden
; + + // Demo documents (in production: query invoices + cms_files for this member) + const documents = [ + { id: '1', title: 'Mitgliedsbeitrag 2026', type: 'Rechnung', date: '2026-01-15', status: 'paid' }, + { id: '2', title: 'Mitgliedsbeitrag 2025', type: 'Rechnung', date: '2025-01-10', status: 'paid' }, + { id: '3', title: 'Beitrittserklärung', type: 'Dokument', date: '2020-01-15', status: 'signed' }, + ]; + + const getStatusBadge = (status: string) => { + switch (status) { + case 'paid': return Bezahlt; + case 'open': return Offen; + case 'signed': return Unterschrieben; + default: return {status}; + } + }; + + const getIcon = (type: string) => { + switch (type) { + case 'Rechnung': return ; + case 'Dokument': return ; + default: return ; + } + }; + + return ( +
+
+
+
+ +

Meine Dokumente

+
+ + + +
+
+ +
+ + + Verfügbare Dokumente +

{String(account.name)} — Dokumente und Rechnungen

+
+ + {documents.length === 0 ? ( +
+ +

Keine Dokumente vorhanden

+
+ ) : ( +
+ {documents.map((doc) => ( +
+
+ {getIcon(doc.type)} +
+

{doc.title}

+

{doc.type} — {new Date(doc.date).toLocaleDateString('de-DE')}

+
+
+
+ {getStatusBadge(doc.status)} + +
+
+ ))} +
+ )} +
+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx new file mode 100644 index 000000000..4343d5812 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/portal/invite/page.tsx @@ -0,0 +1,125 @@ +import { createClient } from '@supabase/supabase-js'; +import { notFound } from 'next/navigation'; +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Button } from '@kit/ui/button'; +import { Input } from '@kit/ui/input'; +import { Label } from '@kit/ui/label'; +import { UserPlus, Shield, CheckCircle } from 'lucide-react'; +import Link from 'next/link'; + +interface Props { + params: Promise<{ slug: string }>; + searchParams: Promise<{ token?: string }>; +} + +export default async function PortalInvitePage({ params, searchParams }: Props) { + const { slug } = await params; + const { token } = await searchParams; + + if (!token) notFound(); + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + // Resolve account + const { data: account } = await supabase.from('accounts').select('id, name').eq('slug', slug).single(); + if (!account) notFound(); + + // Look up invitation + const { data: invitation } = await supabase.from('member_portal_invitations') + .select('id, email, status, expires_at, member_id') + .eq('invite_token', token) + .maybeSingle(); + + if (!invitation || invitation.status !== 'pending') { + return ( +
+ + + +

Einladung ungültig

+

+ Diese Einladung ist abgelaufen, wurde bereits verwendet oder ist ungültig. + Bitte wenden Sie sich an Ihren Vereinsadministrator. +

+ + + +
+
+
+ ); + } + + const expired = new Date(invitation.expires_at) < new Date(); + if (expired) { + return ( +
+ + + +

Einladung abgelaufen

+

+ Diese Einladung ist am {new Date(invitation.expires_at).toLocaleDateString('de-DE')} abgelaufen. + Bitte fordern Sie eine neue Einladung an. +

+
+
+
+ ); + } + + return ( +
+ + +
+ +
+ Einladung zum Mitgliederbereich +

{String(account.name)}

+
+ +
+

+ Sie wurden eingeladen, ein Konto für den Mitgliederbereich zu erstellen. + Damit können Sie Ihr Profil einsehen, Dokumente herunterladen und Ihre Datenschutz-Einstellungen verwalten. +

+
+ +
+ + + +
+ + +

Ihre E-Mail-Adresse wurde vom Verein vorgegeben.

+
+ +
+ + +
+ +
+ + +
+ + +
+ +

+ Bereits ein Konto? Anmelden +

+
+
+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/portal/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/page.tsx new file mode 100644 index 000000000..cedce27ec --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/portal/page.tsx @@ -0,0 +1,100 @@ +import { createClient } from '@supabase/supabase-js'; +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Button } from '@kit/ui/button'; +import { UserCircle, FileText, CreditCard, Shield } from 'lucide-react'; +import Link from 'next/link'; + +import { PortalLoginForm } from '@kit/site-builder/components'; + +interface Props { + params: Promise<{ slug: string }>; +} + +export default async function MemberPortalPage({ params }: Props) { + const { slug } = await params; + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + const { data: account } = await supabase.from('accounts').select('id, name').eq('slug', slug).single(); + if (!account) return
Organisation nicht gefunden
; + + // Check if user is already logged in + const { data: { user } } = await supabase.auth.getUser(); + + if (user) { + // Check if this user is a member of this club + const { data: member } = await supabase.from('members') + .select('id, first_name, last_name, status') + .eq('account_id', account.id) + .eq('user_id', user.id) + .maybeSingle(); + + if (member) { + // Logged in member — show portal dashboard + return ( +
+
+
+
+ +

Mitgliederbereich — {String(account.name)}

+
+
+ {String(member.first_name)} {String(member.last_name)} + +
+
+
+
+

Willkommen, {String(member.first_name)}!

+
+ + + + +

Mein Profil

+

Kontaktdaten und Datenschutz

+
+
+ + + + + +

Dokumente

+

Rechnungen und Bescheinigungen

+
+
+ + + + +

Mitgliedsausweis

+

Digital anzeigen

+
+
+
+
+
+ ); + } + } + + // Not logged in or not a member — show login form + return ( +
+
+
+

Mitgliederbereich

+ +
+
+
+ +
+
+ ); +} diff --git a/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx b/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx new file mode 100644 index 000000000..0ba3b4ee2 --- /dev/null +++ b/apps/web/app/[locale]/club/[slug]/portal/profile/page.tsx @@ -0,0 +1,131 @@ +import { createClient } from '@supabase/supabase-js'; +import { redirect } from 'next/navigation'; +import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card'; +import { Button } from '@kit/ui/button'; +import { Input } from '@kit/ui/input'; +import { Label } from '@kit/ui/label'; +import { UserCircle, Mail, MapPin, Phone, Shield, Calendar } from 'lucide-react'; +import Link from 'next/link'; + +interface Props { + params: Promise<{ slug: string }>; +} + +export default async function PortalProfilePage({ params }: Props) { + const { slug } = await params; + + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_PUBLIC_KEY!, + ); + + const { data: account } = await supabase.from('accounts').select('id, name').eq('slug', slug).single(); + if (!account) return
Organisation nicht gefunden
; + + // Get current user + const { data: { user } } = await supabase.auth.getUser(); + if (!user) redirect(`/club/${slug}/portal`); + + // Find member linked to this user + const { data: member } = await supabase.from('members') + .select('*') + .eq('account_id', account.id) + .eq('user_id', user.id) + .maybeSingle(); + + if (!member) { + return ( +
+ + + +

Kein Mitglied

+

+ Ihr Benutzerkonto ist nicht mit einem Mitgliedsprofil in diesem Verein verknüpft. + Bitte wenden Sie sich an Ihren Vereinsadministrator. +

+ + + +
+
+
+ ); + } + + const m = member; + + return ( +
+
+
+
+ +

Mein Profil

+
+ +
+
+ +
+ + +
+
+ +
+
+

{String(m.first_name)} {String(m.last_name)}

+

+ Nr. {String(m.member_number ?? '—')} — Mitglied seit {m.entry_date ? new Date(String(m.entry_date)).toLocaleDateString('de-DE') : '—'} +

+
+
+
+
+ + + Kontaktdaten + +
+
+
+
+
+
+
+ + + Adresse + +
+
+
+
+
+
+ + + Datenschutz-Einwilligungen + + {[ + { key: 'gdpr_newsletter', label: 'Newsletter per E-Mail', value: m.gdpr_newsletter }, + { key: 'gdpr_internet', label: 'Veröffentlichung auf der Homepage', value: m.gdpr_internet }, + { key: 'gdpr_print', label: 'Veröffentlichung in der Vereinszeitung', value: m.gdpr_print }, + { key: 'gdpr_birthday_info', label: 'Geburtstagsinfo an Mitglieder', value: m.gdpr_birthday_info }, + ].map(({ key, label, value }) => ( + + ))} + + + +
+ +
+
+
+ ); +} diff --git a/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx b/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx index 890e225f6..822274561 100644 --- a/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx +++ b/apps/web/app/[locale]/home/[account]/bookings/new/page.tsx @@ -1,235 +1,28 @@ -import Link from 'next/link'; - -import { ArrowLeft, BedDouble } from 'lucide-react'; - import { getSupabaseServerClient } from '@kit/supabase/server-client'; -import { Button } from '@kit/ui/button'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@kit/ui/card'; - import { createBookingManagementApi } from '@kit/booking-management/api'; - +import { CreateBookingForm } from '@kit/booking-management/components'; import { CmsPageShell } from '~/components/cms-page-shell'; -interface PageProps { - params: Promise<{ account: string }>; -} +interface Props { params: Promise<{ account: string }> } -export default async function NewBookingPage({ params }: PageProps) { +export default async function NewBookingPage({ params }: Props) { const { account } = await params; const client = getSupabaseServerClient(); - - const { data: acct } = await client - .from('accounts') - .select('id') - .eq('slug', account) - .single(); - + const { data: acct } = await client.from('accounts').select('id').eq('slug', account).single(); if (!acct) return
Konto nicht gefunden
; const api = createBookingManagementApi(client); - - const [rooms, guests] = await Promise.all([ - api.listRooms(acct.id), - api.listGuests(acct.id), - ]); + const rooms = await api.listRooms(acct.id); return ( - -
- {/* Header */} -
- - - -
-

Neue Buchung

-

- Buchung für ein Zimmer erstellen -

-
-
- -
- {/* Zimmer & Gast */} - - - - - Zimmer & Gast - - - Wählen Sie Zimmer und Gast für die Buchung aus - - - -
- - -
- -
- - -
-
-
- - {/* Aufenthalt */} - - - Aufenthalt - - Reisedaten und Personenanzahl - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
-
-
- - {/* Preis & Notizen */} - - - Preis & Notizen - - -
- - -
- -
- -