첫 커밋

This commit is contained in:
focp212@naver.com
2025-09-05 15:36:48 +09:00
commit 05238b04c1
825 changed files with 176358 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
export * from './use-navigate';
export * from './use-app-version';
export * from './use-change-bg-color';
export * from './use-device-uid';
export * from './use-fix-scroll-view-safari';
export * from './use-has-bio-hardware';
export * from './use-navigate';
export * from './use-location-permission';
export * from './use-window-focus-change';
export * from './use-router-listener';
export * from './use-scroll-to-top';
export * from './use-app-page-speed';

View File

@@ -0,0 +1,15 @@
import { useEffectOnce } from 'react-use';
import useLocalStorageState from 'use-local-storage-state';
import { StorageKeys } from '@/shared/constants/local-storage';
import { setAfterPageRendered } from '@/shared/lib';
export const useAppPagingSpeed = () => {
return useLocalStorageState(StorageKeys.AppPagingSpeed, { defaultValue: '250' });
};
export const useEffectOnceAfterPageRendered = (callback: () => void) => {
useEffectOnce(() => {
setAfterPageRendered(callback);
});
};

View File

@@ -0,0 +1,17 @@
import { useMemo } from 'react';
import useLocalStorageState from 'use-local-storage-state';
import { config } from '@/shared/configs';
import { StorageKeys } from '@/shared/constants/local-storage';
export const DEFAULT_APP_VERSION = '1.0';
export const useAppVersion = () => {
const [appVersion] = useLocalStorageState(StorageKeys.AppVersion, { defaultValue: DEFAULT_APP_VERSION });
return appVersion;
};
export const webVersion = config.WEB_VERSION.replace(/\b(\d+)(?:\.0\.0)?\b/g, '$1');
export const useFullVersion = () => {
const appVersion = useAppVersion();
const fullVersion = useMemo(() => `${appVersion}.${webVersion}`, [appVersion]);
return { fullVersion };
};

View File

@@ -0,0 +1,10 @@
import { useBlocker } from '@use-blocker';
export const useBlockBack = (blockPaths: string[]) => {
useBlocker(({ nextLocation }) => {
if (blockPaths.includes(nextLocation.pathname)) {
return true;
}
return false;
});
};

View File

@@ -0,0 +1,2 @@
import { useBlocker as _useBlocker } from 'react-router';
export const useBlocker = _useBlocker;

View File

@@ -0,0 +1,44 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useLayoutEffect } from 'react';
import useLocalStorageState from 'use-local-storage-state';
import { DEFAULT_BACKGROUND_COLOR } from '@/shared/constants/colors';
import { StorageKeys } from '@/shared/constants/local-storage';
import { NativeFunction } from '@/shared/constants/native-function';
import { useRouterListener } from './use-router-listener';
export const useChangeBgColor = (color: string) => {
const [, setAppColor] = useAppColor();
useLayoutEffect(() => {
setAppColor(color);
window.webViewBridge.send(NativeFunction.SetAppColor, color);
document.body.style.backgroundColor = color;
}, []);
};
export const useResetBgColor = () => {
const [, setAppColor] = useAppColor();
const defaultColor = DEFAULT_BACKGROUND_COLOR;
useLayoutEffect(() => {
setAppColor(defaultColor);
window.webViewBridge.send(NativeFunction.SetAppColor, defaultColor);
document.body.style.backgroundColor = defaultColor;
}, []);
};
export const useResetBgColorOnLeave = () => {
const [, setAppColor] = useAppColor();
const defaultColor = DEFAULT_BACKGROUND_COLOR;
useRouterListener(() => {
setAppColor(defaultColor);
document.body.style.backgroundColor = defaultColor;
window.webViewBridge.send(NativeFunction.SetAppColor, defaultColor);
});
};
export const useAppColor = () => {
return useLocalStorageState(StorageKeys.AppColor, { defaultValue: DEFAULT_BACKGROUND_COLOR });
};

View File

@@ -0,0 +1,31 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @cspell/spellchecker */
import { useEffect } from 'react';
import useLocalStorageState from 'use-local-storage-state';
// import { useUpdateUserInfo } from '@/entities/user/lib/use-update-user-info';
import { StorageKeys } from '@/shared/constants/local-storage';
import { useStore } from '@/shared/model/store';
export const useDeviceUid = () => {
let defaultUid = '';
if (!window.ReactNativeWebView) {
defaultUid = '1234567890';
}
const userInfo = useStore((state: any) => state.userSlice.userInfo);
const [deviceUid] = useLocalStorageState(StorageKeys.DeviceUniqueId, { defaultValue: defaultUid });
//const { updateUserInfo } = useUpdateUserInfo();
useEffect(() => {
if (deviceUid.length < 1) {
return;
}
if (userInfo?.appEsntlNo && userInfo?.appEsntlNo?.length > 0) {
return;
}
// updateUserInfo((prev: any) => ({ ...prev, appEsntlNo: deviceUid }));
}, [deviceUid]);
return deviceUid;
};

