Skip to content

Commit a80553a

Browse files
authored
Merge pull request #1054 from adobe/fet/gh-id-validation
feat(ue-trial): add GitHub ID validation
2 parents 71c6967 + d8eee95 commit a80553a

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

blocks/ue-trial/ue-trial.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,39 @@
277277
line-height: 1.4;
278278
}
279279

280+
/* GitHub ID validation states */
281+
.ue-trial .form-field.validating input {
282+
border-color: #1473e6;
283+
}
284+
285+
.ue-trial .form-field.valid input {
286+
border-color: #28a745;
287+
}
288+
289+
.ue-trial .form-field.invalid input {
290+
border-color: #dc3545;
291+
}
292+
293+
.ue-trial .validation-message {
294+
font-size: 14px;
295+
margin-top: 5px;
296+
line-height: 1.4;
297+
}
298+
299+
.ue-trial .validation-message.validation-success {
300+
color: #28a745;
301+
}
302+
303+
.ue-trial .validation-message.validation-error {
304+
color: #dc3545;
305+
}
306+
307+
308+
.ue-trial .validation-message.validation-warning {
309+
color: #f39c12;
310+
}
311+
312+
280313
.ue-trial .agreement {
281314
margin-top: 15px;
282315
}

blocks/ue-trial/ue-trial.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,79 @@ function formatStepNameForError(stepKey) {
108108
.trim(); // Remove any leading/trailing spaces
109109
}
110110

111+
/**
112+
* Debounces a function call
113+
* @param {Function} func - The function to debounce
114+
* @param {number} delay - Delay in milliseconds (default: 800)
115+
* @returns {Function} Debounced function
116+
*/
117+
function debounce(func, delay = 800) {
118+
let timeout;
119+
return (...args) => {
120+
clearTimeout(timeout);
121+
timeout = setTimeout(() => func(...args), delay);
122+
};
123+
}
124+
125+
/**
126+
* Validates a GitHub username by checking if it exists
127+
* @param {string} username - The GitHub username to validate
128+
* @returns {Promise<{valid: boolean|null, message: string}>} Validation result
129+
*/
130+
async function validateGitHubId(username) {
131+
if (!username.trim()) {
132+
return { valid: true, message: '' };
133+
}
134+
135+
try {
136+
const response = await fetch(`https://api.github.com/users/${encodeURIComponent(username)}`);
137+
138+
if (response.status === 200) {
139+
return { valid: true, message: 'GitHub user found' };
140+
}
141+
if (response.status === 404) {
142+
return { valid: false, message: 'GitHub user not found' };
143+
}
144+
if (response.status === 403 || response.status === 429) {
145+
return { valid: null, message: 'Rate limit reached. Validation skipped.' };
146+
}
147+
return { valid: null, message: 'Unable to verify' };
148+
} catch (error) {
149+
return { valid: null, message: 'Unable to verify' };
150+
}
151+
}
152+
153+
/**
154+
* Updates the GitHub ID field UI based on validation state
155+
* @param {HTMLInputElement} input - The input element
156+
* @param {string} state - Validation state: clear, validating, valid, invalid, warning
157+
* @param {string} message - The message to display
158+
*/
159+
function updateGitHubValidationUI(input, state, message) {
160+
const fieldWrapper = input.closest('.form-field');
161+
const existingMsg = fieldWrapper.querySelector('.validation-message');
162+
163+
fieldWrapper.classList.remove('validating', 'valid', 'invalid');
164+
if (existingMsg) existingMsg.remove();
165+
166+
if (!state || state === 'clear') return;
167+
168+
if (state === 'validating') {
169+
fieldWrapper.classList.add('validating');
170+
} else if (state === 'valid' && message) {
171+
fieldWrapper.classList.add('valid');
172+
const msg = createTag('div', { class: 'validation-message validation-success' }, message);
173+
input.after(msg);
174+
} else if (state === 'invalid') {
175+
fieldWrapper.classList.add('invalid');
176+
const msg = createTag('div', { class: 'validation-message validation-error' }, message);
177+
input.after(msg);
178+
} else if (state === 'warning') {
179+
const msg = createTag('div', { class: 'validation-message validation-warning' }, message);
180+
input.after(msg);
181+
}
182+
}
183+
111184
/**
112185
* Creates and shows a custom modal dialog
113186
* @param {string} message - The error message to display
@@ -571,6 +644,31 @@ async function buildForm(block) {
571644
});
572645
const githubHelpText = createTag('p', { class: 'help-text' }, 'If you provide your GitHub ID we will also set up a GitHub repo with project files so you can do code and style changes.');
573646
githubField.append(githubLabel, githubInput, githubHelpText);
647+
648+
// Add GitHub ID validation
649+
const debouncedValidate = debounce(async () => {
650+
const username = githubInput.value.trim();
651+
652+
if (!username) {
653+
updateGitHubValidationUI(githubInput, 'clear', '');
654+
return;
655+
}
656+
657+
updateGitHubValidationUI(githubInput, 'validating', '');
658+
const result = await validateGitHubId(username);
659+
660+
if (result.valid === true && result.message) {
661+
updateGitHubValidationUI(githubInput, 'valid', result.message);
662+
} else if (result.valid === false) {
663+
updateGitHubValidationUI(githubInput, 'invalid', result.message);
664+
} else if (result.valid === null) {
665+
updateGitHubValidationUI(githubInput, 'warning', result.message);
666+
} else {
667+
updateGitHubValidationUI(githubInput, 'clear', '');
668+
}
669+
}, 800);
670+
671+
githubInput.addEventListener('input', debouncedValidate);
574672
}
575673

576674
// Terms and Conditions - extract from block content
@@ -662,6 +760,16 @@ async function buildForm(block) {
662760
form.addEventListener('submit', async (e) => {
663761
e.preventDefault();
664762

763+
// Check if GitHub ID field is in invalid state
764+
const githubFieldEl = form.querySelector('#github-field');
765+
if (githubFieldEl && githubFieldEl.classList.contains('invalid')) {
766+
const githubInputEl = githubFieldEl.querySelector('input');
767+
if (githubInputEl) {
768+
githubInputEl.focus();
769+
}
770+
return;
771+
}
772+
665773
// Disable submit button to prevent multiple submissions
666774
const submitButtonSubmit = form.querySelector('button[type="submit"]');
667775
submitButtonSubmit.disabled = true;

0 commit comments

Comments
 (0)