feat: Add globalSpinner

- 글로벌 스피너 추가
- 글로벌 스피너 on/off 기능 추가
This commit is contained in:
yoosangwook 2024-11-22 16:02:10 +09:00
parent 4233958df2
commit 24dca05f75
7 changed files with 92 additions and 60 deletions

View File

@ -32,6 +32,7 @@
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-loading-skeleton": "^3.5.0", "react-loading-skeleton": "^3.5.0",
"react-responsive-modal": "^6.4.2", "react-responsive-modal": "^6.4.2",
"react-spinners": "^0.14.1",
"recoil": "^0.7.7", "recoil": "^0.7.7",
"sweetalert2": "^11.14.1", "sweetalert2": "^11.14.1",
"sweetalert2-react-content": "^5.0.7", "sweetalert2-react-content": "^5.0.7",

View File

@ -7,14 +7,18 @@ import { usePlan } from '@/hooks/usePlan'
import ServerError from './error' import ServerError from './error'
import '@/styles/common.scss' import '@/styles/common.scss'
import GlobalSpinner from '@/components/common/spinner/GlobalSpinner'
export const QcastContext = createContext({ export const QcastContext = createContext({
qcastState: {}, qcastState: {},
setQcastState: () => {}, setQcastState: () => {},
isGlobalLoading: true,
setIsGlobalLoading: () => {},
}) })
export const QcastProvider = ({ children }) => { export const QcastProvider = ({ children }) => {
const [planSave, setPlanSave] = useState(false) const [planSave, setPlanSave] = useState(false)
const [isGlobalLoading, setIsGlobalLoading] = useState(true)
const { currentCanvasPlan, modifiedPlans, checkUnsavedCanvasPlan } = usePlan() const { currentCanvasPlan, modifiedPlans, checkUnsavedCanvasPlan } = usePlan()
const { commonCode, findCommonCode } = useCommonCode() const { commonCode, findCommonCode } = useCommonCode()
@ -43,7 +47,12 @@ export const QcastProvider = ({ children }) => {
return ( return (
<> <>
<QcastContext.Provider value={{ qcastState, setQcastState }}> {isGlobalLoading && (
<div className="fixed inset-0 bg-white z-50 flex items-center justify-center">
<GlobalSpinner />
</div>
)}
<QcastContext.Provider value={{ qcastState, setQcastState, isGlobalLoading, setIsGlobalLoading }}>
<ErrorBoundary fallback={<ServerError />}>{children}</ErrorBoundary> <ErrorBoundary fallback={<ServerError />}>{children}</ErrorBoundary>
</QcastContext.Provider> </QcastContext.Provider>
</> </>

View File

@ -62,16 +62,16 @@ export default async function RootLayout({ children }) {
{headerPathname === '/login' || headerPathname === '/join' ? ( {headerPathname === '/login' || headerPathname === '/join' ? (
<QcastProvider>{children}</QcastProvider> <QcastProvider>{children}</QcastProvider>
) : ( ) : (
<QcastProvider>
<div className="wrap"> <div className="wrap">
<Header userSession={sessionProps} /> <Header userSession={sessionProps} />
<div className="content"> <div className="content">
<Dimmed /> <Dimmed />
<QcastProvider>
<SessionProvider useSession={sessionProps}>{children}</SessionProvider> <SessionProvider useSession={sessionProps}>{children}</SessionProvider>
</QcastProvider>
</div> </div>
<Footer /> <Footer />
</div> </div>
</QcastProvider>
)} )}
<QModal /> <QModal />
<PopupManager /> <PopupManager />

View File

@ -0,0 +1,7 @@
'use client'
import { HashLoader } from 'react-spinners'
export default function GlobalSpinner() {
return <HashLoader />
}

View File

@ -1,5 +1,5 @@
'use client' 'use client'
import { Fragment, useCallback, useEffect, useState } from 'react' import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
@ -17,6 +17,7 @@ import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom' import { globalLocaleStore } from '@/store/localeAtom'
import { stuffSearchState } from '@/store/stuffAtom' import { stuffSearchState } from '@/store/stuffAtom'
import { QcastContext } from '@/app/QcastProvider'
export const ToggleonMouse = (e, act, target) => { export const ToggleonMouse = (e, act, target) => {
const listWrap = e.target.closest(target) const listWrap = e.target.closest(target)
@ -47,6 +48,8 @@ export default function Header(props) {
// } // }
const [selected, setSelected] = useState('') const [selected, setSelected] = useState('')
const { isGlobalLoading } = useContext(QcastContext)
const dimmedState = useRecoilValue(dimmedStore) const dimmedState = useRecoilValue(dimmedStore)
const isDimmed = dimmedState ? 'opacity-50 bg-black' : '' const isDimmed = dimmedState ? 'opacity-50 bg-black' : ''
@ -164,7 +167,8 @@ export default function Header(props) {
} }
return ( return (
!(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && ( <>
{!isGlobalLoading && !(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && (
<header className={isDimmed}> <header className={isDimmed}>
<div className="header-inner"> <div className="header-inner">
<div className="header-right"> <div className="header-right">
@ -212,6 +216,7 @@ export default function Header(props) {
</div> </div>
</div> </div>
</header> </header>
) )}
</>
) )
} }

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import { useContext } from 'react' import { useContext, useEffect } from 'react'
import { useAxios } from '../useAxios' import { useAxios } from '../useAxios'
import { SessionContext } from '@/app/SessionProvider' import { SessionContext } from '@/app/SessionProvider'
import { QcastContext } from '@/app/QcastProvider' import { QcastContext } from '@/app/QcastProvider'
@ -8,7 +8,11 @@ import { QcastContext } from '@/app/QcastProvider'
export const useMainContentsController = () => { export const useMainContentsController = () => {
const { session } = useContext(SessionContext) const { session } = useContext(SessionContext)
const { promiseGet } = useAxios() const { promiseGet } = useAxios()
const { setQcastState } = useContext(QcastContext) const { setQcastState, setIsGlobalLoading } = useContext(QcastContext)
useEffect(() => {
setIsGlobalLoading(true)
}, [])
/** /**
* main search area * main search area
@ -46,6 +50,7 @@ export const useMainContentsController = () => {
businessCharger: res.data.businessCharger, businessCharger: res.data.businessCharger,
businessChargerMail: res.data.businessChargerMail, businessChargerMail: res.data.businessChargerMail,
}) })
setIsGlobalLoading(false)
} else { } else {
// setSaleStoreId('') // setSaleStoreId('')
// setSaleStoreName('') // setSaleStoreName('')

View File

@ -5663,7 +5663,7 @@ react-datepicker@^7.3.0:
prop-types "^15.7.2" prop-types "^15.7.2"
react-onclickoutside "^6.13.0" react-onclickoutside "^6.13.0"
"react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16.3.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", "react-dom@^16.9.0 || ^17 || ^18", react-dom@^18, react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>= 16.3.0", react-dom@>=16.6.0, react-dom@>=16.8.0, react-dom@>=18: "react-dom@^15.5.x || ^16.x || ^17.x || ^18.x", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.3.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17 || ^18", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", "react-dom@^16.9.0 || ^17 || ^18", react-dom@^18, react-dom@^18.0.0, react-dom@^18.2.0, "react-dom@>= 16.3.0", react-dom@>=16.6.0, react-dom@>=16.8.0, react-dom@>=18:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@ -5747,6 +5747,11 @@ react-select@^5.8.1:
react-transition-group "^4.3.0" react-transition-group "^4.3.0"
use-isomorphic-layout-effect "^1.1.2" use-isomorphic-layout-effect "^1.1.2"
react-spinners@^0.14.1:
version "0.14.1"
resolved "https://registry.npmjs.org/react-spinners/-/react-spinners-0.14.1.tgz"
integrity sha512-2Izq+qgQ08HTofCVEdcAQCXFEYfqTDdfeDQJeo/HHQiQJD4imOicNLhkfN2eh1NYEWVOX4D9ok2lhuDB0z3Aag==
react-style-singleton@^2.2.1: react-style-singleton@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz" resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz"
@ -5775,7 +5780,7 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react@*, "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16.3.0 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", "react@^16.9.0 || ^17 || ^18", react@^18, react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16.3.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.13.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=18: react@*, "react@^15.5.x || ^16.x || ^17.x || ^18.x", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.3.0 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", "react@^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", "react@^16.9.0 || ^17 || ^18", react@^18, react@^18.0.0, react@^18.2.0, react@^18.3.1, "react@>= 16.3.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.13.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=18:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==