feat: complete CMS v2 with Docker, Fischerei, Meetings, Verband modules + UX audit fixes
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m26s
Workflow / ⚫️ Test (push) Has been skipped

Major changes:
- Docker Compose: full Supabase stack (11 services) equivalent to supabase CLI
- Fischerei module: 16 DB tables, waters/species/stocking/catch books/competitions
- Sitzungsprotokolle module: meeting protocols, agenda items, task tracking
- Verbandsverwaltung module: federation management, member clubs, contacts, fees
- Per-account module activation via Modules page toggle
- Site Builder: live CMS data in Puck blocks (courses, events, membership registration)
- Public registration APIs: course signup, event registration, membership application
- Document generation: PDF member cards, Excel reports, HTML labels
- Landing page: real Com.BISS content (no filler text)
- UX audit fixes: AccountNotFound component, shared status badges, confirm dialog,
  pagination, duplicate heading removal, emoji→badge replacement, a11y fixes
- QA: healthcheck fix, API auth fix, enum mismatch fix, password required attribute
This commit is contained in:
Zaid Marzguioui
2026-03-31 16:35:46 +02:00
parent 16648c92eb
commit ebd0fd4638
176 changed files with 17133 additions and 981 deletions

View File

@@ -0,0 +1,206 @@
'use server';
import { z } from 'zod';
import { revalidatePath } from 'next/cache';
import { authActionClient } from '@kit/next/safe-action';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import {
CreateMeetingProtocolSchema,
UpdateMeetingProtocolSchema,
CreateProtocolItemSchema,
UpdateProtocolItemSchema,
UpdateItemStatusSchema,
ReorderItemsSchema,
} from '../../schema/meetings.schema';
import { createMeetingsApi } from '../api';
const REVALIDATION_PATH = '/home/[account]/meetings';
// =====================================================
// Protocols
// =====================================================
export const createProtocol = authActionClient
.inputSchema(CreateMeetingProtocolSchema)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.protocol.create' }, 'Protokoll wird erstellt...');
const result = await api.createProtocol(input, userId);
logger.info({ name: 'meetings.protocol.create' }, 'Protokoll erstellt');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const updateProtocol = authActionClient
.inputSchema(UpdateMeetingProtocolSchema)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.protocol.update' }, 'Protokoll wird aktualisiert...');
const result = await api.updateProtocol(input, userId);
logger.info({ name: 'meetings.protocol.update' }, 'Protokoll aktualisiert');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const deleteProtocol = authActionClient
.inputSchema(
z.object({
protocolId: z.string().uuid(),
accountId: z.string().uuid(),
}),
)
.action(async ({ parsedInput: input }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
logger.info({ name: 'meetings.protocol.delete' }, 'Protokoll wird gelöscht...');
await api.deleteProtocol(input.protocolId);
logger.info({ name: 'meetings.protocol.delete' }, 'Protokoll gelöscht');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true };
});
// =====================================================
// Protocol Items
// =====================================================
export const createProtocolItem = authActionClient
.inputSchema(CreateProtocolItemSchema)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.item.create' }, 'Tagesordnungspunkt wird erstellt...');
const result = await api.createItem(input, userId);
logger.info({ name: 'meetings.item.create' }, 'Tagesordnungspunkt erstellt');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const updateProtocolItem = authActionClient
.inputSchema(UpdateProtocolItemSchema)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.item.update' }, 'Tagesordnungspunkt wird aktualisiert...');
const result = await api.updateItem(input, userId);
logger.info({ name: 'meetings.item.update' }, 'Tagesordnungspunkt aktualisiert');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const updateItemStatus = authActionClient
.inputSchema(UpdateItemStatusSchema)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.item.status' }, 'Status wird geändert...');
const result = await api.updateItemStatus(input, userId);
logger.info({ name: 'meetings.item.status' }, 'Status geändert');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const deleteProtocolItem = authActionClient
.inputSchema(
z.object({
itemId: z.string().uuid(),
}),
)
.action(async ({ parsedInput: input }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
logger.info({ name: 'meetings.item.delete' }, 'Tagesordnungspunkt wird gelöscht...');
await api.deleteItem(input.itemId);
logger.info({ name: 'meetings.item.delete' }, 'Tagesordnungspunkt gelöscht');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true };
});
export const reorderProtocolItems = authActionClient
.inputSchema(ReorderItemsSchema)
.action(async ({ parsedInput: input }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
logger.info({ name: 'meetings.items.reorder' }, 'Reihenfolge wird aktualisiert...');
await api.reorderItems(input);
logger.info({ name: 'meetings.items.reorder' }, 'Reihenfolge aktualisiert');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true };
});
// =====================================================
// Attachments
// =====================================================
export const addProtocolAttachment = authActionClient
.inputSchema(
z.object({
protocolId: z.string().uuid(),
fileName: z.string().min(1),
filePath: z.string().min(1),
fileSize: z.number().int().positive(),
mimeType: z.string().min(1),
}),
)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
const userId = ctx.user.id;
logger.info({ name: 'meetings.attachment.add' }, 'Anhang wird hinzugefügt...');
const result = await api.addAttachment(
input.protocolId,
input.fileName,
input.filePath,
input.fileSize,
input.mimeType,
userId,
);
logger.info({ name: 'meetings.attachment.add' }, 'Anhang hinzugefügt');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true, data: result };
});
export const deleteProtocolAttachment = authActionClient
.inputSchema(
z.object({
attachmentId: z.string().uuid(),
}),
)
.action(async ({ parsedInput: input }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createMeetingsApi(client);
logger.info({ name: 'meetings.attachment.delete' }, 'Anhang wird gelöscht...');
await api.deleteAttachment(input.attachmentId);
logger.info({ name: 'meetings.attachment.delete' }, 'Anhang gelöscht');
revalidatePath(REVALIDATION_PATH, 'page');
return { success: true };
});