import { mdiArrowRight, mdiBrain, mdiChartLine, mdiClipboardTextClockOutline, mdiCreation, mdiLightningBolt, mdiRobotOutline, mdiShieldCheck, mdiTarget, } from '@mdi/js'; import Head from 'next/head'; import Link from 'next/link'; import React, { ReactElement, useEffect, useMemo, useState } from 'react'; import axios from 'axios'; import BaseButton from '../components/BaseButton'; import BaseIcon from '../components/BaseIcon'; import CardBox from '../components/CardBox'; import LayoutAuthenticated from '../layouts/Authenticated'; import NotificationBar from '../components/NotificationBar'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; import { hasPermission } from '../helpers/userPermissions'; import { useAppSelector } from '../stores/hooks'; type TemplateSummary = { key: string; title: string; accent: string; description: string; defaultCadence: string; defaultVisibility: string; questionCount: number; previewQuestions: Array<{ prompt: string; question_type: string; }>; }; type RecentSurvey = { id: string; title: string; description: string; status: string; visibility: string; cadence: string; opens_at: string | null; closes_at: string | null; createdAt: string; templateName: string; questionCount: number; responseCount: number; invitationCount: number; responseRate: number; latestAnalysisAt: string | null; latestAnalysisScore: number | null; }; type LaunchpadSummary = { overview: { totalSurveys: number; activeSurveys: number; totalResponses: number; totalInvitations: number; responseRate: number; aiReadyCount: number; activeDepartmentCount: number; }; templates: TemplateSummary[]; recentSurveys: RecentSurvey[]; responseTrend: Array<{ key: string; label: string; count: number; }>; departmentBreakdown: Array<{ id: string; name: string; responseCount: number; }>; }; type AnalysisResult = { overallSentimentScore: number | null; executiveSummary: string; topThemes: Array<{ theme: string; confidence: string; quote: string; insight: string; }>; departmentRisks: Array<{ department: string; riskLevel: string; riskScore: number; signal: string; }>; recommendedActions: Array<{ title: string; owner: string; timeline: string; template: string; }>; }; const cadenceLabels: Record = { one_time: 'One-time', weekly: 'Weekly', biweekly: 'Biweekly', monthly: 'Monthly', quarterly: 'Quarterly', }; const visibilityLabels: Record = { anonymous: 'Anonymous', identified: 'Identified', }; const statusClassNames: Record = { draft: 'bg-slate-500/15 text-slate-200', scheduled: 'bg-sky-500/15 text-sky-200', sending: 'bg-violet-500/15 text-violet-200', active: 'bg-emerald-500/15 text-emerald-200', closed: 'bg-amber-500/15 text-amber-200', archived: 'bg-zinc-500/15 text-zinc-300', }; const questionTypeLabels: Record = { rating_1_10: '1–10 rating', multiple_choice: 'Multiple choice', open_text: 'Open text', emoji_reaction: 'Emoji reaction', }; const getDefaultTitle = (template?: TemplateSummary) => { if (!template) { return ''; } const today = new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', }); return `${template.title} • ${today}`; }; const PulseHQPage = () => { const { currentUser } = useAppSelector((state) => state.auth); const canCreateSurveys = Boolean(currentUser && hasPermission(currentUser, 'CREATE_SURVEYS')); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const [pageError, setPageError] = useState(''); const [selectedTemplateKey, setSelectedTemplateKey] = useState('weekly_pulse'); const [launching, setLaunching] = useState(false); const [launchError, setLaunchError] = useState(''); const [launchSuccess, setLaunchSuccess] = useState(null); const [selectedSurveyId, setSelectedSurveyId] = useState(''); const [analysisLoading, setAnalysisLoading] = useState(false); const [analysisError, setAnalysisError] = useState(''); const [analysisResult, setAnalysisResult] = useState(null); const [form, setForm] = useState({ templateKey: 'weekly_pulse', title: '', description: '', cadence: 'weekly', visibility: 'anonymous', opens_at: '', closes_at: '', include_open_text: true, }); const selectedTemplate = useMemo( () => summary?.templates.find((template) => template.key === selectedTemplateKey) || null, [selectedTemplateKey, summary?.templates], ); const loadSummary = async (preferredSurveyId?: string) => { try { setLoading(true); setPageError(''); const response = await axios.get('/surveys/launchpad/summary'); const nextSummary = response.data as LaunchpadSummary; setSummary(nextSummary); setSelectedSurveyId((current) => { if (preferredSurveyId) { return preferredSurveyId; } if (current && nextSummary.recentSurveys.some((survey) => survey.id === current)) { return current; } return nextSummary.recentSurveys[0]?.id || ''; }); setSelectedTemplateKey((current) => { if (nextSummary.templates.some((template) => template.key === current)) { return current; } return nextSummary.templates[0]?.key || 'weekly_pulse'; }); setForm((current) => { const template = nextSummary.templates.find((item) => item.key === current.templateKey) || nextSummary.templates[0]; return { ...current, templateKey: template?.key || current.templateKey, cadence: current.cadence || template?.defaultCadence || 'weekly', visibility: current.visibility || template?.defaultVisibility || 'anonymous', description: current.description || template?.description || '', title: current.title || getDefaultTitle(template), }; }); } catch (error: any) { console.error('Failed to load Pulse HQ summary:', error); setPageError(error?.response?.data || error?.message || 'Failed to load Pulse HQ.'); } finally { setLoading(false); } }; useEffect(() => { loadSummary(); }, []); useEffect(() => { if (!selectedTemplate) { return; } setForm((current) => ({ ...current, templateKey: selectedTemplate.key, description: current.description && current.templateKey === selectedTemplate.key ? current.description : selectedTemplate.description, cadence: selectedTemplate.defaultCadence, visibility: selectedTemplate.defaultVisibility, title: current.title ? current.title : getDefaultTitle(selectedTemplate), })); }, [selectedTemplateKey, selectedTemplate?.key]); const trendMax = useMemo(() => { const counts = summary?.responseTrend.map((item) => item.count) || []; return Math.max(...counts, 1); }, [summary?.responseTrend]); const selectedSurvey = useMemo( () => summary?.recentSurveys.find((survey) => survey.id === selectedSurveyId) || null, [selectedSurveyId, summary?.recentSurveys], ); const handleFieldChange = ( event: React.ChangeEvent, ) => { const { name, value, type } = event.target; const nextValue = type === 'checkbox' ? (event.target as HTMLInputElement).checked : value; setForm((current) => ({ ...current, [name]: nextValue, })); }; const handleTemplateSelect = (templateKey: string) => { setSelectedTemplateKey(templateKey); const template = summary?.templates.find((item) => item.key === templateKey); if (!template) { return; } setForm((current) => ({ ...current, templateKey, cadence: template.defaultCadence, visibility: template.defaultVisibility, description: template.description, title: getDefaultTitle(template), })); }; const handleLaunch = async (event: React.FormEvent) => { event.preventDefault(); setLaunchError(''); setLaunchSuccess(null); setAnalysisError(''); if (!form.title.trim()) { setLaunchError('Give this launch a clear title so your team recognizes it in the dashboard.'); return; } if (form.opens_at && form.closes_at && new Date(form.opens_at) > new Date(form.closes_at)) { setLaunchError('Choose a close date that comes after the open date.'); return; } try { setLaunching(true); const response = await axios.post('/surveys/launchpad/create', form); const payload = response.data; setLaunchSuccess({ surveyId: payload.survey.id, title: payload.survey.title, questionCount: payload.questionCount, templateTitle: payload.template.title, }); setAnalysisResult(null); await loadSummary(payload.survey.id); } catch (error: any) { console.error('Failed to launch survey:', error); setLaunchError(error?.response?.data || error?.message || 'Survey launch failed.'); } finally { setLaunching(false); } }; const handleGenerateAnalysis = async (surveyId: string) => { if (!surveyId) { setAnalysisError('Pick a survey with responses before generating AI analysis.'); return; } try { setAnalysisLoading(true); setAnalysisError(''); const response = await axios.post(`/surveys/${surveyId}/pulse-analysis`); setAnalysisResult(response.data.analysis); await loadSummary(surveyId); } catch (error: any) { console.error('Failed to generate AI analysis:', error); setAnalysisError( error?.response?.data || error?.message || 'AI analysis could not be generated right now.', ); } finally { setAnalysisLoading(false); } }; return ( <> {getPageTitle('Pulse HQ')} {pageError && ( {pageError} )} {launchSuccess && ( } > {`${launchSuccess.title} is ready. ${launchSuccess.questionCount} questions were created from ${launchSuccess.templateTitle}.`} )}
PulseSurvey launchpad

Launch recurring employee pulse surveys in minutes — then turn responses into AI-backed action.

This first slice gives your People team one polished workspace to launch a survey, review recent sends, and generate an executive-ready AI analysis without leaving the app.

{[ { label: 'Survey volume', value: summary?.overview.totalSurveys ?? '—', note: 'Total launches tracked', }, { label: 'Live response rate', value: summary ? `${summary.overview.responseRate}%` : '—', note: 'Across all invitations', }, { label: 'AI-ready surveys', value: summary?.overview.aiReadyCount ?? '—', note: 'Completed responses collected', }, ].map((metric) => (

{metric.label}

{metric.value}

{metric.note}

))}

Template lineup

Choose your first pulse

{summary?.templates.map((template) => { const active = selectedTemplateKey === template.key; return ( ); })}

Workflow preview

This launch creates

{selectedTemplate?.previewQuestions.map((question, index) => (
{`Q${index + 1}`} {questionTypeLabels[question.question_type] || question.question_type}

{question.prompt}

))}
After launch, your survey appears below, inherits cadence + visibility defaults, and becomes eligible for AI analysis as soon as completed responses start coming in.

Launch survey

Create a branded pulse from a proven template

{!canCreateSurveys && (
You have read access to Pulse HQ, but launching surveys requires the CREATE_SURVEYS permission.
)} {launchError && (
{launchError}
)}