@sonhoseong/mfa-lib 1.3.7 → 1.3.10
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/dist/components/button/ScrollTopButton.js +5 -3
- package/dist/components/error/ErrorBoundary.js +14 -4
- package/dist/components/error/NotFound.d.ts +20 -0
- package/dist/components/error/NotFound.d.ts.map +1 -0
- package/dist/components/error/NotFound.js +84 -0
- package/dist/components/error/index.d.ts +2 -0
- package/dist/components/error/index.d.ts.map +1 -1
- package/dist/components/error/index.js +1 -0
- package/dist/components/icons/Icons.d.ts +51 -0
- package/dist/components/icons/Icons.d.ts.map +1 -0
- package/dist/components/icons/Icons.js +100 -0
- package/dist/components/icons/index.d.ts +5 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/index.js +4 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/layout/Container.js +7 -2
- package/dist/components/loading/DeferredComponent.d.ts +19 -0
- package/dist/components/loading/DeferredComponent.d.ts.map +1 -0
- package/dist/components/loading/DeferredComponent.js +32 -0
- package/dist/components/loading/GlobalLoading.js +14 -3
- package/dist/components/loading/index.d.ts +1 -0
- package/dist/components/loading/index.d.ts.map +1 -1
- package/dist/components/loading/index.js +1 -0
- package/dist/components/logo/Logo.d.ts +2 -0
- package/dist/components/logo/Logo.d.ts.map +1 -1
- package/dist/components/logo/Logo.js +13 -4
- package/dist/components/modal/ModalContainer.js +17 -8
- package/dist/components/modal/ModalContext.js +2 -3
- package/dist/components/navigation/AppNavbar.js +21 -9
- package/dist/components/navigation/AppSidebar.css +58 -3
- package/dist/components/navigation/AppSidebar.d.ts +1 -1
- package/dist/components/navigation/AppSidebar.d.ts.map +1 -1
- package/dist/components/navigation/AppSidebar.js +58 -15
- package/dist/components/navigation/Footer.d.ts +15 -0
- package/dist/components/navigation/Footer.d.ts.map +1 -0
- package/dist/components/navigation/Footer.js +12 -0
- package/dist/components/navigation/Header.d.ts.map +1 -1
- package/dist/components/navigation/Header.js +17 -4
- package/dist/components/navigation/Lnb.d.ts +2 -7
- package/dist/components/navigation/Lnb.d.ts.map +1 -1
- package/dist/components/navigation/Lnb.js +34 -6
- package/dist/components/navigation/StickyNav.js +19 -11
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +1 -0
- package/dist/components/page/LoginPage.d.ts +4 -1
- package/dist/components/page/LoginPage.d.ts.map +1 -1
- package/dist/components/page/LoginPage.js +146 -21
- package/dist/components/remote/RemoteErrorBoundary.d.ts +28 -0
- package/dist/components/remote/RemoteErrorBoundary.d.ts.map +1 -0
- package/dist/components/remote/RemoteErrorBoundary.js +44 -0
- package/dist/components/remote/RemoteErrorFallback.d.ts +16 -0
- package/dist/components/remote/RemoteErrorFallback.d.ts.map +1 -0
- package/dist/components/remote/RemoteErrorFallback.js +76 -0
- package/dist/components/remote/index.d.ts +8 -0
- package/dist/components/remote/index.d.ts.map +1 -0
- package/dist/components/remote/index.js +5 -0
- package/dist/components/router/BrowserRouter.d.ts +13 -0
- package/dist/components/router/BrowserRouter.d.ts.map +1 -0
- package/dist/components/router/BrowserRouter.js +17 -0
- package/dist/components/router/RouteGuard.d.ts +79 -0
- package/dist/components/router/RouteGuard.d.ts.map +1 -0
- package/dist/components/router/RouteGuard.js +86 -0
- package/dist/components/router/index.d.ts +4 -0
- package/dist/components/router/index.d.ts.map +1 -0
- package/dist/components/router/index.js +2 -0
- package/dist/components/toast/ToastContainer.js +17 -6
- package/dist/components/toast/ToastContext.js +2 -3
- package/dist/hooks/index.d.ts +9 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -1
- package/dist/hooks/use-auth.d.ts +2 -1
- package/dist/hooks/use-auth.d.ts.map +1 -1
- package/dist/hooks/use-auth.js +19 -18
- package/dist/hooks/use-debounce.d.ts +56 -0
- package/dist/hooks/use-debounce.d.ts.map +1 -0
- package/dist/hooks/use-debounce.js +140 -0
- package/dist/hooks/use-effect-once.d.ts +77 -0
- package/dist/hooks/use-effect-once.d.ts.map +1 -0
- package/dist/hooks/use-effect-once.js +124 -0
- package/dist/hooks/use-error-notification.d.ts +1 -1
- package/dist/hooks/use-error-notification.js +1 -1
- package/dist/hooks/use-global-loading.d.ts +1 -1
- package/dist/hooks/use-global-loading.js +1 -1
- package/dist/hooks/use-initialize.d.ts +8 -1
- package/dist/hooks/use-initialize.d.ts.map +1 -1
- package/dist/hooks/use-initialize.js +126 -23
- package/dist/hooks/use-modal.d.ts +21 -5
- package/dist/hooks/use-modal.d.ts.map +1 -1
- package/dist/hooks/use-modal.js +57 -17
- package/dist/hooks/use-navigate.d.ts +1 -1
- package/dist/hooks/use-navigate.js +1 -1
- package/dist/hooks/use-network-status.d.ts +15 -0
- package/dist/hooks/use-network-status.d.ts.map +1 -0
- package/dist/hooks/use-network-status.js +49 -0
- package/dist/hooks/use-permission.d.ts +22 -0
- package/dist/hooks/use-permission.d.ts.map +1 -0
- package/dist/hooks/use-permission.js +73 -0
- package/dist/hooks/use-recent-menu.d.ts +46 -0
- package/dist/hooks/use-recent-menu.d.ts.map +1 -0
- package/dist/hooks/use-recent-menu.js +169 -0
- package/dist/hooks/use-scroll-restoration.d.ts +51 -0
- package/dist/hooks/use-scroll-restoration.d.ts.map +1 -0
- package/dist/hooks/use-scroll-restoration.js +143 -0
- package/dist/hooks/use-supabase-auth.d.ts +49 -0
- package/dist/hooks/use-supabase-auth.d.ts.map +1 -0
- package/dist/hooks/use-supabase-auth.js +229 -0
- package/dist/hooks/use-track-history.d.ts +2 -1
- package/dist/hooks/use-track-history.d.ts.map +1 -1
- package/dist/hooks/use-track-history.js +14 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/network/axios-factory.d.ts +30 -1
- package/dist/network/axios-factory.d.ts.map +1 -1
- package/dist/network/axios-factory.js +192 -24
- package/dist/network/index.d.ts +3 -1
- package/dist/network/index.d.ts.map +1 -1
- package/dist/network/index.js +5 -1
- package/dist/network/supabase-client.d.ts +28 -0
- package/dist/network/supabase-client.d.ts.map +1 -0
- package/dist/network/supabase-client.js +46 -0
- package/dist/store/app-store.d.ts +222 -12
- package/dist/store/app-store.d.ts.map +1 -1
- package/dist/store/app-store.js +46 -29
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +3 -0
- package/dist/store/menu-slice.d.ts +96 -0
- package/dist/store/menu-slice.d.ts.map +1 -0
- package/dist/store/menu-slice.js +98 -0
- package/dist/store/recent-menu-slice.d.ts +209 -0
- package/dist/store/recent-menu-slice.d.ts.map +1 -0
- package/dist/store/recent-menu-slice.js +110 -0
- package/dist/store/store-access.d.ts +1 -1
- package/dist/store/store-access.js +1 -1
- package/dist/types/index.d.ts +74 -17
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/service.d.ts +1 -1
- package/dist/types/service.js +1 -1
- package/dist/utils/classnames.d.ts +65 -0
- package/dist/utils/classnames.d.ts.map +1 -0
- package/dist/utils/classnames.js +98 -0
- package/dist/utils/formatter.d.ts +78 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +216 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/permission.d.ts +33 -0
- package/dist/utils/permission.d.ts.map +1 -0
- package/dist/utils/permission.js +132 -0
- package/dist/utils/query-string.d.ts +67 -0
- package/dist/utils/query-string.d.ts.map +1 -0
- package/dist/utils/query-string.js +136 -0
- package/dist/utils/storage.d.ts +1 -1
- package/dist/utils/storage.js +1 -1
- package/dist/utils/validation.d.ts +98 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +260 -0
- package/package.json +5 -3
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryString 유틸리티
|
|
3
|
+
* URL 쿼리 파라미터 파싱 및 생성
|
|
4
|
+
* KOMCA 패턴 업그레이드 버전 (lodash 의존성 제거)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* URL 쿼리 스트링을 객체로 파싱
|
|
8
|
+
* @example
|
|
9
|
+
* parseQueryString('?name=홍길동&age=30')
|
|
10
|
+
* // { name: '홍길동', age: '30' }
|
|
11
|
+
*/
|
|
12
|
+
export function parseQueryString(searchStr) {
|
|
13
|
+
const search = searchStr
|
|
14
|
+
? searchStr.replace(/^\?/, '')
|
|
15
|
+
: (typeof window !== 'undefined' ? window.location.search.replace(/^\?/, '') : '');
|
|
16
|
+
if (!search) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
return search.split('&').reduce((acc, token) => {
|
|
20
|
+
const [key, value] = token.split('=');
|
|
21
|
+
if (key) {
|
|
22
|
+
acc[decodeURIComponent(key)] = value !== undefined
|
|
23
|
+
? decodeURIComponent(value)
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
return acc;
|
|
27
|
+
}, {});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 객체를 쿼리 스트링으로 변환
|
|
31
|
+
* @example
|
|
32
|
+
* createQueryString({ name: '홍길동', age: 30 })
|
|
33
|
+
* // 'name=%ED%99%8D%EA%B8%B8%EB%8F%99&age=30'
|
|
34
|
+
*/
|
|
35
|
+
export function createQueryString(params) {
|
|
36
|
+
const entries = Object.entries(params)
|
|
37
|
+
.filter(([_, value]) => value !== undefined && value !== null && value !== '')
|
|
38
|
+
.map(([key, value]) => {
|
|
39
|
+
return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
|
|
40
|
+
});
|
|
41
|
+
return entries.join('&');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 현재 URL에 쿼리 파라미터 추가/업데이트
|
|
45
|
+
* @example
|
|
46
|
+
* updateQueryParams({ page: 2, sort: 'name' })
|
|
47
|
+
* // 현재 URL에 ?page=2&sort=name 추가
|
|
48
|
+
*/
|
|
49
|
+
export function updateQueryParams(params, options) {
|
|
50
|
+
if (typeof window === 'undefined')
|
|
51
|
+
return;
|
|
52
|
+
const currentParams = parseQueryString();
|
|
53
|
+
const newParams = { ...currentParams };
|
|
54
|
+
// 새 파라미터 추가/업데이트
|
|
55
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
56
|
+
if (value === undefined || value === null) {
|
|
57
|
+
delete newParams[key];
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
newParams[key] = String(value);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
const queryString = createQueryString(newParams);
|
|
64
|
+
const newUrl = `${window.location.pathname}${queryString ? `?${queryString}` : ''}`;
|
|
65
|
+
if (options?.replace) {
|
|
66
|
+
window.history.replaceState(null, '', newUrl);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
window.history.pushState(null, '', newUrl);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 특정 쿼리 파라미터 값 가져오기
|
|
74
|
+
* @example
|
|
75
|
+
* getQueryParam('page') // '1'
|
|
76
|
+
* getQueryParam('page', '1') // 기본값 '1'
|
|
77
|
+
*/
|
|
78
|
+
export function getQueryParam(key, defaultValue) {
|
|
79
|
+
const params = parseQueryString();
|
|
80
|
+
return params[key] ?? defaultValue;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 특정 쿼리 파라미터 삭제
|
|
84
|
+
* @example removeQueryParam('page')
|
|
85
|
+
*/
|
|
86
|
+
export function removeQueryParam(key, options) {
|
|
87
|
+
updateQueryParams({ [key]: undefined }, options);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 모든 쿼리 파라미터 삭제
|
|
91
|
+
*/
|
|
92
|
+
export function clearQueryParams(options) {
|
|
93
|
+
if (typeof window === 'undefined')
|
|
94
|
+
return;
|
|
95
|
+
const newUrl = window.location.pathname;
|
|
96
|
+
if (options?.replace) {
|
|
97
|
+
window.history.replaceState(null, '', newUrl);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
window.history.pushState(null, '', newUrl);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* URL에 쿼리 스트링 추가
|
|
105
|
+
* @example
|
|
106
|
+
* appendQueryString('/users', { page: 1, size: 10 })
|
|
107
|
+
* // '/users?page=1&size=10'
|
|
108
|
+
*/
|
|
109
|
+
export function appendQueryString(url, params) {
|
|
110
|
+
const queryString = createQueryString(params);
|
|
111
|
+
if (!queryString)
|
|
112
|
+
return url;
|
|
113
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
114
|
+
return `${url}${separator}${queryString}`;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 쿼리 파라미터를 숫자로 파싱
|
|
118
|
+
* @example getQueryParamAsNumber('page', 1) // 1
|
|
119
|
+
*/
|
|
120
|
+
export function getQueryParamAsNumber(key, defaultValue = 0) {
|
|
121
|
+
const value = getQueryParam(key);
|
|
122
|
+
if (value === undefined)
|
|
123
|
+
return defaultValue;
|
|
124
|
+
const parsed = parseInt(value, 10);
|
|
125
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 쿼리 파라미터를 boolean으로 파싱
|
|
129
|
+
* @example getQueryParamAsBoolean('active', false) // true
|
|
130
|
+
*/
|
|
131
|
+
export function getQueryParamAsBoolean(key, defaultValue = false) {
|
|
132
|
+
const value = getQueryParam(key);
|
|
133
|
+
if (value === undefined)
|
|
134
|
+
return defaultValue;
|
|
135
|
+
return value === 'true' || value === '1';
|
|
136
|
+
}
|
package/dist/utils/storage.d.ts
CHANGED
package/dist/utils/storage.js
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation 유틸리티
|
|
3
|
+
* 이메일, 비밀번호, 전화번호 등 검증 함수
|
|
4
|
+
* KOMCA 패턴 업그레이드 버전
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 이메일 형식 검증
|
|
8
|
+
* @example isValidEmail('test@example.com') // true
|
|
9
|
+
*/
|
|
10
|
+
export declare function isValidEmail(email: string): boolean;
|
|
11
|
+
export interface PasswordValidationResult {
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
errors: string[];
|
|
14
|
+
strength: 'weak' | 'medium' | 'strong';
|
|
15
|
+
}
|
|
16
|
+
export interface PasswordOptions {
|
|
17
|
+
minLength?: number;
|
|
18
|
+
maxLength?: number;
|
|
19
|
+
requireUppercase?: boolean;
|
|
20
|
+
requireLowercase?: boolean;
|
|
21
|
+
requireNumber?: boolean;
|
|
22
|
+
requireSpecialChar?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 비밀번호 검증 (상세 결과)
|
|
26
|
+
* @example
|
|
27
|
+
* const result = validatePassword('Test1234!');
|
|
28
|
+
* // { isValid: true, errors: [], strength: 'strong' }
|
|
29
|
+
*/
|
|
30
|
+
export declare function validatePassword(password: string, options?: PasswordOptions): PasswordValidationResult;
|
|
31
|
+
/**
|
|
32
|
+
* 비밀번호 형식 검증 (간단)
|
|
33
|
+
* @example isValidPassword('Test1234!') // true
|
|
34
|
+
*/
|
|
35
|
+
export declare function isValidPassword(password: string, options?: PasswordOptions): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* 한국 전화번호 검증
|
|
38
|
+
* @example isValidPhone('010-1234-5678') // true
|
|
39
|
+
* @example isValidPhone('01012345678') // true
|
|
40
|
+
*/
|
|
41
|
+
export declare function isValidPhone(phone: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* 휴대폰 번호만 검증
|
|
44
|
+
* @example isValidMobile('010-1234-5678') // true
|
|
45
|
+
*/
|
|
46
|
+
export declare function isValidMobile(phone: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* URL 형식 검증
|
|
49
|
+
* @example isValidUrl('https://example.com') // true
|
|
50
|
+
*/
|
|
51
|
+
export declare function isValidUrl(url: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* IPv4 주소 검증
|
|
54
|
+
* @example isValidIPv4('192.168.0.1') // true
|
|
55
|
+
*/
|
|
56
|
+
export declare function isValidIPv4(ip: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* 사업자등록번호 검증 (한국)
|
|
59
|
+
* @example isValidBusinessNumber('123-45-67890') // true
|
|
60
|
+
*/
|
|
61
|
+
export declare function isValidBusinessNumber(number: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 주민등록번호 형식 검증 (실제 유효성은 검증하지 않음)
|
|
64
|
+
* @example isValidResidentNumber('900101-1234567') // 형식만 검증
|
|
65
|
+
*/
|
|
66
|
+
export declare function isValidResidentNumberFormat(number: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* 빈 값 검증
|
|
69
|
+
* @example isEmpty('') // true
|
|
70
|
+
* @example isEmpty(null) // true
|
|
71
|
+
* @example isEmpty([]) // true
|
|
72
|
+
*/
|
|
73
|
+
export declare function isEmpty(value: any): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* 빈 값이 아닌지 검증
|
|
76
|
+
*/
|
|
77
|
+
export declare function isNotEmpty(value: any): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* 숫자만 포함하는지 검증
|
|
80
|
+
* @example isNumericOnly('12345') // true
|
|
81
|
+
*/
|
|
82
|
+
export declare function isNumericOnly(value: string): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* 한글만 포함하는지 검증
|
|
85
|
+
* @example isKoreanOnly('홍길동') // true
|
|
86
|
+
*/
|
|
87
|
+
export declare function isKoreanOnly(value: string): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* 영문만 포함하는지 검증
|
|
90
|
+
* @example isAlphaOnly('abc') // true
|
|
91
|
+
*/
|
|
92
|
+
export declare function isAlphaOnly(value: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* 영문+숫자만 포함하는지 검증
|
|
95
|
+
* @example isAlphanumeric('abc123') // true
|
|
96
|
+
*/
|
|
97
|
+
export declare function isAlphanumeric(value: string): boolean;
|
|
98
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAInD;AAMD,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAWD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,wBAAwB,CAsD1B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAEpF;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAcnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIpD;AAMD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ/C;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAI/C;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB7D;AAMD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAUnE;AAMD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAM3C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAE9C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGpD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGrD"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation 유틸리티
|
|
3
|
+
* 이메일, 비밀번호, 전화번호 등 검증 함수
|
|
4
|
+
* KOMCA 패턴 업그레이드 버전
|
|
5
|
+
*/
|
|
6
|
+
// ============================================
|
|
7
|
+
// 이메일 검증
|
|
8
|
+
// ============================================
|
|
9
|
+
/**
|
|
10
|
+
* 이메일 형식 검증
|
|
11
|
+
* @example isValidEmail('test@example.com') // true
|
|
12
|
+
*/
|
|
13
|
+
export function isValidEmail(email) {
|
|
14
|
+
if (!email || typeof email !== 'string')
|
|
15
|
+
return false;
|
|
16
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
17
|
+
return emailRegex.test(email.trim());
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_PASSWORD_OPTIONS = {
|
|
20
|
+
minLength: 8,
|
|
21
|
+
maxLength: 50,
|
|
22
|
+
requireUppercase: true,
|
|
23
|
+
requireLowercase: true,
|
|
24
|
+
requireNumber: true,
|
|
25
|
+
requireSpecialChar: true,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* 비밀번호 검증 (상세 결과)
|
|
29
|
+
* @example
|
|
30
|
+
* const result = validatePassword('Test1234!');
|
|
31
|
+
* // { isValid: true, errors: [], strength: 'strong' }
|
|
32
|
+
*/
|
|
33
|
+
export function validatePassword(password, options = {}) {
|
|
34
|
+
const opts = { ...DEFAULT_PASSWORD_OPTIONS, ...options };
|
|
35
|
+
const errors = [];
|
|
36
|
+
if (!password || typeof password !== 'string') {
|
|
37
|
+
return { isValid: false, errors: ['비밀번호를 입력해주세요.'], strength: 'weak' };
|
|
38
|
+
}
|
|
39
|
+
// 길이 검증
|
|
40
|
+
if (opts.minLength && password.length < opts.minLength) {
|
|
41
|
+
errors.push(`비밀번호는 ${opts.minLength}자 이상이어야 합니다.`);
|
|
42
|
+
}
|
|
43
|
+
if (opts.maxLength && password.length > opts.maxLength) {
|
|
44
|
+
errors.push(`비밀번호는 ${opts.maxLength}자 이하여야 합니다.`);
|
|
45
|
+
}
|
|
46
|
+
// 대문자 검증
|
|
47
|
+
if (opts.requireUppercase && !/[A-Z]/.test(password)) {
|
|
48
|
+
errors.push('대문자를 포함해야 합니다.');
|
|
49
|
+
}
|
|
50
|
+
// 소문자 검증
|
|
51
|
+
if (opts.requireLowercase && !/[a-z]/.test(password)) {
|
|
52
|
+
errors.push('소문자를 포함해야 합니다.');
|
|
53
|
+
}
|
|
54
|
+
// 숫자 검증
|
|
55
|
+
if (opts.requireNumber && !/[0-9]/.test(password)) {
|
|
56
|
+
errors.push('숫자를 포함해야 합니다.');
|
|
57
|
+
}
|
|
58
|
+
// 특수문자 검증
|
|
59
|
+
if (opts.requireSpecialChar && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
|
|
60
|
+
errors.push('특수문자를 포함해야 합니다.');
|
|
61
|
+
}
|
|
62
|
+
// 강도 계산
|
|
63
|
+
let strength = 'weak';
|
|
64
|
+
let score = 0;
|
|
65
|
+
if (password.length >= 8)
|
|
66
|
+
score++;
|
|
67
|
+
if (password.length >= 12)
|
|
68
|
+
score++;
|
|
69
|
+
if (/[A-Z]/.test(password))
|
|
70
|
+
score++;
|
|
71
|
+
if (/[a-z]/.test(password))
|
|
72
|
+
score++;
|
|
73
|
+
if (/[0-9]/.test(password))
|
|
74
|
+
score++;
|
|
75
|
+
if (/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password))
|
|
76
|
+
score++;
|
|
77
|
+
if (score >= 5)
|
|
78
|
+
strength = 'strong';
|
|
79
|
+
else if (score >= 3)
|
|
80
|
+
strength = 'medium';
|
|
81
|
+
return {
|
|
82
|
+
isValid: errors.length === 0,
|
|
83
|
+
errors,
|
|
84
|
+
strength,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 비밀번호 형식 검증 (간단)
|
|
89
|
+
* @example isValidPassword('Test1234!') // true
|
|
90
|
+
*/
|
|
91
|
+
export function isValidPassword(password, options) {
|
|
92
|
+
return validatePassword(password, options).isValid;
|
|
93
|
+
}
|
|
94
|
+
// ============================================
|
|
95
|
+
// 전화번호 검증
|
|
96
|
+
// ============================================
|
|
97
|
+
/**
|
|
98
|
+
* 한국 전화번호 검증
|
|
99
|
+
* @example isValidPhone('010-1234-5678') // true
|
|
100
|
+
* @example isValidPhone('01012345678') // true
|
|
101
|
+
*/
|
|
102
|
+
export function isValidPhone(phone) {
|
|
103
|
+
if (!phone || typeof phone !== 'string')
|
|
104
|
+
return false;
|
|
105
|
+
const cleaned = phone.replace(/[^0-9]/g, '');
|
|
106
|
+
// 휴대폰 (010, 011, 016, 017, 018, 019)
|
|
107
|
+
if (/^01[0-9]\d{7,8}$/.test(cleaned))
|
|
108
|
+
return true;
|
|
109
|
+
// 서울 지역번호
|
|
110
|
+
if (/^02\d{7,8}$/.test(cleaned))
|
|
111
|
+
return true;
|
|
112
|
+
// 기타 지역번호
|
|
113
|
+
if (/^0[3-6][0-9]\d{7,8}$/.test(cleaned))
|
|
114
|
+
return true;
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 휴대폰 번호만 검증
|
|
119
|
+
* @example isValidMobile('010-1234-5678') // true
|
|
120
|
+
*/
|
|
121
|
+
export function isValidMobile(phone) {
|
|
122
|
+
if (!phone || typeof phone !== 'string')
|
|
123
|
+
return false;
|
|
124
|
+
const cleaned = phone.replace(/[^0-9]/g, '');
|
|
125
|
+
return /^01[016789]\d{7,8}$/.test(cleaned);
|
|
126
|
+
}
|
|
127
|
+
// ============================================
|
|
128
|
+
// URL 검증
|
|
129
|
+
// ============================================
|
|
130
|
+
/**
|
|
131
|
+
* URL 형식 검증
|
|
132
|
+
* @example isValidUrl('https://example.com') // true
|
|
133
|
+
*/
|
|
134
|
+
export function isValidUrl(url) {
|
|
135
|
+
if (!url || typeof url !== 'string')
|
|
136
|
+
return false;
|
|
137
|
+
try {
|
|
138
|
+
new URL(url);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// ============================================
|
|
146
|
+
// IP 주소 검증
|
|
147
|
+
// ============================================
|
|
148
|
+
/**
|
|
149
|
+
* IPv4 주소 검증
|
|
150
|
+
* @example isValidIPv4('192.168.0.1') // true
|
|
151
|
+
*/
|
|
152
|
+
export function isValidIPv4(ip) {
|
|
153
|
+
if (!ip || typeof ip !== 'string')
|
|
154
|
+
return false;
|
|
155
|
+
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
156
|
+
return ipv4Regex.test(ip);
|
|
157
|
+
}
|
|
158
|
+
// ============================================
|
|
159
|
+
// 사업자등록번호 검증
|
|
160
|
+
// ============================================
|
|
161
|
+
/**
|
|
162
|
+
* 사업자등록번호 검증 (한국)
|
|
163
|
+
* @example isValidBusinessNumber('123-45-67890') // true
|
|
164
|
+
*/
|
|
165
|
+
export function isValidBusinessNumber(number) {
|
|
166
|
+
if (!number || typeof number !== 'string')
|
|
167
|
+
return false;
|
|
168
|
+
const cleaned = number.replace(/[^0-9]/g, '');
|
|
169
|
+
if (cleaned.length !== 10)
|
|
170
|
+
return false;
|
|
171
|
+
const checkSum = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
172
|
+
let sum = 0;
|
|
173
|
+
for (let i = 0; i < 9; i++) {
|
|
174
|
+
sum += parseInt(cleaned[i]) * checkSum[i];
|
|
175
|
+
}
|
|
176
|
+
sum += Math.floor((parseInt(cleaned[8]) * 5) / 10);
|
|
177
|
+
const remainder = (10 - (sum % 10)) % 10;
|
|
178
|
+
return remainder === parseInt(cleaned[9]);
|
|
179
|
+
}
|
|
180
|
+
// ============================================
|
|
181
|
+
// 주민등록번호 검증
|
|
182
|
+
// ============================================
|
|
183
|
+
/**
|
|
184
|
+
* 주민등록번호 형식 검증 (실제 유효성은 검증하지 않음)
|
|
185
|
+
* @example isValidResidentNumber('900101-1234567') // 형식만 검증
|
|
186
|
+
*/
|
|
187
|
+
export function isValidResidentNumberFormat(number) {
|
|
188
|
+
if (!number || typeof number !== 'string')
|
|
189
|
+
return false;
|
|
190
|
+
const cleaned = number.replace(/[^0-9]/g, '');
|
|
191
|
+
if (cleaned.length !== 13)
|
|
192
|
+
return false;
|
|
193
|
+
// 성별 코드 검증 (1~4)
|
|
194
|
+
const genderCode = parseInt(cleaned[6]);
|
|
195
|
+
if (genderCode < 1 || genderCode > 4)
|
|
196
|
+
return false;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
// ============================================
|
|
200
|
+
// 공통 유틸리티
|
|
201
|
+
// ============================================
|
|
202
|
+
/**
|
|
203
|
+
* 빈 값 검증
|
|
204
|
+
* @example isEmpty('') // true
|
|
205
|
+
* @example isEmpty(null) // true
|
|
206
|
+
* @example isEmpty([]) // true
|
|
207
|
+
*/
|
|
208
|
+
export function isEmpty(value) {
|
|
209
|
+
if (value === null || value === undefined)
|
|
210
|
+
return true;
|
|
211
|
+
if (typeof value === 'string')
|
|
212
|
+
return value.trim() === '';
|
|
213
|
+
if (Array.isArray(value))
|
|
214
|
+
return value.length === 0;
|
|
215
|
+
if (typeof value === 'object')
|
|
216
|
+
return Object.keys(value).length === 0;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* 빈 값이 아닌지 검증
|
|
221
|
+
*/
|
|
222
|
+
export function isNotEmpty(value) {
|
|
223
|
+
return !isEmpty(value);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 숫자만 포함하는지 검증
|
|
227
|
+
* @example isNumericOnly('12345') // true
|
|
228
|
+
*/
|
|
229
|
+
export function isNumericOnly(value) {
|
|
230
|
+
if (!value || typeof value !== 'string')
|
|
231
|
+
return false;
|
|
232
|
+
return /^\d+$/.test(value);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* 한글만 포함하는지 검증
|
|
236
|
+
* @example isKoreanOnly('홍길동') // true
|
|
237
|
+
*/
|
|
238
|
+
export function isKoreanOnly(value) {
|
|
239
|
+
if (!value || typeof value !== 'string')
|
|
240
|
+
return false;
|
|
241
|
+
return /^[가-힣]+$/.test(value);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 영문만 포함하는지 검증
|
|
245
|
+
* @example isAlphaOnly('abc') // true
|
|
246
|
+
*/
|
|
247
|
+
export function isAlphaOnly(value) {
|
|
248
|
+
if (!value || typeof value !== 'string')
|
|
249
|
+
return false;
|
|
250
|
+
return /^[a-zA-Z]+$/.test(value);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 영문+숫자만 포함하는지 검증
|
|
254
|
+
* @example isAlphanumeric('abc123') // true
|
|
255
|
+
*/
|
|
256
|
+
export function isAlphanumeric(value) {
|
|
257
|
+
if (!value || typeof value !== 'string')
|
|
258
|
+
return false;
|
|
259
|
+
return /^[a-zA-Z0-9]+$/.test(value);
|
|
260
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sonhoseong/mfa-lib",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "MFA 공통 라이브러리
|
|
3
|
+
"version": "1.3.10",
|
|
4
|
+
"description": "MFA 공통 라이브러리",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -9,16 +9,18 @@
|
|
|
9
9
|
"README.md"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "tsc",
|
|
12
|
+
"build": "tsc && copyfiles -u 1 \"src/**/*.css\" dist",
|
|
13
13
|
"watch": "tsc --watch"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@reduxjs/toolkit": "^2.11.2",
|
|
17
|
+
"@supabase/supabase-js": "^2.93.3",
|
|
17
18
|
"axios": "^1.7.9",
|
|
18
19
|
"uuid": "^11.1.0"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@types/uuid": "^10.0.0",
|
|
23
|
+
"copyfiles": "^2.4.1",
|
|
22
24
|
"typescript": "^5.0.0"
|
|
23
25
|
},
|
|
24
26
|
"peerDependencies": {
|