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 && (