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
1002 lines
40 KiB
TypeScript
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>
|
|
);
|
|
}
|