Skip to content

Commit 3d6b343

Browse files
authored
feat(frontend): add loading bar indicator (#1170)
1 parent 9e5adeb commit 3d6b343

File tree

5 files changed

+282
-8
lines changed

5 files changed

+282
-8
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@headlessui/react": "^0.3.1",
2121
"@supercharge/request-ip": "^1.1.2",
2222
"@svgr/webpack": "^5.5.0",
23+
"@tanem/react-nprogress": "^3.0.57",
2324
"ace-builds": "^1.4.12",
2425
"axios": "^0.21.1",
2526
"bcrypt": "^5.0.1",
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { NProgress } from '@tanem/react-nprogress';
2+
import React, { useEffect, useState } from 'react';
3+
import ReactDOM from 'react-dom';
4+
import { useRouter } from 'next/router';
5+
6+
interface BarProps {
7+
progress: number;
8+
isFinished: boolean;
9+
}
10+
11+
const Bar = ({ progress, isFinished }: BarProps) => {
12+
return (
13+
<div
14+
className={`fixed top-0 left-0 z-50 w-full transition-opacity ease-out duration-400 ${
15+
isFinished ? 'opacity-0' : 'opacity-100'
16+
}`}
17+
>
18+
<div
19+
className="duration-300 bg-indigo-400 transition-width"
20+
style={{
21+
height: '3px',
22+
width: `${progress * 100}%`,
23+
}}
24+
/>
25+
</div>
26+
);
27+
};
28+
29+
const NProgressBar = ({ loading }: { loading: boolean }) => (
30+
<NProgress isAnimating={loading}>
31+
{({ isFinished, progress }) => (
32+
<Bar progress={progress} isFinished={isFinished} />
33+
)}
34+
</NProgress>
35+
);
36+
37+
const MemoizedNProgress = React.memo(NProgressBar);
38+
39+
const LoadingBar = (): React.ReactPortal | null => {
40+
const [mounted, setMounted] = useState(false);
41+
const [loading, setLoading] = useState(false);
42+
const router = useRouter();
43+
useEffect(() => {
44+
setMounted(true);
45+
}, []);
46+
47+
useEffect(() => {
48+
const handleLoading = () => {
49+
setLoading(true);
50+
};
51+
const handleFinishedLoading = () => {
52+
setLoading(false);
53+
};
54+
router.events.on('routeChangeStart', handleLoading);
55+
router.events.on('routeChangeComplete', handleFinishedLoading);
56+
router.events.on('routeChangeError', handleFinishedLoading);
57+
58+
return () => {
59+
router.events.off('routeChangeStart', handleLoading);
60+
router.events.off('routeChangeComplete', handleFinishedLoading);
61+
router.events.off('routeChangeError', handleFinishedLoading);
62+
};
63+
}, [router]);
64+
65+
return mounted
66+
? ReactDOM.createPortal(
67+
<MemoizedNProgress loading={loading} />,
68+
document.body
69+
)
70+
: null;
71+
};
72+
73+
export default LoadingBar;

src/pages/_app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { InteractionProvider } from '../context/InteractionContext';
1616
import StatusChecker from '../components/StatusChacker';
1717
import { PublicSettingsResponse } from '../../server/interfaces/api/settingsInterfaces';
1818
import { SettingsProvider } from '../context/SettingsContext';
19+
import LoadingBar from '../components/LoadingBar';
1920

2021
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2122
const loadLocaleData = (locale: AvailableLocales): Promise<any> => {
@@ -113,6 +114,7 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
113114
defaultLocale="en"
114115
messages={loadedMessages}
115116
>
117+
<LoadingBar />
116118
<SettingsProvider currentSettings={currentSettings}>
117119
<InteractionProvider>
118120
<ToastProvider components={{ Toast }}>

tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
extend: {
88
transitionProperty: {
99
'max-height': 'max-height',
10+
width: 'width',
1011
},
1112
fontFamily: {
1213
sans: ['Inter var', ...defaultTheme.fontFamily.sans],

0 commit comments

Comments
 (0)