diff --git a/src/entities/additional-service/ui/fund-account/detail/transfer-detail.tsx b/src/entities/additional-service/ui/fund-account/detail/transfer-detail.tsx index 899a54e..e7b2dda 100644 --- a/src/entities/additional-service/ui/fund-account/detail/transfer-detail.tsx +++ b/src/entities/additional-service/ui/fund-account/detail/transfer-detail.tsx @@ -13,6 +13,8 @@ import { DetailMotionDuration, DetailMotionStyle, DetailMotionVariants } from '@ import { FullMenuClose } from '@/entities/common/ui/full-menu-close'; import { showAlert } from '@/widgets/show-alert'; import { checkGrant } from '@/shared/lib/check-grant'; +import { overlay } from 'overlay-kit'; +import { Dialog } from '@/shared/ui/dialogs/dialog'; export interface FundAccountTransferDetailProps { detailOn: boolean; @@ -54,6 +56,27 @@ export const FundAccountTransferDetail = ({ showAlert(t('common.nopermission')); return; } + let msg = t('additionalService.fundAccount.transferRequestMsg'); + + overlay.open(({ + isOpen, + close, + unmount + }) => { + return ( + transferReqeust()} + message={msg} + buttonLabel={[t('common.cancel'), t('common.confirm')]} + /> + ); + }); + } + + const transferReqeust = () => { let params: ExtensionFundAccountTransferRequestParams = { seq: seq }; diff --git a/src/entities/additional-service/ui/link-payment/apply/link-payment-step1.tsx b/src/entities/additional-service/ui/link-payment/apply/link-payment-step1.tsx index 40bcc36..0974aba 100644 --- a/src/entities/additional-service/ui/link-payment/apply/link-payment-step1.tsx +++ b/src/entities/additional-service/ui/link-payment/apply/link-payment-step1.tsx @@ -7,6 +7,7 @@ import { LinkPaymentFormData, LinkPaymentSendMethod } from '@/entities/additiona import { NumericFormat } from 'react-number-format'; import { useStore } from '@/shared/model/store'; import { useEffect } from 'react'; +import { useKeyboardAware } from '@/shared/lib/hooks/use-keyboard-aware'; interface LinkPaymentStep1Props { formData: LinkPaymentFormData; @@ -16,6 +17,7 @@ interface LinkPaymentStep1Props { export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Props) => { const { t, i18n } = useTranslation(); const { navigate } = useNavigate(); + const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); const midOptionsWithoutGids = useStore.getState().UserStore.selectOptionsMidsWithoutGids; const isEnglish = i18n.language === 'en'; @@ -102,6 +104,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop placeholder="" value={formData.goodsName} onChange={(e) => handleInputChange('goodsName', e.target.value)} + onFocus={handleInputFocus} /> @@ -114,6 +117,7 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop allowNegative={false} displayType="input" thousandSeparator={true} + onFocus={handleInputFocus} onValueChange={(values) => { const { floatValue } = values; setFormData({ ...formData, amount: floatValue ?? 0 }); @@ -130,11 +134,12 @@ export const LinkPaymentStep1 = ({ formData, setFormData }: LinkPaymentStep1Prop placeholder="" value={formData.moid} onChange={(e) => handleInputChange('moid', e.target.value)} + onFocus={handleInputFocus} /> -
+
{t('additionalService.linkPayment.paymentValidDate')}
diff --git a/src/locales/en.json b/src/locales/en.json index 729c892..7c53c31 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1060,6 +1060,7 @@ "transferResult": "Transfer Result", "beneficiaryName": "Beneficiary Name", "transferRequestButton": "Transfer Request", + "transferRequestMsg": "Do you want to request the transfer?", "transferRequestSuccess": "Transfer request successful.", "transferRequestFailed": "Transfer request failed.", "transferRequestError": "An error occurred during transfer request.", diff --git a/src/locales/ko.json b/src/locales/ko.json index e04dbc5..5a186cc 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -1060,6 +1060,7 @@ "transferResult": "이체결과", "beneficiaryName": "수취인명", "transferRequestButton": "이체 요청", + "transferRequestMsg": "이체요청을 하시겠습니까?", "transferRequestSuccess": "이체요청을 성공하였습니다.", "transferRequestFailed": "이체요청이 실패하였습니다.", "transferRequestError": "이체요청 중 오류가 발생했습니다.", diff --git a/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx b/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx index 982c2ce..cda9865 100644 --- a/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx +++ b/src/pages/additional-service/account-holder-search/account-holder-search-page.tsx @@ -160,13 +160,13 @@ export const AccountHolderSearchPage = () => { bankCode: bank, resultStatus: resultStatus }).then((rs) => { - if (rs.status) { - setTimeout(() => { - snackBar("다운로드가 완료되었습니다."); - }, 2000); - } else { - snackBar("다운로드에 실패했습니다.") - } + // if (rs.status) { + // setTimeout(() => { + // snackBar("다운로드가 완료되었습니다."); + // }, 2000); + // } else { + // snackBar("다운로드에 실패했습니다.") + // } }); } setEmailBottomSheetOn(false); diff --git a/src/pages/additional-service/ars/request-page.tsx b/src/pages/additional-service/ars/request-page.tsx index 01ee2c9..1c9a6ea 100644 --- a/src/pages/additional-service/ars/request-page.tsx +++ b/src/pages/additional-service/ars/request-page.tsx @@ -18,6 +18,7 @@ import { useStore } from '@/shared/model/store'; import { snackBar } from '@/shared/lib'; import { NumericFormat, PatternFormat } from 'react-number-format'; import { showAlert } from '@/widgets/show-alert'; +import { useKeyboardAware } from '@/shared/lib/hooks/use-keyboard-aware'; export const ArsRequestPage = () => { const { t } = useTranslation(); @@ -42,6 +43,8 @@ export const ArsRequestPage = () => { const [successPageOn, setSuccessPageOn] = useState(false); const [resultMessage, setResultMessage] = useState(''); + const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); + useSetHeaderTitle(t('additionalService.ars.paymentRequest')); useSetHeaderType(HeaderType.LeftArrow); useSetFooterMode(false); @@ -160,6 +163,7 @@ export const ArsRequestPage = () => { type="text" value={moid} onChange={(e: ChangeEvent) => setMoid(e.target.value)} + onFocus={handleInputFocus} />
@@ -171,6 +175,7 @@ export const ArsRequestPage = () => { type="text" value={goodsName} onChange={(e: ChangeEvent) => setGoodsName(e.target.value)} + onFocus={handleInputFocus} />
@@ -187,6 +192,7 @@ export const ArsRequestPage = () => { const { floatValue } = values; setAmount(floatValue ?? 0); }} + onFocus={handleInputFocus} > @@ -211,6 +217,7 @@ export const ArsRequestPage = () => { type="text" value={buyerName} onChange={(e: ChangeEvent) => setBuyerName(e.target.value)} + onFocus={handleInputFocus} /> @@ -230,6 +237,7 @@ export const ArsRequestPage = () => { inputMode="numeric" pattern="[0-9]*" maxLength={11} + onFocus={handleInputFocus} /> @@ -243,12 +251,12 @@ export const ArsRequestPage = () => { placeholder='test@nicepay.co.kr' onChange={(e: ChangeEvent) => setEamil(e.target.value)} className={email && !isValidEmail(email) ? 'error' : ''} - + onFocus={handleInputFocus} /> -
+
{t('additionalService.ars.paymentMethod')} *
{getArsPaymentMethodBtns()} diff --git a/src/pages/additional-service/fund-account/transfer-request-page.tsx b/src/pages/additional-service/fund-account/transfer-request-page.tsx index 86594c1..32361ac 100644 --- a/src/pages/additional-service/fund-account/transfer-request-page.tsx +++ b/src/pages/additional-service/fund-account/transfer-request-page.tsx @@ -14,8 +14,8 @@ import { useStore } from '@/shared/model/store'; import { snackBar } from '@/shared/lib'; import { useExtensionFundAccountTransferRegistMutation } from '@/entities/additional-service/api/fund-account/use-extension-fund-account-transfer-regist-mutation'; import { NumericFormat } from 'react-number-format'; -import { useExtensionFundAccountTransferRequestMutation } from '@/entities/additional-service/api/fund-account/use-extension-fund-account-transfer-request-mutation'; import { showAlert } from '@/widgets/show-alert'; +import { useKeyboardAware } from '@/shared/lib/hooks/use-keyboard-aware'; export const FundAccountTransferRequestPage = () => { @@ -32,6 +32,7 @@ export const FundAccountTransferRequestPage = () => { const [amount, setAmount] = useState(0); const [moid, setMoid] = useState(''); const [depositParameter, setDepositParameter] = useState(''); + const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); const { mutateAsync: extensionFundAccountRegist } = useExtensionFundAccountTransferRegistMutation(); @@ -159,6 +160,7 @@ export const FundAccountTransferRequestPage = () => { return !value || value.length <= 14; }} onValueChange={(values) => setAccountNo(values.value)} + onFocus={handleInputFocus} />
@@ -169,6 +171,7 @@ export const FundAccountTransferRequestPage = () => { type="text" value={accountName} onChange={(e: ChangeEvent) => setAccountName(e.target.value)} + onFocus={handleInputFocus} />
@@ -180,6 +183,7 @@ export const FundAccountTransferRequestPage = () => { allowNegative={false} thousandSeparator={true} displayType='input' + onFocus={handleInputFocus} onValueChange={(values) => { const { floatValue } = values; setAmount(floatValue ?? 0); @@ -194,16 +198,18 @@ export const FundAccountTransferRequestPage = () => { type="text" value={moid} onChange={(e: ChangeEvent) => setMoid(e.target.value)} + onFocus={handleInputFocus} /> -
+
{t('additionalService.fundAccount.depositParameter')}
setDepositParameter(e.target.value)} + onFocus={handleInputFocus} />
diff --git a/src/pages/additional-service/key-in-payment/requeset-page.tsx b/src/pages/additional-service/key-in-payment/requeset-page.tsx index aaa7e20..acc4ade 100644 --- a/src/pages/additional-service/key-in-payment/requeset-page.tsx +++ b/src/pages/additional-service/key-in-payment/requeset-page.tsx @@ -2,7 +2,6 @@ import { ChangeEvent, useState } from 'react'; import { PATHS } from '@/shared/constants/paths'; import { useLocation } from 'react-router'; import { useNavigate } from '@/shared/lib/hooks/use-navigate'; -import { IMAGE_ROOT } from '@/shared/constants/common'; import { HeaderType } from '@/entities/common/model/types'; import { useExtensionKeyinApplyMutation } from '@/entities/additional-service/api/use-extension-keyin-apply-mutation'; import { @@ -11,13 +10,12 @@ import { useSetFooterMode, useSetOnBack } from '@/widgets/sub-layout/use-sub-layout'; -import { overlay } from 'overlay-kit'; -import { Dialog } from '@/shared/ui/dialogs/dialog'; import { useStore } from '@/shared/model/store'; import { snackBar } from '@/shared/lib'; import { NumericFormat, PatternFormat } from 'react-number-format'; import { useTranslation } from 'react-i18next'; import { showAlert } from '@/widgets/show-alert'; +import { useKeyboardAware } from '@/shared/lib/hooks/use-keyboard-aware'; export const KeyInPaymentRequestPage = () => { const { t } = useTranslation(); @@ -43,6 +41,7 @@ export const KeyInPaymentRequestPage = () => { const [orderNumber, setOrderNumber] = useState(''); const { mutateAsync: keyInApply } = useExtensionKeyinApplyMutation(); + const { handleInputFocus, keyboardAwarePadding } = useKeyboardAware(); useSetHeaderTitle(t('additionalService.keyIn.title')); useSetHeaderType(HeaderType.LeftArrow); @@ -191,6 +190,7 @@ export const KeyInPaymentRequestPage = () => { type="text" value={productName} onChange={(e: ChangeEvent) => setProductName(e.target.value)} + onFocus={handleInputFocus} />
@@ -218,6 +218,7 @@ export const KeyInPaymentRequestPage = () => { type="text" value={customerName} onChange={(e: ChangeEvent) => setCustomerName(e.target.value)} + onFocus={handleInputFocus} /> @@ -230,6 +231,7 @@ export const KeyInPaymentRequestPage = () => { value={email} onChange={(e: ChangeEvent) => setEmail(e.target.value)} className={email && !isValidEmail(email) ? 'error' : ''} + onFocus={handleInputFocus} /> @@ -249,6 +251,7 @@ export const KeyInPaymentRequestPage = () => { inputMode="numeric" pattern="[0-9]*" maxLength={11} + onFocus={handleInputFocus} /> @@ -262,6 +265,7 @@ export const KeyInPaymentRequestPage = () => { valueIsNumericString format="####" onChange={(e: ChangeEvent) => setCardNo1(e.target.value)} + onFocus={handleInputFocus} > @@ -272,6 +276,7 @@ export const KeyInPaymentRequestPage = () => { valueIsNumericString format="####" onChange={(e: ChangeEvent) => setCardNo2(e.target.value)} + onFocus={handleInputFocus} > @@ -282,6 +287,7 @@ export const KeyInPaymentRequestPage = () => { valueIsNumericString format="####" onChange={(e: ChangeEvent) => setCardNo3(e.target.value)} + onFocus={handleInputFocus} > @@ -292,6 +298,7 @@ export const KeyInPaymentRequestPage = () => { valueIsNumericString format="####" onChange={(e: ChangeEvent) => setCardNo4(e.target.value)} + onFocus={handleInputFocus} > @@ -315,6 +322,7 @@ export const KeyInPaymentRequestPage = () => { const { value } = values; setExpMon(value); }} + onFocus={handleInputFocus} onBlur={() => { if (expMon.length === 1 && parseInt(expMon) >= 1 && parseInt(expMon) <= 9) { setExpMon(expMon.padStart(2, '0')); @@ -333,6 +341,7 @@ export const KeyInPaymentRequestPage = () => { const { value } = values; setExpYear(value); }} + onFocus={handleInputFocus} style={{ flex: 1 }} inputMode="numeric" /> @@ -367,13 +376,17 @@ export const KeyInPaymentRequestPage = () => { -
+
{t('additionalService.keyIn.orderNumber')}*
) => setOrderNumber(e.target.value)} + onFocus={handleInputFocus} />
diff --git a/src/shared/lib/hooks/index.tsx b/src/shared/lib/hooks/index.tsx index 8ff914c..67a5fe1 100644 --- a/src/shared/lib/hooks/index.tsx +++ b/src/shared/lib/hooks/index.tsx @@ -2,4 +2,4 @@ export * from './use-navigate'; export * from './use-fix-scroll-view-safari'; export * from './use-navigate'; export * from './use-router-listener'; - +export * from './use-keyboard-aware'; diff --git a/src/shared/lib/hooks/use-keyboard-aware.ts b/src/shared/lib/hooks/use-keyboard-aware.ts new file mode 100644 index 0000000..771950b --- /dev/null +++ b/src/shared/lib/hooks/use-keyboard-aware.ts @@ -0,0 +1,88 @@ +import { useEffect, useState, useMemo, CSSProperties } from 'react'; + +export interface UseKeyboardAwareOptions { + defaultPadding?: number; + heightRatio?: number; + maxPadding?: number; + transition?: string; +} + +export const useKeyboardAware = (options?: UseKeyboardAwareOptions) => { + const { + defaultPadding = 60, + heightRatio = 0.7, + maxPadding = 300, + transition = 'padding-bottom 0.2s ease' + } = options || {}; + + const [keyboardHeight, setKeyboardHeight] = useState(0); + + useEffect(() => { + let focusedElement: HTMLInputElement | null = null; + let isKeyboardOpen = false; + + // 네이티브에서 호출할 전역 함수 등록 + (window as any).onKeyboardShow = (height: number) => { + console.log('Keyboard shown, height:', height); + const wasKeyboardOpen = isKeyboardOpen; + isKeyboardOpen = true; + setKeyboardHeight(height); + + // 키보드가 새로 열릴 때만 스크롤 (이미 열려있으면 스크롤 안함) + if (!wasKeyboardOpen && focusedElement) { + setTimeout(() => { + focusedElement?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, 250); + } + }; + + (window as any).onKeyboardHide = () => { + console.log('Keyboard hidden'); + isKeyboardOpen = false; + setKeyboardHeight(0); + focusedElement = null; + }; + + // focus된 요소를 추적하기 위한 함수 + (window as any).setFocusedElement = (element: HTMLInputElement) => { + focusedElement = element; + + // 키보드가 이미 열려있으면 즉시 스크롤 + if (isKeyboardOpen) { + setTimeout(() => { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }, 100); + } + }; + + return () => { + delete (window as any).onKeyboardShow; + delete (window as any).onKeyboardHide; + delete (window as any).setFocusedElement; + }; + }, []); + + // 모든 input에서 사용할 공통 핸들러 + const handleInputFocus = (e: React.FocusEvent) => { + // focus된 요소를 저장 + (window as any).setFocusedElement?.(e.target); + }; + + // 동적 패딩 스타일 계산 + const keyboardAwarePadding: CSSProperties = useMemo(() => { + const calculatedPadding = keyboardHeight > 0 + ? Math.min(keyboardHeight * heightRatio, maxPadding) + : defaultPadding; + + return { + paddingBottom: `${calculatedPadding}px`, + transition + }; + }, [keyboardHeight, defaultPadding, heightRatio, maxPadding, transition]); + + return { + keyboardHeight, + handleInputFocus, + keyboardAwarePadding + }; +};