feat(account-setup): enhance localization and UI for account setup process

- Added new language support and improved translations for account setup steps across multiple languages.
- Updated the organization step to streamline user input and enhance suggestions for organization names.
- Refactored task management components to improve user experience when adding and managing tasks.
- Removed outdated CSS for admin center components to simplify styling and improve maintainability.
- Introduced new UI elements and transitions for a more engaging account setup experience.
- Enhanced Redux state management to accommodate new features and localization updates.
This commit is contained in:
chamikaJ
2025-07-25 12:50:19 +05:30
parent b688f8e114
commit 7dc3dedda5
22 changed files with 1003 additions and 1073 deletions

View File

@@ -57,42 +57,38 @@ const AboutYouPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, ha
<div className="w-full">
<div className="text-center mb-8">
<Title level={3} className="mb-2" style={{ color: token?.colorText }}>
Tell us about yourself
{t('aboutYouStepTitle')}
</Title>
<Paragraph className="text-base" style={{ color: token?.colorTextSecondary }}>
Help us personalize your experience
{t('aboutYouStepDescription')}
</Paragraph>
</div>
{/* Organization Type */}
<Form.Item className="mb-8">
<label className="block font-medium text-base mb-4" style={{ color: token?.colorText }}>
What best describes your organization?
{t('orgTypeQuestion')}
</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-1">
{organizationTypeOptions.map((option) => {
const isSelected = surveyData.organization_type === option.value;
return (
<button
key={option.value}
onClick={() => handleSurveyDataChange('organization_type', option.value)}
className={`
p-4 rounded-lg border-2 transition-all duration-200 text-left
hover:shadow-md hover:scale-[1.02] active:scale-[0.98]
${isSelected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}
`}
className={`p-2 rounded border transition-all duration-200 text-left hover:shadow-sm ${isSelected ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`}
style={{
backgroundColor: isSelected ? undefined : token?.colorBgContainer,
borderColor: isSelected ? undefined : token?.colorBorder,
}}
>
<div className="flex items-center space-x-3">
<span className="text-2xl">{option.icon}</span>
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded-full border flex items-center justify-center ${isSelected ? 'border-blue-500 bg-blue-500' : 'border-gray-300 dark:border-gray-600'}`}>
{isSelected && <div className="w-1.5 h-1.5 bg-white rounded-full"></div>}
</div>
<span className="text-base">{option.icon}</span>
<span
className={`font-medium ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
className={`font-medium text-xs ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
style={{ color: isSelected ? undefined : token?.colorText }}
>
{option.label}
@@ -107,32 +103,28 @@ const AboutYouPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, ha
{/* User Role */}
<Form.Item className="mb-4">
<label className="block font-medium text-base mb-4" style={{ color: token?.colorText }}>
What's your role?
{t('userRoleQuestion')}
</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-1">
{userRoleOptions.map((option) => {
const isSelected = surveyData.user_role === option.value;
return (
<button
key={option.value}
onClick={() => handleSurveyDataChange('user_role', option.value)}
className={`
p-4 rounded-lg border-2 transition-all duration-200 text-left
hover:shadow-md hover:scale-[1.02] active:scale-[0.98]
${isSelected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}
`}
className={`p-2 rounded border transition-all duration-200 text-left hover:shadow-sm ${isSelected ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`}
style={{
backgroundColor: isSelected ? undefined : token?.colorBgContainer,
borderColor: isSelected ? undefined : token?.colorBorder,
}}
>
<div className="flex items-center space-x-3">
<span className="text-2xl">{option.icon}</span>
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded-full border flex items-center justify-center ${isSelected ? 'border-blue-500 bg-blue-500' : 'border-gray-300 dark:border-gray-600'}`}>
{isSelected && <div className="w-1.5 h-1.5 bg-white rounded-full"></div>}
</div>
<span className="text-base">{option.icon}</span>
<span
className={`font-medium ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
className={`font-medium text-xs ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
style={{ color: isSelected ? undefined : token?.colorText }}
>
{option.label}
@@ -160,21 +152,13 @@ const YourNeedsPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
{ value: 'other', label: t('mainUseCasesOther'), description: 'Something else' },
];
// Use the passed handler or fall back to regular handler
const onUseCaseClick = (value: UseCase) => {
if (handleUseCaseToggle) {
handleUseCaseToggle(value);
} else {
const currentUseCases = surveyData.main_use_cases || [];
const isSelected = currentUseCases.includes(value);
let newUseCases;
if (isSelected) {
newUseCases = currentUseCases.filter(useCase => useCase !== value);
} else {
newUseCases = [...currentUseCases, value];
}
const newUseCases = isSelected ? currentUseCases.filter(useCase => useCase !== value) : [...currentUseCases, value];
handleSurveyDataChange('main_use_cases', newUseCases);
}
};
@@ -183,60 +167,43 @@ const YourNeedsPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
<div className="w-full">
<div className="text-center mb-8">
<Title level={3} className="mb-2" style={{ color: token?.colorText }}>
What are your main needs?
{t('yourNeedsStepTitle')}
</Title>
<Paragraph className="text-base" style={{ color: token?.colorTextSecondary }}>
Select all that apply to help us set up your workspace
{t('yourNeedsStepDescription')}
</Paragraph>
</div>
{/* Main Use Cases */}
<Form.Item className="mb-8">
<label className="block font-medium text-base mb-4" style={{ color: token?.colorText }}>
How will you primarily use Worklenz?
{t('yourNeedsQuestion')}
</label>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 gap-1">
{useCaseOptions.map((option) => {
const isSelected = (surveyData.main_use_cases || []).includes(option.value);
return (
<button
key={option.value}
onClick={() => onUseCaseClick(option.value)}
className={`
p-5 rounded-lg border-2 transition-all duration-200 text-left
hover:shadow-md hover:scale-[1.02] active:scale-[0.98]
${isSelected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}
`}
className={`p-2 rounded border transition-all duration-200 text-left hover:shadow-sm ${isSelected ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`}
style={{
backgroundColor: isSelected ? undefined : token?.colorBgContainer,
borderColor: isSelected ? undefined : token?.colorBorder,
}}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<h4
className={`font-semibold text-base mb-1 ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
style={{ color: isSelected ? undefined : token?.colorText }}
>
{option.label}
</h4>
<p
className="text-sm"
style={{ color: token?.colorTextSecondary }}
>
{option.description}
</p>
</div>
{isSelected && (
<div className="ml-3 text-blue-500">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 20 20">
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded border flex items-center justify-center ${isSelected ? 'border-blue-500 bg-blue-500' : 'border-gray-300 dark:border-gray-600'}`}>
{isSelected && (
<svg width="10" height="10" fill="white" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
)}
)}
</div>
<div className="flex-1">
<span className={`font-medium text-xs ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`} style={{ color: isSelected ? undefined : token?.colorText }}>{option.label}</span>
<span className="text-xs ml-2" style={{ color: token?.colorTextSecondary }}>- {option.description}</span>
</div>
</div>
</button>
);
@@ -244,7 +211,7 @@ const YourNeedsPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
</div>
{surveyData.main_use_cases && surveyData.main_use_cases.length > 0 && (
<p className="mt-3 text-sm" style={{ color: token?.colorTextSecondary }}>
{surveyData.main_use_cases.length} selected
{surveyData.main_use_cases.length} {t('selected')}
</p>
)}
</Form.Item>
@@ -252,7 +219,7 @@ const YourNeedsPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
{/* Previous Tools */}
<Form.Item className="mb-4">
<label className="block font-medium text-base mb-2" style={{ color: token?.colorText }}>
What tools have you used before? (Optional)
{t('previousToolsLabel')}
</label>
<TextArea
placeholder="e.g., Asana, Trello, Jira, Monday.com, etc."
@@ -260,11 +227,7 @@ const YourNeedsPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
onChange={(e) => handleSurveyDataChange('previous_tools', e.target.value)}
autoSize={{ minRows: 3, maxRows: 5 }}
className="text-base"
style={{
backgroundColor: token?.colorBgContainer,
borderColor: token?.colorBorder,
color: token?.colorText
}}
style={{ backgroundColor: token?.colorBgContainer, borderColor: token?.colorBorder, color: token?.colorText }}
/>
</Form.Item>
</div>
@@ -288,46 +251,37 @@ const DiscoveryPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
<div className="w-full">
<div className="text-center mb-8">
<Title level={3} className="mb-2" style={{ color: token?.colorText }}>
One last thing...
{t('discoveryTitle')}
</Title>
<Paragraph className="text-base" style={{ color: token?.colorTextSecondary }}>
Help us understand how you discovered Worklenz
{t('discoveryDescription')}
</Paragraph>
</div>
{/* How Heard About */}
<Form.Item className="mb-8">
<label className="block font-medium text-base mb-4" style={{ color: token?.colorText }}>
How did you hear about us?
{t('discoveryQuestion')}
</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
<div className="grid grid-cols-1 md:grid-cols-2 gap-1">
{howHeardAboutOptions.map((option) => {
const isSelected = surveyData.how_heard_about === option.value;
return (
<button
key={option.value}
onClick={() => handleSurveyDataChange('how_heard_about', option.value)}
className={`
p-4 rounded-lg border-2 transition-all duration-200
hover:shadow-md hover:scale-[1.02] active:scale-[0.98]
${isSelected
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
}
`}
className={`p-2 rounded border transition-all duration-200 hover:shadow-sm ${isSelected ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'}`}
style={{
backgroundColor: isSelected ? undefined : token?.colorBgContainer,
borderColor: isSelected ? undefined : token?.colorBorder,
}}
>
<div className="flex flex-col items-center space-y-2">
<span className="text-3xl">{option.icon}</span>
<span
className={`font-medium text-sm ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`}
style={{ color: isSelected ? undefined : token?.colorText }}
>
{option.label}
</span>
<div className="flex items-center space-x-2">
<div className={`w-3 h-3 rounded-full border flex items-center justify-center ${isSelected ? 'border-blue-500 bg-blue-500' : 'border-gray-300 dark:border-gray-600'}`}>
{isSelected && <div className="w-1.5 h-1.5 bg-white rounded-full"></div>}
</div>
<span className="text-base">{option.icon}</span>
<span className={`font-medium text-xs ${isSelected ? 'text-blue-600 dark:text-blue-400' : ''}`} style={{ color: isSelected ? undefined : token?.colorText }}>{option.label}</span>
</div>
</button>
);
@@ -335,22 +289,10 @@ const DiscoveryPage: React.FC<SurveyPageProps> = ({ styles, token, surveyData, h
</div>
</Form.Item>
{/* Success Message */}
<div
className="mt-12 p-6 rounded-lg text-center"
style={{
backgroundColor: token?.colorSuccessBg,
borderColor: token?.colorSuccessBorder,
border: '1px solid'
}}
>
<div className="mt-12 p-1.5 rounded-lg text-center" style={{ backgroundColor: token?.colorSuccessBg, borderColor: token?.colorSuccessBorder, border: '1px solid' }}>
<div className="text-4xl mb-3">🎉</div>
<Title level={4} style={{ color: token?.colorText, marginBottom: 8 }}>
You're all set!
</Title>
<Paragraph style={{ color: token?.colorTextSecondary, marginBottom: 0 }}>
Let's create your first project and get started with Worklenz
</Paragraph>
<Title level={4} style={{ color: token?.colorText, marginBottom: 8 }}>{t('allSetTitle')}</Title>
<Paragraph style={{ color: token?.colorTextSecondary, marginBottom: 0 }}>{t('allSetDescription')}</Paragraph>
</div>
</div>
);
@@ -365,15 +307,10 @@ export const SurveyStep: React.FC<Props> = ({ onEnter, styles, isDarkMode, token
dispatch(setSurveyData({ [field]: value }));
};
// Handle keyboard navigation
React.useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === 'Enter') {
const isValid =
(surveySubStep === 0 && surveyData.organization_type && surveyData.user_role) ||
(surveySubStep === 1 && surveyData.main_use_cases && surveyData.main_use_cases.length > 0) ||
(surveySubStep === 2 && surveyData.how_heard_about);
const isValid = (surveySubStep === 0 && surveyData.organization_type && surveyData.user_role) || (surveySubStep === 1 && surveyData.main_use_cases && surveyData.main_use_cases.length > 0) || (surveySubStep === 2 && surveyData.how_heard_about);
if (isValid && surveySubStep < 2) {
dispatch(setSurveySubStep(surveySubStep + 1));
} else if (isValid && surveySubStep === 2) {
@@ -381,71 +318,33 @@ export const SurveyStep: React.FC<Props> = ({ onEnter, styles, isDarkMode, token
}
}
};
window.addEventListener('keypress', handleKeyPress);
return () => window.removeEventListener('keypress', handleKeyPress);
}, [surveySubStep, surveyData, dispatch, onEnter]);
// Handle multi-select for use cases
const handleUseCaseToggle = (value: UseCase) => {
const currentUseCases = surveyData.main_use_cases || [];
const isSelected = currentUseCases.includes(value);
let newUseCases;
if (isSelected) {
newUseCases = currentUseCases.filter(useCase => useCase !== value);
} else {
newUseCases = [...currentUseCases, value];
}
const newUseCases = isSelected ? currentUseCases.filter(useCase => useCase !== value) : [...currentUseCases, value];
handleSurveyDataChange('main_use_cases', newUseCases);
};
const getSubStepTitle = () => {
switch (surveySubStep) {
case 0:
return 'About You';
case 1:
return 'Your Needs';
case 2:
return 'Discovery';
default:
return '';
case 0: return t('aboutYouStepName');
case 1: return t('yourNeedsStepName');
case 2: return t('discoveryStepName');
default: return '';
}
};
// Create modified page props with custom handler for use cases
const surveyPages = [
<AboutYouPage
key="about-you"
styles={styles}
isDarkMode={isDarkMode}
token={token}
surveyData={surveyData}
handleSurveyDataChange={handleSurveyDataChange}
/>,
<YourNeedsPage
key="your-needs"
styles={styles}
isDarkMode={isDarkMode}
token={token}
surveyData={surveyData}
handleSurveyDataChange={handleSurveyDataChange}
handleUseCaseToggle={handleUseCaseToggle}
/>,
<DiscoveryPage
key="discovery"
styles={styles}
isDarkMode={isDarkMode}
token={token}
surveyData={surveyData}
handleSurveyDataChange={handleSurveyDataChange}
/>
<AboutYouPage key="about-you" styles={styles} isDarkMode={isDarkMode} token={token} surveyData={surveyData} handleSurveyDataChange={handleSurveyDataChange} />,
<YourNeedsPage key="your-needs" styles={styles} isDarkMode={isDarkMode} token={token} surveyData={surveyData} handleSurveyDataChange={handleSurveyDataChange} handleUseCaseToggle={handleUseCaseToggle} />,
<DiscoveryPage key="discovery" styles={styles} isDarkMode={isDarkMode} token={token} surveyData={surveyData} handleSurveyDataChange={handleSurveyDataChange} />
];
// Check if current step is valid for main Continue button
React.useEffect(() => {
// Reset sub-step when entering survey step
dispatch(setSurveySubStep(0));
}, []);
@@ -454,19 +353,10 @@ export const SurveyStep: React.FC<Props> = ({ onEnter, styles, isDarkMode, token
{/* Progress Indicator */}
<div className="mb-8">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium" style={{ color: token?.colorTextSecondary }}>
Step {surveySubStep + 1} of 3: {getSubStepTitle()}
</span>
<span className="text-sm" style={{ color: token?.colorTextSecondary }}>
{Math.round(((surveySubStep + 1) / 3) * 100)}%
</span>
<span className="text-sm font-medium" style={{ color: token?.colorTextSecondary }}>Step {surveySubStep + 1} of 3: {getSubStepTitle()}</span>
<span className="text-sm" style={{ color: token?.colorTextSecondary }}>{Math.round(((surveySubStep + 1) / 3) * 100)}%</span>
</div>
<Progress
percent={Math.round(((surveySubStep + 1) / 3) * 100)}
showInfo={false}
strokeColor={token?.colorPrimary}
className="mb-0"
/>
<Progress percent={Math.round(((surveySubStep + 1) / 3) * 100)} showInfo={false} strokeColor={token?.colorPrimary} className="mb-0" />
</div>
{/* Current Page Content */}