View File

@@ -0,0 +1,12 @@
import { useEffect } from 'react';
export const useFixScrollViewSafari = () => {
useEffect(() => {
const timeout = setTimeout(() => {
window.scrollTo(0, 1);
}, 100);
return () => {
clearTimeout(timeout);
};
}, []);
};

View File

@@ -0,0 +1,45 @@
import { CSSProperties, useEffect, useState } from 'react';
export const useFixedButtonPosition = () => {
const [buttonStyle, setButtonStyle] = useState<CSSProperties | undefined>();
const updateButtonPosition = () => {
const viewport = window.visualViewport;
const viewportHeight = viewport?.height;
const viewportOffsetTop = viewport?.offsetTop ?? 0;
if (viewportHeight && viewportHeight < window.innerHeight) {
setButtonStyle({ bottom: `${window.innerHeight - viewportHeight - viewportOffsetTop + 15}px` });
} else {
setButtonStyle(undefined);
}
};
useEffect(() => {
const handleResize = () => {
updateButtonPosition();
};
visualViewport?.addEventListener('resize', handleResize);
window.addEventListener('resize', handleResize); // fallback for window resize
return () => {
visualViewport?.removeEventListener('resize', handleResize);
window.removeEventListener('resize', handleResize);
};
}, []);
useEffect(() => {
const handleScroll = () => {
updateButtonPosition();
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return buttonStyle;
};

View File

@@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect } from 'react';
import { NativeMessage } from '@/shared/constants/native-message';
import { bridge } from '@/bridge';
export const useHardwareBackPressListener = (callback: () => void) => {
useEffect(() => {
// Subscribe to events from react native.
return bridge.addEventListener(NativeMessage.HardwareBackPress, (message: any) => {
callback?.();
});
}, []);
};

View File

@@ -0,0 +1,7 @@
import useLocalStorageState from 'use-local-storage-state';
import { StorageKeys } from '@/shared/constants/local-storage';
export const useHasBioHardware = () => {
const [hasBioHardware] = useLocalStorageState(StorageKeys.HasBioHardware, { defaultValue: false });
return hasBioHardware;
};

View File

@@ -0,0 +1,46 @@
/* eslint-disable @cspell/spellchecker */
import { useEffect } from 'react';
import { PERMISSION_RESULTS, PermissionResultValues, WebViewBridgeResponse } from '@/shared/@types/webview-bridge';
import { NativeFunction } from '@/shared/constants/native-function';
interface CheckLocationPermissionResponse extends WebViewBridgeResponse {
data: PermissionResultValues;
}
export const useRequestLocationPermission = () => {
useEffect(() => {
window.webViewBridge.send(NativeFunction.LocationPermission, {
success: async () => {
// console.log('res', res);
},
});
}, []);
};
export const useCheckLocationPermission = () => {
useEffect(() => {
window.webViewBridge.send(NativeFunction.CheckLocationPermission, {
success: async (res: CheckLocationPermissionResponse) => {
// console.log('res', res.data);
switch (res.data) {
case PERMISSION_RESULTS.UNAVAILABLE:
console.log('This feature is not available (on this device / in this context)');
break;
case PERMISSION_RESULTS.DENIED:
console.log('The permission has not been requested / is denied but requestable');
break;
case PERMISSION_RESULTS.LIMITED:
console.log('The permission is limited: some actions are possible');
break;
case PERMISSION_RESULTS.GRANTED:
console.log('The permission is granted');
break;
case PERMISSION_RESULTS.BLOCKED:
console.log('The permission is denied and not requestable anymore');
break;
}
},
});
}, []);
};

View File

@@ -0,0 +1,37 @@
import { PathType } from '@/shared/constants/paths';
import {
NavigateOptions,
To,
useNavigate as _useNavigate
} from 'react-router';
export type NavigateTo = PathType | -1 | 0;
export const goBackWebview = (goBack: () => void) => {
if (!window.ReactNativeWebView) {
if (window.history.state?.idx > 0) {
goBack();
return;
} else {
window.close();
}
return;
}
};
export const useNavigate = () => {
const _navigate = _useNavigate();
const navigate = (to: NavigateTo, options?: NavigateOptions): void => {
const path = typeof to === 'number' ? to : to.toString();
_navigate(path as To, options);
};
const reload = async () => {
navigate(0);
};
const navigateBack = () => goBackWebview(() => _navigate(-1));
return { navigate, navigateBack, reload };
};

View File

@@ -0,0 +1,13 @@
import { useEffect } from 'react';
import { NativeFunction } from '@/shared/constants/native-function';
export const useRequestNotification = () => {
useEffect(() => {
window.webViewBridge.send(NativeFunction.RequestNotification, {
success: async () => {
// console.log('res', res);
},
});
}, []);
};

View File

@@ -0,0 +1,20 @@
import { RouterState } from '@remix-run/router';
import { useEffect } from 'react';
import { NavigationType } from 'react-router';
import { router } from '@/shared/configs/sentry';
type IRouterListener = (state: RouterState) => void;
export const useRouterListener = (callback: IRouterListener, actionType?: NavigationType) => {
useEffect(() => {
const unsubscribe = router.subscribe((state: any) => {
if (actionType && state.historyAction !== actionType) return;
if (!actionType || state.historyAction === actionType) {
callback?.(state);
}
});
return unsubscribe;
}, [callback, actionType]);
};

View File

@@ -0,0 +1,37 @@
import { useEffect, useState } from 'react';
export function useScript(src: string) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let script = document.querySelector(`script[src="${src}"]`);
const handleLoad = () => setLoading(false);
const handleError = (err: any) => {
setError(err);
setLoading(false);
};
if(!script){
script = document.createElement('script');
(script as any).src = src;
(script as any).type = 'text/javascript';
(script as any).charset = 'utf-8';
script.addEventListener('load', handleLoad);
script.addEventListener('error', handleError);
console.log(script);
// document.getElementsByTagName('head')[0].appendChild(script);
}
else{
setLoading(false);
}
return () => {
script.removeEventListener('load', handleLoad);
script.removeEventListener('error', handleError);
}
}, [src]);
return [loading, error];
}

