@blastlabs/utils 1.11.1 → 1.12.1
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 +20 -13
- package/dist/components/auth/AuthGuard.d.ts +95 -0
- package/dist/components/auth/AuthGuard.d.ts.map +1 -0
- package/dist/components/auth/AuthGuard.js +103 -0
- package/dist/components/auth/index.d.ts +5 -0
- package/dist/components/auth/index.d.ts.map +1 -0
- package/dist/components/auth/index.js +4 -0
- package/dist/components/dev/ApiLogger.d.ts +2 -2
- package/dist/components/dev/ApiLogger.js +3 -3
- package/dist/components/dev/DevPanel.d.ts +1 -1
- package/dist/components/dev/DevPanel.js +2 -2
- package/dist/components/dev/FormDevTools/FormDevTools.d.ts +1 -1
- package/dist/components/dev/FormDevTools/FormDevTools.js +2 -2
- package/dist/components/dev/FormDevTools/index.d.ts +1 -1
- package/dist/components/dev/FormDevTools/index.d.ts.map +1 -1
- package/dist/components/dev/{IdSelector.d.ts → IdSelector/IdSelector.d.ts} +3 -4
- package/dist/components/dev/IdSelector/IdSelector.d.ts.map +1 -0
- package/dist/components/dev/IdSelector/IdSelector.js +60 -0
- package/dist/components/dev/IdSelector/IdSelector.test.d.ts +2 -0
- package/dist/components/dev/IdSelector/IdSelector.test.d.ts.map +1 -0
- package/dist/components/dev/IdSelector/IdSelector.test.js +203 -0
- package/dist/components/dev/IdSelector/LoginCard.d.ts +17 -0
- package/dist/components/dev/IdSelector/LoginCard.d.ts.map +1 -0
- package/dist/components/dev/IdSelector/LoginCard.js +16 -0
- package/dist/components/dev/IdSelector/index.d.ts +3 -0
- package/dist/components/dev/IdSelector/index.d.ts.map +1 -0
- package/dist/components/dev/IdSelector/index.js +1 -0
- package/dist/components/dev/IdSelector/styles.d.ts +16 -0
- package/dist/components/dev/IdSelector/styles.d.ts.map +1 -0
- package/dist/components/dev/IdSelector/styles.js +66 -0
- package/dist/components/dev/WindowSizeDisplay.d.ts +1 -1
- package/dist/components/dev/WindowSizeDisplay.js +2 -2
- package/dist/components/dev/ZIndexDebugger.d.ts +1 -1
- package/dist/components/dev/ZIndexDebugger.js +1 -1
- package/dist/components/dev/index.d.ts +2 -1
- package/dist/components/dev/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/hooks/auth/__tests__/useAuth.test.d.ts +2 -0
- package/dist/hooks/auth/__tests__/useAuth.test.d.ts.map +1 -0
- package/dist/hooks/auth/__tests__/useAuth.test.js +139 -0
- package/dist/hooks/auth/index.d.ts +5 -0
- package/dist/hooks/auth/index.d.ts.map +1 -0
- package/dist/hooks/auth/index.js +4 -0
- package/dist/hooks/auth/useAuth.d.ts +275 -0
- package/dist/hooks/auth/useAuth.d.ts.map +1 -0
- package/dist/hooks/auth/useAuth.js +384 -0
- package/dist/hooks/event/index.d.ts +6 -0
- package/dist/hooks/event/index.d.ts.map +1 -0
- package/dist/hooks/event/index.js +5 -0
- package/dist/hooks/event/useClickOutside.d.ts.map +1 -0
- package/dist/hooks/event/useEventListener.d.ts.map +1 -0
- package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts +2 -0
- package/dist/hooks/form/__tests__/useCRUDForm.test.d.ts.map +1 -0
- package/dist/hooks/form/__tests__/useCRUDForm.test.js +487 -0
- package/dist/hooks/form/index.d.ts +5 -0
- package/dist/hooks/form/index.d.ts.map +1 -0
- package/dist/hooks/form/index.js +4 -0
- package/dist/hooks/form/useCRUDForm.d.ts +211 -0
- package/dist/hooks/form/useCRUDForm.d.ts.map +1 -0
- package/dist/hooks/form/useCRUDForm.js +287 -0
- package/dist/hooks/index.d.ts +10 -14
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +18 -19
- package/dist/hooks/performance/index.d.ts +7 -0
- package/dist/hooks/performance/index.d.ts.map +1 -0
- package/dist/hooks/performance/index.js +6 -0
- package/dist/hooks/performance/useDebounce.d.ts.map +1 -0
- package/dist/hooks/performance/useIntersectionObserver.d.ts.map +1 -0
- package/dist/hooks/performance/useThrottle.d.ts.map +1 -0
- package/dist/hooks/state/index.d.ts +6 -0
- package/dist/hooks/state/index.d.ts.map +1 -0
- package/dist/hooks/state/index.js +5 -0
- package/dist/hooks/state/usePrevious.d.ts.map +1 -0
- package/dist/hooks/state/useToggle.d.ts.map +1 -0
- package/dist/hooks/storage/index.d.ts +7 -0
- package/dist/hooks/storage/index.d.ts.map +1 -0
- package/dist/hooks/storage/index.js +6 -0
- package/dist/hooks/storage/useCopyToClipboard.d.ts.map +1 -0
- package/dist/hooks/storage/useLocalStorage.d.ts.map +1 -0
- package/dist/hooks/storage/useSessionStorage.d.ts.map +1 -0
- package/dist/hooks/time/__tests__/useCountdown.test.d.ts +2 -0
- package/dist/hooks/time/__tests__/useCountdown.test.d.ts.map +1 -0
- package/dist/hooks/time/__tests__/useCountdown.test.js +150 -0
- package/dist/hooks/time/__tests__/useInterval.test.d.ts +2 -0
- package/dist/hooks/time/__tests__/useInterval.test.d.ts.map +1 -0
- package/dist/hooks/time/__tests__/useInterval.test.js +39 -0
- package/dist/hooks/time/__tests__/useStopwatch.test.d.ts +2 -0
- package/dist/hooks/time/__tests__/useStopwatch.test.d.ts.map +1 -0
- package/dist/hooks/time/__tests__/useStopwatch.test.js +149 -0
- package/dist/hooks/time/index.d.ts +7 -0
- package/dist/hooks/time/index.d.ts.map +1 -0
- package/dist/hooks/time/index.js +6 -0
- package/dist/hooks/time/useCountdown.d.ts +116 -0
- package/dist/hooks/time/useCountdown.d.ts.map +1 -0
- package/dist/hooks/time/useCountdown.js +152 -0
- package/dist/hooks/time/useInterval.d.ts +40 -0
- package/dist/hooks/time/useInterval.d.ts.map +1 -0
- package/dist/hooks/time/useInterval.js +61 -0
- package/dist/hooks/time/useStopwatch.d.ts +142 -0
- package/dist/hooks/time/useStopwatch.d.ts.map +1 -0
- package/dist/hooks/time/useStopwatch.js +179 -0
- package/dist/hooks/ui/index.d.ts +7 -0
- package/dist/hooks/ui/index.d.ts.map +1 -0
- package/dist/hooks/ui/index.js +6 -0
- package/dist/hooks/ui/useMediaQuery.d.ts.map +1 -0
- package/dist/hooks/ui/useTabs.d.ts +33 -0
- package/dist/hooks/ui/useTabs.d.ts.map +1 -0
- package/dist/hooks/ui/useTabs.js +117 -0
- package/dist/hooks/ui/useWindowSize.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +14 -4
- package/dist/components/dev/IdSelector.d.ts.map +0 -1
- package/dist/components/dev/IdSelector.js +0 -129
- package/dist/hooks/useClickOutside.d.ts.map +0 -1
- package/dist/hooks/useCopyToClipboard.d.ts.map +0 -1
- package/dist/hooks/useDebounce.d.ts.map +0 -1
- package/dist/hooks/useEventListener.d.ts.map +0 -1
- package/dist/hooks/useIntersectionObserver.d.ts.map +0 -1
- package/dist/hooks/useLocalStorage.d.ts.map +0 -1
- package/dist/hooks/useMediaQuery.d.ts.map +0 -1
- package/dist/hooks/usePrevious.d.ts.map +0 -1
- package/dist/hooks/useSessionStorage.d.ts.map +0 -1
- package/dist/hooks/useThrottle.d.ts.map +0 -1
- package/dist/hooks/useToggle.d.ts.map +0 -1
- package/dist/hooks/useWindowSize.d.ts.map +0 -1
- /package/dist/hooks/{useClickOutside.d.ts → event/useClickOutside.d.ts} +0 -0
- /package/dist/hooks/{useClickOutside.js → event/useClickOutside.js} +0 -0
- /package/dist/hooks/{useEventListener.d.ts → event/useEventListener.d.ts} +0 -0
- /package/dist/hooks/{useEventListener.js → event/useEventListener.js} +0 -0
- /package/dist/hooks/{useDebounce.d.ts → performance/useDebounce.d.ts} +0 -0
- /package/dist/hooks/{useDebounce.js → performance/useDebounce.js} +0 -0
- /package/dist/hooks/{useIntersectionObserver.d.ts → performance/useIntersectionObserver.d.ts} +0 -0
- /package/dist/hooks/{useIntersectionObserver.js → performance/useIntersectionObserver.js} +0 -0
- /package/dist/hooks/{useThrottle.d.ts → performance/useThrottle.d.ts} +0 -0
- /package/dist/hooks/{useThrottle.js → performance/useThrottle.js} +0 -0
- /package/dist/hooks/{usePrevious.d.ts → state/usePrevious.d.ts} +0 -0
- /package/dist/hooks/{usePrevious.js → state/usePrevious.js} +0 -0
- /package/dist/hooks/{useToggle.d.ts → state/useToggle.d.ts} +0 -0
- /package/dist/hooks/{useToggle.js → state/useToggle.js} +0 -0
- /package/dist/hooks/{useCopyToClipboard.d.ts → storage/useCopyToClipboard.d.ts} +0 -0
- /package/dist/hooks/{useCopyToClipboard.js → storage/useCopyToClipboard.js} +0 -0
- /package/dist/hooks/{useLocalStorage.d.ts → storage/useLocalStorage.d.ts} +0 -0
- /package/dist/hooks/{useLocalStorage.js → storage/useLocalStorage.js} +0 -0
- /package/dist/hooks/{useSessionStorage.d.ts → storage/useSessionStorage.d.ts} +0 -0
- /package/dist/hooks/{useSessionStorage.js → storage/useSessionStorage.js} +0 -0
- /package/dist/hooks/{useMediaQuery.d.ts → ui/useMediaQuery.d.ts} +0 -0
- /package/dist/hooks/{useMediaQuery.js → ui/useMediaQuery.js} +0 -0
- /package/dist/hooks/{useWindowSize.d.ts → ui/useWindowSize.d.ts} +0 -0
- /package/dist/hooks/{useWindowSize.js → ui/useWindowSize.js} +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 인증 상태
|
|
3
|
+
*/
|
|
4
|
+
export interface AuthUser {
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 로그인 credentials
|
|
9
|
+
*/
|
|
10
|
+
export interface LoginCredentials {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 회원가입 데이터
|
|
15
|
+
*/
|
|
16
|
+
export interface RegisterData {
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* useAuth 옵션
|
|
21
|
+
*/
|
|
22
|
+
export interface UseAuthOptions<TUser extends AuthUser = AuthUser> {
|
|
23
|
+
/** 로그인 API 함수 */
|
|
24
|
+
loginFn?: (credentials: LoginCredentials) => Promise<{
|
|
25
|
+
user: TUser;
|
|
26
|
+
token?: string;
|
|
27
|
+
}>;
|
|
28
|
+
/** 로그아웃 API 함수 */
|
|
29
|
+
logoutFn?: () => Promise<void>;
|
|
30
|
+
/** 회원가입 API 함수 */
|
|
31
|
+
registerFn?: (data: RegisterData) => Promise<{
|
|
32
|
+
user: TUser;
|
|
33
|
+
token?: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** 현재 사용자 정보 가져오기 */
|
|
36
|
+
getUserFn?: () => Promise<TUser>;
|
|
37
|
+
/** 토큰 갱신 함수 */
|
|
38
|
+
refreshTokenFn?: () => Promise<{
|
|
39
|
+
token: string;
|
|
40
|
+
}>;
|
|
41
|
+
/** 로그인 성공 시 콜백 */
|
|
42
|
+
onLoginSuccess?: (user: TUser) => void;
|
|
43
|
+
/** 로그아웃 성공 시 콜백 */
|
|
44
|
+
onLogoutSuccess?: () => void;
|
|
45
|
+
/** 회원가입 성공 시 콜백 */
|
|
46
|
+
onRegisterSuccess?: (user: TUser) => void;
|
|
47
|
+
/** 에러 발생 시 콜백 */
|
|
48
|
+
onError?: (error: Error, action: 'login' | 'logout' | 'register' | 'refresh') => void;
|
|
49
|
+
/**
|
|
50
|
+
* localStorage에 저장할 토큰 키 (기본값: 'auth_token')
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```
|
|
54
|
+
* // localStorage 저장 예시:
|
|
55
|
+
* localStorage.setItem('auth_token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...')
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
storageKey?: string;
|
|
59
|
+
/**
|
|
60
|
+
* 사용자 정보 저장 키 (기본값: 'auth_user')
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```
|
|
64
|
+
* // localStorage 저장 예시:
|
|
65
|
+
* localStorage.setItem('auth_user', '{"id":"123","name":"John Doe","email":"john@example.com"}')
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
userStorageKey?: string;
|
|
69
|
+
/**
|
|
70
|
+
* localStorage 대신 sessionStorage 사용 여부 (기본값: false)
|
|
71
|
+
*
|
|
72
|
+
* - false (기본값): localStorage 사용 → 브라우저 종료 후에도 유지
|
|
73
|
+
* - true: sessionStorage 사용 → 브라우저/탭 종료 시 삭제
|
|
74
|
+
*
|
|
75
|
+
* 참고: login() 함수의 rememberMe 옵션으로 동적 선택도 가능합니다.
|
|
76
|
+
*/
|
|
77
|
+
useSessionStorage?: boolean;
|
|
78
|
+
/** 자동 토큰 갱신 여부 (기본값: false) */
|
|
79
|
+
autoRefresh?: boolean;
|
|
80
|
+
/** 토큰 갱신 간격 (밀리초, 기본값: 300000 = 5분) */
|
|
81
|
+
refreshInterval?: number;
|
|
82
|
+
/** 초기화 시 사용자 정보 자동 로드 여부 (기본값: true) */
|
|
83
|
+
autoLoadUser?: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* useAuth 반환 타입
|
|
87
|
+
*/
|
|
88
|
+
export interface UseAuthReturn<TUser extends AuthUser = AuthUser> {
|
|
89
|
+
/** 현재 로그인한 사용자 */
|
|
90
|
+
user: TUser | null;
|
|
91
|
+
/** 인증 여부 */
|
|
92
|
+
isAuthenticated: boolean;
|
|
93
|
+
/** 로딩 중 여부 */
|
|
94
|
+
isLoading: boolean;
|
|
95
|
+
/** 로그인 진행 중 여부 */
|
|
96
|
+
isLoggingIn: boolean;
|
|
97
|
+
/** 로그아웃 진행 중 여부 */
|
|
98
|
+
isLoggingOut: boolean;
|
|
99
|
+
/** 회원가입 진행 중 여부 */
|
|
100
|
+
isRegistering: boolean;
|
|
101
|
+
/** 에러 */
|
|
102
|
+
error: Error | null;
|
|
103
|
+
/** 로그인 함수 */
|
|
104
|
+
login: (credentials: LoginCredentials, options?: {
|
|
105
|
+
rememberMe?: boolean;
|
|
106
|
+
}) => Promise<void>;
|
|
107
|
+
/** 로그아웃 함수 */
|
|
108
|
+
logout: () => Promise<void>;
|
|
109
|
+
/** 회원가입 함수 */
|
|
110
|
+
register: (data: RegisterData, options?: {
|
|
111
|
+
rememberMe?: boolean;
|
|
112
|
+
}) => Promise<void>;
|
|
113
|
+
/** 토큰 갱신 함수 */
|
|
114
|
+
refreshToken: () => Promise<void>;
|
|
115
|
+
/** 사용자 정보 새로고침 */
|
|
116
|
+
refetchUser: () => Promise<void>;
|
|
117
|
+
/** 현재 토큰 */
|
|
118
|
+
token: string | null;
|
|
119
|
+
/** 토큰 수동 설정 */
|
|
120
|
+
setToken: (token: string | null) => void;
|
|
121
|
+
/** 사용자 정보 수동 설정 */
|
|
122
|
+
setUser: (user: TUser | null) => void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 인증 관리 Hook
|
|
126
|
+
*
|
|
127
|
+
* 로그인, 로그아웃, 회원가입, 토큰 관리 등 인증 관련 기능을 제공합니다.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* // 기본 사용 (로그인 유지 옵션 포함)
|
|
132
|
+
* function LoginPage() {
|
|
133
|
+
* const [rememberMe, setRememberMe] = useState(false);
|
|
134
|
+
* const { login, isLoggingIn, error, isAuthenticated } = useAuth({
|
|
135
|
+
* loginFn: (credentials) => api.login(credentials),
|
|
136
|
+
* onLoginSuccess: (user) => {
|
|
137
|
+
* console.log('로그인 성공:', user);
|
|
138
|
+
* navigate('/dashboard');
|
|
139
|
+
* },
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* const handleSubmit = async (e) => {
|
|
143
|
+
* e.preventDefault();
|
|
144
|
+
* // rememberMe가 true면 localStorage, false면 sessionStorage 사용
|
|
145
|
+
* await login({ email, password }, { rememberMe });
|
|
146
|
+
* };
|
|
147
|
+
*
|
|
148
|
+
* if (isAuthenticated) {
|
|
149
|
+
* return <Navigate to="/dashboard" />;
|
|
150
|
+
* }
|
|
151
|
+
*
|
|
152
|
+
* return (
|
|
153
|
+
* <form onSubmit={handleSubmit}>
|
|
154
|
+
* <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
|
155
|
+
* <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
|
156
|
+
* <label>
|
|
157
|
+
* <input type="checkbox" checked={rememberMe} onChange={(e) => setRememberMe(e.target.checked)} />
|
|
158
|
+
* 로그인 유지
|
|
159
|
+
* </label>
|
|
160
|
+
* <button type="submit" disabled={isLoggingIn}>
|
|
161
|
+
* {isLoggingIn ? '로그인 중...' : '로그인'}
|
|
162
|
+
* </button>
|
|
163
|
+
* {error && <div>{error.message}</div>}
|
|
164
|
+
* </form>
|
|
165
|
+
* );
|
|
166
|
+
* }
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```tsx
|
|
171
|
+
* // 자동 토큰 갱신과 함께 사용
|
|
172
|
+
* function App() {
|
|
173
|
+
* const { user, isAuthenticated, isLoading, logout } = useAuth({
|
|
174
|
+
* getUserFn: () => api.getMe(),
|
|
175
|
+
* logoutFn: () => api.logout(),
|
|
176
|
+
* refreshTokenFn: () => api.refreshToken(),
|
|
177
|
+
* autoRefresh: true,
|
|
178
|
+
* refreshInterval: 300000, // 5분마다 갱신
|
|
179
|
+
* onLogoutSuccess: () => navigate('/login'),
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* if (isLoading) return <div>로딩중...</div>;
|
|
183
|
+
*
|
|
184
|
+
* return (
|
|
185
|
+
* <div>
|
|
186
|
+
* {isAuthenticated ? (
|
|
187
|
+
* <>
|
|
188
|
+
* <div>환영합니다, {user?.name}님</div>
|
|
189
|
+
* <button onClick={logout}>로그아웃</button>
|
|
190
|
+
* </>
|
|
191
|
+
* ) : (
|
|
192
|
+
* <LoginPage />
|
|
193
|
+
* )}
|
|
194
|
+
* </div>
|
|
195
|
+
* );
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```tsx
|
|
201
|
+
* // 회원가입 포함
|
|
202
|
+
* function RegisterPage() {
|
|
203
|
+
* const { register, isRegistering, error } = useAuth({
|
|
204
|
+
* registerFn: (data) => api.register(data),
|
|
205
|
+
* onRegisterSuccess: (user) => {
|
|
206
|
+
* toast.success('회원가입 성공!');
|
|
207
|
+
* navigate('/');
|
|
208
|
+
* },
|
|
209
|
+
* });
|
|
210
|
+
*
|
|
211
|
+
* const handleSubmit = async (data) => {
|
|
212
|
+
* await register(data);
|
|
213
|
+
* };
|
|
214
|
+
*
|
|
215
|
+
* return (
|
|
216
|
+
* <form onSubmit={handleSubmit}>
|
|
217
|
+
* <input name="email" />
|
|
218
|
+
* <input name="password" type="password" />
|
|
219
|
+
* <input name="name" />
|
|
220
|
+
* <button type="submit" disabled={isRegistering}>
|
|
221
|
+
* 회원가입
|
|
222
|
+
* </button>
|
|
223
|
+
* </form>
|
|
224
|
+
* );
|
|
225
|
+
* }
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```tsx
|
|
230
|
+
* // Storage에 저장되는 값 확인하기
|
|
231
|
+
* function DebugAuth() {
|
|
232
|
+
* const { login, user, token } = useAuth({
|
|
233
|
+
* loginFn: api.login,
|
|
234
|
+
* storageKey: 'my_app_token', // 커스텀 키 사용 가능
|
|
235
|
+
* userStorageKey: 'my_app_user',
|
|
236
|
+
* });
|
|
237
|
+
*
|
|
238
|
+
* const handleLogin = async () => {
|
|
239
|
+
* await login({ email, password }, { rememberMe: true });
|
|
240
|
+
*
|
|
241
|
+
* // 로그인 후 storage 확인
|
|
242
|
+
* console.log('Token:', localStorage.getItem('my_app_token'));
|
|
243
|
+
* // 출력: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
244
|
+
*
|
|
245
|
+
* console.log('User:', localStorage.getItem('my_app_user'));
|
|
246
|
+
* // 출력: {"id":"123","name":"John Doe","email":"john@example.com"}
|
|
247
|
+
* };
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```tsx
|
|
253
|
+
* // rememberMe에 따른 storage 동작
|
|
254
|
+
* function LoginWithRememberMe() {
|
|
255
|
+
* const [rememberMe, setRememberMe] = useState(false);
|
|
256
|
+
* const { login } = useAuth({ loginFn: api.login });
|
|
257
|
+
*
|
|
258
|
+
* const handleLogin = async () => {
|
|
259
|
+
* await login({ email, password }, { rememberMe });
|
|
260
|
+
*
|
|
261
|
+
* if (rememberMe) {
|
|
262
|
+
* // localStorage에 저장 (브라우저 종료해도 유지)
|
|
263
|
+
* console.log(localStorage.getItem('auth_token')); // "eyJ..."
|
|
264
|
+
* console.log(sessionStorage.getItem('auth_token')); // null
|
|
265
|
+
* } else {
|
|
266
|
+
* // sessionStorage에 저장 (브라우저 종료 시 삭제)
|
|
267
|
+
* console.log(localStorage.getItem('auth_token')); // null
|
|
268
|
+
* console.log(sessionStorage.getItem('auth_token')); // "eyJ..."
|
|
269
|
+
* }
|
|
270
|
+
* };
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export declare function useAuth<TUser extends AuthUser = AuthUser>(options?: UseAuthOptions<TUser>): UseAuthReturn<TUser>;
|
|
275
|
+
//# sourceMappingURL=useAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../src/hooks/auth/useAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* 인증 관리 Hook
|
|
4
|
+
*
|
|
5
|
+
* 로그인, 로그아웃, 회원가입, 토큰 관리 등 인증 관련 기능을 제공합니다.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // 기본 사용 (로그인 유지 옵션 포함)
|
|
10
|
+
* function LoginPage() {
|
|
11
|
+
* const [rememberMe, setRememberMe] = useState(false);
|
|
12
|
+
* const { login, isLoggingIn, error, isAuthenticated } = useAuth({
|
|
13
|
+
* loginFn: (credentials) => api.login(credentials),
|
|
14
|
+
* onLoginSuccess: (user) => {
|
|
15
|
+
* console.log('로그인 성공:', user);
|
|
16
|
+
* navigate('/dashboard');
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const handleSubmit = async (e) => {
|
|
21
|
+
* e.preventDefault();
|
|
22
|
+
* // rememberMe가 true면 localStorage, false면 sessionStorage 사용
|
|
23
|
+
* await login({ email, password }, { rememberMe });
|
|
24
|
+
* };
|
|
25
|
+
*
|
|
26
|
+
* if (isAuthenticated) {
|
|
27
|
+
* return <Navigate to="/dashboard" />;
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* return (
|
|
31
|
+
* <form onSubmit={handleSubmit}>
|
|
32
|
+
* <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
|
|
33
|
+
* <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
|
34
|
+
* <label>
|
|
35
|
+
* <input type="checkbox" checked={rememberMe} onChange={(e) => setRememberMe(e.target.checked)} />
|
|
36
|
+
* 로그인 유지
|
|
37
|
+
* </label>
|
|
38
|
+
* <button type="submit" disabled={isLoggingIn}>
|
|
39
|
+
* {isLoggingIn ? '로그인 중...' : '로그인'}
|
|
40
|
+
* </button>
|
|
41
|
+
* {error && <div>{error.message}</div>}
|
|
42
|
+
* </form>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // 자동 토큰 갱신과 함께 사용
|
|
50
|
+
* function App() {
|
|
51
|
+
* const { user, isAuthenticated, isLoading, logout } = useAuth({
|
|
52
|
+
* getUserFn: () => api.getMe(),
|
|
53
|
+
* logoutFn: () => api.logout(),
|
|
54
|
+
* refreshTokenFn: () => api.refreshToken(),
|
|
55
|
+
* autoRefresh: true,
|
|
56
|
+
* refreshInterval: 300000, // 5분마다 갱신
|
|
57
|
+
* onLogoutSuccess: () => navigate('/login'),
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* if (isLoading) return <div>로딩중...</div>;
|
|
61
|
+
*
|
|
62
|
+
* return (
|
|
63
|
+
* <div>
|
|
64
|
+
* {isAuthenticated ? (
|
|
65
|
+
* <>
|
|
66
|
+
* <div>환영합니다, {user?.name}님</div>
|
|
67
|
+
* <button onClick={logout}>로그아웃</button>
|
|
68
|
+
* </>
|
|
69
|
+
* ) : (
|
|
70
|
+
* <LoginPage />
|
|
71
|
+
* )}
|
|
72
|
+
* </div>
|
|
73
|
+
* );
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```tsx
|
|
79
|
+
* // 회원가입 포함
|
|
80
|
+
* function RegisterPage() {
|
|
81
|
+
* const { register, isRegistering, error } = useAuth({
|
|
82
|
+
* registerFn: (data) => api.register(data),
|
|
83
|
+
* onRegisterSuccess: (user) => {
|
|
84
|
+
* toast.success('회원가입 성공!');
|
|
85
|
+
* navigate('/');
|
|
86
|
+
* },
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* const handleSubmit = async (data) => {
|
|
90
|
+
* await register(data);
|
|
91
|
+
* };
|
|
92
|
+
*
|
|
93
|
+
* return (
|
|
94
|
+
* <form onSubmit={handleSubmit}>
|
|
95
|
+
* <input name="email" />
|
|
96
|
+
* <input name="password" type="password" />
|
|
97
|
+
* <input name="name" />
|
|
98
|
+
* <button type="submit" disabled={isRegistering}>
|
|
99
|
+
* 회원가입
|
|
100
|
+
* </button>
|
|
101
|
+
* </form>
|
|
102
|
+
* );
|
|
103
|
+
* }
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```tsx
|
|
108
|
+
* // Storage에 저장되는 값 확인하기
|
|
109
|
+
* function DebugAuth() {
|
|
110
|
+
* const { login, user, token } = useAuth({
|
|
111
|
+
* loginFn: api.login,
|
|
112
|
+
* storageKey: 'my_app_token', // 커스텀 키 사용 가능
|
|
113
|
+
* userStorageKey: 'my_app_user',
|
|
114
|
+
* });
|
|
115
|
+
*
|
|
116
|
+
* const handleLogin = async () => {
|
|
117
|
+
* await login({ email, password }, { rememberMe: true });
|
|
118
|
+
*
|
|
119
|
+
* // 로그인 후 storage 확인
|
|
120
|
+
* console.log('Token:', localStorage.getItem('my_app_token'));
|
|
121
|
+
* // 출력: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
122
|
+
*
|
|
123
|
+
* console.log('User:', localStorage.getItem('my_app_user'));
|
|
124
|
+
* // 출력: {"id":"123","name":"John Doe","email":"john@example.com"}
|
|
125
|
+
* };
|
|
126
|
+
* }
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* // rememberMe에 따른 storage 동작
|
|
132
|
+
* function LoginWithRememberMe() {
|
|
133
|
+
* const [rememberMe, setRememberMe] = useState(false);
|
|
134
|
+
* const { login } = useAuth({ loginFn: api.login });
|
|
135
|
+
*
|
|
136
|
+
* const handleLogin = async () => {
|
|
137
|
+
* await login({ email, password }, { rememberMe });
|
|
138
|
+
*
|
|
139
|
+
* if (rememberMe) {
|
|
140
|
+
* // localStorage에 저장 (브라우저 종료해도 유지)
|
|
141
|
+
* console.log(localStorage.getItem('auth_token')); // "eyJ..."
|
|
142
|
+
* console.log(sessionStorage.getItem('auth_token')); // null
|
|
143
|
+
* } else {
|
|
144
|
+
* // sessionStorage에 저장 (브라우저 종료 시 삭제)
|
|
145
|
+
* console.log(localStorage.getItem('auth_token')); // null
|
|
146
|
+
* console.log(sessionStorage.getItem('auth_token')); // "eyJ..."
|
|
147
|
+
* }
|
|
148
|
+
* };
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function useAuth(options = {}) {
|
|
153
|
+
const { loginFn, logoutFn, registerFn, getUserFn, refreshTokenFn, onLoginSuccess, onLogoutSuccess, onRegisterSuccess, onError, storageKey = 'auth_token', userStorageKey = 'auth_user', useSessionStorage = false, autoRefresh = false, refreshInterval = 300000, // 5분
|
|
154
|
+
autoLoadUser = true, } = options;
|
|
155
|
+
const [user, setUser] = useState(null);
|
|
156
|
+
const [token, setTokenState] = useState(() => {
|
|
157
|
+
// SSR 환경에서는 storage 접근 불가
|
|
158
|
+
if (typeof window === 'undefined')
|
|
159
|
+
return null;
|
|
160
|
+
// 초기 로드 시 localStorage와 sessionStorage 모두 확인
|
|
161
|
+
return localStorage.getItem(storageKey) || sessionStorage.getItem(storageKey);
|
|
162
|
+
});
|
|
163
|
+
const [isLoading, setIsLoading] = useState(autoLoadUser);
|
|
164
|
+
const [isLoggingIn, setIsLoggingIn] = useState(false);
|
|
165
|
+
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
166
|
+
const [isRegistering, setIsRegistering] = useState(false);
|
|
167
|
+
const [error, setError] = useState(null);
|
|
168
|
+
const refreshIntervalRef = useRef(null);
|
|
169
|
+
// 토큰 설정 함수 (storage 타입 동적 선택 가능)
|
|
170
|
+
const setToken = useCallback((newToken, rememberMe = !useSessionStorage) => {
|
|
171
|
+
setTokenState(newToken);
|
|
172
|
+
// SSR 환경에서는 storage 접근 불가
|
|
173
|
+
if (typeof window === 'undefined')
|
|
174
|
+
return;
|
|
175
|
+
const storage = rememberMe ? localStorage : sessionStorage;
|
|
176
|
+
const otherStorage = rememberMe ? sessionStorage : localStorage;
|
|
177
|
+
if (newToken) {
|
|
178
|
+
storage.setItem(storageKey, newToken);
|
|
179
|
+
// 다른 storage에서는 제거 (중복 방지)
|
|
180
|
+
otherStorage.removeItem(storageKey);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// 로그아웃 시 양쪽 모두 제거
|
|
184
|
+
localStorage.removeItem(storageKey);
|
|
185
|
+
sessionStorage.removeItem(storageKey);
|
|
186
|
+
}
|
|
187
|
+
}, [storageKey, useSessionStorage]);
|
|
188
|
+
// 사용자 정보 저장 (rememberMe 옵션 추가)
|
|
189
|
+
const saveUser = useCallback((userData, rememberMe = !useSessionStorage) => {
|
|
190
|
+
setUser(userData);
|
|
191
|
+
// SSR 환경에서는 storage 접근 불가
|
|
192
|
+
if (typeof window === 'undefined')
|
|
193
|
+
return;
|
|
194
|
+
const storage = rememberMe ? localStorage : sessionStorage;
|
|
195
|
+
const otherStorage = rememberMe ? sessionStorage : localStorage;
|
|
196
|
+
if (userData) {
|
|
197
|
+
storage.setItem(userStorageKey, JSON.stringify(userData));
|
|
198
|
+
// 다른 storage에서는 제거
|
|
199
|
+
otherStorage.removeItem(userStorageKey);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// 로그아웃 시 양쪽 모두 제거
|
|
203
|
+
localStorage.removeItem(userStorageKey);
|
|
204
|
+
sessionStorage.removeItem(userStorageKey);
|
|
205
|
+
}
|
|
206
|
+
}, [userStorageKey, useSessionStorage]);
|
|
207
|
+
// 사용자 정보 로드
|
|
208
|
+
const loadUser = useCallback(async () => {
|
|
209
|
+
if (!getUserFn || !token) {
|
|
210
|
+
setIsLoading(false);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const userData = await getUserFn();
|
|
215
|
+
saveUser(userData);
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
const error = err instanceof Error ? err : new Error('Failed to load user');
|
|
219
|
+
setError(error);
|
|
220
|
+
setToken(null);
|
|
221
|
+
saveUser(null);
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
setIsLoading(false);
|
|
225
|
+
}
|
|
226
|
+
}, [getUserFn, token, saveUser, setToken]);
|
|
227
|
+
// 초기 로드
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
// SSR 환경에서는 storage 접근 불가
|
|
230
|
+
if (typeof window === 'undefined') {
|
|
231
|
+
setIsLoading(false);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (autoLoadUser && token) {
|
|
235
|
+
// localStorage와 sessionStorage 모두 확인
|
|
236
|
+
const savedUser = localStorage.getItem(userStorageKey) || sessionStorage.getItem(userStorageKey);
|
|
237
|
+
if (savedUser) {
|
|
238
|
+
try {
|
|
239
|
+
setUser(JSON.parse(savedUser));
|
|
240
|
+
setIsLoading(false);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// JSON 파싱 실패 시 API에서 다시 가져오기
|
|
244
|
+
loadUser();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
loadUser();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
setIsLoading(false);
|
|
253
|
+
}
|
|
254
|
+
}, []);
|
|
255
|
+
// 로그인
|
|
256
|
+
const login = useCallback(async (credentials, loginOptions) => {
|
|
257
|
+
if (!loginFn) {
|
|
258
|
+
throw new Error('loginFn is required for login');
|
|
259
|
+
}
|
|
260
|
+
const rememberMe = loginOptions?.rememberMe ?? !useSessionStorage;
|
|
261
|
+
setIsLoggingIn(true);
|
|
262
|
+
setError(null);
|
|
263
|
+
try {
|
|
264
|
+
const result = await loginFn(credentials);
|
|
265
|
+
if (result.token) {
|
|
266
|
+
setToken(result.token, rememberMe);
|
|
267
|
+
}
|
|
268
|
+
saveUser(result.user, rememberMe);
|
|
269
|
+
onLoginSuccess?.(result.user);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
const error = err instanceof Error ? err : new Error('Login failed');
|
|
273
|
+
setError(error);
|
|
274
|
+
onError?.(error, 'login');
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
setIsLoggingIn(false);
|
|
279
|
+
}
|
|
280
|
+
}, [loginFn, setToken, saveUser, onLoginSuccess, onError, useSessionStorage]);
|
|
281
|
+
// 로그아웃
|
|
282
|
+
const logout = useCallback(async () => {
|
|
283
|
+
setIsLoggingOut(true);
|
|
284
|
+
setError(null);
|
|
285
|
+
try {
|
|
286
|
+
if (logoutFn) {
|
|
287
|
+
await logoutFn();
|
|
288
|
+
}
|
|
289
|
+
setToken(null);
|
|
290
|
+
saveUser(null);
|
|
291
|
+
onLogoutSuccess?.();
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
const error = err instanceof Error ? err : new Error('Logout failed');
|
|
295
|
+
setError(error);
|
|
296
|
+
onError?.(error, 'logout');
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
setIsLoggingOut(false);
|
|
301
|
+
}
|
|
302
|
+
}, [logoutFn, setToken, saveUser, onLogoutSuccess, onError]);
|
|
303
|
+
// 회원가입
|
|
304
|
+
const register = useCallback(async (data, registerOptions) => {
|
|
305
|
+
if (!registerFn) {
|
|
306
|
+
throw new Error('registerFn is required for register');
|
|
307
|
+
}
|
|
308
|
+
const rememberMe = registerOptions?.rememberMe ?? !useSessionStorage;
|
|
309
|
+
setIsRegistering(true);
|
|
310
|
+
setError(null);
|
|
311
|
+
try {
|
|
312
|
+
const result = await registerFn(data);
|
|
313
|
+
if (result.token) {
|
|
314
|
+
setToken(result.token, rememberMe);
|
|
315
|
+
}
|
|
316
|
+
saveUser(result.user, rememberMe);
|
|
317
|
+
onRegisterSuccess?.(result.user);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
const error = err instanceof Error ? err : new Error('Registration failed');
|
|
321
|
+
setError(error);
|
|
322
|
+
onError?.(error, 'register');
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
setIsRegistering(false);
|
|
327
|
+
}
|
|
328
|
+
}, [registerFn, setToken, saveUser, onRegisterSuccess, onError, useSessionStorage]);
|
|
329
|
+
// 토큰 갱신
|
|
330
|
+
const refreshToken = useCallback(async () => {
|
|
331
|
+
if (!refreshTokenFn) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const result = await refreshTokenFn();
|
|
336
|
+
setToken(result.token);
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
const error = err instanceof Error ? err : new Error('Token refresh failed');
|
|
340
|
+
setError(error);
|
|
341
|
+
onError?.(error, 'refresh');
|
|
342
|
+
// 토큰 갱신 실패 시 로그아웃
|
|
343
|
+
await logout();
|
|
344
|
+
}
|
|
345
|
+
}, [refreshTokenFn, setToken, onError, logout]);
|
|
346
|
+
// 자동 토큰 갱신
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (autoRefresh && token && refreshTokenFn) {
|
|
349
|
+
refreshIntervalRef.current = setInterval(() => {
|
|
350
|
+
refreshToken();
|
|
351
|
+
}, refreshInterval);
|
|
352
|
+
return () => {
|
|
353
|
+
if (refreshIntervalRef.current) {
|
|
354
|
+
clearInterval(refreshIntervalRef.current);
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}, [autoRefresh, token, refreshTokenFn, refreshToken, refreshInterval]);
|
|
359
|
+
// 사용자 정보 새로고침
|
|
360
|
+
const refetchUser = useCallback(async () => {
|
|
361
|
+
if (!getUserFn) {
|
|
362
|
+
throw new Error('getUserFn is required for refetchUser');
|
|
363
|
+
}
|
|
364
|
+
setIsLoading(true);
|
|
365
|
+
await loadUser();
|
|
366
|
+
}, [getUserFn, loadUser]);
|
|
367
|
+
return {
|
|
368
|
+
user,
|
|
369
|
+
isAuthenticated: !!user,
|
|
370
|
+
isLoading,
|
|
371
|
+
isLoggingIn,
|
|
372
|
+
isLoggingOut,
|
|
373
|
+
isRegistering,
|
|
374
|
+
error,
|
|
375
|
+
login,
|
|
376
|
+
logout,
|
|
377
|
+
register,
|
|
378
|
+
refreshToken,
|
|
379
|
+
refetchUser,
|
|
380
|
+
token,
|
|
381
|
+
setToken,
|
|
382
|
+
setUser: saveUser,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/event/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useClickOutside.d.ts","sourceRoot":"","sources":["../../../src/hooks/event/useClickOutside.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACjE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,KAAK,IAAI,EACjD,OAAO,GAAE,OAAc,GACtB,IAAI,CA2BN;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,EACzE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACpB,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,KAAK,IAAI,EACjD,OAAO,GAAE,OAAc,GACtB,IAAI,CA6BN"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEventListener.d.ts","sourceRoot":"","sources":["../../../src/hooks/event/useEventListener.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,cAAc,EAC7D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,EAC3C,OAAO,CAAC,EAAE,SAAS,EACnB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,mBAAmB,EAAE,CAAC,SAAS,WAAW,GAAG,cAAc,EAC1G,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,EAChD,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC;AAER,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAC/D,SAAS,EAAE,CAAC,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,KAAK,IAAI,EAC7C,OAAO,EAAE,QAAQ,EACjB,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI,CAAC"}
|