From aebff54a6b90a956abaca06c27fe49e8ee3f2d4c Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:45:47 +0200 Subject: [PATCH] Improvement to OLButton loading labels (#28659) * Create eslint rule for requiring loadingLabel prop when isLoading is specified on OLButton * Add `loadingLabel` props for OLButton components with `isLoading` * Clarify loading label and button loading state GitOrigin-RevId: 89279d5b4c346f9c3b67a59d0db822a2ff04314a --- services/web/.eslintrc.js | 1 + services/web/frontend/extracted-translations.json | 12 ++++++++++++ .../file-tree-modal-create-file-footer.tsx | 1 + .../modals/file-tree-modal-create-folder.tsx | 7 ++++++- .../components/modals/file-tree-modal-delete.tsx | 7 ++++++- .../group-management/components/managers-table.tsx | 1 + .../members-table/remove-managed-user-modal.tsx | 1 + .../components/change-list/add-label-modal.tsx | 1 + .../history/components/change-list/tag-tooltip.tsx | 1 + .../diff-view/modals/restore-project-modal.tsx | 1 + .../toolbar/toolbar-restore-file-button.tsx | 1 + .../toolbar-restore-file-to-version-button.tsx | 1 + .../pdf-preview/components/detach-compile-button.tsx | 1 + .../components/modals/create-tag-modal.tsx | 1 + .../components/modals/delete-tag-modal.tsx | 1 + .../components/modals/edit-tag-modal.tsx | 1 + .../components/modals/manage-tag-modal.tsx | 2 ++ .../modal-content-new-project-form.tsx | 1 + .../groups/affiliation/reconfirm-affiliation.tsx | 1 + .../emails/actions/make-primary/primary-button.tsx | 3 +++ .../emails/add-email/add-new-email-btn.tsx | 1 + .../emails/resend-confirmation-code-modal.tsx | 1 + .../components/dashboard/reactivate-subscription.tsx | 1 + .../states/active/confirm-unpause-modal.tsx | 1 + services/web/locales/en.json | 5 ++++- .../frontend/js/components/register-form.jsx | 6 +++++- .../project-list/components/notifications.test.tsx | 2 +- .../emails/emails-section-add-new-email.test.tsx | 2 +- 28 files changed, 59 insertions(+), 6 deletions(-) diff --git a/services/web/.eslintrc.js b/services/web/.eslintrc.js index 3f444682bf..52e25ca43e 100644 --- a/services/web/.eslintrc.js +++ b/services/web/.eslintrc.js @@ -531,6 +531,7 @@ module.exports = { rules: { '@overleaf/no-unnecessary-trans': 'error', '@overleaf/should-unescape-trans': 'error', + '@overleaf/require-loading-label': 'error', // https://astexplorer.net/ 'no-restricted-syntax': [ diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index f9fa791080..19c3ffd8fc 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -45,6 +45,7 @@ "accept_selected_changes": "", "accept_terms_and_conditions": "", "accepted_invite": "", + "accepting": "", "access_all_premium_features": "", "access_all_premium_features_including_more_collaborators_real_time_track_changes_and_a_longer_compile_time": "", "access_denied": "", @@ -268,6 +269,7 @@ "cite_faster": "", "clear_cached_files": "", "clear_search": "", + "clearing": "", "click_here_to_view_sl_in_lng": "", "click_to_unpause": "", "clicking_delete_will_remove_sso_config_and_clear_saml_data": "", @@ -359,6 +361,7 @@ "create_project_in_github": "", "created": "", "created_at": "", + "creating": "", "cross_reference": "", "current_file": "", "current_password": "", @@ -646,6 +649,7 @@ "generate_from_text_or_image": "", "generate_tables_and_equations": "", "generate_token": "", + "generating": "", "generic_if_problem_continues_contact_us": "", "generic_linked_file_compile_error": "", "generic_something_went_wrong": "", @@ -1393,9 +1397,11 @@ "recompile_from_scratch": "", "recompile_pdf": "", "reconfirm_secondary_email": "", + "reconfirming": "", "reconnect": "", "reconnecting": "", "reconnecting_in_x_secs": "", + "recovering": "", "recurly_email_update_needed": "", "recurly_email_updated": "", "redirect_to_editor": "", @@ -1417,6 +1423,7 @@ "refresh_page_after_starting_free_trial": "", "refreshing": "", "regards": "", + "registering": "", "reject_change": "", "reject_selected_changes": "", "relink_your_account": "", @@ -1471,9 +1478,11 @@ "restore_file_version": "", "restore_project_to_this_version": "", "restore_this_version": "", + "restoring": "", "resync_completed": "", "resync_message": "", "resync_project_history": "", + "resyncing": "", "retry_test": "", "reverse_x_sort_order": "", "revert_pending_plan_change": "", @@ -1576,6 +1585,7 @@ "send_first_message": "", "send_message": "", "send_request": "", + "sending": "", "server_error": "", "server_pro_license_entitlement_line_1": "", "server_pro_license_entitlement_line_2": "", @@ -1721,6 +1731,7 @@ "subject_area": "", "subject_to_additional_vat": "", "submit_title": "", + "submitting": "", "subscribe": "", "subscribe_to_find_the_symbols_you_need_faster": "", "subscribe_to_plan": "", @@ -2005,6 +2016,7 @@ "unlinking": "", "unmerge_cells": "", "unpause_subscription": "", + "unpausing": "", "unpublish": "", "unpublishing": "", "unsubscribe": "", diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx index 21903b771c..299a0df3d1 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx @@ -77,6 +77,7 @@ export function FileTreeModalCreateFileFooterContent({ form="create-file" disabled={inFlight || !valid} isLoading={inFlight} + loadingLabel={t('creating')} > {t('create')} diff --git a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx index 21d8ec19ae..85044309b2 100644 --- a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx +++ b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx @@ -85,7 +85,12 @@ function FileTreeModalCreateFolder() { {inFlight ? ( - + ) : ( <> diff --git a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-delete.tsx b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-delete.tsx index 2e84a6700b..aa2a0125fa 100644 --- a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-delete.tsx +++ b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-delete.tsx @@ -59,7 +59,12 @@ function FileTreeModalDelete() { {inFlight ? ( - + ) : ( <> diff --git a/services/web/frontend/js/features/group-management/components/managers-table.tsx b/services/web/frontend/js/features/group-management/components/managers-table.tsx index beedcb8a22..9b23d86e63 100644 --- a/services/web/frontend/js/features/group-management/components/managers-table.tsx +++ b/services/web/frontend/js/features/group-management/components/managers-table.tsx @@ -265,6 +265,7 @@ export function ManagersTable({ variant="primary" onClick={addManagers} isLoading={inviteUserInflightCount > 0} + loadingLabel={t('adding')} > {t('add')} diff --git a/services/web/frontend/js/features/group-management/components/members-table/remove-managed-user-modal.tsx b/services/web/frontend/js/features/group-management/components/members-table/remove-managed-user-modal.tsx index 4c6969bf92..862d640993 100644 --- a/services/web/frontend/js/features/group-management/components/members-table/remove-managed-user-modal.tsx +++ b/services/web/frontend/js/features/group-management/components/members-table/remove-managed-user-modal.tsx @@ -128,6 +128,7 @@ export default function RemoveManagedUserModal({ variant="danger" disabled={isLoading || isSuccess || !shouldEnableRemoveUserButton} isLoading={isLoading} + loadingLabel={t('removing')} > {t('remove_user')} diff --git a/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx b/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx index 673d530ac0..c20460c8a9 100644 --- a/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx +++ b/services/web/frontend/js/features/history/components/change-list/add-label-modal.tsx @@ -118,6 +118,7 @@ function AddLabelModal({ show, setShow, version }: AddLabelModalProps) { variant="primary" disabled={isLoading || !comment.length} isLoading={isLoading} + loadingLabel={t('adding')} > {t('history_add_label')} diff --git a/services/web/frontend/js/features/history/components/change-list/tag-tooltip.tsx b/services/web/frontend/js/features/history/components/change-list/tag-tooltip.tsx index 43e2a9b8ee..77cdd60038 100644 --- a/services/web/frontend/js/features/history/components/change-list/tag-tooltip.tsx +++ b/services/web/frontend/js/features/history/components/change-list/tag-tooltip.tsx @@ -122,6 +122,7 @@ const ChangeTag = forwardRef( variant="danger" disabled={isLoading} isLoading={isLoading} + loadingLabel={t('deleting')} onClick={localDeleteHandler} > {t('history_delete_label')} diff --git a/services/web/frontend/js/features/history/components/diff-view/modals/restore-project-modal.tsx b/services/web/frontend/js/features/history/components/diff-view/modals/restore-project-modal.tsx index 8eaf722e0a..2606ca77ae 100644 --- a/services/web/frontend/js/features/history/components/diff-view/modals/restore-project-modal.tsx +++ b/services/web/frontend/js/features/history/components/diff-view/modals/restore-project-modal.tsx @@ -51,6 +51,7 @@ export const RestoreProjectModal = ({ onClick={onRestore} disabled={isRestoring} isLoading={isRestoring} + loadingLabel={t('restoring')} > {t('restore')} diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx index 1f85436094..f62b7052ba 100644 --- a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-button.tsx @@ -20,6 +20,7 @@ export default function ToolbarRestoreFileButton({ size="sm" className="history-react-toolbar-restore-file-button" isLoading={isLoading} + loadingLabel={t('restoring')} onClick={() => restoreDeletedFile(selection)} > {t('restore_file')} diff --git a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-to-version-button.tsx b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-to-version-button.tsx index e1a31c97e2..ecfe50ab61 100644 --- a/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-to-version-button.tsx +++ b/services/web/frontend/js/features/history/components/diff-view/toolbar/toolbar-restore-file-to-version-button.tsx @@ -37,6 +37,7 @@ function ToolbarRestoreFileToVersionButton({ variant="secondary" size="sm" isLoading={isLoading} + loadingLabel={t('restoring')} onClick={() => setShowConfirmModal(true)} > {t('restore_file_version')} diff --git a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx index 0fcfcb4707..a0495f0b9c 100644 --- a/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/detach-compile-button.tsx @@ -36,6 +36,7 @@ function DetachCompileButton() { })} size="sm" isLoading={compiling} + loadingLabel={t('compiling')} > {t('recompile')} diff --git a/services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx index 22ff8bc797..aaff86c160 100644 --- a/services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/modals/create-tag-modal.tsx @@ -129,6 +129,7 @@ export default function CreateTagModal({ status === 'pending' || !tagName?.length || !!validationError } isLoading={isLoading} + loadingLabel={t('creating')} > {t('create')} diff --git a/services/web/frontend/js/features/project-list/components/modals/delete-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/modals/delete-tag-modal.tsx index 88713a698b..7bba5ef026 100644 --- a/services/web/frontend/js/features/project-list/components/modals/delete-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/modals/delete-tag-modal.tsx @@ -72,6 +72,7 @@ export default function DeleteTagModal({ variant="danger" disabled={isLoading} isLoading={isLoading} + loadingLabel={t('deleting')} > {t('delete')} diff --git a/services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx index e0fd11fc52..501e588b73 100644 --- a/services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/modals/edit-tag-modal.tsx @@ -139,6 +139,7 @@ export function EditTagModal({ id, tag, onEdit, onClose }: EditTagModalProps) { !!validationError } isLoading={isLoading} + loadingLabel={t('saving')} > {t('save')} diff --git a/services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx b/services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx index a61d8b8ac7..945ef18b8a 100644 --- a/services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx +++ b/services/web/frontend/js/features/project-list/components/modals/manage-tag-modal.tsx @@ -127,6 +127,7 @@ export function ManageTagModal({ className="me-auto" disabled={isDeleteLoading || isUpdateLoading} isLoading={isDeleteLoading} + loadingLabel={t('deleting')} > {t('delete_tag')} @@ -147,6 +148,7 @@ export function ManageTagModal({ (newTagName === tag?.name && selectedColor === getTagColor(tag)) )} isLoading={isUpdateLoading} + loadingLabel={t('saving')} > {t('save_or_cancel-save')} diff --git a/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx b/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx index fdb8b18ede..409b89d978 100644 --- a/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx +++ b/services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx @@ -109,6 +109,7 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) { onClick={createNewProject} disabled={projectName === '' || isLoading || redirecting} isLoading={isLoading} + loadingLabel={t('creating')} > {t('create')} diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx index ce27f43ebe..dc5e908c0b 100644 --- a/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx +++ b/services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx @@ -58,6 +58,7 @@ function ReconfirmAffiliation({ { setIsPending(true) diff --git a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx index 593c8a75f7..c41fc9bc7b 100644 --- a/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx +++ b/services/web/frontend/js/features/settings/components/emails/actions/make-primary/primary-button.tsx @@ -1,4 +1,5 @@ import OLButton, { OLButtonProps } from '@/shared/components/ol/ol-button' +import { useTranslation } from 'react-i18next' function PrimaryButton({ children, @@ -6,11 +7,13 @@ function PrimaryButton({ isLoading, onClick, }: OLButtonProps) { + const { t } = useTranslation() return ( diff --git a/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx b/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx index 441d126472..8c94febb40 100644 --- a/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx +++ b/services/web/frontend/js/features/settings/components/emails/add-email/add-new-email-btn.tsx @@ -22,6 +22,7 @@ function AddNewEmailBtn({ variant="primary" disabled={(disabled && !isLoading) || !isValidEmail(email)} isLoading={isLoading} + loadingLabel={t('adding')} {...props} > {t('add_new_email')} diff --git a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx index e2eace9410..83e316cb03 100644 --- a/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx +++ b/services/web/frontend/js/features/settings/components/emails/resend-confirmation-code-modal.tsx @@ -96,6 +96,7 @@ function ResendConfirmationCodeModal({ variant={triggerVariant} disabled={groupLoading} isLoading={isLoading} + loadingLabel={t('sending')} onClick={handleResendConfirmationEmail} className={triggerVariant === 'link' ? 'btn-inline-link' : undefined} > diff --git a/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx index fe0be1f46f..26416731b5 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx @@ -31,6 +31,7 @@ function ReactivateSubscription() { disabled={isLoading || isSuccess} onClick={handleReactivate} isLoading={isLoading} + loadingLabel={t('reactivating')} > {t('reactivate_subscription')} diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx index 584e47ac9d..b580a99414 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/confirm-unpause-modal.tsx @@ -93,6 +93,7 @@ export function ConfirmUnpauseSubscriptionModal() { variant="primary" disabled={inflight} isLoading={inflight} + loadingLabel={t('unpausing')} onClick={handleConfirmUnpause} > {t('unpause_subscription')} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 0892d5c844..89ed8ccc53 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -845,6 +845,7 @@ "generate_from_text_or_image": "From text or image", "generate_tables_and_equations": "Generate tables and equations from text and images. Try it for free in the Overleaf toolbar!", "generate_token": "Generate token", + "generating": "Generating", "generic_if_problem_continues_contact_us": "If the problem continues please contact us", "generic_linked_file_compile_error": "This project’s output files are not available because it failed to compile. Please open the project to see the compilation error details.", "generic_something_went_wrong": "Sorry, something went wrong", @@ -1813,6 +1814,7 @@ "reconfirm_account": "Reconfirm account", "reconfirm_explained": "We need to reconfirm your account. Please request a password reset link via the form below to reconfirm your account. If you have any problems reconfirming your account, please contact us at", "reconfirm_secondary_email": "To enhance the security of your __appName__ account, please reconfirm your secondary email address __emailAddress__.", + "reconfirming": "Reconfirming", "reconnect": "Try again", "reconnecting": "Reconnecting", "reconnecting_in_x_secs": "Reconnecting in __seconds__ secs", @@ -1925,6 +1927,7 @@ "resync_completed": "Resync completed!", "resync_message": "Resyncing project history can take several minutes depending on the size of the project.", "resync_project_history": "Resync Project History", + "resyncing": "Resyncing", "retry_test": "Retry test", "return_to_login_page": "Return to Login page", "reverse_x_sort_order": "Reverse __x__ sort order", @@ -2507,7 +2510,6 @@ "uncategorized_projects": "Uncategorized Projects", "unconfirmed": "Unconfirmed", "undelete": "Undelete", - "undeleting": "Undeleting", "understanding_labels": "Understanding labels", "undo": "Undo", "unfold_line": "Unfold line", @@ -2541,6 +2543,7 @@ "unlinking": "Unlinking", "unmerge_cells": "Unmerge cells", "unpause_subscription": "Unpause subscription", + "unpausing": "Unpausing", "unpublish": "Unpublish", "unpublishing": "Unpublishing", "unsubscribe": "Unsubscribe", diff --git a/services/web/modules/user-activate/frontend/js/components/register-form.jsx b/services/web/modules/user-activate/frontend/js/components/register-form.jsx index 2a550fdfc4..c7d32b1c3b 100644 --- a/services/web/modules/user-activate/frontend/js/components/register-form.jsx +++ b/services/web/modules/user-activate/frontend/js/components/register-form.jsx @@ -78,7 +78,11 @@ function RegisterForm({ lg={4} className="mt-3 mt-lg-0 d-flex align-items-center flex-column flex-lg-row" > - + Register diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index e763d4d852..7c32a2c307 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -884,7 +884,7 @@ describe('', function () { screen.getByRole('button', { name: 'Send confirmation code' }) ) - await waitForElementToBeRemoved(() => screen.getByText(/loading/i)) + await waitForElementToBeRemoved(() => screen.getByText(/sending/i)) screen.getByText(/Enter the 6-digit code sent to foo@overleaf.com/i) expect(sendReconfirmationMock.callHistory.called()).to.be.true fireEvent.click( diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx index 3458ab4747..080cb364e0 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx @@ -220,7 +220,7 @@ describe('', function () { await waitForElementToBeRemoved(() => screen.getByRole('button', { - name: 'Loading', + name: /adding/i, }) )