@sonhoseong/mfa-lib 1.0.0
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 +172 -0
- package/dist/components/button/ScrollTopButton.d.ts +5 -0
- package/dist/components/button/ScrollTopButton.d.ts.map +1 -0
- package/dist/components/button/ScrollTopButton.js +28 -0
- package/dist/components/button/index.d.ts +3 -0
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +2 -0
- package/dist/components/button/types/index.d.ts +10 -0
- package/dist/components/button/types/index.d.ts.map +1 -0
- package/dist/components/button/types/index.js +1 -0
- package/dist/components/error/ErrorBoundary.d.ts +25 -0
- package/dist/components/error/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/error/ErrorBoundary.js +130 -0
- package/dist/components/error/index.d.ts +2 -0
- package/dist/components/error/index.d.ts.map +1 -0
- package/dist/components/error/index.js +1 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +10 -0
- package/dist/components/loading/GlobalLoading.d.ts +12 -0
- package/dist/components/loading/GlobalLoading.d.ts.map +1 -0
- package/dist/components/loading/GlobalLoading.js +84 -0
- package/dist/components/loading/index.d.ts +2 -0
- package/dist/components/loading/index.d.ts.map +1 -0
- package/dist/components/loading/index.js +1 -0
- package/dist/components/modal/ModalContainer.d.ts +8 -0
- package/dist/components/modal/ModalContainer.d.ts.map +1 -0
- package/dist/components/modal/ModalContainer.js +161 -0
- package/dist/components/modal/ModalContext.d.ts +43 -0
- package/dist/components/modal/ModalContext.d.ts.map +1 -0
- package/dist/components/modal/ModalContext.js +78 -0
- package/dist/components/modal/index.d.ts +4 -0
- package/dist/components/modal/index.d.ts.map +1 -0
- package/dist/components/modal/index.js +2 -0
- package/dist/components/toast/ToastContainer.d.ts +12 -0
- package/dist/components/toast/ToastContainer.d.ts.map +1 -0
- package/dist/components/toast/ToastContainer.js +125 -0
- package/dist/components/toast/ToastContext.d.ts +40 -0
- package/dist/components/toast/ToastContext.d.ts.map +1 -0
- package/dist/components/toast/ToastContext.js +56 -0
- package/dist/components/toast/index.d.ts +4 -0
- package/dist/components/toast/index.d.ts.map +1 -0
- package/dist/components/toast/index.js +2 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +17 -0
- package/dist/hooks/use-auth.d.ts +38 -0
- package/dist/hooks/use-auth.d.ts.map +1 -0
- package/dist/hooks/use-auth.js +117 -0
- package/dist/hooks/use-error-notification.d.ts +29 -0
- package/dist/hooks/use-error-notification.d.ts.map +1 -0
- package/dist/hooks/use-error-notification.js +91 -0
- package/dist/hooks/use-global-loading.d.ts +16 -0
- package/dist/hooks/use-global-loading.d.ts.map +1 -0
- package/dist/hooks/use-global-loading.js +72 -0
- package/dist/hooks/use-initialize.d.ts +26 -0
- package/dist/hooks/use-initialize.d.ts.map +1 -0
- package/dist/hooks/use-initialize.js +104 -0
- package/dist/hooks/use-modal.d.ts +39 -0
- package/dist/hooks/use-modal.d.ts.map +1 -0
- package/dist/hooks/use-modal.js +83 -0
- package/dist/hooks/use-navigate.d.ts +34 -0
- package/dist/hooks/use-navigate.d.ts.map +1 -0
- package/dist/hooks/use-navigate.js +89 -0
- package/dist/hooks/use-track-history.d.ts +33 -0
- package/dist/hooks/use-track-history.d.ts.map +1 -0
- package/dist/hooks/use-track-history.js +150 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/network/axios-factory.d.ts +61 -0
- package/dist/network/axios-factory.d.ts.map +1 -0
- package/dist/network/axios-factory.js +119 -0
- package/dist/network/index.d.ts +5 -0
- package/dist/network/index.d.ts.map +1 -0
- package/dist/network/index.js +4 -0
- package/dist/network/supabase-axios.d.ts +62 -0
- package/dist/network/supabase-axios.d.ts.map +1 -0
- package/dist/network/supabase-axios.js +74 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/store-access.d.ts +48 -0
- package/dist/store/store-access.d.ts.map +1 -0
- package/dist/store/store-access.js +131 -0
- package/dist/types/index.d.ts +58 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/service.d.ts +17 -0
- package/dist/types/service.d.ts.map +1 -0
- package/dist/types/service.js +31 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/storage.d.ts +27 -0
- package/dist/utils/storage.d.ts.map +1 -0
- package/dist/utils/storage.js +78 -0
- package/package.json +27 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Modal Container - KOMCA 패턴
|
|
4
|
+
* 모달 UI 렌더링
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect } from 'react';
|
|
7
|
+
import { useModalContext } from './ModalContext';
|
|
8
|
+
const ModalContainer = () => {
|
|
9
|
+
const { modals, closeModal } = useModalContext();
|
|
10
|
+
// ESC 키로 모달 닫기
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const handleKeyDown = (e) => {
|
|
13
|
+
if (e.key === 'Escape' && modals.length > 0) {
|
|
14
|
+
const lastModal = modals[modals.length - 1];
|
|
15
|
+
closeModal(lastModal.id, false);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
19
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
20
|
+
}, [modals, closeModal]);
|
|
21
|
+
// 모달 열렸을 때 body 스크롤 방지
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (modals.length > 0) {
|
|
24
|
+
document.body.style.overflow = 'hidden';
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
document.body.style.overflow = '';
|
|
28
|
+
}
|
|
29
|
+
return () => {
|
|
30
|
+
document.body.style.overflow = '';
|
|
31
|
+
};
|
|
32
|
+
}, [modals.length]);
|
|
33
|
+
if (modals.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
const handleOverlayClick = (modal) => {
|
|
36
|
+
if (modal.closeOnOverlayClick !== false) {
|
|
37
|
+
closeModal(modal.id, false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const handleConfirm = async (modal) => {
|
|
41
|
+
if (modal.onConfirm) {
|
|
42
|
+
await modal.onConfirm();
|
|
43
|
+
}
|
|
44
|
+
closeModal(modal.id, true);
|
|
45
|
+
};
|
|
46
|
+
const handleCancel = (modal) => {
|
|
47
|
+
modal.onCancel?.();
|
|
48
|
+
closeModal(modal.id, false);
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs(_Fragment, { children: [modals.map((modal, index) => (_jsx("div", { className: "modal-overlay", style: { zIndex: 10000 + index }, onClick: () => handleOverlayClick(modal), children: _jsx("div", { className: "modal-container", onClick: (e) => e.stopPropagation(), children: modal.content ? (
|
|
51
|
+
// 커스텀 컨텐츠
|
|
52
|
+
modal.content) : (
|
|
53
|
+
// 기본 Alert/Confirm UI
|
|
54
|
+
_jsxs(_Fragment, { children: [modal.title && (_jsx("div", { className: "modal-header", children: _jsx("h3", { className: "modal-title", children: modal.title }) })), _jsx("div", { className: "modal-body", children: _jsx("p", { className: "modal-message", children: modal.message }) }), _jsxs("div", { className: "modal-footer", children: [modal.type === 'confirm' && modal.cancelText && (_jsx("button", { className: "modal-button secondary", onClick: () => handleCancel(modal), children: modal.cancelText })), _jsx("button", { className: "modal-button primary", onClick: () => handleConfirm(modal), children: modal.confirmText || '확인' })] })] })) }) }, modal.id))), _jsx("style", { children: `
|
|
55
|
+
.modal-overlay {
|
|
56
|
+
position: fixed;
|
|
57
|
+
top: 0;
|
|
58
|
+
left: 0;
|
|
59
|
+
right: 0;
|
|
60
|
+
bottom: 0;
|
|
61
|
+
background: rgba(0, 0, 0, 0.5);
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
animation: modal-fade-in 0.2s ease-out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@keyframes modal-fade-in {
|
|
69
|
+
from {
|
|
70
|
+
opacity: 0;
|
|
71
|
+
}
|
|
72
|
+
to {
|
|
73
|
+
opacity: 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.modal-container {
|
|
78
|
+
background: white;
|
|
79
|
+
border-radius: 12px;
|
|
80
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
81
|
+
min-width: 320px;
|
|
82
|
+
max-width: 480px;
|
|
83
|
+
max-height: 90vh;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
animation: modal-slide-up 0.2s ease-out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes modal-slide-up {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
transform: translateY(20px);
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
transform: translateY(0);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.modal-header {
|
|
100
|
+
padding: 20px 24px 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.modal-title {
|
|
104
|
+
margin: 0;
|
|
105
|
+
font-size: 18px;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
color: #111827;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.modal-body {
|
|
111
|
+
padding: 16px 24px 24px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.modal-message {
|
|
115
|
+
margin: 0;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
color: #4b5563;
|
|
118
|
+
line-height: 1.6;
|
|
119
|
+
white-space: pre-wrap;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.modal-footer {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: flex-end;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
padding: 16px 24px;
|
|
127
|
+
background: #f9fafb;
|
|
128
|
+
border-top: 1px solid #e5e7eb;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.modal-button {
|
|
132
|
+
padding: 10px 20px;
|
|
133
|
+
font-size: 14px;
|
|
134
|
+
font-weight: 500;
|
|
135
|
+
border-radius: 8px;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
transition: all 0.15s;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.modal-button.primary {
|
|
141
|
+
background: #3b82f6;
|
|
142
|
+
color: white;
|
|
143
|
+
border: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.modal-button.primary:hover {
|
|
147
|
+
background: #2563eb;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.modal-button.secondary {
|
|
151
|
+
background: white;
|
|
152
|
+
color: #374151;
|
|
153
|
+
border: 1px solid #d1d5db;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.modal-button.secondary:hover {
|
|
157
|
+
background: #f3f4f6;
|
|
158
|
+
}
|
|
159
|
+
` })] }));
|
|
160
|
+
};
|
|
161
|
+
export default ModalContainer;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal Context - KOMCA 패턴
|
|
3
|
+
* 전역 모달 상태 관리
|
|
4
|
+
*/
|
|
5
|
+
import React, { ReactNode } from 'react';
|
|
6
|
+
export type ModalType = 'alert' | 'confirm' | 'custom';
|
|
7
|
+
export interface ModalOptions {
|
|
8
|
+
type: ModalType;
|
|
9
|
+
title?: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
confirmText?: string;
|
|
12
|
+
cancelText?: string;
|
|
13
|
+
content?: ReactNode;
|
|
14
|
+
onConfirm?: () => void | Promise<void>;
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
closeOnOverlayClick?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ModalItem extends ModalOptions {
|
|
19
|
+
id: string;
|
|
20
|
+
resolve?: (value: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
interface ModalContextType {
|
|
23
|
+
modals: ModalItem[];
|
|
24
|
+
alert: (message: string, title?: string) => Promise<void>;
|
|
25
|
+
confirm: (message: string, title?: string) => Promise<boolean>;
|
|
26
|
+
openModal: (options: ModalOptions) => string;
|
|
27
|
+
closeModal: (id: string, result?: boolean) => void;
|
|
28
|
+
closeAll: () => void;
|
|
29
|
+
}
|
|
30
|
+
declare const ModalContext: React.Context<ModalContextType | null>;
|
|
31
|
+
interface ModalProviderProps {
|
|
32
|
+
children: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Modal Provider
|
|
36
|
+
*/
|
|
37
|
+
export declare const ModalProvider: React.FC<ModalProviderProps>;
|
|
38
|
+
/**
|
|
39
|
+
* useModal Hook
|
|
40
|
+
*/
|
|
41
|
+
export declare const useModalContext: () => ModalContextType;
|
|
42
|
+
export default ModalContext;
|
|
43
|
+
//# sourceMappingURL=ModalContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalContext.d.ts","sourceRoot":"","sources":["../../../src/components/modal/ModalContext.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAoD,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3F,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;AAGvD,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC;AAGD,MAAM,WAAW,SAAU,SAAQ,YAAY;IAC3C,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACtC;AAGD,UAAU,gBAAgB;IACtB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,SAAS,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IAC7C,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnD,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,QAAA,MAAM,YAAY,wCAA+C,CAAC;AAElE,UAAU,kBAAkB;IACxB,QAAQ,EAAE,SAAS,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,QAAO,gBAMlC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Modal Context - KOMCA 패턴
|
|
4
|
+
* 전역 모달 상태 관리
|
|
5
|
+
*/
|
|
6
|
+
import { createContext, useContext, useCallback, useState } from 'react';
|
|
7
|
+
const ModalContext = createContext(null);
|
|
8
|
+
/**
|
|
9
|
+
* Modal Provider
|
|
10
|
+
*/
|
|
11
|
+
export const ModalProvider = ({ children }) => {
|
|
12
|
+
const [modals, setModals] = useState([]);
|
|
13
|
+
const openModal = useCallback((options) => {
|
|
14
|
+
const id = `modal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
15
|
+
setModals((prev) => [...prev, { ...options, id }]);
|
|
16
|
+
return id;
|
|
17
|
+
}, []);
|
|
18
|
+
const closeModal = useCallback((id, result = false) => {
|
|
19
|
+
setModals((prev) => {
|
|
20
|
+
const modal = prev.find((m) => m.id === id);
|
|
21
|
+
if (modal?.resolve) {
|
|
22
|
+
modal.resolve(result);
|
|
23
|
+
}
|
|
24
|
+
return prev.filter((m) => m.id !== id);
|
|
25
|
+
});
|
|
26
|
+
}, []);
|
|
27
|
+
const closeAll = useCallback(() => {
|
|
28
|
+
setModals((prev) => {
|
|
29
|
+
prev.forEach((modal) => modal.resolve?.(false));
|
|
30
|
+
return [];
|
|
31
|
+
});
|
|
32
|
+
}, []);
|
|
33
|
+
const alert = useCallback((message, title) => {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const id = `modal-${Date.now()}`;
|
|
36
|
+
setModals((prev) => [
|
|
37
|
+
...prev,
|
|
38
|
+
{
|
|
39
|
+
id,
|
|
40
|
+
type: 'alert',
|
|
41
|
+
title: title || '알림',
|
|
42
|
+
message,
|
|
43
|
+
confirmText: '확인',
|
|
44
|
+
resolve: () => resolve(),
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
}, []);
|
|
49
|
+
const confirm = useCallback((message, title) => {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const id = `modal-${Date.now()}`;
|
|
52
|
+
setModals((prev) => [
|
|
53
|
+
...prev,
|
|
54
|
+
{
|
|
55
|
+
id,
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
title: title || '확인',
|
|
58
|
+
message,
|
|
59
|
+
confirmText: '확인',
|
|
60
|
+
cancelText: '취소',
|
|
61
|
+
resolve,
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
}, []);
|
|
66
|
+
return (_jsx(ModalContext.Provider, { value: { modals, alert, confirm, openModal, closeModal, closeAll }, children: children }));
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* useModal Hook
|
|
70
|
+
*/
|
|
71
|
+
export const useModalContext = () => {
|
|
72
|
+
const context = useContext(ModalContext);
|
|
73
|
+
if (!context) {
|
|
74
|
+
throw new Error('useModalContext must be used within a ModalProvider');
|
|
75
|
+
}
|
|
76
|
+
return context;
|
|
77
|
+
};
|
|
78
|
+
export default ModalContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/modal/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAChE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast Container - KOMCA 패턴
|
|
3
|
+
* 토스트 알림 UI 렌더링
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
interface ToastContainerProps {
|
|
7
|
+
/** 위치 */
|
|
8
|
+
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center';
|
|
9
|
+
}
|
|
10
|
+
declare const ToastContainer: React.FC<ToastContainerProps>;
|
|
11
|
+
export default ToastContainer;
|
|
12
|
+
//# sourceMappingURL=ToastContainer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastContainer.d.ts","sourceRoot":"","sources":["../../../src/components/toast/ToastContainer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAmB1B,UAAU,mBAAmB;IACzB,SAAS;IACT,QAAQ,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,GAAG,eAAe,CAAC;CACzG;AAED,QAAA,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA8IjD,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useToast } from './ToastContext';
|
|
3
|
+
// 타입별 아이콘
|
|
4
|
+
const icons = {
|
|
5
|
+
success: '✓',
|
|
6
|
+
error: '✕',
|
|
7
|
+
warning: '⚠',
|
|
8
|
+
info: 'ℹ',
|
|
9
|
+
};
|
|
10
|
+
// 타입별 색상
|
|
11
|
+
const colors = {
|
|
12
|
+
success: { bg: '#ecfdf5', border: '#10b981', icon: '#059669' },
|
|
13
|
+
error: { bg: '#fef2f2', border: '#ef4444', icon: '#dc2626' },
|
|
14
|
+
warning: { bg: '#fffbeb', border: '#f59e0b', icon: '#d97706' },
|
|
15
|
+
info: { bg: '#eff6ff', border: '#3b82f6', icon: '#2563eb' },
|
|
16
|
+
};
|
|
17
|
+
const ToastContainer = ({ position = 'top-right' }) => {
|
|
18
|
+
const { toasts, removeToast } = useToast();
|
|
19
|
+
const getPositionStyles = () => {
|
|
20
|
+
const base = { position: 'fixed', zIndex: 9998 };
|
|
21
|
+
switch (position) {
|
|
22
|
+
case 'top-left':
|
|
23
|
+
return { ...base, top: 20, left: 20 };
|
|
24
|
+
case 'top-center':
|
|
25
|
+
return { ...base, top: 20, left: '50%', transform: 'translateX(-50%)' };
|
|
26
|
+
case 'bottom-right':
|
|
27
|
+
return { ...base, bottom: 20, right: 20 };
|
|
28
|
+
case 'bottom-left':
|
|
29
|
+
return { ...base, bottom: 20, left: 20 };
|
|
30
|
+
case 'bottom-center':
|
|
31
|
+
return { ...base, bottom: 20, left: '50%', transform: 'translateX(-50%)' };
|
|
32
|
+
case 'top-right':
|
|
33
|
+
default:
|
|
34
|
+
return { ...base, top: 20, right: 20 };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
if (toasts.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
return (_jsxs("div", { style: getPositionStyles(), children: [_jsx("div", { className: "toast-list", children: toasts.map((toast) => (_jsxs("div", { className: `toast-item toast-${toast.type}`, style: {
|
|
40
|
+
backgroundColor: colors[toast.type].bg,
|
|
41
|
+
borderLeftColor: colors[toast.type].border,
|
|
42
|
+
}, children: [_jsx("div", { className: "toast-icon", style: { color: colors[toast.type].icon }, children: icons[toast.type] }), _jsxs("div", { className: "toast-content", children: [toast.title && _jsx("div", { className: "toast-title", children: toast.title }), _jsx("div", { className: "toast-message", children: toast.message })] }), _jsx("button", { className: "toast-close", onClick: () => removeToast(toast.id), "aria-label": "\uB2EB\uAE30", children: "\u2715" })] }, toast.id))) }), _jsx("style", { children: `
|
|
43
|
+
.toast-list {
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 8px;
|
|
47
|
+
max-width: 380px;
|
|
48
|
+
width: 100%;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.toast-item {
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: flex-start;
|
|
54
|
+
gap: 12px;
|
|
55
|
+
padding: 14px 16px;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
border-left: 4px solid;
|
|
58
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
59
|
+
animation: toast-slide-in 0.3s ease-out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@keyframes toast-slide-in {
|
|
63
|
+
from {
|
|
64
|
+
opacity: 0;
|
|
65
|
+
transform: translateX(100%);
|
|
66
|
+
}
|
|
67
|
+
to {
|
|
68
|
+
opacity: 1;
|
|
69
|
+
transform: translateX(0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.toast-icon {
|
|
74
|
+
flex-shrink: 0;
|
|
75
|
+
width: 20px;
|
|
76
|
+
height: 20px;
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
font-size: 14px;
|
|
81
|
+
font-weight: bold;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.toast-content {
|
|
85
|
+
flex: 1;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.toast-title {
|
|
90
|
+
font-size: 14px;
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
color: #111827;
|
|
93
|
+
margin-bottom: 2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.toast-message {
|
|
97
|
+
font-size: 13px;
|
|
98
|
+
color: #4b5563;
|
|
99
|
+
line-height: 1.4;
|
|
100
|
+
word-break: break-word;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.toast-close {
|
|
104
|
+
flex-shrink: 0;
|
|
105
|
+
width: 20px;
|
|
106
|
+
height: 20px;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
background: transparent;
|
|
111
|
+
border: none;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
color: #9ca3af;
|
|
114
|
+
font-size: 12px;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
transition: all 0.15s;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.toast-close:hover {
|
|
120
|
+
background: rgba(0, 0, 0, 0.1);
|
|
121
|
+
color: #374151;
|
|
122
|
+
}
|
|
123
|
+
` })] }));
|
|
124
|
+
};
|
|
125
|
+
export default ToastContainer;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toast Context - KOMCA 패턴
|
|
3
|
+
* 전역 토스트 알림 상태 관리
|
|
4
|
+
*/
|
|
5
|
+
import React, { ReactNode } from 'react';
|
|
6
|
+
export type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
7
|
+
export interface ToastItem {
|
|
8
|
+
id: string;
|
|
9
|
+
type: ToastType;
|
|
10
|
+
message: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
duration?: number;
|
|
13
|
+
}
|
|
14
|
+
interface ToastContextType {
|
|
15
|
+
toasts: ToastItem[];
|
|
16
|
+
addToast: (toast: Omit<ToastItem, 'id'>) => void;
|
|
17
|
+
removeToast: (id: string) => void;
|
|
18
|
+
success: (message: string, title?: string) => void;
|
|
19
|
+
error: (message: string, title?: string) => void;
|
|
20
|
+
warning: (message: string, title?: string) => void;
|
|
21
|
+
info: (message: string, title?: string) => void;
|
|
22
|
+
}
|
|
23
|
+
declare const ToastContext: React.Context<ToastContextType | null>;
|
|
24
|
+
interface ToastProviderProps {
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
/** 기본 표시 시간 (ms) */
|
|
27
|
+
defaultDuration?: number;
|
|
28
|
+
/** 최대 표시 개수 */
|
|
29
|
+
maxToasts?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Toast Provider
|
|
33
|
+
*/
|
|
34
|
+
export declare const ToastProvider: React.FC<ToastProviderProps>;
|
|
35
|
+
/**
|
|
36
|
+
* useToast Hook
|
|
37
|
+
*/
|
|
38
|
+
export declare const useToast: () => ToastContextType;
|
|
39
|
+
export default ToastContext;
|
|
40
|
+
//# sourceMappingURL=ToastContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToastContext.d.ts","sourceRoot":"","sources":["../../../src/components/toast/ToastContext.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAoD,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3F,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAGjE,MAAM,WAAW,SAAS;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAGD,UAAU,gBAAgB;IACtB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC;IACjD,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,QAAA,MAAM,YAAY,wCAA+C,CAAC;AAGlE,UAAU,kBAAkB;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,oBAAoB;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmDtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAO,gBAM3B,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Toast Context - KOMCA 패턴
|
|
4
|
+
* 전역 토스트 알림 상태 관리
|
|
5
|
+
*/
|
|
6
|
+
import { createContext, useContext, useCallback, useState } from 'react';
|
|
7
|
+
const ToastContext = createContext(null);
|
|
8
|
+
/**
|
|
9
|
+
* Toast Provider
|
|
10
|
+
*/
|
|
11
|
+
export const ToastProvider = ({ children, defaultDuration = 3000, maxToasts = 5, }) => {
|
|
12
|
+
const [toasts, setToasts] = useState([]);
|
|
13
|
+
const addToast = useCallback((toast) => {
|
|
14
|
+
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
15
|
+
const duration = toast.duration ?? defaultDuration;
|
|
16
|
+
setToasts((prev) => {
|
|
17
|
+
const newToasts = [...prev, { ...toast, id }];
|
|
18
|
+
// 최대 개수 초과 시 오래된 것 제거
|
|
19
|
+
return newToasts.slice(-maxToasts);
|
|
20
|
+
});
|
|
21
|
+
// 자동 제거
|
|
22
|
+
if (duration > 0) {
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
removeToast(id);
|
|
25
|
+
}, duration);
|
|
26
|
+
}
|
|
27
|
+
}, [defaultDuration, maxToasts]);
|
|
28
|
+
const removeToast = useCallback((id) => {
|
|
29
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
30
|
+
}, []);
|
|
31
|
+
// 편의 메서드
|
|
32
|
+
const success = useCallback((message, title) => {
|
|
33
|
+
addToast({ type: 'success', message, title });
|
|
34
|
+
}, [addToast]);
|
|
35
|
+
const error = useCallback((message, title) => {
|
|
36
|
+
addToast({ type: 'error', message, title, duration: 5000 });
|
|
37
|
+
}, [addToast]);
|
|
38
|
+
const warning = useCallback((message, title) => {
|
|
39
|
+
addToast({ type: 'warning', message, title });
|
|
40
|
+
}, [addToast]);
|
|
41
|
+
const info = useCallback((message, title) => {
|
|
42
|
+
addToast({ type: 'info', message, title });
|
|
43
|
+
}, [addToast]);
|
|
44
|
+
return (_jsx(ToastContext.Provider, { value: { toasts, addToast, removeToast, success, error, warning, info }, children: children }));
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* useToast Hook
|
|
48
|
+
*/
|
|
49
|
+
export const useToast = () => {
|
|
50
|
+
const context = useContext(ToastContext);
|
|
51
|
+
if (!context) {
|
|
52
|
+
throw new Error('useToast must be used within a ToastProvider');
|
|
53
|
+
}
|
|
54
|
+
return context;
|
|
55
|
+
};
|
|
56
|
+
export default ToastContext;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/toast/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks 모듈 - KOMCA 패턴
|
|
3
|
+
*/
|
|
4
|
+
export * from './use-auth';
|
|
5
|
+
export * from './use-initialize';
|
|
6
|
+
export * from './use-track-history';
|
|
7
|
+
export * from './use-navigate';
|
|
8
|
+
export * from './use-global-loading';
|
|
9
|
+
export * from './use-modal';
|
|
10
|
+
export * from './use-error-notification';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,YAAY,CAAC;AAG3B,cAAc,kBAAkB,CAAC;AAGjC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAG5B,cAAc,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks 모듈 - KOMCA 패턴
|
|
3
|
+
*/
|
|
4
|
+
// Auth Hooks
|
|
5
|
+
export * from './use-auth';
|
|
6
|
+
// Initialize Hook
|
|
7
|
+
export * from './use-initialize';
|
|
8
|
+
// Track History Hook
|
|
9
|
+
export * from './use-track-history';
|
|
10
|
+
// Navigate Hook
|
|
11
|
+
export * from './use-navigate';
|
|
12
|
+
// Global Loading Hook
|
|
13
|
+
export * from './use-global-loading';
|
|
14
|
+
// Modal Hooks
|
|
15
|
+
export * from './use-modal';
|
|
16
|
+
// Error Notification Hook
|
|
17
|
+
export * from './use-error-notification';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Hooks - KOMCA 패턴
|
|
3
|
+
* 로그인, 로그아웃, 토큰 갱신
|
|
4
|
+
*/
|
|
5
|
+
import { User } from '../types';
|
|
6
|
+
export interface LoginResponse {
|
|
7
|
+
accessToken: string;
|
|
8
|
+
refreshToken?: string;
|
|
9
|
+
user: User;
|
|
10
|
+
}
|
|
11
|
+
export interface LoginRequest {
|
|
12
|
+
username: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
export type LoginFn = (request: LoginRequest) => Promise<LoginResponse>;
|
|
16
|
+
export type LogoutFn = () => Promise<void>;
|
|
17
|
+
export type RefreshFn = () => Promise<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* 로그인 Hook
|
|
20
|
+
*/
|
|
21
|
+
export declare function useLogin(loginApi?: LoginFn): (request: LoginRequest) => Promise<LoginResponse | null>;
|
|
22
|
+
/**
|
|
23
|
+
* 로그아웃 Hook
|
|
24
|
+
*/
|
|
25
|
+
export declare function useLogout(logoutApi?: LogoutFn): () => Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* 토큰 갱신 Hook
|
|
28
|
+
*/
|
|
29
|
+
export declare function useTokenRefresh(refreshApi?: RefreshFn): () => Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* 인증 상태 확인 Hook
|
|
32
|
+
*/
|
|
33
|
+
export declare function useAuthState(): {
|
|
34
|
+
isAuthenticated: boolean;
|
|
35
|
+
user: User | null;
|
|
36
|
+
accessToken: string;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=use-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-auth.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGhC,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC;CACZ;AAGD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAGxE,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAG3C,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAErD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,aACN,YAAY,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAuChF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,QAAQ,SACf,OAAO,CAAC,IAAI,CAAC,CAuB3C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,SAAS,SACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BpD;AAED;;GAEG;AACH,wBAAgB,YAAY;;;;EAS3B"}
|