mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
fix(desktop): change bank input field to dropdown (#5913)
* Fix the bugs and CSS issues of the devbox * Remove embedded git repository * Add sealos files as regular files * Remove nested sealos repository completely * fix(desktop): change bank input field to dropdown * fix(desktop): change bank input field to dropdown * fix(desktop): change bank input field to dropdown * fix(desktop): change bank input field to dropdown * fix(desktop): change bank input field to dropdown
This commit is contained in:
@@ -218,6 +218,9 @@ export const _enterpriseRealNameAuthCancelRequest = (request: AxiosInstance) =>
|
||||
);
|
||||
};
|
||||
|
||||
export const _getBanksListRequest = (request: AxiosInstance) => () =>
|
||||
request<never, ApiResp<Record<string, string>>>('/api/account/enterpriseRealName?type=banks');
|
||||
|
||||
export const _getAmount = (request: AxiosInstance) => () =>
|
||||
request<never, ApiResp<{ balance: number; deductionBalance: number }>>('/api/account/getAmount');
|
||||
export const _verifyToken = (request: AxiosInstance) => () =>
|
||||
@@ -274,5 +277,6 @@ export const enterpriseRealNameAuthCancelRequest = _enterpriseRealNameAuthCancel
|
||||
|
||||
export const faceAuthGenerateQRcodeUriRequest = _faceAuthGenerateQRcodeUriRequest(request);
|
||||
export const getFaceAuthStatusRequest = _getFaceAuthStatusRequest(request);
|
||||
export const getBanksListRequest = _getBanksListRequest(request);
|
||||
|
||||
export const getAmount = _getAmount(request);
|
||||
|
||||
@@ -20,11 +20,16 @@ import {
|
||||
Spinner,
|
||||
Link,
|
||||
FormErrorMessage,
|
||||
FlexProps
|
||||
FlexProps,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Icon
|
||||
} from '@chakra-ui/react';
|
||||
import { CloseIcon, RefreshIcon, useMessage, WarningIcon } from '@sealos/ui';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState, useMemo, useRef } from 'react';
|
||||
import { Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
@@ -36,7 +41,8 @@ import {
|
||||
enterpriseRealNameAuthVerifyRequest,
|
||||
faceAuthGenerateQRcodeUriRequest,
|
||||
getFaceAuthStatusRequest,
|
||||
refreshRealNameQRecodeUriRequest
|
||||
refreshRealNameQRecodeUriRequest,
|
||||
getBanksListRequest
|
||||
} from '@/api/auth';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import useSessionStore from '@/stores/session';
|
||||
@@ -360,7 +366,7 @@ export function RealNameAuthForm(
|
||||
boxShadow:
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.15)'
|
||||
},
|
||||
_focus: { boxShadow: 'none' }
|
||||
_focus: {}
|
||||
}}
|
||||
>
|
||||
{t('common:enterprise_verification')}
|
||||
@@ -591,6 +597,47 @@ function EnterpriseVerification(
|
||||
const queryClient = useQueryClient();
|
||||
const domain = useConfigStore((state) => state.cloudConfig?.domain);
|
||||
|
||||
|
||||
const [selectedBank, setSelectedBank] = useState<string>('');
|
||||
|
||||
const [searchKeyword, setSearchKeyword] = useState<string>('');
|
||||
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
const clearSearch = useCallback(() => {
|
||||
setSearchKeyword('');
|
||||
}, []);
|
||||
|
||||
const { data: banksResponse, isLoading: banksLoading, error: banksError } = useQuery(
|
||||
['banksList'],
|
||||
getBanksListRequest,
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
select: (response) => {
|
||||
const banksData = response?.data || {};
|
||||
return Object.entries(banksData).map(([key, value]) => ({
|
||||
code: key,
|
||||
name: String(value || ''),
|
||||
shortName: String(key || '')
|
||||
}));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const banksList = banksResponse || [];
|
||||
const filteredBanksList = useMemo(() => {
|
||||
if (!searchKeyword.trim()) {
|
||||
return banksList;
|
||||
}
|
||||
return banksList.filter(bank => {
|
||||
const bankShortName = bank.shortName.toLowerCase();
|
||||
const keyword = searchKeyword.toLowerCase();
|
||||
return bankShortName.includes(keyword);
|
||||
});
|
||||
}, [banksList, searchKeyword]);
|
||||
|
||||
const schema = z.object({
|
||||
key: z.string().min(1, { message: t('common:enterprise_key_required') }),
|
||||
accountBank: z.string().min(1, { message: t('common:account_bank_required') }),
|
||||
@@ -620,6 +667,7 @@ function EnterpriseVerification(
|
||||
register: registerMain,
|
||||
handleSubmit: handleMainSubmit,
|
||||
reset: resetMain,
|
||||
setValue: setMainValue,
|
||||
formState: { errors: mainErrors }
|
||||
} = useForm<z.infer<typeof schema>>({
|
||||
resolver: zodResolver(schema),
|
||||
@@ -653,9 +701,23 @@ function EnterpriseVerification(
|
||||
usrName,
|
||||
contactInfo
|
||||
});
|
||||
setSelectedBank(accountBank || '');
|
||||
}
|
||||
}, [enterpriseRealNameAuthInfo?.data, resetMain]);
|
||||
|
||||
|
||||
const handleBankSelect = useCallback((bankName: string) => {
|
||||
setSelectedBank(bankName);
|
||||
setMainValue('accountBank', bankName);
|
||||
setSearchKeyword('');
|
||||
}, [setMainValue]);
|
||||
|
||||
|
||||
const resetSearchState = useCallback(() => {
|
||||
clearSearch();
|
||||
setSelectedBank('');
|
||||
}, [clearSearch]);
|
||||
|
||||
const canPayment = enterpriseRealNameAuthInfo?.data?.paymentStatus !== PAYMENTSTATUS.PROCESSING;
|
||||
|
||||
const canVerify = enterpriseRealNameAuthInfo?.data?.paymentStatus === PAYMENTSTATUS.PROCESSING;
|
||||
@@ -721,6 +783,7 @@ function EnterpriseVerification(
|
||||
queryClient.invalidateQueries(['enterpriseRealNameAuthInfo']);
|
||||
resetMain();
|
||||
resetVerification();
|
||||
resetSearchState();
|
||||
|
||||
if (props.onFormSuccess) {
|
||||
props.onFormSuccess();
|
||||
@@ -1038,6 +1101,7 @@ function EnterpriseVerification(
|
||||
</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
|
||||
<FormControl
|
||||
isInvalid={!!mainErrors.accountBank}
|
||||
w="full"
|
||||
@@ -1064,17 +1128,250 @@ function EnterpriseVerification(
|
||||
>
|
||||
{t('common:bank_name')}
|
||||
</FormLabel>
|
||||
<Box position="relative" w="full">
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
isDisabled={!canInput || banksLoading}
|
||||
placeholder={banksLoading
|
||||
? t('common:loading')
|
||||
: banksError
|
||||
? t('common:get_code_failed')
|
||||
: t('common:please_enter_bank_name')
|
||||
}
|
||||
value={searchKeyword || selectedBank}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchKeyword(value);
|
||||
|
||||
if (selectedBank && value !== selectedBank) {
|
||||
setSelectedBank('');
|
||||
setMainValue('accountBank', '');
|
||||
}
|
||||
|
||||
if (value) {
|
||||
setIsMenuOpen(true);
|
||||
}
|
||||
}}
|
||||
onFocus={() => {
|
||||
if (filteredBanksList.length > 0) {
|
||||
setIsMenuOpen(true);
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
|
||||
setTimeout(() => {
|
||||
setIsMenuOpen(false);
|
||||
}, 150);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
clearSearch();
|
||||
setIsMenuOpen(false);
|
||||
} else if (e.key === 'Enter' && filteredBanksList.length > 0) {
|
||||
e.preventDefault();
|
||||
|
||||
if (filteredBanksList.length === 1) {
|
||||
handleBankSelect(filteredBanksList[0].shortName);
|
||||
setIsMenuOpen(false);
|
||||
}
|
||||
}
|
||||
}}
|
||||
h="32px"
|
||||
px="12px"
|
||||
pr="40px"
|
||||
w="full"
|
||||
borderRadius="6px"
|
||||
borderColor={isMenuOpen ? "brightBlue.600" : "grayModern.200"}
|
||||
bg="grayModern.50"
|
||||
fontSize="14px"
|
||||
fontWeight={400}
|
||||
lineHeight="20px"
|
||||
color="grayModern.900"
|
||||
backgroundColor={selectedBank ? "rgb(231, 240, 254)" : "grayModern.50"}
|
||||
_disabled={{
|
||||
bg: "grayModern.100",
|
||||
color: "grayModern.400",
|
||||
cursor: "not-allowed",
|
||||
opacity: 0.6
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<Flex
|
||||
position="absolute"
|
||||
right="12px"
|
||||
top="50%"
|
||||
transform="translateY(-50%)"
|
||||
alignItems="center"
|
||||
gap="4px"
|
||||
pointerEvents={!canInput || banksLoading ? "none" : "auto"}
|
||||
>
|
||||
{(searchKeyword || selectedBank) && (
|
||||
<Icon
|
||||
viewBox="0 0 16 16"
|
||||
w="14px"
|
||||
h="14px"
|
||||
color="grayModern.400"
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedBank('');
|
||||
setMainValue('accountBank', '');
|
||||
setSearchKeyword('');
|
||||
if (searchInputRef.current) {
|
||||
searchInputRef.current.focus();
|
||||
}
|
||||
}}
|
||||
_hover={{ color: "grayModern.600" }}
|
||||
>
|
||||
<path
|
||||
d="M8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1ZM10.7071 5.29289C11.0976 5.68342 11.0976 6.31658 10.7071 6.70711L9.41421 8L10.7071 9.29289C11.0976 9.68342 11.0976 10.3166 10.7071 10.7071C10.3166 11.0976 9.68342 11.0976 9.29289 10.7071L8 9.41421L6.70711 10.7071C6.31658 11.0976 5.68342 11.0976 5.29289 10.7071C4.90237 10.3166 4.90237 9.68342 5.29289 9.29289L6.58579 8L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L8 6.58579L9.29289 5.29289C9.68342 4.90237 10.3166 4.90237 10.7071 5.29289Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</Icon>
|
||||
)}
|
||||
<Icon
|
||||
viewBox="0 0 16 16"
|
||||
w="14px"
|
||||
h="14px"
|
||||
color="grayModern.500"
|
||||
transform={isMenuOpen ? "rotate(180deg)" : "rotate(0deg)"}
|
||||
transition="transform 0.15s ease"
|
||||
cursor="pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (isMenuOpen) {
|
||||
setIsMenuOpen(false);
|
||||
} else {
|
||||
setIsMenuOpen(true);
|
||||
if (searchInputRef.current) {
|
||||
searchInputRef.current.focus();
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M4 6L8 10L12 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
</Icon>
|
||||
</Flex>
|
||||
|
||||
{isMenuOpen && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="100%"
|
||||
left="0"
|
||||
right="0"
|
||||
maxH="280px"
|
||||
borderRadius="8px"
|
||||
borderColor="white"
|
||||
border="none"
|
||||
bg="white"
|
||||
boxShadow="0px 8px 24px rgba(19, 51, 107, 0.15)"
|
||||
py="8px"
|
||||
mt="4px"
|
||||
zIndex={1500}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Box maxH="320px" overflowY="auto">
|
||||
{Array.isArray(filteredBanksList) && filteredBanksList.length > 0 ? (
|
||||
filteredBanksList.map((bank, index) => (
|
||||
<Box
|
||||
key={`${bank.code}-${index}`}
|
||||
onClick={() => {
|
||||
handleBankSelect(bank.shortName);
|
||||
setIsMenuOpen(false);
|
||||
}}
|
||||
h="48px"
|
||||
px="12px"
|
||||
py="8px"
|
||||
cursor="pointer"
|
||||
bg={selectedBank === bank.shortName ? "brightBlue.50" : "white"}
|
||||
_hover={{
|
||||
bg: selectedBank === bank.shortName ? "brightBlue.100" : "grayModern.50"
|
||||
}}
|
||||
transition="background-color 0.1s ease"
|
||||
>
|
||||
<Flex alignItems="center" w="full" h="full">
|
||||
<Box
|
||||
w="24px"
|
||||
h="24px"
|
||||
borderRadius="4px"
|
||||
bg="grayModern.200"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
mr="12px"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Text fontSize="10px" fontWeight="bold" color="grayModern.600">
|
||||
{bank.shortName.charAt(0)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Text
|
||||
fontSize="14px"
|
||||
fontWeight={selectedBank === bank.shortName ? 600 : 400}
|
||||
color="grayModern.900"
|
||||
flex="1"
|
||||
>
|
||||
{bank.shortName}
|
||||
</Text>
|
||||
|
||||
{selectedBank === bank.shortName && (
|
||||
<Icon
|
||||
viewBox="0 0 16 16"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="brightBlue.600"
|
||||
ml="8px"
|
||||
>
|
||||
<path
|
||||
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</Icon>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
))
|
||||
) : searchKeyword ? (
|
||||
<Flex
|
||||
h="48px"
|
||||
px="12px"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
color="grayModern.500"
|
||||
fontSize="14px"
|
||||
>
|
||||
未找到相关银行
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
h="48px"
|
||||
px="12px"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
color="grayModern.500"
|
||||
fontSize="14px"
|
||||
>
|
||||
{banksError ? t('common:get_code_failed') : '请选择银行'}
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Input
|
||||
{...registerMain('accountBank')}
|
||||
isDisabled={!canInput}
|
||||
placeholder={t('common:please_enter_bank_name')}
|
||||
h="32px"
|
||||
px="12px"
|
||||
alignItems="center"
|
||||
w="full"
|
||||
borderRadius="6px"
|
||||
borderColor="grayModern.200"
|
||||
bg="grayModern.50"
|
||||
type="hidden"
|
||||
value={selectedBank}
|
||||
/>
|
||||
</Flex>
|
||||
<FormErrorMessage
|
||||
|
||||
@@ -76,6 +76,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
if (req.query.type === 'banks') {
|
||||
return handleGetBanks(req, res, payload);
|
||||
}
|
||||
return handleGet(req, res, payload.userUid);
|
||||
case 'POST':
|
||||
return handlePost(req, res, payload.userUid, payload);
|
||||
@@ -199,7 +202,7 @@ async function handlePost(
|
||||
regionUid: payload.regionUid
|
||||
});
|
||||
|
||||
const response = await fetch(enterpriseRealNameAuthApi, {
|
||||
const response = await fetch(`${enterpriseRealNameAuthApi}/v1/enterprise-auth`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -321,3 +324,55 @@ async function handlePatch(req: NextApiRequest, res: NextApiResponse, userUid: s
|
||||
return jsonRes(res, { code: 500, message: 'Internal server error' });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleGetBanks(req: NextApiRequest, res: NextApiResponse, payload: AccessTokenPayload) {
|
||||
try {
|
||||
const realNameAuthProvider: RealNameAuthProvider | null =
|
||||
await globalPrisma.realNameAuthProvider.findFirst({
|
||||
where: {
|
||||
backend: 'UNIONPAY',
|
||||
authType: '3060'
|
||||
}
|
||||
});
|
||||
|
||||
if (!realNameAuthProvider) {
|
||||
throw new Error('enterpriseRealNameAuth: Real name authentication provider not found');
|
||||
}
|
||||
|
||||
const config: UnionPay3060Config = realNameAuthProvider.config as UnionPay3060Config;
|
||||
const enterpriseRealNameAuthApi = config.api;
|
||||
|
||||
const globalToken = generateAuthenticationToken({
|
||||
userUid: payload.userUid,
|
||||
userId: payload.userId,
|
||||
regionUid: payload.regionUid
|
||||
});
|
||||
|
||||
const fullApiUrl = `${enterpriseRealNameAuthApi}/v1/banks`;
|
||||
|
||||
|
||||
const response = await fetch(fullApiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${globalToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch banks: ${response.status}`);
|
||||
}
|
||||
|
||||
const banksResponse = await response.json();
|
||||
|
||||
const banksData = banksResponse?.data || {};
|
||||
|
||||
return jsonRes(res, {
|
||||
code: 200,
|
||||
data: banksData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching banks list:', error);
|
||||
return jsonRes(res, { code: 500, message: 'Failed to fetch banks list' });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user