Compare commits

..

21 Commits

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

### 🐛 Bug Fixes

* **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
2025-03-15 15:51:45 +00:00
web@ppanel 0c8f0911c7 🐛 fix(site): Se ref to store site configuration for updates 2025-03-15 22:47:19 +07:00
semantic-release-bot db0d9e003e 🔖 chore(release): v1.0.0-beta.30 [skip ci]
# [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

* **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

* **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

* **dashboard**:  Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
2025-03-15 09:02:24 +00:00
web@ppanel f219c52306 🔧 chore: Merge branch 'beta' into develop 2025-03-15 15:58:37 +07:00
web@ppanel 3f7b6d16ed feat(node-form): Update number input fields to enforce step, min, and max values 2025-03-15 15:48:59 +07:00
web@ppanel 6dbebd152d feat(global): Add custom data 2025-03-15 15:39:55 +07:00
web@ppanel bb3bd7b50c feat(email): Add traffic exhaustion template 2025-03-14 23:28:36 +07:00
web@ppanel fbad3b0e51 feat(timeline): Simplify timeline component layout and remove commented-out code 2025-03-14 23:21:17 +07:00
web@ppanel e551232564 🐛 fix(dashboard): Update date display to use start_time if available 2025-03-14 22:10:24 +07:00
web@ppanel bf58f25072 feat(formatting): Update differenceInDays function to return whole days or two decimal places 2025-03-14 20:51:17 +07:00
web@ppanel 85f55def2e feat(payment): Add isEdit prop to PaymentForm and disable fields when editing 2025-03-14 14:04:10 +07:00
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 f72df3a5e8 feat(loading): Replace loading animation with a simpler spinner and loading text 2025-03-14 13:42:58 +07:00
web@ppanel 48a1b97051 feat(api): Add CheckoutOrder request and response types, and update user purchase request parameters 2025-03-14 13:42:58 +07:00
semantic-release-bot 786ba0e41c 🔖 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-14 13:42:58 +07:00
web@ppanel ce3197298d feat(input): Add minimum value constraint and enhance number handling in EnhancedInput 2025-03-14 13:42:58 +07:00
semantic-release-bot 092477b2d3 🔖 chore(release): v1.0.0-beta.27 [skip ci]
* **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))

* **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))

* **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))

