diff --git a/app/assets/javascripts/page_loader.js b/app/assets/javascripts/page_loader.js new file mode 100644 index 000000000..d24cf0597 --- /dev/null +++ b/app/assets/javascripts/page_loader.js @@ -0,0 +1,64 @@ +// Show loader immediately when user clicks a link to a slow page. +// Hide it once the destination page's DOM is ready. + +(function() { + var SLOW_PAGE_PATTERNS = [ + /^\/people(\/|\?|$)/, + /^\/explore\/projects(\/|\?|$)/, + /^\/explore\/orgs(\/|\?|$)/, + /^\/committers(\/|\?|$)/, + /^\/accounts(\/|\?|$)/, + /^\/p\/[^/]+\/commits(\/|\?|$)/ + ]; + + function isSlowPage(url) { + try { + var path = new URL(url, window.location.origin).pathname; + return SLOW_PAGE_PATTERNS.some(function(pattern) { + return pattern.test(path); + }); + } catch (e) { + return false; + } + } + + function showLoader() { + var loader = document.getElementById('page-loader'); + if (loader) loader.classList.remove('hidden'); + } + + function hideLoader() { + var loader = document.getElementById('page-loader'); + if (loader) loader.classList.add('hidden'); + } + + // Show loader when clicking links to slow pages + document.addEventListener('click', function(e) { + // Skip if event was already prevented or not a left-click + if (e.defaultPrevented || e.button !== 0) return; + // Skip if modifier keys held (would open in new tab/window) + if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; + + var link = e.target.closest('a[href]'); + if (!link) return; + + // Skip if link targets something other than current window + if (link.target && link.target.toLowerCase() !== '_self') return; + + var href = link.getAttribute('href'); + // Skip hash-only links + if (!href || href.charAt(0) === '#') return; + + if (isSlowPage(href)) { + showLoader(); + } + }); + + // Hide loader once this page's DOM is ready + document.addEventListener('DOMContentLoaded', hideLoader); + + // Hide loader when page is restored from bfcache (browser back/forward) + window.addEventListener('pageshow', function(e) { + if (e.persisted) hideLoader(); + }); +})(); diff --git a/app/assets/stylesheets/dark_theme.sass b/app/assets/stylesheets/dark_theme.sass index ff7f87325..687096346 100644 --- a/app/assets/stylesheets/dark_theme.sass +++ b/app/assets/stylesheets/dark_theme.sass @@ -348,7 +348,7 @@ html.dark box-shadow: 0 3px 6px rgba(0, 0, 0, 0.32), 0 3px 6px rgba(0, 0, 0, 0.46) &:hover background-color: #e6a617 - color: #1D0631 + color: #1D0631 !important .people-card background-color: #2D1548 !important diff --git a/app/assets/stylesheets/page_loader.sass b/app/assets/stylesheets/page_loader.sass new file mode 100644 index 000000000..be1cce5ff --- /dev/null +++ b/app/assets/stylesheets/page_loader.sass @@ -0,0 +1,48 @@ +// Page loader spinner styling +.page-loader + position: fixed + top: 0 + left: 0 + width: 100% + height: 100% + background: rgba(255, 255, 255, 0.95) + display: flex + align-items: center + justify-content: center + z-index: 9999 + opacity: 1 + transition: opacity 0.3s ease-out + + html.dark & + background: rgba(0, 0, 0, 0.95) + + &.hidden + display: none + + .loader-content + text-align: center + + i.fa-spinner + font-size: 48px + color: #666 + margin-bottom: 16px + display: block + animation: spin 2s linear infinite + + html.dark & + color: #9ca3af + + p + color: #666 + font-size: 16px + margin: 0 + font-weight: 500 + + html.dark & + color: #d1d5db + +@keyframes spin + 0% + transform: rotate(0deg) + 100% + transform: rotate(360deg) diff --git a/app/assets/stylesheets/search-dingus.sass b/app/assets/stylesheets/search-dingus.sass index bd0e7407b..5f5e6898c 100644 --- a/app/assets/stylesheets/search-dingus.sass +++ b/app/assets/stylesheets/search-dingus.sass @@ -180,7 +180,7 @@ border-radius: 12px border: 1px solid rgba(229, 231, 235, 0.8) padding: 16px - margin-bottom: 24px + margin-bottom: 12px box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24) transition: background-color 0.3s ease @@ -195,21 +195,6 @@ gap: 12px flex-wrap: wrap - .pagination-info - font-size: 14px - color: #6b7280 - white-space: nowrap - - html.dark & - color: #d1d5db - - .page-number - font-weight: 600 - color: #1f2937 - - html.dark & - color: #ffffff - .commits_display_date_range font-size: 13px color: #6b7280 @@ -521,6 +506,18 @@ &.inline display: inline-flex +// Pagination info shown between filter bar and results +.pagination-info + font-size: 15px + color: #6b7280 + margin-bottom: 12px + + html.dark & + color: #9ca3af + + .page-number + font-weight: 600 + // Responsive adjustments for search-filter-bar @media (max-width: 640px) .search-filter-bar @@ -530,10 +527,6 @@ width: 100% min-width: 0 - .pagination-info - width: 100% - white-space: normal - label.checkbox, label.radio width: 100% diff --git a/app/views/accounts/settings.html.haml b/app/views/accounts/settings.html.haml index bdb43c7f6..cd7c878a0 100644 --- a/app/views/accounts/settings.html.haml +++ b/app/views/accounts/settings.html.haml @@ -19,12 +19,13 @@ %h4= t('.account_basics') %span.settings_description= t('.account_basics_settings') - %a.settings_module{ href: alter_password_edit_account_path(@account) } - .settings-icon-wrapper.gradient-red - %i.fa.fa-lock - .settings-text - %h4= t('.password') - %span.settings_description= t('.change_passwords') + - if my_account?(@account) + %a.settings_module{ href: alter_password_edit_account_path(@account) } + .settings-icon-wrapper.gradient-red + %i.fa.fa-lock + .settings-text + %h4= t('.password') + %span.settings_description= t('.change_passwords') %a.settings_module{ href: account_api_keys_path(@account) } .settings-icon-wrapper.gradient-blue diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index a673ea1ef..5af0cca4c 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -51,5 +51,6 @@ .clear %footer= render partial: 'layouts/partials/footer' = render partial: 'cookies/consent_banner' unless cookies[:cookie_consented] + = render 'shared/page_loader' = render partial: 'layouts/partials/js_scripts' diff --git a/app/views/passwords/create.html.haml b/app/views/passwords/create.html.haml index dd77b2df9..12e4d77bd 100644 --- a/app/views/passwords/create.html.haml +++ b/app/views/passwords/create.html.haml @@ -14,7 +14,7 @@ .pwd-reset-success__icon %i.fa.fa-check - %h2.signup-form-title Check your email + %h2.signup-form-title Request Received %p.pwd-reset-success__desc= t('.success') diff --git a/app/views/shared/_page_loader.html.haml b/app/views/shared/_page_loader.html.haml new file mode 100644 index 000000000..379c24f48 --- /dev/null +++ b/app/views/shared/_page_loader.html.haml @@ -0,0 +1,5 @@ +/ Page loader - hidden by default, shown via JS when navigating to slow pages +#page-loader.page-loader.hidden + .loader-content{ role: 'status', 'aria-live': 'polite' } + %i.fa.fa-spinner.fa-spin{ 'aria-hidden': 'true' } + %p= t('shared.page_loader.loading') diff --git a/app/views/shared/_search_dingus.html.haml b/app/views/shared/_search_dingus.html.haml index 61893b1f8..fbc0618c2 100644 --- a/app/views/shared/_search_dingus.html.haml +++ b/app/views/shared/_search_dingus.html.haml @@ -9,10 +9,10 @@ .search-filter-bar %form .search-filter-content - = render 'shared/search_dingus/page_entries_info', collection: collection, total_count: total_count - = render 'shared/search_dingus/search_bar', search_type: search_type, tags: tags = render 'shared/search_dingus/sort', filter_type: filter_type, sort_context: sort_context += render 'shared/search_dingus/page_entries_info', collection: collection, total_count: total_count + = render 'shared/search_dingus/no_match_found', collection: collection, no_match_found_type: no_match_found_type diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c272aa3d..7a38aa87c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -81,6 +81,10 @@ en: api_exception: We apologize but we encountered an internal error. Please try again later or contact info@openhub.net if the problem persists. kb_message: "Published message for project %{project_id}" + shared: + page_loader: + loading: 'Loading data...' + layouts: application: openhub: "Open Hub" @@ -410,7 +414,7 @@ en: need_help: 'Need help?' contact_support: 'Contact Support' create: - success: A link to reset your password was sent to your email address. + success: If your email address exists in our database, you will receive a password reset link in a few minutes. reset: success: Your password has been reset successfully. confirm: