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-loading-skeleton": "^3.5.0",
"react-responsive-modal": "^6.4.2",
"react-spinners": "^0.14.1",
"recoil": "^0.7.7",
"sweetalert2": "^11.14.1",
"sweetalert2-react-content": "^5.0.7",

View File

@ -7,14 +7,18 @@ import { usePlan } from '@/hooks/usePlan'
import ServerError from './error'
import '@/styles/common.scss'
import GlobalSpinner from '@/components/common/spinner/GlobalSpinner'
export const QcastContext = createContext({
qcastState: {},
setQcastState: () => {},
isGlobalLoading: true,
setIsGlobalLoading: () => {},
})
export const QcastProvider = ({ children }) => {
const [planSave, setPlanSave] = useState(false)
const [isGlobalLoading, setIsGlobalLoading] = useState(true)
const { currentCanvasPlan, modifiedPlans, checkUnsavedCanvasPlan } = usePlan()
const { commonCode, findCommonCode } = useCommonCode()
@ -43,7 +47,12 @@ export const QcastProvider = ({ children }) => {
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>
</QcastContext.Provider>
</>

View File

@ -62,16 +62,16 @@ export default async function RootLayout({ children }) {
{headerPathname === '/login' || headerPathname === '/join' ? (
<QcastProvider>{children}</QcastProvider>
) : (
<div className="wrap">
<Header userSession={sessionProps} />
<div className="content">
<Dimmed />
<QcastProvider>
<QcastProvider>
<div className="wrap">
<Header userSession={sessionProps} />
<div className="content">
<Dimmed />
<SessionProvider useSession={sessionProps}>{children}</SessionProvider>
</QcastProvider>
</div>
<Footer />
</div>
<Footer />
</div>
</QcastProvider>
)}
<QModal />
<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'
import { Fragment, useCallback, useEffect, useState } from 'react'
import { Fragment, useCallback, useContext, useEffect, useState } from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
@ -17,6 +17,7 @@ import { useAxios } from '@/hooks/useAxios'
import { globalLocaleStore } from '@/store/localeAtom'
import { stuffSearchState } from '@/store/stuffAtom'
import { QcastContext } from '@/app/QcastProvider'
export const ToggleonMouse = (e, act, target) => {
const listWrap = e.target.closest(target)
@ -47,6 +48,8 @@ export default function Header(props) {
// }
const [selected, setSelected] = useState('')
const { isGlobalLoading } = useContext(QcastContext)
const dimmedState = useRecoilValue(dimmedStore)
const isDimmed = dimmedState ? 'opacity-50 bg-black' : ''
@ -164,54 +167,56 @@ export default function Header(props) {
}
return (
!(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && (
<header className={isDimmed}>
<div className="header-inner">
<div className="header-right">
<h1 className="logo">
<Link href={'/'}></Link>
</h1>
<nav>
<ul className="nav-list ">{getMenuTemplate(menus)}</ul>
</nav>
</div>
<div className="header-left">
<div className="profile-box">
<Link
href="#"
onClick={() => {
setUserInfoModal(true)
}}
>
<button className="profile">{userSession.userNm}</button>
</Link>
{userInfoModal && <UserInfoModal userId={sessionState.userId} userInfoModal={userInfoModal} setUserInfoModal={setUserInfoModal} />}
<>
{!isGlobalLoading && !(pathName.includes('login') || pathName.includes('join') || sessionState.pwdInitYn === 'N') && (
<header className={isDimmed}>
<div className="header-inner">
<div className="header-right">
<h1 className="logo">
<Link href={'/'}></Link>
</h1>
<nav>
<ul className="nav-list ">{getMenuTemplate(menus)}</ul>
</nav>
</div>
<div className="sign-out-box">
<button
className="sign-out"
onClick={() => {
setStuffSearch({
...stuffSearch,
code: 'DELETE',
})
logout()
}}
>
{getMessage('header.logout')}
</button>
</div>
<div className="select-box">
<QSelectBox options={SelectOptions} onChange={onChangeSelect} />
</div>
<div className="btn-wrap">
<button className="btn-frame small dark" onClick={() => navPage()}>
{getMessage('header.go')}
</button>
<div className="header-left">
<div className="profile-box">
<Link
href="#"
onClick={() => {
setUserInfoModal(true)
}}
>
<button className="profile">{userSession.userNm}</button>
</Link>
{userInfoModal && <UserInfoModal userId={sessionState.userId} userInfoModal={userInfoModal} setUserInfoModal={setUserInfoModal} />}
</div>
<div className="sign-out-box">
<button
className="sign-out"
onClick={() => {
setStuffSearch({
...stuffSearch,
code: 'DELETE',
})
logout()
}}
>
{getMessage('header.logout')}
</button>
</div>
<div className="select-box">
<QSelectBox options={SelectOptions} onChange={onChangeSelect} />
</div>
<div className="btn-wrap">
<button className="btn-frame small dark" onClick={() => navPage()}>
{getMessage('header.go')}
</button>
</div>
</div>
</div>
</div>
</header>
)
</header>
)}
</>
)
}

View File

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

View File

@ -5663,7 +5663,7 @@ react-datepicker@^7.3.0:
prop-types "^15.7.2"
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"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@ -5747,6 +5747,11 @@ react-select@^5.8.1:
react-transition-group "^4.3.0"
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:
version "2.2.1"
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"
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"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==