View File

@@ -0,0 +1,7 @@
import { useEffect } from 'react';
export const useScrollToTop = () => {
useEffect(() => {
document.body.scrollTop = 0;
}, []);
};

View File

@@ -0,0 +1,50 @@
import { overlay } from 'overlay-kit';
import { BottomSheet } from '@/shared/ui/bottom-sheets/bottom-sheet';
import {
SelectTemplate,
ElementType,
RadioChangeProps,
SelectTemplateProps,
} from '@/shared/ui/selects/select-template';
import { JSX } from 'react/jsx-runtime';
export interface SelectBottomSheetProps<T> extends SelectTemplateProps<T> {
title?: string | JSX.Element;
description?: string | JSX.Element;
onClose?: () => void;
}
export const useSelectBottomSheet = <T,>() => {
const openSelectBottomSheet = (selectBottomSheet: SelectBottomSheetProps<T>) => {
const {
values = [],
labels = [],
valueKey = undefined,
selectedLabel = '',
selectedValue = '',
title = '',
description = '',
} = selectBottomSheet;
const handleRadioChange = ({ event, label, value }: RadioChangeProps<ElementType<typeof values>>) => {
selectBottomSheet?.onRadioChange?.({ event, label, value });
};
overlay.open(({ isOpen, close, unmount }) => {
return (
<BottomSheet title={title} description={description} afterLeave={unmount} open={isOpen} onClose={close}>
<SelectTemplate
values={values}
valueKey={valueKey}
labels={labels}
selectedLabel={selectedLabel}
selectedValue={selectedValue}
onRadioChange={handleRadioChange}
/>
</BottomSheet>
);
});
};
return { openSelectBottomSheet, closeBottomSheet: overlay.closeAll };
};

View File

@@ -0,0 +1,28 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect } from 'react';
interface WindowFocusProps {
onFocus?: () => void;
onBlur?: () => void;
args?: any[];
}
export const useWindowFocusChange = ({ onFocus, onBlur, ...args }: WindowFocusProps) => {
const dependencies: any[] = [];
if (Array.isArray(args)) {
dependencies.push(...args);
}
const handleEvent = () => {
if (document.hidden) {
// the page is hidden
onBlur?.();
} else {
// the page is visible
onFocus?.();
}
};
useEffect(() => {
window.document.addEventListener('visibilitychange', handleEvent);
return () => window.document.removeEventListener('visibilitychange', handleEvent);
}, [onFocus, onBlur, ...dependencies]);
};