Compare commits

...

5 Commits

Author SHA1 Message Date
semantic-release-bot 29bc3c7722 🔖 chore(release): v1.0.0-beta.29 [skip ci]
# [1.0.0-beta.29](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.28...v1.0.0-beta.29) (2025-03-14)

###  Features

* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
* **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
2025-03-14 06:43:47 +00:00
web@ppanel b8316bba33 feat(loading): Replace loading animation with a simpler spinner and loading text 2025-03-14 13:40:56 +07:00
web@ppanel dddc21c686 feat(api): Add CheckoutOrder request and response types, and update user purchase request parameters 2025-03-14 13:34:36 +07:00
semantic-release-bot d10ecc9fdf 🔖 chore(release): v1.0.0-beta.28 [skip ci]
# [1.0.0-beta.28](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.27...v1.0.0-beta.28) (2025-03-13)

###  Features

* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
2025-03-13 10:35:19 +00:00
web@ppanel 94822d902f feat(input): Add minimum value constraint and enhance number handling in EnhancedInput 2025-03-13 17:32:36 +07:00
17 changed files with 207 additions and 93 deletions
+38 -24
View File
@@ -1,41 +1,55 @@
<a name="readme-top"></a> <a name="readme-top"></a>
# Changelog # Changelog
# [1.0.0-beta.27](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.26...v1.0.0-beta.27) (2025-03-13) # [1.0.0-beta.29](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.28...v1.0.0-beta.29) (2025-03-14)
### ♻ Code Refactoring
* **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
* Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
### ✨ Features ### ✨ Features
* **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733)) * **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
* **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57)) * **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
* **subscription**: Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
* **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
<a name="readme-top"></a>
# Changelog
# [1.0.0-beta.28](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.27...v1.0.0-beta.28) (2025-03-13)
### ✨ Features
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
# [1.0.0-beta.27](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.26...v1.0.0-beta.27) (2025-03-13)
### ♻ Code Refactoring
- **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
- Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
### ✨ Features
- **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
- **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
- **subscription**: Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
- **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
### 🎫 Chores ### 🎫 Chores
* **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016)) - **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
### 🐛 Bug Fixes ### 🐛 Bug Fixes
* **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9)) - **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
* **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0)) - **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
* **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1)) - **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
* **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8)) - **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
* **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079)) - **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
* **payment**: Update checkout type from 'link' to 'url' for consistency ([136a1ab](https://github.com/perfect-panel/ppanel-web/commit/136a1ab)) - **payment**: Update checkout type from 'link' to 'url' for consistency ([136a1ab](https://github.com/perfect-panel/ppanel-web/commit/136a1ab))
* **payment**: Update payment information ([70d6a38](https://github.com/perfect-panel/ppanel-web/commit/70d6a38)) - **payment**: Update payment information ([70d6a38](https://github.com/perfect-panel/ppanel-web/commit/70d6a38))
* **payment**: Update payment method update logic to include row data ([6752420](https://github.com/perfect-panel/ppanel-web/commit/6752420)) - **payment**: Update payment method update logic to include row data ([6752420](https://github.com/perfect-panel/ppanel-web/commit/6752420))
* **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49)) - **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
* **ui**: Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab ([fc0da76](https://github.com/perfect-panel/ppanel-web/commit/fc0da76)) - **ui**: Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab ([fc0da76](https://github.com/perfect-panel/ppanel-web/commit/fc0da76))
* **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3)) - **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
<a name="readme-top"></a> <a name="readme-top"></a>
@@ -113,6 +113,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='number' type='number'
min={0}
value={field.value} value={field.value}
prefix={currency?.currency_symbol ?? '$'} prefix={currency?.currency_symbol ?? '$'}
formatInput={(value) => unitConversion('centsToDollars', value)} formatInput={(value) => unitConversion('centsToDollars', value)}
@@ -136,6 +137,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='number' type='number'
min={0}
value={field.value} value={field.value}
prefix={currency?.currency_symbol ?? '$'} prefix={currency?.currency_symbol ?? '$'}
formatInput={(value) => unitConversion('centsToDollars', value)} formatInput={(value) => unitConversion('centsToDollars', value)}
@@ -159,6 +161,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
<FormControl> <FormControl>
<EnhancedInput <EnhancedInput
type='number' type='number'
min={0}
value={field.value} value={field.value}
prefix={currency?.currency_symbol ?? '$'} prefix={currency?.currency_symbol ?? '$'}
formatInput={(value) => unitConversion('centsToDollars', value)} formatInput={(value) => unitConversion('centsToDollars', value)}
+1 -1
View File
@@ -1,5 +1,5 @@
// @ts-ignore // @ts-ignore
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as ads from './ads'; import * as ads from './ads';
+16
View File
@@ -116,6 +116,17 @@ declare namespace API {
ids: number[]; ids: number[];
}; };
type CheckoutOrderRequest = {
orderNo: string;
returnUrl?: string;
};
type CheckoutOrderResponse = {
type: string;
checkout_url?: string;
stripe?: StripePayment;
};
type CloseOrderRequest = { type CloseOrderRequest = {
orderNo: string; orderNo: string;
}; };
@@ -1097,6 +1108,11 @@ declare namespace API {
total: number; total: number;
}; };
type QueryUserAffiliateListRequest = {
page: number;
size: number;
};
type QueryUserAffiliateListResponse = { type QueryUserAffiliateListResponse = {
list: UserAffiliate[]; list: UserAffiliate[];
total: number; total: number;
+1 -1
View File
@@ -1,5 +1,5 @@
// @ts-ignore // @ts-ignore
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as auth from './auth'; import * as auth from './auth';
+16
View File
@@ -94,6 +94,17 @@ declare namespace API {
enabled: boolean; enabled: boolean;
}; };
type CheckoutOrderRequest = {
orderNo: string;
returnUrl?: string;
};
type CheckoutOrderResponse = {
type: string;
checkout_url?: string;
stripe?: StripePayment;
};
type CheckUserParams = { type CheckUserParams = {
email: string; email: string;
}; };
@@ -496,6 +507,11 @@ declare namespace API {
total: number; total: number;
}; };
type QueryUserAffiliateListRequest = {
page: number;
size: number;
};
type QueryUserAffiliateListResponse = { type QueryUserAffiliateListResponse = {
list: UserAffiliate[]; list: UserAffiliate[];
total: number; total: number;
+9 -6
View File
@@ -27,7 +27,7 @@ export default function Content({ subscription }: { subscription?: API.Subscribe
subscribe_id: 0, subscribe_id: 0,
payment: -1, payment: -1,
coupon: '', coupon: '',
platform: 'email', auth_type: 'email',
identifier: '', identifier: '',
password: '', password: '',
}); });
@@ -70,12 +70,15 @@ export default function Content({ subscription }: { subscription?: API.Subscribe
startTransition(async () => { startTransition(async () => {
try { try {
const { data } = await purchase(params); const { data } = await purchase(params);
console.log(data); const { order_no } = data.data!;
const { order_no, check_url, type } = data.data!;
if (order_no) { if (order_no) {
if (type === 'link') { localStorage.setItem(
window.location.href = check_url!; order_no,
} JSON.stringify({
auth_type: params.auth_type,
identifier: params.identifier,
}),
);
router.push(`/purchasing/order?order_no=${order_no}`); router.push(`/purchasing/order?order_no=${order_no}`);
} }
} catch (error) { } catch (error) {
@@ -37,7 +37,10 @@ export default function Page() {
enabled: enabled, enabled: enabled,
queryKey: ['queryPurchaseOrder', orderNo], queryKey: ['queryPurchaseOrder', orderNo],
queryFn: async () => { queryFn: async () => {
const { data } = await queryPurchaseOrder({ order_no: orderNo! }); if (!orderNo) return;
const params = localStorage.getItem(orderNo);
const authParams = params ? JSON.parse(params) : {};
const { data } = await queryPurchaseOrder({ order_no: orderNo!, ...authParams });
if (data?.data?.status !== 1) { if (data?.data?.status !== 1) {
setEnabled(false); setEnabled(false);
if (data?.data?.token) { if (data?.data?.token) {
+4 -15
View File
@@ -11,21 +11,10 @@ export default function Loading({ loading = true }: LoadingProps) {
: 'pointer-events-none bg-transparent opacity-0 backdrop-blur-0' : 'pointer-events-none bg-transparent opacity-0 backdrop-blur-0'
}`} }`}
> >
<div <div className='flex min-h-screen items-center justify-center'>
className={`transform-gpu transition-all duration-500 ${ <div className='text-center'>
loading ? 'scale-100 opacity-100' : 'scale-50 opacity-0' <div className='border-primary mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-t-2'></div>
}`} <p className='text-muted-foreground text-lg'>Loading...</p>
>
<div className='relative h-32 w-32 animate-[spin_3s_linear_infinite] overflow-hidden rounded-full shadow-[0_0_15px_rgba(0,0,0,0.1)] dark:shadow-[0_0_15px_rgba(255,255,255,0.1)]'>
<div className='absolute inset-0 h-full w-full rounded-full bg-black shadow-inner dark:bg-white'>
<div className='absolute right-0 top-0 h-full w-1/2 rounded-r-full bg-white shadow-sm dark:bg-black' />
<div className='absolute left-1/2 top-0 h-1/2 w-1/2 -translate-x-1/2 rounded-full bg-white shadow-sm dark:bg-black'>
<div className='absolute left-1/2 top-1/2 h-1/4 w-1/4 -translate-x-1/2 -translate-y-1/2 rounded-full bg-black shadow-inner dark:bg-white' />
</div>
<div className='absolute bottom-0 left-1/2 h-1/2 w-1/2 -translate-x-1/2 rounded-full bg-black shadow-sm dark:bg-white'>
<div className='absolute left-1/2 top-1/2 h-1/4 w-1/4 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white shadow-inner dark:bg-black' />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -22,10 +22,11 @@ const PaymentMethods: React.FC<PaymentMethodsProps> = ({ value, onChange, balanc
queryKey: ['getAvailablePaymentMethods', { balance }], queryKey: ['getAvailablePaymentMethods', { balance }],
queryFn: async () => { queryFn: async () => {
const { data } = await getAvailablePaymentMethods(); const { data } = await getAvailablePaymentMethods();
const methods = data.data?.list || []; const list = data.data?.list || [];
if (!value && methods[0]?.id) onChange(methods[0]?.id); const methods = balance ? list : list.filter((item) => item.id !== -1);
if (balance) return methods; const defaultMethod = methods.find((item) => item.id)?.id;
return methods.filter((item) => item.id !== -1); if (defaultMethod) onChange(defaultMethod);
return methods;
}, },
}); });
return ( return (
+1 -1
View File
@@ -1,5 +1,5 @@
// @ts-ignore // @ts-ignore
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as auth from './auth'; import * as auth from './auth';
+16
View File
@@ -94,6 +94,17 @@ declare namespace API {
enabled: boolean; enabled: boolean;
}; };
type CheckoutOrderRequest = {
orderNo: string;
returnUrl?: string;
};
type CheckoutOrderResponse = {
type: string;
checkout_url?: string;
stripe?: StripePayment;
};
type CheckUserParams = { type CheckUserParams = {
email: string; email: string;
}; };
@@ -496,6 +507,11 @@ declare namespace API {
total: number; total: number;
}; };
type QueryUserAffiliateListRequest = {
page: number;
size: number;
};
type QueryUserAffiliateListResponse = { type QueryUserAffiliateListResponse = {
list: UserAffiliate[]; list: UserAffiliate[];
total: number; total: number;
+1 -1
View File
@@ -1,5 +1,5 @@
// @ts-ignore // @ts-ignore
// API 更新时间: // API 更新时间:
// API 唯一标识: // API 唯一标识:
import * as announcement from './announcement'; import * as announcement from './announcement';
+5 -1
View File
@@ -438,8 +438,8 @@ declare namespace API {
}; };
type PortalPurchaseRequest = { type PortalPurchaseRequest = {
auth_type: string;
identifier: string; identifier: string;
platform: string;
password?: string; password?: string;
payment: number; payment: number;
subscribe_id: number; subscribe_id: number;
@@ -572,10 +572,14 @@ declare namespace API {
}; };
type QueryPurchaseOrderParams = { type QueryPurchaseOrderParams = {
auth_type: string;
identifier: string;
order_no: string; order_no: string;
}; };
type QueryPurchaseOrderRequest = { type QueryPurchaseOrderRequest = {
auth_type: string;
identifier: string;
order_no: string; order_no: string;
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ppanel-web", "name": "ppanel-web",
"version": "1.0.0-beta.27", "version": "1.0.0-beta.29",
"private": true, "private": true,
"homepage": "https://github.com/perfect-panel/ppanel-web", "homepage": "https://github.com/perfect-panel/ppanel-web",
"bugs": { "bugs": {
@@ -6,7 +6,7 @@ export interface EnhancedInputProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> { extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
prefix?: string | ReactNode; prefix?: string | ReactNode;
suffix?: string | ReactNode; suffix?: string | ReactNode;
formatInput?: (value: string | number) => string; formatInput?: (value: string | number) => string | number;
formatOutput?: (value: string | number) => string | number; formatOutput?: (value: string | number) => string | number;
onValueChange?: (value: string | number) => void; onValueChange?: (value: string | number) => void;
onValueBlur?: (value: string | number) => void; onValueBlur?: (value: string | number) => void;
@@ -26,50 +26,94 @@ export function EnhancedInput({
...props ...props
}: EnhancedInputProps) { }: EnhancedInputProps) {
const getProcessedValue = (inputValue: unknown) => { const getProcessedValue = (inputValue: unknown) => {
const newValue = inputValue === '' || inputValue === 0 ? '' : String(inputValue ?? ''); if (inputValue === '' || inputValue === 0 || inputValue === '0') return '';
const newValue = String(inputValue ?? '');
return formatInput ? formatInput(newValue) : newValue; return formatInput ? formatInput(newValue) : newValue;
}; };
const [value, setValue] = useState<string>(() => getProcessedValue(initialValue)); const [value, setValue] = useState<string | number>(() => getProcessedValue(initialValue));
// @ts-expect-error - This is a controlled component
const [internalValue, setInternalValue] = useState<string | number>(initialValue ?? '');
useEffect(() => { useEffect(() => {
if (initialValue !== value) { if (initialValue !== internalValue) {
const newValue = getProcessedValue(initialValue); const newValue = getProcessedValue(initialValue);
if (value !== newValue) setValue(newValue); if (value !== newValue) {
setValue(newValue);
// @ts-expect-error - This is a controlled component
setInternalValue(initialValue ?? '');
}
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialValue, formatInput]); }, [initialValue, formatInput]);
const processValue = (inputValue: string) => { const processValue = (inputValue: string | number) => {
let processedValue: number | string = inputValue?.toString().trim(); let processedValue: number | string = inputValue?.toString().trim();
if (processedValue === '0' && props.type === 'number') {
return 0;
}
if (processedValue && props.type === 'number') processedValue = Number(processedValue); if (processedValue && props.type === 'number') processedValue = Number(processedValue);
return formatOutput ? formatOutput(processedValue) : processedValue; return formatOutput ? formatOutput(processedValue) : processedValue;
}; };
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
let inputValue = e.target.value; let inputValue = e.target.value;
if (props.type === 'number' && inputValue) {
const numericValue = Number(inputValue); if (props.type === 'number') {
if (!isNaN(numericValue)) { if (inputValue === '0') {
const min = Number.isFinite(props.min) ? props.min : -Infinity; setValue('');
const max = Number.isFinite(props.max) ? props.max : Infinity; setInternalValue(0);
inputValue = String(Math.max(min!, Math.min(max!, numericValue))); onValueChange?.(0);
return;
}
if (/^-?\d*\.?\d*$/.test(inputValue) || inputValue === '-' || inputValue === '.') {
const numericValue = Number(inputValue);
if (!isNaN(numericValue) && inputValue !== '-' && inputValue !== '.') {
const min = Number.isFinite(props.min) ? props.min : -Infinity;
const max = Number.isFinite(props.max) ? props.max : Infinity;
const constrainedValue = Math.max(min!, Math.min(max!, numericValue));
inputValue = String(constrainedValue);
setInternalValue(constrainedValue);
} else {
setInternalValue(inputValue);
}
setValue(inputValue === '0' ? '' : inputValue);
} }
setValue(inputValue === '0' ? '' : inputValue);
} else { } else {
setValue(inputValue); setValue(inputValue);
setInternalValue(inputValue);
} }
const outputValue = processValue(inputValue); const outputValue = processValue(inputValue);
console.log();
onValueChange?.(outputValue); onValueChange?.(outputValue);
}; };
const handleBlur = () => { const handleBlur = () => {
if (props.type === 'number' && value) {
if (value === '-' || value === '.') {
setValue('');
setInternalValue('');
onValueBlur?.('');
return;
}
// 确保0值显示为空
if (value === '0') {
setValue('');
onValueBlur?.(0);
return;
}
}
const outputValue = processValue(value); const outputValue = processValue(value);
if ((initialValue || '') !== outputValue) { if ((initialValue || '') !== outputValue) {
onValueBlur?.(outputValue); onValueBlur?.(outputValue);
} }
}; };
const renderPrefix = () => { const renderPrefix = () => {
return typeof prefix === 'string' ? ( return typeof prefix === 'string' ? (
<div className='bg-muted relative mr-px flex h-9 items-center text-nowrap px-3'>{prefix}</div> <div className='bg-muted relative mr-px flex h-9 items-center text-nowrap px-3'>{prefix}</div>
@@ -77,6 +121,7 @@ export function EnhancedInput({
prefix prefix
); );
}; };
const renderSuffix = () => { const renderSuffix = () => {
return typeof suffix === 'string' ? ( return typeof suffix === 'string' ? (
<div className='bg-muted relative ml-px flex h-9 items-center text-nowrap px-3'>{suffix}</div> <div className='bg-muted relative ml-px flex h-9 items-center text-nowrap px-3'>{suffix}</div>
@@ -95,6 +140,7 @@ export function EnhancedInput({
> >
{renderPrefix()} {renderPrefix()}
<Input <Input
step={0.01}
{...props} {...props}
value={value} value={value}
className='block rounded-none border-none' className='block rounded-none border-none'
+26 -23
View File
@@ -1,32 +1,35 @@
import { evaluate, format } from 'mathjs'; import { evaluate, format } from 'mathjs';
export function unitConversion( type ConversionType =
type: 'centsToDollars' | 'dollarsToCents' | 'bitsToMb' | 'mbToBits' | 'bytesToGb' | 'gbToBytes', | 'centsToDollars'
value?: number | string, | 'dollarsToCents'
) { | 'bitsToMb'
if (!value) return; | 'mbToBits'
switch (type) { | 'bytesToGb'
case 'centsToDollars': | 'gbToBytes';
return evaluate(`${value} / 100`);
case 'dollarsToCents': const conversionConfig: Record<ConversionType, { formula: string; precision: number }> = {
return evaluate(`${value} * 100`); centsToDollars: { formula: 'value / 100', precision: 2 },
case 'bitsToMb': dollarsToCents: { formula: 'value * 100', precision: 0 },
return evaluate(`${value} / 1024 / 1024`); bitsToMb: { formula: 'value / 1024 / 1024', precision: 2 },
case 'mbToBits': mbToBits: { formula: 'value * 1024 * 1024', precision: 0 },
return evaluate(`${value} * 1024 * 1024`); bytesToGb: { formula: 'value / 1024 / 1024 / 1024', precision: 2 },
case 'bytesToGb': gbToBytes: { formula: 'value * 1024 * 1024 * 1024', precision: 0 },
return evaluate(`${value} / 1024 / 1024 / 1024`); };
case 'gbToBytes':
return evaluate(`${value} * 1024 * 1024 * 1024`); export function unitConversion(type: ConversionType, value?: number | string) {
default: if (!value) return 0;
throw new Error('Invalid conversion type');
} const config = conversionConfig[type];
if (!config) throw new Error('Invalid conversion type');
const formula = config.formula.replace('value', `${value}`);
const result = evaluate(formula);
return Number(format(result, { notation: 'fixed', precision: config.precision }));
} }
export function evaluateWithPrecision(expression: string) { export function evaluateWithPrecision(expression: string) {
const result = evaluate(expression); const result = evaluate(expression);
const formatted = format(result, { notation: 'fixed', precision: 2 }); const formatted = format(result, { notation: 'fixed', precision: 2 });
return Number(formatted); return Number(formatted);
} }