Browse documentation

Frontend Specs

Section 7 — System Configuration (`/admin/system`)

Role access: ADMIN only. VIEWER/EDITOR/CLIENT redirected to /dashboard.

docs/specs/frontend/admin-spec-03a-system.md
On this page

Role access: ADMIN only. VIEWER/EDITOR/CLIENT redirected to /dashboard. Layout: Tabbed interface with 3 tabs: Settings, Integrations, Maintenance. 1200px max-width. Sidebar: carbon (#12161E) background. Notes: All changes saved immediately via admin.updateConfig with optimistic UI + rollback on error. Unsaved changes show a floating save bar.


7.1 System Settings Panel#

1. Purpose#

Central control panel for all global platform configuration. Admins adjust rate limits, API keys, email templates, feature flags, and business rules without redeploying.

2. Visual Layout#

Section A: API & Rate Limits (top-left, 50% width)

  • 2-column grid of editable fields:
    • OpenAI API key: masked input (••••••sk-...) + "Show" toggle + "Rotate" button
    • Max tokens per request: number input (16384 default)
    • Rate limit (req/min): number input (60 default)
    • Max concurrent jobs: number input (50 default)
    • Default model: select (gpt-4o / gpt-4o-mini / gpt-4-turbo)
    • Temperature default: slider 0.01.0 with value label (0.7 default)
  • Each field has a label + helper text below in muted-text (#4B5C67)
  • Edited fields show an amber dot indicator left of the label
  • "Save Changes" sticky button at bottom of section (disabled until changes detected)

Section B: Business Rules (top-right, 50% width)

  • Form fields stacked:
    • Trial days: number input (14 default)
    • Cancellation grace period (days): number input (7 default)
    • Max clients per plan: number inputs per tier (Starter: 1, Growth: 3, Pro: 10)
    • Content approval queue: toggle ON/OFF (for medical clients)
    • Auto-approve threshold: number input (3 days, for non-medical clients)
    • Default timezone: select (UTC, Asia/Kolkata, America/New_York, etc.)
    • Currency default: select (USD, INR, EUR, GBP)
  • Each tier input has a mini plan badge: Starter, Growth, Pro in pill style

Section C: Email & Notifications (bottom, full width)

  • 2-column layout:
    • Left: SMTP settings (host, port, username, password, from address, from name)
    • Right: Default notification templates (welcome email subject, cancellation reminder subject, password reset subject)
  • All fields editable inline. "Test Send" button per template to send a test email to the admin's own address.

Section D: Feature Flags (bottom, full width)

  • Table of toggles:
    • AI_CONTENT_GENERATION: enabled (default)
    • GBP_AUTO_POSTING: enabled (default)
    • SOCIAL_AUTO_POSTING: enabled (default)
    • CITATION_AUTO_SUBMISSION: enabled (default)
    • NEW_SIGNUP_ENABLED: enabled (default)
    • IMPERSONATION_ENABLED: enabled (default)
    • BETA_FEATURES: disabled (default)
  • Each row: flag name + description + toggle switch + last changed timestamp + who changed it
  • Toggle switches use Ember Orange (#EC652B) for ON state, muted-text for OFF state
  • Disabled features show a grayed-out description with "Contact engineering to enable" tooltip

3. Data Source (tRPC endpoint)#

// Read config
admin.getSystemConfig.useQuery(undefined, { refetchInterval: 300000 }); // 5min static

// Update single config
admin.updateConfig.useMutation();

// Rotate API key
admin.rotateApiKey.useMutation({ keyName: z.enum(["OPENAI", "STRIPE", "SENDGRID"]) });

4. Zod Schema#

const SystemConfigSchema = z.object({
  api: z.object({
    openAiKeyMasked: z.string().regex(/^sk-[a-zA-Z0-9]{4}\*{48}$/), // e.g., sk-Abcd••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••
    maxTokens: z.number().min(1).max(128000).default(16384),
    rateLimitPerMinute: z.number().min(1).max(1000).default(60),
    maxConcurrentJobs: z.number().min(1).max(500).default(50),
    defaultModel: z.enum(["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"]).default("gpt-4o"),
    defaultTemperature: z.number().min(0).max(1).default(0.7),
  }),
  business: z.object({
    trialDays: z.number().min(0).max(90).default(14),
    cancellationGraceDays: z.number().min(0).max(30).default(7),
    maxClientsPerPlan: z.object({
      starter: z.number().min(1).default(1),
      growth: z.number().min(1).default(3),
      pro: z.number().min(1).default(10),
    }),
    contentApprovalQueueEnabled: z.boolean().default(true),
    autoApproveThresholdDays: z.number().min(0).max(30).default(3),
    defaultTimezone: z.string().default("UTC"),
    defaultCurrency: z.enum(["USD", "INR", "EUR", "GBP"]).default("USD"),
  }),
  email: z.object({
    smtp: z.object({
      host: z.string().min(1),
      port: z.number().min(1).max(65535).default(587),
      username: z.string().email(),
      password: z.string().min(1), // masked in UI
      fromAddress: z.string().email(),
      fromName: z.string().min(1).default("RankFlow AI"),
    }),
    templates: z.object({
      welcomeSubject: z.string().default("Welcome to RankFlow AI 🚀"),
      cancellationReminderSubject: z.string().default("Your RankFlow AI subscription ends in {days} days"),
      passwordResetSubject: z.string().default("Reset your RankFlow AI password"),
    }),
  }),
  featureFlags: z.array(z.object({
    name: z.string(),
    description: z.string(),
    enabled: z.boolean(),
    lastChangedAt: z.date(),
    lastChangedBy: z.string(), // user name
  })),
});

5. Fetch Frequency#

  • Static (300000ms / 5min): Config changes rarely. 5min polling is sufficient.
  • On-demand: After any mutation, invalidate admin.getSystemConfig.
  • Real-time: Feature flags push via SSE when changed by another admin session.

6. Data Manipulations#

  • Masking: API keys displayed as sk-Abcd•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• — first 8 chars visible, rest masked. Full key only shown on "Show" toggle.
  • Rotation: Clicking "Rotate" generates new key server-side, returns masked version, updates lastChangedAt.
  • Diff tracking: Form state tracks dirty fields. Floating save bar shows count: "3 unsaved changes".
  • Validation: Inline Zod validation on blur. Red border + error message below field.
  • Tiers: Max clients per plan rendered as 3 mini input groups with colored plan badges.

7. Rationale#

  • Single config screen: Prevents config from being scattered across files, environment variables, and DB. One place to check all settings.
  • Masked keys: Security — keys never displayed in full by default. Rotation is one-click.
  • Feature flags: Enables toggling features without code deploy. Critical for incident response (e.g., disable new signups during overload).
  • 5min polling: Config changes are rare and expensive to compute. No need for real-time.
  • Optimistic UI: Admin changes a setting → UI updates immediately → mutation fires. If mutation fails, UI reverts with toast explaining why. Prevents "did it save?" anxiety.

8. Interaction Flows#

  • Edit field: Type → amber dot appears → save bar slides up with "Save 3 changes" + "Discard" → click Save → mutation → green toast "Saved" → dots disappear.
  • Rotate API key: Click "Rotate" → confirmation modal ("This will invalidate the old key immediately. Confirm?") → mutation → new masked key appears → green toast "Key rotated. Update any external integrations." → lastChangedAt updates.
  • Test email: Click "Test Send" next to template → toast "Test email sent to admin@rankflow.ai" → check inbox.
  • Toggle feature flag: Click toggle → immediate optimistic UI change → mutation → if failed, toggle reverts with red toast. If succeeded, green toast + lastChangedAt and lastChangedBy update.
  • Discard changes: Click "Discard" in save bar → all fields revert to last saved state → confirmation if >5 changes.
  • Impersonation: Settings are read-only during impersonation. All fields disabled, save bar hidden. Red banner shows "Impersonation — Read Only".

9. Error States#

  • Loading: Skeleton form with 8 shimmer rows.
  • Permission denied: If non-ADMIN navigates here, 403 redirect (handled at route level, not in widget).
  • Mutation fail: Network error → toast "Failed to save. Retry?" with retry button. Field keeps amber dot.
  • Validation error: Red border on field + text below: "Must be between 1 and 500" or "Invalid email format".
  • API key rotation fail: If key provider API down, toast "Rotation failed: OpenAI API unreachable. Key unchanged."
  • Test email fail: Toast "SMTP configuration error. Check host and credentials."
  • Concurrent edit: If another admin changes the same field while you're editing, banner appears: "This setting was changed by {name} at {time}. Reload to see latest values." with "Reload" button.

10. Role-Based Variations#

  • ADMIN: Full read/write. Can rotate keys, toggle flags, save changes.
  • Other roles: No access. Redirected to /dashboard.
  • Impersonation: Read-only. All inputs disabled, save bar hidden. Can view but not modify. Actions are logged with impersonating: true and action: "VIEW_SYSTEM_CONFIG".

7.2 Integration Status#

1. Purpose#

Live health dashboard for all external service integrations. Admins see at a glance if OpenAI, Google, Stripe, SendGrid, Redis, or any third-party service is up, degraded, or down.

2. Visual Layout#

Grid of service cards: 3 columns desktop, 2 tablet, 1 mobile. Each card:

  • Top row: Service icon (Lucide) + service name + status badge
  • Status badge:
    • HEALTHY: bg-emerald-50 + text-emerald-700 + green dot + "Healthy"
    • DEGRADED: bg-amber-50 + text-amber-700 + amber dot + "Degraded" + tooltip with reason
    • DOWN: bg-red-50 + text-red-700 + red dot + "Down" + tooltip with last error
  • Metrics row: 2-3 mini stats:
    • For OpenAI: "Last request: 2s ago", "Avg latency: 847ms", "Error rate: 0.1%"
    • For Stripe: "Last webhook: 5min ago", "Pending events: 0"
    • For SendGrid: "Queue depth: 0", "Last sent: 1min ago"
    • For Redis: "Memory: 47MB/128MB", "Connected clients: 12"
  • Action row: "Test Connection" button + "View Logs" link (opens Audit & Logs with service filter)
  • Card background: bg-white with shadow-sm. Border-left: 4px colored by status (green/amber/red).

Services monitored:

  • OpenAI API
  • Google Cloud Platform (GBP, Search Console, Places API)
  • Stripe API + Webhooks
  • SendGrid / SMTP
  • Redis (BullMQ)
  • Inngest (event queue)
  • Prisma / Database
  • Better Auth (session store)
  • External social APIs (Meta, X, LinkedIn — if connected)

3. Data Source (tRPC endpoint)#

admin.getIntegrationStatus.useQuery(undefined, { refetchInterval: 10000 }); // 10s real-time
admin.testIntegration.useMutation({ service: z.enum(["OPENAI", "STRIPE", "SENDGRID", "REDIS", "INGEST", "GCP"]) });

4. Zod Schema#

const IntegrationStatusSchema = z.array(z.object({
  service: z.enum(["OPENAI", "STRIPE", "SENDGRID", "REDIS", "INGEST", "GCP", "META", "X", "LINKEDIN", "PRISMA", "BETTER_AUTH"]),
  status: z.enum(["HEALTHY", "DEGRADED", "DOWN"]),
  lastCheckedAt: z.date(),
  lastError: z.string().optional(),
  lastErrorAt: z.date().optional(),
  metrics: z.object({
    lastRequestMs: z.number().optional(), // ms since last request
    avgLatencyMs: z.number().optional(),
    errorRatePercent: z.number().optional(),
    queueDepth: z.number().optional(),
    memoryUsedMb: z.number().optional(),
    memoryTotalMb: z.number().optional(),
    connectedClients: z.number().optional(),
    pendingEvents: z.number().optional(),
  }).optional(),
  testEndpoint: z.string().url().optional(), // URL to test
}));

5. Fetch Frequency#

  • Real-time (10000ms / 10s): Integration status is critical for platform health. 10s polling.
  • On-demand: Click "Test Connection" → fires one-time test mutation, returns live result.
  • Push (SSE): If status changes from HEALTHY to DOWN or DEGRADED, push immediate update via SSE channel.

6. Data Manipulations#

  • Color coding: Status → card border-left color + badge color.
  • Latency formatting: < 1000ms → "847ms"; >= 1000ms → "1.2s" (red if > 5000ms).
  • Error rate: 0% → hidden; > 0% → shown in red if > 1%, amber if > 0.1%.
  • Memory bar: memoryUsedMb / memoryTotalMb → thin progress bar inside card (green if < 50%, amber if < 80%, red if >= 80%).
  • Last checked: "Just now" (< 30s), "2s ago", "15s ago", "1min ago".
  • Sorting: Default by status severity (DOWN first, then DEGRADED, then HEALTHY) then alphabetical.

7. Rationale#

  • Card grid: Admins need to scan 10+ services quickly. Card grid with color-coded borders lets them spot problems in < 2 seconds.
  • 10s polling: Integration status is critical infrastructure. If Stripe is down, signups fail. If OpenAI is down, content generation stops. Need near-real-time awareness.
  • In-card metrics: Key metrics (latency, error rate) without needing to drill down. Contextual to each service.
  • Test button: When a service shows DEGRADED, admin can immediately test the connection to confirm if it's a transient issue or real outage.
  • SSE push for status changes: Polling every 10s catches most issues, but a service going DOWN needs immediate attention. SSE pushes the change instantly.

8. Interaction Flows#

  • Card hover: Slight shadow lift (shadow-md). Click anywhere on card opens detail panel (slide-in from right) with full metrics history (last 24h).
  • Test Connection: Click button → button shows spinner → result appears inline (green "Connected in 234ms" or red "Connection failed: timeout") → if failed, card border turns red and status updates.
  • View Logs: Click link → navigates to /admin/audit with service filter pre-selected.
  • DEGRADED tooltip: Hover over amber badge → tooltip shows specific reason: "High latency: 4.2s average (threshold: 2s)" or "Error rate: 3.2% (threshold: 1%)".
  • DOWN tooltip: Hover red badge → shows last error message + "Since {time}".
  • Impersonation: Read-only. Test Connection button disabled. Detail panel accessible but no actions.

9. Error States#

  • Loading: Skeleton card grid (3x3 cards with shimmer).
  • All healthy: Subtle green header "All systems operational — checked {time}".
  • One down: Red banner at top of page: "⚠️ {Service} is down. Revenue-impacting." with direct link to logs.
  • Multiple down: Red banner + alert sound (optional browser notification) + card grid auto-scrolls to first DOWN card.
  • Test fail: Inline red text below button. "Timeout after 10s. Check firewall rules."
  • No services: "No integrations configured. Check environment variables." (system misconfiguration).

10. Role-Based Variations#

  • ADMIN: Full access. Test, view logs, view detail panel.
  • Other roles: No access.
  • Impersonation: Read-only. Test buttons disabled. Detail panel viewable.

7.3 Maintenance Mode Toggle#

1. Purpose#

One-click emergency switch to put the entire platform in maintenance mode. Shows a branded maintenance page to all non-admin users. Used for critical deployments, database migrations, or incident response.

2. Visual Layout#

Prominent toggle card at top of page (full width, bg-red-50 border border-red-200):

  • Left: Warning icon (triangle, text-red-600) + heading "Maintenance Mode" in text-2xl text-red-700
  • Below heading: "When enabled, all non-admin users see a maintenance page. Current sessions are not terminated but new requests are blocked."
  • Right: Large toggle switch (w-14 h-8 scale). ON state: red background, white circle. OFF state: gray-300 background.
  • Below toggle: "Last used: {date} by {name}" or "Never used".

When toggle is ON:

  • Card background changes to bg-red-100 with border-red-300.
  • Additional fields appear below:
    • "Custom message" textarea (default: "We're performing scheduled maintenance. Back in a few minutes.")
    • "Estimated return" datetime picker (optional)
    • "Allow admins to bypass": toggle ON by default
  • Live preview panel on right: shows exactly what users will see (maintenance page preview in an iframe or mock card).

When toggle is OFF:

  • Card returns to bg-white with border-red-200 border.
  • Extra fields hidden.
  • "Last used: {date} by {name}" if previously used.

3. Data Source (tRPC endpoint)#

admin.getMaintenanceStatus.useQuery(undefined, { refetchInterval: 30000 }); // 30s frequent
admin.toggleMaintenanceMode.useMutation({
  enabled: z.boolean(),
  customMessage: z.string().max(500).optional(),
  estimatedReturnAt: z.date().optional(),
  allowAdminBypass: z.boolean().default(true),
});

4. Zod Schema#

const MaintenanceModeSchema = z.object({
  enabled: z.boolean().default(false),
  customMessage: z.string().max(500).default("We're performing scheduled maintenance. Back in a few minutes."),
  estimatedReturnAt: z.date().optional().nullable(),
  allowAdminBypass: z.boolean().default(true),
  lastToggledAt: z.date().optional(),
  lastToggledBy: z.string().optional(), // user name
  toggleHistory: z.array(z.object({
    action: z.enum(["ENABLED", "DISABLED"]),
    at: z.date(),
    by: z.string(),
    reason: z.string().optional(),
  })).optional(),
});

5. Fetch Frequency#

  • Frequent (30000ms / 30s): Maintenance status is critical for platform availability. 30s polling.
  • On-demand: After toggle mutation, invalidate and refetch immediately.
  • SSE: When maintenance mode is toggled by another admin, push update to all admin sessions.

6. Data Manipulations#

  • Toggle animation: Switch animates with duration-300 ease. Circle slides left/right with spring feel.
  • Preview: Live preview of maintenance page rendered in a card mock-up showing the custom message and estimated return time.
  • History: Last 5 toggles shown in a collapsible "History" accordion below the card.
  • Reason field: When toggling ON, prompt admin for reason (optional but encouraged): "Why are you enabling maintenance mode?" (deploy, migration, incident, etc.). Logged in audit trail.

7. Rationale#

  • Big red toggle: This is a nuclear option. It needs to be prominent, hard to miss, and hard to trigger accidentally. The large red toggle + warning icon + red card makes it visually distinct from all other settings.
  • Live preview: Admin sees exactly what users will experience before committing. Reduces anxiety about breaking the user experience.
  • Admin bypass: Critical so admins can continue working during maintenance (e.g., verifying the fix before turning off maintenance mode).
  • 30s polling: If another admin toggles it, everyone needs to know. Prevents conflicting actions.
  • Audit trail: Every toggle is logged with reason, who, when. Essential for post-incident review.
  • Custom message: During a known migration, admin can set "Database migration in progress — back by 3:00 PM IST." During an incident, "We're investigating an issue — back soon."

8. Interaction Flows#

  • Toggle ON: Click toggle → confirmation modal: "Enable maintenance mode? All non-admin users will be blocked. Admins can still access." + optional reason textarea → click "Enable" → mutation → card turns red → preview updates → toast "Maintenance mode enabled. Users blocked." → SSE broadcast to all clients → all non-admin users see maintenance page immediately.
  • Toggle OFF: Click toggle → confirmation: "Disable maintenance mode? Users will regain access immediately." → click "Disable" → mutation → card returns to white → toast "Maintenance mode disabled. Users can access." → SSE broadcast.
  • Edit message: Type in textarea → preview updates live in real-time.
  • Set estimated return: Pick datetime → preview shows "Expected back: {time}".
  • History expand: Click "History" → accordion opens → shows last 5 toggles with timestamps and reasons.
  • Impersonation: Toggle is hidden entirely during impersonation. Cannot see or modify maintenance mode. Only visible in non-impersonated admin sessions.

9. Error States#

  • Toggle fail: If mutation fails (e.g., DB connection issue), toggle reverts to original state + red toast "Failed to toggle maintenance mode. Check system status."
  • Concurrent toggle: If another admin toggled it while you were viewing, banner: "Maintenance mode was {enabled/disabled} by {name} at {time}." → reload to sync.
  • Already in maintenance: If navigating to /admin/system while platform is in maintenance mode, show red banner at top: "⚠️ Platform is in maintenance mode. Users are blocked."
  • Preview fail: If preview can't render, show "Preview unavailable. Toggle to test."
  • No permission: Non-ADMIN sees 403 before reaching this widget.

10. Role-Based Variations#

  • ADMIN: Full toggle control, edit message, set return time, view history.
  • Other roles: No access to admin routes at all.
  • Impersonation: Toggle hidden. Admin cannot accidentally put platform in maintenance mode while impersonating a client. Section is replaced with: "Maintenance mode controls are hidden during impersonation. End session to access."