fix: QA audit — lint cleanup, i18n fixes, module visibility, sidebar UX
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 5m40s
Workflow / ⚫️ Test (push) Has been skipped

- Fix 97 lint errors → 0 (unused imports, params, variables across 40+ files)
- Fix i18n key format: colon → dot notation for next-intl compatibility
- Add missing i18n keys (routes.application, routes.home, confirm)
- Fix module visibility: sidebar now respects per-account DB features
- Fix inject function: use dot-notation keys, add collapsed:true defaults
- Fix ConfirmDialog: use useTranslations instead of hardcoded German defaults
- Fix events page: replace placeholder 'Beschreibung' with proper description
- Fix Dockerfile: add NEXT_PUBLIC_CI ARG for Docker builds
- Collapse secondary sidebar sections by default for cleaner UX
This commit is contained in:
Zaid Marzguioui
2026-04-02 14:39:20 +02:00
parent c6d564836f
commit 0bd5d0cf42
56 changed files with 387 additions and 234 deletions

View File

@@ -1,11 +1,11 @@
/**
* E2E Test: Course Enrollment
*/
import { test, expect } from '@playwright/test';
import { test } from '@playwright/test';
test.describe('Course Management', () => {
test('create course, enroll participant, check capacity, waitlist', async ({
page,
page: _page,
}) => {
// Create course with capacity 2
// Enroll participant 1 → status: enrolled
@@ -13,13 +13,13 @@ test.describe('Course Management', () => {
// Enroll participant 3 → status: waitlisted (capacity full)
});
test('course calendar view shows sessions', async ({ page }) => {
test('course calendar view shows sessions', async ({ page: _page }) => {
// Create course with sessions
// Navigate to calendar
// Verify sessions visible
});
test('attendance tracking', async ({ page }) => {
test('attendance tracking', async ({ page: _page }) => {
// Create course + session + participants
// Mark attendance
// Verify attendance persists

View File

@@ -4,7 +4,7 @@
import { test, expect } from '@playwright/test';
test.describe('Member Management', () => {
test('create member, edit, search, filter by status', async ({ page }) => {
test('create member, edit, search, filter by status', async ({ page: _page }) => {
await page.goto('/auth/sign-in');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'testpassword123');
@@ -16,14 +16,14 @@ test.describe('Member Management', () => {
});
test('application workflow: submit → review → approve → member created', async ({
page,
page: _page,
}) => {
// Submit application
// Review application
// Approve → verify member auto-created
});
test('SEPA mandate management', async ({ page }) => {
test('SEPA mandate management', async ({ page: _page }) => {
// Create member with IBAN
// Verify IBAN validation
// Create SEPA batch from dues

View File

@@ -5,7 +5,7 @@ import { test, expect } from '@playwright/test';
test.describe('Module Builder', () => {
test('create module, add fields, insert record, query, update, soft-delete', async ({
page,
page: _page,
}) => {
// Login
await page.goto('/auth/sign-in');
@@ -24,7 +24,7 @@ test.describe('Module Builder', () => {
});
test.describe('Cross-tenant isolation', () => {
test('tenant A cannot see tenant B data', async ({ page }) => {
test('tenant A cannot see tenant B data', async ({ page: _page }) => {
// Login as tenant A user
// Verify can see own modules
// Verify cannot access tenant B module URL

View File

@@ -1,11 +1,11 @@
/**
* E2E Test: Newsletter
*/
import { test, expect } from '@playwright/test';
import { test } from '@playwright/test';
test.describe('Newsletter', () => {
test('create campaign, select recipients from members, preview, send', async ({
page,
page: _page,
}) => {
// Create newsletter
// Add recipients from member filter (status=active, hasEmail=true)
@@ -14,7 +14,7 @@ test.describe('Newsletter', () => {
// Verify sent_count
});
test('template variable substitution works', async ({ page }) => {
test('template variable substitution works', async ({ page: _page }) => {
// Create template with {{first_name}} {{member_number}}
// Create newsletter from template
// Preview — verify variables replaced

View File

@@ -1,11 +1,11 @@
/**
* E2E Test: SEPA Batch Processing
*/
import { test, expect } from '@playwright/test';
import { test } from '@playwright/test';
test.describe('SEPA / Finance', () => {
test('create SEPA direct debit batch, add items, generate XML', async ({
page,
page: _page,
}) => {
// Create batch
// Add items with valid IBANs
@@ -14,12 +14,12 @@ test.describe('SEPA / Finance', () => {
// Verify amounts sum correctly
});
test('IBAN validation rejects invalid IBANs', async ({ page }) => {
test('IBAN validation rejects invalid IBANs', async ({ page: _page }) => {
// Try to add item with invalid IBAN
// Verify rejection
});
test('invoice creation with line items', async ({ page }) => {
test('invoice creation with line items', async ({ page: _page }) => {
// Create invoice
// Add 3 line items
// Verify subtotal, tax, total calculations

View File

@@ -10,7 +10,7 @@ interface Props {
}
export default async function NewsletterSubscribePage({ params }: Props) {
const { slug } = await params;
const { slug: _slug } = await params;
return (
<div className="bg-muted/30 flex min-h-screen items-center justify-center p-6">

View File

@@ -5,7 +5,7 @@ import { useCallback, useEffect, useState } from 'react';
import type { Provider, UserIdentity } from '@supabase/supabase-js';
import { createClient } from '@supabase/supabase-js';
import { Link2, Link2Off, Loader2 } from 'lucide-react';
import { Link2Off, Loader2 } from 'lucide-react';
import { useTranslations } from 'next-intl';
import {

View File

@@ -1,5 +1,3 @@
import Link from 'next/link';
import { Plus, Users } from 'lucide-react';
import { getTranslations } from 'next-intl/server';

View File

@@ -7,7 +7,6 @@ import {
Plus,
Users,
Calendar,
Euro,
} from 'lucide-react';
import { getTranslations } from 'next-intl/server';

View File

@@ -114,9 +114,9 @@ async function generateMemberCards(
Text,
StyleSheet,
renderToBuffer,
Svg,
Rect,
Circle,
Svg: _Svg,
Rect: _Rect,
Circle: _Circle,
} = await import('@react-pdf/renderer');
// — Brand colors (configurable later via account settings) —

View File

@@ -1,5 +1,3 @@
import Link from 'next/link';
import { FileText, Plus } from 'lucide-react';
import { getTranslations } from 'next-intl/server';

View File

@@ -96,36 +96,37 @@ function injectAccountFeatureRoutes(
if (features.fischerei) {
featureGroups.push({
label: 'common:routes.fisheriesManagement',
label: 'common.routes.fisheriesManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.fisheriesOverview',
label: 'common.routes.fisheriesOverview',
path: `/home/${account}/fischerei`,
Icon: <Fish className={iconClasses} />,
},
{
label: 'common:routes.fisheriesWaters',
label: 'common.routes.fisheriesWaters',
path: `/home/${account}/fischerei/waters`,
Icon: <Waves className={iconClasses} />,
},
{
label: 'common:routes.fisheriesLeases',
label: 'common.routes.fisheriesLeases',
path: `/home/${account}/fischerei/leases`,
Icon: <Anchor className={iconClasses} />,
},
{
label: 'common:routes.fisheriesCatchBooks',
label: 'common.routes.fisheriesCatchBooks',
path: `/home/${account}/fischerei/catch-books`,
Icon: <BookOpen className={iconClasses} />,
},
{
label: 'common:routes.fisheriesPermits',
label: 'common.routes.fisheriesPermits',
path: `/home/${account}/fischerei/permits`,
Icon: <ShieldCheck className={iconClasses} />,
},
{
label: 'common:routes.fisheriesCompetitions',
label: 'common.routes.fisheriesCompetitions',
path: `/home/${account}/fischerei/competitions`,
Icon: <Trophy className={iconClasses} />,
},
@@ -135,21 +136,22 @@ function injectAccountFeatureRoutes(
if (features.meetings) {
featureGroups.push({
label: 'common:routes.meetingProtocols',
label: 'common.routes.meetingProtocols',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.meetingsOverview',
label: 'common.routes.meetingsOverview',
path: `/home/${account}/meetings`,
Icon: <BookMarked className={iconClasses} />,
},
{
label: 'common:routes.meetingsProtocols',
label: 'common.routes.meetingsProtocols',
path: `/home/${account}/meetings/protocols`,
Icon: <ScrollText className={iconClasses} />,
},
{
label: 'common:routes.meetingsTasks',
label: 'common.routes.meetingsTasks',
path: `/home/${account}/meetings/tasks`,
Icon: <ListChecks className={iconClasses} />,
},
@@ -159,36 +161,37 @@ function injectAccountFeatureRoutes(
if (features.verband) {
featureGroups.push({
label: 'common:routes.associationManagement',
label: 'common.routes.associationManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.associationOverview',
label: 'common.routes.associationOverview',
path: `/home/${account}/verband`,
Icon: <Building2 className={iconClasses} />,
},
{
label: 'common:routes.associationHierarchy',
label: 'common.routes.associationHierarchy',
path: `/home/${account}/verband/hierarchy`,
Icon: <Network className={iconClasses} />,
},
{
label: 'common:routes.associationMemberSearch',
label: 'common.routes.associationMemberSearch',
path: `/home/${account}/verband/members`,
Icon: <SearchCheck className={iconClasses} />,
},
{
label: 'common:routes.associationEvents',
label: 'common.routes.associationEvents',
path: `/home/${account}/verband/events`,
Icon: <Share2 className={iconClasses} />,
},
{
label: 'common:routes.associationReporting',
label: 'common.routes.associationReporting',
path: `/home/${account}/verband/reporting`,
Icon: <PieChart className={iconClasses} />,
},
{
label: 'common:routes.associationTemplates',
label: 'common.routes.associationTemplates',
path: `/home/${account}/verband/templates`,
Icon: <LayoutTemplate className={iconClasses} />,
},
@@ -222,7 +225,7 @@ async function SidebarLayout({
redirect('/');
}
const baseConfig = getTeamAccountSidebarConfig(account);
const baseConfig = getTeamAccountSidebarConfig(account, features);
const config = injectAccountFeatureRoutes(baseConfig, account, features);
const accounts = data.accounts.map(({ name, slug, picture_url }) => ({
@@ -275,7 +278,7 @@ async function HeaderLayout({
getAccountFeatures(account),
]);
const baseConfig = getTeamAccountSidebarConfig(account);
const baseConfig = getTeamAccountSidebarConfig(account, features);
const config = injectAccountFeatureRoutes(baseConfig, account, features);
const accounts = data.accounts.map(({ name, slug, picture_url }) => ({

View File

@@ -62,7 +62,6 @@ export function InvitationsView({
invitations,
members,
accountId,
account,
}: InvitationsViewProps) {
const t = useTranslations('members');
const router = useRouter();

View File

@@ -1,4 +1,4 @@
import { Link2, List } from 'lucide-react';
import { List } from 'lucide-react';
import { createModuleBuilderApi } from '@kit/module-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';

View File

@@ -1,4 +1,3 @@
import Link from 'next/link';
import { FileText, Plus } from 'lucide-react';
import { getTranslations } from 'next-intl/server';

View File

@@ -17,7 +17,7 @@ import { createSiteBuilderApi } from '@kit/site-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@kit/ui/card';
import { Card, CardContent } from '@kit/ui/card';
import { cn } from '@kit/ui/utils';
import { AccountNotFound } from '~/components/account-not-found';

View File

@@ -1,5 +1,3 @@
import Link from 'next/link';
import { Plus } from 'lucide-react';
import { getTranslations } from 'next-intl/server';
@@ -15,7 +13,6 @@ import { createSiteBuilderApi } from '@kit/site-builder/api';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { Badge } from '@kit/ui/badge';
import { Button } from '@kit/ui/button';
import { Card, CardContent } from '@kit/ui/card';
import { AccountNotFound } from '~/components/account-not-found';
import { CmsPageShell } from '~/components/cms-page-shell';

View File

@@ -26,7 +26,7 @@ async function getSupabaseHealthCheck() {
try {
const client = getSupabaseServerAdminClient();
const { data, error } = await client
const { data: _data, error } = await client
.from('config')
.select('billing_provider')
.limit(1)

View File

@@ -4,5 +4,5 @@ import { Noto_Serif } from 'next/font/google';
const notoSerif = Noto_Serif({ subsets: ['latin'], variable: '--font-serif' });
export default function RootLayout({ children }: React.PropsWithChildren) {
return children;
return <div className={notoSerif.variable}>{children}</div>;
}

View File

@@ -1,5 +1,7 @@
'use client';
import { useTranslations } from 'next-intl';
import {
AlertDialog,
AlertDialogAction,
@@ -26,11 +28,15 @@ export function ConfirmDialog({
trigger,
title,
description,
confirmLabel = 'Bestätigen',
cancelLabel = 'Abbrechen',
confirmLabel,
cancelLabel,
variant = 'default',
onConfirm,
}: ConfirmDialogProps) {
const t = useTranslations('common');
const resolvedConfirmLabel = confirmLabel ?? t('confirm');
const resolvedCancelLabel = cancelLabel ?? t('cancel');
return (
<AlertDialog>
<AlertDialogTrigger
@@ -42,7 +48,7 @@ export function ConfirmDialog({
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{cancelLabel}</AlertDialogCancel>
<AlertDialogCancel>{resolvedCancelLabel}</AlertDialogCancel>
<AlertDialogAction
onClick={onConfirm}
className={
@@ -51,7 +57,7 @@ export function ConfirmDialog({
: ''
}
>
{confirmLabel}
{resolvedConfirmLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -40,6 +40,24 @@ import {
PanelTop,
Newspaper,
Palette,
// Fisheries
Fish,
Waves,
Anchor,
BookOpen,
ShieldCheck,
Trophy,
// Meetings
BookMarked,
ListChecks,
ScrollText,
// Association (Verband)
Building2,
Network,
SearchCheck,
Share2,
PieChart,
LayoutTemplate,
// Modules
Database,
} from 'lucide-react';
@@ -51,7 +69,7 @@ import pathsConfig from '~/config/paths.config';
const iconClasses = 'w-4';
const getRoutes = (account: string) => {
const getRoutes = (account: string, accountFeatures?: Record<string, boolean>) => {
const routes: Array<
| {
label: string;
@@ -71,10 +89,10 @@ const getRoutes = (account: string) => {
> = [
// ── Dashboard ──
{
label: 'common:routes.dashboard',
label: 'common.routes.dashboard',
children: [
{
label: 'common:routes.dashboard',
label: 'common.routes.dashboard',
path: pathsConfig.app.accountHome.replace('[account]', account),
Icon: <LayoutDashboard className={iconClasses} />,
highlightMatch: `${pathsConfig.app.home}$`,
@@ -94,12 +112,12 @@ const getRoutes = (account: string) => {
if (featureFlagsConfig.enableMemberManagement) {
peopleChildren.push(
{
label: 'common:routes.clubMembers',
label: 'common.routes.clubMembers',
path: createPath(pathsConfig.app.accountCmsMembers, account),
Icon: <UserCheck className={iconClasses} />,
},
{
label: 'common:routes.memberApplications',
label: 'common.routes.memberApplications',
path: createPath(
pathsConfig.app.accountCmsMembers + '/applications',
account,
@@ -108,7 +126,7 @@ const getRoutes = (account: string) => {
},
// NOTE: memberPortal page does not exist yet — nav entry commented out until built
// {
// label: 'common:routes.memberPortal',
// label: 'common.routes.memberPortal',
// path: createPath(
// pathsConfig.app.accountCmsMembers + '/portal',
// account,
@@ -116,7 +134,7 @@ const getRoutes = (account: string) => {
// Icon: <KeyRound className={iconClasses} />,
// },
{
label: 'common:routes.memberCards',
label: 'common.routes.memberCards',
path: createPath(
pathsConfig.app.accountCmsMembers + '/cards',
account,
@@ -124,7 +142,7 @@ const getRoutes = (account: string) => {
Icon: <IdCard className={iconClasses} />,
},
{
label: 'common:routes.memberDues',
label: 'common.routes.memberDues',
path: createPath(
pathsConfig.app.accountCmsMembers + '/dues',
account,
@@ -136,13 +154,13 @@ const getRoutes = (account: string) => {
// Admin users who can log in — always visible
peopleChildren.push({
label: 'common:routes.accessAndRoles',
label: 'common.routes.accessAndRoles',
path: createPath(pathsConfig.app.accountMembers, account),
Icon: <UserCog className={iconClasses} />,
});
routes.push({
label: 'common:routes.people',
label: 'common.routes.people',
collapsible: true,
children: peopleChildren,
});
@@ -151,16 +169,16 @@ const getRoutes = (account: string) => {
// ── Courses ──
if (featureFlagsConfig.enableCourseManagement) {
routes.push({
label: 'common:routes.courseManagement',
label: 'common.routes.courseManagement',
collapsible: true,
children: [
{
label: 'common:routes.courseList',
label: 'common.routes.courseList',
path: createPath(pathsConfig.app.accountCourses, account),
Icon: <GraduationCap className={iconClasses} />,
},
{
label: 'common:routes.courseCalendar',
label: 'common.routes.courseCalendar',
path: createPath(
pathsConfig.app.accountCourses + '/calendar',
account,
@@ -168,7 +186,7 @@ const getRoutes = (account: string) => {
Icon: <CalendarDays className={iconClasses} />,
},
{
label: 'common:routes.courseInstructors',
label: 'common.routes.courseInstructors',
path: createPath(
pathsConfig.app.accountCourses + '/instructors',
account,
@@ -176,7 +194,7 @@ const getRoutes = (account: string) => {
Icon: <UserRound className={iconClasses} />,
},
{
label: 'common:routes.courseLocations',
label: 'common.routes.courseLocations',
path: createPath(
pathsConfig.app.accountCourses + '/locations',
account,
@@ -189,21 +207,21 @@ const getRoutes = (account: string) => {
// ── Events ──
routes.push({
label: 'common:routes.eventManagement',
label: 'common.routes.eventManagement',
collapsible: true,
children: [
{
label: 'common:routes.eventList',
label: 'common.routes.eventList',
path: createPath('/home/[account]/events', account),
Icon: <CalendarHeart className={iconClasses} />,
},
{
label: 'common:routes.eventRegistrations',
label: 'common.routes.eventRegistrations',
path: createPath('/home/[account]/events/registrations', account),
Icon: <Ticket className={iconClasses} />,
},
{
label: 'common:routes.holidayPasses',
label: 'common.routes.holidayPasses',
path: createPath('/home/[account]/events/holiday-passes', account),
Icon: <PartyPopper className={iconClasses} />,
},
@@ -213,16 +231,17 @@ const getRoutes = (account: string) => {
// ── Bookings ──
if (featureFlagsConfig.enableBookingManagement) {
routes.push({
label: 'common:routes.bookingManagement',
label: 'common.routes.bookingManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.bookingList',
label: 'common.routes.bookingList',
path: createPath(pathsConfig.app.accountBookings, account),
Icon: <Hotel className={iconClasses} />,
},
{
label: 'common:routes.bookingCalendar',
label: 'common.routes.bookingCalendar',
path: createPath(
pathsConfig.app.accountBookings + '/calendar',
account,
@@ -230,12 +249,12 @@ const getRoutes = (account: string) => {
Icon: <CalendarRange className={iconClasses} />,
},
{
label: 'common:routes.bookingRooms',
label: 'common.routes.bookingRooms',
path: createPath(pathsConfig.app.accountBookings + '/rooms', account),
Icon: <BedDouble className={iconClasses} />,
},
{
label: 'common:routes.bookingGuests',
label: 'common.routes.bookingGuests',
path: createPath(
pathsConfig.app.accountBookings + '/guests',
account,
@@ -249,16 +268,17 @@ const getRoutes = (account: string) => {
// ── Finance ──
if (featureFlagsConfig.enableSepaPayments) {
routes.push({
label: 'common:routes.financeManagement',
label: 'common.routes.financeManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.financeOverview',
label: 'common.routes.financeOverview',
path: createPath(pathsConfig.app.accountFinance, account),
Icon: <Wallet className={iconClasses} />,
},
{
label: 'common:routes.financeInvoices',
label: 'common.routes.financeInvoices',
path: createPath(
pathsConfig.app.accountFinance + '/invoices',
account,
@@ -266,12 +286,12 @@ const getRoutes = (account: string) => {
Icon: <Receipt className={iconClasses} />,
},
{
label: 'common:routes.financeSepa',
label: 'common.routes.financeSepa',
path: createPath(pathsConfig.app.accountFinance + '/sepa', account),
Icon: <Landmark className={iconClasses} />,
},
{
label: 'common:routes.financePayments',
label: 'common.routes.financePayments',
path: createPath(
pathsConfig.app.accountFinance + '/payments',
account,
@@ -285,16 +305,17 @@ const getRoutes = (account: string) => {
// ── Documents ──
if (featureFlagsConfig.enableDocumentGeneration) {
routes.push({
label: 'common:routes.documentManagement',
label: 'common.routes.documentManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.documentOverview',
label: 'common.routes.documentOverview',
path: createPath(pathsConfig.app.accountDocuments, account),
Icon: <FileText className={iconClasses} />,
},
{
label: 'common:routes.documentGenerate',
label: 'common.routes.documentGenerate',
path: createPath(
pathsConfig.app.accountDocuments + '/generate',
account,
@@ -302,7 +323,7 @@ const getRoutes = (account: string) => {
Icon: <FilePlus className={iconClasses} />,
},
{
label: 'common:routes.documentTemplates',
label: 'common.routes.documentTemplates',
path: createPath(
pathsConfig.app.accountDocuments + '/templates',
account,
@@ -310,7 +331,7 @@ const getRoutes = (account: string) => {
Icon: <FileStack className={iconClasses} />,
},
{
label: 'common:routes.files',
label: 'common.routes.files',
path: createPath(pathsConfig.app.accountFiles, account),
Icon: <FolderOpen className={iconClasses} />,
},
@@ -321,21 +342,22 @@ const getRoutes = (account: string) => {
// ── Newsletter ──
if (featureFlagsConfig.enableNewsletter) {
routes.push({
label: 'common:routes.newsletterManagement',
label: 'common.routes.newsletterManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.newsletterCampaigns',
label: 'common.routes.newsletterCampaigns',
path: createPath(pathsConfig.app.accountNewsletter, account),
Icon: <Mail className={iconClasses} />,
},
{
label: 'common:routes.newsletterNew',
label: 'common.routes.newsletterNew',
path: createPath(pathsConfig.app.accountNewsletter + '/new', account),
Icon: <MailPlus className={iconClasses} />,
},
{
label: 'common:routes.newsletterTemplates',
label: 'common.routes.newsletterTemplates',
path: createPath(
pathsConfig.app.accountNewsletter + '/templates',
account,
@@ -349,16 +371,17 @@ const getRoutes = (account: string) => {
// ── Site Builder ──
if (featureFlagsConfig.enableSiteBuilder) {
routes.push({
label: 'common:routes.siteBuilder',
label: 'common.routes.siteBuilder',
collapsible: true,
collapsed: true,
children: [
{
label: 'common:routes.sitePages',
label: 'common.routes.sitePages',
path: createPath(pathsConfig.app.accountSiteBuilder, account),
Icon: <PanelTop className={iconClasses} />,
},
{
label: 'common:routes.sitePosts',
label: 'common.routes.sitePosts',
path: createPath(
pathsConfig.app.accountSiteBuilder + '/posts',
account,
@@ -366,7 +389,7 @@ const getRoutes = (account: string) => {
Icon: <Newspaper className={iconClasses} />,
},
{
label: 'common:routes.siteSettings',
label: 'common.routes.siteSettings',
path: createPath(
pathsConfig.app.accountSiteBuilder + '/settings',
account,
@@ -377,58 +400,189 @@ const getRoutes = (account: string) => {
});
}
// Note: Fischerei, Meetings, and Verband sections are injected at runtime
// via injectAccountFeatureRoutes() in the layout, based on per-account
// settings (account_settings.features). They are NOT added here to avoid
// duplicate entries when both the global feature flag and per-account
// setting are enabled.
// ── Custom Modules ──
if (featureFlagsConfig.enableModuleBuilder) {
routes.push({
label: 'common.routes.customModules',
collapsible: true,
collapsed: true,
children: [
{
label: 'common.routes.moduleList',
path: createPath(pathsConfig.app.accountModules, account),
Icon: <Database className={iconClasses} />,
},
],
});
}
// ── Fisheries ──
if (featureFlagsConfig.enableFischerei && (accountFeatures?.fischerei !== false)) {
routes.push({
label: 'common.routes.fisheriesManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common.routes.fisheriesOverview',
path: createPath(pathsConfig.app.accountFischerei, account),
Icon: <Fish className={iconClasses} />,
},
{
label: 'common.routes.fisheriesWaters',
path: createPath(
pathsConfig.app.accountFischerei + '/waters',
account,
),
Icon: <Waves className={iconClasses} />,
},
{
label: 'common.routes.fisheriesLeases',
path: createPath(
pathsConfig.app.accountFischerei + '/leases',
account,
),
Icon: <Anchor className={iconClasses} />,
},
{
label: 'common.routes.fisheriesCatchBooks',
path: createPath(
pathsConfig.app.accountFischerei + '/catch-books',
account,
),
Icon: <BookOpen className={iconClasses} />,
},
{
label: 'common.routes.fisheriesPermits',
path: createPath(
pathsConfig.app.accountFischerei + '/permits',
account,
),
Icon: <ShieldCheck className={iconClasses} />,
},
{
label: 'common.routes.fisheriesCompetitions',
path: createPath(
pathsConfig.app.accountFischerei + '/competitions',
account,
),
Icon: <Trophy className={iconClasses} />,
},
],
});
}
// ── Meeting Protocols ──
if (featureFlagsConfig.enableMeetingProtocols && (accountFeatures?.meetings !== false)) {
routes.push({
label: 'common.routes.meetingProtocols',
collapsible: true,
collapsed: true,
children: [
{
label: 'common.routes.meetingsOverview',
path: createPath(pathsConfig.app.accountMeetings, account),
Icon: <BookMarked className={iconClasses} />,
},
{
label: 'common.routes.meetingsProtocols',
path: createPath(
pathsConfig.app.accountMeetings + '/protocols',
account,
),
Icon: <ScrollText className={iconClasses} />,
},
{
label: 'common.routes.meetingsTasks',
path: createPath(pathsConfig.app.accountMeetings + '/tasks', account),
Icon: <ListChecks className={iconClasses} />,
},
],
});
}
// ── Association Management (Verband) ──
if (featureFlagsConfig.enableVerbandsverwaltung && (accountFeatures?.verband !== false)) {
routes.push({
label: 'common.routes.associationManagement',
collapsible: true,
collapsed: true,
children: [
{
label: 'common.routes.associationOverview',
path: createPath(pathsConfig.app.accountVerband, account),
Icon: <Building2 className={iconClasses} />,
},
{
label: 'common.routes.associationHierarchy',
path: createPath(
pathsConfig.app.accountVerband + '/hierarchy',
account,
),
Icon: <Network className={iconClasses} />,
},
{
label: 'common.routes.associationMemberSearch',
path: createPath(
pathsConfig.app.accountVerband + '/members',
account,
),
Icon: <SearchCheck className={iconClasses} />,
},
{
label: 'common.routes.associationEvents',
path: createPath(pathsConfig.app.accountVerband + '/events', account),
Icon: <Share2 className={iconClasses} />,
},
{
label: 'common.routes.associationReporting',
path: createPath(
pathsConfig.app.accountVerband + '/reporting',
account,
),
Icon: <PieChart className={iconClasses} />,
},
{
label: 'common.routes.associationTemplates',
path: createPath(
pathsConfig.app.accountVerband + '/templates',
account,
),
Icon: <LayoutTemplate className={iconClasses} />,
},
],
});
}
// ── Administration ──
{
const adminChildren: Array<
| {
label: string;
path: string;
Icon: React.ReactNode;
}
| undefined
> = [
routes.push({
label: 'common.routes.administration',
collapsible: false,
children: [
{
label: 'common:routes.accountSettings',
label: 'common.routes.accountSettings',
path: createPath(pathsConfig.app.accountSettings, account),
Icon: <Settings className={iconClasses} />,
},
];
if (featureFlagsConfig.enableModuleBuilder) {
adminChildren.push({
label: 'common:routes.moduleList',
path: createPath(pathsConfig.app.accountModules, account),
Icon: <Database className={iconClasses} />,
});
}
if (featureFlagsConfig.enableTeamAccountBilling) {
adminChildren.push({
label: 'common:routes.billing',
path: createPath(pathsConfig.app.accountBilling, account),
Icon: <CreditCard className={iconClasses} />,
});
}
routes.push({
label: 'common:routes.administration',
collapsible: false,
children: adminChildren,
});
}
featureFlagsConfig.enableTeamAccountBilling
? {
label: 'common.routes.billing',
path: createPath(pathsConfig.app.accountBilling, account),
Icon: <CreditCard className={iconClasses} />,
}
: undefined,
],
});
return routes;
};
export function getTeamAccountSidebarConfig(account: string) {
export function getTeamAccountSidebarConfig(
account: string,
accountFeatures?: Record<string, boolean>,
) {
return NavigationConfigSchema.parse({
routes: getRoutes(account),
routes: getRoutes(account, accountFeatures),
style: process.env.NEXT_PUBLIC_TEAM_NAVIGATION_STYLE,
sidebarCollapsed: process.env.NEXT_PUBLIC_TEAM_SIDEBAR_COLLAPSED,
sidebarCollapsedStyle: process.env.NEXT_PUBLIC_SIDEBAR_COLLAPSIBLE_STYLE,

View File

@@ -170,7 +170,7 @@
},
"events": {
"title": "Veranstaltungen",
"description": "Beschreibung",
"description": "Veranstaltungen planen, verwalten und Anmeldungen erfassen",
"newEvent": "Neue Veranstaltung",
"registrations": "Anmeldungen",
"holidayPasses": "Ferienpässe",

View File

@@ -137,7 +137,9 @@
"associationReporting": "Berichte",
"associationTemplates": "Geteilte Vorlagen",
"administration": "Administration",
"accountSettings": "Kontoeinstellungen"
"accountSettings": "Kontoeinstellungen",
"application": "Anwendung",
"home": "Startseite"
},
"roles": {
"owner": {
@@ -219,5 +221,6 @@
"title": "Konto nicht gefunden",
"description": "Das angeforderte Konto existiert nicht oder Sie haben keine Berechtigung darauf zuzugreifen.",
"action": "Zum Dashboard"
}
},
"confirm": "Bestätigen"
}

View File

@@ -170,7 +170,7 @@
},
"events": {
"title": "Events",
"description": "Description",
"description": "Plan, manage, and track event registrations",
"newEvent": "New Event",
"registrations": "Registrations",
"holidayPasses": "Holiday Passes",

View File

@@ -138,7 +138,8 @@
"associationReporting": "Reports",
"associationTemplates": "Shared Templates",
"administration": "Administration",
"accountSettings": "Account Settings"
"accountSettings": "Account Settings",
"application": "Application"
},
"roles": {
"owner": {
@@ -220,5 +221,6 @@
"title": "Account not found",
"description": "The requested account does not exist or you do not have permission to access it.",
"action": "Go to Dashboard"
}
},
"confirm": "Confirm"
}