* **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))
2025-03-14 13:42:58 +07: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
74 changed files with 524 additions and 140 deletions
+124 -31
View File
@@ -1,41 +1,134 @@
<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.31](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.30...v1.0.0-beta.31) (2025-03-15)
### ♻ 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 ### 🐛 Bug Fixes
* **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9)) * **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
* **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)) # [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)
* **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)) ### ✨ Features
* **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)) * **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
* **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49)) * **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
* **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)) * **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
* **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3)) * **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
* **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
* **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> <a name="readme-top"></a>
@@ -286,9 +286,13 @@ export default function Page() {
</TabsContent> </TabsContent>
<TabsContent value='template'> <TabsContent value='template'>
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'> <div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
{['verify_email_template', 'expiration_email_template', 'maintenance_email_template'].map( {[
(templateKey) => ( 'verify_email_template',
'expiration_email_template',
'maintenance_email_template',
'traffic_exceed_email_template',
].map((templateKey) => (
<Card key={templateKey}> <Card key={templateKey}>
<CardHeader> <CardHeader>
<CardTitle>{t(`${templateKey}`)}</CardTitle> <CardTitle>{t(`${templateKey}`)}</CardTitle>
@@ -309,8 +313,7 @@ export default function Page() {
/> />
</CardContent> </CardContent>
</Card> </Card>
), ))}
)}
</div> </div>
</TabsContent> </TabsContent>
@@ -45,6 +45,7 @@ interface PaymentFormProps<T> {
loading?: boolean; loading?: boolean;
initialValues?: T; initialValues?: T;
onSubmit: (values: T) => Promise<boolean>; onSubmit: (values: T) => Promise<boolean>;
isEdit?: boolean;
} }
export default function PaymentForm<T>({ export default function PaymentForm<T>({
@@ -53,6 +54,7 @@ export default function PaymentForm<T>({
loading, loading,
initialValues, initialValues,
onSubmit, onSubmit,
isEdit,
}: PaymentFormProps<T>) { }: PaymentFormProps<T>) {
const t = useTranslations('payment'); const t = useTranslations('payment');
const { common } = useGlobalStore(); const { common } = useGlobalStore();
@@ -317,6 +319,7 @@ export default function PaymentForm<T>({
}} }}
defaultValue={field.value} defaultValue={field.value}
value={field.value} value={field.value}
disabled={isEdit}
> >
<FormControl> <FormControl>
<SelectTrigger> <SelectTrigger>
@@ -136,6 +136,7 @@ export default function PaymentTable() {
actions={{ actions={{
render: (row) => [ render: (row) => [
<PaymentForm<API.UpdatePaymentMethodRequest> <PaymentForm<API.UpdatePaymentMethodRequest>
isEdit
key='edit' key='edit'
trigger={<Button>{t('edit')}</Button>} trigger={<Button>{t('edit')}</Button>}
title={t('editPayment')} title={t('editPayment')}
@@ -362,9 +362,10 @@ export default function NodeForm<T extends { [x: string]: any }>({
<EnhancedInput <EnhancedInput
{...field} {...field}
type='number' type='number'
placeholder='1-65535' step={1}
min={1} min={1}
max={65535} max={65535}
placeholder='1-65535'
onValueChange={(value) => { onValueChange={(value) => {
form.setValue(field.name, value); form.setValue(field.name, value);
}} }}
@@ -418,6 +419,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
<EnhancedInput <EnhancedInput
{...field} {...field}
type='number' type='number'
step={1}
min={1}
max={65535}
placeholder='1-65535' placeholder='1-65535'
onValueChange={(value) => { onValueChange={(value) => {
form.setValue(field.name, value); form.setValue(field.name, value);
@@ -899,6 +903,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
{ {
name: 'port', name: 'port',
type: 'number', type: 'number',
step: 1,
min: 1, min: 1,
max: 65535, max: 65535,
placeholder: t('form.relayPort'), placeholder: t('form.relayPort'),
+48 -2
View File
@@ -5,18 +5,21 @@ import { useQuery } from '@tanstack/react-query';
import { Label } from '@workspace/ui/components/label'; import { Label } from '@workspace/ui/components/label';
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table'; import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
import { Textarea } from '@workspace/ui/components/textarea'; 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 { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { UploadImage } from '@workspace/ui/custom-components/upload-image'; import { UploadImage } from '@workspace/ui/custom-components/upload-image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useRef } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
export default function Site() { export default function Site() {
const t = useTranslations('system.site'); const t = useTranslations('system.site');
const ref = useRef<API.SiteConfig | undefined>(undefined);
const { data, refetch } = useQuery({ const { data, refetch } = useQuery({
queryKey: ['getSiteConfig'], queryKey: ['getSiteConfig'],
queryFn: async () => { queryFn: async () => {
const { data } = await getSiteConfig(); const { data } = await getSiteConfig();
ref.current = data.data;
return data.data; return data.data;
}, },
}); });
@@ -25,7 +28,7 @@ export default function Site() {
if (data?.[key] === value) return; if (data?.[key] === value) return;
try { try {
await updateSiteConfig({ await updateSiteConfig({
...data, ...ref.current,
[key]: value, [key]: value,
} as API.SiteConfig); } as API.SiteConfig);
toast.success(t('saveSuccess')); toast.success(t('saveSuccess'));
@@ -130,6 +133,49 @@ export default function Site() {
/> />
</TableCell> </TableCell>
</TableRow> </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> </TableBody>
</Table> </Table>
); );
@@ -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)}
+14 -1
View File
@@ -58,8 +58,21 @@ export default function Page() {
<Switch <Switch
defaultChecked={row.getValue('enable')} defaultChecked={row.getValue('enable')}
onCheckedChange={async (checked) => { 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({ await updateUserBasicInfo({
...row.original, user_id: id,
...rest,
enable: checked, enable: checked,
} as unknown as API.UpdateUserBasiceInfoRequest); } as unknown as API.UpdateUserBasiceInfoRequest);
toast.success(t('updateSuccess')); toast.success(t('updateSuccess'));
+1
View File
@@ -16,6 +16,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
site_logo: '', site_logo: '',
keywords: '', keywords: '',
custom_html: '', custom_html: '',
custom_data: '',
}, },
verify: { verify: {
turnstile_site_key: '', turnstile_site_key: '',
+2
View File
@@ -39,6 +39,8 @@
"status": "Stav", "status": "Stav",
"subject": "Předmět", "subject": "Předmět",
"template": "Šablona", "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_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.", "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", "whitelistSuffixes": "Povolené přípony",
+2
View File
@@ -13,6 +13,8 @@
"title": "Zásady ochrany osobních údajů" "title": "Zásady ochrany osobních údajů"
}, },
"site": { "site": {
"customData": "Vlastní data",
"customDataDescription": "Vlastní data, používaná pro vlastní data webu",
"customHtml": "Vlastní HTML", "customHtml": "Vlastní HTML",
"customHtmlDescription": "Vlastní HTML kód, který bude vložen na konec tagu body na stránce.", "customHtmlDescription": "Vlastní HTML kód, který bude vložen na konec tagu body na stránce.",
"keywords": "Klíčová slova", "keywords": "Klíčová slova",
+2
View File
@@ -39,6 +39,8 @@
"status": "Status", "status": "Status",
"subject": "Betreff", "subject": "Betreff",
"template": "Vorlage", "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_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.", "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", "whitelistSuffixes": "Whitelist-Suffixe",
+2
View File
@@ -13,6 +13,8 @@
"title": "Datenschutzrichtlinie" "title": "Datenschutzrichtlinie"
}, },
"site": { "site": {
"customData": "Benutzerdefinierte Daten",
"customDataDescription": "Benutzerdefinierte Daten, die für benutzerdefinierte Daten der Website verwendet werden",
"customHtml": "Benutzerdefiniertes HTML", "customHtml": "Benutzerdefiniertes HTML",
"customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingefügt werden soll.", "customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingefügt werden soll.",
"keywords": "Schlüsselwörter", "keywords": "Schlüsselwörter",
+2
View File
@@ -39,6 +39,8 @@
"status": "Status", "status": "Status",
"subject": "Subject", "subject": "Subject",
"template": "Template", "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_template": "Verification Email Template",
"verify_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.", "verify_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
"whitelistSuffixes": "Whitelist Suffixes", "whitelistSuffixes": "Whitelist Suffixes",
+2
View File
@@ -13,6 +13,8 @@
"title": "Privacy Policy" "title": "Privacy Policy"
}, },
"site": { "site": {
"customData": "Custom Data",
"customDataDescription": "Custom Data, used for custom data of the site",
"customHtml": "Custom HTML", "customHtml": "Custom HTML",
"customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.", "customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.",
"keywords": "Keywords", "keywords": "Keywords",
+2
View File
@@ -39,6 +39,8 @@
"status": "Estado", "status": "Estado",
"subject": "Asunto", "subject": "Asunto",
"template": "Plantilla", "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_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.", "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", "whitelistSuffixes": "Sufijos de la lista blanca",
+2
View File
@@ -13,6 +13,8 @@
"title": "Política de Privacidad" "title": "Política de Privacidad"
}, },
"site": { "site": {
"customData": "Datos Personalizados",
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
"customHtml": "HTML personalizado", "customHtml": "HTML personalizado",
"customHtmlDescription": "Código HTML personalizado que se inyectará en la parte inferior de la etiqueta body del sitio.", "customHtmlDescription": "Código HTML personalizado que se inyectará en la parte inferior de la etiqueta body del sitio.",
"keywords": "Palabras clave", "keywords": "Palabras clave",
+2
View File
@@ -39,6 +39,8 @@
"status": "Estado", "status": "Estado",
"subject": "Asunto", "subject": "Asunto",
"template": "Plantilla", "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_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.", "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", "whitelistSuffixes": "Sufijos de Lista Blanca",
+2
View File
@@ -13,6 +13,8 @@
"title": "Política de Privacidad" "title": "Política de Privacidad"
}, },
"site": { "site": {
"customData": "Datos Personalizados",
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
"customHtml": "HTML personalizado", "customHtml": "HTML personalizado",
"customHtmlDescription": "Código HTML personalizado que se inyectará al final de la etiqueta body del sitio.", "customHtmlDescription": "Código HTML personalizado que se inyectará al final de la etiqueta body del sitio.",
"keywords": "Palabras clave", "keywords": "Palabras clave",
+2
View File
@@ -39,6 +39,8 @@
"status": "وضعیت", "status": "وضعیت",
"subject": "موضوع", "subject": "موضوع",
"template": "الگو", "template": "الگو",
"traffic_exceed_email_template": "الگوی اعلان ترافیک بیش از حد",
"traffic_exceed_email_templateDescription": "جایگزین‌های {after}.variable{before} با داده‌های واقعی خواهد شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
"verify_email_template": "قالب ایمیل تأیید", "verify_email_template": "قالب ایمیل تأیید",
"verify_email_templateDescription": "جای‌گذاری‌های {after}.variable{before} با داده‌های واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.", "verify_email_templateDescription": "جای‌گذاری‌های {after}.variable{before} با داده‌های واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
"whitelistSuffixes": "پسوندهای لیست سفید", "whitelistSuffixes": "پسوندهای لیست سفید",
+2
View File
@@ -13,6 +13,8 @@
"title": "سیاست حفظ حریم خصوصی" "title": "سیاست حفظ حریم خصوصی"
}, },
"site": { "site": {
"customData": "داده‌های سفارشی",
"customDataDescription": "داده‌های سفارشی، استفاده شده برای داده‌های سفارشی سایت",
"customHtml": "HTML سفارشی", "customHtml": "HTML سفارشی",
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.", "customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.",
"keywords": "کلمات کلیدی", "keywords": "کلمات کلیدی",
+2
View File
@@ -39,6 +39,8 @@
"status": "Tila", "status": "Tila",
"subject": "Aihe", "subject": "Aihe",
"template": "Malline", "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_template": "Sähköpostin Vahvistusmalli",
"verify_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.", "verify_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.",
"whitelistSuffixes": "Sallittujen luetteloiden päätteet", "whitelistSuffixes": "Sallittujen luetteloiden päätteet",
+2
View File
@@ -13,6 +13,8 @@
"title": "Tietosuojakäytäntö" "title": "Tietosuojakäytäntö"
}, },
"site": { "site": {
"customData": "Mukautettu tieto",
"customDataDescription": "Mukautettu tieto, jota käytetään sivuston mukautettuun tietoon",
"customHtml": "Mukautettu HTML", "customHtml": "Mukautettu HTML",
"customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston body -tagin loppuun.", "customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston body -tagin loppuun.",
"keywords": "Avainsanat", "keywords": "Avainsanat",
+2
View File
@@ -39,6 +39,8 @@
"status": "Statut", "status": "Statut",
"subject": "Sujet", "subject": "Sujet",
"template": "Modèle", "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_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.", "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", "whitelistSuffixes": "Suffixes de la liste blanche",
+2
View File
@@ -13,6 +13,8 @@
"title": "Politique de confidentialité" "title": "Politique de confidentialité"
}, },
"site": { "site": {
"customData": "Données personnalisées",
"customDataDescription": "Données personnalisées, utilisées pour les données personnalisées du site",
"customHtml": "HTML personnalisé", "customHtml": "HTML personnalisé",
"customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise body du site.", "customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise body du site.",
"keywords": "Mots-clés", "keywords": "Mots-clés",
+2
View File
@@ -39,6 +39,8 @@
"status": "स्थिति", "status": "स्थिति",
"subject": "विषय", "subject": "विषय",
"template": "टेम्पलेट", "template": "टेम्पलेट",
"traffic_exceed_email_template": "ट्रैफिक अधिकता सूचना टेम्पलेट",
"traffic_exceed_email_templateDescription": "{after}.variable{before} प्लेसहोल्डर्स को वास्तविक डेटा के साथ प्रतिस्थापित किया जाएगा। सुनिश्चित करें कि इन वेरिएबल्स को बनाए रखें।",
"verify_email_template": "सत्यापन ईमेल टेम्पलेट", "verify_email_template": "सत्यापन ईमेल टेम्पलेट",
"verify_email_templateDescription": "{after}.variable{before} प्लेसहोल्डर्स को वास्तविक डेटा से बदला जाएगा। सुनिश्चित करें कि इन वेरिएबल्स को बनाए रखें।", "verify_email_templateDescription": "{after}.variable{before} प्लेसहोल्डर्स को वास्तविक डेटा से बदला जाएगा। सुनिश्चित करें कि इन वेरिएबल्स को बनाए रखें।",
"whitelistSuffixes": "श्वेतसूची प्रत्यय", "whitelistSuffixes": "श्वेतसूची प्रत्यय",
+2
View File
@@ -13,6 +13,8 @@
"title": "गोपनीयता नीति" "title": "गोपनीयता नीति"
}, },
"site": { "site": {
"customData": "कस्टम डेटा",
"customDataDescription": "कस्टम डेटा, साइट के कस्टम डेटा के लिए उपयोग किया जाता है",
"customHtml": "कस्टम HTML", "customHtml": "कस्टम HTML",
"customHtmlDescription": "साइट के bodyैग के नीचे इंजेक्ट करने के लिए कस्टम HTML कोड।", "customHtmlDescription": "साइट के bodyैग के नीचे इंजेक्ट करने के लिए कस्टम HTML कोड।",
"keywords": "कीवर्ड", "keywords": "कीवर्ड",
+2
View File
@@ -39,6 +39,8 @@
"status": "Állapot", "status": "Állapot",
"subject": "Tárgy", "subject": "Tárgy",
"template": "Sablon", "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_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.", "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", "whitelistSuffixes": "Engedélyezett utótagok",
+2
View File
@@ -13,6 +13,8 @@
"title": "Adatvédelmi irányelvek" "title": "Adatvédelmi irányelvek"
}, },
"site": { "site": {
"customData": "Egyedi adatok",
"customDataDescription": "Egyedi adatok, a webhely egyedi adatainak használatára",
"customHtml": "Egyedi HTML", "customHtml": "Egyedi HTML",
"customHtmlDescription": "Egyedi HTML kód, amelyet a webhely bodyímkéjének aljára kell beilleszteni.", "customHtmlDescription": "Egyedi HTML kód, amelyet a webhely bodyímkéjének aljára kell beilleszteni.",
"keywords": "Kulcsszavak", "keywords": "Kulcsszavak",
+2
View File
@@ -39,6 +39,8 @@
"status": "ステータス", "status": "ステータス",
"subject": "件名", "subject": "件名",
"template": "テンプレート", "template": "テンプレート",
"traffic_exceed_email_template": "トラフィック超過通知テンプレート",
"traffic_exceed_email_templateDescription": "{after}と{before}の変数は実際のデータに置き換えられます。これらの変数を保持することを確認してください。",
"verify_email_template": "確認メールテンプレート", "verify_email_template": "確認メールテンプレート",
"verify_email_templateDescription": "{after}.variable{before} のプレースホルダーは実際のデータに置き換えられます。これらの変数を保持してください。", "verify_email_templateDescription": "{after}.variable{before} のプレースホルダーは実際のデータに置き換えられます。これらの変数を保持してください。",
"whitelistSuffixes": "ホワイトリストのサフィックス", "whitelistSuffixes": "ホワイトリストのサフィックス",
+2
View File
@@ -13,6 +13,8 @@
"title": "プライバシーポリシー" "title": "プライバシーポリシー"
}, },
"site": { "site": {
"customData": "カスタムデータ",
"customDataDescription": "サイトのカスタムデータに使用されるカスタムデータ",
"customHtml": "カスタムHTML", "customHtml": "カスタムHTML",
"customHtmlDescription": "サイトの body タグの下部に挿入されるカスタム HTML コード。", "customHtmlDescription": "サイトの body タグの下部に挿入されるカスタム HTML コード。",
"keywords": "キーワード", "keywords": "キーワード",
+2
View File
@@ -39,6 +39,8 @@
"status": "상태", "status": "상태",
"subject": "제목", "subject": "제목",
"template": "템플릿", "template": "템플릿",
"traffic_exceed_email_template": "트래픽 초과 알림 템플릿",
"traffic_exceed_email_templateDescription": "{after}.variable{before} 자리표시는 실제 데이터로 대체됩니다. 이러한 변수를 유지해야 합니다.",
"verify_email_template": "이메일 인증 템플릿", "verify_email_template": "이메일 인증 템플릿",
"verify_email_templateDescription": "{after}.variable{before} 자리표시는 실제 데이터로 대체됩니다. 이 변수를 반드시 유지하십시오.", "verify_email_templateDescription": "{after}.variable{before} 자리표시는 실제 데이터로 대체됩니다. 이 변수를 반드시 유지하십시오.",
"whitelistSuffixes": "허용 목록 접미사", "whitelistSuffixes": "허용 목록 접미사",
+2
View File
@@ -13,6 +13,8 @@
"title": "개인정보 처리방침" "title": "개인정보 처리방침"
}, },
"site": { "site": {
"customData": "사용자 정의 데이터",
"customDataDescription": "사이트의 사용자 정의 데이터에 사용되는 사용자 정의 데이터",
"customHtml": "사용자 정의 HTML", "customHtml": "사용자 정의 HTML",
"customHtmlDescription": "사이트의 body그 하단에 삽입될 사용자 정의 HTML 코드입니다.", "customHtmlDescription": "사이트의 body그 하단에 삽입될 사용자 정의 HTML 코드입니다.",
"keywords": "키워드", "keywords": "키워드",
+2
View File
@@ -39,6 +39,8 @@
"status": "Status", "status": "Status",
"subject": "Emne", "subject": "Emne",
"template": "Mal", "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_template": "Verifikasjons-e-postmal",
"verify_email_templateDescription": "Plassholderne {after}.variable{before} vil bli erstattet med faktiske data. Sørg for å beholde disse variablene.", "verify_email_templateDescription": "Plassholderne {after}.variable{before} vil bli erstattet med faktiske data. Sørg for å beholde disse variablene.",
"whitelistSuffixes": "Hvitelistesuffikser", "whitelistSuffixes": "Hvitelistesuffikser",
+2
View File
@@ -13,6 +13,8 @@
"title": "Personvernerklæring" "title": "Personvernerklæring"
}, },
"site": { "site": {
"customData": "Egendata",
"customDataDescription": "Egendata, brukt for egendata på nettstedet",
"customHtml": "Egendefinert HTML", "customHtml": "Egendefinert HTML",
"customHtmlDescription": "Egendefinert HTML-kode som skal settes inn i bunnen av nettstedets body-tag.", "customHtmlDescription": "Egendefinert HTML-kode som skal settes inn i bunnen av nettstedets body-tag.",
"keywords": "Nøkkelord", "keywords": "Nøkkelord",
+2
View File
@@ -39,6 +39,8 @@
"status": "Status", "status": "Status",
"subject": "Temat", "subject": "Temat",
"template": "Szablon", "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_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.", "verify_email_templateDescription": "Elementy zastępcze {after}.variable{before} zostaną zastąpione rzeczywistymi danymi. Upewnij się, że te zmienne zostaną zachowane.",
"whitelistSuffixes": "Dozwolone Sufiksy", "whitelistSuffixes": "Dozwolone Sufiksy",
+2
View File
@@ -13,6 +13,8 @@
"title": "Polityka prywatności" "title": "Polityka prywatności"
}, },
"site": { "site": {
"customData": "Dane niestandardowe",
"customDataDescription": "Dane niestandardowe, używane do niestandardowych danych witryny",
"customHtml": "Niestandardowy HTML", "customHtml": "Niestandardowy HTML",
"customHtmlDescription": "Niestandardowy kod HTML, który ma być wstrzyknięty na końcu tagu bodyitryny.", "customHtmlDescription": "Niestandardowy kod HTML, który ma być wstrzyknięty na końcu tagu bodyitryny.",
"keywords": "Słowa kluczowe", "keywords": "Słowa kluczowe",
+2
View File
@@ -39,6 +39,8 @@
"status": "Status", "status": "Status",
"subject": "Assunto", "subject": "Assunto",
"template": "Modelo", "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_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.", "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", "whitelistSuffixes": "Sufixos de Lista Branca",
+2
View File
@@ -13,6 +13,8 @@
"title": "Política de Privacidade" "title": "Política de Privacidade"
}, },
"site": { "site": {
"customData": "Dados Personalizados",
"customDataDescription": "Dados personalizados, usados para dados personalizados do site",
"customHtml": "HTML Personalizado", "customHtml": "HTML Personalizado",
"customHtmlDescription": "Código HTML personalizado a ser inserido no final da tag bodyo site.", "customHtmlDescription": "Código HTML personalizado a ser inserido no final da tag bodyo site.",
"keywords": "Palavras-chave", "keywords": "Palavras-chave",
+2
View File
@@ -39,6 +39,8 @@
"status": "Stare", "status": "Stare",
"subject": "Subiect", "subject": "Subiect",
"template": "Șablon", "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_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.", "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ă", "whitelistSuffixes": "Sufixe pe lista albă",
+2
View File
@@ -13,6 +13,8 @@
"title": "Politica de confidențialitate" "title": "Politica de confidențialitate"
}, },
"site": { "site": {
"customData": "Date personalizate",
"customDataDescription": "Date personalizate, utilizate pentru datele personalizate ale site-ului",
"customHtml": "HTML personalizat", "customHtml": "HTML personalizat",
"customHtmlDescription": "Cod HTML personalizat care va fi injectat în partea de jos a etichetei body a site-ului.", "customHtmlDescription": "Cod HTML personalizat care va fi injectat în partea de jos a etichetei body a site-ului.",
"keywords": "Cuvinte cheie", "keywords": "Cuvinte cheie",
+2
View File
@@ -39,6 +39,8 @@
"status": "Статус", "status": "Статус",
"subject": "Тема", "subject": "Тема",
"template": "Шаблон", "template": "Шаблон",
"traffic_exceed_email_template": "Шаблон уведомления о превышении трафика",
"traffic_exceed_email_templateDescription": "Места {after}.variable{before} будут заменены на фактические данные. Убедитесь, что эти переменные сохранены.",
"verify_email_template": "Шаблон письма для подтверждения", "verify_email_template": "Шаблон письма для подтверждения",
"verify_email_templateDescription": "Заполнители {after}.variable{before} будут заменены фактическими данными. Убедитесь, что эти переменные сохранены.", "verify_email_templateDescription": "Заполнители {after}.variable{before} будут заменены фактическими данными. Убедитесь, что эти переменные сохранены.",
"whitelistSuffixes": "Белый список суффиксов", "whitelistSuffixes": "Белый список суффиксов",
+2
View File
@@ -13,6 +13,8 @@
"title": "Политика конфиденциальности" "title": "Политика конфиденциальности"
}, },
"site": { "site": {
"customData": "Пользовательские данные",
"customDataDescription": "Пользовательские данные, используемые для пользовательских данных сайта",
"customHtml": "Пользовательский HTML", "customHtml": "Пользовательский HTML",
"customHtmlDescription": "Пользовательский HTML-код, который будет вставлен в конец тега body сайта.", "customHtmlDescription": "Пользовательский HTML-код, который будет вставлен в конец тега body сайта.",
"keywords": "Ключевые слова", "keywords": "Ключевые слова",
+2
View File
@@ -39,6 +39,8 @@
"status": "สถานะ", "status": "สถานะ",
"subject": "เรื่อง", "subject": "เรื่อง",
"template": "แม่แบบ", "template": "แม่แบบ",
"traffic_exceed_email_template": "เทมเพลตการแจ้งเตือนการเกินการจราจร",
"traffic_exceed_email_templateDescription": "ตัวแปร {after}.variable{before} จะถูกแทนที่ด้วยข้อมูลจริง โปรดตรวจสอบให้แน่ใจว่าได้เก็บตัวแปรเหล่านี้ไว้",
"verify_email_template": "เทมเพลตอีเมลยืนยัน", "verify_email_template": "เทมเพลตอีเมลยืนยัน",
"verify_email_templateDescription": "ตัวแปร {after}.variable{before} จะถูกแทนที่ด้วยข้อมูลจริง โปรดตรวจสอบให้แน่ใจว่าเก็บตัวแปรเหล่านี้ไว้", "verify_email_templateDescription": "ตัวแปร {after}.variable{before} จะถูกแทนที่ด้วยข้อมูลจริง โปรดตรวจสอบให้แน่ใจว่าเก็บตัวแปรเหล่านี้ไว้",
"whitelistSuffixes": "อนุญาตให้ใช้คำต่อท้าย", "whitelistSuffixes": "อนุญาตให้ใช้คำต่อท้าย",
+2
View File
@@ -13,6 +13,8 @@
"title": "นโยบายความเป็นส่วนตัว" "title": "นโยบายความเป็นส่วนตัว"
}, },
"site": { "site": {
"customData": "ข้อมูลที่กำหนดเอง",
"customDataDescription": "ข้อมูลที่กำหนดเอง ใช้สำหรับข้อมูลที่กำหนดเองของเว็บไซต์",
"customHtml": "HTML ที่กำหนดเอง", "customHtml": "HTML ที่กำหนดเอง",
"customHtmlDescription": "รหัส HTML ที่กำหนดเองเพื่อแทรกลงในส่วนล่างของแท็ก body ของเว็บไซต์.", "customHtmlDescription": "รหัส HTML ที่กำหนดเองเพื่อแทรกลงในส่วนล่างของแท็ก body ของเว็บไซต์.",
"keywords": "คำสำคัญ", "keywords": "คำสำคัญ",
+2
View File
@@ -39,6 +39,8 @@
"status": "Durum", "status": "Durum",
"subject": "Konu", "subject": "Konu",
"template": "Şablon", "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_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.", "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", "whitelistSuffixes": "Beyaz Liste Sonekleri",
+2
View File
@@ -13,6 +13,8 @@
"title": "Gizlilik Politikası" "title": "Gizlilik Politikası"
}, },
"site": { "site": {
"customData": "Özel Veri",
"customDataDescription": "Özel Veri, sitenin özel verileri için kullanılır",
"customHtml": "Özel HTML", "customHtml": "Özel HTML",
"customHtmlDescription": "Site'in body etiketinin altına yerleştirilecek özel HTML kodu.", "customHtmlDescription": "Site'in body etiketinin altına yerleştirilecek özel HTML kodu.",
"keywords": "Anahtar Kelimeler", "keywords": "Anahtar Kelimeler",
+2
View File
@@ -39,6 +39,8 @@
"status": "Статус", "status": "Статус",
"subject": "Тема", "subject": "Тема",
"template": "Шаблон", "template": "Шаблон",
"traffic_exceed_email_template": "Шаблон сповіщення про перевищення трафіку",
"traffic_exceed_email_templateDescription": "Місця для змінних {after}.variable{before} будуть замінені на фактичні дані. Переконайтеся, що ці змінні залишаються.",
"verify_email_template": "Шаблон електронного листа для підтвердження", "verify_email_template": "Шаблон електронного листа для підтвердження",
"verify_email_templateDescription": "Заповнювачі {after}.variable{before} будуть замінені на фактичні дані. Переконайтеся, що зберегли ці змінні.", "verify_email_templateDescription": "Заповнювачі {after}.variable{before} будуть замінені на фактичні дані. Переконайтеся, що зберегли ці змінні.",
"whitelistSuffixes": "Білий список суфіксів", "whitelistSuffixes": "Білий список суфіксів",
+2
View File
@@ -13,6 +13,8 @@
"title": "Політика конфіденційності" "title": "Політика конфіденційності"
}, },
"site": { "site": {
"customData": "Користувацькі дані",
"customDataDescription": "Користувацькі дані, що використовуються для користувацьких даних сайту",
"customHtml": "Користувацький HTML", "customHtml": "Користувацький HTML",
"customHtmlDescription": "Користувацький HTML-код, який буде вставлено в кінець тегу body сайту.", "customHtmlDescription": "Користувацький HTML-код, який буде вставлено в кінець тегу body сайту.",
"keywords": "Ключові слова", "keywords": "Ключові слова",
+2
View File
@@ -39,6 +39,8 @@
"status": "Trạng thái", "status": "Trạng thái",
"subject": "Chủ đề", "subject": "Chủ đề",
"template": "Mẫu", "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_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.", "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ố", "whitelistSuffixes": "Danh sách trắng Hậu tố",
+2
View File
@@ -13,6 +13,8 @@
"title": "Chính sách bảo mật" "title": "Chính sách bảo mật"
}, },
"site": { "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", "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.", "customHtmlDescription": "Mã HTML tùy chỉnh để được chèn vào cuối thẻ body của trang.",
"keywords": "Từ khóa", "keywords": "Từ khóa",
+2
View File
@@ -39,6 +39,8 @@
"status": "状态", "status": "状态",
"subject": "主题", "subject": "主题",
"template": "模板", "template": "模板",
"traffic_exceed_email_template": "流量超限通知模板",
"traffic_exceed_email_templateDescription": "{after}和{before}变量将被实际数据替换。请确保保留这些变量。",
"verify_email_template": "验证电子邮件模板", "verify_email_template": "验证电子邮件模板",
"verify_email_templateDescription": "{after}.variable{before} 占位符将被实际数据替换。请确保保留这些变量。", "verify_email_templateDescription": "{after}.variable{before} 占位符将被实际数据替换。请确保保留这些变量。",
"whitelistSuffixes": "白名单后缀", "whitelistSuffixes": "白名单后缀",
+2
View File
@@ -13,6 +13,8 @@
"title": "隐私政策" "title": "隐私政策"
}, },
"site": { "site": {
"customData": "自定义数据",
"customDataDescription": "自定义数据,用于网站的自定义数据",
"customHtml": "自定义HTML", "customHtml": "自定义HTML",
"customHtmlDescription": "要注入到网站 body 标签底部的自定义 HTML 代码。", "customHtmlDescription": "要注入到网站 body 标签底部的自定义 HTML 代码。",
"keywords": "关键词", "keywords": "关键词",
+2
View File
@@ -39,6 +39,8 @@
"status": "狀態", "status": "狀態",
"subject": "主題", "subject": "主題",
"template": "模板", "template": "模板",
"traffic_exceed_email_template": "流量超標通知模板",
"traffic_exceed_email_templateDescription": "{after}和{before}變量將會被實際數據替換。請確保保留這些變量。",
"verify_email_template": "驗證電郵範本", "verify_email_template": "驗證電郵範本",
"verify_email_templateDescription": "{after}.variable{before} 佔位符將被實際數據替換。請確保保留這些變數。", "verify_email_templateDescription": "{after}.variable{before} 佔位符將被實際數據替換。請確保保留這些變數。",
"whitelistSuffixes": "白名單後綴", "whitelistSuffixes": "白名單後綴",
+2
View File
@@ -13,6 +13,8 @@
"title": "私隱政策" "title": "私隱政策"
}, },
"site": { "site": {
"customData": "自定義數據",
"customDataDescription": "自定義數據,用於網站的自定義數據",
"customHtml": "自定義HTML", "customHtml": "自定義HTML",
"customHtmlDescription": "自定義 HTML 代碼,將注入到網站 body 標籤的底部。", "customHtmlDescription": "自定義 HTML 代碼,將注入到網站 body 標籤的底部。",
"keywords": "關鍵字", "keywords": "關鍵字",
+23
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;
@@ -1253,6 +1269,13 @@ declare namespace API {
site_logo: string; site_logo: string;
keywords: string; keywords: string;
custom_html: string; custom_html: string;
custom_data: string;
};
type SiteCustomDataContacts = {
email: string;
telephone: string;
address: string;
}; };
type SortItem = { type SortItem = {
+23
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;
@@ -643,6 +659,13 @@ declare namespace API {
site_logo: string; site_logo: string;
keywords: string; keywords: string;
custom_html: string; custom_html: string;
custom_data: string;
};
type SiteCustomDataContacts = {
email: string;
telephone: string;
address: string;
}; };
type SortItem = { 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'> <CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'>
<CardTitle className='font-medium'> <CardTitle className='font-medium'>
{item.subscribe.name} {item.subscribe.name}
<p className='text-foreground/50 mt-1 text-sm'> <p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
{formatDate(item.subscribe.created_at)}
</p>
</CardTitle> </CardTitle>
<div className='flex flex-wrap gap-2'> <div className='flex flex-wrap gap-2'>
<AlertDialog> <AlertDialog>
+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
View File
@@ -22,6 +22,7 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
site_logo: '', site_logo: '',
keywords: '', keywords: '',
custom_html: '', custom_html: '',
custom_data: '',
}, },
verify: { verify: {
turnstile_site_key: '', turnstile_site_key: '',
+23
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;
@@ -643,6 +659,13 @@ declare namespace API {
site_logo: string; site_logo: string;
keywords: string; keywords: string;
custom_html: string; custom_html: string;
custom_data: string;
};
type SiteCustomDataContacts = {
email: string;
telephone: string;
address: string;
}; };
type SortItem = { type SortItem = {
+12 -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;
}; };
@@ -773,6 +777,13 @@ declare namespace API {
site_logo: string; site_logo: string;
keywords: string; keywords: string;
custom_html: string; custom_html: string;
custom_data: string;
};
type SiteCustomDataContacts = {
email: string;
telephone: string;
address: string;
}; };
type SortItem = { type SortItem = {
+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.31",
"private": true, "private": true,
"homepage": "https://github.com/perfect-panel/ppanel-web", "homepage": "https://github.com/perfect-panel/ppanel-web",
"bugs": { "bugs": {
+6 -6
View File
@@ -28,25 +28,25 @@ export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]); const opacityTransform = useTransform(scrollYProgress, [0, 0.1], [0, 1]);
return ( return (
<div className='w-full bg-white font-sans md:px-10 dark:bg-neutral-950' ref={containerRef}> <div className='w-full font-sans md:px-10' ref={containerRef}>
<div className='mx-auto max-w-7xl px-4 py-20 md:px-8 lg:px-10'> <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'> {/* <h2 className='mb-4 max-w-4xl text-lg text-black md:text-4xl dark:text-white'>
Changelog from my journey Changelog from my journey
</h2> </h2>
<p className='max-w-sm text-sm text-neutral-700 md:text-base dark:text-neutral-300'> <p className='max-w-sm text-sm text-neutral-700 md:text-base dark:text-neutral-300'>
I&apos;ve been working on Aceternity for the past 2 years. Here&apos;s a timeline of my I&apos;ve been working on Aceternity for the past 2 years. Here&apos;s a timeline of my
journey. journey.
</p> </p> */}
</div> </div>
<div ref={ref} className='relative mx-auto max-w-7xl pb-20'> <div ref={ref} className='relative mx-auto max-w-7xl pb-20'>
{data.map((item, index) => ( {data.map((item, index) => (
<div key={index} className='flex justify-start pt-10 md:gap-10 md:pt-40'> <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='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 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> </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} {item.title}
</h3> </h3>
</div> </div>
@@ -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) {
if (props.type === 'number') {
if (inputValue === '0') {
setValue('');
setInternalValue(0);
onValueChange?.(0);
return;
}
if (/^-?\d*\.?\d*$/.test(inputValue) || inputValue === '-' || inputValue === '.') {
const numericValue = Number(inputValue); const numericValue = Number(inputValue);
if (!isNaN(numericValue)) { if (!isNaN(numericValue) && inputValue !== '-' && inputValue !== '.') {
const min = Number.isFinite(props.min) ? props.min : -Infinity; const min = Number.isFinite(props.min) ? props.min : -Infinity;
const max = Number.isFinite(props.max) ? props.max : Infinity; const max = Number.isFinite(props.max) ? props.max : Infinity;
inputValue = String(Math.max(min!, Math.min(max!, numericValue))); 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'
+2 -2
View File
@@ -28,6 +28,6 @@ export function formatDate(date?: Date | number, showTime: boolean = true) {
export function differenceInDays(dateLeft: Date | number, dateRight: Date | number) { export function differenceInDays(dateLeft: Date | number, dateRight: Date | number) {
const diffInMs = differenceInMilliseconds(dateLeft, dateRight); const diffInMs = differenceInMilliseconds(dateLeft, dateRight);
const diffInDays = diffInMs / (1000 * 60 * 60 * 24); const diffInDays = diffInMs / (1000 * 60 * 60 * 24);
if (diffInDays % 1 === 0) return diffInDays; if (diffInDays >= 1) return diffInDays.toFixed(0);
return Number(diffInDays.toFixed(1)); return Number(diffInDays.toFixed(2));
} }
+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);
} }