diff --git a/src/components/BannerForm.js b/src/components/BannerForm.js index 6c49c6a6..2035bb60 100644 --- a/src/components/BannerForm.js +++ b/src/components/BannerForm.js @@ -65,6 +65,11 @@ const BackgroundPreview = ({ src, backgroundColor }) => { const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { const [options, setOptions] = useState({ username: '', + additionalUsers: [], + isCompanyBanner: false, + companyLogoUrl: '', + companyLogoKind: 'no', + companyLogoUploadUrl: '', backgroundColor: '#5badd6', backgroundImageUrl: '', displayBadgeCount: true, @@ -94,6 +99,7 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { displayAgentblazerRank: true, }); const [uploadedFile, setUploadedFile] = useState(null); + const [uploadedLogoFile, setUploadedLogoFile] = useState(null); const [showOptions, setShowOptions] = useState(false); const [isGenerating, setIsGenerating] = useState(false); const [usernameError, setUsernameError] = useState(''); @@ -137,8 +143,28 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { handleCustomUrlChange(e.target.value, setOptions, setBackgroundImageUrlError); }; - const handleImageChange = async (e) => { - await handleFileChange(e.target.files[0], setBackgroundImageUrlError, setOptions, setUploadedFile); + const handleImageChange = async (e, isCompanyLogo = false) => { + if (isCompanyLogo) { + const file = e.target.files[0]; + if (!file) return; + + try { + const reader = new FileReader(); + reader.onload = () => { + setOptions({ + ...options, + companyLogoUrl: reader.result, + companyLogoUploadUrl: reader.result, + }); + setUploadedLogoFile(file); + }; + reader.readAsDataURL(file); + } catch (error) { + console.error('Error reading company logo file:', error); + } + } else { + await handleFileChange(e.target.files[0], setBackgroundImageUrlError, setOptions, setUploadedFile); + } }; const handlePredefinedImage = (src) => { @@ -151,6 +177,7 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { setIsGenerating(true); setShowOptions(false); + // Validate primary username const usernameFormatResult = validateUsernameFormat(options.username.toLowerCase()); if (!usernameFormatResult.valid) { setMainError(new Error(usernameFormatResult.message)); @@ -159,20 +186,66 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { return; } - const usernameApiResult = await validateUsernameWithApi(options.username.toLowerCase()); + // Validate additional usernames if company banner is enabled + if (options.isCompanyBanner && options.additionalUsers.length > 0) { + const additionalUsernameErrors = []; + for (let i = 0; i < options.additionalUsers.length; i++) { + const username = options.additionalUsers[i]; + if (username) { + const formatResult = validateUsernameFormat(username.toLowerCase()); + if (!formatResult.valid) { + additionalUsernameErrors.push(`Team member ${i + 1}: ${formatResult.message}`); + } + } + } + + if (additionalUsernameErrors.length > 0) { + const error = new Error(`Validation failed: ${additionalUsernameErrors.join('. ')}`); + setMainError(error); + onValidationError(error, options); + setIsGenerating(false); + return; + } + + // Validate all usernames with API + const allUsernames = [options.username, ...options.additionalUsers.filter((u) => u)]; + const apiValidations = await Promise.all( + allUsernames.map((username) => validateUsernameWithApi(username.toLowerCase())) + ); + + const apiErrors = apiValidations + .map((result, index) => (!result.valid ? `${allUsernames[index]}: ${result.message}` : null)) + .filter((error) => error); + + if (apiErrors.length > 0) { + const error = new Error(`API validation failed: ${apiErrors.join('. ')}`); + setMainError(error); + onValidationError(error, options); + setIsGenerating(false); + return; + } + } else { + // Single user validation + const usernameApiResult = await validateUsernameWithApi(options.username.toLowerCase()); + if (!usernameApiResult.valid) { + const error = new Error(`Validation failed: ${usernameApiResult.message}`); + setMainError(error); + onValidationError(error, options); + setIsGenerating(false); + return; + } + } + + // Validate background image if using custom URL const imageUrlValidation = options.backgroundKind === 'customUrl' ? await validateImageUrl(options.customBackgroundImageUrl) : { valid: true }; - if (!usernameApiResult.valid || !imageUrlValidation.valid) { - const errorMessages = []; - if (!usernameApiResult.valid) errorMessages.push(usernameApiResult.message); - if (!imageUrlValidation.valid) errorMessages.push(imageUrlValidation.message); - - const validationError = new Error(`Validation failed: ${errorMessages.join('. And ')}`); - setMainError(validationError); - onValidationError(validationError, options); + if (!imageUrlValidation.valid) { + const error = new Error(`Validation failed: ${imageUrlValidation.message}`); + setMainError(error); + onValidationError(error, options); setIsGenerating(false); return; } @@ -184,6 +257,8 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { backgroundImageUrl, lastXCertifications: options.lastXCertifications ? parseInt(options.lastXCertifications) : undefined, lastXSuperbadges: options.lastXSuperbadges ? parseInt(options.lastXSuperbadges) : undefined, + // Clean up additionalUsers by removing empty strings + additionalUsers: options.additionalUsers.filter((username) => username.trim()), }); setIsGenerating(false); @@ -197,23 +272,23 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { type='text' value={options.username} onChange={handleUsernameChange} - onBlur={handleUsernameBlur} // Add onBlur event to validate username - placeholder='Enter Trailhead username' // Add placeholder + onBlur={handleUsernameBlur} + placeholder='Enter primary Trailhead username' required className={`input ${validationResult?.state === 'invalid' ? 'input-error' : ''} ${validationResult?.state === 'private' ? 'input-warning' : ''} ${validationResult?.state === 'ok' ? 'input-success' : ''}`} name='trailhead-username' autoComplete='off' - data-lpignore='true' // LastPass specific attribute to ignore + data-lpignore='true' data-form-type='other' /> {validationResult && (
{validationResult.state === 'ok' ? ( - // Checkmark + ) : validationResult.state === 'private' ? ( - // Yellow warning + ) : ( - // Red cross + )}
)} @@ -223,6 +298,108 @@ const BannerForm = ({ onSubmit, setMainError, onValidationError }) => { )} + +
+ +
+ + {options.isCompanyBanner && ( +
+

Company Banner Options

+
+

Company Logo

+ + {options.companyLogoKind === 'url' && ( +
+ +

+
+ )} + {options.companyLogoKind === 'upload' && ( +
+ + {uploadedLogoFile &&

Selected file: {uploadedLogoFile.name}

} +

+
+ )} +
+
+

Additional Team Members

+ {options.additionalUsers.map((user, index) => ( +
+ { + const newUsers = [...options.additionalUsers]; + newUsers[index] = e.target.value; + setOptions({ ...options, additionalUsers: newUsers }); + }} + placeholder={`Enter team member ${index + 1} username`} + className='input' + /> + +
+ ))} + +
+
+ )} + {!isGenerating && (