Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db0d9e003e | |||
| f219c52306 | |||
| 3f7b6d16ed | |||
| 6dbebd152d | |||
| bb3bd7b50c | |||
| fbad3b0e51 | |||
| e551232564 | |||
| bf58f25072 | |||
| 85f55def2e | |||
| 29bc3c7722 | |||
| f72df3a5e8 | |||
| 48a1b97051 | |||
| 786ba0e41c | |||
| ce3197298d | |||
| 092477b2d3 | |||
| b8316bba33 | |||
| dddc21c686 | |||
| d10ecc9fdf | |||
| 94822d902f |
+109
-23
@@ -1,41 +1,127 @@
|
||||
<a name="readme-top"></a>
|
||||
# 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)
|
||||
|
||||
|
||||
### ♻ 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))
|
||||
# [1.0.0-beta.30](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.29...v1.0.0-beta.30) (2025-03-15)
|
||||
|
||||
|
||||
### ✨ 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))
|
||||
* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
|
||||
* **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
|
||||
* **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
|
||||
* **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
|
||||
* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
|
||||
* **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
|
||||
* **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
|
||||
* **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
|
||||
* **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))
|
||||
|
||||
|
||||
### 🎫 Chores
|
||||
|
||||
* **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
|
||||
* **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
|
||||
* **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
|
||||
* Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **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))
|
||||
* **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**: 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 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))
|
||||
* **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**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
|
||||
* **dashboard**: Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [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))
|
||||
|
||||
<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
|
||||
|
||||
- **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **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))
|
||||
- **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**: 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 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))
|
||||
- **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**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
|
||||
|
||||
<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
|
||||
|
||||
- **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **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))
|
||||
- **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**: 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 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))
|
||||
- **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**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
|
||||
@@ -286,31 +286,34 @@ export default function Page() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='template'>
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
||||
{['verify_email_template', 'expiration_email_template', 'maintenance_email_template'].map(
|
||||
(templateKey) => (
|
||||
<Card key={templateKey}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
||||
<CardDescription>
|
||||
{t(`${templateKey}Description`, { after: '{{', before: '}}' })}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HTMLEditor
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config?.[templateKey] as string}
|
||||
onBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
[templateKey]: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
)}
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
|
||||
{[
|
||||
'verify_email_template',
|
||||
'expiration_email_template',
|
||||
'maintenance_email_template',
|
||||
'traffic_exceed_email_template',
|
||||
].map((templateKey) => (
|
||||
<Card key={templateKey}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
||||
<CardDescription>
|
||||
{t(`${templateKey}Description`, { after: '{{', before: '}}' })}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HTMLEditor
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config?.[templateKey] as string}
|
||||
onBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
[templateKey]: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ interface PaymentFormProps<T> {
|
||||
loading?: boolean;
|
||||
initialValues?: T;
|
||||
onSubmit: (values: T) => Promise<boolean>;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
|
||||
export default function PaymentForm<T>({
|
||||
@@ -53,6 +54,7 @@ export default function PaymentForm<T>({
|
||||
loading,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
isEdit,
|
||||
}: PaymentFormProps<T>) {
|
||||
const t = useTranslations('payment');
|
||||
const { common } = useGlobalStore();
|
||||
@@ -317,6 +319,7 @@ export default function PaymentForm<T>({
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
disabled={isEdit}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
|
||||
@@ -136,6 +136,7 @@ export default function PaymentTable() {
|
||||
actions={{
|
||||
render: (row) => [
|
||||
<PaymentForm<API.UpdatePaymentMethodRequest>
|
||||
isEdit
|
||||
key='edit'
|
||||
trigger={<Button>{t('edit')}</Button>}
|
||||
title={t('editPayment')}
|
||||
|
||||
@@ -362,9 +362,10 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
placeholder='1-65535'
|
||||
step={1}
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder='1-65535'
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
@@ -418,6 +419,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
step={1}
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder='1-65535'
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -899,6 +903,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
{
|
||||
name: 'port',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
min: 1,
|
||||
max: 65535,
|
||||
placeholder: t('form.relayPort'),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { Textarea } from '@workspace/ui/components/textarea';
|
||||
import { JSONEditor } from '@workspace/ui/custom-components/editor';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { UploadImage } from '@workspace/ui/custom-components/upload-image';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@@ -130,6 +131,49 @@ export default function Site() {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('customData')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('customDataDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='w-1/2 text-right'>
|
||||
<JSONEditor
|
||||
schema={{
|
||||
type: 'object',
|
||||
additionalProperties: true,
|
||||
properties: {
|
||||
website: { type: 'string', title: 'Website' },
|
||||
contacts: {
|
||||
type: 'object',
|
||||
title: 'Contacts',
|
||||
additionalProperties: true,
|
||||
properties: {
|
||||
email: { type: 'string', title: 'Email' },
|
||||
telephone: { type: 'string', title: 'Telephone' },
|
||||
address: { type: 'string', title: 'Address' },
|
||||
},
|
||||
},
|
||||
community: {
|
||||
type: 'object',
|
||||
title: 'Community',
|
||||
additionalProperties: true,
|
||||
properties: {
|
||||
telegram: { type: 'string', title: 'Telegram' },
|
||||
twitter: { type: 'string', title: 'Twitter' },
|
||||
discord: { type: 'string', title: 'Discord' },
|
||||
instagram: { type: 'string', title: 'Instagram' },
|
||||
linkedin: { type: 'string', title: 'Linkedin' },
|
||||
facebook: { type: 'string', title: 'Facebook' },
|
||||
github: { type: 'string', title: 'Github' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
value={data?.custom_data}
|
||||
onBlur={(value) => updateConfig('custom_data', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -113,6 +113,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='number'
|
||||
min={0}
|
||||
value={field.value}
|
||||
prefix={currency?.currency_symbol ?? '$'}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
@@ -136,6 +137,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='number'
|
||||
min={0}
|
||||
value={field.value}
|
||||
prefix={currency?.currency_symbol ?? '$'}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
@@ -159,6 +161,7 @@ export function BasicInfoForm({ user, refetch }: { user: API.User; refetch: () =
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='number'
|
||||
min={0}
|
||||
value={field.value}
|
||||
prefix={currency?.currency_symbol ?? '$'}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
|
||||
@@ -58,8 +58,21 @@ export default function Page() {
|
||||
<Switch
|
||||
defaultChecked={row.getValue('enable')}
|
||||
onCheckedChange={async (checked) => {
|
||||
const {
|
||||
auth_methods,
|
||||
user_devices,
|
||||
enable_balance_notify,
|
||||
enable_login_notify,
|
||||
enable_subscribe_notify,
|
||||
enable_trade_notify,
|
||||
updated_at,
|
||||
created_at,
|
||||
id,
|
||||
...rest
|
||||
} = row.original;
|
||||
await updateUserBasicInfo({
|
||||
...row.original,
|
||||
user_id: id,
|
||||
...rest,
|
||||
enable: checked,
|
||||
} as unknown as API.UpdateUserBasiceInfoRequest);
|
||||
toast.success(t('updateSuccess'));
|
||||
|
||||
@@ -16,6 +16,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
||||
site_logo: '',
|
||||
keywords: '',
|
||||
custom_html: '',
|
||||
custom_data: '',
|
||||
},
|
||||
verify: {
|
||||
turnstile_site_key: '',
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Stav",
|
||||
"subject": "Předmět",
|
||||
"template": "Šablona",
|
||||
"traffic_exceed_email_template": "Šablona oznámení o překročení provozu",
|
||||
"traffic_exceed_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
||||
"verify_email_template": "Šablona ověřovacího e-mailu",
|
||||
"verify_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
||||
"whitelistSuffixes": "Povolené přípony",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Zásady ochrany osobních údajů"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Vlastní data",
|
||||
"customDataDescription": "Vlastní data, používaná pro vlastní data webu",
|
||||
"customHtml": "Vlastní HTML",
|
||||
"customHtmlDescription": "Vlastní HTML kód, který bude vložen na konec tagu body na stránce.",
|
||||
"keywords": "Klíčová slova",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Betreff",
|
||||
"template": "Vorlage",
|
||||
"traffic_exceed_email_template": "Benachrichtigung über überschreitenden Verkehr",
|
||||
"traffic_exceed_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass Sie diese Variablen beibehalten.",
|
||||
"verify_email_template": "E-Mail-Bestätigungsvorlage",
|
||||
"verify_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass diese Variablen beibehalten werden.",
|
||||
"whitelistSuffixes": "Whitelist-Suffixe",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Datenschutzrichtlinie"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Benutzerdefinierte Daten",
|
||||
"customDataDescription": "Benutzerdefinierte Daten, die für benutzerdefinierte Daten der Website verwendet werden",
|
||||
"customHtml": "Benutzerdefiniertes HTML",
|
||||
"customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingefügt werden soll.",
|
||||
"keywords": "Schlüsselwörter",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Subject",
|
||||
"template": "Template",
|
||||
"traffic_exceed_email_template": "Traffic Exceed Notice Template",
|
||||
"traffic_exceed_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||
"verify_email_template": "Verification Email Template",
|
||||
"verify_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||
"whitelistSuffixes": "Whitelist Suffixes",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Privacy Policy"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Custom Data",
|
||||
"customDataDescription": "Custom Data, used for custom data of the site",
|
||||
"customHtml": "Custom HTML",
|
||||
"customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.",
|
||||
"keywords": "Keywords",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Estado",
|
||||
"subject": "Asunto",
|
||||
"template": "Plantilla",
|
||||
"traffic_exceed_email_template": "Plantilla de Notificación de Exceso de Tráfico",
|
||||
"traffic_exceed_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
||||
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrese de mantener estas variables.",
|
||||
"whitelistSuffixes": "Sufijos de la lista blanca",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Política de Privacidad"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Datos Personalizados",
|
||||
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||
"customHtml": "HTML personalizado",
|
||||
"customHtmlDescription": "Código HTML personalizado que se inyectará en la parte inferior de la etiqueta body del sitio.",
|
||||
"keywords": "Palabras clave",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Estado",
|
||||
"subject": "Asunto",
|
||||
"template": "Plantilla",
|
||||
"traffic_exceed_email_template": "Plantilla de Notificación de Exceso de Tráfico",
|
||||
"traffic_exceed_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
||||
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||
"whitelistSuffixes": "Sufijos de Lista Blanca",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Política de Privacidad"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Datos Personalizados",
|
||||
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||
"customHtml": "HTML personalizado",
|
||||
"customHtmlDescription": "Código HTML personalizado que se inyectará al final de la etiqueta body del sitio.",
|
||||
"keywords": "Palabras clave",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "وضعیت",
|
||||
"subject": "موضوع",
|
||||
"template": "الگو",
|
||||
"traffic_exceed_email_template": "الگوی اعلان ترافیک بیش از حد",
|
||||
"traffic_exceed_email_templateDescription": "جایگزینهای {after}.variable{before} با دادههای واقعی خواهد شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
||||
"verify_email_template": "قالب ایمیل تأیید",
|
||||
"verify_email_templateDescription": "جایگذاریهای {after}.variable{before} با دادههای واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
||||
"whitelistSuffixes": "پسوندهای لیست سفید",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "سیاست حفظ حریم خصوصی"
|
||||
},
|
||||
"site": {
|
||||
"customData": "دادههای سفارشی",
|
||||
"customDataDescription": "دادههای سفارشی، استفاده شده برای دادههای سفارشی سایت",
|
||||
"customHtml": "HTML سفارشی",
|
||||
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.",
|
||||
"keywords": "کلمات کلیدی",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Tila",
|
||||
"subject": "Aihe",
|
||||
"template": "Malline",
|
||||
"traffic_exceed_email_template": "Liikenteen Ylitysilmoitusmalli",
|
||||
"traffic_exceed_email_templateDescription": "{after}.variable{before} -paikat korvataan todellisilla tiedoilla. Varmista, että säilytät nämä muuttujat.",
|
||||
"verify_email_template": "Sähköpostin Vahvistusmalli",
|
||||
"verify_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.",
|
||||
"whitelistSuffixes": "Sallittujen luetteloiden päätteet",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Tietosuojakäytäntö"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Mukautettu tieto",
|
||||
"customDataDescription": "Mukautettu tieto, jota käytetään sivuston mukautettuun tietoon",
|
||||
"customHtml": "Mukautettu HTML",
|
||||
"customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston body -tagin loppuun.",
|
||||
"keywords": "Avainsanat",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Statut",
|
||||
"subject": "Sujet",
|
||||
"template": "Modèle",
|
||||
"traffic_exceed_email_template": "Modèle de notification de dépassement de trafic",
|
||||
"traffic_exceed_email_templateDescription": "Les espaces réservés {after}.variable{before} seront remplacés par des données réelles. Assurez-vous de conserver ces variables.",
|
||||
"verify_email_template": "Modèle d'Email de Vérification",
|
||||
"verify_email_templateDescription": "Les espaces réservés {after}.variable{before} seront remplacés par des données réelles. Assurez-vous de conserver ces variables.",
|
||||
"whitelistSuffixes": "Suffixes de la liste blanche",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Politique de confidentialité"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Données personnalisées",
|
||||
"customDataDescription": "Données personnalisées, utilisées pour les données personnalisées du site",
|
||||
"customHtml": "HTML personnalisé",
|
||||
"customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise body du site.",
|
||||
"keywords": "Mots-clés",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "स्थिति",
|
||||
"subject": "विषय",
|
||||
"template": "टेम्पलेट",
|
||||
"traffic_exceed_email_template": "ट्रैफिक अधिकता सूचना टेम्पलेट",
|
||||
"traffic_exceed_email_templateDescription": "{after}.variable{before} प्लेसहोल्डर्स को वास्तविक डेटा के साथ प्रतिस्थापित किया जाएगा। सुनिश्चित करें कि इन वेरिएबल्स को बनाए रखें।",
|
||||
"verify_email_template": "सत्यापन ईमेल टेम्पलेट",
|
||||
"verify_email_templateDescription": "{after}.variable{before} प्लेसहोल्डर्स को वास्तविक डेटा से बदला जाएगा। सुनिश्चित करें कि इन वेरिएबल्स को बनाए रखें।",
|
||||
"whitelistSuffixes": "श्वेतसूची प्रत्यय",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "गोपनीयता नीति"
|
||||
},
|
||||
"site": {
|
||||
"customData": "कस्टम डेटा",
|
||||
"customDataDescription": "कस्टम डेटा, साइट के कस्टम डेटा के लिए उपयोग किया जाता है",
|
||||
"customHtml": "कस्टम HTML",
|
||||
"customHtmlDescription": "साइट के bodyैग के नीचे इंजेक्ट करने के लिए कस्टम HTML कोड।",
|
||||
"keywords": "कीवर्ड",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Állapot",
|
||||
"subject": "Tárgy",
|
||||
"template": "Sablon",
|
||||
"traffic_exceed_email_template": "Forgalmi Túllépés Értesítő Sablon",
|
||||
"traffic_exceed_email_templateDescription": "A {after}.variable{before} helyettesítők tényleges adatokkal lesznek helyettesítve. Győződjön meg róla, hogy ezeket a változókat megőrzi.",
|
||||
"verify_email_template": "Ellenőrző Email Sablon",
|
||||
"verify_email_templateDescription": "A {after}.variable{before} helyőrzőket tényleges adatokkal fogjuk helyettesíteni. Ügyeljen arra, hogy ezeket a változókat megtartsa.",
|
||||
"whitelistSuffixes": "Engedélyezett utótagok",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Adatvédelmi irányelvek"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Egyedi adatok",
|
||||
"customDataDescription": "Egyedi adatok, a webhely egyedi adatainak használatára",
|
||||
"customHtml": "Egyedi HTML",
|
||||
"customHtmlDescription": "Egyedi HTML kód, amelyet a webhely bodyímkéjének aljára kell beilleszteni.",
|
||||
"keywords": "Kulcsszavak",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "ステータス",
|
||||
"subject": "件名",
|
||||
"template": "テンプレート",
|
||||
"traffic_exceed_email_template": "トラフィック超過通知テンプレート",
|
||||
"traffic_exceed_email_templateDescription": "{after}と{before}の変数は実際のデータに置き換えられます。これらの変数を保持することを確認してください。",
|
||||
"verify_email_template": "確認メールテンプレート",
|
||||
"verify_email_templateDescription": "{after}.variable{before} のプレースホルダーは実際のデータに置き換えられます。これらの変数を保持してください。",
|
||||
"whitelistSuffixes": "ホワイトリストのサフィックス",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "プライバシーポリシー"
|
||||
},
|
||||
"site": {
|
||||
"customData": "カスタムデータ",
|
||||
"customDataDescription": "サイトのカスタムデータに使用されるカスタムデータ",
|
||||
"customHtml": "カスタムHTML",
|
||||
"customHtmlDescription": "サイトの body タグの下部に挿入されるカスタム HTML コード。",
|
||||
"keywords": "キーワード",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "상태",
|
||||
"subject": "제목",
|
||||
"template": "템플릿",
|
||||
"traffic_exceed_email_template": "트래픽 초과 알림 템플릿",
|
||||
"traffic_exceed_email_templateDescription": "{after}.variable{before} 자리표시는 실제 데이터로 대체됩니다. 이러한 변수를 유지해야 합니다.",
|
||||
"verify_email_template": "이메일 인증 템플릿",
|
||||
"verify_email_templateDescription": "{after}.variable{before} 자리표시는 실제 데이터로 대체됩니다. 이 변수를 반드시 유지하십시오.",
|
||||
"whitelistSuffixes": "허용 목록 접미사",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "개인정보 처리방침"
|
||||
},
|
||||
"site": {
|
||||
"customData": "사용자 정의 데이터",
|
||||
"customDataDescription": "사이트의 사용자 정의 데이터에 사용되는 사용자 정의 데이터",
|
||||
"customHtml": "사용자 정의 HTML",
|
||||
"customHtmlDescription": "사이트의 body그 하단에 삽입될 사용자 정의 HTML 코드입니다.",
|
||||
"keywords": "키워드",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Emne",
|
||||
"template": "Mal",
|
||||
"traffic_exceed_email_template": "Varsel om overskridelse av trafikk",
|
||||
"traffic_exceed_email_templateDescription": "Plassholderne {after}.variable{before} vil bli erstattet med faktiske data. Sørg for å beholde disse variablene.",
|
||||
"verify_email_template": "Verifikasjons-e-postmal",
|
||||
"verify_email_templateDescription": "Plassholderne {after}.variable{before} vil bli erstattet med faktiske data. Sørg for å beholde disse variablene.",
|
||||
"whitelistSuffixes": "Hvitelistesuffikser",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Personvernerklæring"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Egendata",
|
||||
"customDataDescription": "Egendata, brukt for egendata på nettstedet",
|
||||
"customHtml": "Egendefinert HTML",
|
||||
"customHtmlDescription": "Egendefinert HTML-kode som skal settes inn i bunnen av nettstedets body-tag.",
|
||||
"keywords": "Nøkkelord",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Temat",
|
||||
"template": "Szablon",
|
||||
"traffic_exceed_email_template": "Szablon powiadomienia o przekroczeniu ruchu",
|
||||
"traffic_exceed_email_templateDescription": "Miejsca {after}.variable{before} zostaną zastąpione rzeczywistymi danymi. Upewnij się, że te zmienne są zachowane.",
|
||||
"verify_email_template": "Szablon e-maila weryfikacyjnego",
|
||||
"verify_email_templateDescription": "Elementy zastępcze {after}.variable{before} zostaną zastąpione rzeczywistymi danymi. Upewnij się, że te zmienne zostaną zachowane.",
|
||||
"whitelistSuffixes": "Dozwolone Sufiksy",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Polityka prywatności"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Dane niestandardowe",
|
||||
"customDataDescription": "Dane niestandardowe, używane do niestandardowych danych witryny",
|
||||
"customHtml": "Niestandardowy HTML",
|
||||
"customHtmlDescription": "Niestandardowy kod HTML, który ma być wstrzyknięty na końcu tagu bodyitryny.",
|
||||
"keywords": "Słowa kluczowe",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Assunto",
|
||||
"template": "Modelo",
|
||||
"traffic_exceed_email_template": "Modelo de Notificação de Excesso de Tráfego",
|
||||
"traffic_exceed_email_templateDescription": "Os espaços reservados {after}.variable{before} serão substituídos por dados reais. Certifique-se de manter essas variáveis.",
|
||||
"verify_email_template": "Modelo de E-mail de Verificação",
|
||||
"verify_email_templateDescription": "Os marcadores {after}.variable{before} serão substituídos por dados reais. Certifique-se de manter essas variáveis.",
|
||||
"whitelistSuffixes": "Sufixos de Lista Branca",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Política de Privacidade"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Dados Personalizados",
|
||||
"customDataDescription": "Dados personalizados, usados para dados personalizados do site",
|
||||
"customHtml": "HTML Personalizado",
|
||||
"customHtmlDescription": "Código HTML personalizado a ser inserido no final da tag bodyo site.",
|
||||
"keywords": "Palavras-chave",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Stare",
|
||||
"subject": "Subiect",
|
||||
"template": "Șablon",
|
||||
"traffic_exceed_email_template": "Șablon de notificare a depășirii traficului",
|
||||
"traffic_exceed_email_templateDescription": "Plasamentele {after}.variable{before} vor fi înlocuite cu datele reale. Asigurați-vă că păstrați aceste variabile.",
|
||||
"verify_email_template": "Șablon de e-mail pentru verificare",
|
||||
"verify_email_templateDescription": "Locurile rezervate {after}.variable{before} vor fi înlocuite cu date reale. Asigurați-vă că păstrați aceste variabile.",
|
||||
"whitelistSuffixes": "Sufixe pe lista albă",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Politica de confidențialitate"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Date personalizate",
|
||||
"customDataDescription": "Date personalizate, utilizate pentru datele personalizate ale site-ului",
|
||||
"customHtml": "HTML personalizat",
|
||||
"customHtmlDescription": "Cod HTML personalizat care va fi injectat în partea de jos a etichetei body a site-ului.",
|
||||
"keywords": "Cuvinte cheie",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Статус",
|
||||
"subject": "Тема",
|
||||
"template": "Шаблон",
|
||||
"traffic_exceed_email_template": "Шаблон уведомления о превышении трафика",
|
||||
"traffic_exceed_email_templateDescription": "Места {after}.variable{before} будут заменены на фактические данные. Убедитесь, что эти переменные сохранены.",
|
||||
"verify_email_template": "Шаблон письма для подтверждения",
|
||||
"verify_email_templateDescription": "Заполнители {after}.variable{before} будут заменены фактическими данными. Убедитесь, что эти переменные сохранены.",
|
||||
"whitelistSuffixes": "Белый список суффиксов",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Политика конфиденциальности"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Пользовательские данные",
|
||||
"customDataDescription": "Пользовательские данные, используемые для пользовательских данных сайта",
|
||||
"customHtml": "Пользовательский HTML",
|
||||
"customHtmlDescription": "Пользовательский HTML-код, который будет вставлен в конец тега body сайта.",
|
||||
"keywords": "Ключевые слова",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "สถานะ",
|
||||
"subject": "เรื่อง",
|
||||
"template": "แม่แบบ",
|
||||
"traffic_exceed_email_template": "เทมเพลตการแจ้งเตือนการเกินการจราจร",
|
||||
"traffic_exceed_email_templateDescription": "ตัวแปร {after}.variable{before} จะถูกแทนที่ด้วยข้อมูลจริง โปรดตรวจสอบให้แน่ใจว่าได้เก็บตัวแปรเหล่านี้ไว้",
|
||||
"verify_email_template": "เทมเพลตอีเมลยืนยัน",
|
||||
"verify_email_templateDescription": "ตัวแปร {after}.variable{before} จะถูกแทนที่ด้วยข้อมูลจริง โปรดตรวจสอบให้แน่ใจว่าเก็บตัวแปรเหล่านี้ไว้",
|
||||
"whitelistSuffixes": "อนุญาตให้ใช้คำต่อท้าย",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "นโยบายความเป็นส่วนตัว"
|
||||
},
|
||||
"site": {
|
||||
"customData": "ข้อมูลที่กำหนดเอง",
|
||||
"customDataDescription": "ข้อมูลที่กำหนดเอง ใช้สำหรับข้อมูลที่กำหนดเองของเว็บไซต์",
|
||||
"customHtml": "HTML ที่กำหนดเอง",
|
||||
"customHtmlDescription": "รหัส HTML ที่กำหนดเองเพื่อแทรกลงในส่วนล่างของแท็ก body ของเว็บไซต์.",
|
||||
"keywords": "คำสำคัญ",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Durum",
|
||||
"subject": "Konu",
|
||||
"template": "Şablon",
|
||||
"traffic_exceed_email_template": "Trafik Aşım Bildirimi Şablonu",
|
||||
"traffic_exceed_email_templateDescription": "{after}.variable{before} yer tutucuları gerçek verilerle değiştirilecektir. Bu değişkenleri koruduğunuzdan emin olun.",
|
||||
"verify_email_template": "Doğrulama E-posta Şablonu",
|
||||
"verify_email_templateDescription": "{after}.variable{before} yer tutucuları gerçek verilerle değiştirilecektir. Bu değişkenleri koruduğunuzdan emin olun.",
|
||||
"whitelistSuffixes": "Beyaz Liste Sonekleri",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Gizlilik Politikası"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Özel Veri",
|
||||
"customDataDescription": "Özel Veri, sitenin özel verileri için kullanılır",
|
||||
"customHtml": "Özel HTML",
|
||||
"customHtmlDescription": "Site'in body etiketinin altına yerleştirilecek özel HTML kodu.",
|
||||
"keywords": "Anahtar Kelimeler",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Статус",
|
||||
"subject": "Тема",
|
||||
"template": "Шаблон",
|
||||
"traffic_exceed_email_template": "Шаблон сповіщення про перевищення трафіку",
|
||||
"traffic_exceed_email_templateDescription": "Місця для змінних {after}.variable{before} будуть замінені на фактичні дані. Переконайтеся, що ці змінні залишаються.",
|
||||
"verify_email_template": "Шаблон електронного листа для підтвердження",
|
||||
"verify_email_templateDescription": "Заповнювачі {after}.variable{before} будуть замінені на фактичні дані. Переконайтеся, що зберегли ці змінні.",
|
||||
"whitelistSuffixes": "Білий список суфіксів",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Політика конфіденційності"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Користувацькі дані",
|
||||
"customDataDescription": "Користувацькі дані, що використовуються для користувацьких даних сайту",
|
||||
"customHtml": "Користувацький HTML",
|
||||
"customHtmlDescription": "Користувацький HTML-код, який буде вставлено в кінець тегу body сайту.",
|
||||
"keywords": "Ключові слова",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Trạng thái",
|
||||
"subject": "Chủ đề",
|
||||
"template": "Mẫu",
|
||||
"traffic_exceed_email_template": "Mẫu Thông Báo Vượt Quá Lưu Lượng",
|
||||
"traffic_exceed_email_templateDescription": "Các biến {after}.variable{before} sẽ được thay thế bằng dữ liệu thực tế. Đảm bảo giữ nguyên các biến này.",
|
||||
"verify_email_template": "Mẫu Email Xác Minh",
|
||||
"verify_email_templateDescription": "Các chỗ trống {after}.variable{before} sẽ được thay thế bằng dữ liệu thực tế. Đảm bảo giữ nguyên các biến này.",
|
||||
"whitelistSuffixes": "Danh sách trắng Hậu tố",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Chính sách bảo mật"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Dữ liệu tùy chỉnh",
|
||||
"customDataDescription": "Dữ liệu tùy chỉnh, được sử dụng cho dữ liệu tùy chỉnh của trang web",
|
||||
"customHtml": "HTML tùy chỉnh",
|
||||
"customHtmlDescription": "Mã HTML tùy chỉnh để được chèn vào cuối thẻ body của trang.",
|
||||
"keywords": "Từ khóa",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "状态",
|
||||
"subject": "主题",
|
||||
"template": "模板",
|
||||
"traffic_exceed_email_template": "流量超限通知模板",
|
||||
"traffic_exceed_email_templateDescription": "{after}和{before}变量将被实际数据替换。请确保保留这些变量。",
|
||||
"verify_email_template": "验证电子邮件模板",
|
||||
"verify_email_templateDescription": "{after}.variable{before} 占位符将被实际数据替换。请确保保留这些变量。",
|
||||
"whitelistSuffixes": "白名单后缀",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "隐私政策"
|
||||
},
|
||||
"site": {
|
||||
"customData": "自定义数据",
|
||||
"customDataDescription": "自定义数据,用于网站的自定义数据",
|
||||
"customHtml": "自定义HTML",
|
||||
"customHtmlDescription": "要注入到网站 body 标签底部的自定义 HTML 代码。",
|
||||
"keywords": "关键词",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "狀態",
|
||||
"subject": "主題",
|
||||
"template": "模板",
|
||||
"traffic_exceed_email_template": "流量超標通知模板",
|
||||
"traffic_exceed_email_templateDescription": "{after}和{before}變量將會被實際數據替換。請確保保留這些變量。",
|
||||
"verify_email_template": "驗證電郵範本",
|
||||
"verify_email_templateDescription": "{after}.variable{before} 佔位符將被實際數據替換。請確保保留這些變數。",
|
||||
"whitelistSuffixes": "白名單後綴",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "私隱政策"
|
||||
},
|
||||
"site": {
|
||||
"customData": "自定義數據",
|
||||
"customDataDescription": "自定義數據,用於網站的自定義數據",
|
||||
"customHtml": "自定義HTML",
|
||||
"customHtmlDescription": "自定義 HTML 代碼,將注入到網站 body 標籤的底部。",
|
||||
"keywords": "關鍵字",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
|
||||
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as ads from './ads';
|
||||
|
||||
+23
@@ -116,6 +116,17 @@ declare namespace API {
|
||||
ids: number[];
|
||||
};
|
||||
|
||||
type CheckoutOrderRequest = {
|
||||
orderNo: string;
|
||||
returnUrl?: string;
|
||||
};
|
||||
|
||||
type CheckoutOrderResponse = {
|
||||
type: string;
|
||||
checkout_url?: string;
|
||||
stripe?: StripePayment;
|
||||
};
|
||||
|
||||
type CloseOrderRequest = {
|
||||
orderNo: string;
|
||||
};
|
||||
@@ -1097,6 +1108,11 @@ declare namespace API {
|
||||
total: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListRequest = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListResponse = {
|
||||
list: UserAffiliate[];
|
||||
total: number;
|
||||
@@ -1253,6 +1269,13 @@ declare namespace API {
|
||||
site_logo: string;
|
||||
keywords: string;
|
||||
custom_html: string;
|
||||
custom_data: string;
|
||||
};
|
||||
|
||||
type SiteCustomDataContacts = {
|
||||
email: string;
|
||||
telephone: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
type SortItem = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
|
||||
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as auth from './auth';
|
||||
|
||||
+23
@@ -94,6 +94,17 @@ declare namespace API {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type CheckoutOrderRequest = {
|
||||
orderNo: string;
|
||||
returnUrl?: string;
|
||||
};
|
||||
|
||||
type CheckoutOrderResponse = {
|
||||
type: string;
|
||||
checkout_url?: string;
|
||||
stripe?: StripePayment;
|
||||
};
|
||||
|
||||
type CheckUserParams = {
|
||||
email: string;
|
||||
};
|
||||
@@ -496,6 +507,11 @@ declare namespace API {
|
||||
total: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListRequest = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListResponse = {
|
||||
list: UserAffiliate[];
|
||||
total: number;
|
||||
@@ -643,6 +659,13 @@ declare namespace API {
|
||||
site_logo: string;
|
||||
keywords: string;
|
||||
custom_html: string;
|
||||
custom_data: string;
|
||||
};
|
||||
|
||||
type SiteCustomDataContacts = {
|
||||
email: string;
|
||||
telephone: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
type SortItem = {
|
||||
|
||||
@@ -164,9 +164,7 @@ export default function Content() {
|
||||
<CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'>
|
||||
<CardTitle className='font-medium'>
|
||||
{item.subscribe.name}
|
||||
<p className='text-foreground/50 mt-1 text-sm'>
|
||||
{formatDate(item.subscribe.created_at)}
|
||||
</p>
|
||||
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
|
||||
</CardTitle>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
<AlertDialog>
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function Content({ subscription }: { subscription?: API.Subscribe
|
||||
subscribe_id: 0,
|
||||
payment: -1,
|
||||
coupon: '',
|
||||
platform: 'email',
|
||||
auth_type: 'email',
|
||||
identifier: '',
|
||||
password: '',
|
||||
});
|
||||
@@ -70,12 +70,15 @@ export default function Content({ subscription }: { subscription?: API.Subscribe
|
||||
startTransition(async () => {
|
||||
try {
|
||||
const { data } = await purchase(params);
|
||||
console.log(data);
|
||||
const { order_no, check_url, type } = data.data!;
|
||||
const { order_no } = data.data!;
|
||||
if (order_no) {
|
||||
if (type === 'link') {
|
||||
window.location.href = check_url!;
|
||||
}
|
||||
localStorage.setItem(
|
||||
order_no,
|
||||
JSON.stringify({
|
||||
auth_type: params.auth_type,
|
||||
identifier: params.identifier,
|
||||
}),
|
||||
);
|
||||
router.push(`/purchasing/order?order_no=${order_no}`);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -37,7 +37,10 @@ export default function Page() {
|
||||
enabled: enabled,
|
||||
queryKey: ['queryPurchaseOrder', orderNo],
|
||||
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) {
|
||||
setEnabled(false);
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`transform-gpu transition-all duration-500 ${
|
||||
loading ? 'scale-100 opacity-100' : 'scale-50 opacity-0'
|
||||
}`}
|
||||
>
|
||||
<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 className='flex min-h-screen items-center justify-center'>
|
||||
<div className='text-center'>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,10 +22,11 @@ const PaymentMethods: React.FC<PaymentMethodsProps> = ({ value, onChange, balanc
|
||||
queryKey: ['getAvailablePaymentMethods', { balance }],
|
||||
queryFn: async () => {
|
||||
const { data } = await getAvailablePaymentMethods();
|
||||
const methods = data.data?.list || [];
|
||||
if (!value && methods[0]?.id) onChange(methods[0]?.id);
|
||||
if (balance) return methods;
|
||||
return methods.filter((item) => item.id !== -1);
|
||||
const list = data.data?.list || [];
|
||||
const methods = balance ? list : list.filter((item) => item.id !== -1);
|
||||
const defaultMethod = methods.find((item) => item.id)?.id;
|
||||
if (defaultMethod) onChange(defaultMethod);
|
||||
return methods;
|
||||
},
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -22,6 +22,7 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
|
||||
site_logo: '',
|
||||
keywords: '',
|
||||
custom_html: '',
|
||||
custom_data: '',
|
||||
},
|
||||
verify: {
|
||||
turnstile_site_key: '',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
|
||||
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as auth from './auth';
|
||||
|
||||
+23
@@ -94,6 +94,17 @@ declare namespace API {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
type CheckoutOrderRequest = {
|
||||
orderNo: string;
|
||||
returnUrl?: string;
|
||||
};
|
||||
|
||||
type CheckoutOrderResponse = {
|
||||
type: string;
|
||||
checkout_url?: string;
|
||||
stripe?: StripePayment;
|
||||
};
|
||||
|
||||
type CheckUserParams = {
|
||||
email: string;
|
||||
};
|
||||
@@ -496,6 +507,11 @@ declare namespace API {
|
||||
total: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListRequest = {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
type QueryUserAffiliateListResponse = {
|
||||
list: UserAffiliate[];
|
||||
total: number;
|
||||
@@ -643,6 +659,13 @@ declare namespace API {
|
||||
site_logo: string;
|
||||
keywords: string;
|
||||
custom_html: string;
|
||||
custom_data: string;
|
||||
};
|
||||
|
||||
type SiteCustomDataContacts = {
|
||||
email: string;
|
||||
telephone: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
type SortItem = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-ignore
|
||||
|
||||
|
||||
// API 更新时间:
|
||||
// API 唯一标识:
|
||||
import * as announcement from './announcement';
|
||||
|
||||
Vendored
+12
-1
@@ -438,8 +438,8 @@ declare namespace API {
|
||||
};
|
||||
|
||||
type PortalPurchaseRequest = {
|
||||
auth_type: string;
|
||||
identifier: string;
|
||||
platform: string;
|
||||
password?: string;
|
||||
payment: number;
|
||||
subscribe_id: number;
|
||||
@@ -572,10 +572,14 @@ declare namespace API {
|
||||
};
|
||||
|
||||
type QueryPurchaseOrderParams = {
|
||||
auth_type: string;
|
||||
identifier: string;
|
||||
order_no: string;
|
||||
};
|
||||
|
||||
type QueryPurchaseOrderRequest = {
|
||||
auth_type: string;
|
||||
identifier: string;
|
||||
order_no: string;
|
||||
};
|
||||
|
||||
@@ -773,6 +777,13 @@ declare namespace API {
|
||||
site_logo: string;
|
||||
keywords: string;
|
||||
custom_html: string;
|
||||
custom_data: string;
|
||||
};
|
||||
|
||||
type SiteCustomDataContacts = {
|
||||
email: string;
|
||||
telephone: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
type SortItem = {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ppanel-web",
|
||||
"version": "1.0.0-beta.27",
|
||||
"version": "1.0.0-beta.30",
|
||||
"private": true,
|
||||
"homepage": "https://github.com/perfect-panel/ppanel-web",
|
||||
"bugs": {
|
||||
|
||||
@@ -28,25 +28,25 @@ export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
|
||||
const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
|
||||
|
||||
return (
|
||||
<div className='w-full bg-white font-sans md:px-10 dark:bg-neutral-950' ref={containerRef}>
|
||||
<div className='mx-auto max-w-7xl px-4 py-20 md:px-8 lg:px-10'>
|
||||
<h2 className='mb-4 max-w-4xl text-lg text-black md:text-4xl dark:text-white'>
|
||||
<div className='w-full font-sans md:px-10' ref={containerRef}>
|
||||
<div className='mx-auto max-w-7xl px-4 py-10 md:px-8 lg:px-10'>
|
||||
{/* <h2 className='mb-4 max-w-4xl text-lg text-black md:text-4xl dark:text-white'>
|
||||
Changelog from my journey
|
||||
</h2>
|
||||
<p className='max-w-sm text-sm text-neutral-700 md:text-base dark:text-neutral-300'>
|
||||
I've been working on Aceternity for the past 2 years. Here's a timeline of my
|
||||
journey.
|
||||
</p>
|
||||
</p> */}
|
||||
</div>
|
||||
|
||||
<div ref={ref} className='relative mx-auto max-w-7xl pb-20'>
|
||||
{data.map((item, index) => (
|
||||
<div key={index} className='flex justify-start pt-10 md:gap-10 md:pt-40'>
|
||||
<div className='sticky top-40 z-40 flex max-w-xs flex-col items-center self-start md:w-full md:flex-row lg:max-w-sm'>
|
||||
<div className='sticky top-40 z-40 flex max-w-xs flex-col items-center self-start whitespace-nowrap md:w-auto md:flex-row lg:max-w-sm'>
|
||||
<div className='absolute left-3 flex h-10 w-10 items-center justify-center rounded-full bg-white md:left-3 dark:bg-black'>
|
||||
<div className='h-4 w-4 rounded-full border border-neutral-300 bg-neutral-200 p-2 dark:border-neutral-700 dark:bg-neutral-800' />
|
||||
</div>
|
||||
<h3 className='hidden text-xl font-bold text-neutral-500 md:block md:pl-20 md:text-5xl dark:text-neutral-500'>
|
||||
<h3 className='hidden text-xl font-bold text-neutral-500 md:block md:pl-20 md:text-xl dark:text-neutral-500'>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface EnhancedInputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
|
||||
prefix?: string | ReactNode;
|
||||
suffix?: string | ReactNode;
|
||||
formatInput?: (value: string | number) => string;
|
||||
formatInput?: (value: string | number) => string | number;
|
||||
formatOutput?: (value: string | number) => string | number;
|
||||
onValueChange?: (value: string | number) => void;
|
||||
onValueBlur?: (value: string | number) => void;
|
||||
@@ -26,50 +26,94 @@ export function EnhancedInput({
|
||||
...props
|
||||
}: EnhancedInputProps) {
|
||||
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;
|
||||
};
|
||||
|
||||
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(() => {
|
||||
if (initialValue !== value) {
|
||||
if (initialValue !== internalValue) {
|
||||
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
|
||||
}, [initialValue, formatInput]);
|
||||
|
||||
const processValue = (inputValue: string) => {
|
||||
const processValue = (inputValue: string | number) => {
|
||||
let processedValue: number | string = inputValue?.toString().trim();
|
||||
|
||||
if (processedValue === '0' && props.type === 'number') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (processedValue && props.type === 'number') processedValue = Number(processedValue);
|
||||
return formatOutput ? formatOutput(processedValue) : processedValue;
|
||||
};
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
let inputValue = e.target.value;
|
||||
if (props.type === 'number' && inputValue) {
|
||||
const numericValue = Number(inputValue);
|
||||
if (!isNaN(numericValue)) {
|
||||
const min = Number.isFinite(props.min) ? props.min : -Infinity;
|
||||
const max = Number.isFinite(props.max) ? props.max : Infinity;
|
||||
inputValue = String(Math.max(min!, Math.min(max!, numericValue)));
|
||||
|
||||
if (props.type === 'number') {
|
||||
if (inputValue === '0') {
|
||||
setValue('');
|
||||
setInternalValue(0);
|
||||
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 {
|
||||
setValue(inputValue);
|
||||
setInternalValue(inputValue);
|
||||
}
|
||||
|
||||
const outputValue = processValue(inputValue);
|
||||
console.log();
|
||||
onValueChange?.(outputValue);
|
||||
};
|
||||
|
||||
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);
|
||||
if ((initialValue || '') !== outputValue) {
|
||||
onValueBlur?.(outputValue);
|
||||
}
|
||||
};
|
||||
|
||||
const renderPrefix = () => {
|
||||
return typeof prefix === 'string' ? (
|
||||
<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
|
||||
);
|
||||
};
|
||||
|
||||
const renderSuffix = () => {
|
||||
return typeof suffix === 'string' ? (
|
||||
<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()}
|
||||
<Input
|
||||
step={0.01}
|
||||
{...props}
|
||||
value={value}
|
||||
className='block rounded-none border-none'
|
||||
|
||||
@@ -28,6 +28,6 @@ export function formatDate(date?: Date | number, showTime: boolean = true) {
|
||||
export function differenceInDays(dateLeft: Date | number, dateRight: Date | number) {
|
||||
const diffInMs = differenceInMilliseconds(dateLeft, dateRight);
|
||||
const diffInDays = diffInMs / (1000 * 60 * 60 * 24);
|
||||
if (diffInDays % 1 === 0) return diffInDays;
|
||||
return Number(diffInDays.toFixed(1));
|
||||
if (diffInDays >= 1) return diffInDays.toFixed(0);
|
||||
return Number(diffInDays.toFixed(2));
|
||||
}
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
import { evaluate, format } from 'mathjs';
|
||||
|
||||
export function unitConversion(
|
||||
type: 'centsToDollars' | 'dollarsToCents' | 'bitsToMb' | 'mbToBits' | 'bytesToGb' | 'gbToBytes',
|
||||
value?: number | string,
|
||||
) {
|
||||
if (!value) return;
|
||||
switch (type) {
|
||||
case 'centsToDollars':
|
||||
return evaluate(`${value} / 100`);
|
||||
case 'dollarsToCents':
|
||||
return evaluate(`${value} * 100`);
|
||||
case 'bitsToMb':
|
||||
return evaluate(`${value} / 1024 / 1024`);
|
||||
case 'mbToBits':
|
||||
return evaluate(`${value} * 1024 * 1024`);
|
||||
case 'bytesToGb':
|
||||
return evaluate(`${value} / 1024 / 1024 / 1024`);
|
||||
case 'gbToBytes':
|
||||
return evaluate(`${value} * 1024 * 1024 * 1024`);
|
||||
default:
|
||||
throw new Error('Invalid conversion type');
|
||||
}
|
||||
type ConversionType =
|
||||
| 'centsToDollars'
|
||||
| 'dollarsToCents'
|
||||
| 'bitsToMb'
|
||||
| 'mbToBits'
|
||||
| 'bytesToGb'
|
||||
| 'gbToBytes';
|
||||
|
||||
const conversionConfig: Record<ConversionType, { formula: string; precision: number }> = {
|
||||
centsToDollars: { formula: 'value / 100', precision: 2 },
|
||||
dollarsToCents: { formula: 'value * 100', precision: 0 },
|
||||
bitsToMb: { formula: 'value / 1024 / 1024', precision: 2 },
|
||||
mbToBits: { formula: 'value * 1024 * 1024', precision: 0 },
|
||||
bytesToGb: { formula: 'value / 1024 / 1024 / 1024', precision: 2 },
|
||||
gbToBytes: { formula: 'value * 1024 * 1024 * 1024', precision: 0 },
|
||||
};
|
||||
|
||||
export function unitConversion(type: ConversionType, value?: number | string) {
|
||||
if (!value) return 0;
|
||||
|
||||
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) {
|
||||
const result = evaluate(expression);
|
||||
|
||||
const formatted = format(result, { notation: 'fixed', precision: 2 });
|
||||
|
||||
return Number(formatted);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user