Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 383638f702 | |||
| 5939763b57 | |||
| fa56eb8293 | |||
| 6a34bfb78d | |||
| b8f4f1e694 | |||
| aa1d42651d | |||
| 0c8f0911c7 | |||
| db0d9e003e | |||
| f219c52306 | |||
| 3f7b6d16ed | |||
| 6dbebd152d | |||
| bb3bd7b50c | |||
| fbad3b0e51 | |||
| e551232564 | |||
| bf58f25072 | |||
| 85f55def2e | |||
| 29bc3c7722 | |||
| f72df3a5e8 | |||
| 48a1b97051 | |||
| 786ba0e41c | |||
| ce3197298d | |||
| 092477b2d3 | |||
| b8316bba33 | |||
| dddc21c686 | |||
| d10ecc9fdf | |||
| 94822d902f | |||
| 85fdc36c14 | |||
| cfa3fc024d | |||
| c06ea49d6f | |||
| c3138a863d | |||
| 7fa3a57df4 | |||
| 70d6a38a29 | |||
| 5c710e1add | |||
| 6752420ba5 | |||
| 710947209a | |||
| fc0da761b5 | |||
| 136a1abd8b | |||
| a5e2079ddc | |||
| 35f92c9329 | |||
| f5d8fd3b65 | |||
| 0c907337e1 | |||
| e4630f8ca9 | |||
| 2215c7f2b9 | |||
| 3222016799 | |||
| d0a04679ce | |||
| d2732e650b | |||
| 79edea7973 | |||
| f17bf8db6e | |||
| 047a69886e | |||
| 71bf002370 | |||
| d0e6df044e | |||
| 8e16ef17ed | |||
| 2b80496637 | |||
| 01a3aa02ec | |||
| 35210fe8cf | |||
| d5847faeda | |||
| 0130e02ffd | |||
| c2b858e3fe | |||
| e11f18c034 | |||
| 2ca299224d | |||
| cf1d66d07e | |||
| 62662bb79c | |||
| c0fb34fa50 | |||
| 68f6ab2e6c | |||
| ca642c2001 | |||
| 5b151cdbfc | |||
| 14b3af5f96 | |||
| baa68f03e3 | |||
| 87381da1e5 | |||
| bbd44f0107 | |||
| c65a44c2c7 | |||
| 715d0113ca | |||
| 0f15fb82cb | |||
| 320a7dc342 | |||
| 42ba9e88ad | |||
| 4a002333d4 | |||
| 6384237782 | |||
| 557c5cdc29 | |||
| f9d7736e48 | |||
| 5b64389544 | |||
| 23833b47d1 | |||
| c3eff0a0e7 | |||
| 6f7bc37233 | |||
| b2e8fadaf3 | |||
| 315c8f9afe | |||
| 0fb0d8bc11 | |||
| c356bc26ff | |||
| 23c974a94d | |||
| ee286dd629 |
+316
-40
@@ -1,76 +1,352 @@
|
||||
<a name="readme-top"></a>
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.33](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.32...v1.0.0-beta.33) (2025-03-18)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **subscribe**: Handle optional values in price and discount calculations ([5939763](https://github.com/perfect-panel/ppanel-web/commit/5939763))
|
||||
|
||||
# [1.0.0-beta.32](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.31...v1.0.0-beta.32) (2025-03-17)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **forms**: Add step attribute to number inputs for better value control ([b8f4f1e](https://github.com/perfect-panel/ppanel-web/commit/b8f4f1e))
|
||||
* **locales**: Update invite code text to indicate it's optional ([6a34bfb](https://github.com/perfect-panel/ppanel-web/commit/6a34bfb))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [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))
|
||||
|
||||
# [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))
|
||||
|
||||
<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>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.26](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.25...v1.0.0-beta.26) (2025-03-02)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
|
||||
|
||||
# [1.0.0-beta.25](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.24...v1.0.0-beta.25) (2025-03-01)
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
|
||||
- **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
|
||||
- **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
|
||||
|
||||
# [1.0.0-beta.24](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2025-02-27)
|
||||
|
||||
### ♻ Code Refactoring
|
||||
|
||||
- **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
|
||||
- Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
|
||||
|
||||
### 🎫 Chores
|
||||
|
||||
- **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.23](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.22...v1.0.0-beta.23) (2025-02-24)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Update email verification logic to use domain suffix check ([62662bb](https://github.com/perfect-panel/ppanel-web/commit/62662bb))
|
||||
|
||||
# [1.0.0-beta.22](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.21...v1.0.0-beta.22) (2025-02-23)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **locales**: Removed language file import to clean up unnecessary language support ([68f6ab2](https://github.com/perfect-panel/ppanel-web/commit/68f6ab2))
|
||||
|
||||
# [1.0.0-beta.21](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.20...v1.0.0-beta.21) (2025-02-23)
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **privacy-policy**: Add privacy policy related text and links ([baa68f0](https://github.com/perfect-panel/ppanel-web/commit/baa68f0))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **locales**: Removed multilingual files to clean up unnecessary language support ([5b151cd](https://github.com/perfect-panel/ppanel-web/commit/5b151cd))
|
||||
- **locales**: Update custom HTML description in language file, ([87381da](https://github.com/perfect-panel/ppanel-web/commit/87381da))
|
||||
- **table**: Update privacy policy tab translation key and remove unnecessary requestType from OAuth callback ([14b3af5](https://github.com/perfect-panel/ppanel-web/commit/14b3af5))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.20](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.19...v1.0.0-beta.20) (2025-02-23)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Add error handling to form submission and reset Turnstile on failure ([715d011](https://github.com/perfect-panel/ppanel-web/commit/715d011))
|
||||
- **subscribe**: Update subscription domain placeholder to include examples; improve site name retrieval in global store ([c65a44c](https://github.com/perfect-panel/ppanel-web/commit/c65a44c))
|
||||
|
||||
# [1.0.0-beta.19](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.18...v1.0.0-beta.19) (2025-02-23)
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **form**: Make version field optional and set default value; update site domain placeholder for clarity ([42ba9e8](https://github.com/perfect-panel/ppanel-web/commit/42ba9e8))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Refactor forms to use Turnstile ref for reset functionality ([320a7dc](https://github.com/perfect-panel/ppanel-web/commit/320a7dc))
|
||||
|
||||
# [1.0.0-beta.18](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.17...v1.0.0-beta.18) (2025-02-22)
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- **platform**: Update platform naming and add keywords and custom HTML fields ([6384237](https://github.com/perfect-panel/ppanel-web/commit/6384237))
|
||||
- **site**: Added localization support for custom HTML and keyword fields ([f9d7736](https://github.com/perfect-panel/ppanel-web/commit/f9d7736))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **locales**: Update custom HTML description for clarity across multiple languages ([557c5cd](https://github.com/perfect-panel/ppanel-web/commit/557c5cd))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.17](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.16...v1.0.0-beta.17) (2025-02-21)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Refactor reset password form to simplify code input and update placeholder text ([23833b4](https://github.com/perfect-panel/ppanel-web/commit/23833b4))
|
||||
|
||||
# [1.0.0-beta.16](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2025-02-21)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **auth**: Simplify email verification code input rendering ([6f7bc37](https://github.com/perfect-panel/ppanel-web/commit/6f7bc37))
|
||||
|
||||
# [1.0.0-beta.15](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.14...v1.0.0-beta.15) (2025-02-21)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **profile**: Restore filter to ensure only valid OAuth accounts are shown ([315c8f9](https://github.com/perfect-panel/ppanel-web/commit/315c8f9))
|
||||
|
||||
# [1.0.0-beta.14](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.13...v1.0.0-beta.14) (2025-02-21)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **locales**: Fixed description in multilingual files, updated text related to email registration functionality ([c356bc2](https://github.com/perfect-panel/ppanel-web/commit/c356bc2))
|
||||
|
||||
# [1.0.0-beta.13](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.12...v1.0.0-beta.13) (2025-02-21)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **api**: Fix type error in API request and add return URL parameter ([ee286dd](https://github.com/perfect-panel/ppanel-web/commit/ee286dd))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
# Changelog
|
||||
|
||||
# [1.0.0-beta.12](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2025-02-18)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* More bugs ([2d88a3a](https://github.com/perfect-panel/ppanel-web/commit/2d88a3a))
|
||||
- More bugs ([2d88a3a](https://github.com/perfect-panel/ppanel-web/commit/2d88a3a))
|
||||
|
||||
# [1.0.0-beta.11](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2025-02-16)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **email**: Update platform configuration handling to use current ref for consistency ([c90175b](https://github.com/perfect-panel/ppanel-web/commit/c90175b))
|
||||
- **email**: Update platform configuration handling to use current ref for consistency ([c90175b](https://github.com/perfect-panel/ppanel-web/commit/c90175b))
|
||||
|
||||
# [1.0.0-beta.10](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2025-02-16)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **subscribe**: Update forms to include refetch functionality and improve toast messages ([fc55e95](https://github.com/perfect-panel/ppanel-web/commit/fc55e95))
|
||||
- **subscribe**: Update forms to include refetch functionality and improve toast messages ([fc55e95](https://github.com/perfect-panel/ppanel-web/commit/fc55e95))
|
||||
|
||||
# [1.0.0-beta.9](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2025-02-15)
|
||||
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **register**: Adjust user email verification logic to handle domain suffix checks correctly ([686aa2d](https://github.com/perfect-panel/ppanel-web/commit/686aa2d))
|
||||
- **register**: Adjust user email verification logic to handle domain suffix checks correctly ([686aa2d](https://github.com/perfect-panel/ppanel-web/commit/686aa2d))
|
||||
|
||||
# [1.0.0-beta.8](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2025-02-15)
|
||||
|
||||
|
||||
### ✨ Features
|
||||
|
||||
* **accounts**: Update third-party account binding and unbinding ([1841552](https://github.com/perfect-panel/ppanel-web/commit/1841552))
|
||||
* **auth-control**: Adding phone number labels to mobile verification configurations in multiple languages ([046740f](https://github.com/perfect-panel/ppanel-web/commit/046740f))
|
||||
* **auth-control**: Update general ([3883646](https://github.com/perfect-panel/ppanel-web/commit/3883646))
|
||||
* **auth**: Add type parameter to SendCode and update related API typings ([4198871](https://github.com/perfect-panel/ppanel-web/commit/4198871))
|
||||
* **auth**: Refactor mobile authentication config to support whitelist functionality ([c761ec7](https://github.com/perfect-panel/ppanel-web/commit/c761ec7))
|
||||
* **device**: Modify IMEI to device identifier support ([e3f9ef6](https://github.com/perfect-panel/ppanel-web/commit/e3f9ef6))
|
||||
* **imei**: Add IMEI related internationalization support and menu items ([13c3337](https://github.com/perfect-panel/ppanel-web/commit/13c3337))
|
||||
* **locales**: Add kick offline confirmation and success messages in multiple languages ([5db5343](https://github.com/perfect-panel/ppanel-web/commit/5db5343))
|
||||
* **locales**: Update 'sms' to 'mobile' in authentication methods across multiple languages ([fea2171](https://github.com/perfect-panel/ppanel-web/commit/fea2171))
|
||||
* **log**: Add message log retrieval functionality and update related typings ([1c0ecae](https://github.com/perfect-panel/ppanel-web/commit/1c0ecae))
|
||||
* **profile**: Update localization strings and enhance third-party account binding ([2d1effb](https://github.com/perfect-panel/ppanel-web/commit/2d1effb))
|
||||
* **release**: Extend supported platforms for Docker images, closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([e3a31eb](https://github.com/perfect-panel/ppanel-web/commit/e3a31eb))
|
||||
* **subscription**: Add delete user subscription functionality ([1fc3a10](https://github.com/perfect-panel/ppanel-web/commit/1fc3a10))
|
||||
* **ui**: Update input components and enhance card minimum width for better layout ([8a02310](https://github.com/perfect-panel/ppanel-web/commit/8a02310))
|
||||
* **user**: Add user Detail ([3a3d223](https://github.com/perfect-panel/ppanel-web/commit/3a3d223))
|
||||
* **user**: Add User Detail ([fdaf11b](https://github.com/perfect-panel/ppanel-web/commit/fdaf11b))
|
||||
|
||||
- **accounts**: Update third-party account binding and unbinding ([1841552](https://github.com/perfect-panel/ppanel-web/commit/1841552))
|
||||
- **auth-control**: Adding phone number labels to mobile verification configurations in multiple languages ([046740f](https://github.com/perfect-panel/ppanel-web/commit/046740f))
|
||||
- **auth-control**: Update general ([3883646](https://github.com/perfect-panel/ppanel-web/commit/3883646))
|
||||
- **auth**: Add type parameter to SendCode and update related API typings ([4198871](https://github.com/perfect-panel/ppanel-web/commit/4198871))
|
||||
- **auth**: Refactor mobile authentication config to support whitelist functionality ([c761ec7](https://github.com/perfect-panel/ppanel-web/commit/c761ec7))
|
||||
- **device**: Modify IMEI to device identifier support ([e3f9ef6](https://github.com/perfect-panel/ppanel-web/commit/e3f9ef6))
|
||||
- **imei**: Add IMEI related internationalization support and menu items ([13c3337](https://github.com/perfect-panel/ppanel-web/commit/13c3337))
|
||||
- **locales**: Add kick offline confirmation and success messages in multiple languages ([5db5343](https://github.com/perfect-panel/ppanel-web/commit/5db5343))
|
||||
- **locales**: Update 'sms' to 'mobile' in authentication methods across multiple languages ([fea2171](https://github.com/perfect-panel/ppanel-web/commit/fea2171))
|
||||
- **log**: Add message log retrieval functionality and update related typings ([1c0ecae](https://github.com/perfect-panel/ppanel-web/commit/1c0ecae))
|
||||
- **profile**: Update localization strings and enhance third-party account binding ([2d1effb](https://github.com/perfect-panel/ppanel-web/commit/2d1effb))
|
||||
- **release**: Extend supported platforms for Docker images, closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([e3a31eb](https://github.com/perfect-panel/ppanel-web/commit/e3a31eb))
|
||||
- **subscription**: Add delete user subscription functionality ([1fc3a10](https://github.com/perfect-panel/ppanel-web/commit/1fc3a10))
|
||||
- **ui**: Update input components and enhance card minimum width for better layout ([8a02310](https://github.com/perfect-panel/ppanel-web/commit/8a02310))
|
||||
- **user**: Add user Detail ([3a3d223](https://github.com/perfect-panel/ppanel-web/commit/3a3d223))
|
||||
- **user**: Add User Detail ([fdaf11b](https://github.com/perfect-panel/ppanel-web/commit/fdaf11b))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
* **auth-control**: Fix citation error for platform values ([c940f3c](https://github.com/perfect-panel/ppanel-web/commit/c940f3c))
|
||||
* **auth-control**: Fix citation error for platform values ([28813d2](https://github.com/perfect-panel/ppanel-web/commit/28813d2))
|
||||
* **auth-control**: Rename phone_variable to phone_number in mobile verification configuration ([e5455aa](https://github.com/perfect-panel/ppanel-web/commit/e5455aa))
|
||||
* **auth**: Update authentication configuration and localization strings ([47f2c58](https://github.com/perfect-panel/ppanel-web/commit/47f2c58))
|
||||
* **locales**: Update expiration time description from minutes to seconds in multiple languages ([5bac933](https://github.com/perfect-panel/ppanel-web/commit/5bac933))
|
||||
* **notify**: Ensure user info is updated after notification settings submission ([9bc3a94](https://github.com/perfect-panel/ppanel-web/commit/9bc3a94))
|
||||
* **notify**: Set default values for notification settings to false ([3652819](https://github.com/perfect-panel/ppanel-web/commit/3652819))
|
||||
* **third-party-accounts**: Remove mobile display logic from third-party accounts component ([b4946f7](https://github.com/perfect-panel/ppanel-web/commit/b4946f7))
|
||||
* **third-party-accounts**: Update redirect property name in binding response handling ([012e83a](https://github.com/perfect-panel/ppanel-web/commit/012e83a))
|
||||
* **user**: Refactor user form validation and reset password fields ([6733fc2](https://github.com/perfect-panel/ppanel-web/commit/6733fc2))
|
||||
* **user**: Update locales ([4e7d249](https://github.com/perfect-panel/ppanel-web/commit/4e7d249))
|
||||
* **user**: Update notification and verify code settings ([574b043](https://github.com/perfect-panel/ppanel-web/commit/574b043))
|
||||
|
||||
- **auth-control**: Fix citation error for platform values ([c940f3c](https://github.com/perfect-panel/ppanel-web/commit/c940f3c))
|
||||
- **auth-control**: Fix citation error for platform values ([28813d2](https://github.com/perfect-panel/ppanel-web/commit/28813d2))
|
||||
- **auth-control**: Rename phone_variable to phone_number in mobile verification configuration ([e5455aa](https://github.com/perfect-panel/ppanel-web/commit/e5455aa))
|
||||
- **auth**: Update authentication configuration and localization strings ([47f2c58](https://github.com/perfect-panel/ppanel-web/commit/47f2c58))
|
||||
- **locales**: Update expiration time description from minutes to seconds in multiple languages ([5bac933](https://github.com/perfect-panel/ppanel-web/commit/5bac933))
|
||||
- **notify**: Ensure user info is updated after notification settings submission ([9bc3a94](https://github.com/perfect-panel/ppanel-web/commit/9bc3a94))
|
||||
- **notify**: Set default values for notification settings to false ([3652819](https://github.com/perfect-panel/ppanel-web/commit/3652819))
|
||||
- **third-party-accounts**: Remove mobile display logic from third-party accounts component ([b4946f7](https://github.com/perfect-panel/ppanel-web/commit/b4946f7))
|
||||
- **third-party-accounts**: Update redirect property name in binding response handling ([012e83a](https://github.com/perfect-panel/ppanel-web/commit/012e83a))
|
||||
- **user**: Refactor user form validation and reset password fields ([6733fc2](https://github.com/perfect-panel/ppanel-web/commit/6733fc2))
|
||||
- **user**: Update locales ([4e7d249](https://github.com/perfect-panel/ppanel-web/commit/4e7d249))
|
||||
- **user**: Update notification and verify code settings ([574b043](https://github.com/perfect-panel/ppanel-web/commit/574b043))
|
||||
|
||||
### 💄 Styles
|
||||
|
||||
* **dashboard**: Enhance card components with full height and improved empty state handling ([7e1d551](https://github.com/perfect-panel/ppanel-web/commit/7e1d551))
|
||||
- **dashboard**: Enhance card components with full height and improved empty state handling ([7e1d551](https://github.com/perfect-panel/ppanel-web/commit/7e1d551))
|
||||
|
||||
<a name="readme-top"></a>
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@workspace/
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import CloudFlareTurnstile from '../turnstile';
|
||||
import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
|
||||
|
||||
export default function LoginForm({
|
||||
loading,
|
||||
@@ -38,10 +38,19 @@ export default function LoginForm({
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
|
||||
const turnstile = useRef<TurnstileRef>(null);
|
||||
const handleSubmit = form.handleSubmit((data) => {
|
||||
try {
|
||||
onSubmit(data);
|
||||
} catch (error) {
|
||||
turnstile.current?.reset();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'>
|
||||
<form onSubmit={handleSubmit} className='grid gap-6'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
@@ -73,7 +82,7 @@ export default function LoginForm({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CloudFlareTurnstile id='login' {...field} />
|
||||
<CloudFlareTurnstile id='login' {...field} ref={turnstile} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -6,11 +6,11 @@ import { Input } from '@workspace/ui/components/input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { Markdown } from '@workspace/ui/custom-components/markdown';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import SendCode from '../send-code';
|
||||
import CloudFlareTurnstile from '../turnstile';
|
||||
import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
|
||||
|
||||
export default function RegisterForm({
|
||||
loading,
|
||||
@@ -31,11 +31,10 @@ export default function RegisterForm({
|
||||
|
||||
const handleCheckUser = async (email: string) => {
|
||||
try {
|
||||
if (!auth.email.enable_domain_suffix) return true;
|
||||
const domain = email.split('@')[1];
|
||||
const isValid =
|
||||
!auth.email.enable_verify ||
|
||||
auth.email?.domain_suffix_list.split('\n').includes(domain || '');
|
||||
return !isValid;
|
||||
const isValid = auth.email?.domain_suffix_list.split('\n').includes(domain || '');
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
console.log('Error checking user:', error);
|
||||
return false;
|
||||
@@ -53,7 +52,7 @@ export default function RegisterForm({
|
||||
password: z.string(),
|
||||
repeat_password: z.string(),
|
||||
code: auth.email.enable_verify ? z.string() : z.string().nullish(),
|
||||
invite: invite.forced_invite ? z.string() : z.string().nullish(),
|
||||
invite: invite.forced_invite ? z.string().min(1) : z.string().nullish(),
|
||||
cf_token:
|
||||
verify.enable_register_verify && verify.turnstile_site_key
|
||||
? z.string()
|
||||
@@ -77,13 +76,22 @@ export default function RegisterForm({
|
||||
},
|
||||
});
|
||||
|
||||
const turnstile = useRef<TurnstileRef>(null);
|
||||
const handleSubmit = form.handleSubmit((data) => {
|
||||
try {
|
||||
onSubmit(data);
|
||||
} catch (error) {
|
||||
turnstile.current?.reset();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{auth.register.stop_register ? (
|
||||
<Markdown>{t('message')}</Markdown>
|
||||
) : (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'>
|
||||
<form onSubmit={handleSubmit} className='grid gap-6'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
@@ -114,7 +122,12 @@ export default function RegisterForm({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input placeholder='Enter password again...' type='password' {...field} />
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder='Enter password again...'
|
||||
type='password'
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -129,6 +142,7 @@ export default function RegisterForm({
|
||||
<FormControl>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder='Enter code...'
|
||||
type='text'
|
||||
{...field}
|
||||
@@ -172,7 +186,7 @@ export default function RegisterForm({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CloudFlareTurnstile id='register' {...field} />
|
||||
<CloudFlareTurnstile id='register' {...field} ref={turnstile} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@workspace/
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Dispatch, SetStateAction, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import SendCode from '../send-code';
|
||||
import CloudFlareTurnstile from '../turnstile';
|
||||
import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
|
||||
|
||||
export default function ResetForm({
|
||||
loading,
|
||||
@@ -43,10 +43,19 @@ export default function ResetForm({
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
|
||||
const turnstile = useRef<TurnstileRef>(null);
|
||||
const handleSubmit = form.handleSubmit((data) => {
|
||||
try {
|
||||
onSubmit(data);
|
||||
} catch (error) {
|
||||
turnstile.current?.reset();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'>
|
||||
<form onSubmit={handleSubmit} className='grid gap-6'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
@@ -59,20 +68,6 @@ export default function ResetForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='password'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input placeholder='Enter your password...' type='password' {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{auth?.email?.enable_verify && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='code'
|
||||
@@ -81,6 +76,7 @@ export default function ResetForm({
|
||||
<FormControl>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Input
|
||||
disabled={loading}
|
||||
placeholder='Enter code...'
|
||||
type='text'
|
||||
{...field}
|
||||
@@ -99,7 +95,18 @@ export default function ResetForm({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='password'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input placeholder='Enter your new password...' type='password' {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{verify.enable_reset_password_verify && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -107,7 +114,7 @@ export default function ResetForm({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<CloudFlareTurnstile id='reset' {...field} />
|
||||
<CloudFlareTurnstile id='reset' {...field} ref={turnstile} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect } from 'react';
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||
import Turnstile, { useTurnstile } from 'react-turnstile';
|
||||
|
||||
export default function CloudFlareTurnstile({
|
||||
id,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
|
||||
export type TurnstileRef = {
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
const CloudFlareTurnstile = forwardRef<
|
||||
TurnstileRef,
|
||||
{
|
||||
id?: string;
|
||||
value?: null | string;
|
||||
onChange: (value?: string) => void;
|
||||
}) {
|
||||
}
|
||||
>(function CloudFlareTurnstile({ id, value, onChange }, ref) {
|
||||
const { common } = useGlobalStore();
|
||||
const { verify } = common;
|
||||
const { resolvedTheme } = useTheme();
|
||||
const locale = useLocale();
|
||||
const turnstile = useTurnstile();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
reset: () => turnstile.reset(),
|
||||
}),
|
||||
[turnstile],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (value === '') {
|
||||
turnstile.reset();
|
||||
@@ -51,4 +63,6 @@ export default function CloudFlareTurnstile({
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default CloudFlareTurnstile;
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@workspace/ui/components/form';
|
||||
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@workspace/ui/components/sheet';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z.string(),
|
||||
type: z.enum(['image', 'video']),
|
||||
content: z.string(),
|
||||
description: z.string(),
|
||||
target_url: z.string().url(),
|
||||
start_time: z.number(),
|
||||
end_time: z.number(),
|
||||
});
|
||||
|
||||
interface AdsFormProps<T> {
|
||||
onSubmit: (data: T) => Promise<boolean> | boolean;
|
||||
initialValues?: T;
|
||||
loading?: boolean;
|
||||
trigger: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default function AdsForm<T extends Record<string, any>>({
|
||||
onSubmit,
|
||||
initialValues,
|
||||
loading,
|
||||
trigger,
|
||||
title,
|
||||
}: AdsFormProps<T>) {
|
||||
const t = useTranslations('ads');
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
...initialValues,
|
||||
} as any,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form?.reset(initialValues);
|
||||
}, [form, initialValues]);
|
||||
|
||||
const type = form.watch('type');
|
||||
const startTime = form.watch('start_time');
|
||||
|
||||
const renderContentField = () => {
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='content'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.content')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={
|
||||
type === 'image'
|
||||
? 'https://example.com/image.jpg'
|
||||
: 'https://example.com/video.mp4'
|
||||
}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue('content', value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
async function handleSubmit(data: { [x: string]: any }) {
|
||||
const bool = await onSubmit(data as T);
|
||||
if (bool) setOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.reset();
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{trigger}
|
||||
</Button>
|
||||
</SheetTrigger>
|
||||
<SheetContent className='w-[500px] max-w-full md:max-w-screen-md'>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{title}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<ScrollArea className='-mx-6 h-[calc(100vh-48px-36px-36px-env(safe-area-inset-top))]'>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-4 px-6 pt-4'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='title'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.title')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('form.enterTitle')}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='type'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.type')}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
defaultValue={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
className='flex gap-4'
|
||||
>
|
||||
<FormItem className='flex items-center space-x-3 space-y-0'>
|
||||
<FormControl>
|
||||
<RadioGroupItem value='image' />
|
||||
</FormControl>
|
||||
<FormLabel className='font-normal'>{t('form.typeImage')}</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className='flex items-center space-x-3 space-y-0'>
|
||||
<FormControl>
|
||||
<RadioGroupItem value='video' />
|
||||
</FormControl>
|
||||
<FormLabel className='font-normal'>{t('form.typeVideo')}</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{renderContentField()}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='description'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.description')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('form.enterDescription')}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='target_url'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.targetUrl')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('form.enterTargetUrl')}
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='start_time'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.startTime')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='datetime-local'
|
||||
placeholder={t('form.enterStartTime')}
|
||||
value={field.value ? new Date(field.value).toISOString().slice(0, 16) : ''}
|
||||
min={Number(new Date().toISOString().slice(0, 16))}
|
||||
onValueChange={(value) => {
|
||||
const timestamp = value ? new Date(value).getTime() : 0;
|
||||
form.setValue(field.name, timestamp);
|
||||
const endTime = form.getValues('end_time');
|
||||
if (endTime && timestamp > endTime) {
|
||||
form.setValue('end_time', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='end_time'
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.endTime')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='datetime-local'
|
||||
placeholder={t('form.enterEndTime')}
|
||||
value={
|
||||
field.value ? new Date(field.value).toISOString().slice(0, 16) : ''
|
||||
}
|
||||
min={Number(
|
||||
startTime
|
||||
? new Date(startTime).toISOString().slice(0, 16)
|
||||
: new Date().toISOString().slice(0, 16),
|
||||
)}
|
||||
disabled={!startTime}
|
||||
onValueChange={(value) => {
|
||||
const timestamp = value ? new Date(value).getTime() : 0;
|
||||
if (!startTime || timestamp < startTime) return;
|
||||
form.setValue(field.name, timestamp);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</ScrollArea>
|
||||
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
|
||||
<Button
|
||||
variant='outline'
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{t('form.cancel')}
|
||||
</Button>
|
||||
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
|
||||
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
|
||||
{t('form.confirm')}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
'use client';
|
||||
|
||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||
import { createAds, deleteAds, getAdsList, updateAds } from '@/services/admin/ads';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||
import { formatDate } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import AdsForm from './ads-form';
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('ads');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const ref = useRef<ProTableActions>(null);
|
||||
|
||||
return (
|
||||
<ProTable<API.Ads, Record<string, unknown>>
|
||||
action={ref}
|
||||
header={{
|
||||
toolbar: (
|
||||
<AdsForm<API.CreateAdsRequest>
|
||||
trigger={t('create')}
|
||||
title={t('createAds')}
|
||||
loading={loading}
|
||||
onSubmit={async (values) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await createAds({
|
||||
...values,
|
||||
status: 0,
|
||||
});
|
||||
toast.success(t('createSuccess'));
|
||||
ref.current?.refresh();
|
||||
setLoading(false);
|
||||
return true;
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
params={[
|
||||
{
|
||||
key: 'search',
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
placeholder: t('status'),
|
||||
options: [
|
||||
{ label: t('enabled'), value: '1' },
|
||||
{ label: t('disabled'), value: '0' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
request={async (pagination, filters) => {
|
||||
const { data } = await getAdsList({
|
||||
...pagination,
|
||||
...filters,
|
||||
});
|
||||
return {
|
||||
list: data.data?.list || [],
|
||||
total: data.data?.total || 0,
|
||||
};
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: t('status'),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Switch
|
||||
defaultChecked={row.getValue('status') === 1}
|
||||
onCheckedChange={async (checked) => {
|
||||
await updateAds({
|
||||
...row.original,
|
||||
status: checked ? 1 : 0,
|
||||
});
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'title',
|
||||
header: t('title'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: t('type'),
|
||||
cell: ({ row }) => {
|
||||
const type = row.original.type;
|
||||
return <Badge>{type}</Badge>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'target_url',
|
||||
header: t('targetUrl'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: t('form.description'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'period',
|
||||
header: t('validityPeriod'),
|
||||
cell: ({ row }) => {
|
||||
const { start_time, end_time } = row.original;
|
||||
return (
|
||||
<>
|
||||
{formatDate(start_time)} - {formatDate(end_time)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
actions={{
|
||||
render: (row) => [
|
||||
<AdsForm<API.UpdateAdsRequest>
|
||||
key='edit'
|
||||
trigger={t('edit')}
|
||||
title={t('editAds')}
|
||||
loading={loading}
|
||||
initialValues={row}
|
||||
onSubmit={async (values) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await updateAds({ ...row, ...values });
|
||||
toast.success(t('updateSuccess'));
|
||||
ref.current?.refresh();
|
||||
setLoading(false);
|
||||
return true;
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
<ConfirmButton
|
||||
key='delete'
|
||||
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||
title={t('confirmDelete')}
|
||||
description={t('deleteWarning')}
|
||||
onConfirm={async () => {
|
||||
await deleteAds({ id: row.id });
|
||||
toast.success(t('deleteSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
cancelText={t('cancel')}
|
||||
confirmText={t('confirm')}
|
||||
/>,
|
||||
],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -23,12 +23,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,7 +5,10 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { DicesIcon } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { uid } from 'radash';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Page() {
|
||||
@@ -21,12 +24,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
@@ -49,6 +52,89 @@ export default function Page() {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('showAds')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('showAdsDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.config?.show_ads}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
show_ads: checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('blockVirtualMachine')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('blockVirtualMachineDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.config?.only_real_device}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
only_real_device: checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('enableSecurity')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('enableSecurityDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.config?.enable_security}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
enable_security: checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('communicationKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('communicationKeyDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
value={data?.config?.security_secret}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
security_secret: value,
|
||||
});
|
||||
}}
|
||||
suffix={
|
||||
<div className='bg-muted flex h-9 items-center text-nowrap px-3'>
|
||||
<DicesIcon
|
||||
onClick={() => {
|
||||
const id = uid(32).toLowerCase();
|
||||
const formatted = `${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(16, 20)}-${id.slice(20)}`;
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
security_secret: formatted,
|
||||
});
|
||||
}}
|
||||
className='cursor-pointer'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function Page() {
|
||||
await updateAuthMethodConfig({
|
||||
...ref.current,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
@@ -286,9 +286,13 @@ 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) => (
|
||||
<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>
|
||||
@@ -309,8 +313,7 @@ export default function Page() {
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -22,12 +22,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,12 +22,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,12 +22,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function Page() {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('updateSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -22,12 +22,12 @@ export default function Page() {
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: keyof API.UpdataAuthMethodConfigRequest, value: unknown) {
|
||||
async function updateConfig(key: keyof API.UpdateAuthMethodConfigRequest, value: unknown) {
|
||||
try {
|
||||
await updateAuthMethodConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdataAuthMethodConfigRequest);
|
||||
} as API.UpdateAuthMethodConfigRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
|
||||
@@ -307,6 +307,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.countPlaceholder')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -327,6 +328,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.userLimitPlaceholder')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
|
||||
@@ -116,7 +116,7 @@ export default function Page(props: any) {
|
||||
<ul className='grid gap-3'>
|
||||
<li className='flex items-center justify-between'>
|
||||
<span className='text-muted-foreground'>{t('method')}</span>
|
||||
<span>{t(`methods.${row.original.method}`)}</span>
|
||||
<span>{row.original?.payment?.name || row.original?.payment?.platform}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</HoverCardContent>
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { getAlipayF2FPaymentConfig, updateAlipayF2FPaymentConfig } from '@/services/admin/payment';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { Textarea } from '@workspace/ui/components/textarea';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { unitConversion } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function AlipayF2F() {
|
||||
const t = useTranslations('payment');
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getAlipayF2FPaymentConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getAlipayF2FPaymentConfig();
|
||||
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: string, value: unknown) {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateAlipayF2FPaymentConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdateAlipayF2fRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('enable')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.enable}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('enable', checked);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('alipayf2f.sandbox')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('alipayf2f.sandboxDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.config.sandbox}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
sandbox: checked,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('showName')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.name}
|
||||
onValueBlur={(value) => updateConfig('name', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('iconUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.icon_url}
|
||||
onValueBlur={(value) => updateConfig('icon', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('notifyUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.domain}
|
||||
onValueBlur={(value) => updateConfig('domain', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feeMode')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Select
|
||||
value={String(data?.fee_mode)}
|
||||
onValueChange={(value) => {
|
||||
updateConfig('fee_mode', Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='请选择' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feePercent')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
max={100}
|
||||
maxLength={3}
|
||||
value={data?.fee_percent}
|
||||
onValueBlur={(value) => updateConfig('fee_percent', value)}
|
||||
suffix='%'
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('fixedFee')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
min={0}
|
||||
value={data?.fee_amount}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('alipayf2f.appId')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.app_id}
|
||||
onValueBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
app_id: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('alipayf2f.privateKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Textarea
|
||||
placeholder={t('inputPlaceholder')}
|
||||
defaultValue={data?.config.private_key}
|
||||
onBlur={(e) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
private_key: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('alipayf2f.publicKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Textarea
|
||||
placeholder={t('inputPlaceholder')}
|
||||
defaultValue={data?.config.public_key}
|
||||
onBlur={(e) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
public_key: e.target.value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('alipayf2f.invoiceName')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('alipayf2f.invoiceNameDescription')}
|
||||
value={data?.config.invoice_name}
|
||||
onValueBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
invoice_name: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { getEpayPaymentConfig, updateEpayPaymentConfig } from '@/services/admin/payment';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { unitConversion } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Epay() {
|
||||
const t = useTranslations('payment');
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getEpayPaymentConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getEpayPaymentConfig();
|
||||
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: string, value: unknown) {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateEpayPaymentConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.UpdateEpayRequest);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('enable')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.enable}
|
||||
onCheckedChange={(checked) => {
|
||||
updateConfig('enable', checked);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('showName')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.name}
|
||||
onValueBlur={(value) => updateConfig('name', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('iconUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.icon}
|
||||
onValueBlur={(value) => updateConfig('icon', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('notifyUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.domain}
|
||||
onValueBlur={(value) => updateConfig('domain', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feeMode')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Select
|
||||
value={String(data?.fee_mode)}
|
||||
onValueChange={(value) => {
|
||||
updateConfig('fee_mode', Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='请选择' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feePercent')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
max={100}
|
||||
maxLength={3}
|
||||
value={data?.fee_percent}
|
||||
onValueBlur={(value) => updateConfig('fee_percent', value)}
|
||||
suffix='%'
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('fixedFee')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
value={data?.fee_amount}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('epay.url')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.url}
|
||||
onValueBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
url: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('epay.pid')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.pid}
|
||||
onValueBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
pid: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('epay.key')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.key}
|
||||
onValueBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
key: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +1,11 @@
|
||||
import Billing from '@/components/billing';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
import AlipayF2F from './alipayf2f';
|
||||
import Epay from './epay';
|
||||
import StripeAlipay from './stripe-alipay';
|
||||
import StripeWeChatPay from './stripe-wechat-pay';
|
||||
import PaymentTable from './payment-table';
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<>
|
||||
<Tabs defaultValue='Epay'>
|
||||
<TabsList className='h-full flex-wrap'>
|
||||
<TabsTrigger value='Epay'>Epay</TabsTrigger>
|
||||
<TabsTrigger value='Stripe-Alipay'>Stripe(AliPay)</TabsTrigger>
|
||||
<TabsTrigger value='Strip-WeChatPay'>Stripe(WeChatPay)</TabsTrigger>
|
||||
<TabsTrigger value='AlipayF2F'>AlipayF2F</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='Epay'>
|
||||
<Epay />
|
||||
</TabsContent>
|
||||
<TabsContent value='Stripe-Alipay'>
|
||||
<StripeAlipay />
|
||||
</TabsContent>
|
||||
<TabsContent value='Strip-WeChatPay'>
|
||||
<StripeWeChatPay />
|
||||
</TabsContent>
|
||||
<TabsContent value='AlipayF2F'>
|
||||
<AlipayF2F />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div className='flex flex-col gap-3'>
|
||||
<PaymentTable />
|
||||
<div className='mt-5 flex flex-col gap-3'>
|
||||
<Billing type='payment' />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
'use client';
|
||||
|
||||
import useGlobalStore from '@/config/use-global';
|
||||
import { getPaymentPlatform } from '@/services/admin/payment';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@workspace/ui/components/form';
|
||||
import { RadioGroup, RadioGroupItem } from '@workspace/ui/components/radio-group';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '@workspace/ui/components/sheet';
|
||||
import { MarkdownEditor } from '@workspace/ui/custom-components/editor';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { unitConversion } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
|
||||
interface PaymentFormProps<T> {
|
||||
trigger: React.ReactNode;
|
||||
title: string;
|
||||
loading?: boolean;
|
||||
initialValues?: T;
|
||||
onSubmit: (values: T) => Promise<boolean>;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
|
||||
export default function PaymentForm<T>({
|
||||
trigger,
|
||||
title,
|
||||
loading,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
isEdit,
|
||||
}: PaymentFormProps<T>) {
|
||||
const t = useTranslations('payment');
|
||||
const { common } = useGlobalStore();
|
||||
const { currency } = common;
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { data: platformData } = useQuery({
|
||||
queryKey: ['getPaymentPlatform'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getPaymentPlatform();
|
||||
return data?.data?.list || [];
|
||||
},
|
||||
});
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, { message: t('nameRequired') }),
|
||||
platform: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
domain: z.string().optional(),
|
||||
config: z.any(),
|
||||
fee_mode: z.coerce.number().min(0).max(2),
|
||||
fee_percent: z.coerce.number().optional(),
|
||||
fee_amount: z.coerce.number().optional(),
|
||||
description: z.string().optional(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
platform: '',
|
||||
icon: '',
|
||||
domain: '',
|
||||
config: {},
|
||||
fee_mode: 0,
|
||||
fee_percent: 0,
|
||||
fee_amount: 0,
|
||||
...(initialValues as any),
|
||||
},
|
||||
});
|
||||
|
||||
const feeMode = form.watch('fee_mode');
|
||||
const platformValue = form.watch('platform');
|
||||
const configValues = form.watch('config');
|
||||
|
||||
const currentPlatform = platformData?.find((p) => p.platform === platformValue);
|
||||
const currentFieldDescriptions = currentPlatform?.platform_field_description || {};
|
||||
const configFields = Object.keys(currentFieldDescriptions) || [];
|
||||
const platformUrl = currentPlatform?.platform_url || '';
|
||||
|
||||
useEffect(() => {
|
||||
if (feeMode === 0) {
|
||||
form.setValue('fee_amount', 0);
|
||||
form.setValue('fee_percent', 0);
|
||||
} else if (feeMode === 1) {
|
||||
form.setValue('fee_amount', 0);
|
||||
} else if (feeMode === 2) {
|
||||
form.setValue('fee_percent', 0);
|
||||
}
|
||||
}, [feeMode, form]);
|
||||
|
||||
const handleClose = () => {
|
||||
form.reset();
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
const cleanedValues = { ...values };
|
||||
|
||||
if (values.fee_mode === 0) {
|
||||
cleanedValues.fee_amount = undefined;
|
||||
cleanedValues.fee_percent = undefined;
|
||||
} else if (values.fee_mode === 1) {
|
||||
cleanedValues.fee_amount = undefined;
|
||||
} else if (values.fee_mode === 2) {
|
||||
cleanedValues.fee_percent = undefined;
|
||||
}
|
||||
|
||||
const success = await onSubmit(cleanedValues as unknown as T);
|
||||
if (success) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const openPlatformUrl = () => {
|
||||
if (platformUrl) {
|
||||
window.open(platformUrl, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetTrigger asChild>{trigger}</SheetTrigger>
|
||||
<SheetContent className='w-[550px] max-w-full md:max-w-screen-md'>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{title}</SheetTitle>
|
||||
</SheetHeader>
|
||||
<ScrollArea className='-mx-6 h-[calc(100vh-48px-36px-36px-env(safe-area-inset-top))]'>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-6 px-6 pt-4'>
|
||||
{/* 基本信息分组 */}
|
||||
<div className='space-y-4'>
|
||||
<div className='grid grid-cols-1 gap-4 sm:grid-cols-2'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='name'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('namePlaceholder')}
|
||||
value={field.value}
|
||||
onValueChange={(value) => form.setValue('name', value as string)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='icon'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('icon')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('iconPlaceholder')}
|
||||
value={field.value}
|
||||
onValueChange={(value) => form.setValue('icon', value as string)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='domain'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('domain')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('domainPlaceholder', { example: 'http(s)://example.com' })}
|
||||
value={field.value}
|
||||
onValueChange={(value) => form.setValue('domain', value as string)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='fee_mode'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('handlingFee')}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={(value) => field.onChange(parseInt(value))}
|
||||
value={field.value.toString()}
|
||||
className='flex flex-wrap gap-4'
|
||||
>
|
||||
<FormItem className='flex items-center space-x-2'>
|
||||
<FormControl>
|
||||
<RadioGroupItem value='0' />
|
||||
</FormControl>
|
||||
<FormLabel className='!mt-0 cursor-pointer'>{t('noFee')}</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className='flex items-center space-x-2'>
|
||||
<FormControl>
|
||||
<RadioGroupItem value='1' />
|
||||
</FormControl>
|
||||
<FormLabel className='!mt-0 cursor-pointer'>
|
||||
{t('percentFee')}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<FormItem className='flex items-center space-x-2'>
|
||||
<FormControl>
|
||||
<RadioGroupItem value='2' />
|
||||
</FormControl>
|
||||
<FormLabel className='!mt-0 cursor-pointer'>{t('fixedFee')}</FormLabel>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{feeMode === 1 && (
|
||||
<div className='grid grid-cols-1 sm:w-1/2'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='fee_percent'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('feePercent')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='number'
|
||||
step='0.01'
|
||||
suffix='%'
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{feeMode === 2 && (
|
||||
<div className='grid grid-cols-1 sm:w-1/2'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='fee_amount'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('feeAmount')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
type='number'
|
||||
step='0.01'
|
||||
prefix={currency.currency_symbol}
|
||||
suffix={currency.currency_unit}
|
||||
value={unitConversion('centsToDollars', field.value)}
|
||||
onValueChange={(value) =>
|
||||
field.onChange(unitConversion('dollarsToCents', value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='space-y-4'>
|
||||
{(!platformValue || platformData?.find((p) => p.platform === platformValue)) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='platform'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('platform')}</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
form.setValue('platform', value as string);
|
||||
form.setValue('config', {});
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
// @ts-expect-error
|
||||
disabled={isEdit && Boolean(initialValues?.platform)}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('selectPlatform')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{platformData?.map((platform) => (
|
||||
<SelectItem key={platform.platform} value={platform.platform}>
|
||||
{platform.platform}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{platformUrl ? (
|
||||
<div className='mt-1 flex justify-end'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
className='h-6 px-2 text-xs'
|
||||
onClick={openPlatformUrl}
|
||||
>
|
||||
<Icon icon='tabler:external-link' className='mr-1 h-3 w-3' />
|
||||
{t('applyForPayment')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className='mt-1 h-6'></div>
|
||||
)}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{configFields.length > 0 && (
|
||||
<div className='mt-4 space-y-4'>
|
||||
{configFields.map((fieldKey) => (
|
||||
<FormItem key={fieldKey}>
|
||||
<FormLabel>{currentFieldDescriptions[fieldKey]}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('configPlaceholder', {
|
||||
field: currentFieldDescriptions[fieldKey],
|
||||
})}
|
||||
value={
|
||||
configValues && configValues[fieldKey] !== undefined
|
||||
? configValues[fieldKey]
|
||||
: ''
|
||||
}
|
||||
onValueChange={(value) => {
|
||||
const newConfig = { ...configValues };
|
||||
newConfig[fieldKey] = value;
|
||||
form.setValue('config', newConfig);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='description'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('description')}</FormLabel>
|
||||
<FormControl>
|
||||
<MarkdownEditor
|
||||
value={field.value}
|
||||
onChange={(value) => form.setValue(field.name, value as string)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</ScrollArea>
|
||||
|
||||
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
|
||||
<Button variant='outline' disabled={loading} onClick={handleClose}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
|
||||
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
'use client';
|
||||
|
||||
import { Display } from '@/components/display';
|
||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||
import {
|
||||
createPaymentMethod,
|
||||
deletePaymentMethod,
|
||||
getPaymentMethodList,
|
||||
updatePaymentMethod,
|
||||
} from '@/services/admin/payment';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components/avatar';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import PaymentForm from './payment-form';
|
||||
|
||||
export default function PaymentTable() {
|
||||
const t = useTranslations('payment');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const ref = useRef<ProTableActions>(null);
|
||||
|
||||
return (
|
||||
<ProTable<API.PaymentConfig, { search: string }>
|
||||
action={ref}
|
||||
header={{
|
||||
title: t('paymentManagement'),
|
||||
toolbar: (
|
||||
<PaymentForm<API.CreatePaymentMethodRequest>
|
||||
trigger={<Button>{t('create')}</Button>}
|
||||
title={t('createPayment')}
|
||||
loading={loading}
|
||||
onSubmit={async (values) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await createPaymentMethod({
|
||||
...values,
|
||||
enable: false,
|
||||
});
|
||||
toast.success(t('createSuccess'));
|
||||
ref.current?.refresh();
|
||||
setLoading(false);
|
||||
return true;
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'enable',
|
||||
header: t('enable'),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<Switch
|
||||
checked={Boolean(row.getValue('enable'))}
|
||||
onCheckedChange={async (checked) => {
|
||||
await updatePaymentMethod({
|
||||
...row.original,
|
||||
enable: checked,
|
||||
});
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'icon',
|
||||
header: t('icon'),
|
||||
cell: ({ row }) => {
|
||||
const icon = row.getValue('icon') as string;
|
||||
return (
|
||||
<Avatar className='h-8 w-8'>
|
||||
{icon ? <AvatarImage src={icon} alt={row.getValue('name')} /> : null}
|
||||
<AvatarFallback>
|
||||
{(row.getValue('name') as string)?.charAt(0) || '?'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: t('name'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'platform',
|
||||
header: t('platform'),
|
||||
cell: ({ row }) => <Badge>{t(row.getValue('platform'))}</Badge>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'notify_url',
|
||||
header: t('notify_url'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'fee',
|
||||
header: t('handlingFee'),
|
||||
cell: ({ row }) => {
|
||||
const feeMode = row.original.fee_mode;
|
||||
if (feeMode === 1) {
|
||||
return <Badge>{row.original.fee_percent}%</Badge>;
|
||||
} else if (feeMode === 2) {
|
||||
return (
|
||||
<Badge>
|
||||
<Display value={row.original.fee_amount} type='currency' />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return '--';
|
||||
},
|
||||
},
|
||||
]}
|
||||
params={[
|
||||
{
|
||||
key: 'search',
|
||||
placeholder: t('searchPlaceholder'),
|
||||
},
|
||||
]}
|
||||
request={async (pagination, filter) => {
|
||||
const { data } = await getPaymentMethodList({
|
||||
...pagination,
|
||||
...filter,
|
||||
});
|
||||
return {
|
||||
list: data?.data?.list || [],
|
||||
total: data?.data?.total || 0,
|
||||
};
|
||||
}}
|
||||
actions={{
|
||||
render: (row) => [
|
||||
<PaymentForm<API.UpdatePaymentMethodRequest>
|
||||
isEdit
|
||||
key='edit'
|
||||
trigger={<Button>{t('edit')}</Button>}
|
||||
title={t('editPayment')}
|
||||
loading={loading}
|
||||
initialValues={row}
|
||||
onSubmit={async (values) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await updatePaymentMethod({
|
||||
...row,
|
||||
...values,
|
||||
});
|
||||
toast.success(t('updateSuccess'));
|
||||
ref.current?.refresh();
|
||||
setLoading(false);
|
||||
return true;
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
/>,
|
||||
<ConfirmButton
|
||||
key='delete'
|
||||
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||
title={t('confirmDelete')}
|
||||
description={t('deleteWarning')}
|
||||
onConfirm={async () => {
|
||||
await deletePaymentMethod({
|
||||
id: row.id,
|
||||
});
|
||||
toast.success(t('deleteSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
cancelText={t('cancel')}
|
||||
confirmText={t('confirm')}
|
||||
/>,
|
||||
<Button
|
||||
key='copy'
|
||||
variant='outline'
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { id, ...params } = row;
|
||||
await createPaymentMethod({
|
||||
...params,
|
||||
enable: false,
|
||||
});
|
||||
toast.success(t('copySuccess'));
|
||||
ref.current?.refresh();
|
||||
setLoading(false);
|
||||
return true;
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('copy')}
|
||||
</Button>,
|
||||
],
|
||||
batchRender(rows) {
|
||||
return [
|
||||
<ConfirmButton
|
||||
key='delete'
|
||||
trigger={<Button variant='destructive'>{t('batchDelete')}</Button>}
|
||||
title={t('confirmDelete')}
|
||||
description={t('deleteWarning')}
|
||||
onConfirm={async () => {
|
||||
for (const row of rows) {
|
||||
await deletePaymentMethod({ id: row.id });
|
||||
}
|
||||
toast.success(t('deleteSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
cancelText={t('cancel')}
|
||||
confirmText={t('confirm')}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
getStripeAlipayPaymentConfig,
|
||||
updateStripeAlipayPaymentConfig,
|
||||
} from '@/services/admin/payment';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { unitConversion } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Stripe() {
|
||||
const t = useTranslations('payment');
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getStripeAlipayPaymentConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getStripeAlipayPaymentConfig();
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: string, value: unknown) {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateStripeAlipayPaymentConfig({
|
||||
...data,
|
||||
mark: 'stripe_alipay',
|
||||
[key]: value,
|
||||
} as any);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('aliPay')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.enable}
|
||||
onCheckedChange={async (checked) => {
|
||||
updateConfig('enable', checked);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('showName')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.name}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('name', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('iconUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.icon}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('icon', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('notifyUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.domain}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('domain', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feeMode')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Select
|
||||
value={String(data?.fee_mode)}
|
||||
onValueChange={(value) => {
|
||||
updateConfig('fee_mode', Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='请选择' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feePercent')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
max={100}
|
||||
value={data?.fee_percent}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('fee_percent', value);
|
||||
}}
|
||||
suffix='%'
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('fixedFee')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
value={data?.fee_amount}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.publicKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.public_key}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
public_key: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.secretKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.secret_key}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
secret_key: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.webhookSecret')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.webhook_secret}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
webhook_secret: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
getStripeWeChatPayPaymentConfig,
|
||||
updateStripeWeChatPayPaymentConfig,
|
||||
} from '@/services/admin/payment';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||
import { unitConversion } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function StripeWeChatPay() {
|
||||
const t = useTranslations('payment');
|
||||
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getStripeWeChatPayPaymentConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getStripeWeChatPayPaymentConfig();
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: string, value: unknown) {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateStripeWeChatPayPaymentConfig({
|
||||
...data,
|
||||
mark: 'stripe_wechat_pay',
|
||||
[key]: value,
|
||||
} as any);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('wechatPay')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('enableDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Switch
|
||||
checked={data?.enable}
|
||||
onCheckedChange={async (checked) => {
|
||||
updateConfig('enable', checked);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('showName')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('showNameDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.name}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('name', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('iconUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('iconUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.icon}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('icon', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('notifyUrl')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('notifyUrlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.domain}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('domain', value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feeMode')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feeModeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Select
|
||||
value={String(data?.fee_mode)}
|
||||
onValueChange={(value) => {
|
||||
updateConfig('fee_mode', Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder='请选择' />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value='0'>{t('feeModeItems.0')}</SelectItem>
|
||||
<SelectItem value='1'>{t('feeModeItems.1')}</SelectItem>
|
||||
<SelectItem value='2'>{t('feeModeItems.2')}</SelectItem>
|
||||
<SelectItem value='3'>{t('feeModeItems.3')}</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('feePercent')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('feePercentDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
max={100}
|
||||
value={data?.fee_percent}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('fee_percent', value);
|
||||
}}
|
||||
suffix='%'
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('fixedFee')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('fixedFeeDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
type='number'
|
||||
min={0}
|
||||
value={data?.fee_amount}
|
||||
formatInput={(value) => unitConversion('centsToDollars', value)}
|
||||
formatOutput={(value) => unitConversion('dollarsToCents', value)}
|
||||
onValueBlur={(value) => updateConfig('fee_amount', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.publicKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.public_key}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
public_key: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.secretKey')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.secret_key}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
secret_key: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('stripe.webhookSecret')}</Label>
|
||||
<p className='text-muted-foreground text-xs' />
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config.webhook_secret}
|
||||
onValueBlur={(value) => {
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
webhook_secret: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -50,7 +50,7 @@ const defaultValues = {
|
||||
|
||||
const versionSchema = z.object({
|
||||
url: z.string(),
|
||||
version: z.string(),
|
||||
version: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
is_default: z.boolean().optional(),
|
||||
});
|
||||
@@ -221,6 +221,7 @@ export default function SubscribeAppForm<
|
||||
name: 'version',
|
||||
type: 'text',
|
||||
placeholder: t('version'),
|
||||
defaultValue: '1.0.0',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
@@ -251,7 +252,10 @@ export default function SubscribeAppForm<
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
form.setValue(
|
||||
field.name,
|
||||
value.filter((item) => item.url),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function SubscribeConfig() {
|
||||
<TableCell className='text-right'>
|
||||
<Textarea
|
||||
className='h-52'
|
||||
placeholder={t('subscriptionDomainPlaceholder')}
|
||||
placeholder={`${t('subscriptionDomainPlaceholder')}\nexample.com\nwww.example.com`}
|
||||
defaultValue={data?.subscribe_domain}
|
||||
onBlur={(e) => {
|
||||
updateConfig('subscribe_domain', e.target.value);
|
||||
|
||||
@@ -298,6 +298,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -321,7 +322,8 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
value={field.value === -1 ? 0 : field.value}
|
||||
step={1}
|
||||
value={field.value}
|
||||
min={0}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -343,6 +345,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -545,6 +548,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
{
|
||||
name: 'quantity',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
min: 1,
|
||||
suffix: unit_time && t(`form.${unit_time}`),
|
||||
},
|
||||
@@ -560,7 +564,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
return {
|
||||
...data,
|
||||
price: evaluateWithPrecision(
|
||||
`${unit_price} * ${data.quantity} * ${data.discount} / 100`,
|
||||
`${unit_price || 0} * ${data.quantity || 0} * ${data.discount || 0} / 100`,
|
||||
),
|
||||
};
|
||||
},
|
||||
@@ -569,7 +573,6 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
name: 'price',
|
||||
placeholder: t('form.discount_price'),
|
||||
type: 'number',
|
||||
min: 0,
|
||||
formatInput: (value) => unitConversion('centsToDollars', value),
|
||||
formatOutput: (value) => unitConversion('dollarsToCents', value),
|
||||
internal: true,
|
||||
@@ -578,7 +581,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
return {
|
||||
...data,
|
||||
discount: evaluateWithPrecision(
|
||||
`${data.price} / ${data.quantity} / ${unit_price} * 100`,
|
||||
`${data.price || 0} / ${data.quantity || 0} / ${unit_price || 0} * 100`,
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Currency from './currency';
|
||||
import PrivacyPolicy from './privacy-policy';
|
||||
import Site from './site';
|
||||
import Tos from './tos';
|
||||
|
||||
@@ -13,6 +14,7 @@ export default async function Page() {
|
||||
<TabsTrigger value='site'>{t('tabs.site')}</TabsTrigger>
|
||||
<TabsTrigger value='currency'>{t('tabs.currency')}</TabsTrigger>
|
||||
<TabsTrigger value='tos'>{t('tabs.tos')}</TabsTrigger>
|
||||
<TabsTrigger value='privacy-policy'>{t('privacy-policy.title')}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='site'>
|
||||
<Site />
|
||||
@@ -23,6 +25,9 @@ export default async function Page() {
|
||||
<TabsContent value='tos'>
|
||||
<Tos />
|
||||
</TabsContent>
|
||||
<TabsContent value='privacy-policy'>
|
||||
<PrivacyPolicy />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { getPrivacyPolicyConfig, updatePrivacyPolicyConfig } from '@/services/admin/system';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { MarkdownEditor } from '@workspace/ui/custom-components/editor';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
const t = useTranslations('system.privacy-policy');
|
||||
const { data, refetch, isFetched } = useQuery({
|
||||
queryKey: ['getPrivacyPolicyConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getPrivacyPolicyConfig();
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
async function updateConfig(key: string, value: unknown) {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updatePrivacyPolicyConfig({
|
||||
...data,
|
||||
[key]: value,
|
||||
} as API.PrivacyPolicyConfig);
|
||||
toast.success(t('saveSuccess'));
|
||||
refetch();
|
||||
} catch (error) {
|
||||
/* empty */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
isFetched && (
|
||||
<div className='h-[calc(100dvh-132px-env(safe-area-inset-top))] overflow-hidden'>
|
||||
<MarkdownEditor
|
||||
title={t('title')}
|
||||
value={data?.privacy_policy}
|
||||
onBlur={(value) => {
|
||||
if (data?.privacy_policy !== value) {
|
||||
updateConfig('privacy_policy', value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -5,18 +5,21 @@ 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';
|
||||
import { useRef } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Site() {
|
||||
const t = useTranslations('system.site');
|
||||
|
||||
const ref = useRef<API.SiteConfig | undefined>(undefined);
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getSiteConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getSiteConfig();
|
||||
ref.current = data.data;
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
@@ -25,7 +28,7 @@ export default function Site() {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateSiteConfig({
|
||||
...data,
|
||||
...ref.current,
|
||||
[key]: value,
|
||||
} as API.SiteConfig);
|
||||
toast.success(t('saveSuccess'));
|
||||
@@ -85,6 +88,35 @@ export default function Site() {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Label>{t('keywords')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('keywordsDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<EnhancedInput
|
||||
placeholder={t('keywordsDescription')}
|
||||
value={data?.keywords}
|
||||
onValueBlur={(value) => updateConfig('keywords', value)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className='align-top'>
|
||||
<Label>{t('customHtml')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>{t('customHtmlDescription')}</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<Textarea
|
||||
className='h-52'
|
||||
placeholder={t('customHtmlDescription')}
|
||||
defaultValue={data?.custom_html}
|
||||
onBlur={(e) => {
|
||||
updateConfig('custom_html', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell className='align-top'>
|
||||
<Label>{t('siteDomain')}</Label>
|
||||
@@ -93,7 +125,7 @@ export default function Site() {
|
||||
<TableCell className='text-right'>
|
||||
<Textarea
|
||||
className='h-52'
|
||||
placeholder={t('siteDomainPlaceholder')}
|
||||
placeholder={`${t('siteDomainPlaceholder')}\nexample.com\nwww.example.com`}
|
||||
defaultValue={data?.host}
|
||||
onBlur={(e) => {
|
||||
updateConfig('host', e.target.value);
|
||||
@@ -101,6 +133,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)}
|
||||
|
||||
@@ -57,6 +57,7 @@ export function SubscriptionDetail({
|
||||
{t('onlineDevices')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className='mt-4 max-h-[60dvh] overflow-y-auto'>
|
||||
<TabsContent value='logs'>
|
||||
<ProTable<API.UserSubscribeLog, Record<string, unknown>>
|
||||
columns={[
|
||||
@@ -97,7 +98,9 @@ export function SubscriptionDetail({
|
||||
{
|
||||
accessorKey: 'download',
|
||||
header: t('download'),
|
||||
cell: ({ row }) => <Display type='traffic' value={row.getValue('download')} />,
|
||||
cell: ({ row }) => (
|
||||
<Display type='traffic' value={row.getValue('download')} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'upload',
|
||||
@@ -115,7 +118,7 @@ export function SubscriptionDetail({
|
||||
user_id: userId,
|
||||
subscribe_id: subscriptionId,
|
||||
...pagination,
|
||||
});
|
||||
} as API.GetUserSubscribeTrafficLogsParams);
|
||||
return {
|
||||
list: data.data?.list || [],
|
||||
total: data.data?.total || 0,
|
||||
@@ -143,7 +146,7 @@ export function SubscriptionDetail({
|
||||
header: 'ID',
|
||||
},
|
||||
{
|
||||
accessorKey: 'imei',
|
||||
accessorKey: 'identifier',
|
||||
header: 'IMEI',
|
||||
},
|
||||
{
|
||||
@@ -182,7 +185,7 @@ export function SubscriptionDetail({
|
||||
}}
|
||||
actions={{
|
||||
render: (row) => {
|
||||
if (!row.imei) return [];
|
||||
if (!row.identifier) return [];
|
||||
return [
|
||||
<ConfirmButton
|
||||
key='offline'
|
||||
@@ -201,6 +204,7 @@ export function SubscriptionDetail({
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -11,13 +11,38 @@ import {
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { getCountry } from '@workspace/ui/utils';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
const languages = {
|
||||
'cs-CZ': 'Čeština',
|
||||
'de-DE': 'Deutsch',
|
||||
'en-US': 'English',
|
||||
'es-ES': 'Español',
|
||||
'es-MX': 'Español (México)',
|
||||
'fa-IR': 'فارسی',
|
||||
'fi-FI': 'Suomi',
|
||||
'fr-FR': 'Français',
|
||||
'hi-IN': 'हिन्दी',
|
||||
'hu-HU': 'Magyar',
|
||||
'ja-JP': '日本語',
|
||||
'ko-KR': '한국어',
|
||||
'no-NO': 'Norsk',
|
||||
'pl-PL': 'Polski',
|
||||
'pt-BR': 'Português (Brasil)',
|
||||
'ro-RO': 'Română',
|
||||
'ru-RU': 'Русский',
|
||||
'th-TH': 'ไทย',
|
||||
'tr-TR': 'Türkçe',
|
||||
'uk-UA': 'Українська',
|
||||
'vi-VN': 'Tiếng Việt',
|
||||
'zh-CN': '简体中文',
|
||||
'zh-HK': '繁體中文',
|
||||
} as const;
|
||||
|
||||
export default function LanguageSwitch() {
|
||||
const locale = useLocale();
|
||||
const country = getCountry(locale);
|
||||
const t = useTranslations('language');
|
||||
const router = useRouter();
|
||||
|
||||
const handleLanguageChange = (value: string) => {
|
||||
@@ -31,7 +56,7 @@ export default function LanguageSwitch() {
|
||||
<SelectValue>
|
||||
<div className='flex items-center'>
|
||||
<Icon icon={`flagpack:${country?.alpha2.toLowerCase()}`} className='!size-5' />
|
||||
<span className='sr-only'>{t(`${locale}`)}</span>
|
||||
<span className='sr-only'>{languages[locale as keyof typeof languages]}</span>
|
||||
</div>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
@@ -40,7 +65,7 @@ export default function LanguageSwitch() {
|
||||
<SelectItem key={`${item?.lang}-${item?.alpha2}`} value={`${item?.lang}-${item?.alpha2}`}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Icon icon={`flagpack:${item?.alpha2.toLowerCase()}`} className='!size-5' />
|
||||
{t(`${item?.lang}-${item?.alpha2}`)}
|
||||
{languages[`${item?.lang}-${item?.alpha2}` as keyof typeof languages]}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
||||
@@ -61,6 +61,11 @@ export const navs = [
|
||||
icon: 'flat-color-icons:lock-portrait',
|
||||
items: AuthControl,
|
||||
},
|
||||
{
|
||||
title: 'ADS Config',
|
||||
url: '/dashboard/ads',
|
||||
icon: 'flat-color-icons:electrical-sensor',
|
||||
},
|
||||
{
|
||||
title: 'Payment Config',
|
||||
url: '/dashboard/payment',
|
||||
@@ -115,7 +120,7 @@ export const navs = [
|
||||
{
|
||||
title: 'Announcement Management',
|
||||
url: '/dashboard/announcement',
|
||||
icon: 'flat-color-icons:news',
|
||||
icon: 'flat-color-icons:advertising',
|
||||
},
|
||||
{
|
||||
title: 'Ticket Management',
|
||||
|
||||
@@ -14,6 +14,9 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
||||
site_name: '',
|
||||
site_desc: '',
|
||||
site_logo: '',
|
||||
keywords: '',
|
||||
custom_html: '',
|
||||
custom_data: '',
|
||||
},
|
||||
verify: {
|
||||
turnstile_site_key: '',
|
||||
@@ -61,6 +64,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
||||
verify_code_interval: 60,
|
||||
},
|
||||
oauth_methods: [],
|
||||
web_ad: false,
|
||||
},
|
||||
user: undefined,
|
||||
setCommon: (common) =>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Zrušit",
|
||||
"confirm": "Potvrdit",
|
||||
"confirmDelete": "Potvrdit smazání",
|
||||
"create": "Vytvořit inzerát",
|
||||
"createAds": "Vytvořit reklamu",
|
||||
"createSuccess": "Úspěšně vytvořeno",
|
||||
"delete": "Smazat",
|
||||
"deleteSuccess": "Úspěšně smazáno",
|
||||
"deleteWarning": "Opravdu chcete smazat tento inzerát?",
|
||||
"disabled": "Deaktivováno",
|
||||
"edit": "Upravit",
|
||||
"editAds": "Upravit reklamu",
|
||||
"enabled": "Aktivováno",
|
||||
"form": {
|
||||
"cancel": "Zrušit",
|
||||
"confirm": "Potvrdit",
|
||||
"content": "Obsah",
|
||||
"description": "Popis",
|
||||
"endTime": "Čas ukončení",
|
||||
"enterContent": "Prosím zadejte obsah inzerátu",
|
||||
"enterDescription": "Prosím zadejte popis inzerátu",
|
||||
"enterEndTime": "Prosím vyberte čas ukončení",
|
||||
"enterStartTime": "Prosím vyberte čas zahájení",
|
||||
"enterTargetUrl": "Prosím zadejte cílovou URL",
|
||||
"enterTitle": "Prosím zadejte název inzerátu",
|
||||
"startTime": "Čas zahájení",
|
||||
"targetUrl": "Cílová URL",
|
||||
"title": "Název",
|
||||
"type": "Typ inzerátu",
|
||||
"typeImage": "Obrázek",
|
||||
"typeVideo": "Video"
|
||||
},
|
||||
"status": "Stav",
|
||||
"targetUrl": "Cílová URL",
|
||||
"title": "Název",
|
||||
"type": "Typ",
|
||||
"updateSuccess": "Úspěšně aktualizováno",
|
||||
"validityPeriod": "Doba platnosti"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Zadejte platnou e-mailovou adresu.",
|
||||
"existingAccount": "Již máte účet?",
|
||||
"get": "Získat",
|
||||
"invite": "Pozvánkový kód",
|
||||
"invite": "Pozvánkový kód (volitelné)",
|
||||
"message": "#### Vážený uživateli,\n\nDěkujeme za váš zájem a podporu. V důsledku úpravy provozní strategie jsme uzavřeli registraci nových uživatelů. Během této doby nebude používání stávajících účtů nijak ovlivněno.\n\nSnažíme se vám poskytnout lepší služby a zážitky, a proto během uzavření registrace provedeme komplexní optimalizaci systému a aktualizaci funkcí. V budoucnu vás přivítáme s kvalitnějším obsahem a službami.\n\nSledujte naše webové stránky a sociální média pro nejnovější aktualizace a oznámení. Děkujeme za vaše pochopení a podporu.\n\nPokud máte jakékoli dotazy nebo potřebujete pomoc, neváhejte kontaktovat náš zákaznický servis.\n\n**Ještě jednou děkujeme za vaši podporu a pochopení.**",
|
||||
"passwordMismatch": "Hesla se neshodují",
|
||||
"success": "Registrace úspěšná, automaticky přihlášeno!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Blokovat virtuální stroj",
|
||||
"blockVirtualMachineDescription": "Pokud je povoleno, zařízení nebudou moci běžet na virtuálních strojích nebo emulátorech",
|
||||
"communicationKey": "Komunikační klíč",
|
||||
"communicationKeyDescription": "Komunikační klíč se používá pro bezpečnou komunikaci mezi zařízeními a servery",
|
||||
"enable": "Povolit",
|
||||
"enableDescription": "Po povolení jsou podporovány různé identifikátory zařízení, jako jsou IMEI/IDFA/IDFV/AndroidID/Mac adresa pro přihlášení a registraci"
|
||||
"enableDescription": "Po povolení jsou podporovány různé identifikátory zařízení, jako jsou IMEI/IDFA/IDFV/AndroidID/Mac adresa pro přihlášení a registraci",
|
||||
"enableSecurity": "Povolit šifrování komunikace",
|
||||
"enableSecurityDescription": "Pokud je povoleno, komunikace mezi zařízeními a servery bude šifrována",
|
||||
"saveFailed": "Uložení se nezdařilo",
|
||||
"saveSuccess": "Uložení bylo úspěšné",
|
||||
"showAds": "Zobrazit reklamy",
|
||||
"showAdsDescription": "Pokud je povoleno, na zařízeních se budou zobrazovat reklamy"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Ověření e-mailu",
|
||||
"emailVerificationDescription": "Když je povoleno, uživatelé budou muset ověřit svůj e-mail",
|
||||
"enable": "Povolit",
|
||||
"enableDescription": "Po povolení budou povoleny funkce registrace, přihlášení, připojení a odpojení mobilního telefonu",
|
||||
"enableDescription": "Po aktivaci budou povoleny funkce registrace e-mailem, přihlášení, propojení a odpojení.",
|
||||
"expiration_email_template": "Šablona oznámení o vypršení platnosti",
|
||||
"expiration_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
||||
"failed": "Neúspěch",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "čeština",
|
||||
"de-DE": "němčina",
|
||||
"en-US": "angličtina",
|
||||
"es-ES": "španělština",
|
||||
"es-MX": "španělština (Mexiko)",
|
||||
"fa-IR": "Perština",
|
||||
"fi-FI": "finština",
|
||||
"fr-FR": "Francouzština",
|
||||
"hi-IN": "hindština",
|
||||
"hu-HU": "maďarština",
|
||||
"ja-JP": "Japonština",
|
||||
"ko-KR": "Korejština",
|
||||
"no-NO": "norština",
|
||||
"pl-PL": "polština",
|
||||
"pt-BR": "portugalština (Brazílie)",
|
||||
"ro-RO": "rumunština",
|
||||
"ru-RU": "ruština",
|
||||
"th-TH": "thajština",
|
||||
"tr-TR": "turečtina",
|
||||
"uk-UA": "Ukrajinština",
|
||||
"vi-VN": "Vietnamština",
|
||||
"zh-CN": "čeština (CS)",
|
||||
"zh-HK": "čínština (tradiční)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "Konfigurace ADS",
|
||||
"Announcement Management": "Správa oznámení",
|
||||
"Apple": "Apple ID",
|
||||
"Auth Control": "Řízení ověřování",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Platba Alipay",
|
||||
"alipayf2f": {
|
||||
"appId": "ID aplikace Alipay",
|
||||
"invoiceName": "Název vlastního produktu",
|
||||
"invoiceNameDescription": "Bude zobrazeno na faktuře Alipay",
|
||||
"privateKey": "Soukromý klíč Alipay",
|
||||
"publicKey": "Veřejný klíč Alipay",
|
||||
"sandbox": "Režim sandbox",
|
||||
"sandboxDescription": "Povolte režim sandbox pro testování plateb"
|
||||
},
|
||||
"AlipayF2F": "Alipay Osobně",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Žádat o platbu",
|
||||
"balance": "Zůstatek",
|
||||
"batchDelete": "Hromadné smazání",
|
||||
"cancel": "Zrušit",
|
||||
"config": "Nastavení",
|
||||
"configInfo": "Informace o nastavení",
|
||||
"configPlaceholder": "Vyplňte prosím poskytnutou konfiguraci {field}",
|
||||
"confirm": "Potvrdit",
|
||||
"confirmDelete": "Potvrdit smazání",
|
||||
"copy": "Kopírovat",
|
||||
"copySuccess": "Úspěšně zkopírováno",
|
||||
"create": "Přidat platební metodu",
|
||||
"createPayment": "Přidat platební metodu",
|
||||
"createSuccess": "Úspěšně vytvořeno",
|
||||
"delete": "Smazat",
|
||||
"deleteSuccess": "Úspěšně smazáno",
|
||||
"deleteWarning": "Opravdu chcete smazat tuto platební metodu? Tuto akci nelze vrátit zpět.",
|
||||
"description": "Popis",
|
||||
"descriptionPlaceholder": "Zadejte popis platební metody",
|
||||
"domain": "Doména",
|
||||
"domainPlaceholder": "volitelné, např. {example}",
|
||||
"edit": "Upravit",
|
||||
"editPayment": "Upravit platební metodu",
|
||||
"enable": "Povolit",
|
||||
"enableDescription": "Povolit tento způsob platby",
|
||||
"epay": {
|
||||
"key": "KLÍČ",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Pevná částka",
|
||||
"feeMode": "Způsob účtování",
|
||||
"feeModeDescription": "Způsob účtování dodatečných poplatků",
|
||||
"feeModeItems": ["Bez poplatku", "Procento", "Pevná částka", "Obojí výše uvedené"],
|
||||
"feePercent": "Procentuální poplatek (volitelně)",
|
||||
"feePercentDescription": "Přidání poplatku k částce objednávky",
|
||||
"fixedFee": "Pevný poplatek (volitelný)",
|
||||
"fixedFeeDescription": "Přidání pevného poplatku k částce objednávky",
|
||||
"iconUrl": "URL ikony (volitelné)",
|
||||
"iconUrlDescription": "Používá se pro zobrazení na frontendu",
|
||||
"inputPlaceholder": "Prosím zadejte",
|
||||
"key": "KLÍČ",
|
||||
"notifyUrl": "Vlastní oznámení URL (volitelné)",
|
||||
"notifyUrlDescription": "Oznámení brány budou odeslána na tuto doménu",
|
||||
"payForRecommendations": "Zaplatit za doporučení",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Uložení bylo úspěšné",
|
||||
"showName": "Zobrazit název",
|
||||
"showNameDescription": "Používá se pro zobrazení na frontendu",
|
||||
"stripe": {
|
||||
"publicKey": "VEŘEJNÝ KLÍČ",
|
||||
"secretKey": "TAJNÝ KLÍČ",
|
||||
"webhookSecret": "PODPIS WEBHOOK KLÍČE"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "WeChat Pay"
|
||||
"handlingFee": "Poplatek za zpracování",
|
||||
"icon": "Ikona",
|
||||
"iconPlaceholder": "Zadejte URL ikony",
|
||||
"mark": "Identifikátor",
|
||||
"markPlaceholder": "Zadejte identifikátor platební metody",
|
||||
"name": "Název",
|
||||
"namePlaceholder": "Zadejte název platební metody",
|
||||
"nameRequired": "Název je povinný",
|
||||
"noFee": "Žádný poplatek",
|
||||
"notify_url": "URL pro oznámení",
|
||||
"paymentManagement": "Správa plateb",
|
||||
"percentFee": "Procento",
|
||||
"platform": "Platforma",
|
||||
"searchPlaceholder": "Zadejte hledané výrazy",
|
||||
"selectPlatform": "Vyberte platformu",
|
||||
"selectType": "Vyberte typ platby",
|
||||
"submit": "Odeslat",
|
||||
"type": "Typ",
|
||||
"updateSuccess": "Úspěšně aktualizováno"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Pouze pro zobrazení, po změně se změní všechny měnové jednotky v systému",
|
||||
"saveSuccess": "Úspěšně uloženo"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Uložení bylo úspěšné",
|
||||
"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",
|
||||
"keywordsDescription": "Používá se pro SEO účely",
|
||||
"logo": "LOGO",
|
||||
"logoDescription": "Používá se k zobrazení místa, kde je potřeba zobrazit LOGO",
|
||||
"logoPlaceholder": "Zadejte URL adresu LOGA, nekončete '/'",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Bestätigen",
|
||||
"confirmDelete": "Löschen bestätigen",
|
||||
"create": "Anzeige erstellen",
|
||||
"createAds": "Anzeige erstellen",
|
||||
"createSuccess": "Erfolgreich erstellt",
|
||||
"delete": "Löschen",
|
||||
"deleteSuccess": "Erfolgreich gelöscht",
|
||||
"deleteWarning": "Sind Sie sicher, dass Sie diese Anzeige löschen möchten?",
|
||||
"disabled": "Deaktiviert",
|
||||
"edit": "Bearbeiten",
|
||||
"editAds": "Anzeige bearbeiten",
|
||||
"enabled": "Aktiviert",
|
||||
"form": {
|
||||
"cancel": "Abbrechen",
|
||||
"confirm": "Bestätigen",
|
||||
"content": "Inhalt",
|
||||
"description": "Beschreibung",
|
||||
"endTime": "Endzeit",
|
||||
"enterContent": "Bitte geben Sie den Anzeigeninhalt ein",
|
||||
"enterDescription": "Bitte geben Sie die Anzeigenbeschreibung ein",
|
||||
"enterEndTime": "Bitte wählen Sie die Endzeit aus",
|
||||
"enterStartTime": "Bitte wählen Sie die Startzeit aus",
|
||||
"enterTargetUrl": "Bitte geben Sie die Ziel-URL ein",
|
||||
"enterTitle": "Bitte geben Sie den Anzeigentitel ein",
|
||||
"startTime": "Startzeit",
|
||||
"targetUrl": "Ziel-URL",
|
||||
"title": "Titel",
|
||||
"type": "Anzeigentyp",
|
||||
"typeImage": "Bild",
|
||||
"typeVideo": "Video"
|
||||
},
|
||||
"status": "Status",
|
||||
"targetUrl": "Ziel-URL",
|
||||
"title": "Titel",
|
||||
"type": "Typ",
|
||||
"updateSuccess": "Erfolgreich aktualisiert",
|
||||
"validityPeriod": "Gültigkeitsdauer"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
||||
"existingAccount": "Bereits ein Konto?",
|
||||
"get": "Erhalten",
|
||||
"invite": "Einladungscode",
|
||||
"invite": "Einladungscode (optional)",
|
||||
"message": "#### Sehr geehrter Nutzer,\n\nVielen Dank für Ihr Interesse und Ihre Unterstützung. Aufgrund einer Anpassung unserer Betriebsstrategie haben wir die Registrierung für neue Nutzer geschlossen. Während dieser Zeit wird die Nutzung für bestehende Nutzer nicht beeinträchtigt.\n\nWir sind bestrebt, Ihnen einen besseren Service und ein besseres Erlebnis zu bieten, und werden daher während der Schließung der Registrierung umfassende Systemoptimierungen und Funktionsupgrades durchführen. In Zukunft werden wir Sie mit hochwertigeren Inhalten und Dienstleistungen willkommen heißen.\n\nBitte folgen Sie unserer Website und unseren Social-Media-Plattformen, um die neuesten Updates und Benachrichtigungen zu erhalten. Vielen Dank für Ihr Verständnis und Ihre Unterstützung.\n\nBei Fragen oder wenn Sie Hilfe benötigen, kontaktieren Sie bitte jederzeit unser Kundenserviceteam.\n\n**Nochmals vielen Dank für Ihre Unterstützung und Ihr Verständnis.**",
|
||||
"passwordMismatch": "Die eingegebenen Passwörter stimmen nicht überein",
|
||||
"success": "Registrierung erfolgreich, Sie sind automatisch eingeloggt!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Virtuelle Maschine blockieren",
|
||||
"blockVirtualMachineDescription": "Wenn aktiviert, wird verhindert, dass Geräte auf virtuellen Maschinen oder Emulatoren ausgeführt werden.",
|
||||
"communicationKey": "Kommunikationsschlüssel",
|
||||
"communicationKeyDescription": "Der Kommunikationsschlüssel wird für die sichere Kommunikation zwischen Geräten und Servern verwendet.",
|
||||
"enable": "Aktivieren",
|
||||
"enableDescription": "Nach der Aktivierung werden mehrere Geräteidentifikatoren wie IMEI/IDFA/IDFV/AndroidID/Mac-Adresse für die Anmeldung und Registrierung unterstützt."
|
||||
"enableDescription": "Nach der Aktivierung werden mehrere Geräteidentifikatoren wie IMEI/IDFA/IDFV/AndroidID/Mac-Adresse für die Anmeldung und Registrierung unterstützt.",
|
||||
"enableSecurity": "Kommunikationsverschlüsselung aktivieren",
|
||||
"enableSecurityDescription": "Wenn aktiviert, wird die Kommunikation zwischen Geräten und Servern verschlüsselt.",
|
||||
"saveFailed": "Speichern fehlgeschlagen",
|
||||
"saveSuccess": "Speichern erfolgreich",
|
||||
"showAds": "Werbung anzeigen",
|
||||
"showAdsDescription": "Wenn aktiviert, werden auf den Geräten Anzeigen angezeigt."
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "E-Mail-Verifizierung",
|
||||
"emailVerificationDescription": "Wenn aktiviert, müssen Benutzer ihre E-Mail-Adresse verifizieren",
|
||||
"enable": "Aktivieren",
|
||||
"enableDescription": "Nach der Aktivierung werden die Funktionen zur Registrierung, Anmeldung, Bindung und Entbindung von Mobiltelefonen aktiviert",
|
||||
"enableDescription": "Nach der Aktivierung werden die Funktionen zur E-Mail-Registrierung, Anmeldung, Bindung und Entbindung aktiviert.",
|
||||
"expiration_email_template": "Vorlage für Ablaufbenachrichtigung",
|
||||
"expiration_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass Sie diese Variablen beibehalten.",
|
||||
"failed": "Fehlgeschlagen",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "Tschechisch",
|
||||
"de-DE": "Deutsch",
|
||||
"en-US": "Englisch",
|
||||
"es-ES": "Spanisch",
|
||||
"es-MX": "Spanisch (Mexiko)",
|
||||
"fa-IR": "Persisch",
|
||||
"fi-FI": "Finnisch",
|
||||
"fr-FR": "Französisch",
|
||||
"hi-IN": "Hindi",
|
||||
"hu-HU": "Ungarisch",
|
||||
"ja-JP": "Japanisch",
|
||||
"ko-KR": "Koreanisch",
|
||||
"no-NO": "Norwegisch",
|
||||
"pl-PL": "Polnisch",
|
||||
"pt-BR": "Portugiesisch (Brasilien)",
|
||||
"ro-RO": "Rumänisch",
|
||||
"ru-RU": "Russisch",
|
||||
"th-TH": "Thailändisch",
|
||||
"tr-TR": "Türkisch",
|
||||
"uk-UA": "Ukrainisch",
|
||||
"vi-VN": "Vietnamesisch",
|
||||
"zh-CN": "Chinesisch (vereinfacht)",
|
||||
"zh-HK": "Chinesisch (Traditionell)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "ADS-Konfiguration",
|
||||
"Announcement Management": "Ankündigungsverwaltung",
|
||||
"Apple": "Apple-ID",
|
||||
"Auth Control": "Authentifizierungskontrolle",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Alipay-Zahlung",
|
||||
"alipayf2f": {
|
||||
"appId": "Alipay Anwendungs-ID",
|
||||
"invoiceName": "Benutzerdefinierter Produktname",
|
||||
"invoiceNameDescription": "Wird in der Alipay-Rechnung angezeigt",
|
||||
"privateKey": "Alipay Privatschlüssel",
|
||||
"publicKey": "Alipay Öffentlicher Schlüssel",
|
||||
"sandbox": "Sandbox-Modus",
|
||||
"sandboxDescription": "Aktivieren Sie den Sandbox-Modus, um Zahlungen zu testen"
|
||||
},
|
||||
"AlipayF2F": "Alipay Gesicht zu Gesicht",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Zahlung beantragen",
|
||||
"balance": "Guthaben",
|
||||
"batchDelete": "Massenlöschung",
|
||||
"cancel": "Abbrechen",
|
||||
"config": "Konfiguration",
|
||||
"configInfo": "Konfigurationsinformationen",
|
||||
"configPlaceholder": "Bitte füllen Sie die bereitgestellte {field} Konfiguration aus",
|
||||
"confirm": "Bestätigen",
|
||||
"confirmDelete": "Löschen bestätigen",
|
||||
"copy": "Kopieren",
|
||||
"copySuccess": "Erfolgreich kopiert",
|
||||
"create": "Zahlungsmethode hinzufügen",
|
||||
"createPayment": "Zahlungsmethode hinzufügen",
|
||||
"createSuccess": "Erfolgreich erstellt",
|
||||
"delete": "Löschen",
|
||||
"deleteSuccess": "Erfolgreich gelöscht",
|
||||
"deleteWarning": "Sind Sie sicher, dass Sie diese Zahlungsmethode löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"description": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie die Beschreibung der Zahlungsmethode ein",
|
||||
"domain": "Domain",
|
||||
"domainPlaceholder": "optional, z.B. {example}",
|
||||
"edit": "Bearbeiten",
|
||||
"editPayment": "Zahlungsmethode bearbeiten",
|
||||
"enable": "Aktivieren",
|
||||
"enableDescription": "Diese Zahlungsmethode aktivieren",
|
||||
"epay": {
|
||||
"key": "SCHLÜSSEL",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Fester Betrag",
|
||||
"feeMode": "Gebührenmodus",
|
||||
"feeModeDescription": "Gebührenmodus für zusätzliche Gebühren",
|
||||
"feeModeItems": ["Keine Gebühr", "Prozentsatz", "Fester Betrag", "Beides"],
|
||||
"feePercent": "Prozentgebühr (optional)",
|
||||
"feePercentDescription": "Gebühren werden zusätzlich zum Bestellbetrag hinzugefügt",
|
||||
"fixedFee": "Feste Gebühr (optional)",
|
||||
"fixedFeeDescription": "Feste Gebühr zusätzlich zum Bestellbetrag hinzufügen",
|
||||
"iconUrl": "Symbol-URL (optional)",
|
||||
"iconUrlDescription": "Zur Anzeige im Frontend verwendet",
|
||||
"inputPlaceholder": "Bitte eingeben",
|
||||
"key": "SCHLÜSSEL",
|
||||
"notifyUrl": "Benutzerdefinierte Benachrichtigungs-URL (optional)",
|
||||
"notifyUrlDescription": "Gateway-Benachrichtigungen werden an diese Domain gesendet",
|
||||
"payForRecommendations": "Für Empfehlungen bezahlen",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Erfolgreich gespeichert",
|
||||
"showName": "Anzeigename",
|
||||
"showNameDescription": "Zur Anzeige im Frontend verwendet",
|
||||
"stripe": {
|
||||
"publicKey": "ÖFFENTLICHER SCHLÜSSEL",
|
||||
"secretKey": "GEHEIMER SCHLÜSSEL",
|
||||
"webhookSecret": "WEBHOOK-SCHLÜSSEL-SIGNATUR"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "WeChat Pay"
|
||||
"handlingFee": "Bearbeitungsgebühr",
|
||||
"icon": "Symbol",
|
||||
"iconPlaceholder": "Geben Sie die URL des Symbols ein",
|
||||
"mark": "Kennung",
|
||||
"markPlaceholder": "Geben Sie die Kennung der Zahlungsmethode ein",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Geben Sie den Namen der Zahlungsmethode ein",
|
||||
"nameRequired": "Name ist erforderlich",
|
||||
"noFee": "Keine Gebühr",
|
||||
"notify_url": "Benachrichtigungs-URL",
|
||||
"paymentManagement": "Zahlungsverwaltung",
|
||||
"percentFee": "Prozentsatz",
|
||||
"platform": "Plattform",
|
||||
"searchPlaceholder": "Suchbegriffe eingeben",
|
||||
"selectPlatform": "Plattform auswählen",
|
||||
"selectType": "Zahlungsart auswählen",
|
||||
"submit": "Einreichen",
|
||||
"type": "Typ",
|
||||
"updateSuccess": "Erfolgreich aktualisiert"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Nur zur Anzeige verwendet, nach der Änderung werden alle Währungseinheiten im System geändert",
|
||||
"saveSuccess": "Erfolgreich gespeichert"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Erfolgreich gespeichert",
|
||||
"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",
|
||||
"keywordsDescription": "Für SEO-Zwecke verwendet",
|
||||
"logo": "LOGO",
|
||||
"logoDescription": "Position zur Anzeige des LOGOs",
|
||||
"logoPlaceholder": "Bitte geben Sie die URL-Adresse des LOGOs ein, ohne '/' am Ende",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"confirmDelete": "Confirm Delete",
|
||||
"create": "Create Ad",
|
||||
"createAds": "Create Advertisement",
|
||||
"createSuccess": "Successfully created",
|
||||
"delete": "Delete",
|
||||
"deleteSuccess": "Successfully deleted",
|
||||
"deleteWarning": "Are you sure you want to delete this ad?",
|
||||
"disabled": "Disabled",
|
||||
"edit": "Edit",
|
||||
"editAds": "Edit Advertisement",
|
||||
"enabled": "Enabled",
|
||||
"form": {
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"content": "Content",
|
||||
"description": "Description",
|
||||
"endTime": "End Time",
|
||||
"enterContent": "Please enter ad content",
|
||||
"enterDescription": "Please enter ad description",
|
||||
"enterEndTime": "Please select end time",
|
||||
"enterStartTime": "Please select start time",
|
||||
"enterTargetUrl": "Please enter target URL",
|
||||
"enterTitle": "Please enter ad title",
|
||||
"startTime": "Start Time",
|
||||
"targetUrl": "Target URL",
|
||||
"title": "Title",
|
||||
"type": "Ad Type",
|
||||
"typeImage": "Image",
|
||||
"typeVideo": "Video"
|
||||
},
|
||||
"status": "Status",
|
||||
"targetUrl": "Target URL",
|
||||
"title": "Title",
|
||||
"type": "Type",
|
||||
"updateSuccess": "Successfully updated",
|
||||
"validityPeriod": "Validity Period"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Please enter a valid email address.",
|
||||
"existingAccount": "Already have an account?",
|
||||
"get": "Get",
|
||||
"invite": "Invitation code",
|
||||
"invite": "Invitation code (optional)",
|
||||
"message": "#### Dear User, Hello!\n\nThank you for your attention and support. Due to adjustments in site operation strategy, we have closed the new user registration function. During this period, existing users will not be affected.\n\nWe are committed to providing you with better service and experience, so we will conduct comprehensive system optimization and feature upgrades during the registration closure. In the future, we will welcome you with better content and services.\n\nPlease follow our website and social media platforms to get the latest updates and notifications. Thank you for your understanding and support.\n\nIf you have any questions or need assistance, please feel free to contact our customer service team.\n\n**Thank you again for your support and understanding.**",
|
||||
"passwordMismatch": "Passwords do not match",
|
||||
"success": "Registration successful, automatically logged in!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Block Virtual Machine",
|
||||
"blockVirtualMachineDescription": "When enabled, devices will be prevented from running on virtual machines or emulators",
|
||||
"communicationKey": "Communication Key",
|
||||
"communicationKeyDescription": "Communication key is used for secure communication between devices and servers",
|
||||
"enable": "Enable",
|
||||
"enableDescription": "After enabling, multiple device identifiers such as IMEI/IDFA/IDFV/AndroidID/Mac address are supported for login and registration"
|
||||
"enableDescription": "After enabling, multiple device identifiers such as IMEI/IDFA/IDFV/AndroidID/Mac address are supported for login and registration",
|
||||
"enableSecurity": "Enable Communication Encryption",
|
||||
"enableSecurityDescription": "When enabled, the communication between devices and servers will be encrypted",
|
||||
"saveFailed": "Save failed",
|
||||
"saveSuccess": "Save successful",
|
||||
"showAds": "Show Advertisements",
|
||||
"showAdsDescription": "When enabled, advertisements will be displayed on devices"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Email Verification",
|
||||
"emailVerificationDescription": "When enabled, users will need to verify their email",
|
||||
"enable": "Enable",
|
||||
"enableDescription": "After enabling, mobile phone registration, login, binding, and unbinding functions will be enabled",
|
||||
"enableDescription": "After enabling, email registration, login, binding, and unbinding functions will be enabled",
|
||||
"expiration_email_template": "Expiration Notice Template",
|
||||
"expiration_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||
"failed": "Failed",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "Czech",
|
||||
"de-DE": "German",
|
||||
"en-US": "English",
|
||||
"es-ES": "Spanish",
|
||||
"es-MX": "Spanish (Mexico)",
|
||||
"fa-IR": "Persian",
|
||||
"fi-FI": "Finnish",
|
||||
"fr-FR": "French",
|
||||
"hi-IN": "Hindi",
|
||||
"hu-HU": "Hungarian",
|
||||
"ja-JP": "Japanese",
|
||||
"ko-KR": "Korean",
|
||||
"no-NO": "Norwegian",
|
||||
"pl-PL": "Polish",
|
||||
"pt-BR": "Portuguese (Brazil)",
|
||||
"ro-RO": "Romanian",
|
||||
"ru-RU": "Russian",
|
||||
"th-TH": "Thai",
|
||||
"tr-TR": "Turkish",
|
||||
"uk-UA": "Ukrainian",
|
||||
"vi-VN": "Vietnamese",
|
||||
"zh-CN": "Chinese (Simplified)",
|
||||
"zh-HK": "Chinese (Traditional)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "ADS Config",
|
||||
"Announcement Management": "Announcement Management",
|
||||
"Apple": "Apple ID",
|
||||
"Auth Control": "Auth Control",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Alipay payment",
|
||||
"alipayf2f": {
|
||||
"appId": "Alipay App ID",
|
||||
"invoiceName": "Custom Product Name",
|
||||
"invoiceNameDescription": "Will be displayed on Alipay bill",
|
||||
"privateKey": "Alipay Private Key",
|
||||
"publicKey": "Alipay Public Key",
|
||||
"sandbox": "Sandbox Mode",
|
||||
"sandboxDescription": "Enable sandbox mode to test payments"
|
||||
},
|
||||
"AlipayF2F": "Alipay Face to Face",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Apply for Payment",
|
||||
"balance": "Balance",
|
||||
"batchDelete": "Batch Delete",
|
||||
"cancel": "Cancel",
|
||||
"config": "Configuration",
|
||||
"configInfo": "Configuration Information",
|
||||
"configPlaceholder": "Please fill in the provided {field} configuration",
|
||||
"confirm": "Confirm",
|
||||
"confirmDelete": "Confirm Delete",
|
||||
"copy": "Copy",
|
||||
"copySuccess": "Copied successfully",
|
||||
"create": "Add Payment Method",
|
||||
"createPayment": "Add Payment Method",
|
||||
"createSuccess": "Created successfully",
|
||||
"delete": "Delete",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteWarning": "Are you sure you want to delete this payment method? This action cannot be undone.",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Enter payment method description",
|
||||
"domain": "Domain",
|
||||
"domainPlaceholder": "optional, e.g. {example}",
|
||||
"edit": "Edit",
|
||||
"editPayment": "Edit Payment Method",
|
||||
"enable": "Enable",
|
||||
"enableDescription": "Enable this payment method",
|
||||
"epay": {
|
||||
"key": "KEY",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Fixed Amount",
|
||||
"feeMode": "Fee Mode",
|
||||
"feeModeDescription": "Fee charging method for additional fees",
|
||||
"feeModeItems": ["No Fee", "Percentage", "Fixed Amount", "Both"],
|
||||
"feePercent": "Percentage Fee (optional)",
|
||||
"feePercentDescription": "Add fee on top of order amount",
|
||||
"fixedFee": "Fixed Fee (optional)",
|
||||
"fixedFeeDescription": "Add fixed fee on top of order amount",
|
||||
"iconUrl": "Icon URL (optional)",
|
||||
"iconUrlDescription": "Used for frontend display",
|
||||
"inputPlaceholder": "Please enter",
|
||||
"key": "KEY",
|
||||
"notifyUrl": "Custom Notification Domain (optional)",
|
||||
"notifyUrlDescription": "Gateway notifications will be sent to this domain",
|
||||
"payForRecommendations": "Pay for Recommendations",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Save Successful",
|
||||
"showName": "Display Name",
|
||||
"showNameDescription": "Used for frontend display",
|
||||
"stripe": {
|
||||
"publicKey": "PUBLIC KEY",
|
||||
"secretKey": "SECRET KEY",
|
||||
"webhookSecret": "WEBHOOK Secret Key"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "WeChat Pay"
|
||||
"feePercent": "Fee Percentage",
|
||||
"fixedFee": "Fixed Amount",
|
||||
"handlingFee": "Handling Fee",
|
||||
"icon": "Icon",
|
||||
"iconPlaceholder": "Enter icon URL",
|
||||
"mark": "Identifier",
|
||||
"markPlaceholder": "Enter payment method identifier",
|
||||
"name": "Name",
|
||||
"namePlaceholder": "Enter payment method name",
|
||||
"nameRequired": "Name is required",
|
||||
"noFee": "No Fee",
|
||||
"notify_url": "Notify URL",
|
||||
"paymentManagement": "Payment Management",
|
||||
"percentFee": "Percentage",
|
||||
"platform": "Platform",
|
||||
"searchPlaceholder": "Enter search terms",
|
||||
"selectPlatform": "Select Platform",
|
||||
"selectType": "Select payment type",
|
||||
"submit": "Submit",
|
||||
"type": "Type",
|
||||
"updateSuccess": "Updated successfully"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Used for display purposes only; changing this will affect all currency units in the system",
|
||||
"saveSuccess": "Save Successful"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Save Successful",
|
||||
"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",
|
||||
"keywordsDescription": "Used for SEO purposes",
|
||||
"logo": "Logo",
|
||||
"logoDescription": "Used for displaying the logo in designated locations",
|
||||
"logoPlaceholder": "Enter the URL of the logo, without ending with '/'",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"confirmDelete": "Confirmar eliminación",
|
||||
"create": "Crear anuncio",
|
||||
"createAds": "Crear publicidad",
|
||||
"createSuccess": "Creado con éxito",
|
||||
"delete": "Eliminar",
|
||||
"deleteSuccess": "Eliminado con éxito",
|
||||
"deleteWarning": "¿Está seguro de que desea eliminar este anuncio?",
|
||||
"disabled": "Deshabilitado",
|
||||
"edit": "Editar",
|
||||
"editAds": "Editar publicidad",
|
||||
"enabled": "Habilitado",
|
||||
"form": {
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"content": "Contenido",
|
||||
"description": "Descripción",
|
||||
"endTime": "Hora de finalización",
|
||||
"enterContent": "Por favor, introduzca el contenido del anuncio",
|
||||
"enterDescription": "Por favor, introduzca la descripción del anuncio",
|
||||
"enterEndTime": "Por favor, seleccione la hora de finalización",
|
||||
"enterStartTime": "Por favor, seleccione la hora de inicio",
|
||||
"enterTargetUrl": "Por favor, introduzca la URL de destino",
|
||||
"enterTitle": "Por favor, introduzca el título del anuncio",
|
||||
"startTime": "Hora de inicio",
|
||||
"targetUrl": "URL de destino",
|
||||
"title": "Título",
|
||||
"type": "Tipo de anuncio",
|
||||
"typeImage": "Imagen",
|
||||
"typeVideo": "Vídeo"
|
||||
},
|
||||
"status": "Estado",
|
||||
"targetUrl": "URL de destino",
|
||||
"title": "Título",
|
||||
"type": "Tipo",
|
||||
"updateSuccess": "Actualizado con éxito",
|
||||
"validityPeriod": "Período de validez"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Por favor, introduce una dirección de correo electrónico válida.",
|
||||
"existingAccount": "¿Ya tienes una cuenta?",
|
||||
"get": "Obtener",
|
||||
"invite": "Código de invitación",
|
||||
"invite": "Código de invitación (opcional)",
|
||||
"message": "#### ¡Estimado usuario!\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a brindarle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
||||
"passwordMismatch": "Las contraseñas no coinciden",
|
||||
"success": "¡Registro exitoso, sesión iniciada automáticamente!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Bloquear Máquina Virtual",
|
||||
"blockVirtualMachineDescription": "Cuando está habilitado, se evitará que los dispositivos se ejecuten en máquinas virtuales o emuladores",
|
||||
"communicationKey": "Clave de Comunicación",
|
||||
"communicationKeyDescription": "La clave de comunicación se utiliza para la comunicación segura entre dispositivos y servidores",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Después de habilitar, se admiten múltiples identificadores de dispositivos como IMEI/IDFA/IDFV/AndroidID/dirección MAC para el inicio de sesión y el registro"
|
||||
"enableDescription": "Después de habilitar, se admiten múltiples identificadores de dispositivos como IMEI/IDFA/IDFV/AndroidID/dirección MAC para el inicio de sesión y el registro",
|
||||
"enableSecurity": "Habilitar Cifrado de Comunicación",
|
||||
"enableSecurityDescription": "Cuando está habilitado, la comunicación entre dispositivos y servidores será cifrada",
|
||||
"saveFailed": "Error al guardar",
|
||||
"saveSuccess": "Guardado exitoso",
|
||||
"showAds": "Mostrar Anuncios",
|
||||
"showAdsDescription": "Cuando está habilitado, se mostrarán anuncios en los dispositivos"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Verificación de correo electrónico",
|
||||
"emailVerificationDescription": "Cuando esté habilitado, los usuarios deberán verificar su correo electrónico",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Después de habilitar, se activarán las funciones de registro, inicio de sesión, vinculación y desvinculación de teléfonos móviles",
|
||||
"enableDescription": "Después de habilitar, se activarán las funciones de registro por correo electrónico, inicio de sesión, vinculación y desvinculación.",
|
||||
"expiration_email_template": "Plantilla de Aviso de Vencimiento",
|
||||
"expiration_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||
"failed": "Fallido",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "Checo",
|
||||
"de-DE": "Alemán",
|
||||
"en-US": "inglés",
|
||||
"es-ES": "español",
|
||||
"es-MX": "Español (México)",
|
||||
"fa-IR": "Persa",
|
||||
"fi-FI": "Finlandés",
|
||||
"fr-FR": "Francés",
|
||||
"hi-IN": "hindi",
|
||||
"hu-HU": "húngaro",
|
||||
"ja-JP": "Japonés",
|
||||
"ko-KR": "Coreano",
|
||||
"no-NO": "Noruego",
|
||||
"pl-PL": "Polaco",
|
||||
"pt-BR": "portugués (Brasil)",
|
||||
"ro-RO": "Rumano",
|
||||
"ru-RU": "Ruso",
|
||||
"th-TH": "Tailandés",
|
||||
"tr-TR": "turco",
|
||||
"uk-UA": "Ucraniano",
|
||||
"vi-VN": "Vietnamita",
|
||||
"zh-CN": "Español (España)",
|
||||
"zh-HK": "chino (tradicional)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "Configuración de ADS",
|
||||
"Announcement Management": "Gestión de Anuncios",
|
||||
"Apple": "ID de Apple",
|
||||
"Auth Control": "Control de Autenticación",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Pago con Alipay",
|
||||
"alipayf2f": {
|
||||
"appId": "ID de la aplicación de Alipay",
|
||||
"invoiceName": "Nombre personalizado del producto",
|
||||
"invoiceNameDescription": "Se mostrará en la factura de Alipay",
|
||||
"privateKey": "Clave privada de Alipay",
|
||||
"publicKey": "Clave pública de Alipay",
|
||||
"sandbox": "Modo sandbox",
|
||||
"sandboxDescription": "Habilitar el modo sandbox para probar el pago"
|
||||
},
|
||||
"AlipayF2F": "Alipay Cara a Cara",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Solicitar Pago",
|
||||
"balance": "Saldo",
|
||||
"batchDelete": "Eliminar en Lote",
|
||||
"cancel": "Cancelar",
|
||||
"config": "Configuración",
|
||||
"configInfo": "Información de Configuración",
|
||||
"configPlaceholder": "Por favor, complete la configuración de {field} proporcionada",
|
||||
"confirm": "Confirmar",
|
||||
"confirmDelete": "Confirmar Eliminación",
|
||||
"copy": "Copiar",
|
||||
"copySuccess": "Copiado con éxito",
|
||||
"create": "Agregar Método de Pago",
|
||||
"createPayment": "Agregar Método de Pago",
|
||||
"createSuccess": "Creado con éxito",
|
||||
"delete": "Eliminar",
|
||||
"deleteSuccess": "Eliminado con éxito",
|
||||
"deleteWarning": "¿Está seguro de que desea eliminar este método de pago? Esta acción no se puede deshacer.",
|
||||
"description": "Descripción",
|
||||
"descriptionPlaceholder": "Ingrese la descripción del método de pago",
|
||||
"domain": "Dominio",
|
||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||
"edit": "Editar",
|
||||
"editPayment": "Editar Método de Pago",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Habilitar este método de pago",
|
||||
"epay": {
|
||||
"key": "CLAVE",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Monto Fijo",
|
||||
"feeMode": "Modo de cobro",
|
||||
"feeModeDescription": "Modo de cobro de tarifas adicionales",
|
||||
"feeModeItems": ["Sin tarifa", "Porcentaje", "Cantidad fija", "Ambos anteriores"],
|
||||
"feePercent": "Porcentaje de tarifa (opcional)",
|
||||
"feePercentDescription": "Se añade una tarifa sobre el monto del pedido",
|
||||
"fixedFee": "Tarifa fija (opcional)",
|
||||
"fixedFeeDescription": "Se añade una tarifa fija sobre el monto del pedido",
|
||||
"iconUrl": "URL del icono (opcional)",
|
||||
"iconUrlDescription": "Usado para mostrar en el frontend",
|
||||
"inputPlaceholder": "Por favor, introduzca",
|
||||
"key": "CLAVE",
|
||||
"notifyUrl": "Dominio de notificación personalizado (opcional)",
|
||||
"notifyUrlDescription": "La notificación del gateway se enviará a este dominio",
|
||||
"payForRecommendations": "Pagar por recomendaciones",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Guardado exitosamente",
|
||||
"showName": "Mostrar nombre",
|
||||
"showNameDescription": "Usado para mostrar en el frontend",
|
||||
"stripe": {
|
||||
"publicKey": "CLAVE PÚBLICA",
|
||||
"secretKey": "CLAVE SECRETA",
|
||||
"webhookSecret": "FIRMA DE CLAVE WEBHOOK"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "Pago con WeChat"
|
||||
"handlingFee": "Tarifa de manejo",
|
||||
"icon": "Ícono",
|
||||
"iconPlaceholder": "Ingrese la URL del ícono",
|
||||
"mark": "Identificador",
|
||||
"markPlaceholder": "Ingrese el identificador del método de pago",
|
||||
"name": "Nombre",
|
||||
"namePlaceholder": "Ingrese el nombre del método de pago",
|
||||
"nameRequired": "El nombre es obligatorio",
|
||||
"noFee": "Sin Tarifa",
|
||||
"notify_url": "URL de notificación",
|
||||
"paymentManagement": "Gestión de Pagos",
|
||||
"percentFee": "Porcentaje",
|
||||
"platform": "Plataforma",
|
||||
"searchPlaceholder": "Ingrese términos de búsqueda",
|
||||
"selectPlatform": "Seleccionar Plataforma",
|
||||
"selectType": "Seleccionar tipo de pago",
|
||||
"submit": "Enviar",
|
||||
"type": "Tipo",
|
||||
"updateSuccess": "Actualizado con éxito"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Solo para uso de visualización, al cambiarlo se modificarán todas las unidades monetarias en el sistema",
|
||||
"saveSuccess": "Guardado exitosamente"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Guardado con éxito",
|
||||
"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",
|
||||
"keywordsDescription": "Utilizado para fines de SEO",
|
||||
"logo": "LOGO",
|
||||
"logoDescription": "Se utiliza para mostrar la ubicación donde se debe mostrar el LOGO",
|
||||
"logoPlaceholder": "Por favor, introduce la URL del LOGO, no termines con '/'",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"confirmDelete": "Confirmar Eliminación",
|
||||
"create": "Crear Anuncio",
|
||||
"createAds": "Crear Publicidad",
|
||||
"createSuccess": "Creado con éxito",
|
||||
"delete": "Eliminar",
|
||||
"deleteSuccess": "Eliminado con éxito",
|
||||
"deleteWarning": "¿Está seguro de que desea eliminar este anuncio?",
|
||||
"disabled": "Deshabilitado",
|
||||
"edit": "Editar",
|
||||
"editAds": "Editar Publicidad",
|
||||
"enabled": "Habilitado",
|
||||
"form": {
|
||||
"cancel": "Cancelar",
|
||||
"confirm": "Confirmar",
|
||||
"content": "Contenido",
|
||||
"description": "Descripción",
|
||||
"endTime": "Hora de Finalización",
|
||||
"enterContent": "Por favor ingrese el contenido del anuncio",
|
||||
"enterDescription": "Por favor ingrese la descripción del anuncio",
|
||||
"enterEndTime": "Por favor seleccione la hora de finalización",
|
||||
"enterStartTime": "Por favor seleccione la hora de inicio",
|
||||
"enterTargetUrl": "Por favor ingrese la URL de destino",
|
||||
"enterTitle": "Por favor ingrese el título del anuncio",
|
||||
"startTime": "Hora de Inicio",
|
||||
"targetUrl": "URL de Destino",
|
||||
"title": "Título",
|
||||
"type": "Tipo de Anuncio",
|
||||
"typeImage": "Imagen",
|
||||
"typeVideo": "Video"
|
||||
},
|
||||
"status": "Estado",
|
||||
"targetUrl": "URL de Destino",
|
||||
"title": "Título",
|
||||
"type": "Tipo",
|
||||
"updateSuccess": "Actualizado con éxito",
|
||||
"validityPeriod": "Período de Validez"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Por favor, ingresa una dirección de correo electrónico válida.",
|
||||
"existingAccount": "¿Ya tienes una cuenta?",
|
||||
"get": "Obtener",
|
||||
"invite": "Código de invitación",
|
||||
"invite": "Código de invitación (opcional)",
|
||||
"message": "#### Estimado usuario,\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a ofrecerle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
||||
"passwordMismatch": "Las contraseñas no coinciden",
|
||||
"success": "¡Registro exitoso, has iniciado sesión automáticamente!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Bloquear Máquina Virtual",
|
||||
"blockVirtualMachineDescription": "Cuando está habilitado, se evitará que los dispositivos se ejecuten en máquinas virtuales o emuladores",
|
||||
"communicationKey": "Clave de Comunicación",
|
||||
"communicationKeyDescription": "La clave de comunicación se utiliza para la comunicación segura entre dispositivos y servidores",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Después de habilitar, se admiten múltiples identificadores de dispositivos como IMEI/IDFA/IDFV/AndroidID/dirección Mac para iniciar sesión y registrarse"
|
||||
"enableDescription": "Después de habilitar, se admiten múltiples identificadores de dispositivos como IMEI/IDFA/IDFV/AndroidID/dirección Mac para iniciar sesión y registrarse",
|
||||
"enableSecurity": "Habilitar Cifrado de Comunicación",
|
||||
"enableSecurityDescription": "Cuando está habilitado, la comunicación entre dispositivos y servidores será cifrada",
|
||||
"saveFailed": "Error al guardar",
|
||||
"saveSuccess": "Guardado exitoso",
|
||||
"showAds": "Mostrar Anuncios",
|
||||
"showAdsDescription": "Cuando está habilitado, se mostrarán anuncios en los dispositivos"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Verificación de Correo Electrónico",
|
||||
"emailVerificationDescription": "Cuando esté habilitado, los usuarios deberán verificar su correo electrónico",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Después de habilitar, se activarán las funciones de registro, inicio de sesión, vinculación y desvinculación de teléfonos móviles",
|
||||
"enableDescription": "Después de habilitar, se activarán las funciones de registro por correo electrónico, inicio de sesión, vinculación y desvinculación.",
|
||||
"expiration_email_template": "Plantilla de Aviso de Vencimiento",
|
||||
"expiration_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||
"failed": "Fallido",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "Checo",
|
||||
"de-DE": "Alemán",
|
||||
"en-US": "inglés",
|
||||
"es-ES": "español",
|
||||
"es-MX": "Español (México)",
|
||||
"fa-IR": "Persa",
|
||||
"fi-FI": "Finlandés",
|
||||
"fr-FR": "Francés",
|
||||
"hi-IN": "Hindi",
|
||||
"hu-HU": "Húngaro",
|
||||
"ja-JP": "Japonés",
|
||||
"ko-KR": "Coreano",
|
||||
"no-NO": "Noruego",
|
||||
"pl-PL": "Polaco",
|
||||
"pt-BR": "portugués (Brasil)",
|
||||
"ro-RO": "Rumano",
|
||||
"ru-RU": "Ruso",
|
||||
"th-TH": "Tailandés",
|
||||
"tr-TR": "turco",
|
||||
"uk-UA": "Ucraniano",
|
||||
"vi-VN": "Vietnamita",
|
||||
"zh-CN": "español (México)",
|
||||
"zh-HK": "Chino (Tradicional)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "Configuración de ADS",
|
||||
"Announcement Management": "Gestión de Anuncios",
|
||||
"Apple": "ID de Apple",
|
||||
"Auth Control": "Control de Autenticación",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Pago con Alipay",
|
||||
"alipayf2f": {
|
||||
"appId": "ID de la aplicación de Alipay",
|
||||
"invoiceName": "Nombre personalizado del producto",
|
||||
"invoiceNameDescription": "Se mostrará en la factura de Alipay",
|
||||
"privateKey": "Clave privada de Alipay",
|
||||
"publicKey": "Clave pública de Alipay",
|
||||
"sandbox": "Modo sandbox",
|
||||
"sandboxDescription": "Habilitar el modo sandbox para probar el pago"
|
||||
},
|
||||
"AlipayF2F": "Alipay Cara a Cara",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Solicitar Pago",
|
||||
"balance": "Saldo",
|
||||
"batchDelete": "Eliminar en Lote",
|
||||
"cancel": "Cancelar",
|
||||
"config": "Configuración",
|
||||
"configInfo": "Información de Configuración",
|
||||
"configPlaceholder": "Por favor, complete la configuración de {field} proporcionada",
|
||||
"confirm": "Confirmar",
|
||||
"confirmDelete": "Confirmar Eliminación",
|
||||
"copy": "Copiar",
|
||||
"copySuccess": "Copiado con éxito",
|
||||
"create": "Agregar Método de Pago",
|
||||
"createPayment": "Agregar Método de Pago",
|
||||
"createSuccess": "Creado con éxito",
|
||||
"delete": "Eliminar",
|
||||
"deleteSuccess": "Eliminado con éxito",
|
||||
"deleteWarning": "¿Está seguro de que desea eliminar este método de pago? Esta acción no se puede deshacer.",
|
||||
"description": "Descripción",
|
||||
"descriptionPlaceholder": "Ingresa la descripción del método de pago",
|
||||
"domain": "Dominio",
|
||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||
"edit": "Editar",
|
||||
"editPayment": "Editar Método de Pago",
|
||||
"enable": "Habilitar",
|
||||
"enableDescription": "Habilitar este método de pago",
|
||||
"epay": {
|
||||
"key": "CLAVE",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Monto Fijo",
|
||||
"feeMode": "Modo de cobro",
|
||||
"feeModeDescription": "Modo de cobro de tarifas adicionales",
|
||||
"feeModeItems": ["Sin tarifa", "Porcentaje", "Cantidad fija", "Ambos"],
|
||||
"feePercent": "Porcentaje de tarifa (opcional)",
|
||||
"feePercentDescription": "Se añade una tarifa sobre el monto del pedido",
|
||||
"fixedFee": "Cuota fija (opcional)",
|
||||
"fixedFeeDescription": "Agregar una tarifa fija sobre el monto del pedido",
|
||||
"iconUrl": "URL del ícono (opcional)",
|
||||
"iconUrlDescription": "Usado para mostrar en el frontend",
|
||||
"inputPlaceholder": "Por favor ingrese",
|
||||
"key": "LLAVE",
|
||||
"notifyUrl": "Dominio de notificación personalizado (opcional)",
|
||||
"notifyUrlDescription": "La notificación del gateway se enviará a este dominio",
|
||||
"payForRecommendations": "Pagar por recomendaciones",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Guardado exitoso",
|
||||
"showName": "Mostrar nombre",
|
||||
"showNameDescription": "Usado para mostrar en el frontend",
|
||||
"stripe": {
|
||||
"publicKey": "CLAVE PÚBLICA",
|
||||
"secretKey": "CLAVE SECRETA",
|
||||
"webhookSecret": "FIRMA DE SECRETO DE WEBHOOK"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "WeChat Pay"
|
||||
"handlingFee": "Cargo por manejo",
|
||||
"icon": "Ícono",
|
||||
"iconPlaceholder": "Ingrese la URL del ícono",
|
||||
"mark": "Identificador",
|
||||
"markPlaceholder": "Ingrese el identificador del método de pago",
|
||||
"name": "Nombre",
|
||||
"namePlaceholder": "Ingrese el nombre del método de pago",
|
||||
"nameRequired": "El nombre es obligatorio",
|
||||
"noFee": "Sin Tarifa",
|
||||
"notify_url": "URL de notificación",
|
||||
"paymentManagement": "Gestión de Pagos",
|
||||
"percentFee": "Porcentaje",
|
||||
"platform": "Plataforma",
|
||||
"searchPlaceholder": "Ingrese términos de búsqueda",
|
||||
"selectPlatform": "Seleccionar Plataforma",
|
||||
"selectType": "Seleccionar tipo de pago",
|
||||
"submit": "Enviar",
|
||||
"type": "Tipo",
|
||||
"updateSuccess": "Actualizado con éxito"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Solo para fines de visualización, cambiarlo alterará todas las unidades monetarias en el sistema",
|
||||
"saveSuccess": "Guardado exitosamente"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Guardado exitoso",
|
||||
"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",
|
||||
"keywordsDescription": "Utilizado para propósitos de SEO",
|
||||
"logo": "LOGO",
|
||||
"logoDescription": "Usado para mostrar la ubicación donde se debe exhibir el LOGO",
|
||||
"logoPlaceholder": "Por favor, ingresa la URL del LOGO, no termines con '/'",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "لغو",
|
||||
"confirm": "تأیید",
|
||||
"confirmDelete": "تأیید حذف",
|
||||
"create": "ایجاد آگهی",
|
||||
"createAds": "ایجاد تبلیغ",
|
||||
"createSuccess": "با موفقیت ایجاد شد",
|
||||
"delete": "حذف",
|
||||
"deleteSuccess": "با موفقیت حذف شد",
|
||||
"deleteWarning": "آیا مطمئن هستید که میخواهید این آگهی را حذف کنید؟",
|
||||
"disabled": "غیرفعال",
|
||||
"edit": "ویرایش",
|
||||
"editAds": "ویرایش تبلیغ",
|
||||
"enabled": "فعال",
|
||||
"form": {
|
||||
"cancel": "لغو",
|
||||
"confirm": "تأیید",
|
||||
"content": "محتوا",
|
||||
"description": "توضیحات",
|
||||
"endTime": "زمان پایان",
|
||||
"enterContent": "لطفاً محتوای آگهی را وارد کنید",
|
||||
"enterDescription": "لطفاً توضیحات آگهی را وارد کنید",
|
||||
"enterEndTime": "لطفاً زمان پایان را انتخاب کنید",
|
||||
"enterStartTime": "لطفاً زمان شروع را انتخاب کنید",
|
||||
"enterTargetUrl": "لطفاً URL هدف را وارد کنید",
|
||||
"enterTitle": "لطفاً عنوان آگهی را وارد کنید",
|
||||
"startTime": "زمان شروع",
|
||||
"targetUrl": "URL هدف",
|
||||
"title": "عنوان",
|
||||
"type": "نوع آگهی",
|
||||
"typeImage": "تصویر",
|
||||
"typeVideo": "ویدیو"
|
||||
},
|
||||
"status": "وضعیت",
|
||||
"targetUrl": "URL هدف",
|
||||
"title": "عنوان",
|
||||
"type": "نوع",
|
||||
"updateSuccess": "با موفقیت بهروزرسانی شد",
|
||||
"validityPeriod": "مدت اعتبار"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "لطفاً یک آدرس ایمیل معتبر وارد کنید.",
|
||||
"existingAccount": "آیا قبلاً حساب کاربری دارید؟",
|
||||
"get": "دریافت",
|
||||
"invite": "کد دعوت",
|
||||
"invite": "کد دعوت (اختیاری)",
|
||||
"message": "#### کاربر گرامی، سلام!\n\nاز توجه و حمایت شما سپاسگزاریم. به دلیل تغییرات در استراتژی عملیاتی سایت، عملکرد ثبتنام کاربران جدید را متوقف کردهایم. در این مدت، کاربران فعلی تحت تأثیر قرار نخواهند گرفت.\n\nما متعهد به ارائه خدمات و تجربه بهتر به شما هستیم، بنابراین در طول این دوره، بهینهسازی جامع سیستم و ارتقاء ویژگیها را انجام خواهیم داد. در آینده، با محتوای و خدمات بهتر از شما استقبال خواهیم کرد.\n\nلطفاً برای دریافت آخرین بهروزرسانیها و اطلاعیهها، وبسایت و پلتفرمهای اجتماعی ما را دنبال کنید. از درک و حمایت شما سپاسگزاریم.\n\nاگر سوالی دارید یا به کمک نیاز دارید، لطفاً با تیم خدمات مشتری ما تماس بگیرید.\n\n**بار دیگر از حمایت و درک شما سپاسگزاریم.**",
|
||||
"passwordMismatch": "رمزهای عبور مطابقت ندارند",
|
||||
"success": "ثبتنام موفقیتآمیز بود، بهطور خودکار وارد شدید!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "مسدود کردن ماشین مجازی",
|
||||
"blockVirtualMachineDescription": "هنگامی که فعال باشد، دستگاهها از اجرای ماشینهای مجازی یا شبیهسازها جلوگیری خواهند شد",
|
||||
"communicationKey": "کلید ارتباطی",
|
||||
"communicationKeyDescription": "کلید ارتباطی برای ارتباط امن بین دستگاهها و سرورها استفاده میشود",
|
||||
"enable": "فعال کردن",
|
||||
"enableDescription": "پس از فعالسازی، شناسههای دستگاههای مختلف مانند IMEI/IDFA/IDFV/AndroidID/آدرس مک برای ورود و ثبتنام پشتیبانی میشوند"
|
||||
"enableDescription": "پس از فعالسازی، شناسههای دستگاههای مختلف مانند IMEI/IDFA/IDFV/AndroidID/آدرس مک برای ورود و ثبتنام پشتیبانی میشوند",
|
||||
"enableSecurity": "فعالسازی رمزنگاری ارتباطات",
|
||||
"enableSecurityDescription": "هنگامی که فعال باشد، ارتباط بین دستگاهها و سرورها رمزنگاری خواهد شد",
|
||||
"saveFailed": "ذخیرهسازی ناموفق بود",
|
||||
"saveSuccess": "ذخیرهسازی با موفقیت انجام شد",
|
||||
"showAds": "نمایش تبلیغات",
|
||||
"showAdsDescription": "هنگامی که فعال باشد، تبلیغات بر روی دستگاهها نمایش داده خواهد شد"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "تأیید ایمیل",
|
||||
"emailVerificationDescription": "هنگامی که فعال شود، کاربران باید ایمیل خود را تأیید کنند",
|
||||
"enable": "فعال کردن",
|
||||
"enableDescription": "پس از فعالسازی، عملکردهای ثبتنام، ورود، اتصال و قطع اتصال تلفن همراه فعال خواهند شد",
|
||||
"enableDescription": "پس از فعالسازی، ثبتنام ایمیلی، ورود، پیوند و جداسازی عملکردها فعال خواهند شد",
|
||||
"expiration_email_template": "قالب اطلاعیه انقضا",
|
||||
"expiration_email_templateDescription": "جایگذاریهای {after}.variable{before} با دادههای واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
||||
"failed": "ناموفق",
|
||||
@@ -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": "پسوندهای لیست سفید",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "چکی",
|
||||
"de-DE": "آلمانی",
|
||||
"en-US": "انگلیسی",
|
||||
"es-ES": "اسپانیایی",
|
||||
"es-MX": "اسپانیایی (مکزیک)",
|
||||
"fa-IR": "فارسی",
|
||||
"fi-FI": "فنلاندی",
|
||||
"fr-FR": "فرانسوی",
|
||||
"hi-IN": "هندی",
|
||||
"hu-HU": "مجاری",
|
||||
"ja-JP": "ژاپنی",
|
||||
"ko-KR": "کرهای",
|
||||
"no-NO": "نروژی",
|
||||
"pl-PL": "لهستانی",
|
||||
"pt-BR": "پرتغالی (برزیل)",
|
||||
"ro-RO": "رومانیایی",
|
||||
"ru-RU": "روسی",
|
||||
"th-TH": "تایلندی",
|
||||
"tr-TR": "ترکی",
|
||||
"uk-UA": "اوکراینی",
|
||||
"vi-VN": "ویتنامی",
|
||||
"zh-CN": "چینی (ساده شده)",
|
||||
"zh-HK": "چینی (سنتی)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "تنظیمات ADS",
|
||||
"Announcement Management": "مدیریت اطلاعیهها",
|
||||
"Apple": "شناسه اپل",
|
||||
"Auth Control": "کنترل احراز هویت",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "پرداخت علیپی",
|
||||
"alipayf2f": {
|
||||
"appId": "شناسه برنامه Alipay",
|
||||
"invoiceName": "نام محصول سفارشی",
|
||||
"invoiceNameDescription": "در صورتحساب Alipay نمایش داده خواهد شد",
|
||||
"privateKey": "کلید خصوصی Alipay",
|
||||
"publicKey": "کلید عمومی Alipay",
|
||||
"sandbox": "حالت Sandbox",
|
||||
"sandboxDescription": "حالت sandbox را برای آزمایش پرداختها فعال کنید"
|
||||
},
|
||||
"AlipayF2F": "علیپی چهره به چهره",
|
||||
"EPay": "ایپی",
|
||||
"Stripe": "استرایپ",
|
||||
"applyForPayment": "درخواست پرداخت",
|
||||
"balance": "موجودی",
|
||||
"batchDelete": "حذف دستهای",
|
||||
"cancel": "لغو",
|
||||
"config": "پیکربندی",
|
||||
"configInfo": "اطلاعات پیکربندی",
|
||||
"configPlaceholder": "لطفاً پیکربندی {field} ارائه شده را پر کنید",
|
||||
"confirm": "تأیید",
|
||||
"confirmDelete": "تأیید حذف",
|
||||
"copy": "کپی",
|
||||
"copySuccess": "با موفقیت کپی شد",
|
||||
"create": "افزودن روش پرداخت",
|
||||
"createPayment": "افزودن روش پرداخت",
|
||||
"createSuccess": "با موفقیت ایجاد شد",
|
||||
"delete": "حذف",
|
||||
"deleteSuccess": "با موفقیت حذف شد",
|
||||
"deleteWarning": "آیا مطمئن هستید که میخواهید این روش پرداخت را حذف کنید؟ این عمل قابل بازگشت نیست.",
|
||||
"description": "توضیحات",
|
||||
"descriptionPlaceholder": "توضیحات روش پرداخت را وارد کنید",
|
||||
"domain": "دامنه",
|
||||
"domainPlaceholder": "اختیاری، به عنوان مثال {example}",
|
||||
"edit": "ویرایش",
|
||||
"editPayment": "ویرایش روش پرداخت",
|
||||
"enable": "فعال کردن",
|
||||
"enableDescription": "این روش پرداخت را فعال کنید",
|
||||
"epay": {
|
||||
"key": "کلید",
|
||||
"pid": "شناسه محصول",
|
||||
"url": "نشانی وب"
|
||||
},
|
||||
"feeAmount": "مقدار ثابت",
|
||||
"feeMode": "حالت هزینه",
|
||||
"feeModeDescription": "روش دریافت هزینه برای هزینههای اضافی",
|
||||
"feeModeItems": ["بدون هزینه", "درصدی", "مبلغ ثابت", "هر دو"],
|
||||
"feePercent": "درصد کارمزد (اختیاری)",
|
||||
"feePercentDescription": "افزودن کارمزد به مبلغ سفارش",
|
||||
"fixedFee": "هزینه ثابت (اختیاری)",
|
||||
"fixedFeeDescription": "افزودن هزینه ثابت به مبلغ سفارش",
|
||||
"iconUrl": "آدرس آیکون (اختیاری)",
|
||||
"iconUrlDescription": "برای نمایش در رابط کاربری استفاده میشود",
|
||||
"inputPlaceholder": "لطفاً وارد کنید",
|
||||
"key": "کلید",
|
||||
"notifyUrl": "دامنه اعلان سفارشی (اختیاری)",
|
||||
"notifyUrlDescription": "اعلانهای درگاه به این دامنه ارسال خواهند شد",
|
||||
"payForRecommendations": "پرداخت برای توصیهها",
|
||||
"pid": "شناسه فرآیند",
|
||||
"saveSuccess": "ذخیره با موفقیت انجام شد",
|
||||
"showName": "نمایش نام",
|
||||
"showNameDescription": "برای نمایش در قسمت جلویی استفاده میشود",
|
||||
"stripe": {
|
||||
"publicKey": "کلید عمومی",
|
||||
"secretKey": "کلید مخفی",
|
||||
"webhookSecret": "کلید مخفی وبهوک"
|
||||
},
|
||||
"url": "آدرس اینترنتی",
|
||||
"wechatPay": "ویچت پی"
|
||||
"handlingFee": "هزینه خدمات",
|
||||
"icon": "آیکون",
|
||||
"iconPlaceholder": "آدرس URL آیکون را وارد کنید",
|
||||
"mark": "شناسه",
|
||||
"markPlaceholder": "شناسه روش پرداخت را وارد کنید",
|
||||
"name": "نام",
|
||||
"namePlaceholder": "نام روش پرداخت را وارد کنید",
|
||||
"nameRequired": "نام الزامی است",
|
||||
"noFee": "بدون هزینه",
|
||||
"notify_url": "آدرس اطلاعرسانی",
|
||||
"paymentManagement": "مدیریت پرداخت",
|
||||
"percentFee": "درصد",
|
||||
"platform": "پلتفرم",
|
||||
"searchPlaceholder": "عبارات جستجو را وارد کنید",
|
||||
"selectPlatform": "انتخاب پلتفرم",
|
||||
"selectType": "انتخاب نوع پرداخت",
|
||||
"submit": "ارسال",
|
||||
"type": "نوع",
|
||||
"updateSuccess": "با موفقیت بهروزرسانی شد"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "فقط برای نمایش استفاده میشود؛ تغییر این مورد بر تمام واحدهای ارزی در سیستم تأثیر خواهد گذاشت",
|
||||
"saveSuccess": "ذخیرهسازی موفقیتآمیز"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "ذخیره با موفقیت انجام شد",
|
||||
"title": "سیاست حفظ حریم خصوصی"
|
||||
},
|
||||
"site": {
|
||||
"customData": "دادههای سفارشی",
|
||||
"customDataDescription": "دادههای سفارشی، استفاده شده برای دادههای سفارشی سایت",
|
||||
"customHtml": "HTML سفارشی",
|
||||
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.",
|
||||
"keywords": "کلمات کلیدی",
|
||||
"keywordsDescription": "برای اهداف سئو استفاده میشود",
|
||||
"logo": "لوگو",
|
||||
"logoDescription": "برای نمایش لوگو در مکانهای تعیینشده استفاده میشود",
|
||||
"logoPlaceholder": "آدرس URL لوگو را وارد کنید، بدون '/' در انتها",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Peruuta",
|
||||
"confirm": "Vahvista",
|
||||
"confirmDelete": "Vahvista poisto",
|
||||
"create": "Luo ilmoitus",
|
||||
"createAds": "Luo mainos",
|
||||
"createSuccess": "Luonti onnistui",
|
||||
"delete": "Poista",
|
||||
"deleteSuccess": "Poisto onnistui",
|
||||
"deleteWarning": "Oletko varma, että haluat poistaa tämän ilmoituksen?",
|
||||
"disabled": "Pois käytöstä",
|
||||
"edit": "Muokkaa",
|
||||
"editAds": "Muokkaa mainosta",
|
||||
"enabled": "Käytössä",
|
||||
"form": {
|
||||
"cancel": "Peruuta",
|
||||
"confirm": "Vahvista",
|
||||
"content": "Sisältö",
|
||||
"description": "Kuvaus",
|
||||
"endTime": "Loppuaika",
|
||||
"enterContent": "Ole hyvä ja syötä mainoksen sisältö",
|
||||
"enterDescription": "Ole hyvä ja syötä mainoksen kuvaus",
|
||||
"enterEndTime": "Ole hyvä ja valitse loppuaika",
|
||||
"enterStartTime": "Ole hyvä ja valitse aloitusaika",
|
||||
"enterTargetUrl": "Ole hyvä ja syötä kohde-URL",
|
||||
"enterTitle": "Ole hyvä ja syötä mainoksen otsikko",
|
||||
"startTime": "Aloitusaika",
|
||||
"targetUrl": "Kohde-URL",
|
||||
"title": "Otsikko",
|
||||
"type": "Mainostyyppi",
|
||||
"typeImage": "Kuva",
|
||||
"typeVideo": "Video"
|
||||
},
|
||||
"status": "Tila",
|
||||
"targetUrl": "Kohde-URL",
|
||||
"title": "Otsikko",
|
||||
"type": "Tyyppi",
|
||||
"updateSuccess": "Päivitys onnistui",
|
||||
"validityPeriod": "Voimassaoloaika"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Anna kelvollinen sähköpostiosoite.",
|
||||
"existingAccount": "Onko sinulla jo tili?",
|
||||
"get": "Hanki",
|
||||
"invite": "Kutsukoodi",
|
||||
"invite": "Kutsukoodi (valinnainen)",
|
||||
"message": "#### Arvoisa käyttäjä,\n\nKiitos mielenkiinnostasi ja tuestasi. Sivuston toimintastrategian muutosten vuoksi olemme sulkeneet uusien käyttäjien rekisteröinnin. Tänä aikana nykyisten käyttäjien käyttö ei vaikutu millään tavalla.\n\nOlemme sitoutuneet tarjoamaan sinulle parempaa palvelua ja kokemusta, joten rekisteröinnin sulkemisen aikana suoritamme kattavan järjestelmän optimoinnin ja toiminnallisuuden päivityksen. Tulevaisuudessa toivotamme sinut tervetulleeksi entistä laadukkaamman sisällön ja palvelun kera.\n\nSeuraa verkkosivustoamme ja sosiaalisen median alustoja saadaksesi uusimmat päivitykset ja ilmoitukset. Kiitos ymmärryksestäsi ja tuestasi.\n\nJos sinulla on kysyttävää tai tarvitset apua, ota yhteyttä asiakaspalvelutiimiimme.\n\n**Kiitos vielä kerran tuestasi ja ymmärryksestäsi.**",
|
||||
"passwordMismatch": "Salasanat eivät täsmää",
|
||||
"success": "Rekisteröinti onnistui, olet kirjautunut automaattisesti!",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Estä virtuaalikone",
|
||||
"blockVirtualMachineDescription": "Kun tämä on käytössä, laitteiden käyttö virtuaalikoneissa tai emulaattoreissa estetään",
|
||||
"communicationKey": "Viestintäavain",
|
||||
"communicationKeyDescription": "Viestintäavain käytetään turvalliseen viestintään laitteiden ja palvelimien välillä",
|
||||
"enable": "Ota käyttöön",
|
||||
"enableDescription": "Kun otat käyttöön, useita laiteidentifikaattoreita, kuten IMEI/IDFA/IDFV/AndroidID/Mac-osoite, tuetaan kirjautumiseen ja rekisteröitymiseen."
|
||||
"enableDescription": "Kun otat käyttöön, useita laiteidentifikaattoreita, kuten IMEI/IDFA/IDFV/AndroidID/Mac-osoite, tuetaan kirjautumiseen ja rekisteröitymiseen.",
|
||||
"enableSecurity": "Ota käyttöön viestinnän salaus",
|
||||
"enableSecurityDescription": "Kun tämä on käytössä, laitteiden ja palvelimien välinen viestintä salataan",
|
||||
"saveFailed": "Tallennus epäonnistui",
|
||||
"saveSuccess": "Tallennus onnistui",
|
||||
"showAds": "Näytä mainoksia",
|
||||
"showAdsDescription": "Kun tämä on käytössä, mainoksia näytetään laitteilla"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Sähköpostin vahvistus",
|
||||
"emailVerificationDescription": "Kun tämä on käytössä, käyttäjien on vahvistettava sähköpostinsa",
|
||||
"enable": "Ota käyttöön",
|
||||
"enableDescription": "Kun otat tämän käyttöön, matkapuhelimen rekisteröinti-, kirjautumis-, sitomis- ja purkutoiminnot otetaan käyttöön",
|
||||
"enableDescription": "Kun tämä on otettu käyttöön, sähköpostirekisteröinti, kirjautuminen, liittäminen ja irrottaminen aktivoituvat.",
|
||||
"expiration_email_template": "Vanhentumisilmoituksen malli",
|
||||
"expiration_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.",
|
||||
"failed": "Epäonnistui",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "tšekki",
|
||||
"de-DE": "Saksa",
|
||||
"en-US": "englanti",
|
||||
"es-ES": "espanja",
|
||||
"es-MX": "espanja (Meksiko)",
|
||||
"fa-IR": "Persia",
|
||||
"fi-FI": "suomi",
|
||||
"fr-FR": "ranska",
|
||||
"hi-IN": "hindi",
|
||||
"hu-HU": "unkarinkieli",
|
||||
"ja-JP": "japani",
|
||||
"ko-KR": "Korea",
|
||||
"no-NO": "norja",
|
||||
"pl-PL": "puola",
|
||||
"pt-BR": "portugali (Brasilia)",
|
||||
"ro-RO": "romania",
|
||||
"ru-RU": "Venäjä",
|
||||
"th-TH": "thaikieli",
|
||||
"tr-TR": "turkki",
|
||||
"uk-UA": "ukrainaksi",
|
||||
"vi-VN": "vietnamiksi",
|
||||
"zh-CN": "kiina (yksinkertaistettu)",
|
||||
"zh-HK": "kiina (perinteinen)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "ADS-asetukset",
|
||||
"Announcement Management": "Ilmoitusten hallinta",
|
||||
"Apple": "Apple ID",
|
||||
"Auth Control": "Todennuksen hallinta",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Alipay-maksu",
|
||||
"alipayf2f": {
|
||||
"appId": "Alipay-sovelluksen ID",
|
||||
"invoiceName": "Mukautettu tuotteen nimi",
|
||||
"invoiceNameDescription": "Näkyy Alipay-laskussa",
|
||||
"privateKey": "Alipay-yksityinen avain",
|
||||
"publicKey": "Alipay-julkinen avain",
|
||||
"sandbox": "Hiekkalaatikkotila",
|
||||
"sandboxDescription": "Ota hiekkalaatikkotila käyttöön maksujen testaamiseksi"
|
||||
},
|
||||
"AlipayF2F": "Alipay Kasvokkain",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Hae maksua",
|
||||
"balance": "Saldo",
|
||||
"batchDelete": "Ryhmän poisto",
|
||||
"cancel": "Peruuta",
|
||||
"config": "Konfiguraatio",
|
||||
"configInfo": "Konfiguraatio tiedot",
|
||||
"configPlaceholder": "Täytä annettu {field} konfiguraatio",
|
||||
"confirm": "Vahvista",
|
||||
"confirmDelete": "Vahvista poisto",
|
||||
"copy": "Kopioi",
|
||||
"copySuccess": "Kopiointi onnistui",
|
||||
"create": "Lisää maksutapa",
|
||||
"createPayment": "Lisää maksutapa",
|
||||
"createSuccess": "Luonti onnistui",
|
||||
"delete": "Poista",
|
||||
"deleteSuccess": "Poisto onnistui",
|
||||
"deleteWarning": "Oletko varma, että haluat poistaa tämän maksutavan? Tätä toimintoa ei voi peruuttaa.",
|
||||
"description": "Kuvaus",
|
||||
"descriptionPlaceholder": "Syötä maksutavan kuvaus",
|
||||
"domain": "Verkkotunnus",
|
||||
"domainPlaceholder": "valinnainen, esim. {esimerkki}",
|
||||
"edit": "Muokkaa",
|
||||
"editPayment": "Muokkaa maksutapaa",
|
||||
"enable": "Ota käyttöön",
|
||||
"enableDescription": "Ota tämä maksutapa käyttöön",
|
||||
"epay": {
|
||||
"key": "AVAIN",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Kiinteä summa",
|
||||
"feeMode": "Maksutapa",
|
||||
"feeModeDescription": "Lisämaksun veloitustapa",
|
||||
"feeModeItems": ["Ei käsittelymaksua", "Prosenttiosuus", "Kiinteä summa", "Molemmat yllä olevat"],
|
||||
"feePercent": "Prosenttiosuusmaksu (valinnainen)",
|
||||
"feePercentDescription": "Lisätään maksu tilauksen summan päälle",
|
||||
"fixedFee": "Kiinteä maksu (valinnainen)",
|
||||
"fixedFeeDescription": "Lisää kiinteä maksu tilauksen summan päälle",
|
||||
"iconUrl": "Kuvakkeen URL (valinnainen)",
|
||||
"iconUrlDescription": "Käytetään etunäytössä",
|
||||
"inputPlaceholder": "Kirjoita tähän",
|
||||
"key": "AVAIN",
|
||||
"notifyUrl": "Mukautettu ilmoitusalue (valinnainen)",
|
||||
"notifyUrlDescription": "Yhdyskäytävän ilmoitus lähetetään tähän verkkotunnukseen",
|
||||
"payForRecommendations": "Maksa suosituksista",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Tallennus onnistui",
|
||||
"showName": "Näytä nimi",
|
||||
"showNameDescription": "Näytetään käyttöliittymässä",
|
||||
"stripe": {
|
||||
"publicKey": "JULKINEN AVAIN",
|
||||
"secretKey": "SALAINEN AVAIN",
|
||||
"webhookSecret": "WEBHOOK-SALAINEN ALLEKIRJOITUS"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "WeChat-maksu"
|
||||
"handlingFee": "Käsittelymaksu",
|
||||
"icon": "Ikoni",
|
||||
"iconPlaceholder": "Syötä ikonin URL-osoite",
|
||||
"mark": "Tunniste",
|
||||
"markPlaceholder": "Syötä maksutavan tunniste",
|
||||
"name": "Nimi",
|
||||
"namePlaceholder": "Syötä maksutavan nimi",
|
||||
"nameRequired": "Nimi on pakollinen",
|
||||
"noFee": "Ei maksua",
|
||||
"notify_url": "Ilmoitus-URL",
|
||||
"paymentManagement": "Maksujen hallinta",
|
||||
"percentFee": "Prosentti",
|
||||
"platform": "Alusta",
|
||||
"searchPlaceholder": "Syötä hakusanat",
|
||||
"selectPlatform": "Valitse alusta",
|
||||
"selectType": "Valitse maksutyyppi",
|
||||
"submit": "Lähetä",
|
||||
"type": "Tyyppi",
|
||||
"updateSuccess": "Päivitys onnistui"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"currencyUnitDescription": "Käytetään vain näyttötarkoituksiin, muutoksen jälkeen kaikki järjestelmän valuuttayksiköt muuttuvat",
|
||||
"saveSuccess": "Tallennus onnistui"
|
||||
},
|
||||
"privacy-policy": {
|
||||
"saveSuccess": "Tallennus onnistui",
|
||||
"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",
|
||||
"keywordsDescription": "Käytetään SEO-tarkoituksiin",
|
||||
"logo": "LOGO",
|
||||
"logoDescription": "Näyttääksesi paikan, jossa LOGO tulee näkyä",
|
||||
"logoPlaceholder": "Syötä LOGO:n URL-osoite, älä lopeta '/'-merkillä",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"confirmDelete": "Confirmer la suppression",
|
||||
"create": "Créer une annonce",
|
||||
"createAds": "Créer une publicité",
|
||||
"createSuccess": "Créé avec succès",
|
||||
"delete": "Supprimer",
|
||||
"deleteSuccess": "Supprimé avec succès",
|
||||
"deleteWarning": "Êtes-vous sûr de vouloir supprimer cette annonce ?",
|
||||
"disabled": "Désactivé",
|
||||
"edit": "Modifier",
|
||||
"editAds": "Modifier la publicité",
|
||||
"enabled": "Activé",
|
||||
"form": {
|
||||
"cancel": "Annuler",
|
||||
"confirm": "Confirmer",
|
||||
"content": "Contenu",
|
||||
"description": "Description",
|
||||
"endTime": "Heure de fin",
|
||||
"enterContent": "Veuillez entrer le contenu de l'annonce",
|
||||
"enterDescription": "Veuillez entrer la description de l'annonce",
|
||||
"enterEndTime": "Veuillez sélectionner l'heure de fin",
|
||||
"enterStartTime": "Veuillez sélectionner l'heure de début",
|
||||
"enterTargetUrl": "Veuillez entrer l'URL cible",
|
||||
"enterTitle": "Veuillez entrer le titre de l'annonce",
|
||||
"startTime": "Heure de début",
|
||||
"targetUrl": "URL cible",
|
||||
"title": "Titre",
|
||||
"type": "Type d'annonce",
|
||||
"typeImage": "Image",
|
||||
"typeVideo": "Vidéo"
|
||||
},
|
||||
"status": "Statut",
|
||||
"targetUrl": "URL cible",
|
||||
"title": "Titre",
|
||||
"type": "Type",
|
||||
"updateSuccess": "Mis à jour avec succès",
|
||||
"validityPeriod": "Période de validité"
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Veuillez entrer une adresse e-mail valide.",
|
||||
"existingAccount": "Vous avez déjà un compte ?",
|
||||
"get": "Obtenir",
|
||||
"invite": "Code d'invitation",
|
||||
"invite": "Code d'invitation (facultatif)",
|
||||
"message": "#### Cher utilisateur,\n\nMerci pour votre intérêt et votre soutien. En raison d'un ajustement de notre stratégie opérationnelle, nous avons fermé la fonction d'inscription pour les nouveaux utilisateurs. Pendant cette période, l'utilisation par les utilisateurs existants ne sera pas affectée.\n\nNous nous engageons à vous offrir un meilleur service et une meilleure expérience, c'est pourquoi nous procéderons à une optimisation complète du système et à une mise à niveau des fonctionnalités pendant la fermeture des inscriptions. À l'avenir, nous vous accueillerons avec un contenu et des services de meilleure qualité.\n\nVeuillez suivre notre site web et nos plateformes de médias sociaux pour obtenir les dernières nouvelles et notifications. Merci pour votre compréhension et votre soutien.\n\nSi vous avez des questions ou avez besoin d'aide, n'hésitez pas à contacter notre équipe de service client.\n\n**Merci encore pour votre soutien et votre compréhension.**",
|
||||
"passwordMismatch": "Les mots de passe saisis ne correspondent pas",
|
||||
"success": "Inscription réussie, vous êtes maintenant connecté automatiquement !",
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"blockVirtualMachine": "Bloquer la machine virtuelle",
|
||||
"blockVirtualMachineDescription": "Lorsqu'il est activé, les appareils seront empêchés de fonctionner sur des machines virtuelles ou des émulateurs",
|
||||
"communicationKey": "Clé de communication",
|
||||
"communicationKeyDescription": "La clé de communication est utilisée pour une communication sécurisée entre les appareils et les serveurs",
|
||||
"enable": "Activer",
|
||||
"enableDescription": "Après activation, plusieurs identifiants de périphériques tels que IMEI/IDFA/IDFV/AndroidID/adresse Mac sont pris en charge pour la connexion et l'enregistrement"
|
||||
"enableDescription": "Après activation, plusieurs identifiants de périphériques tels que IMEI/IDFA/IDFV/AndroidID/adresse Mac sont pris en charge pour la connexion et l'enregistrement",
|
||||
"enableSecurity": "Activer le chiffrement de la communication",
|
||||
"enableSecurityDescription": "Lorsqu'il est activé, la communication entre les appareils et les serveurs sera chiffrée",
|
||||
"saveFailed": "Échec de l'enregistrement",
|
||||
"saveSuccess": "Enregistrement réussi",
|
||||
"showAds": "Afficher les publicités",
|
||||
"showAdsDescription": "Lorsqu'il est activé, des publicités seront affichées sur les appareils"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"emailVerification": "Vérification de l'email",
|
||||
"emailVerificationDescription": "Lorsqu'elle est activée, les utilisateurs devront vérifier leur adresse e-mail",
|
||||
"enable": "Activer",
|
||||
"enableDescription": "Après activation, les fonctions d'enregistrement, de connexion, de liaison et de déliaison par téléphone mobile seront activées",
|
||||
"enableDescription": "Après activation, les fonctions d'enregistrement par e-mail, de connexion, de liaison et de délégation seront activées",
|
||||
"expiration_email_template": "Modèle d'avis d'expiration",
|
||||
"expiration_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.",
|
||||
"failed": "Échoué",
|
||||
@@ -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",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"cs-CZ": "Tchèque",
|
||||
"de-DE": "Allemand",
|
||||
"en-US": "anglais",
|
||||
"es-ES": "Espagnol",
|
||||
"es-MX": "Espagnol (Mexique)",
|
||||
"fa-IR": "Persan",
|
||||
"fi-FI": "finnois",
|
||||
"fr-FR": "français",
|
||||
"hi-IN": "hindi",
|
||||
"hu-HU": "Hongrois",
|
||||
"ja-JP": "Japonais",
|
||||
"ko-KR": "coréen",
|
||||
"no-NO": "Norvégien",
|
||||
"pl-PL": "Polonais",
|
||||
"pt-BR": "portugais (Brésil)",
|
||||
"ro-RO": "Roumain",
|
||||
"ru-RU": "Russe",
|
||||
"th-TH": "thaïlandais",
|
||||
"tr-TR": "turc",
|
||||
"uk-UA": "Ukrainien",
|
||||
"vi-VN": "Vietnamien",
|
||||
"zh-CN": "français (France)",
|
||||
"zh-HK": "chinois (traditionnel)"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"ADS Config": "Configuration ADS",
|
||||
"Announcement Management": "Gestion des annonces",
|
||||
"Apple": "Identifiant Apple",
|
||||
"Auth Control": "Contrôle d'authentification",
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
{
|
||||
"aliPay": "Paiement par Alipay",
|
||||
"alipayf2f": {
|
||||
"appId": "ID de l'application Alipay",
|
||||
"invoiceName": "Nom personnalisé du produit",
|
||||
"invoiceNameDescription": "S'affichera sur la facture Alipay",
|
||||
"privateKey": "Clé privée Alipay",
|
||||
"publicKey": "Clé publique Alipay",
|
||||
"sandbox": "Mode bac à sable",
|
||||
"sandboxDescription": "Activez le mode bac à sable pour tester le paiement"
|
||||
},
|
||||
"AlipayF2F": "Alipay Face à Face",
|
||||
"EPay": "EPay",
|
||||
"Stripe": "Stripe",
|
||||
"applyForPayment": "Demander un paiement",
|
||||
"balance": "Solde",
|
||||
"batchDelete": "Suppression par lot",
|
||||
"cancel": "Annuler",
|
||||
"config": "Configuration",
|
||||
"configInfo": "Informations de configuration",
|
||||
"configPlaceholder": "Veuillez remplir la configuration {field} fournie",
|
||||
"confirm": "Confirmer",
|
||||
"confirmDelete": "Confirmer la suppression",
|
||||
"copy": "Copier",
|
||||
"copySuccess": "Copié avec succès",
|
||||
"create": "Ajouter un mode de paiement",
|
||||
"createPayment": "Ajouter un mode de paiement",
|
||||
"createSuccess": "Créé avec succès",
|
||||
"delete": "Supprimer",
|
||||
"deleteSuccess": "Supprimé avec succès",
|
||||
"deleteWarning": "Êtes-vous sûr de vouloir supprimer ce mode de paiement ? Cette action ne peut pas être annulée.",
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Entrez la description du mode de paiement",
|
||||
"domain": "Domaine",
|
||||
"domainPlaceholder": "facultatif, par exemple {exemple}",
|
||||
"edit": "Modifier",
|
||||
"editPayment": "Modifier le mode de paiement",
|
||||
"enable": "Activer",
|
||||
"enableDescription": "Activer ce mode de paiement",
|
||||
"epay": {
|
||||
"key": "CLÉ",
|
||||
"pid": "PID",
|
||||
"url": "URL"
|
||||
},
|
||||
"feeAmount": "Montant fixe",
|
||||
"feeMode": "Mode de facturation",
|
||||
"feeModeDescription": "Mode de facturation des frais supplémentaires",
|
||||
"feeModeItems": ["Pas de frais", "Pourcentage", "Montant fixe", "Les deux ci-dessus"],
|
||||
"feePercent": "Pourcentage de frais (optionnel)",
|
||||
"feePercentDescription": "Ajouter des frais au montant de la commande",
|
||||
"fixedFee": "Frais fixes (optionnel)",
|
||||
"fixedFeeDescription": "Ajouter des frais fixes au montant de la commande",
|
||||
"iconUrl": "URL de l'icône (facultatif)",
|
||||
"iconUrlDescription": "Utilisé pour l'affichage frontal",
|
||||
"inputPlaceholder": "Veuillez entrer",
|
||||
"key": "CLÉ",
|
||||
"notifyUrl": "Domaine de notification personnalisé (facultatif)",
|
||||
"notifyUrlDescription": "Les notifications de la passerelle seront envoyées à ce domaine",
|
||||
"payForRecommendations": "Payer pour les recommandations",
|
||||
"pid": "PID",
|
||||
"saveSuccess": "Enregistrement réussi",
|
||||
"showName": "Afficher le nom",
|
||||
"showNameDescription": "Utilisé pour l'affichage frontal",
|
||||
"stripe": {
|
||||
"publicKey": "CLÉ PUBLIQUE",
|
||||
"secretKey": "CLÉ SECRÈTE",
|
||||
"webhookSecret": "SIGNATURE DE CLÉ WEBHOOK"
|
||||
},
|
||||
"url": "URL",
|
||||
"wechatPay": "Paiement WeChat"
|
||||
"handlingFee": "Frais de traitement",
|
||||
"icon": "Icône",
|
||||
"iconPlaceholder": "Entrez l'URL de l'icône",
|
||||
"mark": "Identifiant",
|
||||
"markPlaceholder": "Entrez l'identifiant du mode de paiement",
|
||||
"name": "Nom",
|
||||
"namePlaceholder": "Entrez le nom du mode de paiement",
|
||||
"nameRequired": "Le nom est requis",
|
||||
"noFee": "Pas de frais",
|
||||
"notify_url": "URL de notification",
|
||||
"paymentManagement": "Gestion des paiements",
|
||||
"percentFee": "Pourcentage",
|
||||
"platform": "Plateforme",
|
||||
"searchPlaceholder": "Entrez des termes de recherche",
|
||||
"selectPlatform": "Sélectionner la plateforme",
|
||||
"selectType": "Sélectionner le type de paiement",
|
||||
"submit": "Soumettre",
|
||||
"type": "Type",
|
||||
"updateSuccess": "Mis à jour avec succès"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user