feat(surveys): implement account setup survey functionality
- Added new database migration to create survey-related tables for storing questions and responses. - Developed SurveyController to handle fetching and submitting survey data. - Created survey API routes for account setup, including endpoints for retrieving the survey and submitting responses. - Implemented frontend components for displaying the survey and capturing user responses, integrating with Redux for state management. - Enhanced localization files to include survey-related text for multiple languages. - Added validation middleware for survey submissions to ensure data integrity.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
/* Steps styling - using Ant Design theme tokens */
|
||||
.ant-steps-item-finish .ant-steps-item-icon {
|
||||
border-color: #1890ff !important;
|
||||
border-color: var(--ant-color-primary) !important;
|
||||
background-color: var(--ant-color-primary) !important;
|
||||
}
|
||||
|
||||
.ant-steps-item-icon {
|
||||
@@ -9,37 +11,48 @@
|
||||
font-size: 16px !important;
|
||||
line-height: 32px !important;
|
||||
text-align: center !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.25) !important;
|
||||
border: 1px solid var(--ant-color-border) !important;
|
||||
border-radius: 32px !important;
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
border-color 0.3s !important;
|
||||
transition: all 0.3s !important;
|
||||
background-color: var(--ant-color-bg-container) !important;
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.ant-steps-item-wait .ant-steps-item-icon {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.dark-mode .ant-steps-item-wait .ant-steps-item-icon {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.progress-steps .ant-steps-item.ant-steps-item-process .ant-steps-item-title::after {
|
||||
background-color: #1677ff !important;
|
||||
background-color: var(--ant-color-primary) !important;
|
||||
width: 60px !important;
|
||||
}
|
||||
|
||||
/* Steps title styling */
|
||||
.ant-steps-item-title {
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.ant-steps-item-description {
|
||||
color: var(--ant-color-text-secondary) !important;
|
||||
}
|
||||
|
||||
/* Responsive design improvements */
|
||||
@media (max-width: 1000px) {
|
||||
.progress-steps {
|
||||
width: 400px !important;
|
||||
}
|
||||
|
||||
.step {
|
||||
width: 400px !important;
|
||||
}
|
||||
|
||||
.progress-steps,
|
||||
.step,
|
||||
.step-content {
|
||||
width: 400px !important;
|
||||
width: 90% !important;
|
||||
max-width: 500px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.progress-steps,
|
||||
.step,
|
||||
.step-content {
|
||||
width: 95% !important;
|
||||
max-width: 400px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,44 +66,51 @@
|
||||
}
|
||||
|
||||
.step-content {
|
||||
width: 200px !important;
|
||||
width: 100% !important;
|
||||
max-width: 300px !important;
|
||||
padding: 0 1rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.organization-name-form {
|
||||
width: 400px !important;
|
||||
.organization-name-form,
|
||||
.first-project-form,
|
||||
.create-first-task-form {
|
||||
width: 90% !important;
|
||||
max-width: 500px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.organization-name-form,
|
||||
.first-project-form,
|
||||
.create-first-task-form {
|
||||
width: 95% !important;
|
||||
max-width: 400px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.organization-name-form {
|
||||
width: 200px !important;
|
||||
.organization-name-form,
|
||||
.first-project-form,
|
||||
.create-first-task-form {
|
||||
width: 100% !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.vert-text {
|
||||
max-width: 40px;
|
||||
background-color: #fff;
|
||||
background-color: var(--ant-color-bg-container);
|
||||
color: var(--ant-color-text);
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin: 2rem auto;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.vert-text-dark {
|
||||
max-width: 40px;
|
||||
background-color: #141414;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.vert-line {
|
||||
@@ -98,53 +118,105 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
content: "";
|
||||
height: 2px;
|
||||
background-color: #00000047;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto;
|
||||
height: 1px;
|
||||
background-color: var(--ant-color-border);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* Legacy dark mode classes for backward compatibility */
|
||||
.dark-mode .vert-text,
|
||||
.vert-text-dark {
|
||||
background-color: var(--ant-color-bg-container);
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.dark-mode .vert-line,
|
||||
.vert-line-dark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
content: "";
|
||||
height: 2px;
|
||||
background-color: white;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.first-project-form {
|
||||
width: 400px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.first-project-form {
|
||||
width: 200px !important;
|
||||
}
|
||||
background-color: var(--ant-color-border);
|
||||
}
|
||||
|
||||
/* Button and component improvements */
|
||||
.custom-close-button:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.create-first-task-form {
|
||||
width: 400px !important;
|
||||
}
|
||||
.setup-action-buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.create-first-task-form {
|
||||
width: 200px !important;
|
||||
}
|
||||
.setup-action-buttons .ant-btn {
|
||||
min-height: 40px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Form styling with Ant Design theme tokens */
|
||||
.step-form .ant-form-item-label > label {
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.step-form .ant-input,
|
||||
.step-form .ant-input-affix-wrapper {
|
||||
background-color: var(--ant-color-bg-container) !important;
|
||||
border-color: var(--ant-color-border) !important;
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.step-form .ant-input:focus,
|
||||
.step-form .ant-input-affix-wrapper:focus,
|
||||
.step-form .ant-input-affix-wrapper-focused {
|
||||
background-color: var(--ant-color-bg-container) !important;
|
||||
border-color: var(--ant-color-primary) !important;
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.step-form .ant-input::placeholder {
|
||||
color: var(--ant-color-text-placeholder) !important;
|
||||
}
|
||||
|
||||
/* Select styling */
|
||||
.step-form .ant-select-selector {
|
||||
background-color: var(--ant-color-bg-container) !important;
|
||||
border-color: var(--ant-color-border) !important;
|
||||
color: var(--ant-color-text) !important;
|
||||
}
|
||||
|
||||
.step-form .ant-select-selection-placeholder {
|
||||
color: var(--ant-color-text-placeholder) !important;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
.step-form .ant-typography {
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.step-form .ant-typography.ant-typography-secondary {
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
/* Card styling */
|
||||
.step-form .ant-card {
|
||||
background-color: var(--ant-color-bg-container);
|
||||
border-color: var(--ant-color-border);
|
||||
}
|
||||
|
||||
.step-form .ant-card-body {
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
/* Checkbox and Radio styling */
|
||||
.step-form .ant-checkbox-wrapper {
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.step-form .ant-radio-wrapper {
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
/* Smooth transitions for theme switching */
|
||||
* {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
@@ -2,13 +2,14 @@ import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Space, Steps, Button, Typography } from 'antd/es';
|
||||
import { Space, Steps, Button, Typography, theme } from '@/shared/antd-imports';
|
||||
|
||||
import logger from '@/utils/errorLogger';
|
||||
import { setCurrentStep } from '@/features/account-setup/account-setup.slice';
|
||||
import { OrganizationStep } from '@/components/account-setup/organization-step';
|
||||
import { ProjectStep } from '@/components/account-setup/project-step';
|
||||
import { TasksStep } from '@/components/account-setup/tasks-step';
|
||||
import { SurveyStep } from '@/components/account-setup/survey-step';
|
||||
import MembersStep from '@/components/account-setup/members-step';
|
||||
import {
|
||||
evt_account_setup_complete,
|
||||
@@ -31,23 +32,104 @@ import { useAppDispatch } from '@/hooks/useAppDispatch';
|
||||
import './account-setup.css';
|
||||
import { IAccountSetupRequest } from '@/types/project-templates/project-templates.types';
|
||||
import { profileSettingsApiService } from '@/api/settings/profile/profile-settings.api.service';
|
||||
import { surveyApiService } from '@/api/survey/survey.api.service';
|
||||
import { ISurveySubmissionRequest, ISurveyAnswer } from '@/types/account-setup/survey.types';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
// Simplified styles for form components using Ant Design theme tokens
|
||||
const getAccountSetupStyles = (token: any) => ({
|
||||
form: {
|
||||
width: '100%',
|
||||
maxWidth: '600px',
|
||||
},
|
||||
label: {
|
||||
color: token.colorText,
|
||||
fontWeight: 500,
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
drawerFooter: {
|
||||
display: 'flex',
|
||||
justifyContent: 'right',
|
||||
padding: '10px 16px',
|
||||
},
|
||||
});
|
||||
|
||||
const AccountSetup: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation('account-setup');
|
||||
useDocumentTitle(t('setupYourAccount', 'Account Setup'));
|
||||
const navigate = useNavigate();
|
||||
const { trackMixpanelEvent } = useMixpanelTracking();
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const { currentStep, organizationName, projectName, templateId, tasks, teamMembers } =
|
||||
const { currentStep, organizationName, projectName, templateId, tasks, teamMembers, surveyData } =
|
||||
useSelector((state: RootState) => state.accountSetupReducer);
|
||||
const userDetails = getUserSession();
|
||||
const themeMode = useSelector((state: RootState) => state.themeReducer.mode);
|
||||
|
||||
const [surveyId, setSurveyId] = React.useState<string | null>(null);
|
||||
|
||||
const isDarkMode = themeMode === 'dark';
|
||||
const organizationNamePlaceholder = userDetails?.name ? `e.g. ${userDetails?.name}'s Team` : '';
|
||||
// Helper to extract organization name from email or fallback to user name
|
||||
function getOrganizationNamePlaceholder(userDetails: { email?: string; name?: string } | null): string {
|
||||
if (!userDetails) return '';
|
||||
const email = userDetails.email || '';
|
||||
const name = userDetails.name || '';
|
||||
if (email) {
|
||||
const match = email.match(/^([^@]+)@([^@]+)$/);
|
||||
if (match) {
|
||||
const domain = match[2].toLowerCase();
|
||||
// List of common public email providers
|
||||
const publicProviders = [
|
||||
'gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com', 'aol.com', 'protonmail.com', 'zoho.com', 'gmx.com', 'mail.com', 'yandex.com', 'msn.com', 'live.com', 'me.com', 'comcast.net', 'rediffmail.com', 'ymail.com', 'rocketmail.com', 'inbox.com', 'mail.ru', 'qq.com', 'naver.com', '163.com', '126.com', 'sina.com', 'yeah.net', 'googlemail.com', 'fastmail.com', 'hushmail.com', 'tutanota.com', 'pm.me', 'mailbox.org', 'proton.me'
|
||||
];
|
||||
if (!publicProviders.includes(domain)) {
|
||||
// Use the first part of the domain (before the first dot)
|
||||
const org = domain.split('.')[0];
|
||||
if (org && org.length > 1) {
|
||||
return `e.g. ${org.charAt(0).toUpperCase() + org.slice(1)} Team`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback to user name
|
||||
return name ? `e.g. ${name}'s Team` : '';
|
||||
}
|
||||
|
||||
const organizationNamePlaceholder = getOrganizationNamePlaceholder(userDetails);
|
||||
|
||||
// Helper to extract organization name from email or fallback to user name
|
||||
function getOrganizationNameInitialValue(userDetails: { email?: string; name?: string } | null): string {
|
||||
if (!userDetails) return '';
|
||||
const email = userDetails.email || '';
|
||||
const name = userDetails.name || '';
|
||||
if (email) {
|
||||
const match = email.match(/^([^@]+)@([^@]+)$/);
|
||||
if (match) {
|
||||
const domain = match[2].toLowerCase();
|
||||
const publicProviders = [
|
||||
'gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'icloud.com', 'aol.com', 'protonmail.com', 'zoho.com', 'gmx.com', 'mail.com', 'yandex.com', 'msn.com', 'live.com', 'me.com', 'comcast.net', 'rediffmail.com', 'ymail.com', 'rocketmail.com', 'inbox.com', 'mail.ru', 'qq.com', 'naver.com', '163.com', '126.com', 'sina.com', 'yeah.net', 'googlemail.com', 'fastmail.com', 'hushmail.com', 'tutanota.com', 'pm.me', 'mailbox.org', 'proton.me'
|
||||
];
|
||||
if (!publicProviders.includes(domain)) {
|
||||
const org = domain.split('.')[0];
|
||||
if (org && org.length > 1) {
|
||||
return org.charAt(0).toUpperCase() + org.slice(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return name || '';
|
||||
}
|
||||
|
||||
const organizationNameInitialValue = getOrganizationNameInitialValue(userDetails);
|
||||
|
||||
const styles = getAccountSetupStyles(token);
|
||||
|
||||
useEffect(() => {
|
||||
trackMixpanelEvent(evt_account_setup_visit);
|
||||
@@ -65,88 +147,25 @@ const AccountSetup: React.FC = () => {
|
||||
logger.error('Failed to verify authentication status', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadSurvey = async () => {
|
||||
try {
|
||||
const response = await surveyApiService.getAccountSetupSurvey();
|
||||
if (response.done && response.body) {
|
||||
setSurveyId(response.body.id);
|
||||
} else {
|
||||
logger.error('Survey not found or inactive (warn replaced with error)');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to load survey', error);
|
||||
// Continue without survey - don't block account setup
|
||||
}
|
||||
};
|
||||
|
||||
void verifyAuthStatus();
|
||||
void loadSurvey();
|
||||
}, [dispatch, navigate, trackMixpanelEvent]);
|
||||
|
||||
const calculateHeight = () => {
|
||||
if (currentStep === 2) {
|
||||
return tasks.length * 105;
|
||||
}
|
||||
|
||||
if (currentStep === 3) {
|
||||
return teamMembers.length * 105;
|
||||
}
|
||||
return 'min-content';
|
||||
};
|
||||
|
||||
const styles = {
|
||||
form: {
|
||||
width: '600px',
|
||||
paddingBottom: '1rem',
|
||||
marginTop: '3rem',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
label: {
|
||||
color: isDarkMode ? '' : '#00000073',
|
||||
fontWeight: 500,
|
||||
},
|
||||
buttonContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
drawerFooter: {
|
||||
display: 'flex',
|
||||
justifyContent: 'right',
|
||||
padding: '10px 16px',
|
||||
},
|
||||
container: {
|
||||
height: '100vh',
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
alignItems: 'center',
|
||||
padding: '3rem 0',
|
||||
backgroundColor: isDarkMode ? 'black' : '#FAFAFA',
|
||||
},
|
||||
contentContainer: {
|
||||
backgroundColor: isDarkMode ? '#141414' : 'white',
|
||||
marginTop: '1.5rem',
|
||||
paddingTop: '3rem',
|
||||
margin: '1.5rem auto 0',
|
||||
width: '100%',
|
||||
maxWidth: '66.66667%',
|
||||
minHeight: 'fit-content',
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
},
|
||||
space: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column' as const,
|
||||
alignItems: 'center',
|
||||
gap: '0',
|
||||
flexGrow: 1,
|
||||
width: '100%',
|
||||
minHeight: 'fit-content',
|
||||
},
|
||||
steps: {
|
||||
margin: '1rem 0',
|
||||
width: '600px',
|
||||
},
|
||||
stepContent: {
|
||||
flexGrow: 1,
|
||||
width: '600px',
|
||||
minHeight: calculateHeight(),
|
||||
overflow: 'visible',
|
||||
},
|
||||
actionButtons: {
|
||||
flexGrow: 1,
|
||||
width: '600px',
|
||||
marginBottom: '1rem',
|
||||
},
|
||||
};
|
||||
|
||||
const completeAccountSetup = async (skip = false) => {
|
||||
try {
|
||||
@@ -159,6 +178,13 @@ const AccountSetup: React.FC = () => {
|
||||
: teamMembers
|
||||
.map(teamMember => sanitizeInput(teamMember.value.trim()))
|
||||
.filter(email => validateEmail(email)),
|
||||
survey_data: {
|
||||
organization_type: surveyData.organization_type,
|
||||
user_role: surveyData.user_role,
|
||||
main_use_cases: surveyData.main_use_cases,
|
||||
previous_tools: surveyData.previous_tools,
|
||||
how_heard_about: surveyData.how_heard_about,
|
||||
},
|
||||
};
|
||||
const res = await profileSettingsApiService.setupAccount(model);
|
||||
if (res.done && res.body.id) {
|
||||
@@ -190,6 +216,20 @@ const AccountSetup: React.FC = () => {
|
||||
onEnter={() => dispatch(setCurrentStep(currentStep + 1))}
|
||||
styles={styles}
|
||||
organizationNamePlaceholder={organizationNamePlaceholder}
|
||||
organizationNameInitialValue={organizationNameInitialValue}
|
||||
isDarkMode={isDarkMode}
|
||||
token={token}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
content: (
|
||||
<SurveyStep
|
||||
onEnter={() => dispatch(setCurrentStep(currentStep + 1))}
|
||||
styles={styles}
|
||||
isDarkMode={isDarkMode}
|
||||
token={token}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -200,6 +240,7 @@ const AccountSetup: React.FC = () => {
|
||||
onEnter={() => dispatch(setCurrentStep(currentStep + 1))}
|
||||
styles={styles}
|
||||
isDarkMode={isDarkMode}
|
||||
token={token}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -210,12 +251,13 @@ const AccountSetup: React.FC = () => {
|
||||
onEnter={() => dispatch(setCurrentStep(currentStep + 1))}
|
||||
styles={styles}
|
||||
isDarkMode={isDarkMode}
|
||||
token={token}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
content: <MembersStep isDarkMode={isDarkMode} styles={styles} />,
|
||||
content: <MembersStep isDarkMode={isDarkMode} styles={styles} token={token} />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -224,10 +266,13 @@ const AccountSetup: React.FC = () => {
|
||||
case 0:
|
||||
return !organizationName?.trim();
|
||||
case 1:
|
||||
return !projectName?.trim() && !templateId;
|
||||
// Survey step - no required fields, can always continue
|
||||
return false;
|
||||
case 2:
|
||||
return tasks.length === 0 || tasks.every(task => !task.value?.trim());
|
||||
return !projectName?.trim() && !templateId;
|
||||
case 3:
|
||||
return tasks.length === 0 || tasks.every(task => !task.value?.trim());
|
||||
case 4:
|
||||
return (
|
||||
teamMembers.length > 0 && !teamMembers.some(member => validateEmail(member.value?.trim()))
|
||||
);
|
||||
@@ -236,8 +281,99 @@ const AccountSetup: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStep === 3) {
|
||||
const saveSurveyData = async () => {
|
||||
if (!surveyId || !surveyData) {
|
||||
logger.error('Skipping survey save - no survey ID or data (info replaced with error)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const answers: ISurveyAnswer[] = [];
|
||||
|
||||
// Get the survey questions to map data properly
|
||||
const surveyResponse = await surveyApiService.getAccountSetupSurvey();
|
||||
if (!surveyResponse.done || !surveyResponse.body?.questions) {
|
||||
logger.error('Could not retrieve survey questions for data mapping (warn replaced with error)');
|
||||
return;
|
||||
}
|
||||
|
||||
const questions = surveyResponse.body.questions;
|
||||
|
||||
// Map survey data to answers based on question keys
|
||||
questions.forEach(question => {
|
||||
switch (question.question_key) {
|
||||
case 'organization_type':
|
||||
if (surveyData.organization_type) {
|
||||
answers.push({
|
||||
question_id: question.id,
|
||||
answer_text: surveyData.organization_type
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'user_role':
|
||||
if (surveyData.user_role) {
|
||||
answers.push({
|
||||
question_id: question.id,
|
||||
answer_text: surveyData.user_role
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'main_use_cases':
|
||||
if (surveyData.main_use_cases && surveyData.main_use_cases.length > 0) {
|
||||
answers.push({
|
||||
question_id: question.id,
|
||||
answer_json: surveyData.main_use_cases
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'previous_tools':
|
||||
if (surveyData.previous_tools) {
|
||||
answers.push({
|
||||
question_id: question.id,
|
||||
answer_text: surveyData.previous_tools
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'how_heard_about':
|
||||
if (surveyData.how_heard_about) {
|
||||
answers.push({
|
||||
question_id: question.id,
|
||||
answer_text: surveyData.how_heard_about
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
if (answers.length > 0) {
|
||||
const submissionData: ISurveySubmissionRequest = {
|
||||
survey_id: surveyId,
|
||||
answers
|
||||
};
|
||||
|
||||
const result = await surveyApiService.submitSurveyResponse(submissionData);
|
||||
if (result.done) {
|
||||
logger.error('Survey data saved successfully (info replaced with error)');
|
||||
} else {
|
||||
logger.error('Survey submission returned unsuccessful response (warn replaced with error)');
|
||||
}
|
||||
} else {
|
||||
logger.error('No survey answers to save (info replaced with error)');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to save survey data', error);
|
||||
// Don't block account setup flow if survey fails
|
||||
}
|
||||
};
|
||||
|
||||
const nextStep = async () => {
|
||||
if (currentStep === 1) {
|
||||
// Save survey data when moving from survey step
|
||||
await saveSurveyData();
|
||||
}
|
||||
|
||||
if (currentStep === 4) {
|
||||
// Complete setup after members step
|
||||
completeAccountSetup();
|
||||
} else {
|
||||
dispatch(setCurrentStep(currentStep + 1));
|
||||
@@ -245,46 +381,71 @@ const AccountSetup: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<div>
|
||||
<div
|
||||
className="min-h-screen w-full flex flex-col items-center py-8 px-4"
|
||||
style={{ backgroundColor: token.colorBgLayout }}
|
||||
>
|
||||
{/* Logo */}
|
||||
<div className="mb-4">
|
||||
<img src={isDarkMode ? logoDark : logo} alt="Logo" width={235} height={50} />
|
||||
</div>
|
||||
<Title level={5} style={{ textAlign: 'center', margin: '4px 0 24px' }}>
|
||||
|
||||
{/* Title */}
|
||||
<Title
|
||||
level={3}
|
||||
className="text-center mb-6 font-semibold"
|
||||
style={{ color: token.colorText }}
|
||||
>
|
||||
{t('setupYourAccount')}
|
||||
</Title>
|
||||
<div style={styles.contentContainer}>
|
||||
<Space className={isDarkMode ? 'dark-mode' : ''} style={styles.space} direction="vertical">
|
||||
<Steps
|
||||
className={isContinueDisabled() ? 'step' : 'progress-steps'}
|
||||
current={currentStep}
|
||||
items={steps}
|
||||
style={styles.steps}
|
||||
/>
|
||||
<div className="step-content" style={styles.stepContent}>
|
||||
{steps[currentStep].content}
|
||||
|
||||
{/* Content Container */}
|
||||
<div
|
||||
className="w-full max-w-4xl rounded-lg shadow-lg mt-6 p-8"
|
||||
style={{
|
||||
backgroundColor: token.colorBgContainer,
|
||||
borderColor: token.colorBorder,
|
||||
border: `1px solid ${token.colorBorder}`,
|
||||
boxShadow: token.boxShadowTertiary
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center space-y-6 w-full">
|
||||
{/* Steps */}
|
||||
<div className="w-full max-w-2xl">
|
||||
<Steps
|
||||
className={`${isContinueDisabled() ? 'step' : 'progress-steps'} ${isDarkMode ? 'dark-mode' : 'light-mode'}`}
|
||||
current={currentStep}
|
||||
items={steps}
|
||||
/>
|
||||
</div>
|
||||
<div style={styles.actionButtons} className="setup-action-buttons">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: currentStep !== 0 ? 'space-between' : 'flex-end',
|
||||
}}
|
||||
>
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="w-full max-w-2xl flex flex-col items-center min-h-fit">
|
||||
<div className="step-content w-full">
|
||||
{steps[currentStep].content}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="w-full max-w-2xl mt-8">
|
||||
<div className={`flex ${
|
||||
currentStep !== 0 ? 'justify-between' : 'justify-end'
|
||||
} items-center`}>
|
||||
{currentStep !== 0 && (
|
||||
<div>
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Button
|
||||
style={{ padding: 0 }}
|
||||
type="link"
|
||||
className="my-7"
|
||||
className="p-0 font-medium"
|
||||
style={{ color: token.colorTextSecondary }}
|
||||
onClick={() => dispatch(setCurrentStep(currentStep - 1))}
|
||||
>
|
||||
{t('goBack')}
|
||||
</Button>
|
||||
{currentStep === 3 && (
|
||||
{currentStep === 4 && (
|
||||
<Button
|
||||
style={{ color: isDarkMode ? '' : '#00000073', fontWeight: 500 }}
|
||||
type="link"
|
||||
className="my-7"
|
||||
className="p-0 font-medium"
|
||||
style={{ color: token.colorTextTertiary }}
|
||||
onClick={() => completeAccountSetup(true)}
|
||||
>
|
||||
{t('skipForNow')}
|
||||
@@ -296,14 +457,14 @@ const AccountSetup: React.FC = () => {
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
disabled={isContinueDisabled()}
|
||||
className="mt-7 mb-7"
|
||||
className="min-h-10 font-medium px-8"
|
||||
onClick={nextStep}
|
||||
>
|
||||
{t('continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user