bunker-admin 900a0affe5 Add CRM activity enrichment, notification bridging, crash-safe scheduled jobs, and quick wins
Workstream A — CRM & Notifications:
- Add fire-and-forget CRM activity helper (api/src/utils/crm-activity.ts) hooked into
  campaign email, canvass visit, donation, and purchase write sites
- Add 5 operational NotificationType enum values (shift_signup_confirmed, shift_reminder,
  shift_cancelled, canvass_session_summary, reengagement) via Prisma migration
- Bridge notification email queue to in-app notifications for volunteer-facing events
- Extend TYPE_TO_PREF map and NotificationsPage labels for new types

Workstream B — Quick Wins:
- Extract shared role constants (11 roles) to admin/src/utils/role-constants.ts,
  update 4 consuming pages
- Add Ad Analytics sidebar entry in payments submenu
- Gate 6 calendar routes with enableSocialCalendar feature flag
- Add GET /series/:id/count endpoint and fix hardcoded shiftsCount={0} in ShiftsPage
- Add influenceCampaignId to Order model for donation-campaign attribution,
  wire through Stripe checkout metadata

Workstream C — Crash-Safe Scheduled Jobs:
- Create BullMQ scheduled-jobs queue with 10 repeatable job types replacing
  setInterval blocks in server.ts (dynamic imports, concurrency: 2)
- Keep presenceService (1min) and challengeScoringService (5min) as setInterval

Bunker Admin
2026-03-09 14:15:30 -06:00

1002 lines
40 KiB
TypeScript

