Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29bc3c7722 | |||
| b8316bba33 | |||
| dddc21c686 | |||
| d10ecc9fdf | |||
| 94822d902f |
+38
-24
@@ -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)}
|
||||||
|
|||||||
+16
@@ -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;
|
||||||
|
|||||||
+16
@@ -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;
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
+16
@@ -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;
|
||||||
|
|||||||
Vendored
+5
-1
@@ -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
@@ -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'
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user