@uniai-fe/next-providers 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/package.json +5 -3
- package/src/NextRoots.tsx +7 -11
- package/src/index.tsx +6 -3
- package/src/lib/RuntimeRouteGuard.tsx +106 -0
- package/src/lib/RouterEventsDetector.tsx +0 -30
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
# client / next-providers
|
|
2
2
|
|
|
3
3
|
상태관리 도구 \<Provider /> 및 보조 도구 커스텀 등 관리
|
|
4
|
+
|
|
5
|
+
## Exported utilities
|
|
6
|
+
|
|
7
|
+
- `NextRoots` — React Query/Jotai/Airtable 등 루트 Provider 묶음
|
|
8
|
+
- `modalProviders` 슬롯을 통해 `@uniai-fe/ui-legacy`나 신규 `@uniai-fe/uds-*` 모달 Provider를 서비스 측에서 직접 주입한다.
|
|
9
|
+
- `onRouteChange` 콜백을 전달해 라우트 변경 시 모달 스택 초기화 등 앱 전용 부가 로직을 실행할 수 있다.
|
|
10
|
+
- `SetQueryCookie` — query string 기반 쿠키 동기화
|
|
11
|
+
- `RuntimeRouteGuard` / `runtimeRouteRedirectAtom` — 런타임 환경에 따라 `/mobile` 등 경로를 강제하거나 차단하는 가드 + Jotai 토글 atom
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/next-providers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Next.js State Providers for UNIAI FE Projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"@tanstack/react-query": ">= 5",
|
|
49
49
|
"jotai": ">= 2",
|
|
50
50
|
"styled-components": ">= 6",
|
|
51
|
-
"airtable": ">= 0.12"
|
|
51
|
+
"airtable": ">= 0.12",
|
|
52
|
+
"@uniai-fe/runtime-env": ">= 0.1.6",
|
|
53
|
+
"@uniai-fe/util-jotai": ">= 0.1.5"
|
|
52
54
|
},
|
|
53
55
|
"peerDependenciesMeta": {
|
|
54
56
|
"optional": {
|
|
@@ -62,8 +64,8 @@
|
|
|
62
64
|
"@uniai-fe/tsconfig": "workspace:*",
|
|
63
65
|
"@uniai-fe/next-devkit": "workspace:*",
|
|
64
66
|
"@uniai-fe/util-functions": "workspace:*",
|
|
67
|
+
"@uniai-fe/runtime-env": "workspace:*",
|
|
65
68
|
"@uniai-fe/util-jotai": "workspace:*",
|
|
66
|
-
"@uniai-fe/ui-legacy": "workspace:*",
|
|
67
69
|
"@uniai-fe/i18n": "workspace:*",
|
|
68
70
|
"@tanstack/react-query": "^5.90.12",
|
|
69
71
|
"@types/node": "^24.10.2",
|
package/src/NextRoots.tsx
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import { Suspense } from "react";
|
|
2
|
-
import { Modal } from "@uniai-fe/ui-legacy";
|
|
3
|
-
|
|
4
1
|
import StyledComponentsRegistry from "./lib/StyledComponentsRegistry";
|
|
5
2
|
import AirtableProvider from "./provider/AirtableProvider";
|
|
6
3
|
import { JotaiProvider } from "./provider/JotaiProvider";
|
|
7
4
|
import { ReactQueryProvider } from "./provider/ReactQueryProvider";
|
|
8
|
-
import RouterEventDetector from "./lib/RouterEventsDetector";
|
|
9
5
|
import SetQueryCookie from "./lib/SetQueryCookie";
|
|
10
6
|
import SystemThemeChecker from "./lib/SystemThemeChecker";
|
|
11
7
|
import type { SystemLanguageType } from "@uniai-fe/next-devkit/types";
|
|
@@ -17,23 +13,27 @@ import type { SystemLanguageType } from "@uniai-fe/next-devkit/types";
|
|
|
17
13
|
* @param {SystemLanguageType} [props.locale] 언어설정; "ko", "en"
|
|
18
14
|
* @param {object} [props.systemThemeOptions] 시스템 테마 체크 옵션
|
|
19
15
|
* @param {string[]} [props.systemThemeOptions.exceptPaths] 테마 체크 제외 경로
|
|
16
|
+
* @param {React.ReactNode} [props.modalProviders] 모달 Provider 슬롯(@uniai-fe/ui-legacy 등 서비스 선택)
|
|
17
|
+
* @param {() => void} [props.onRouteChange] 경로 변경 시 실행할 콜백(예: 모달 스택 초기화)
|
|
20
18
|
* @param {React.ReactNode} props.children
|
|
21
19
|
* @desc
|
|
22
20
|
* - React Query
|
|
23
21
|
* - Jotai
|
|
24
|
-
* - Apollo GraphQL
|
|
25
22
|
* - Airtable
|
|
26
|
-
* - Modal / Draggable Modal
|
|
27
23
|
* - System Theme Checker
|
|
28
24
|
* - Router Event Detector
|
|
29
25
|
*/
|
|
30
26
|
export default function NextRoots({
|
|
31
27
|
locale = "ko",
|
|
32
28
|
systemThemeOptions,
|
|
29
|
+
modalProviders,
|
|
30
|
+
|
|
33
31
|
children,
|
|
34
32
|
}: {
|
|
35
33
|
locale?: SystemLanguageType;
|
|
36
34
|
systemThemeOptions?: { exceptPaths: string[] };
|
|
35
|
+
modalProviders?: React.ReactNode;
|
|
36
|
+
|
|
37
37
|
children: React.ReactNode;
|
|
38
38
|
}) {
|
|
39
39
|
return (
|
|
@@ -43,13 +43,9 @@ export default function NextRoots({
|
|
|
43
43
|
<StyledComponentsRegistry>
|
|
44
44
|
<AirtableProvider locale={locale}>
|
|
45
45
|
{children}
|
|
46
|
-
|
|
47
|
-
<Modal.Draggable.Provider />
|
|
46
|
+
{modalProviders}
|
|
48
47
|
</AirtableProvider>
|
|
49
48
|
<SystemThemeChecker exceptPaths={systemThemeOptions?.exceptPaths} />
|
|
50
|
-
<Suspense fallback={null}>
|
|
51
|
-
<RouterEventDetector />
|
|
52
|
-
</Suspense>
|
|
53
49
|
</StyledComponentsRegistry>
|
|
54
50
|
</JotaiProvider>
|
|
55
51
|
</ReactQueryProvider>
|
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export {
|
|
2
|
+
RuntimeRouteGuard,
|
|
3
|
+
runtimeRouteRedirectAtom,
|
|
4
|
+
} from "./lib/RuntimeRouteGuard";
|
|
3
5
|
|
|
4
|
-
export { NextRoots
|
|
6
|
+
export { default as NextRoots } from "./NextRoots";
|
|
7
|
+
export { default as SetQueryCookie } from "./lib/SetQueryCookie";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
5
|
+
import { atom, type PrimitiveAtom, useAtomValue } from "jotai";
|
|
6
|
+
import {
|
|
7
|
+
runtimeEnvState,
|
|
8
|
+
type RuntimeEnvState,
|
|
9
|
+
} from "@uniai-fe/runtime-env/client";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_MOBILE_BASE_PATH = "/mobile";
|
|
12
|
+
const DEFAULT_DESKTOP_FALLBACK_PATH = "/";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 런타임 라우팅 가드 판단에 필요한 환경 상태.
|
|
16
|
+
* @typedef {Pick<RuntimeEnvState, "hardware" | "isStandalone" | "isMobileNonPwa">} RuntimeRouteConditions
|
|
17
|
+
*/
|
|
18
|
+
export type RuntimeRouteConditions = Pick<
|
|
19
|
+
RuntimeEnvState,
|
|
20
|
+
"hardware" | "isStandalone" | "isMobileNonPwa"
|
|
21
|
+
>;
|
|
22
|
+
|
|
23
|
+
export interface RuntimeRouteGuardProps {
|
|
24
|
+
mobileBasePath?: string;
|
|
25
|
+
desktopFallbackPath?: string;
|
|
26
|
+
redirectAtom?: PrimitiveAtom<boolean>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 모바일 경로 리디렉션 on/off 상태를 제어하는 Jotai atom.
|
|
31
|
+
* @constant
|
|
32
|
+
*/
|
|
33
|
+
export const runtimeRouteRedirectAtom = atom(true);
|
|
34
|
+
|
|
35
|
+
const isMobilePath = (pathname?: string | null, basePath?: string): boolean =>
|
|
36
|
+
typeof pathname === "string" &&
|
|
37
|
+
typeof basePath === "string" &&
|
|
38
|
+
pathname.startsWith(basePath);
|
|
39
|
+
|
|
40
|
+
const shouldForceMobilePath = (
|
|
41
|
+
pathname: string | null,
|
|
42
|
+
{ isStandalone, hardware }: RuntimeRouteConditions,
|
|
43
|
+
basePath: string,
|
|
44
|
+
): boolean => {
|
|
45
|
+
if (!isStandalone) return false;
|
|
46
|
+
if (hardware !== "mobile" && hardware !== "tablet") return false;
|
|
47
|
+
return !isMobilePath(pathname, basePath);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const shouldPreventMobilePath = (
|
|
51
|
+
pathname: string | null,
|
|
52
|
+
{ isStandalone, isMobileNonPwa }: RuntimeRouteConditions,
|
|
53
|
+
basePath: string,
|
|
54
|
+
): boolean => {
|
|
55
|
+
if (isStandalone) return false;
|
|
56
|
+
if (!isMobileNonPwa) return false;
|
|
57
|
+
return isMobilePath(pathname, basePath);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Runtime 환경에 따라 모바일 경로를 강제 또는 제한하는 라우트 가드.
|
|
62
|
+
* @component
|
|
63
|
+
* @param {RuntimeRouteGuardProps} props
|
|
64
|
+
* @param {string} [props.mobileBasePath="/mobile"] 모바일 PWA 화면 루트 경로
|
|
65
|
+
* @param {string} [props.desktopFallbackPath="/"] 모바일 접근 제한 시 이동할 데스크톱 경로
|
|
66
|
+
* @param {PrimitiveAtom<boolean>} [props.redirectAtom=runtimeRouteRedirectAtom] 리디렉션 on/off를 제어하는 Jotai atom
|
|
67
|
+
* @example
|
|
68
|
+
* <RuntimeRouteGuard mobileBasePath="/mobile" desktopFallbackPath="/" />
|
|
69
|
+
* @desc
|
|
70
|
+
* - 모바일 standalone(PWA) 환경에서는 지정된 모바일 경로로 강제 이동한다.
|
|
71
|
+
* - 모바일 브라우저 환경(비 PWA)에서는 모바일 경로 접근을 막고 기본 경로로 전환한다.
|
|
72
|
+
* - redirectAtom을 이용해 DevTools 또는 UI에서 손쉽게 토글할 수 있다.
|
|
73
|
+
*/
|
|
74
|
+
export function RuntimeRouteGuard({
|
|
75
|
+
mobileBasePath = DEFAULT_MOBILE_BASE_PATH,
|
|
76
|
+
desktopFallbackPath = DEFAULT_DESKTOP_FALLBACK_PATH,
|
|
77
|
+
redirectAtom = runtimeRouteRedirectAtom,
|
|
78
|
+
}: RuntimeRouteGuardProps = {}) {
|
|
79
|
+
const router = useRouter();
|
|
80
|
+
const pathname = usePathname();
|
|
81
|
+
const env = useAtomValue(runtimeEnvState);
|
|
82
|
+
const redirectEnabled = useAtomValue(redirectAtom);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!redirectEnabled) return;
|
|
86
|
+
if (typeof pathname !== "string") return;
|
|
87
|
+
|
|
88
|
+
if (shouldForceMobilePath(pathname, env, mobileBasePath)) {
|
|
89
|
+
router.replace(mobileBasePath);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (shouldPreventMobilePath(pathname, env, mobileBasePath)) {
|
|
94
|
+
router.replace(desktopFallbackPath);
|
|
95
|
+
}
|
|
96
|
+
}, [
|
|
97
|
+
desktopFallbackPath,
|
|
98
|
+
env,
|
|
99
|
+
mobileBasePath,
|
|
100
|
+
pathname,
|
|
101
|
+
redirectEnabled,
|
|
102
|
+
router,
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { usePathname } from "next/navigation";
|
|
4
|
-
import { useEffect } from "react";
|
|
5
|
-
import { useSetAtom } from "jotai";
|
|
6
|
-
|
|
7
|
-
import { modalState, modalDraggableState } from "@uniai-fe/ui-legacy";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 경로변경 이벤트 감지
|
|
11
|
-
* @component
|
|
12
|
-
* @desc
|
|
13
|
-
* - 최상위 layout에서, 경로 변경을 감지
|
|
14
|
-
*/
|
|
15
|
-
export default function RouterEventDetector() {
|
|
16
|
-
const resetModalStack = useSetAtom(modalState);
|
|
17
|
-
const resetDraggableModalStack = useSetAtom(modalDraggableState);
|
|
18
|
-
|
|
19
|
-
const pathname = usePathname();
|
|
20
|
-
|
|
21
|
-
// 경로 변경 감지
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
// 모달 스택 초기화
|
|
24
|
-
resetModalStack([]);
|
|
25
|
-
// 드래거블 모달 스택 초기화
|
|
26
|
-
resetDraggableModalStack([]);
|
|
27
|
-
}, [pathname, resetDraggableModalStack, resetModalStack]);
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
}
|