import * as icon from '@mdi/js' import Head from 'next/head' import React from 'react' import axios from 'axios' import type { ReactElement } from 'react' import Link from 'next/link' import LayoutAuthenticated from '../layouts/Authenticated' import SectionMain from '../components/SectionMain' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' import BaseIcon from '../components/BaseIcon' import BaseButton from '../components/BaseButton' import CardBox from '../components/CardBox' import { getPageTitle } from '../config' import { hasPermission } from '../helpers/userPermissions' import { getBusinessMenuLabel } from '../helpers/businessPlanLabels' import { getPortalLabel, isInternalAdmin } from '../helpers/portalRoles' import { fetchWidgets } from '../stores/roles/rolesSlice' import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator' import { SmartWidget } from '../components/SmartWidget/SmartWidget' import { useAppDispatch, useAppSelector } from '../stores/hooks' type EntityKey = | 'users' | 'roles' | 'permissions' | 'businesses' | 'customers' | 'transactions' | 'review_requests' | 'stripe_events' | 'email_delivery_logs' | 'cron_runs' type CountValue = string | number | null type CountState = Record type DashboardCard = { key: EntityKey label: string description: string href: string iconPath: string permission: string } type DashboardAction = { label: string href: string permission?: string | string[] } type DashboardActionGroup = { title: string description: string actions: DashboardAction[] } const loadingMessage = 'Loading...' const entityKeys: EntityKey[] = [ 'users', 'roles', 'permissions', 'businesses', 'customers', 'transactions', 'review_requests', 'stripe_events', 'email_delivery_logs', 'cron_runs', ] const entityConfig: Record = { users: { endpoint: 'users', permission: 'READ_USERS' }, roles: { endpoint: 'roles', permission: 'READ_ROLES' }, permissions: { endpoint: 'permissions', permission: 'READ_PERMISSIONS' }, businesses: { endpoint: 'businesses', permission: 'READ_BUSINESSES' }, customers: { endpoint: 'customers', permission: 'READ_CUSTOMERS' }, transactions: { endpoint: 'transactions', permission: 'READ_TRANSACTIONS' }, review_requests: { endpoint: 'review_requests', permission: 'READ_REVIEW_REQUESTS' }, stripe_events: { endpoint: 'stripe_events', permission: 'READ_STRIPE_EVENTS' }, email_delivery_logs: { endpoint: 'email_delivery_logs', permission: 'READ_EMAIL_DELIVERY_LOGS' }, cron_runs: { endpoint: 'cron_runs', permission: 'READ_CRON_RUNS' }, } const initialCounts = entityKeys.reduce((counts, key) => { counts[key] = loadingMessage return counts }, {} as CountState) const storeIcon = 'mdiStore' in icon ? icon['mdiStore' as keyof typeof icon] : icon.mdiTable const accountMultipleIcon = 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable const emailFastIcon = 'mdiEmailFastOutline' in icon ? icon['mdiEmailFastOutline' as keyof typeof icon] : icon.mdiTable const webhookIcon = 'mdiWebhook' in icon ? icon['mdiWebhook' as keyof typeof icon] : icon.mdiTable const emailCheckIcon = 'mdiEmailCheckOutline' in icon ? icon['mdiEmailCheckOutline' as keyof typeof icon] : icon.mdiTable const clockIcon = 'mdiClockOutline' in icon ? icon['mdiClockOutline' as keyof typeof icon] : icon.mdiTable function formatCount(value: CountValue) { if (value === null || value === undefined) return '—' if (typeof value === 'number') return value.toLocaleString() return value } function StatCard({ card, value, corners, cardsStyle, iconsColor, }: { card: DashboardCard value: CountValue corners: string cardsStyle: string iconsColor: string }) { return (
{card.label}
{formatCount(value)}

{card.description}

) } function ActionGroupCard({ group, currentUser }: { group: DashboardActionGroup; currentUser: any }) { const visibleActions = group.actions.filter( (action) => !action.permission || hasPermission(currentUser, action.permission), ) if (!visibleActions.length) return null return (

{group.title}

{group.description}

{visibleActions.map((action) => ( ))}
) } function PortalIntroCard({ currentUser, adminPortal }: { currentUser: any; adminPortal: boolean }) { const portalLabel = getPortalLabel(currentUser) const roleName = currentUser?.app_role?.name || 'User' const name = currentUser?.firstName || currentUser?.email || 'there' return (

{portalLabel}

Welcome, {name}

{adminPortal ? 'This internal area is for running the SaaS business: customer accounts, business profiles, billing events, review operations, and access control.' : 'This customer workspace is for setting up your business profile, connecting review automation, managing customers, tracking transactions, and handling your subscription.'}

Signed in as

{roleName}

{adminPortal ? 'Customer workspace setup links are intentionally hidden from this portal.' : 'Internal platform administration links are intentionally hidden from this workspace.'}

) } const Dashboard = () => { const dispatch = useAppDispatch() const iconsColor = useAppSelector((state) => state.style.iconsColor) const corners = useAppSelector((state) => state.style.corners) const cardsStyle = useAppSelector((state) => state.style.cardsStyle) const { currentUser } = useAppSelector((state) => state.auth) const { isFetchingQuery } = useAppSelector((state) => state.openAi) const { rolesWidgets, loading } = useAppSelector((state) => state.roles) const [counts, setCounts] = React.useState(initialCounts) const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, }) const adminPortal = isInternalAdmin(currentUser) const businessLabel = getBusinessMenuLabel(currentUser?.subscriptionPlanId) const businessProfilesLabel = adminPortal ? 'Business profiles' : businessLabel const loadData = React.useCallback(async () => { if (!currentUser) return const requests = entityKeys.map(async (key) => { const config = entityConfig[key] if (!hasPermission(currentUser, config.permission)) { return { key, count: null } } const response = await axios.get(`/${config.endpoint}/count`) return { key, count: response.data.count as CountValue } }) const results = await Promise.allSettled(requests) setCounts((previousCounts) => { const nextCounts = { ...previousCounts } results.forEach((result, index) => { const key = entityKeys[index] if (result.status === 'fulfilled') { nextCounts[result.value.key] = result.value.count } else { console.error(`Failed to load ${key} dashboard count:`, result.reason) nextCounts[key] = 'Error' } }) return nextCounts }) }, [currentUser]) const getWidgets = React.useCallback(async (roleId: string) => { await dispatch(fetchWidgets(roleId)) }, [dispatch]) React.useEffect(() => { if (!currentUser) return loadData().then() setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name, }, }) }, [currentUser, loadData]) React.useEffect(() => { if (!currentUser || !widgetsRole?.role?.value || adminPortal) return getWidgets(widgetsRole?.role?.value || '').then() }, [adminPortal, currentUser, getWidgets, widgetsRole?.role?.value]) const adminCards: DashboardCard[] = [ { key: 'users', label: 'Customer accounts', description: 'Owners and team users across the platform.', href: '/users/users-list', iconPath: icon.mdiAccountGroup, permission: 'READ_USERS', }, { key: 'businesses', label: 'Business profiles', description: 'Business locations connected to review flows.', href: '/businesses/businesses-list', iconPath: storeIcon, permission: 'READ_BUSINESSES', }, { key: 'transactions', label: 'Transactions', description: 'Payment records feeding review requests.', href: '/transactions/transactions-list', iconPath: icon.mdiCreditCardOutline, permission: 'READ_TRANSACTIONS', }, { key: 'review_requests', label: 'Review requests', description: 'Review invitations generated by the system.', href: '/review_requests/review_requests-list', iconPath: emailFastIcon, permission: 'READ_REVIEW_REQUESTS', }, { key: 'stripe_events', label: 'Payment events', description: 'Stripe webhook events and processing status.', href: '/stripe_events/stripe_events-list', iconPath: webhookIcon, permission: 'READ_STRIPE_EVENTS', }, { key: 'cron_runs', label: 'Automation runs', description: 'Scheduled background job execution history.', href: '/cron_runs/cron_runs-list', iconPath: clockIcon, permission: 'READ_CRON_RUNS', }, ] const customerCards: DashboardCard[] = [ { key: 'businesses', label: businessProfilesLabel, description: 'Your business profile and Google review destination.', href: '/businesses/businesses-list', iconPath: storeIcon, permission: 'READ_BUSINESSES', }, { key: 'customers', label: 'Customers', description: 'Customer records created from payments or imports.', href: '/customers/customers-list', iconPath: accountMultipleIcon, permission: 'READ_CUSTOMERS', }, { key: 'transactions', label: 'Transactions', description: 'Payments that can trigger review follow-up.', href: '/transactions/transactions-list', iconPath: icon.mdiCreditCardOutline, permission: 'READ_TRANSACTIONS', }, { key: 'review_requests', label: 'Review requests', description: 'Messages scheduled or sent to customers.', href: '/review_requests/review_requests-list', iconPath: emailFastIcon, permission: 'READ_REVIEW_REQUESTS', }, { key: 'email_delivery_logs', label: 'Email delivery', description: 'Delivery activity for review request emails.', href: '/email_delivery_logs/email_delivery_logs-list', iconPath: emailCheckIcon, permission: 'READ_EMAIL_DELIVERY_LOGS', }, ] const adminActionGroups: DashboardActionGroup[] = [ { title: 'Customer operations', description: 'Support customer accounts and the business profiles they manage.', actions: [ { label: 'Review customer accounts', href: '/users/users-list', permission: 'READ_USERS' }, { label: 'Review business profiles', href: '/businesses/businesses-list', permission: 'READ_BUSINESSES' }, { label: 'Review end customers', href: '/customers/customers-list', permission: 'READ_CUSTOMERS' }, ], }, { title: 'Billing & review operations', description: 'Monitor payment data, webhook events, review requests, and delivery health.', actions: [ { label: 'View transactions', href: '/transactions/transactions-list', permission: 'READ_TRANSACTIONS' }, { label: 'View payment events', href: '/stripe_events/stripe_events-list', permission: 'READ_STRIPE_EVENTS' }, { label: 'View review requests', href: '/review_requests/review_requests-list', permission: 'READ_REVIEW_REQUESTS' }, { label: 'View email delivery logs', href: '/email_delivery_logs/email_delivery_logs-list', permission: 'READ_EMAIL_DELIVERY_LOGS' }, { label: 'View automation runs', href: '/cron_runs/cron_runs-list', permission: 'READ_CRON_RUNS' }, ], }, { title: 'Platform access control', description: 'Manage internal roles and permissions for the platform.', actions: [ { label: 'Manage roles', href: '/roles/roles-list', permission: 'READ_ROLES' }, { label: 'Manage permissions', href: '/permissions/permissions-list', permission: 'READ_PERMISSIONS' }, ], }, ] const customerActionGroups: DashboardActionGroup[] = [ { title: 'Review automation setup', description: 'Configure the business profile, review request templates, and payment triggers.', actions: [ { label: 'Open Review Flow', href: '/reviewflow' }, { label: `Manage ${businessLabel}`, href: '/businesses/businesses-list', permission: 'READ_BUSINESSES' }, { label: 'Manage review requests', href: '/review_requests/review_requests-list', permission: 'READ_REVIEW_REQUESTS' }, ], }, { title: 'Customer records', description: 'Track customers and transactions that power follow-up messages.', actions: [ { label: 'Manage customers', href: '/customers/customers-list', permission: 'READ_CUSTOMERS' }, { label: 'View transactions', href: '/transactions/transactions-list', permission: 'READ_TRANSACTIONS' }, { label: 'View email delivery', href: '/email_delivery_logs/email_delivery_logs-list', permission: 'READ_EMAIL_DELIVERY_LOGS' }, ], }, { title: 'Plan & billing', description: 'Review plan limits, usage, and billing status for this workspace.', actions: [ { label: 'Open subscription', href: '/subscription' }, ], }, ] const cards = adminPortal ? adminCards : customerCards const actionGroups = adminPortal ? adminActionGroups : customerActionGroups const visibleCards = cards.filter((card) => hasPermission(currentUser, card.permission)) const title = adminPortal ? 'Internal admin portal' : 'Customer workspace' const sectionIcon = adminPortal ? icon.mdiShieldAccountVariantOutline : icon.mdiChartTimelineVariant const showCustomerWidgets = !adminPortal && hasPermission(currentUser, 'CREATE_ROLES') return ( <> {getPageTitle(title)} {''} {showCustomerWidgets && ( )} {!!rolesWidgets.length && showCustomerWidgets && (

{`${widgetsRole?.role?.label || 'Users'}'s widgets`}

)} {!adminPortal && (
{(isFetchingQuery || loading) && (
{' '} Loading widgets...
)} {rolesWidgets.map((widget) => ( ))}
)} {!adminPortal && !!rolesWidgets.length &&
}
{visibleCards.map((card) => ( ))}
{actionGroups.map((group) => ( ))}
) } Dashboard.getLayout = function getLayout(page: ReactElement) { return {page} } export default Dashboard