import { useEffect } from 'react';
import { App as AntApp, ConfigProvider, theme, Spin } from 'antd';
import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom';
import { useAuthStore } from '@/stores/auth.store';
import { useSettingsStore } from '@/stores/settings.store';
import ProtectedRoute from '@/components/ProtectedRoute';
import FeatureGate from '@/components/FeatureGate';
import AppLayout from '@/components/AppLayout';
import PublicLayout from '@/components/PublicLayout';
import VolunteerLayout from '@/components/VolunteerLayout';
import MediaPublicLayout from '@/components/MediaPublicLayout';
import LoginPage from '@/pages/LoginPage';
import DashboardPage from '@/pages/DashboardPage';
import UsersPage from '@/pages/UsersPage';
import CampaignsPage from '@/pages/CampaignsPage';
import RepresentativesPage from '@/pages/RepresentativesPage';
import EmailQueuePage from '@/pages/EmailQueuePage';
import EmailTemplatesPage from '@/pages/EmailTemplatesPage';
import ResponsesPage from '@/pages/ResponsesPage';
import LocationsPage from '@/pages/LocationsPage';
import DataQualityDashboardPage from '@/pages/DataQualityDashboardPage';
import CutsPage from '@/pages/CutsPage';
import ShiftsPage from '@/pages/ShiftsPage';
import MapSettingsPage from '@/pages/MapSettingsPage';
import CutExportPage from '@/pages/CutExportPage';
import CanvassDashboardPage from '@/pages/CanvassDashboardPage';
import ListmonkPage from '@/pages/ListmonkPage';
import LandingPagesPage from '@/pages/LandingPagesPage';
import DocsPage from '@/pages/DocsPage';
import MkDocsSettingsPage from '@/pages/MkDocsSettingsPage';
import CodeEditorPage from '@/pages/CodeEditorPage';
import NocoDBPage from '@/pages/NocoDBPage';
import N8nPage from '@/pages/N8nPage';
import GiteaPage from '@/pages/GiteaPage';
import MailHogPage from '@/pages/MailHogPage';
import MiniQRPage from '@/pages/MiniQRPage';
import ExcalidrawPage from '@/pages/ExcalidrawPage';
import VaultwardenPage from '@/pages/VaultwardenPage';
import RocketChatPage from '@/pages/RocketChatPage';
import GancioPage from '@/pages/GancioPage';
import JitsiMeetPage from '@/pages/JitsiMeetPage';
import SettingsPage from '@/pages/SettingsPage';
import NavigationSettingsPage from '@/pages/NavigationSettingsPage';
import PangolinPage from '@/pages/PangolinPage';
import ObservabilityPage from '@/pages/ObservabilityPage';
import DocsAnalyticsPage from '@/pages/DocsAnalyticsPage';
import DocsCommentsPage from '@/pages/DocsCommentsPage';
import PaymentsDashboardPage from '@/pages/payments/PaymentsDashboardPage';
import SubscribersPage from '@/pages/payments/SubscribersPage';
import PaymentProductsPage from '@/pages/payments/ProductsPage';
import PaymentDonationsPage from '@/pages/payments/DonationsPage';
import DonationPagesPage from '@/pages/payments/DonationPagesPage';
import PlansPage from '@/pages/payments/PlansPage';
import PaymentSettingsPage from '@/pages/payments/PaymentSettingsPage';
import LibraryPage from '@/pages/media/LibraryPage';
import AnalyticsDashboardPage from '@/pages/media/AnalyticsDashboardPage';
import MediaJobsPage from '@/pages/media/MediaJobsPage';
import CommentModerationPage from '@/pages/media/CommentModerationPage';
import GalleryAdsPage from '@/pages/media/GalleryAdsPage';
import AdAnalyticsDashboardPage from '@/pages/media/AdAnalyticsDashboardPage';
import CampaignModerationPage from '@/pages/influence/CampaignModerationPage';
import CampaignEffectivenessPage from '@/pages/influence/CampaignEffectivenessPage';
import PublicLandingPage from '@/pages/public/LandingPage';
import PagesIndexPage from '@/pages/public/PagesIndexPage';
import EventsPage from '@/pages/public/EventsPage';
import HomePage from '@/pages/public/HomePage';
import CampaignsListPage from '@/pages/public/CampaignsListPage';
import CampaignPage from '@/pages/public/CampaignPage';
import CreateCampaignPage from '@/pages/public/CreateCampaignPage';
import MyCampaignsPage from '@/pages/public/MyCampaignsPage';
import ResponseWallPage from '@/pages/public/ResponseWallPage';
import MapPage from '@/pages/public/MapPage';
import PublicShiftsPage from '@/pages/public/ShiftsPage';
import MediaGalleryPage from '@/pages/public/MediaGalleryPage';
import ShortsPage from '@/pages/public/ShortsPage';
import MediaViewerPage from '@/pages/public/MediaViewerPage';
import PlaylistBrowsePage from '@/pages/public/PlaylistBrowsePage';
import PlaylistViewerPage from '@/pages/public/PlaylistViewerPage';
import PlaylistManagementPage from '@/pages/media/PlaylistManagementPage';
import MyStatsPage from '@/pages/public/MyStatsPage';
import MySettingsPage from '@/pages/public/MySettingsPage';
import VolunteerChatPage from '@/pages/volunteer/VolunteerChatPage';
import PricingPage from '@/pages/public/PricingPage';
import ShopPage from '@/pages/public/ShopPage';
import ProductDetailPage from '@/pages/public/ProductDetailPage';
import PlanDetailPage from '@/pages/public/PlanDetailPage';
import DonatePage from '@/pages/public/DonatePage';
import DonationPagesListPage from '@/pages/public/DonationPagesListPage';
import PaymentSuccessPage from '@/pages/public/PaymentSuccessPage';
import MyActivityPage from '@/pages/volunteer/MyActivityPage';
import VolunteerShiftsPage from '@/pages/volunteer/VolunteerShiftsPage';
import MyRoutesPage from '@/pages/volunteer/MyRoutesPage';
import VolunteerMapPage from '@/pages/volunteer/VolunteerMapPage';
import FriendsPage from '@/pages/volunteer/FriendsPage';
import SocialProfilePage from '@/pages/volunteer/SocialProfilePage';
import NotificationsPage from '@/pages/volunteer/NotificationsPage';
import SocialFeedPage from '@/pages/volunteer/SocialFeedPage';
import DiscoverPage from '@/pages/volunteer/DiscoverPage';
import GroupDetailPage from '@/pages/volunteer/GroupDetailPage';
import AchievementsPage from '@/pages/volunteer/AchievementsPage';
import {
ADMIN_ROLES,
INFLUENCE_ROLES,
BROADCAST_ROLES,
CONTENT_ROLES,
MAP_ROLES,
SCHEDULING_ROLES,
MEDIA_ROLES,
PAYMENTS_ROLES,
EVENTS_ROLES,
SOCIAL_ROLES,
SYSTEM_ROLES,
} from '@/types/api';
import { isAdmin } from '@/utils/roles';
import QuickJoinPage from '@/pages/public/QuickJoinPage';
import VerifyEmailPage from '@/pages/VerifyEmailPage';
import ResetPasswordPage from '@/pages/ResetPasswordPage';
import SmsDashboardPage from '@/pages/sms/SmsDashboardPage';
import SmsContactsPage from '@/pages/sms/SmsContactsPage';
import SmsCampaignsPage from '@/pages/sms/SmsCampaignsPage';
import SmsConversationsPage from '@/pages/sms/SmsConversationsPage';
import SmsTemplatesPage from '@/pages/sms/SmsTemplatesPage';
import SmsSetupPage from '@/pages/sms/SmsSetupPage';
import PeoplePage from '@/pages/PeoplePage';
import ContactProfilePage from '@/pages/public/ContactProfilePage';
import SocialDashboardPage from '@/pages/social/SocialDashboardPage';
import SocialGraphPage from '@/pages/social/SocialGraphPage';
import SocialModerationPage from '@/pages/social/SocialModerationPage';
import ReferralAdminPage from '@/pages/social/ReferralAdminPage';
import SpotlightAdminPage from '@/pages/social/SpotlightAdminPage';
import ChallengesAdminPage from '@/pages/social/ChallengesAdminPage';
import ImpactStoriesPage from '@/pages/influence/ImpactStoriesPage';
import ReferralsPage from '@/pages/volunteer/ReferralsPage';
import ChallengesPage from '@/pages/volunteer/ChallengesPage';
import ChallengeDetailPage from '@/pages/volunteer/ChallengeDetailPage';
import WallOfFamePage from '@/pages/public/WallOfFamePage';
import MeetingJoinPage from '@/pages/public/MeetingJoinPage';
import MeetingPlannerPage from '@/pages/MeetingPlannerPage';
import SchedulingPollPage from '@/pages/public/SchedulingPollPage';
import PollsListPage from '@/pages/public/PollsListPage';
import JitsiAuthPage from '@/pages/JitsiAuthPage';
import SchedulingCalendarPage from '@/pages/SchedulingCalendarPage';
import AdminCalendarViewPage from '@/pages/AdminCalendarViewPage';
import TicketedEventsPage from '@/pages/events/TicketedEventsPage';
import EventDetailPage from '@/pages/events/EventDetailPage';
import CheckInScannerPage from '@/pages/events/CheckInScannerPage';
import TicketedEventDetailPage from '@/pages/public/TicketedEventDetailPage';
import TicketConfirmationPage from '@/pages/public/TicketConfirmationPage';
import MyTicketsPage from '@/pages/volunteer/MyTicketsPage';
import MyCalendarPage from '@/pages/volunteer/MyCalendarPage';
import SharedCalendarsPage from '@/pages/volunteer/SharedCalendarsPage';
import SharedCalendarViewPage from '@/pages/volunteer/SharedCalendarViewPage';
import FriendCalendarPage from '@/pages/volunteer/FriendCalendarPage';
import NotFoundPage from '@/pages/NotFoundPage';
import CommandPalette from '@/components/command-palette/CommandPalette';
function RoleAwareRedirect() {
const { user, isAuthenticated } = useAuthStore();
if (!isAuthenticated) return <Navigate to="/home" replace />;
if (user && isAdmin(user)) return <Navigate to="/app" replace />;
return <Navigate to="/volunteer" replace />;
}
function NavigateToCutMap() {
const { cutId } = useParams<{ cutId: string }>();
return <Navigate to={`/volunteer?cutId=${cutId}`} replace />;
}
export default function App() {
const { hydrate, isLoading } = useAuthStore();
const { settings, fetchSettings } = useSettingsStore();
useEffect(() => {
hydrate();
fetchSettings();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Dynamic document title + favicon from settings
useEffect(() => {
if (!settings) return;
document.title = `${settings.organizationName || 'Changemaker Lite'} — Admin`;
if (settings.organizationFaviconUrl) {
let link = document.querySelector<HTMLLinkElement>("link[rel~='icon']");
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
link.href = settings.organizationFaviconUrl;
}
}, [settings]);
if (isLoading) {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100vh',
}}
>
<Spin size="large" />
</div>
);
}
return (
<ConfigProvider
theme={{
algorithm: theme.darkAlgorithm,
token: {
colorPrimary: settings?.adminColorPrimary ?? '#9d4edd',
colorBgBase: settings?.adminColorBgBase ?? '#1a1025',
colorBorder: 'rgba(255,255,255,0.08)',
colorBorderSecondary: 'rgba(255,255,255,0.06)',
borderRadius: 6,
},
}}
>
<AntApp>
<BrowserRouter>
<CommandPalette />
<Routes>
{/* Public homepage */}
<Route path="/home" element={<PublicLayout />}>
<Route index element={<HomePage />} />
</Route>
{/* Public pages (no auth, dark blue theme) — feature-gated */}
<Route path="/campaigns" element={<FeatureGate feature="enableInfluence"><PublicLayout /></FeatureGate>}>
<Route index element={<CampaignsListPage />} />
</Route>
<Route path="/campaigns/create" element={
<FeatureGate feature="enableInfluence">
<ProtectedRoute>
<PublicLayout />
</ProtectedRoute>
</FeatureGate>
}>
<Route index element={<CreateCampaignPage />} />
</Route>
<Route path="/campaigns/mine" element={
<FeatureGate feature="enableInfluence">
<ProtectedRoute>
<PublicLayout />
</ProtectedRoute>
</FeatureGate>
}>
<Route index element={<MyCampaignsPage />} />
</Route>
<Route path="/campaign" element={<FeatureGate feature="enableInfluence"><PublicLayout /></FeatureGate>}>
<Route path=":slug" element={<CampaignPage />} />
<Route path=":slug/responses" element={<ResponseWallPage />} />
</Route>
<Route path="/shifts" element={<FeatureGate feature="enableMap"><PublicLayout /></FeatureGate>}>
<Route index element={<PublicShiftsPage />} />
</Route>
<Route path="/map" element={<FeatureGate feature="enableMap"><MapPage /></FeatureGate>} />
<Route path="/events" element={<FeatureGate feature="enableEvents"><PublicLayout /></FeatureGate>}>
<Route index element={<EventsPage />} />
</Route>
<Route path="/wall-of-fame" element={<FeatureGate feature="enableSocial"><PublicLayout /></FeatureGate>}>
<Route index element={<WallOfFamePage />} />
</Route>
{/* Scheduling polls — feature-gated */}
<Route path="/polls" element={<FeatureGate feature="enableMeetingPlanner"><PublicLayout /></FeatureGate>}>
<Route index element={<PollsListPage />} />
</Route>
<Route path="/poll/:slug" element={<FeatureGate feature="enableMeetingPlanner"><PublicLayout /></FeatureGate>}>
<Route index element={<SchedulingPollPage />} />
</Route>
{/* Public ticketed event pages — feature-gated */}
<Route path="/event/:slug" element={<FeatureGate feature="enableTicketedEvents"><PublicLayout /></FeatureGate>}>
<Route index element={<TicketedEventDetailPage />} />
</Route>
<Route path="/event/:slug/ticket/:ticketCode" element={<FeatureGate feature="enableTicketedEvents"><PublicLayout /></FeatureGate>}>
<Route index element={<TicketConfirmationPage />} />
</Route>
{/* Public meeting join page — feature-gated */}
<Route path="/meet/:slug" element={<FeatureGate feature="enableMeet"><PublicLayout /></FeatureGate>}>
<Route index element={<MeetingJoinPage />} />
</Route>
<Route path="/pages" element={<FeatureGate feature="enableLandingPages"><PublicLayout /></FeatureGate>}>
<Route index element={<PagesIndexPage />} />
</Route>
<Route path="/p/:slug" element={<FeatureGate feature="enableLandingPages"><PublicLandingPage /></FeatureGate>} />
{/* Public Payment pages (PublicLayout, dark blue theme) — feature-gated */}
<Route path="/pricing" element={<FeatureGate feature="enablePayments"><PublicLayout /></FeatureGate>}>
<Route index element={<PricingPage />} />
<Route path=":slug" element={<PlanDetailPage />} />
</Route>
<Route path="/shop" element={<FeatureGate feature="enablePayments"><PublicLayout /></FeatureGate>}>
<Route index element={<ShopPage />} />
<Route path=":slug" element={<ProductDetailPage />} />
</Route>
<Route path="/donate" element={<FeatureGate feature="enablePayments"><PublicLayout /></FeatureGate>}>
<Route index element={<DonationPagesListPage />} />
<Route path=":slug" element={<DonatePage />} />
</Route>
<Route path="/payments/success" element={<FeatureGate feature="enablePayments"><PublicLayout /></FeatureGate>}>
<Route index element={<PaymentSuccessPage />} />
</Route>
{/* Self-service contact profile (no auth, token-based access) */}
<Route path="/profile/:token" element={<PublicLayout />}>
<Route index element={<ContactProfilePage />} />
</Route>
{/* Public Media Gallery (purple theme) — feature-gated */}
<Route path="/gallery" element={<FeatureGate feature="enableMediaFeatures"><MediaPublicLayout /></FeatureGate>}>
<Route index element={<MediaGalleryPage />} />
<Route path="shorts" element={<ShortsPage />} />
<Route path=":category" element={<MediaGalleryPage />} />
</Route>
<Route path="/gallery/curated" element={<FeatureGate feature="enableMediaFeatures"><MediaPublicLayout /></FeatureGate>}>
<Route index element={<PlaylistBrowsePage />} />
</Route>
<Route path="/gallery/curated/share/:token" element={<FeatureGate feature="enableMediaFeatures"><PlaylistViewerPage /></FeatureGate>} />
<Route path="/gallery/curated/:playlistId" element={<FeatureGate feature="enableMediaFeatures"><PlaylistViewerPage /></FeatureGate>} />
<Route path="/gallery/my-stats" element={<FeatureGate feature="enableMediaFeatures"><ProtectedRoute><MediaPublicLayout /></ProtectedRoute></FeatureGate>}>
<Route index element={<MyStatsPage />} />
</Route>
<Route path="/gallery/my-settings" element={<FeatureGate feature="enableMediaFeatures"><ProtectedRoute><MediaPublicLayout /></ProtectedRoute></FeatureGate>}>
<Route index element={<MySettingsPage />} />
</Route>
<Route path="/gallery/watch/:id" element={<FeatureGate feature="enableMediaFeatures"><MediaViewerPage /></FeatureGate>} />
{/* Email link alias for video viewer */}
<Route path="/media/:id" element={<MediaViewerPage />} />
{/* Volunteer map — full-screen, default landing page */}
<Route
path="/volunteer"
element={
<ProtectedRoute>
<VolunteerMapPage />
</ProtectedRoute>
}
/>
{/* Full-screen volunteer chat — outside VolunteerLayout for max screen space */}
<Route
path="/volunteer/chat"
element={
<ProtectedRoute>
<VolunteerChatPage />
</ProtectedRoute>
}
/>
{/* Volunteer pages with VolunteerLayout */}
<Route
element={
<ProtectedRoute>
<VolunteerLayout />
</ProtectedRoute>
}
>
<Route path="/volunteer/activity" element={<MyActivityPage />} />
<Route path="/volunteer/shifts" element={<VolunteerShiftsPage />} />
<Route path="/volunteer/routes" element={<MyRoutesPage />} />
<Route path="/volunteer/feed" element={<SocialFeedPage />} />
<Route path="/volunteer/friends" element={<FriendsPage />} />
<Route path="/volunteer/discover" element={<DiscoverPage />} />
<Route path="/volunteer/profile/:userId" element={<SocialProfilePage />} />
<Route path="/volunteer/profile" element={<SocialProfilePage />} />
<Route path="/volunteer/notifications" element={<NotificationsPage />} />
<Route path="/volunteer/groups/:id" element={<GroupDetailPage />} />
<Route path="/volunteer/achievements" element={<AchievementsPage />} />
<Route path="/volunteer/referrals" element={<ReferralsPage />} />
<Route path="/volunteer/challenges" element={<ChallengesPage />} />
<Route path="/volunteer/challenges/:id" element={<ChallengeDetailPage />} />
<Route path="/volunteer/tickets" element={<MyTicketsPage />} />
<Route path="/volunteer/calendar/shared/:id" element={<FeatureGate feature="enableSocialCalendar"><SharedCalendarViewPage /></FeatureGate>} />
<Route path="/volunteer/calendar/shared" element={<FeatureGate feature="enableSocialCalendar"><SharedCalendarsPage /></FeatureGate>} />
<Route path="/volunteer/calendar/friend/:userId" element={<FeatureGate feature="enableSocialCalendar"><FriendCalendarPage /></FeatureGate>} />
<Route path="/volunteer/calendar" element={<FeatureGate feature="enableSocialCalendar"><MyCalendarPage /></FeatureGate>} />
<Route path="/volunteer/*" element={<NotFoundPage />} />
</Route>
{/* Redirect old canvass routes to map with query param */}
<Route
path="/volunteer/canvass/:cutId"
element={<NavigateToCutMap />}
/>
{/* Full-screen check-in scanner (outside AppLayout) */}
<Route
path="/app/events/:id/checkin"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<FeatureGate feature="enableTicketedEvents">
<CheckInScannerPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route path="/join" element={<QuickJoinPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/jitsi-auth/:room" element={<JitsiAuthPage />} />
<Route path="/verify-email" element={<VerifyEmailPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
<Route
path="/app"
element={
<ProtectedRoute>
<AppLayout />
</ProtectedRoute>
}
>
<Route index element={<DashboardPage />} />
<Route
path="people"
element={
<ProtectedRoute requiredRoles={ADMIN_ROLES}>
<PeoplePage />
</ProtectedRoute>
}
/>
<Route
path="users"
element={
<ProtectedRoute requiredRoles={ADMIN_ROLES}>
<UsersPage />
</ProtectedRoute>
}
/>
<Route
path="social"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<SocialDashboardPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="social/graph"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<SocialGraphPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="social/moderation"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<SocialModerationPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="social/referrals"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<ReferralAdminPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="social/spotlights"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<SpotlightAdminPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="social/challenges"
element={
<ProtectedRoute requiredRoles={SOCIAL_ROLES}>
<FeatureGate feature="enableSocial">
<ChallengesAdminPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="campaigns"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<CampaignsPage />
</ProtectedRoute>
}
/>
<Route
path="representatives"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<RepresentativesPage />
</ProtectedRoute>
}
/>
<Route
path="email-queue"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<EmailQueuePage />
</ProtectedRoute>
}
/>
<Route
path="email-templates"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<EmailTemplatesPage />
</ProtectedRoute>
}
/>
<Route
path="responses"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<ResponsesPage />
</ProtectedRoute>
}
/>
<Route
path="campaign-moderation"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<CampaignModerationPage />
</ProtectedRoute>
}
/>
<Route
path="influence/effectiveness"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<CampaignEffectivenessPage />
</ProtectedRoute>
}
/>
<Route
path="influence/stories"
element={
<ProtectedRoute requiredRoles={INFLUENCE_ROLES}>
<ImpactStoriesPage />
</ProtectedRoute>
}
/>
<Route
path="listmonk"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<ListmonkPage />
</ProtectedRoute>
}
/>
<Route
path="pages"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<LandingPagesPage />
</ProtectedRoute>
}
/>
<Route
path="docs"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<DocsPage />
</ProtectedRoute>
}
/>
<Route
path="docs/settings"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<MkDocsSettingsPage />
</ProtectedRoute>
}
/>
<Route
path="docs/analytics"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<DocsAnalyticsPage />
</ProtectedRoute>
}
/>
<Route
path="docs/comments"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<DocsCommentsPage />
</ProtectedRoute>
}
/>
<Route
path="navigation"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<NavigationSettingsPage />
</ProtectedRoute>
}
/>
<Route
path="code"
element={
<ProtectedRoute requiredRoles={CONTENT_ROLES}>
<CodeEditorPage />
</ProtectedRoute>
}
/>
<Route
path="services/nocodb"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<NocoDBPage />
</ProtectedRoute>
}
/>
<Route
path="services/n8n"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<N8nPage />
</ProtectedRoute>
}
/>
<Route
path="services/gitea"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<GiteaPage />
</ProtectedRoute>
}
/>
<Route
path="services/mailhog"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<MailHogPage />
</ProtectedRoute>
}
/>
<Route
path="services/miniqr"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<MiniQRPage />
</ProtectedRoute>
}
/>
<Route
path="services/excalidraw"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<ExcalidrawPage />
</ProtectedRoute>
}
/>
<Route
path="services/vaultwarden"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<VaultwardenPage />
</ProtectedRoute>
}
/>
<Route
path="services/rocketchat"
element={
<ProtectedRoute>
<RocketChatPage />
</ProtectedRoute>
}
/>
<Route
path="services/gancio"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<GancioPage />
</ProtectedRoute>
}
/>
<Route
path="services/jitsi"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<JitsiMeetPage />
</ProtectedRoute>
}
/>
{/* SMS Campaign Routes */}
<Route
path="sms/setup"
element={
<ProtectedRoute requiredRoles={['SUPER_ADMIN']}>
<SmsSetupPage />
</ProtectedRoute>
}
/>
<Route
path="sms"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<SmsDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="sms/contacts"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<SmsContactsPage />
</ProtectedRoute>
}
/>
<Route
path="sms/campaigns"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<SmsCampaignsPage />
</ProtectedRoute>
}
/>
<Route
path="sms/conversations"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<SmsConversationsPage />
</ProtectedRoute>
}
/>
<Route
path="sms/templates"
element={
<ProtectedRoute requiredRoles={BROADCAST_ROLES}>
<SmsTemplatesPage />
</ProtectedRoute>
}
/>
<Route
path="settings"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<SettingsPage />
</ProtectedRoute>
}
/>
<Route
path="tunnel"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<PangolinPage />
</ProtectedRoute>
}
/>
<Route
path="observability"
element={
<ProtectedRoute requiredRoles={SYSTEM_ROLES}>
<ObservabilityPage />
</ProtectedRoute>
}
/>
<Route
path="map"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<LocationsPage />
</ProtectedRoute>
}
/>
<Route
path="map/data-quality"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<DataQualityDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="map/shifts"
element={
<ProtectedRoute requiredRoles={SCHEDULING_ROLES}>
<ShiftsPage />
</ProtectedRoute>
}
/>
<Route
path="meeting-planner"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<MeetingPlannerPage />
</ProtectedRoute>
}
/>
<Route
path="scheduling/calendar-views/:id"
element={
<ProtectedRoute requiredRoles={SCHEDULING_ROLES}>
<FeatureGate feature="enableSocialCalendar">
<AdminCalendarViewPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="scheduling/calendar"
element={
<ProtectedRoute requiredRoles={SCHEDULING_ROLES}>
<FeatureGate feature="enableSocialCalendar">
<SchedulingCalendarPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="events"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<FeatureGate feature="enableTicketedEvents">
<TicketedEventsPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="events/:id"
element={
<ProtectedRoute requiredRoles={EVENTS_ROLES}>
<FeatureGate feature="enableTicketedEvents">
<EventDetailPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="map/cuts"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<CutsPage />
</ProtectedRoute>
}
/>
<Route
path="map/settings"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<MapSettingsPage />
</ProtectedRoute>
}
/>
<Route
path="map/cuts/:id/export"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<CutExportPage />
</ProtectedRoute>
}
/>
<Route
path="map/canvass"
element={
<ProtectedRoute requiredRoles={MAP_ROLES}>
<CanvassDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="media/library"
element={
<ProtectedRoute requiredRoles={MEDIA_ROLES}>
<LibraryPage />
</ProtectedRoute>
}
/>
<Route
path="media/analytics"
element={
<ProtectedRoute requiredRoles={MEDIA_ROLES}>
<AnalyticsDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="media/jobs"
element={
<ProtectedRoute requiredRoles={MEDIA_ROLES}>
<MediaJobsPage />
</ProtectedRoute>
}
/>
<Route
path="media/curated"
element={
<ProtectedRoute requiredRoles={MEDIA_ROLES}>
<PlaylistManagementPage />
</ProtectedRoute>
}
/>
<Route
path="media/moderation"
element={
<ProtectedRoute requiredRoles={MEDIA_ROLES}>
<CommentModerationPage />
</ProtectedRoute>
}
/>
<Route
path="payments/ads/analytics"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<AdAnalyticsDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="payments/ads"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<GalleryAdsPage />
</ProtectedRoute>
}
/>
<Route
path="payments"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<PaymentsDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="payments/plans"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<PlansPage />
</ProtectedRoute>
}
/>
<Route
path="payments/subscribers"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<SubscribersPage />
</ProtectedRoute>
}
/>
<Route
path="payments/products"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<PaymentProductsPage />
</ProtectedRoute>
}
/>
<Route
path="payments/donations"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<PaymentDonationsPage />
</ProtectedRoute>
}
/>
<Route
path="payments/donation-pages"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<DonationPagesPage />
</ProtectedRoute>
}
/>
<Route
path="payments/settings"
element={
<ProtectedRoute requiredRoles={PAYMENTS_ROLES}>
<PaymentSettingsPage />
</ProtectedRoute>
}
/>
<Route path="*" element={<NotFoundPage />} />
</Route>
<Route path="/" element={<RoleAwareRedirect />} />
<Route path="*" element={<PublicLayout />}>
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
</BrowserRouter>
</AntApp>
</ConfigProvider>
);
}