mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
feat: yearly/monthly billing for cloudserver (#4781)
* feat:yearly/monthly billing for cloudserver * fix list servertype * feat cost tip * add tip * add error tip
This commit is contained in:
@@ -52,9 +52,9 @@
|
||||
"Quantity tips": "Quantity is {{amount1}} to {{amount2}}",
|
||||
"ReStart": "Restart",
|
||||
"Reference fee": "Reference fee",
|
||||
"Reference fee disk tips": "{{price}}/GiB/hour",
|
||||
"Reference fee tip": "{{price}}/hour",
|
||||
"Reference fee bandwidth tips": "{{price}}/Mbps/hour",
|
||||
"Reference fee disk tips": "{{price}}/GiB",
|
||||
"Reference fee tip": "{{price}}",
|
||||
"Reference fee bandwidth tips": "{{price}}/Mbps",
|
||||
"Restarting": "Restarting",
|
||||
"Set Password": "Set Password",
|
||||
"Start": "Start",
|
||||
@@ -111,5 +111,13 @@
|
||||
"highPerformance": "High Performance",
|
||||
"Public IP is not enabled": "Public IP is not enabled",
|
||||
"interval": "Interval",
|
||||
"sold out": "Sold Out"
|
||||
"sold out": "Sold Out",
|
||||
"month": "month",
|
||||
"indivual": "indivual",
|
||||
"year": "year",
|
||||
"duration": "duration",
|
||||
"total": "total",
|
||||
"After the annual and monthly cloud hosting is sold": "After the annual and monthly cloud hosting is sold",
|
||||
"No refund allowed": "No refund allowed,",
|
||||
"Pay Confirm tips": "Clicking \"Confirm\" indicates that you have understood and agreed to this clause. If you do not agree to the clause \"No refunds are allowed after the annual or monthly cloud hosting is sold\", please click the \"Cancel\" button. If you do not agree, this purchase will be canceled and you will not be charged any fees."
|
||||
}
|
||||
@@ -75,7 +75,7 @@
|
||||
"Changing": "变更中",
|
||||
"Restarting": "重启中",
|
||||
"Reference fee": "参考费用",
|
||||
"Reference fee tip": "{{price}}/小时",
|
||||
"Reference fee tip": "{{price}}",
|
||||
"Configuration fee details": "配置费用明细",
|
||||
"Instance": "实例",
|
||||
"storage fees": "存储费用",
|
||||
@@ -85,8 +85,8 @@
|
||||
"publicIpAssigned tips": "请注意:未分配独立公网IP地址的情况下无法使用外网IP地址对外进行互相通信。",
|
||||
"Fee inquiry in progress": "费用查询中...",
|
||||
"hour": "小时",
|
||||
"Reference fee disk tips": "{{price}}/GiB/小时",
|
||||
"Reference fee bandwidth tips": "{{price}}/Mbps/小时",
|
||||
"Reference fee disk tips": "{{price}}/GiB",
|
||||
"Reference fee bandwidth tips": "{{price}}/Mbps",
|
||||
"Billing rules": "计费规则",
|
||||
"The maximum number of instances is 20": "最大数量是20",
|
||||
"Billing model": "计费模式",
|
||||
@@ -111,5 +111,13 @@
|
||||
"highPerformance": "计算型",
|
||||
"Public IP is not enabled": "未开启公网IP",
|
||||
"interval": "区间",
|
||||
"sold out": "售空"
|
||||
}
|
||||
"sold out": "售空",
|
||||
"month": "月",
|
||||
"indivual": "个",
|
||||
"year": "年",
|
||||
"duration": "时长",
|
||||
"total": "总计",
|
||||
"After the annual and monthly cloud hosting is sold": "包年包月云主机售卖后",
|
||||
"No refund allowed": "不允许退费,",
|
||||
"Pay Confirm tips": "点击“确认”表示您已知晓并同意此条款。如果您不同意“包年包月云主机售卖后不允许退费”的条款,请点击“取消”按钮。不同意后,本次购买将被撤销,您不会被收取任何费用。"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 14" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5004 2.0529C4.76824 2.0529 2.55339 4.26775 2.55339 6.99991C2.55339 9.73207 4.76824 11.9469 7.5004 11.9469C10.2326 11.9469 12.4474 9.73207 12.4474 6.99991C12.4474 4.26775 10.2326 2.0529 7.5004 2.0529ZM1.38672 6.99991C1.38672 3.62342 4.12391 0.88623 7.5004 0.88623C10.8769 0.88623 13.6141 3.62342 13.6141 6.99991C13.6141 10.3764 10.8769 13.1136 7.5004 13.1136C4.12391 13.1136 1.38672 10.3764 1.38672 6.99991ZM6.91706 4.78777C6.91706 4.4656 7.17823 4.20444 7.5004 4.20444H7.50593C7.82809 4.20444 8.08926 4.4656 8.08926 4.78777C8.08926 5.10994 7.82809 5.3711 7.50593 5.3711H7.5004C7.17823 5.3711 6.91706 5.10994 6.91706 4.78777ZM7.5004 6.41657C7.82256 6.41657 8.08373 6.67774 8.08373 6.99991V9.21205C8.08373 9.53421 7.82256 9.79538 7.5004 9.79538C7.17823 9.79538 6.91706 9.53421 6.91706 9.21205V6.99991C6.91706 6.67774 7.17823 6.41657 7.5004 6.41657Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1004 B |
@@ -29,6 +29,7 @@ const map = {
|
||||
connection: require('./icons/connection.svg').default,
|
||||
info: require('./icons/info.svg').default,
|
||||
restore: require('./icons/restore.svg').default,
|
||||
infoWarn: require('./icons/infoWarn.svg').default,
|
||||
download: require('./icons/download.svg').default,
|
||||
check: require('./icons/check.svg').default,
|
||||
close: require('./icons/close.svg').default,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { ReactNode, useCallback, useRef } from 'react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
@@ -17,7 +17,7 @@ export const useConfirm = ({
|
||||
confirmText = 'Confirm'
|
||||
}: {
|
||||
title?: string;
|
||||
content: string;
|
||||
content: ReactNode;
|
||||
confirmText?: string;
|
||||
}) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
@@ -41,7 +41,7 @@ export const useConfirm = ({
|
||||
() => (
|
||||
<AlertDialog isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}>
|
||||
<AlertDialogOverlay>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogContent maxW={'600px'}>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
{t(title)}
|
||||
</AlertDialogHeader>
|
||||
|
||||
@@ -31,15 +31,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
zone: form.zone,
|
||||
virtualMachineType: form.virtualMachineType,
|
||||
virtualMachineArch: form.virtualMachineArch,
|
||||
chareType: form.chargeType
|
||||
chargeType: form.chargeType,
|
||||
period: parseInt(form.period)
|
||||
};
|
||||
|
||||
const { data } = await POST('/action/create', payload, {
|
||||
const { data, error } = await POST('/action/create', payload, {
|
||||
headers: {
|
||||
Authorization: req.headers.authorization
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return jsonRes(res, { code: 500, error: error });
|
||||
}
|
||||
|
||||
return jsonRes(res, {
|
||||
data: data
|
||||
});
|
||||
|
||||
@@ -17,7 +17,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await verifyAccessToken(req);
|
||||
|
||||
const payload = req.body as ServerTypePayload;
|
||||
console.log(payload);
|
||||
|
||||
const { data, error } = await POST('/action/get-virtual-machine-package', payload, {
|
||||
headers: {
|
||||
|
||||
@@ -31,7 +31,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
zone: form.zone,
|
||||
virtualMachineType: form.virtualMachineType,
|
||||
virtualMachineArch: form.virtualMachineArch,
|
||||
chareType: form.chargeType
|
||||
chargeType: form.chargeType,
|
||||
period: parseInt(form.period)
|
||||
};
|
||||
|
||||
const result = await POST('/action/get-price', payload, {
|
||||
@@ -44,6 +45,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
data: result?.data
|
||||
});
|
||||
} catch (error) {
|
||||
jsonRes(res, { code: 500, error: error });
|
||||
jsonRes(res, { code: 500, error: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,14 +22,14 @@ const ErrorModal = ({
|
||||
return (
|
||||
<Modal isOpen={true} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent py={3}>
|
||||
<ModalContent>
|
||||
<ModalHeader display={'flex'} alignItems={'center'} color={'myRed.600'}>
|
||||
<MyIcon name="warning"></MyIcon>
|
||||
<Box ml={3} fontSize={'xl'}>
|
||||
{title}
|
||||
</Box>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton color={'myRed.600'} fontSize={'16px'} transform={'translateY(10px)'} />
|
||||
<ModalCloseButton color={'myRed.600'} fontSize={'16px'} />
|
||||
<ModalBody maxH={'50vh'} overflow={'auto'} whiteSpace={'pre-wrap'}>
|
||||
{content}
|
||||
</ModalBody>
|
||||
|
||||
@@ -3,7 +3,13 @@ import MyIcon from '@/components/Icon';
|
||||
import { MyTable, TableColumnsType } from '@/components/MyTable';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { CloudServerStatus, CloudServerType, EditForm, StorageType } from '@/types/cloudserver';
|
||||
import { CVMArchType, CVMRegionType, CVMZoneType, VirtualMachineType } from '@/types/region';
|
||||
import {
|
||||
CVMArchType,
|
||||
CVMChargeType,
|
||||
CVMRegionType,
|
||||
CVMZoneType,
|
||||
VirtualMachineType
|
||||
} from '@/types/region';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -261,6 +267,15 @@ function OuterTabs({ systemRegion }: { systemRegion: CVMRegionType[] }) {
|
||||
onChange={(e) => {
|
||||
const item = systemRegion[e];
|
||||
formHook?.setValue('chargeType', item.chargeType);
|
||||
formHook?.setValue('virtualMachineArch', item.zone[0].arch[0].arch);
|
||||
formHook.setValue(
|
||||
'virtualMachineType',
|
||||
item.zone[0].arch[0].virtualMachineType[0].virtualMachineType
|
||||
);
|
||||
formHook.setValue(
|
||||
'virtualMachinePackageFamily',
|
||||
item.zone[0].arch[0].virtualMachineType[0].virtualMachinePackageFamily[0]
|
||||
);
|
||||
}}
|
||||
>
|
||||
<TabList alignItems={'center'}>
|
||||
@@ -430,7 +445,10 @@ export default function Form({
|
||||
item.status === CloudServerStatus.Unavailable ? 'grayModern.500' : 'brightBlue.700'
|
||||
}
|
||||
>
|
||||
{t('Reference fee tip', { price: item.instancePrice })}{' '}
|
||||
{t('Reference fee tip', { price: item.instancePrice })}/
|
||||
{formHook.getValues('chargeType') === CVMChargeType.postPaidByHour
|
||||
? t('hour')
|
||||
: t('month')}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -555,6 +573,29 @@ export default function Form({
|
||||
[getValues, register, removeStorages, setValue, t]
|
||||
);
|
||||
|
||||
const monthsArray = useMemo(() => {
|
||||
const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36];
|
||||
return months.map((month) => {
|
||||
let label;
|
||||
if (month < 12) {
|
||||
label = `${month} ${t('indivual')} ${t('month')}`;
|
||||
} else if (month === 12) {
|
||||
label = `1${t('year')}`;
|
||||
} else {
|
||||
const years = Math.floor(month / 12);
|
||||
const remainingMonths = month % 12;
|
||||
label =
|
||||
remainingMonths === 0
|
||||
? `${years} ${t('year')}`
|
||||
: `${years} ${t('year')}${remainingMonths} ${t('indivual')} ${t('month')}`;
|
||||
}
|
||||
return {
|
||||
value: month.toString(),
|
||||
label: label
|
||||
};
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
height={'100%'}
|
||||
@@ -846,6 +887,26 @@ export default function Form({
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{formHook.getValues('chargeType') === CVMChargeType.prePaid && (
|
||||
<Flex alignItems={'center'} mb={'24px'} flex={1}>
|
||||
<Label>
|
||||
<Text>{t('duration')}</Text>
|
||||
</Label>
|
||||
<Box>
|
||||
<MySelect
|
||||
isInvalid={!!errors.period}
|
||||
width={'200px'}
|
||||
value={getValues('period')}
|
||||
list={monthsArray}
|
||||
onchange={(id: string) => {
|
||||
setValue('period', id);
|
||||
formHook.clearErrors();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
+148
-103
@@ -8,24 +8,166 @@ import {
|
||||
Divider,
|
||||
Flex,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverHeader,
|
||||
PopoverTrigger,
|
||||
Text
|
||||
} from '@chakra-ui/react';
|
||||
import { Decimal } from 'decimal.js';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Decimal } from 'decimal.js';
|
||||
|
||||
export const priceItemStyle = {
|
||||
alignItems: 'center',
|
||||
py: '4px',
|
||||
px: '6px',
|
||||
borderRadius: 'base',
|
||||
_hover: {
|
||||
bg: 'grayModern.100'
|
||||
}
|
||||
};
|
||||
|
||||
export const CostTipContent = ({
|
||||
prices,
|
||||
instanceType,
|
||||
isMonth,
|
||||
isModalTip
|
||||
}: {
|
||||
isMonth: boolean;
|
||||
prices?: CloudServerPrice;
|
||||
instanceType?: CloudServerType;
|
||||
isModalTip?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{prices && isModalTip && (
|
||||
<>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={'12px'}
|
||||
py={'12px'}
|
||||
bg={'#FEF3F2'}
|
||||
borderRadius={'6px'}
|
||||
color={'#F04438'}
|
||||
gap={'8px'}
|
||||
mb={'16px'}
|
||||
>
|
||||
<MyIcon name="infoWarn" width={'20px'} />
|
||||
<Box>
|
||||
{t('After the annual and monthly cloud hosting is sold')}
|
||||
<Text display={'inline'} fontWeight={500} px={'2px'}>
|
||||
{t('No refund allowed')}
|
||||
</Text>
|
||||
{t('Pay Confirm tips')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} pr={'10px'}>
|
||||
<Box px={'18px'} py={'12px'} fontWeight={'bold'} fontSize={'md'}>
|
||||
{t('total')}
|
||||
</Box>
|
||||
|
||||
<Text fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥
|
||||
{new Decimal(prices?.diskPrice)
|
||||
.plus(new Decimal(prices?.instancePrice))
|
||||
.plus(new Decimal(prices?.networkPrice))
|
||||
.toNumber()}
|
||||
</Text>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<Box px={'18px'} py={'12px'} fontWeight={'bold'} fontSize={'md'}>
|
||||
{t('Configuration fee details')}
|
||||
</Box>
|
||||
<Divider />
|
||||
<Flex py={'7px'} px={'10px'} flexDirection={'column'} gap={'4px'}>
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text fontSize={'base'}>{t('Instance')}</Text>
|
||||
<Text ml={'auto'} fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥{prices?.instancePrice}
|
||||
{!isMonth && `/${t('hour')}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text alignSelf={'self-start'} fontSize={'base'} width={'50px'}>
|
||||
{t('storage fees')}
|
||||
</Text>
|
||||
<Text ml={'auto'} fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥{prices?.diskPrice}
|
||||
{!isMonth && `/${t('hour')}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text alignSelf={'self-start'} fontSize={'base'} width={'50px'}>
|
||||
{t('Public network bandwidth')}
|
||||
</Text>
|
||||
<Text ml={'auto'} alignSelf={'self-start'} fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥{prices?.networkPrice}
|
||||
{!isMonth && `/${t('hour')}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* Billing rules */}
|
||||
<Box px={'18px'} py={'12px'} fontWeight={'bold'} fontSize={'md'}>
|
||||
{t('Billing rules')}
|
||||
</Box>
|
||||
<Divider />
|
||||
<Flex py={'7px'} px={'10px'} flexDirection={'column'} gap={'4px'}>
|
||||
<Flex {...priceItemStyle} fontSize={'base'} justifyContent={'space-between'}>
|
||||
<Text w={'50px'} fontSize={'base'}>
|
||||
{t('Storage')}
|
||||
</Text>
|
||||
<Text fontSize={'base'}>
|
||||
{t('Reference fee disk tips', { price: instanceType?.diskPerG })}/
|
||||
{isMonth ? t('month') : t('hour')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex {...priceItemStyle} justifyContent={'space-between'}>
|
||||
<Text flexShrink={0} w={'50px'} alignSelf={'self-start'} fontSize={'base'} width={'50px'}>
|
||||
{t('BandWidth')}
|
||||
</Text>
|
||||
<Flex flexDirection={'column'} fontSize={'base'} wordBreak={'break-all'}>
|
||||
<Flex gap={'8px'} justifyContent={'space-between'}>
|
||||
<Text>{t('interval')}</Text>
|
||||
<Text>{t('price')}</Text>
|
||||
</Flex>
|
||||
{instanceType?.bandwidthPricingTiers.map((item, index) => {
|
||||
return (
|
||||
<Flex
|
||||
gap={'8px'}
|
||||
justifyContent={'space-between'}
|
||||
key={item.pricePerMbps + item.minBandwidth}
|
||||
>
|
||||
<Text>{`(${item.minBandwidth} , ${
|
||||
item.maxBandwidth === null ? '∞' : item.maxBandwidth
|
||||
}]`}</Text>
|
||||
<Text>
|
||||
{t('Reference fee bandwidth tips', { price: item.pricePerMbps })}/
|
||||
{isMonth ? t('month') : t('hour')}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = ({
|
||||
title,
|
||||
applyCb,
|
||||
applyBtnText,
|
||||
prices,
|
||||
instanceType
|
||||
instanceType,
|
||||
isMonth
|
||||
}: {
|
||||
isMonth: boolean;
|
||||
title: string;
|
||||
applyCb: () => void;
|
||||
applyBtnText: string;
|
||||
@@ -36,16 +178,6 @@ const Header = ({
|
||||
const router = useRouter();
|
||||
const { lastRoute } = useGlobalStore();
|
||||
|
||||
const priceItemStyle = {
|
||||
alignItems: 'center',
|
||||
py: '4px',
|
||||
px: '6px',
|
||||
borderRadius: 'base',
|
||||
_hover: {
|
||||
bg: 'grayModern.100'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex w={'100%'} px={10} h={'86px'} alignItems={'center'}>
|
||||
<Flex alignItems={'center'} cursor={'pointer'} onClick={() => router.replace(lastRoute)}>
|
||||
@@ -75,94 +207,7 @@ const Header = ({
|
||||
</Flex>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<PopoverHeader px={'18px'} py={'12px'} fontWeight={'bold'} fontSize={'md'}>
|
||||
{t('Configuration fee details')}
|
||||
</PopoverHeader>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex flexDirection={'column'} gap={'4px'}>
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text fontSize={'base'}>{t('Instance')}</Text>
|
||||
<Text ml={'auto'} fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥{prices?.instancePrice}/{t('hour')}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text alignSelf={'self-start'} fontSize={'base'} width={'50px'}>
|
||||
{t('storage fees')}
|
||||
</Text>
|
||||
<Text ml={'auto'} fontWeight={'bold'} color={'brightBlue.600'}>
|
||||
¥{prices?.diskPrice}/{t('hour')}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Flex {...priceItemStyle}>
|
||||
<Text alignSelf={'self-start'} fontSize={'base'} width={'50px'}>
|
||||
{t('Public network bandwidth')}
|
||||
</Text>
|
||||
<Text
|
||||
ml={'auto'}
|
||||
alignSelf={'self-start'}
|
||||
fontWeight={'bold'}
|
||||
color={'brightBlue.600'}
|
||||
>
|
||||
¥{prices?.networkPrice}/{t('hour')}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
|
||||
{/* Billing rules */}
|
||||
<PopoverHeader px={'18px'} py={'12px'} fontWeight={'bold'} fontSize={'md'}>
|
||||
{t('Billing rules')}
|
||||
</PopoverHeader>
|
||||
<PopoverArrow />
|
||||
<PopoverBody>
|
||||
<Flex flexDirection={'column'} gap={'4px'}>
|
||||
<Flex {...priceItemStyle} fontSize={'base'} justifyContent={'space-between'}>
|
||||
<Text w={'50px'} fontSize={'base'}>
|
||||
{t('Storage')}
|
||||
</Text>
|
||||
<Text fontSize={'base'}>
|
||||
{t('Reference fee disk tips', { price: instanceType?.diskPerG })}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex {...priceItemStyle} justifyContent={'space-between'}>
|
||||
<Text
|
||||
flexShrink={0}
|
||||
w={'50px'}
|
||||
alignSelf={'self-start'}
|
||||
fontSize={'base'}
|
||||
width={'50px'}
|
||||
>
|
||||
{t('BandWidth')}
|
||||
</Text>
|
||||
<Flex flexDirection={'column'} fontSize={'base'} wordBreak={'break-all'}>
|
||||
<Flex gap={'8px'} justifyContent={'space-between'}>
|
||||
<Text>{t('interval')}</Text>
|
||||
<Text>{t('price')}</Text>
|
||||
</Flex>
|
||||
{instanceType?.bandwidthPricingTiers.map((item, index) => {
|
||||
return (
|
||||
<Flex
|
||||
gap={'8px'}
|
||||
justifyContent={'space-between'}
|
||||
key={item.pricePerMbps + item.minBandwidth}
|
||||
>
|
||||
<Text>{`[${item.minBandwidth} , ${
|
||||
item.maxBandwidth === null ? '∞' : item.maxBandwidth
|
||||
})`}</Text>
|
||||
<Text>
|
||||
{t('Reference fee bandwidth tips', { price: item.pricePerMbps })}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
<CostTipContent isMonth={isMonth} instanceType={instanceType} prices={prices} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useLoading } from '@/hooks/useLoading';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { CloudServerType, EditForm } from '@/types/cloudserver';
|
||||
import { CVMChargeType } from '@/types/region';
|
||||
import { serviceSideProps } from '@/utils/i18n';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -13,7 +14,7 @@ import { useCallback, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import ErrorModal from './components/ErrorModal';
|
||||
import Form from './components/Form';
|
||||
import Header from './components/Header';
|
||||
import Header, { CostTipContent } from './components/Header';
|
||||
|
||||
export default function EditOrder() {
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
@@ -25,10 +26,6 @@ export default function EditOrder() {
|
||||
const { lastRoute } = useGlobalStore();
|
||||
const [instanceType, setInstanceType] = useState<CloudServerType>();
|
||||
|
||||
const { openConfirm, ConfirmChild } = useConfirm({
|
||||
content: t('Are you sure to create a cloud host?')
|
||||
});
|
||||
|
||||
// form
|
||||
const formHook = useForm<EditForm>({
|
||||
defaultValues: {
|
||||
@@ -42,7 +39,8 @@ export default function EditOrder() {
|
||||
amount: 1
|
||||
}
|
||||
],
|
||||
systemImageId: ''
|
||||
systemImageId: '',
|
||||
period: '1'
|
||||
// chargeType: CVMChargeType.postPaidByHour,
|
||||
// zone: 'Guangzhou-6',
|
||||
// virtualMachineArch: 'x86_64',
|
||||
@@ -56,10 +54,16 @@ export default function EditOrder() {
|
||||
setForceUpdate(!forceUpdate);
|
||||
});
|
||||
|
||||
const { data: prices } = useQuery(['getCloudServerPrice', forceUpdate], () => {
|
||||
const temp = formHook.getValues();
|
||||
return getCloudServerPrice(temp);
|
||||
});
|
||||
const { data: prices } = useQuery(
|
||||
['getCloudServerPrice', forceUpdate],
|
||||
() => {
|
||||
const temp = formHook.getValues();
|
||||
return getCloudServerPrice(temp);
|
||||
},
|
||||
{
|
||||
enabled: !!formHook.getValues('virtualMachinePackageName')
|
||||
}
|
||||
);
|
||||
|
||||
const submitSuccess = async (data: EditForm) => {
|
||||
console.log(data);
|
||||
@@ -96,6 +100,23 @@ export default function EditOrder() {
|
||||
});
|
||||
}, [formHook.formState.errors, t, toast]);
|
||||
|
||||
const { openConfirm, ConfirmChild } = useConfirm({
|
||||
content: (
|
||||
<Box>
|
||||
{formHook.getValues('chargeType') === CVMChargeType.prePaid ? (
|
||||
<CostTipContent
|
||||
isMonth={formHook.getValues('chargeType') === CVMChargeType.prePaid}
|
||||
instanceType={instanceType}
|
||||
isModalTip
|
||||
prices={prices}
|
||||
/>
|
||||
) : (
|
||||
<Box>{t('Are you sure to create a cloud host?')}</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection={'column'}
|
||||
@@ -106,12 +127,14 @@ export default function EditOrder() {
|
||||
bg={'grayModern.100'}
|
||||
>
|
||||
<Header
|
||||
isMonth={formHook.getValues('chargeType') === CVMChargeType.prePaid}
|
||||
instanceType={instanceType}
|
||||
prices={prices}
|
||||
title="New Server"
|
||||
applyCb={() =>
|
||||
formHook.handleSubmit((data) => openConfirm(() => submitSuccess(data))(), submitError)()
|
||||
}
|
||||
applyCb={() => {
|
||||
formHook.handleSubmit((data) => openConfirm(() => submitSuccess(data))(), submitError)();
|
||||
// openConfirm()();
|
||||
}}
|
||||
applyBtnText="Submit"
|
||||
/>
|
||||
<Flex h={'calc(100% - 126px)'} justifyContent={'center'} borderRadius={'4px'}>
|
||||
|
||||
@@ -14,6 +14,7 @@ export type EditForm = {
|
||||
virtualMachineArch: string;
|
||||
chargeType: CVMChargeType;
|
||||
zone: string;
|
||||
period: string;
|
||||
};
|
||||
|
||||
export type StorageType = {
|
||||
@@ -152,5 +153,6 @@ export type CreateCloudServerPayload = {
|
||||
zone: string;
|
||||
virtualMachineType: string;
|
||||
virtualMachineArch: string;
|
||||
chareType: CVMChargeType;
|
||||
chargeType: CVMChargeType;
|
||||
period: number;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { addHours, format, set, startOfDay } from 'date-fns';
|
||||
import dayjs from 'dayjs';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
export const formatTime = (time: string | number | Date, format = 'YYYY-MM-DD HH:mm:ss') => {
|
||||
return dayjs(time).format(format);
|
||||
|
||||
Reference in New Issue
Block a user