Skip to content

Commit 8c4b29a

Browse files
committed
Adds a dedicated spinner page (for iOS redirect)
Implements a full-page spinner component with theming support. This provides a visually consistent loading indicator across the application, and improves the user experience during asynchronous operations. The spinner can be themed via a URL parameter, or defaults to the browser's preferred color scheme.
1 parent 886cc43 commit 8c4b29a

File tree

7 files changed

+116
-1
lines changed

7 files changed

+116
-1
lines changed

ui/component/fileActions/view.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export default function FileActions(props: Props) {
191191
<MenuItem className="comment__menu-option" onSelect={handleRepostClick}>
192192
<div className="menu__link">
193193
<Icon aria-hidden icon={ICONS.REPOST} />
194-
{claimMeta.reposted > 1
194+
{claimMeta?.reposted > 1
195195
? __(`%repost_total% Reposts`, { repost_total: claimMeta.reposted })
196196
: __('Repost')}
197197
</div>

ui/component/router/view.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const IconsViewerPage = lazyImport(() => import('page/iconsViewer' /* webpackChu
4545

4646
const FypPage = lazyImport(() => import('web/page/fyp' /* webpackChunkName: "fyp" */));
4747
const YouTubeTOSPage = lazyImport(() => import('web/page/youtubetos' /* webpackChunkName: "youtubetos" */));
48+
const SpinnerPage = lazyImport(() => import('web/page/spinner' /* webpackChunkName: "spinner" */));
4849

4950
const SignInPage = lazyImport(() => import('page/signIn' /* webpackChunkName: "signIn" */));
5051
const SignInWalletPasswordPage = lazyImport(() =>
@@ -406,6 +407,7 @@ function AppRouter(props: Props) {
406407
<Route path={`/$/${PAGES.CAREERS_SENIOR_IOS_DEVELOPER}`} exact component={SeniorIosDeveloperPage} />
407408
<Route path={`/$/${PAGES.FYP}`} exact component={FypPage} />
408409
<Route path={`/$/${PAGES.YOUTUBE_TOS}`} exact component={YouTubeTOSPage} />
410+
<Route path={`/$/${PAGES.SPINNER}`} exact component={SpinnerPage} />
409411
<Route path={`/$/${PAGES.ICONS_VIEWER}`} exact component={IconsViewerPage} />
410412

411413
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />

ui/constants/pages.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,4 @@ exports.CAREERS_SENIOR_ANDROID_DEVELOPER = 'careers/senior_android_developer';
115115
exports.CAREERS_SENIOR_IOS_DEVELOPER = 'careers/senior_ios_developer';
116116
exports.ICONS_VIEWER = 'icons_viewer';
117117
exports.HIDDEN_CONTENT = 'hidden';
118+
exports.SPINNER = 'spinner';

ui/scss/all.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
@import 'component/section';
5555
@import 'component/snack-bar';
5656
@import 'component/spinner';
57+
@import 'component/spinner-page';
5758
@import 'component/splash';
5859
@import 'component/status-bar';
5960
@import 'component/syntax-highlighter';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.spinner-page {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
min-height: 100vh;
6+
width: 100%;
7+
margin: 0;
8+
padding: 0;
9+
position: fixed;
10+
top: 0;
11+
left: 0;
12+
right: 0;
13+
bottom: 0;
14+
}
15+
16+
.spinner-page--dark {
17+
background-color: #0b0b0d;
18+
}
19+
20+
.spinner-page--light {
21+
background-color: #f7f7f7;
22+
}
23+
24+
.spinner-page__container {
25+
position: relative;
26+
width: 120px;
27+
height: 120px;
28+
display: flex;
29+
align-items: center;
30+
justify-content: center;
31+
}
32+
33+
.spinner-page__logo {
34+
position: absolute;
35+
display: flex;
36+
align-items: center;
37+
justify-content: center;
38+
39+
.icon {
40+
width: 64px;
41+
height: 64px;
42+
}
43+
}
44+
45+
.spinner-page__ring {
46+
position: absolute;
47+
width: 100px;
48+
height: 100px;
49+
border-radius: 50%;
50+
animation: spinner-page-rotate 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
51+
}
52+
53+
.spinner-page--dark .spinner-page__ring {
54+
border: 3px solid transparent;
55+
border-top-color: #f24158;
56+
}
57+
58+
.spinner-page--light .spinner-page__ring {
59+
border: 3px solid transparent;
60+
border-top-color: #f24158;
61+
}
62+
63+
@keyframes spinner-page-rotate {
64+
0% {
65+
transform: rotate(0deg);
66+
}
67+
100% {
68+
transform: rotate(360deg);
69+
}
70+
}

web/page/spinner/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import SpinnerPage from './view';
2+
export default SpinnerPage;

web/page/spinner/view.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// @flow
2+
import React from 'react';
3+
import * as ICONS from 'constants/icons';
4+
import Icon from 'component/common/icon';
5+
6+
function getThemeFromParams(): ?string {
7+
const urlParams = new URLSearchParams(window.location.search);
8+
const themeParam = urlParams.get('theme');
9+
if (themeParam === 'dark' || themeParam === 'light') {
10+
return themeParam;
11+
}
12+
return null;
13+
}
14+
15+
function getBrowserTheme(): string {
16+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
17+
return 'light';
18+
}
19+
return 'dark';
20+
}
21+
22+
const SpinnerPage = () => {
23+
const paramTheme = getThemeFromParams();
24+
const theme = paramTheme || getBrowserTheme();
25+
const isDark = theme === 'dark';
26+
27+
return (
28+
<div className={`spinner-page ${isDark ? 'spinner-page--dark' : 'spinner-page--light'}`}>
29+
<div className="spinner-page__container">
30+
<div className="spinner-page__ring" />
31+
<div className="spinner-page__logo">
32+
<Icon icon={ICONS.ODYSEE_LOGO} />
33+
</div>
34+
</div>
35+
</div>
36+
);
37+
};
38+
39+
export default SpinnerPage;

0 commit comments

Comments
 (0)