fix: QA audit — lint cleanup, i18n fixes, module visibility, sidebar UX
- 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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user