|
158 | 158 | </div> |
159 | 159 | </div> |
160 | 160 | <hr class="card__hr" aria-hidden="true" /> |
161 | | - {% comment %} Persists the Profile links to the current user via PATCH /api/v1/users/me/. {% endcomment %} |
| 161 | + {% comment %} Save changes: links are validated and persisted via PATCH /api/v1/users/me/ |
| 162 | + (see saveLinks in profileForm) before the section form submits, so one click covers both; |
| 163 | + a link validation/save failure blocks the section submit and the button stays "Save changes". {% endcomment %} |
162 | 164 | <div class="card__cta_section"> |
163 | | - {% include 'v3/includes/_button.html' with style='primary' type='submit' name='v3_update_profile' label='Save changes' label_x_text="saved ? 'Changes Saved' : 'Save changes'" alpine_disabled='saved' only %} |
| 165 | + {% include 'v3/includes/_button.html' with style='primary' type='submit' name='v3_update_profile' label='Save changes' label_x_text="saved ? 'Changes Saved' : 'Save changes'" alpine_disabled='saved || saving' only %} |
164 | 166 | {% include 'v3/includes/_button.html' with style='secondary' type='reset' label='Cancel' only %} |
165 | | - {% include 'v3/includes/_button.html' with style='primary' type='button' label='Save Changes' alpine_disabled="saving" alpine_click="save()" only %} |
166 | | - <span class="user-profile__save-indicator" role="status" aria-live="polite"> |
167 | | - <span class="user-profile__save-state" x-show="saveStatus === 'saved'" x-cloak> |
168 | | - <span class="user-profile__save-text">Saved</span> |
169 | | - {% include "includes/icon.html" with icon_name="saved" icon_class="create-post-page__save-icon" icon_size=16 %} |
170 | | - </span> |
171 | | - <span class="field__error" x-show="saveStatus === 'error'" x-text="saveError" x-cloak></span> |
172 | | - </span> |
| 167 | + <span class="field__error" x-show="saveError" x-text="saveError" x-cloak></span> |
173 | 168 | </div> |
174 | 169 | </form> |
175 | 170 | </div> |
|
328 | 323 | return |
329 | 324 | } |
330 | 325 | event.preventDefault() |
| 326 | + // Only the Profile form mixes in profileForm(); other sections |
| 327 | + // (Update Details, Email Preferences) have no saveLinks and skip |
| 328 | + // straight to their own submit. |
| 329 | + if (typeof this.saveLinks === 'function') { |
| 330 | + const linksSaved = await this.saveLinks() |
| 331 | + if (!linksSaved) return |
| 332 | + } |
331 | 333 | const form = event.target |
332 | 334 | const submitter = event.submitter |
333 | 335 | let succeeded = false |
|
491 | 493 | const SLACK_MEMBER_ID_PATTERN = /^[A-Z0-9]{9,11}$/i; |
492 | 494 |
|
493 | 495 | return { |
494 | | - saving: false, |
495 | | - saveStatus: '', // '' | 'saved' | 'error' (button shows "saving" via :disabled) |
| 496 | + saving: false, // true while the links PATCH is in flight; disables the Save changes button |
496 | 497 | saveError: '', |
497 | 498 | errors: { github: '', website: '', email: '', slack: '' }, // inline per-link errors, keyed by type |
498 | 499 |
|
499 | 500 | // Clear a link's inline error as soon as the user edits that field. |
500 | 501 | clearLinkError(e) { |
501 | 502 | const type = e.target.dataset.linkType; |
502 | 503 | if (type) this.errors[type] = ''; |
503 | | - this.saveStatus = ''; |
504 | 504 | this.saveError = ''; |
505 | 505 | }, |
506 | 506 |
|
|
568 | 568 | return links; |
569 | 569 | }, |
570 | 570 |
|
571 | | - async save() { |
572 | | - if (this.saving) return; |
| 571 | + // Validates and persists the profile links via PATCH before the section |
| 572 | + // form submit proceeds (see handleSubmit in createSectionForm). Returns |
| 573 | + // true when it's safe to continue with the section submit, false to |
| 574 | + // abort it (validation or save failure) and leave the button as-is. |
| 575 | + async saveLinks() { |
| 576 | + if (this.saving) return false; |
573 | 577 |
|
574 | 578 | // Validate all links up front; block the save if any fail. |
575 | 579 | let hasError = false; |
|
578 | 582 | this.errors[type] = msg; |
579 | 583 | if (msg) hasError = true; |
580 | 584 | }); |
581 | | - if (hasError) return; |
| 585 | + if (hasError) return false; |
582 | 586 |
|
583 | 587 | this.saving = true; |
584 | | - this.saveStatus = ''; |
585 | 588 | this.saveError = ''; |
586 | 589 | try { |
587 | 590 | const res = await fetch('/api/v1/users/me/', { |
|
606 | 609 | Object.entries(linkErrors).forEach(([type, msg]) => { |
607 | 610 | this.errors[type] = Array.isArray(msg) ? msg[0] : msg; |
608 | 611 | }); |
609 | | - return; |
| 612 | + return false; |
610 | 613 | } |
611 | 614 |
|
612 | 615 | const fieldErrors = Object.values(data).flat().filter(Boolean); |
613 | 616 | throw new Error(fieldErrors.length ? fieldErrors.join(' ') : `Save failed (${res.status})`); |
614 | 617 | } |
615 | | - this.saveStatus = 'saved'; |
| 618 | + return true; |
616 | 619 | } catch (err) { |
617 | | - console.error('Profile save failed:', err); |
618 | | - this.saveStatus = 'error'; |
| 620 | + console.error('Profile links save failed:', err); |
619 | 621 | this.saveError = err.message || 'Could not save. Please try again.'; |
| 622 | + return false; |
620 | 623 | } finally { |
621 | 624 | this.saving = false; |
622 | 625 | } |
|
0 commit comments