Compare commits

..

110 Commits

Author SHA1 Message Date
semantic-release-bot 7f81de31ab 🔖 chore(release): v1.1.0 [skip ci]
# [1.1.0](https://github.com/perfect-panel/ppanel-web/compare/v1.0.2...v1.1.0) (2025-07-06)

###  Features

* **view**: Add AnyTLS protocol support and enhance node configuration options ([bcfb10a](https://github.com/perfect-panel/ppanel-web/commit/bcfb10a))
2025-07-06 11:59:27 +00:00
web bcfb10a6c7 feat(view): Add AnyTLS protocol support and enhance node configuration options 2025-07-06 04:56:00 -07:00
semantic-release-bot 9235fef3c6 🔖 chore(release): v1.0.2 [skip ci]
## [1.0.2](https://github.com/perfect-panel/ppanel-web/compare/v1.0.1...v1.0.2) (2025-06-29)

### 🐛 Bug Fixes

* **subscription**: User subscription information ([7d18ff6](https://github.com/perfect-panel/ppanel-web/commit/7d18ff6))
2025-06-29 09:19:10 +00:00
web@ppanel ee0c3371f7 Merge branch 'main' into develop 2025-06-29 08:39:58 +00:00
web@ppanel 7d18ff6825 🐛 fix(subscription): User subscription information 2025-06-29 08:33:43 +00:00
semantic-release-bot c86d28f798 🔖 chore(release): v1.0.1 [skip ci]
## [1.0.1](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0...v1.0.1) (2025-04-28)

### 🐛 Bug Fixes

* **payment**: Disable webhook_secret field in PaymentForm component ([d323af8](https://github.com/perfect-panel/ppanel-web/commit/d323af8))
* **recharge**: Set balance prop to false in PaymentMethods component ([356ae5b](https://github.com/perfect-panel/ppanel-web/commit/356ae5b))
2025-04-28 11:54:50 +00:00
web@ppanel d323af8bb6 🐛 fix(payment): Disable webhook_secret field in PaymentForm component 2025-04-28 11:51:41 +00:00
web@ppanel 356ae5b777 🐛 fix(recharge): Set balance prop to false in PaymentMethods component 2025-04-28 11:30:06 +00:00
semantic-release-bot 50bdd2cd21 🔖 chore(release): v1.0.0 [skip ci]
# 1.0.0 (2025-04-24)

### ♻ Code Refactoring

* **api**: Sort and Announcement ([38d5616](https://github.com/perfect-panel/ppanel-web/commit/38d5616))
* **auth**: Refactor user authorization handling and improve error logging ([68bc18f](https://github.com/perfect-panel/ppanel-web/commit/68bc18f))
* **config**: GenerateMetadata ([a0bb101](https://github.com/perfect-panel/ppanel-web/commit/a0bb101))
* **config**: Simplify environment variable handling and improve build script ([cf54d0f](https://github.com/perfect-panel/ppanel-web/commit/cf54d0f))
* **config**: Viewport ([24b8601](https://github.com/perfect-panel/ppanel-web/commit/24b8601))
* **core**: Restructure project for better module separation ([9d0cb8b](https://github.com/perfect-panel/ppanel-web/commit/9d0cb8b))
* **deps**: Update ([19837a1](https://github.com/perfect-panel/ppanel-web/commit/19837a1))
* **empty**: Content ([aa4c667](https://github.com/perfect-panel/ppanel-web/commit/aa4c667))
* **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
* **sbscribe**: Rename and reorganize components for better structure and clarity ([5e5e4ed](https://github.com/perfect-panel/ppanel-web/commit/5e5e4ed))
* **ui**: Dependencies ([727d779](https://github.com/perfect-panel/ppanel-web/commit/727d779))
* **ui**: Layout ([9262d7d](https://github.com/perfect-panel/ppanel-web/commit/9262d7d))
* **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
* Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
* Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))

###  Performance Improvements

* **subscribe**: Form discount price ([059a892](https://github.com/perfect-panel/ppanel-web/commit/059a892))

###  Features

* **accounts**: Update third-party account binding and unbinding ([1841552](https://github.com/perfect-panel/ppanel-web/commit/1841552))
* **ad**: Advertise ([b1105cd](https://github.com/perfect-panel/ppanel-web/commit/b1105cd))
* **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
* **affiliate**: Add Affiliate component with commission display and invite link functionality ([4aea4e8](https://github.com/perfect-panel/ppanel-web/commit/4aea4e8))
* **affiliate**: Affiliate Detail ([a782c17](https://github.com/perfect-panel/ppanel-web/commit/a782c17))
* **affiliate**: Commission Rate ([5eec430](https://github.com/perfect-panel/ppanel-web/commit/5eec430))
* **affiliate**: Update affiliate component to display total commission and improve data fetching ([cc834ca](https://github.com/perfect-panel/ppanel-web/commit/cc834ca))
* **announcement**: Popup and pinned ([f3680a7](https://github.com/perfect-panel/ppanel-web/commit/f3680a7))
* **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
* **api**: Add new subscription properties and locale support for deduction ratios and reset cycles ([fec80f5](https://github.com/perfect-panel/ppanel-web/commit/fec80f5))
* **api**: Add Time Period Configuration ([837157c](https://github.com/perfect-panel/ppanel-web/commit/837157c))
* **api**: Telegram ([17ce96a](https://github.com/perfect-panel/ppanel-web/commit/17ce96a))
* **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 email and SMS code sending functionality with localization updates ([57eaa55](https://github.com/perfect-panel/ppanel-web/commit/57eaa55))
* **auth**: Add Oauth configuration for Telegram, Facebook, Google, Github, and Apple ([18ee600](https://github.com/perfect-panel/ppanel-web/commit/18ee600))
* **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
* **auth**: Add SMS and email configuration options to global store and update localization ([4acf7b1](https://github.com/perfect-panel/ppanel-web/commit/4acf7b1))
* **auth**: Add type parameter to SendCode and update related API typings ([4198871](https://github.com/perfect-panel/ppanel-web/commit/4198871))
* **auth**: Enhance user registration with invite handling and logo display ([207bc24](https://github.com/perfect-panel/ppanel-web/commit/207bc24))
* **auth**: Redirect user after OAuth login and add logos icon collection ([aa6dda8](https://github.com/perfect-panel/ppanel-web/commit/aa6dda8))
* **auth**: Refactor mobile authentication config to support whitelist functionality ([c761ec7](https://github.com/perfect-panel/ppanel-web/commit/c761ec7))
* **billing**: Update Billing ([078fc9d](https://github.com/perfect-panel/ppanel-web/commit/078fc9d))
* **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
* **config**: Add application selection and encryption settings to configuration form ([88b3504](https://github.com/perfect-panel/ppanel-web/commit/88b3504))
* **config**: FormatBytes ([9251a09](https://github.com/perfect-panel/ppanel-web/commit/9251a09))
* **config**: Protocol type ([a3b45b4](https://github.com/perfect-panel/ppanel-web/commit/a3b45b4))
* **config**: Update encryption fields in configuration form and refactor OAuth callback parameters ([652e032](https://github.com/perfect-panel/ppanel-web/commit/652e032))
* **config**: Webhook Domain ([01e06c6](https://github.com/perfect-panel/ppanel-web/commit/01e06c6))
* **dashboard**: Optimization ([5b3f4b4](https://github.com/perfect-panel/ppanel-web/commit/5b3f4b4))
* **dashboard**: Statistics ([2926abc](https://github.com/perfect-panel/ppanel-web/commit/2926abc))
* **device**: Modify IMEI to device identifier support ([e3f9ef6](https://github.com/perfect-panel/ppanel-web/commit/e3f9ef6))
* **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
* **favicon**: Update SVG favicon design for admin and user interfaces ([1d91738](https://github.com/perfect-panel/ppanel-web/commit/1d91738))
* **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
* **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))
* **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
* **global**: Add SMS configuration options to global store ([39a9ce6](https://github.com/perfect-panel/ppanel-web/commit/39a9ce6))
* **header**: Update locales ([bfb6c27](https://github.com/perfect-panel/ppanel-web/commit/bfb6c27))
* **imei**: Add IMEI related internationalization support and menu items ([13c3337](https://github.com/perfect-panel/ppanel-web/commit/13c3337))
* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
* **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
* **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
* **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
* **locale**: Add Persian ([93a0a88](https://github.com/perfect-panel/ppanel-web/commit/93a0a88))
* **locales**: Add area code and telephone fields to user forms in multiple languages ([9b8258c](https://github.com/perfect-panel/ppanel-web/commit/9b8258c))
* **locales**: Add description information of communication keys and encryption methods to enhance client configuration capabilities ([d1f5a9b](https://github.com/perfect-panel/ppanel-web/commit/d1f5a9b))
* **locales**: Add kick offline confirmation and success messages in multiple languages ([5db5343](https://github.com/perfect-panel/ppanel-web/commit/5db5343))
* **locales**: Add multiple languages ([b243ab9](https://github.com/perfect-panel/ppanel-web/commit/b243ab9))
* **locales**: Replace 'nodeGroupId' with 'groupId' in multiple language files for consistency ([a4e9d5d](https://github.com/perfect-panel/ppanel-web/commit/a4e9d5d))
* **locales**: Update 'deductBalance' to 'giftAmount' across multiple languages and fix newline in announcement.json ([70497af](https://github.com/perfect-panel/ppanel-web/commit/70497af))
* **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))
* **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
* **node-subscription**: Add copy functionality for columns ([3a81e37](https://github.com/perfect-panel/ppanel-web/commit/3a81e37))
* **node**: Add NodeStatus ([c712624](https://github.com/perfect-panel/ppanel-web/commit/c712624))
* **node**: Add protocol ([301b635](https://github.com/perfect-panel/ppanel-web/commit/301b635))
* **node**: Add serverKey ([25ce37e](https://github.com/perfect-panel/ppanel-web/commit/25ce37e))
* **node**: Add status ([c06372b](https://github.com/perfect-panel/ppanel-web/commit/c06372b))
* **node**: Add tags ([f408fdf](https://github.com/perfect-panel/ppanel-web/commit/f408fdf))
* **node**: Move the node configuration to the server module ([7f0f5ce](https://github.com/perfect-panel/ppanel-web/commit/7f0f5ce))
* **oauth**: Add certification component for handling OAuth login callbacks and improve user authentication flow ([5ed04c0](https://github.com/perfect-panel/ppanel-web/commit/5ed04c0))
* **oauth**: Implement OAuth token retrieval and refactor login callback handling ([40a6f7c](https://github.com/perfect-panel/ppanel-web/commit/40a6f7c))
* **oauth**: Refactor platform parameter handling and improve logout redirection logic ([8346c85](https://github.com/perfect-panel/ppanel-web/commit/8346c85))
* **oauth**: Update OAuth login handling to use callback parameter and improve URL parameter retrieval ([9227411](https://github.com/perfect-panel/ppanel-web/commit/9227411))
* **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
* **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
* **platform**: Update platform naming and add keywords and custom HTML fields ([6384237](https://github.com/perfect-panel/ppanel-web/commit/6384237))
* **privacy-policy**: Add privacy policy related text and links ([baa68f0](https://github.com/perfect-panel/ppanel-web/commit/baa68f0))
* **profile**:  Update localization strings and enhance third-party account binding ([2d1effb](https://github.com/perfect-panel/ppanel-web/commit/2d1effb))
* **relay**: Add relay mode configuration and update related schemas ([3cc9477](https://github.com/perfect-panel/ppanel-web/commit/3cc9477))
* **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))
* **schema**: Add security field to hysteria2 and tuic schemas ([cd59d44](https://github.com/perfect-panel/ppanel-web/commit/cd59d44))
* **site**: Added localization support for custom HTML and keyword fields ([f9d7736](https://github.com/perfect-panel/ppanel-web/commit/f9d7736))
* **sms**: Update locales ([938363b](https://github.com/perfect-panel/ppanel-web/commit/938363b))
* **stats**: Replace dynamic stat fetching with environment constants for user, server, and location counts ([46ae166](https://github.com/perfect-panel/ppanel-web/commit/46ae166))
* **subscribe**: Add 'sold' column to SubscribeTable and update inventory terminology ([19619fd](https://github.com/perfect-panel/ppanel-web/commit/19619fd))
* **subscribe**: Add reset_time to API typings and update unsubscribe logic ([eeea165](https://github.com/perfect-panel/ppanel-web/commit/eeea165))
* **subscribe**: Add subscribe_discount type ([f99c604](https://github.com/perfect-panel/ppanel-web/commit/f99c604))
* **subscribe**: Add subscription credits ([5bc7905](https://github.com/perfect-panel/ppanel-web/commit/5bc7905))
* **subscribe**: Add unit time ([39d07ec](https://github.com/perfect-panel/ppanel-web/commit/39d07ec))
* **subscribe**: Add unsubscribe functionality with confirmation messages and localized strings ([b2a2f42](https://github.com/perfect-panel/ppanel-web/commit/b2a2f42))
* **subscribe**: Improve error handling in subscription forms and update component props ([d28a10b](https://github.com/perfect-panel/ppanel-web/commit/d28a10b))
* **subscribe**: Improve layout and styling in subscription components ([5766376](https://github.com/perfect-panel/ppanel-web/commit/5766376))
* **subscribe**: Move subscription configuration and application to subscription module ([f90d4d2](https://github.com/perfect-panel/ppanel-web/commit/f90d4d2))
* **subscribe**: Update SubscribeTable component to use API.SubscribeItem type and ensure proper type casting ([f26f1c2](https://github.com/perfect-panel/ppanel-web/commit/f26f1c2))
* **subscribe**: Update suffix from 'MB' to 'Mbps' and enhance speed limit display logic ([3547bb1](https://github.com/perfect-panel/ppanel-web/commit/3547bb1))
* **subscription**:  Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
* **subscription**: Add delete user subscription functionality ([1fc3a10](https://github.com/perfect-panel/ppanel-web/commit/1fc3a10))
* **subscription**: Add localized messages for existing subscriptions and deletion restrictions ([e8a72d5](https://github.com/perfect-panel/ppanel-web/commit/e8a72d5))
* **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
* **table**: Add sorting support for Node and subscription columns ([27924b0](https://github.com/perfect-panel/ppanel-web/commit/27924b0))
* **table**: Supports drag and drop sorting ([2f56ef5](https://github.com/perfect-panel/ppanel-web/commit/2f56ef5))
* **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))
* **tos**: Display data ([6024454](https://github.com/perfect-panel/ppanel-web/commit/6024454))
* **tutorial**: Add common tutorial list ([872252c](https://github.com/perfect-panel/ppanel-web/commit/872252c))
* **tutorial**: Fetch the latest tutorial version from GitHub API for dynamic URL generation ([28f8c78](https://github.com/perfect-panel/ppanel-web/commit/28f8c78))
* **ui**: System Tool ([1836980](https://github.com/perfect-panel/ppanel-web/commit/1836980))
* **ui**: Update homepage data ([8425b13](https://github.com/perfect-panel/ppanel-web/commit/8425b13))
* **ui**: Update input components and enhance card minimum width for better layout ([8a02310](https://github.com/perfect-panel/ppanel-web/commit/8a02310))
* **user**: Add 'gift_amount' field and update related references in user services and components ([b13c77e](https://github.com/perfect-panel/ppanel-web/commit/b13c77e))
* **user**: Add telephone input with area code selection and update localization ([585b99c](https://github.com/perfect-panel/ppanel-web/commit/585b99c))
* **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))
* **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
* Update Auth Control ([c59742a](https://github.com/perfect-panel/ppanel-web/commit/c59742a))

### 🎫 Chores

* **config**: Entry locale ([5737331](https://github.com/perfect-panel/ppanel-web/commit/5737331))
* **deps**: Update package dependencies across multiple projects for improved stability and performance ([b01a5bc](https://github.com/perfect-panel/ppanel-web/commit/b01a5bc))
* **init**: Project initialization ([829edfa](https://github.com/perfect-panel/ppanel-web/commit/829edfa))
* **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
* **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
* **release**: V1.0.0-beta.1 [skip ci] ([7284d1c](https://github.com/perfect-panel/ppanel-web/commit/7284d1c))
* **release**: V1.0.0-beta.10 [skip ci] ([5cf573a](https://github.com/perfect-panel/ppanel-web/commit/5cf573a))
* **release**: V1.0.0-beta.11 [skip ci] ([1f29506](https://github.com/perfect-panel/ppanel-web/commit/1f29506))
* **release**: V1.0.0-beta.12 [skip ci] ([4418c47](https://github.com/perfect-panel/ppanel-web/commit/4418c47))
* **release**: V1.0.0-beta.13 [skip ci] ([23c974a](https://github.com/perfect-panel/ppanel-web/commit/23c974a))
* **release**: V1.0.0-beta.14 [skip ci] ([0fb0d8b](https://github.com/perfect-panel/ppanel-web/commit/0fb0d8b))
* **release**: V1.0.0-beta.15 [skip ci] ([b2e8fad](https://github.com/perfect-panel/ppanel-web/commit/b2e8fad))
* **release**: V1.0.0-beta.16 [skip ci] ([c3eff0a](https://github.com/perfect-panel/ppanel-web/commit/c3eff0a))
* **release**: V1.0.0-beta.17 [skip ci] ([5b64389](https://github.com/perfect-panel/ppanel-web/commit/5b64389))
* **release**: V1.0.0-beta.18 [skip ci] ([4a00233](https://github.com/perfect-panel/ppanel-web/commit/4a00233))
* **release**: V1.0.0-beta.19 [skip ci] ([0f15fb8](https://github.com/perfect-panel/ppanel-web/commit/0f15fb8))
* **release**: V1.0.0-beta.2 [skip ci] ([087c36c](https://github.com/perfect-panel/ppanel-web/commit/087c36c))
* **release**: V1.0.0-beta.20 [skip ci] ([bbd44f0](https://github.com/perfect-panel/ppanel-web/commit/bbd44f0))
* **release**: V1.0.0-beta.21 [skip ci] ([ca642c2](https://github.com/perfect-panel/ppanel-web/commit/ca642c2))
* **release**: V1.0.0-beta.22 [skip ci] ([c0fb34f](https://github.com/perfect-panel/ppanel-web/commit/c0fb34f))
* **release**: V1.0.0-beta.23 [skip ci] ([cf1d66d](https://github.com/perfect-panel/ppanel-web/commit/cf1d66d))
* **release**: V1.0.0-beta.24 [skip ci] ([01a3aa0](https://github.com/perfect-panel/ppanel-web/commit/01a3aa0))
* **release**: V1.0.0-beta.25 [skip ci] ([047a698](https://github.com/perfect-panel/ppanel-web/commit/047a698))
* **release**: V1.0.0-beta.26 [skip ci] ([79edea7](https://github.com/perfect-panel/ppanel-web/commit/79edea7))
* **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
* **release**: V1.0.0-beta.27 [skip ci] ([85fdc36](https://github.com/perfect-panel/ppanel-web/commit/85fdc36))
* **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
* **release**: V1.0.0-beta.28 [skip ci] ([d10ecc9](https://github.com/perfect-panel/ppanel-web/commit/d10ecc9))
* **release**: V1.0.0-beta.29 [skip ci] ([29bc3c7](https://github.com/perfect-panel/ppanel-web/commit/29bc3c7))
* **release**: V1.0.0-beta.3 [skip ci] ([cd49427](https://github.com/perfect-panel/ppanel-web/commit/cd49427))
* **release**: V1.0.0-beta.30 [skip ci] ([db0d9e0](https://github.com/perfect-panel/ppanel-web/commit/db0d9e0))
* **release**: V1.0.0-beta.31 [skip ci] ([aa1d426](https://github.com/perfect-panel/ppanel-web/commit/aa1d426))
* **release**: V1.0.0-beta.32 [skip ci] ([fa56eb8](https://github.com/perfect-panel/ppanel-web/commit/fa56eb8))
* **release**: V1.0.0-beta.33 [skip ci] ([383638f](https://github.com/perfect-panel/ppanel-web/commit/383638f))
* **release**: V1.0.0-beta.34 [skip ci] ([7023875](https://github.com/perfect-panel/ppanel-web/commit/7023875))
* **release**: V1.0.0-beta.4 [skip ci] ([10d322f](https://github.com/perfect-panel/ppanel-web/commit/10d322f))
* **release**: V1.0.0-beta.5 [skip ci] ([f275f01](https://github.com/perfect-panel/ppanel-web/commit/f275f01))
* **release**: V1.0.0-beta.6 [skip ci] ([dd23279](https://github.com/perfect-panel/ppanel-web/commit/dd23279))
* **release**: V1.0.0-beta.7 [skip ci] ([f60d40c](https://github.com/perfect-panel/ppanel-web/commit/f60d40c))
* **release**: V1.0.0-beta.8 [skip ci], closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([a593eac](https://github.com/perfect-panel/ppanel-web/commit/a593eac))
* **release**: V1.0.0-beta.9 [skip ci] ([855d1b0](https://github.com/perfect-panel/ppanel-web/commit/855d1b0))
* **ui**: Update package dependencies for improved stability and performance ([25da429](https://github.com/perfect-panel/ppanel-web/commit/25da429))
* Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))
* Update changelog, enhance prepare script, and add openapi command ([a93db4e](https://github.com/perfect-panel/ppanel-web/commit/a93db4e))

### 🐛 Bug Fixes

* **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
* **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
* **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
* **api**: Fix type error in API request and add return URL parameter ([ee286dd](https://github.com/perfect-panel/ppanel-web/commit/ee286dd))
* **api**: PreCreateOrder ([ca747f5](https://github.com/perfect-panel/ppanel-web/commit/ca747f5))
* **api**: Purge ([98c1c30](https://github.com/perfect-panel/ppanel-web/commit/98c1c30))
* **api**: Remove redundant requestType parameter in appleLoginCallback ([0aa5d5b](https://github.com/perfect-panel/ppanel-web/commit/0aa5d5b))
* **api**: Rename app-related functions and types to application for consistency ([9d8b814](https://github.com/perfect-panel/ppanel-web/commit/9d8b814))
* **api**: Replace 'deduction' with 'gift_amount' and add 'commission' field in type definitions ([77edf1d](https://github.com/perfect-panel/ppanel-web/commit/77edf1d))
* **api**: Server and order ([255bd82](https://github.com/perfect-panel/ppanel-web/commit/255bd82))
* **api**: Statistics ([7962162](https://github.com/perfect-panel/ppanel-web/commit/7962162))
* **api**: Subscribe token ([1932ba7](https://github.com/perfect-panel/ppanel-web/commit/1932ba7))
* **api**: Update API type definitions to replace 'deduction' with 'gift_amount' and make 'commission' field optional ([c2af060](https://github.com/perfect-panel/ppanel-web/commit/c2af060))
* **api**: Update Model ([39aaa73](https://github.com/perfect-panel/ppanel-web/commit/39aaa73))
* **api**: Update subscription_protocol to subscribe_type for consistency across services ([b6da51b](https://github.com/perfect-panel/ppanel-web/commit/b6da51b))
* **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**: Add error handling to form submission and reset Turnstile on failure ([715d011](https://github.com/perfect-panel/ppanel-web/commit/715d011))
* **auth**: Change Textarea value to defaultValue for client_secret in Apple auth page ([69fc670](https://github.com/perfect-panel/ppanel-web/commit/69fc670))
* **auth**: Refactor forms to use Turnstile ref for reset functionality ([320a7dc](https://github.com/perfect-panel/ppanel-web/commit/320a7dc))
* **auth**: Refactor reset password form to simplify code input and update placeholder text ([23833b4](https://github.com/perfect-panel/ppanel-web/commit/23833b4))
* **auth**: Refactor user authentication forms to remove global store dependency and improve type handling ([12026b0](https://github.com/perfect-panel/ppanel-web/commit/12026b0))
* **auth**: Remove unused telephone code login function and update typings for telephone login requests ([7239685](https://github.com/perfect-panel/ppanel-web/commit/7239685))
* **auth**: Require minimum length for invite string when forced invite is enabled ([a604f28](https://github.com/perfect-panel/ppanel-web/commit/a604f28))
* **auth**: Simplify email verification code input rendering ([6f7bc37](https://github.com/perfect-panel/ppanel-web/commit/6f7bc37))
* **auth**: Update authentication configuration and localization strings ([47f2c58](https://github.com/perfect-panel/ppanel-web/commit/47f2c58))
* **auth**: Update email verification logic to use domain suffix check ([62662bb](https://github.com/perfect-panel/ppanel-web/commit/62662bb))
* **auth**: Update user authentication flow to include email and phone code verification ([5d078fd](https://github.com/perfect-panel/ppanel-web/commit/5d078fd))
* **auth**: Update UserCheckForm to use setInitialValues and modify onSubmit type ([c984c0d](https://github.com/perfect-panel/ppanel-web/commit/c984c0d))
* **billing**: ExpiryDate ([e85e545](https://github.com/perfect-panel/ppanel-web/commit/e85e545))
* **billing**: I18n and styles ([81e0f21](https://github.com/perfect-panel/ppanel-web/commit/81e0f21))
* **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
* **config**: AlipayF2F ([6c07107](https://github.com/perfect-panel/ppanel-web/commit/6c07107))
* **config**: Bugs ([f57e40c](https://github.com/perfect-panel/ppanel-web/commit/f57e40c))
* **config**: Checkout Order ([a31e763](https://github.com/perfect-panel/ppanel-web/commit/a31e763))
* **config**: FormatBytes ([bbc2da0](https://github.com/perfect-panel/ppanel-web/commit/bbc2da0))
* **config**: NoStore ([2cc18cf](https://github.com/perfect-panel/ppanel-web/commit/2cc18cf))
* **config**: Runtime env ([a1e4999](https://github.com/perfect-panel/ppanel-web/commit/a1e4999))
* **config**: Status Percentag ([8f322fb](https://github.com/perfect-panel/ppanel-web/commit/8f322fb))
* **config**: SubLink ([1c61966](https://github.com/perfect-panel/ppanel-web/commit/1c61966))
* **config**: Subscribe Link ([11ea821](https://github.com/perfect-panel/ppanel-web/commit/11ea821))
* **content**: Parse subscription description and display features with icons ([3c5542a](https://github.com/perfect-panel/ppanel-web/commit/3c5542a))
* **controller**: Order status ([8c6a097](https://github.com/perfect-panel/ppanel-web/commit/8c6a097))
* **coupon**: Rename 'server' field to 'subscribe' in coupon form and update coupon update request type ([f8b6d82](https://github.com/perfect-panel/ppanel-web/commit/f8b6d82))
* **dashboard**:  Improve URL encoding for subscription links and enhance success message handling ([4983c33](https://github.com/perfect-panel/ppanel-web/commit/4983c33))
* **dashboard**:  Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
* **dashboard**: Correct progress value calculations and update groupId accessor ([36c7667](https://github.com/perfect-panel/ppanel-web/commit/36c7667))
* **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
* **dashboard**: Format Bytes ([d8b0bd9](https://github.com/perfect-panel/ppanel-web/commit/d8b0bd9))
* **dashboard**: Update icon imports for platform consistency and adjust icon size ([3e8912e](https://github.com/perfect-panel/ppanel-web/commit/3e8912e))
* **dashboard**: Update platform detection logic and improve layout responsiveness ([b0aa364](https://github.com/perfect-panel/ppanel-web/commit/b0aa364))
* **deps**: Remove outdated @iconify/react dependency and add iconify-json packages ([d6fbc38](https://github.com/perfect-panel/ppanel-web/commit/d6fbc38))
* **deps**: Typescript config ([34e24b8](https://github.com/perfect-panel/ppanel-web/commit/34e24b8))
* **deps**: Update clipboard ([5572710](https://github.com/perfect-panel/ppanel-web/commit/5572710))
* **editor**: Change value ([4fdfeb2](https://github.com/perfect-panel/ppanel-web/commit/4fdfeb2))
* **email**: Update platform configuration handling to use current ref for consistency ([c90175b](https://github.com/perfect-panel/ppanel-web/commit/c90175b))
* **footer**: Email address ([a451f44](https://github.com/perfect-panel/ppanel-web/commit/a451f44))
* **forms**: Add step attribute to number inputs for better value control ([b8f4f1e](https://github.com/perfect-panel/ppanel-web/commit/b8f4f1e))
* **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
* **layout**: Remove unnecessary cookie initialization in Logout function ([3065c3a](https://github.com/perfect-panel/ppanel-web/commit/3065c3a))
* **locale**: Default value ([937408f](https://github.com/perfect-panel/ppanel-web/commit/937408f))
* **locale**: Document ([6f0fa20](https://github.com/perfect-panel/ppanel-web/commit/6f0fa20))
* **locale**: Empty ([3832d20](https://github.com/perfect-panel/ppanel-web/commit/3832d20))
* **locale**: Input Placeholder Webhook Domain ([bca0935](https://github.com/perfect-panel/ppanel-web/commit/bca0935))
* **locale**: Language Select ([0befdb0](https://github.com/perfect-panel/ppanel-web/commit/0befdb0))
* **locales**: Add error message for incorrect user information ([52c1d1f](https://github.com/perfect-panel/ppanel-web/commit/52c1d1f))
* **locales**: Add error message for incorrect user information ([3d92902](https://github.com/perfect-panel/ppanel-web/commit/3d92902))
* **locales**: Add logout message to authentication localization files ([1d0d911](https://github.com/perfect-panel/ppanel-web/commit/1d0d911))
* **locales**: Fixed description in multilingual files, updated text related to email registration functionality ([c356bc2](https://github.com/perfect-panel/ppanel-web/commit/c356bc2))
* **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
* **locales**: Removed language file import to clean up unnecessary language support ([68f6ab2](https://github.com/perfect-panel/ppanel-web/commit/68f6ab2))
* **locales**: Removed multilingual files to clean up unnecessary language support ([5b151cd](https://github.com/perfect-panel/ppanel-web/commit/5b151cd))
* **locale**: Subscription Path Description ([4c67387](https://github.com/perfect-panel/ppanel-web/commit/4c67387))
* **locales**: Update custom HTML description for clarity across multiple languages ([557c5cd](https://github.com/perfect-panel/ppanel-web/commit/557c5cd))
* **locales**: Update custom HTML description in language file, ([87381da](https://github.com/perfect-panel/ppanel-web/commit/87381da))
* **locales**: Update expiration time description from minutes to seconds in multiple languages ([5bac933](https://github.com/perfect-panel/ppanel-web/commit/5bac933))
* **locales**: Update Hong Kong ([6d0d069](https://github.com/perfect-panel/ppanel-web/commit/6d0d069))
* **locales**: Update invite code text to indicate it's optional ([6a34bfb](https://github.com/perfect-panel/ppanel-web/commit/6a34bfb))
* **logs**: Update log display to render key-value pairs and remove badge ([5ea6489](https://github.com/perfect-panel/ppanel-web/commit/5ea6489))
* **metadata**: Global metadata ([15d5ecf](https://github.com/perfect-panel/ppanel-web/commit/15d5ecf))
* **nav**: Comment out unused social login options to simplify navigation configuration ([cefcb31](https://github.com/perfect-panel/ppanel-web/commit/cefcb31))
* **node-config**: Add null checks for time slots and ensure proper handling of undefined values ([1cdb7e7](https://github.com/perfect-panel/ppanel-web/commit/1cdb7e7))
* **node**: Add country and city fields to the form schema and localization files ([8775fb6](https://github.com/perfect-panel/ppanel-web/commit/8775fb6))
* **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
* **node**: Locale and form ([38be4d5](https://github.com/perfect-panel/ppanel-web/commit/38be4d5))
* **node**: Port config ([a20834a](https://github.com/perfect-panel/ppanel-web/commit/a20834a))
* **node**: Reality config ([fadd17f](https://github.com/perfect-panel/ppanel-web/commit/fadd17f))
* **node**: Service Name config ([d0be685](https://github.com/perfect-panel/ppanel-web/commit/d0be685))
* **node**: TLS config ([57fae12](https://github.com/perfect-panel/ppanel-web/commit/57fae12))
* **node**: Trojan protocol config ([7e1eb90](https://github.com/perfect-panel/ppanel-web/commit/7e1eb90))
* **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))
* **oauth**: Refactor OAuth configuration types and update related API methods ([6227ba9](https://github.com/perfect-panel/ppanel-web/commit/6227ba9))
* **oauth**: Remove redundant checks when updating configuration to simplify logic ([9140b8a](https://github.com/perfect-panel/ppanel-web/commit/9140b8a))
* **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
* **payment**: Config and types ([b0c87fb](https://github.com/perfect-panel/ppanel-web/commit/b0c87fb))
* **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
* **payment**: Qrcode ([a9a535b](https://github.com/perfect-panel/ppanel-web/commit/a9a535b))
* **payment**: Refactor payment form placeholder and update localization files ([4a4d364](https://github.com/perfect-panel/ppanel-web/commit/4a4d364))
* **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
* **payment**: Replace window.open with window.location.href for checkout links ([1d8c765](https://github.com/perfect-panel/ppanel-web/commit/1d8c765))
* **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))
* **phone**: Update SMS expiration time field to use 'sms_expire_time' with default value of 300 ([18b07c7](https://github.com/perfect-panel/ppanel-web/commit/18b07c7))
* **profile**: Restore filter to ensure only valid OAuth accounts are shown ([315c8f9](https://github.com/perfect-panel/ppanel-web/commit/315c8f9))
* **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
* **redirect**: Simplify redirect URL logic by removing unnecessary condition for sessionStorage ([c53ac61](https://github.com/perfect-panel/ppanel-web/commit/c53ac61))
* **redirect**: Update redirect URL logic to ensure proper handling of OAuth and auth paths ([7954762](https://github.com/perfect-panel/ppanel-web/commit/7954762))
* **register**: Adjust user email verification logic to handle domain suffix checks correctly ([686aa2d](https://github.com/perfect-panel/ppanel-web/commit/686aa2d))
* **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
* **request**: Locale ([37d408f](https://github.com/perfect-panel/ppanel-web/commit/37d408f))
* **rule-form**: Remove redundant rule set display ([6e0c9b6](https://github.com/perfect-panel/ppanel-web/commit/6e0c9b6))
* **rules**: Remove unused MATCH rule ([674a01c](https://github.com/perfect-panel/ppanel-web/commit/674a01c))
* **site**: Add image upload functionality for site logo configuration ([4ea6e4a](https://github.com/perfect-panel/ppanel-web/commit/4ea6e4a))
* **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
* **sort**: Refactor sorting logic in NodeTable and SubscribeTable components for improved clarity and performance ([331bbea](https://github.com/perfect-panel/ppanel-web/commit/331bbea))
* **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
* **subscribe**: Discount ([35a9f69](https://github.com/perfect-panel/ppanel-web/commit/35a9f69))
* **subscribe**: Extract Domain ([40d61a9](https://github.com/perfect-panel/ppanel-web/commit/40d61a9))
* **subscribe**: Handle optional values in price and discount calculations ([5939763](https://github.com/perfect-panel/ppanel-web/commit/5939763))
* **subscribe**: Jumps and internationalization ([13fdec3](https://github.com/perfect-panel/ppanel-web/commit/13fdec3))
* **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
* **subscribe**: Server group id ([90e6764](https://github.com/perfect-panel/ppanel-web/commit/90e6764))
* **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
* **subscribe**: Update forms to include refetch functionality and improve toast messages ([fc55e95](https://github.com/perfect-panel/ppanel-web/commit/fc55e95))
* **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
* **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))
* **subscribe**: Update value validation to check for number type in subscribe form ([6de29d5](https://github.com/perfect-panel/ppanel-web/commit/6de29d5))
* **subscription**: Add reset functionality for user subscription token ([39e89bf](https://github.com/perfect-panel/ppanel-web/commit/39e89bf))
* **table**: Update privacy policy tab translation key and remove unnecessary requestType from OAuth callback ([14b3af5](https://github.com/perfect-panel/ppanel-web/commit/14b3af5))
* **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))
* **turnstile**: Turnstile_site_key ([0327b73](https://github.com/perfect-panel/ppanel-web/commit/0327b73))
* **type**: Fix ts type check error ([3cb0629](https://github.com/perfect-panel/ppanel-web/commit/3cb0629))
* **types**: Add 'gift_amount' field to API type definitions ([8f8a12a](https://github.com/perfect-panel/ppanel-web/commit/8f8a12a))
* **types**: Checking ([2992824](https://github.com/perfect-panel/ppanel-web/commit/2992824))
* **types**: Order type ([c7e50a9](https://github.com/perfect-panel/ppanel-web/commit/c7e50a9))
* **ui**: Bugs ([b023d0f](https://github.com/perfect-panel/ppanel-web/commit/b023d0f))
* **ui**: Components ([a7927d7](https://github.com/perfect-panel/ppanel-web/commit/a7927d7))
* **ui**: Fix json formatting ([e1ddd94](https://github.com/perfect-panel/ppanel-web/commit/e1ddd94))
* **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))
* **user-nav**: Update user avatar and label to display telephone if email is not available ([7b6bb7b](https://github.com/perfect-panel/ppanel-web/commit/7b6bb7b))
* **user**: Add the 'gift_amount' field to the user service's type definition ([6301409](https://github.com/perfect-panel/ppanel-web/commit/6301409))
* **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))
* **user**: Update user identifier field and localizations ([1b6befa](https://github.com/perfect-panel/ppanel-web/commit/1b6befa))
* **user**: Update user subscribe display ([3bb714d](https://github.com/perfect-panel/ppanel-web/commit/3bb714d))
* **utils**: Login redirect url ([cbe5f0d](https://github.com/perfect-panel/ppanel-web/commit/cbe5f0d))
* More bugs ([2d88a3a](https://github.com/perfect-panel/ppanel-web/commit/2d88a3a))

### 👷 Build System

* **config**:  Update pm2 config ([d95b425](https://github.com/perfect-panel/ppanel-web/commit/d95b425))

### 💄 Styles

* **dashboard**: Adjust grid layout and update image dimensions in application display ([f3204b7](https://github.com/perfect-panel/ppanel-web/commit/f3204b7))
* **dashboard**: Enhance card components with full height and improved empty state handling ([7e1d551](https://github.com/perfect-panel/ppanel-web/commit/7e1d551))
* **document**: Update ([0a8109b](https://github.com/perfect-panel/ppanel-web/commit/0a8109b))
* **globals**: Refactor delete confirmation button and update badge styles in node and subscribe tables ([30ae781](https://github.com/perfect-panel/ppanel-web/commit/30ae781))
* **locales**: Remove unused subscription labels from multiple locale files ([fb0c510](https://github.com/perfect-panel/ppanel-web/commit/fb0c510))
* **locales**: Update server.json to reorganize relay mode options and improve labels ([701cdee](https://github.com/perfect-panel/ppanel-web/commit/701cdee))
* **node**: Form ([d5f5add](https://github.com/perfect-panel/ppanel-web/commit/d5f5add))
* **node**: Improve layout and spacing in NodeStatusCell component ([136287d](https://github.com/perfect-panel/ppanel-web/commit/136287d))
* **node**: Protocol Tab ([2bcb925](https://github.com/perfect-panel/ppanel-web/commit/2bcb925))
* **time-slot**: Add chart display ([c44ad47](https://github.com/perfect-panel/ppanel-web/commit/c44ad47))
* **ui**: Update mobile style ([eda18bc](https://github.com/perfect-panel/ppanel-web/commit/eda18bc))
* Update node secret UI and add telephone code field to authentication form ([770932e](https://github.com/perfect-panel/ppanel-web/commit/770932e))

### 📝 Documentation

* **readme**: License name ([74cb16b](https://github.com/perfect-panel/ppanel-web/commit/74cb16b))

### 🔧 Continuous Integration

* **github**: Release docker ([5af60aa](https://github.com/perfect-panel/ppanel-web/commit/5af60aa))
* **step**: Update step name ([9eca618](https://github.com/perfect-panel/ppanel-web/commit/9eca618))
2025-04-24 10:14:50 +00:00
web@ppanel 4a4d364f17 🐛 fix(payment): Refactor payment form placeholder and update localization files 2025-04-24 06:11:55 -04:00
web@ppanel 5ea64893e8 🐛 fix(logs): Update log display to render key-value pairs and remove badge 2025-04-24 05:08:03 -04:00
web@ppanel 674a01c813 🐛 fix(rules): Remove unused MATCH rule 2025-04-20 03:51:00 -04:00
web@ppanel 3c5542a3ea 🐛 fix(content): Parse subscription description and display features with icons 2025-04-14 09:16:24 -04:00
web@ppanel 6e0c9b6698 🐛 fix(rule-form): Remove redundant rule set display 2025-04-14 03:09:44 -04:00
web@ppanel 3bb714d15c 🐛 fix(user): Update user subscribe display 2025-04-09 03:27:22 -04:00
semantic-release-bot 7023875548 🔖 chore(release): v1.0.0-beta.34 [skip ci]
# [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02)

###  Features

* **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
* **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
* **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))

### 🐛 Bug Fixes

* **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
* **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
* **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
* **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
* **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
* **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
2025-04-02 08:24:17 +00:00
web@ppanel a7aa5fee3e 🐛 fix(admin): Modify the label type in the rule form to a string array 2025-04-02 11:52:38 +07:00
web@ppanel 64cd842926 🐛 fix(admin): Hidden versions and system upgrades 2025-04-02 00:15:15 +07:00
web@ppanel 8b43e69bfe feat(admin): Add application and rule management entries to localization files 2025-04-02 00:12:02 +07:00
web@ppanel ef153747bd 🐛 fix(subscribe): Update default selection logic in subscription form to ensure proper state management 2025-04-01 12:37:17 +07:00
web@ppanel 328838d754 🐛 fix(subscribe): Add value prop to field in subscription form for proper state management 2025-03-30 20:27:03 +07:00
web@ppanel 423b24077f 🐛 fix(subscribe): Refactor discount calculations and default selection logic in subscription forms 2025-03-30 00:53:47 +07:00
web@ppanel fa2fb2864f 🐛 fix(node): Handle potential null value for online users count 2025-03-29 23:43:21 +07:00
web@ppanel 8d49daca21 feat(user): Integrate subscription list into user management, update request parameters and types 2025-03-26 15:01:43 +07:00
web@ppanel cf5c39cfe5 feat(api): Add an interface to obtain user subscription details, update related type definitions and localized text 2025-03-25 23:55:31 +07:00
semantic-release-bot 383638f702 🔖 chore(release): v1.0.0-beta.33 [skip ci]
# [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))
2025-03-18 07:53:53 +00:00
web@ppanel 5939763b57 🐛 fix(subscribe): Handle optional values in price and discount calculations 2025-03-18 14:50:48 +07:00
semantic-release-bot fa56eb8293 🔖 chore(release): v1.0.0-beta.32 [skip ci]
# [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))
2025-03-17 15:53:15 +00:00
web@ppanel 6a34bfb78d 🐛 fix(locales): Update invite code text to indicate it's optional 2025-03-17 22:50:21 +07:00
web@ppanel b8f4f1e694 🐛 fix(forms): Add step attribute to number inputs for better value control 2025-03-17 22:47:42 +07:00
semantic-release-bot aa1d42651d 🔖 chore(release): v1.0.0-beta.31 [skip ci]
# [1.0.0-beta.31](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.30...v1.0.0-beta.31) (2025-03-15)

### 🐛 Bug Fixes

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

###  Features

* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
* **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
* **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
* **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
* **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
* **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
* **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
* **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))

### 🎫 Chores

* **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
* **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
* Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))

### 🐛 Bug Fixes

* **dashboard**:  Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
2025-03-15 09:02:24 +00:00
web@ppanel f219c52306 🔧 chore: Merge branch 'beta' into develop 2025-03-15 15:58:37 +07:00
web@ppanel 3f7b6d16ed feat(node-form): Update number input fields to enforce step, min, and max values 2025-03-15 15:48:59 +07:00
web@ppanel 6dbebd152d feat(global): Add custom data 2025-03-15 15:39:55 +07:00
web@ppanel bb3bd7b50c feat(email): Add traffic exhaustion template 2025-03-14 23:28:36 +07:00
web@ppanel fbad3b0e51 feat(timeline): Simplify timeline component layout and remove commented-out code 2025-03-14 23:21:17 +07:00
web@ppanel e551232564 🐛 fix(dashboard): Update date display to use start_time if available 2025-03-14 22:10:24 +07:00
web@ppanel bf58f25072 feat(formatting): Update differenceInDays function to return whole days or two decimal places 2025-03-14 20:51:17 +07:00
web@ppanel 85f55def2e feat(payment): Add isEdit prop to PaymentForm and disable fields when editing 2025-03-14 14:04:10 +07:00
semantic-release-bot 29bc3c7722 🔖 chore(release): v1.0.0-beta.29 [skip ci]
# [1.0.0-beta.29](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.28...v1.0.0-beta.29) (2025-03-14)

###  Features

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

###  Features

* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
2025-03-14 13:42:58 +07:00
web@ppanel ce3197298d feat(input): Add minimum value constraint and enhance number handling in EnhancedInput 2025-03-14 13:42:58 +07:00
semantic-release-bot 092477b2d3 🔖 chore(release): v1.0.0-beta.27 [skip ci]
* **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
* Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))

* **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
* **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
* **subscription**:  Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
* **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))

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

* **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
* **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
* **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
* **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
* **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
* **payment**: Update checkout type from 'link' to 'url' for consistency ([136a1ab](https://github.com/perfect-panel/ppanel-web/commit/136a1ab))
* **payment**: Update payment information ([70d6a38](https://github.com/perfect-panel/ppanel-web/commit/70d6a38))
* **payment**: Update payment method update logic to include row data ([6752420](https://github.com/perfect-panel/ppanel-web/commit/6752420))
* **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
* **ui**: Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab ([fc0da76](https://github.com/perfect-panel/ppanel-web/commit/fc0da76))
* **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
2025-03-14 13:42:58 +07:00
web@ppanel b8316bba33 feat(loading): Replace loading animation with a simpler spinner and loading text 2025-03-14 13:40:56 +07:00
web@ppanel dddc21c686 feat(api): Add CheckoutOrder request and response types, and update user purchase request parameters 2025-03-14 13:34:36 +07:00
semantic-release-bot d10ecc9fdf 🔖 chore(release): v1.0.0-beta.28 [skip ci]
# [1.0.0-beta.28](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.27...v1.0.0-beta.28) (2025-03-13)

###  Features

* **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
2025-03-13 10:35:19 +00:00
web@ppanel 94822d902f feat(input): Add minimum value constraint and enhance number handling in EnhancedInput 2025-03-13 17:32:36 +07:00
semantic-release-bot 85fdc36c14 🔖 chore(release): v1.0.0-beta.27 [skip ci]
# [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))
2025-03-13 04:27:12 +00:00
web@ppanel cfa3fc024d 🐛 fix(changelog): Update change log style 2025-03-13 11:23:14 +07:00
web@ppanel c06ea49d6f 🐛 fix(purchasing): Update payment type to lowercase and add optional chaining for discounts 2025-03-13 00:06:42 +07:00
web@ppanel c3138a863d 🐛 fix(payment): Fix payment related type definitions and update payment method references 2025-03-12 21:17:04 +07:00
web@ppanel 7fa3a57df4 feat(payment): Add bank card payment 2025-03-12 17:15:32 +07:00
web@ppanel 70d6a38a29 🐛 fix(payment): Update payment information 2025-03-12 13:43:06 +07:00
web@ppanel 5c710e1add 🐛 fix(payment): Add notification URL field to payment management interface 2025-03-11 21:22:18 +07:00
web@ppanel 6752420ba5 🐛 fix(payment): Update payment method update logic to include row data 2025-03-11 21:15:07 +07:00
web@ppanel 710947209a ♻️ refactor(payment): Reconstruct the payment page 2025-03-11 20:06:12 +07:00
web@ppanel fc0da761b5 🐛 fix(ui): Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab 2025-03-10 18:32:07 +07:00
web@ppanel 136a1abd8b 🐛 fix(payment): Update checkout type from 'link' to 'url' for consistency 2025-03-08 20:10:45 +07:00
web@ppanel a5e2079ddc 🐛 fix(payment): Refactor purchaseCheckout usage and remove redundant code 2025-03-08 20:04:33 +07:00
web@ppanel 35f92c9329 🐛 fix(affiliate): Update user identifier 2025-03-08 15:43:20 +07:00
web@ppanel f5d8fd3b65 🐛 fix(ui): Multiple display bugs 2025-03-08 14:46:20 +07:00
web@ppanel 0c907337e1 feat(cdn): Add CDN URL configuration and update related references 2025-03-08 12:54:58 +07:00
web@ppanel e4630f8ca9 feat(subscription): Improve layout and organization of subscription detail tabs 2025-03-08 12:43:57 +07:00
web@ppanel 2215c7f2b9 feat(subscription): Refactor subscription handling and update imports for better organization 2025-03-08 12:36:25 +07:00
semantic-release-bot 3222016799 🔧 chore(merge): Bump version to 1.0.0-beta.26 and update changelog 2025-03-08 12:36:02 +07:00
web@ppanel d0a04679ce Merge pull request #12 from turbolnk-com/develop
♻️ refactor: Enhance user navigation dropdown ui and styling
2025-03-07 11:46:01 +07:00
turbolnk d2732e650b ♻️ refactor: Enhance user navigation dropdown ui and styling 2025-03-06 09:34:39 +08:00
semantic-release-bot 79edea7973 🔖 chore(release): v1.0.0-beta.26 [skip ci]
# [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))
2025-03-02 16:40:10 +00:00
web@ppanel f17bf8db6e 🐛 fix(icon): Comment out unused icon collection imports 2025-03-02 23:37:15 +07:00
semantic-release-bot 047a69886e 🔖 chore(release): v1.0.0-beta.25 [skip ci]
# [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))
2025-03-01 12:59:06 +00:00
web@ppanel 71bf002370 🐛 fix(request): Add error code 40005 to trigger logout 2025-03-01 19:55:55 +07:00
web@ppanel d0e6df044e 🐛 fix(dashboard): Display subscription creation date in user dashboard 2025-03-01 19:55:31 +07:00
web@ppanel 8e16ef17ed feat(auth): Add privacy policy link to the footer 2025-03-01 19:40:45 +07:00
web@ppanel 2b80496637 🐛 fix(subscribe): Update payment return URL 2025-03-01 19:35:32 +07:00
semantic-release-bot 01a3aa02ec 🔖 chore(release): v1.0.0-beta.24 [skip ci]
# [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))
2025-02-27 08:02:53 +00:00
web@ppanel 35210fe8cf 🐛 fix(locales): Order recharge related fields 2025-02-27 14:53:19 +07:00
web@ppanel d5847faeda feat(loading): Add loading components and integrate them in Providers 2025-02-27 14:44:30 +07:00
web@ppanel 0130e02ffd 🔧 chore(merge): Add advertising module and device settings 2025-02-27 13:46:51 +07:00
web@ppanel c2b858e3fe Merge pull request #11 from turbolnk-com/develop
♻️ refactor(ui): Optimize document display
2025-02-25 18:35:05 +07:00
turbolnk e11f18c034 ♻️ refactor: Reduce code complexity and improve readability 2025-02-25 19:31:32 +08:00
turbolnk 2ca299224d ♻️ refactor(ui): Optimize document display 2025-02-25 18:38:25 +08:00
semantic-release-bot cf1d66d07e 🔖 chore(release): v1.0.0-beta.23 [skip ci]
# [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))
2025-02-24 05:10:30 +00:00
web@ppanel 62662bb79c 🐛 fix(auth): Update email verification logic to use domain suffix check 2025-02-24 12:07:41 +07:00
semantic-release-bot c0fb34fa50 🔖 chore(release): v1.0.0-beta.22 [skip ci]
# [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))
2025-02-23 15:06:05 +00:00
web@ppanel 68f6ab2e6c 🐛 fix(locales): Removed language file import to clean up unnecessary language support 2025-02-23 22:03:15 +07:00
semantic-release-bot ca642c2001 🔖 chore(release): v1.0.0-beta.21 [skip ci]
# [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))
2025-02-23 14:17:29 +00:00
web@ppanel 5b151cdbfc 🐛 fix(locales): Removed multilingual files to clean up unnecessary language support 2025-02-23 21:14:17 +07:00
web@ppanel 14b3af5f96 🐛 fix(table): Update privacy policy tab translation key and remove unnecessary requestType from OAuth callback 2025-02-23 20:59:48 +07:00
web@ppanel baa68f03e3 feat(privacy-policy): Add privacy policy related text and links 2025-02-23 20:54:56 +07:00
web@ppanel 87381da1e5 🐛 fix(locales): Update custom HTML description in language file, 2025-02-23 17:25:31 +07:00
semantic-release-bot bbd44f0107 🔖 chore(release): v1.0.0-beta.20 [skip ci]
# [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))
2025-02-23 06:31:01 +00:00
web@ppanel c65a44c2c7 🐛 fix(subscribe): Update subscription domain placeholder to include examples; improve site name retrieval in global store 2025-02-23 13:25:01 +07:00
web@ppanel 715d0113ca 🐛 fix(auth): Add error handling to form submission and reset Turnstile on failure 2025-02-23 13:12:25 +07:00
semantic-release-bot 0f15fb82cb 🔖 chore(release): v1.0.0-beta.19 [skip ci]
# [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))
2025-02-23 05:58:05 +00:00
web@ppanel 320a7dc342 🐛 fix(auth): Refactor forms to use Turnstile ref for reset functionality 2025-02-23 12:55:21 +07:00
web@ppanel 42ba9e88ad feat(form): Make version field optional and set default value; update site domain placeholder for clarity 2025-02-23 12:46:29 +07:00
semantic-release-bot 4a002333d4 🔖 chore(release): v1.0.0-beta.18 [skip ci]
# [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))
2025-02-22 16:38:46 +00:00
web@ppanel 6384237782 feat(platform): Update platform naming and add keywords and custom HTML fields 2025-02-22 23:35:30 +07:00
web@ppanel 557c5cdc29 🐛 fix(locales): Update custom HTML description for clarity across multiple languages 2025-02-22 12:51:57 +07:00
web@ppanel f9d7736e48 feat(site): Added localization support for custom HTML and keyword fields 2025-02-22 12:33:09 +07:00
semantic-release-bot 5b64389544 🔖 chore(release): v1.0.0-beta.17 [skip ci]
# [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))
2025-02-21 13:26:11 +00:00
web@ppanel 23833b47d1 🐛 fix(auth): Refactor reset password form to simplify code input and update placeholder text 2025-02-21 20:23:30 +07:00
semantic-release-bot c3eff0a0e7 🔖 chore(release): v1.0.0-beta.16 [skip ci]
# [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))
2025-02-21 13:18:47 +00:00
web@ppanel 6f7bc37233 🐛 fix(auth): Simplify email verification code input rendering 2025-02-21 20:16:02 +07:00
semantic-release-bot b2e8fadaf3 🔖 chore(release): v1.0.0-beta.15 [skip ci]
# [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))
2025-02-21 12:59:02 +00:00
web@ppanel 315c8f9afe 🐛 fix(profile): Restore filter to ensure only valid OAuth accounts are shown 2025-02-21 19:55:24 +07:00
540 changed files with 22023 additions and 4732 deletions
+683 -4
View File
@@ -1,19 +1,698 @@
<a name="readme-top"></a>
# Changelog
# [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)
# [1.1.0](https://github.com/perfect-panel/ppanel-web/compare/v1.0.2...v1.1.0) (2025-07-06)
### ✨ Features
* **view**: Add AnyTLS protocol support and enhance node configuration options ([bcfb10a](https://github.com/perfect-panel/ppanel-web/commit/bcfb10a))
<a name="readme-top"></a>
# Changelog
## [1.0.2](https://github.com/perfect-panel/ppanel-web/compare/v1.0.1...v1.0.2) (2025-06-29)
### 🐛 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))
- **subscription**: User subscription information ([7d18ff6](https://github.com/perfect-panel/ppanel-web/commit/7d18ff6))
## [1.0.1](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0...v1.0.1) (2025-04-28)
### 🐛 Bug Fixes
- **payment**: Disable webhook_secret field in PaymentForm component ([d323af8](https://github.com/perfect-panel/ppanel-web/commit/d323af8))
- **recharge**: Set balance prop to false in PaymentMethods component ([356ae5b](https://github.com/perfect-panel/ppanel-web/commit/356ae5b))
# 1.0.0 (2025-04-24)
### ♻ Code Refactoring
- **api**: Sort and Announcement ([38d5616](https://github.com/perfect-panel/ppanel-web/commit/38d5616))
- **auth**: Refactor user authorization handling and improve error logging ([68bc18f](https://github.com/perfect-panel/ppanel-web/commit/68bc18f))
- **config**: GenerateMetadata ([a0bb101](https://github.com/perfect-panel/ppanel-web/commit/a0bb101))
- **config**: Simplify environment variable handling and improve build script ([cf54d0f](https://github.com/perfect-panel/ppanel-web/commit/cf54d0f))
- **config**: Viewport ([24b8601](https://github.com/perfect-panel/ppanel-web/commit/24b8601))
- **core**: Restructure project for better module separation ([9d0cb8b](https://github.com/perfect-panel/ppanel-web/commit/9d0cb8b))
- **deps**: Update ([19837a1](https://github.com/perfect-panel/ppanel-web/commit/19837a1))
- **empty**: Content ([aa4c667](https://github.com/perfect-panel/ppanel-web/commit/aa4c667))
- **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
- **sbscribe**: Rename and reorganize components for better structure and clarity ([5e5e4ed](https://github.com/perfect-panel/ppanel-web/commit/5e5e4ed))
- **ui**: Dependencies ([727d779](https://github.com/perfect-panel/ppanel-web/commit/727d779))
- **ui**: Layout ([9262d7d](https://github.com/perfect-panel/ppanel-web/commit/9262d7d))
- **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
- Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
- Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))
### ⚡ Performance Improvements
- **subscribe**: Form discount price ([059a892](https://github.com/perfect-panel/ppanel-web/commit/059a892))
### ✨ Features
- **accounts**: Update third-party account binding and unbinding ([1841552](https://github.com/perfect-panel/ppanel-web/commit/1841552))
- **ad**: Advertise ([b1105cd](https://github.com/perfect-panel/ppanel-web/commit/b1105cd))
- **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
- **affiliate**: Add Affiliate component with commission display and invite link functionality ([4aea4e8](https://github.com/perfect-panel/ppanel-web/commit/4aea4e8))
- **affiliate**: Affiliate Detail ([a782c17](https://github.com/perfect-panel/ppanel-web/commit/a782c17))
- **affiliate**: Commission Rate ([5eec430](https://github.com/perfect-panel/ppanel-web/commit/5eec430))
- **affiliate**: Update affiliate component to display total commission and improve data fetching ([cc834ca](https://github.com/perfect-panel/ppanel-web/commit/cc834ca))
- **announcement**: Popup and pinned ([f3680a7](https://github.com/perfect-panel/ppanel-web/commit/f3680a7))
- **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
- **api**: Add new subscription properties and locale support for deduction ratios and reset cycles ([fec80f5](https://github.com/perfect-panel/ppanel-web/commit/fec80f5))
- **api**: Add Time Period Configuration ([837157c](https://github.com/perfect-panel/ppanel-web/commit/837157c))
- **api**: Telegram ([17ce96a](https://github.com/perfect-panel/ppanel-web/commit/17ce96a))
- **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 email and SMS code sending functionality with localization updates ([57eaa55](https://github.com/perfect-panel/ppanel-web/commit/57eaa55))
- **auth**: Add Oauth configuration for Telegram, Facebook, Google, Github, and Apple ([18ee600](https://github.com/perfect-panel/ppanel-web/commit/18ee600))
- **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
- **auth**: Add SMS and email configuration options to global store and update localization ([4acf7b1](https://github.com/perfect-panel/ppanel-web/commit/4acf7b1))
- **auth**: Add type parameter to SendCode and update related API typings ([4198871](https://github.com/perfect-panel/ppanel-web/commit/4198871))
- **auth**: Enhance user registration with invite handling and logo display ([207bc24](https://github.com/perfect-panel/ppanel-web/commit/207bc24))
- **auth**: Redirect user after OAuth login and add logos icon collection ([aa6dda8](https://github.com/perfect-panel/ppanel-web/commit/aa6dda8))
- **auth**: Refactor mobile authentication config to support whitelist functionality ([c761ec7](https://github.com/perfect-panel/ppanel-web/commit/c761ec7))
- **billing**: Update Billing ([078fc9d](https://github.com/perfect-panel/ppanel-web/commit/078fc9d))
- **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
- **config**: Add application selection and encryption settings to configuration form ([88b3504](https://github.com/perfect-panel/ppanel-web/commit/88b3504))
- **config**: FormatBytes ([9251a09](https://github.com/perfect-panel/ppanel-web/commit/9251a09))
- **config**: Protocol type ([a3b45b4](https://github.com/perfect-panel/ppanel-web/commit/a3b45b4))
- **config**: Update encryption fields in configuration form and refactor OAuth callback parameters ([652e032](https://github.com/perfect-panel/ppanel-web/commit/652e032))
- **config**: Webhook Domain ([01e06c6](https://github.com/perfect-panel/ppanel-web/commit/01e06c6))
- **dashboard**: Optimization ([5b3f4b4](https://github.com/perfect-panel/ppanel-web/commit/5b3f4b4))
- **dashboard**: Statistics ([2926abc](https://github.com/perfect-panel/ppanel-web/commit/2926abc))
- **device**: Modify IMEI to device identifier support ([e3f9ef6](https://github.com/perfect-panel/ppanel-web/commit/e3f9ef6))
- **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
- **favicon**: Update SVG favicon design for admin and user interfaces ([1d91738](https://github.com/perfect-panel/ppanel-web/commit/1d91738))
- **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
- **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))
- **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
- **global**: Add SMS configuration options to global store ([39a9ce6](https://github.com/perfect-panel/ppanel-web/commit/39a9ce6))
- **header**: Update locales ([bfb6c27](https://github.com/perfect-panel/ppanel-web/commit/bfb6c27))
- **imei**: Add IMEI related internationalization support and menu items ([13c3337](https://github.com/perfect-panel/ppanel-web/commit/13c3337))
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
- **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
- **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
- **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
- **locale**: Add Persian ([93a0a88](https://github.com/perfect-panel/ppanel-web/commit/93a0a88))
- **locales**: Add area code and telephone fields to user forms in multiple languages ([9b8258c](https://github.com/perfect-panel/ppanel-web/commit/9b8258c))
- **locales**: Add description information of communication keys and encryption methods to enhance client configuration capabilities ([d1f5a9b](https://github.com/perfect-panel/ppanel-web/commit/d1f5a9b))
- **locales**: Add kick offline confirmation and success messages in multiple languages ([5db5343](https://github.com/perfect-panel/ppanel-web/commit/5db5343))
- **locales**: Add multiple languages ([b243ab9](https://github.com/perfect-panel/ppanel-web/commit/b243ab9))
- **locales**: Replace 'nodeGroupId' with 'groupId' in multiple language files for consistency ([a4e9d5d](https://github.com/perfect-panel/ppanel-web/commit/a4e9d5d))
- **locales**: Update 'deductBalance' to 'giftAmount' across multiple languages and fix newline in announcement.json ([70497af](https://github.com/perfect-panel/ppanel-web/commit/70497af))
- **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))
- **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
- **node-subscription**: Add copy functionality for columns ([3a81e37](https://github.com/perfect-panel/ppanel-web/commit/3a81e37))
- **node**: Add NodeStatus ([c712624](https://github.com/perfect-panel/ppanel-web/commit/c712624))
- **node**: Add protocol ([301b635](https://github.com/perfect-panel/ppanel-web/commit/301b635))
- **node**: Add serverKey ([25ce37e](https://github.com/perfect-panel/ppanel-web/commit/25ce37e))
- **node**: Add status ([c06372b](https://github.com/perfect-panel/ppanel-web/commit/c06372b))
- **node**: Add tags ([f408fdf](https://github.com/perfect-panel/ppanel-web/commit/f408fdf))
- **node**: Move the node configuration to the server module ([7f0f5ce](https://github.com/perfect-panel/ppanel-web/commit/7f0f5ce))
- **oauth**: Add certification component for handling OAuth login callbacks and improve user authentication flow ([5ed04c0](https://github.com/perfect-panel/ppanel-web/commit/5ed04c0))
- **oauth**: Implement OAuth token retrieval and refactor login callback handling ([40a6f7c](https://github.com/perfect-panel/ppanel-web/commit/40a6f7c))
- **oauth**: Refactor platform parameter handling and improve logout redirection logic ([8346c85](https://github.com/perfect-panel/ppanel-web/commit/8346c85))
- **oauth**: Update OAuth login handling to use callback parameter and improve URL parameter retrieval ([9227411](https://github.com/perfect-panel/ppanel-web/commit/9227411))
- **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
- **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
- **platform**: Update platform naming and add keywords and custom HTML fields ([6384237](https://github.com/perfect-panel/ppanel-web/commit/6384237))
- **privacy-policy**: Add privacy policy related text and links ([baa68f0](https://github.com/perfect-panel/ppanel-web/commit/baa68f0))
- **profile**: Update localization strings and enhance third-party account binding ([2d1effb](https://github.com/perfect-panel/ppanel-web/commit/2d1effb))
- **relay**: Add relay mode configuration and update related schemas ([3cc9477](https://github.com/perfect-panel/ppanel-web/commit/3cc9477))
- **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))
- **schema**: Add security field to hysteria2 and tuic schemas ([cd59d44](https://github.com/perfect-panel/ppanel-web/commit/cd59d44))
- **site**: Added localization support for custom HTML and keyword fields ([f9d7736](https://github.com/perfect-panel/ppanel-web/commit/f9d7736))
- **sms**: Update locales ([938363b](https://github.com/perfect-panel/ppanel-web/commit/938363b))
- **stats**: Replace dynamic stat fetching with environment constants for user, server, and location counts ([46ae166](https://github.com/perfect-panel/ppanel-web/commit/46ae166))
- **subscribe**: Add 'sold' column to SubscribeTable and update inventory terminology ([19619fd](https://github.com/perfect-panel/ppanel-web/commit/19619fd))
- **subscribe**: Add reset_time to API typings and update unsubscribe logic ([eeea165](https://github.com/perfect-panel/ppanel-web/commit/eeea165))
- **subscribe**: Add subscribe_discount type ([f99c604](https://github.com/perfect-panel/ppanel-web/commit/f99c604))
- **subscribe**: Add subscription credits ([5bc7905](https://github.com/perfect-panel/ppanel-web/commit/5bc7905))
- **subscribe**: Add unit time ([39d07ec](https://github.com/perfect-panel/ppanel-web/commit/39d07ec))
- **subscribe**: Add unsubscribe functionality with confirmation messages and localized strings ([b2a2f42](https://github.com/perfect-panel/ppanel-web/commit/b2a2f42))
- **subscribe**: Improve error handling in subscription forms and update component props ([d28a10b](https://github.com/perfect-panel/ppanel-web/commit/d28a10b))
- **subscribe**: Improve layout and styling in subscription components ([5766376](https://github.com/perfect-panel/ppanel-web/commit/5766376))
- **subscribe**: Move subscription configuration and application to subscription module ([f90d4d2](https://github.com/perfect-panel/ppanel-web/commit/f90d4d2))
- **subscribe**: Update SubscribeTable component to use API.SubscribeItem type and ensure proper type casting ([f26f1c2](https://github.com/perfect-panel/ppanel-web/commit/f26f1c2))
- **subscribe**: Update suffix from 'MB' to 'Mbps' and enhance speed limit display logic ([3547bb1](https://github.com/perfect-panel/ppanel-web/commit/3547bb1))
- **subscription**: Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
- **subscription**: Add delete user subscription functionality ([1fc3a10](https://github.com/perfect-panel/ppanel-web/commit/1fc3a10))
- **subscription**: Add localized messages for existing subscriptions and deletion restrictions ([e8a72d5](https://github.com/perfect-panel/ppanel-web/commit/e8a72d5))
- **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
- **table**: Add sorting support for Node and subscription columns ([27924b0](https://github.com/perfect-panel/ppanel-web/commit/27924b0))
- **table**: Supports drag and drop sorting ([2f56ef5](https://github.com/perfect-panel/ppanel-web/commit/2f56ef5))
- **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))
- **tos**: Display data ([6024454](https://github.com/perfect-panel/ppanel-web/commit/6024454))
- **tutorial**: Add common tutorial list ([872252c](https://github.com/perfect-panel/ppanel-web/commit/872252c))
- **tutorial**: Fetch the latest tutorial version from GitHub API for dynamic URL generation ([28f8c78](https://github.com/perfect-panel/ppanel-web/commit/28f8c78))
- **ui**: System Tool ([1836980](https://github.com/perfect-panel/ppanel-web/commit/1836980))
- **ui**: Update homepage data ([8425b13](https://github.com/perfect-panel/ppanel-web/commit/8425b13))
- **ui**: Update input components and enhance card minimum width for better layout ([8a02310](https://github.com/perfect-panel/ppanel-web/commit/8a02310))
- **user**: Add 'gift_amount' field and update related references in user services and components ([b13c77e](https://github.com/perfect-panel/ppanel-web/commit/b13c77e))
- **user**: Add telephone input with area code selection and update localization ([585b99c](https://github.com/perfect-panel/ppanel-web/commit/585b99c))
- **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))
- **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
- Update Auth Control ([c59742a](https://github.com/perfect-panel/ppanel-web/commit/c59742a))
### 🎫 Chores
- **config**: Entry locale ([5737331](https://github.com/perfect-panel/ppanel-web/commit/5737331))
- **deps**: Update package dependencies across multiple projects for improved stability and performance ([b01a5bc](https://github.com/perfect-panel/ppanel-web/commit/b01a5bc))
- **init**: Project initialization ([829edfa](https://github.com/perfect-panel/ppanel-web/commit/829edfa))
- **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
- **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
- **release**: V1.0.0-beta.1 [skip ci] ([7284d1c](https://github.com/perfect-panel/ppanel-web/commit/7284d1c))
- **release**: V1.0.0-beta.10 [skip ci] ([5cf573a](https://github.com/perfect-panel/ppanel-web/commit/5cf573a))
- **release**: V1.0.0-beta.11 [skip ci] ([1f29506](https://github.com/perfect-panel/ppanel-web/commit/1f29506))
- **release**: V1.0.0-beta.12 [skip ci] ([4418c47](https://github.com/perfect-panel/ppanel-web/commit/4418c47))
- **release**: V1.0.0-beta.13 [skip ci] ([23c974a](https://github.com/perfect-panel/ppanel-web/commit/23c974a))
- **release**: V1.0.0-beta.14 [skip ci] ([0fb0d8b](https://github.com/perfect-panel/ppanel-web/commit/0fb0d8b))
- **release**: V1.0.0-beta.15 [skip ci] ([b2e8fad](https://github.com/perfect-panel/ppanel-web/commit/b2e8fad))
- **release**: V1.0.0-beta.16 [skip ci] ([c3eff0a](https://github.com/perfect-panel/ppanel-web/commit/c3eff0a))
- **release**: V1.0.0-beta.17 [skip ci] ([5b64389](https://github.com/perfect-panel/ppanel-web/commit/5b64389))
- **release**: V1.0.0-beta.18 [skip ci] ([4a00233](https://github.com/perfect-panel/ppanel-web/commit/4a00233))
- **release**: V1.0.0-beta.19 [skip ci] ([0f15fb8](https://github.com/perfect-panel/ppanel-web/commit/0f15fb8))
- **release**: V1.0.0-beta.2 [skip ci] ([087c36c](https://github.com/perfect-panel/ppanel-web/commit/087c36c))
- **release**: V1.0.0-beta.20 [skip ci] ([bbd44f0](https://github.com/perfect-panel/ppanel-web/commit/bbd44f0))
- **release**: V1.0.0-beta.21 [skip ci] ([ca642c2](https://github.com/perfect-panel/ppanel-web/commit/ca642c2))
- **release**: V1.0.0-beta.22 [skip ci] ([c0fb34f](https://github.com/perfect-panel/ppanel-web/commit/c0fb34f))
- **release**: V1.0.0-beta.23 [skip ci] ([cf1d66d](https://github.com/perfect-panel/ppanel-web/commit/cf1d66d))
- **release**: V1.0.0-beta.24 [skip ci] ([01a3aa0](https://github.com/perfect-panel/ppanel-web/commit/01a3aa0))
- **release**: V1.0.0-beta.25 [skip ci] ([047a698](https://github.com/perfect-panel/ppanel-web/commit/047a698))
- **release**: V1.0.0-beta.26 [skip ci] ([79edea7](https://github.com/perfect-panel/ppanel-web/commit/79edea7))
- **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
- **release**: V1.0.0-beta.27 [skip ci] ([85fdc36](https://github.com/perfect-panel/ppanel-web/commit/85fdc36))
- **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
- **release**: V1.0.0-beta.28 [skip ci] ([d10ecc9](https://github.com/perfect-panel/ppanel-web/commit/d10ecc9))
- **release**: V1.0.0-beta.29 [skip ci] ([29bc3c7](https://github.com/perfect-panel/ppanel-web/commit/29bc3c7))
- **release**: V1.0.0-beta.3 [skip ci] ([cd49427](https://github.com/perfect-panel/ppanel-web/commit/cd49427))
- **release**: V1.0.0-beta.30 [skip ci] ([db0d9e0](https://github.com/perfect-panel/ppanel-web/commit/db0d9e0))
- **release**: V1.0.0-beta.31 [skip ci] ([aa1d426](https://github.com/perfect-panel/ppanel-web/commit/aa1d426))
- **release**: V1.0.0-beta.32 [skip ci] ([fa56eb8](https://github.com/perfect-panel/ppanel-web/commit/fa56eb8))
- **release**: V1.0.0-beta.33 [skip ci] ([383638f](https://github.com/perfect-panel/ppanel-web/commit/383638f))
- **release**: V1.0.0-beta.34 [skip ci] ([7023875](https://github.com/perfect-panel/ppanel-web/commit/7023875))
- **release**: V1.0.0-beta.4 [skip ci] ([10d322f](https://github.com/perfect-panel/ppanel-web/commit/10d322f))
- **release**: V1.0.0-beta.5 [skip ci] ([f275f01](https://github.com/perfect-panel/ppanel-web/commit/f275f01))
- **release**: V1.0.0-beta.6 [skip ci] ([dd23279](https://github.com/perfect-panel/ppanel-web/commit/dd23279))
- **release**: V1.0.0-beta.7 [skip ci] ([f60d40c](https://github.com/perfect-panel/ppanel-web/commit/f60d40c))
- **release**: V1.0.0-beta.8 [skip ci], closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([a593eac](https://github.com/perfect-panel/ppanel-web/commit/a593eac))
- **release**: V1.0.0-beta.9 [skip ci] ([855d1b0](https://github.com/perfect-panel/ppanel-web/commit/855d1b0))
- **ui**: Update package dependencies for improved stability and performance ([25da429](https://github.com/perfect-panel/ppanel-web/commit/25da429))
- Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))
- Update changelog, enhance prepare script, and add openapi command ([a93db4e](https://github.com/perfect-panel/ppanel-web/commit/a93db4e))
### 🐛 Bug Fixes
- **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
- **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
- **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
- **api**: Fix type error in API request and add return URL parameter ([ee286dd](https://github.com/perfect-panel/ppanel-web/commit/ee286dd))
- **api**: PreCreateOrder ([ca747f5](https://github.com/perfect-panel/ppanel-web/commit/ca747f5))
- **api**: Purge ([98c1c30](https://github.com/perfect-panel/ppanel-web/commit/98c1c30))
- **api**: Remove redundant requestType parameter in appleLoginCallback ([0aa5d5b](https://github.com/perfect-panel/ppanel-web/commit/0aa5d5b))
- **api**: Rename app-related functions and types to application for consistency ([9d8b814](https://github.com/perfect-panel/ppanel-web/commit/9d8b814))
- **api**: Replace 'deduction' with 'gift_amount' and add 'commission' field in type definitions ([77edf1d](https://github.com/perfect-panel/ppanel-web/commit/77edf1d))
- **api**: Server and order ([255bd82](https://github.com/perfect-panel/ppanel-web/commit/255bd82))
- **api**: Statistics ([7962162](https://github.com/perfect-panel/ppanel-web/commit/7962162))
- **api**: Subscribe token ([1932ba7](https://github.com/perfect-panel/ppanel-web/commit/1932ba7))
- **api**: Update API type definitions to replace 'deduction' with 'gift_amount' and make 'commission' field optional ([c2af060](https://github.com/perfect-panel/ppanel-web/commit/c2af060))
- **api**: Update Model ([39aaa73](https://github.com/perfect-panel/ppanel-web/commit/39aaa73))
- **api**: Update subscription_protocol to subscribe_type for consistency across services ([b6da51b](https://github.com/perfect-panel/ppanel-web/commit/b6da51b))
- **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**: Add error handling to form submission and reset Turnstile on failure ([715d011](https://github.com/perfect-panel/ppanel-web/commit/715d011))
- **auth**: Change Textarea value to defaultValue for client_secret in Apple auth page ([69fc670](https://github.com/perfect-panel/ppanel-web/commit/69fc670))
- **auth**: Refactor forms to use Turnstile ref for reset functionality ([320a7dc](https://github.com/perfect-panel/ppanel-web/commit/320a7dc))
- **auth**: Refactor reset password form to simplify code input and update placeholder text ([23833b4](https://github.com/perfect-panel/ppanel-web/commit/23833b4))
- **auth**: Refactor user authentication forms to remove global store dependency and improve type handling ([12026b0](https://github.com/perfect-panel/ppanel-web/commit/12026b0))
- **auth**: Remove unused telephone code login function and update typings for telephone login requests ([7239685](https://github.com/perfect-panel/ppanel-web/commit/7239685))
- **auth**: Require minimum length for invite string when forced invite is enabled ([a604f28](https://github.com/perfect-panel/ppanel-web/commit/a604f28))
- **auth**: Simplify email verification code input rendering ([6f7bc37](https://github.com/perfect-panel/ppanel-web/commit/6f7bc37))
- **auth**: Update authentication configuration and localization strings ([47f2c58](https://github.com/perfect-panel/ppanel-web/commit/47f2c58))
- **auth**: Update email verification logic to use domain suffix check ([62662bb](https://github.com/perfect-panel/ppanel-web/commit/62662bb))
- **auth**: Update user authentication flow to include email and phone code verification ([5d078fd](https://github.com/perfect-panel/ppanel-web/commit/5d078fd))
- **auth**: Update UserCheckForm to use setInitialValues and modify onSubmit type ([c984c0d](https://github.com/perfect-panel/ppanel-web/commit/c984c0d))
- **billing**: ExpiryDate ([e85e545](https://github.com/perfect-panel/ppanel-web/commit/e85e545))
- **billing**: I18n and styles ([81e0f21](https://github.com/perfect-panel/ppanel-web/commit/81e0f21))
- **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
- **config**: AlipayF2F ([6c07107](https://github.com/perfect-panel/ppanel-web/commit/6c07107))
- **config**: Bugs ([f57e40c](https://github.com/perfect-panel/ppanel-web/commit/f57e40c))
- **config**: Checkout Order ([a31e763](https://github.com/perfect-panel/ppanel-web/commit/a31e763))
- **config**: FormatBytes ([bbc2da0](https://github.com/perfect-panel/ppanel-web/commit/bbc2da0))
- **config**: NoStore ([2cc18cf](https://github.com/perfect-panel/ppanel-web/commit/2cc18cf))
- **config**: Runtime env ([a1e4999](https://github.com/perfect-panel/ppanel-web/commit/a1e4999))
- **config**: Status Percentag ([8f322fb](https://github.com/perfect-panel/ppanel-web/commit/8f322fb))
- **config**: SubLink ([1c61966](https://github.com/perfect-panel/ppanel-web/commit/1c61966))
- **config**: Subscribe Link ([11ea821](https://github.com/perfect-panel/ppanel-web/commit/11ea821))
- **content**: Parse subscription description and display features with icons ([3c5542a](https://github.com/perfect-panel/ppanel-web/commit/3c5542a))
- **controller**: Order status ([8c6a097](https://github.com/perfect-panel/ppanel-web/commit/8c6a097))
- **coupon**: Rename 'server' field to 'subscribe' in coupon form and update coupon update request type ([f8b6d82](https://github.com/perfect-panel/ppanel-web/commit/f8b6d82))
- **dashboard**: Improve URL encoding for subscription links and enhance success message handling ([4983c33](https://github.com/perfect-panel/ppanel-web/commit/4983c33))
- **dashboard**: Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
- **dashboard**: Correct progress value calculations and update groupId accessor ([36c7667](https://github.com/perfect-panel/ppanel-web/commit/36c7667))
- **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
- **dashboard**: Format Bytes ([d8b0bd9](https://github.com/perfect-panel/ppanel-web/commit/d8b0bd9))
- **dashboard**: Update icon imports for platform consistency and adjust icon size ([3e8912e](https://github.com/perfect-panel/ppanel-web/commit/3e8912e))
- **dashboard**: Update platform detection logic and improve layout responsiveness ([b0aa364](https://github.com/perfect-panel/ppanel-web/commit/b0aa364))
- **deps**: Remove outdated @iconify/react dependency and add iconify-json packages ([d6fbc38](https://github.com/perfect-panel/ppanel-web/commit/d6fbc38))
- **deps**: Typescript config ([34e24b8](https://github.com/perfect-panel/ppanel-web/commit/34e24b8))
- **deps**: Update clipboard ([5572710](https://github.com/perfect-panel/ppanel-web/commit/5572710))
- **editor**: Change value ([4fdfeb2](https://github.com/perfect-panel/ppanel-web/commit/4fdfeb2))
- **email**: Update platform configuration handling to use current ref for consistency ([c90175b](https://github.com/perfect-panel/ppanel-web/commit/c90175b))
- **footer**: Email address ([a451f44](https://github.com/perfect-panel/ppanel-web/commit/a451f44))
- **forms**: Add step attribute to number inputs for better value control ([b8f4f1e](https://github.com/perfect-panel/ppanel-web/commit/b8f4f1e))
- **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
- **layout**: Remove unnecessary cookie initialization in Logout function ([3065c3a](https://github.com/perfect-panel/ppanel-web/commit/3065c3a))
- **locale**: Default value ([937408f](https://github.com/perfect-panel/ppanel-web/commit/937408f))
- **locale**: Document ([6f0fa20](https://github.com/perfect-panel/ppanel-web/commit/6f0fa20))
- **locale**: Empty ([3832d20](https://github.com/perfect-panel/ppanel-web/commit/3832d20))
- **locale**: Input Placeholder Webhook Domain ([bca0935](https://github.com/perfect-panel/ppanel-web/commit/bca0935))
- **locale**: Language Select ([0befdb0](https://github.com/perfect-panel/ppanel-web/commit/0befdb0))
- **locales**: Add error message for incorrect user information ([52c1d1f](https://github.com/perfect-panel/ppanel-web/commit/52c1d1f))
- **locales**: Add error message for incorrect user information ([3d92902](https://github.com/perfect-panel/ppanel-web/commit/3d92902))
- **locales**: Add logout message to authentication localization files ([1d0d911](https://github.com/perfect-panel/ppanel-web/commit/1d0d911))
- **locales**: Fixed description in multilingual files, updated text related to email registration functionality ([c356bc2](https://github.com/perfect-panel/ppanel-web/commit/c356bc2))
- **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
- **locales**: Removed language file import to clean up unnecessary language support ([68f6ab2](https://github.com/perfect-panel/ppanel-web/commit/68f6ab2))
- **locales**: Removed multilingual files to clean up unnecessary language support ([5b151cd](https://github.com/perfect-panel/ppanel-web/commit/5b151cd))
- **locale**: Subscription Path Description ([4c67387](https://github.com/perfect-panel/ppanel-web/commit/4c67387))
- **locales**: Update custom HTML description for clarity across multiple languages ([557c5cd](https://github.com/perfect-panel/ppanel-web/commit/557c5cd))
- **locales**: Update custom HTML description in language file, ([87381da](https://github.com/perfect-panel/ppanel-web/commit/87381da))
- **locales**: Update expiration time description from minutes to seconds in multiple languages ([5bac933](https://github.com/perfect-panel/ppanel-web/commit/5bac933))
- **locales**: Update Hong Kong ([6d0d069](https://github.com/perfect-panel/ppanel-web/commit/6d0d069))
- **locales**: Update invite code text to indicate it's optional ([6a34bfb](https://github.com/perfect-panel/ppanel-web/commit/6a34bfb))
- **logs**: Update log display to render key-value pairs and remove badge ([5ea6489](https://github.com/perfect-panel/ppanel-web/commit/5ea6489))
- **metadata**: Global metadata ([15d5ecf](https://github.com/perfect-panel/ppanel-web/commit/15d5ecf))
- **nav**: Comment out unused social login options to simplify navigation configuration ([cefcb31](https://github.com/perfect-panel/ppanel-web/commit/cefcb31))
- **node-config**: Add null checks for time slots and ensure proper handling of undefined values ([1cdb7e7](https://github.com/perfect-panel/ppanel-web/commit/1cdb7e7))
- **node**: Add country and city fields to the form schema and localization files ([8775fb6](https://github.com/perfect-panel/ppanel-web/commit/8775fb6))
- **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
- **node**: Locale and form ([38be4d5](https://github.com/perfect-panel/ppanel-web/commit/38be4d5))
- **node**: Port config ([a20834a](https://github.com/perfect-panel/ppanel-web/commit/a20834a))
- **node**: Reality config ([fadd17f](https://github.com/perfect-panel/ppanel-web/commit/fadd17f))
- **node**: Service Name config ([d0be685](https://github.com/perfect-panel/ppanel-web/commit/d0be685))
- **node**: TLS config ([57fae12](https://github.com/perfect-panel/ppanel-web/commit/57fae12))
- **node**: Trojan protocol config ([7e1eb90](https://github.com/perfect-panel/ppanel-web/commit/7e1eb90))
- **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))
- **oauth**: Refactor OAuth configuration types and update related API methods ([6227ba9](https://github.com/perfect-panel/ppanel-web/commit/6227ba9))
- **oauth**: Remove redundant checks when updating configuration to simplify logic ([9140b8a](https://github.com/perfect-panel/ppanel-web/commit/9140b8a))
- **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
- **payment**: Config and types ([b0c87fb](https://github.com/perfect-panel/ppanel-web/commit/b0c87fb))
- **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
- **payment**: Qrcode ([a9a535b](https://github.com/perfect-panel/ppanel-web/commit/a9a535b))
- **payment**: Refactor payment form placeholder and update localization files ([4a4d364](https://github.com/perfect-panel/ppanel-web/commit/4a4d364))
- **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
- **payment**: Replace window.open with window.location.href for checkout links ([1d8c765](https://github.com/perfect-panel/ppanel-web/commit/1d8c765))
- **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))
- **phone**: Update SMS expiration time field to use 'sms_expire_time' with default value of 300 ([18b07c7](https://github.com/perfect-panel/ppanel-web/commit/18b07c7))
- **profile**: Restore filter to ensure only valid OAuth accounts are shown ([315c8f9](https://github.com/perfect-panel/ppanel-web/commit/315c8f9))
- **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
- **redirect**: Simplify redirect URL logic by removing unnecessary condition for sessionStorage ([c53ac61](https://github.com/perfect-panel/ppanel-web/commit/c53ac61))
- **redirect**: Update redirect URL logic to ensure proper handling of OAuth and auth paths ([7954762](https://github.com/perfect-panel/ppanel-web/commit/7954762))
- **register**: Adjust user email verification logic to handle domain suffix checks correctly ([686aa2d](https://github.com/perfect-panel/ppanel-web/commit/686aa2d))
- **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
- **request**: Locale ([37d408f](https://github.com/perfect-panel/ppanel-web/commit/37d408f))
- **rule-form**: Remove redundant rule set display ([6e0c9b6](https://github.com/perfect-panel/ppanel-web/commit/6e0c9b6))
- **rules**: Remove unused MATCH rule ([674a01c](https://github.com/perfect-panel/ppanel-web/commit/674a01c))
- **site**: Add image upload functionality for site logo configuration ([4ea6e4a](https://github.com/perfect-panel/ppanel-web/commit/4ea6e4a))
- **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
- **sort**: Refactor sorting logic in NodeTable and SubscribeTable components for improved clarity and performance ([331bbea](https://github.com/perfect-panel/ppanel-web/commit/331bbea))
- **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
- **subscribe**: Discount ([35a9f69](https://github.com/perfect-panel/ppanel-web/commit/35a9f69))
- **subscribe**: Extract Domain ([40d61a9](https://github.com/perfect-panel/ppanel-web/commit/40d61a9))
- **subscribe**: Handle optional values in price and discount calculations ([5939763](https://github.com/perfect-panel/ppanel-web/commit/5939763))
- **subscribe**: Jumps and internationalization ([13fdec3](https://github.com/perfect-panel/ppanel-web/commit/13fdec3))
- **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
- **subscribe**: Server group id ([90e6764](https://github.com/perfect-panel/ppanel-web/commit/90e6764))
- **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
- **subscribe**: Update forms to include refetch functionality and improve toast messages ([fc55e95](https://github.com/perfect-panel/ppanel-web/commit/fc55e95))
- **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
- **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))
- **subscribe**: Update value validation to check for number type in subscribe form ([6de29d5](https://github.com/perfect-panel/ppanel-web/commit/6de29d5))
- **subscription**: Add reset functionality for user subscription token ([39e89bf](https://github.com/perfect-panel/ppanel-web/commit/39e89bf))
- **table**: Update privacy policy tab translation key and remove unnecessary requestType from OAuth callback ([14b3af5](https://github.com/perfect-panel/ppanel-web/commit/14b3af5))
- **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))
- **turnstile**: Turnstile_site_key ([0327b73](https://github.com/perfect-panel/ppanel-web/commit/0327b73))
- **type**: Fix ts type check error ([3cb0629](https://github.com/perfect-panel/ppanel-web/commit/3cb0629))
- **types**: Add 'gift_amount' field to API type definitions ([8f8a12a](https://github.com/perfect-panel/ppanel-web/commit/8f8a12a))
- **types**: Checking ([2992824](https://github.com/perfect-panel/ppanel-web/commit/2992824))
- **types**: Order type ([c7e50a9](https://github.com/perfect-panel/ppanel-web/commit/c7e50a9))
- **ui**: Bugs ([b023d0f](https://github.com/perfect-panel/ppanel-web/commit/b023d0f))
- **ui**: Components ([a7927d7](https://github.com/perfect-panel/ppanel-web/commit/a7927d7))
- **ui**: Fix json formatting ([e1ddd94](https://github.com/perfect-panel/ppanel-web/commit/e1ddd94))
- **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))
- **user-nav**: Update user avatar and label to display telephone if email is not available ([7b6bb7b](https://github.com/perfect-panel/ppanel-web/commit/7b6bb7b))
- **user**: Add the 'gift_amount' field to the user service's type definition ([6301409](https://github.com/perfect-panel/ppanel-web/commit/6301409))
- **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))
- **user**: Update user identifier field and localizations ([1b6befa](https://github.com/perfect-panel/ppanel-web/commit/1b6befa))
- **user**: Update user subscribe display ([3bb714d](https://github.com/perfect-panel/ppanel-web/commit/3bb714d))
- **utils**: Login redirect url ([cbe5f0d](https://github.com/perfect-panel/ppanel-web/commit/cbe5f0d))
- More bugs ([2d88a3a](https://github.com/perfect-panel/ppanel-web/commit/2d88a3a))
### 👷 Build System
- **config**: Update pm2 config ([d95b425](https://github.com/perfect-panel/ppanel-web/commit/d95b425))
### 💄 Styles
- **dashboard**: Adjust grid layout and update image dimensions in application display ([f3204b7](https://github.com/perfect-panel/ppanel-web/commit/f3204b7))
- **dashboard**: Enhance card components with full height and improved empty state handling ([7e1d551](https://github.com/perfect-panel/ppanel-web/commit/7e1d551))
- **document**: Update ([0a8109b](https://github.com/perfect-panel/ppanel-web/commit/0a8109b))
- **globals**: Refactor delete confirmation button and update badge styles in node and subscribe tables ([30ae781](https://github.com/perfect-panel/ppanel-web/commit/30ae781))
- **locales**: Remove unused subscription labels from multiple locale files ([fb0c510](https://github.com/perfect-panel/ppanel-web/commit/fb0c510))
- **locales**: Update server.json to reorganize relay mode options and improve labels ([701cdee](https://github.com/perfect-panel/ppanel-web/commit/701cdee))
- **node**: Form ([d5f5add](https://github.com/perfect-panel/ppanel-web/commit/d5f5add))
- **node**: Improve layout and spacing in NodeStatusCell component ([136287d](https://github.com/perfect-panel/ppanel-web/commit/136287d))
- **node**: Protocol Tab ([2bcb925](https://github.com/perfect-panel/ppanel-web/commit/2bcb925))
- **time-slot**: Add chart display ([c44ad47](https://github.com/perfect-panel/ppanel-web/commit/c44ad47))
- **ui**: Update mobile style ([eda18bc](https://github.com/perfect-panel/ppanel-web/commit/eda18bc))
- Update node secret UI and add telephone code field to authentication form ([770932e](https://github.com/perfect-panel/ppanel-web/commit/770932e))
### 📝 Documentation
- **readme**: License name ([74cb16b](https://github.com/perfect-panel/ppanel-web/commit/74cb16b))
### 🔧 Continuous Integration
- **github**: Release docker ([5af60aa](https://github.com/perfect-panel/ppanel-web/commit/5af60aa))
- **step**: Update step name ([9eca618](https://github.com/perfect-panel/ppanel-web/commit/9eca618))
<a name="readme-top"></a>
# Changelog
# [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02)
### ✨ Features
- **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
- **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
- **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
### 🐛 Bug Fixes
- **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
- **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
- **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
- **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
- **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
- **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
<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))
- **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>
+13 -4
View File
@@ -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>
+24 -10
View File
@@ -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>
+25 -18
View File
@@ -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>
+23 -9
View File
@@ -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;
+299
View File
@@ -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>
);
}
+162
View File
@@ -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')}
/>,
],
}}
/>
);
}
@@ -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',
@@ -231,16 +232,6 @@ export default function SubscribeAppForm<
name: 'is_default',
type: 'boolean',
placeholder: t('defaultVersion'),
calculateValue: (value) => {
const newField = field.value?.map((item) => {
if (item.is_default) {
item.is_default = false;
}
return item;
});
form.setValue(field.name, newField);
return value;
},
},
{
name: 'url',
@@ -251,7 +242,32 @@ export default function SubscribeAppForm<
]}
value={field.value}
onChange={(value) => {
form.setValue(field.name, value);
const filteredValue = value.filter((item) => item.url);
const newDefaultIndex = value.findIndex(
(item, idx) =>
item.is_default &&
(!field.value?.[idx] || !field.value[idx].is_default),
);
let finalValue = filteredValue;
if (newDefaultIndex >= 0 && filteredValue[newDefaultIndex]) {
finalValue = filteredValue.map((item, index) => ({
...item,
is_default: index === newDefaultIndex,
}));
} else if (
!filteredValue.some((item) => item.is_default) &&
filteredValue.length > 0
) {
finalValue = filteredValue.map((item, index) => ({
...item,
is_default: index === 0,
}));
}
form.setValue(field.name, finalValue as any);
}}
/>
</FormControl>
@@ -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() {
@@ -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>
);
@@ -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>
@@ -136,14 +136,14 @@ export function Register() {
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<TableRow className='flex flex-col'>
<TableCell className='w-full'>
<Label>{t('trialSubscribePlan')}</Label>
<p className='text-muted-foreground text-xs'>
{t('trialSubscribePlanDescription')}
</p>
</TableCell>
<TableCell className='text-right'>
<TableCell className='max-w-96'>
<EnhancedInput
placeholder={t('trialDuration')}
type='number'
@@ -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);
+1 -1
View File
@@ -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>
);
}
-221
View File
@@ -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>
);
}
+3 -26
View File
@@ -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,420 @@
'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='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]
: ''
}
disabled={fieldKey === 'webhook_secret'}
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>
);
}
@@ -0,0 +1,215 @@
'use client';
import { createRuleGroup } from '@/services/admin/server';
import { Button } from '@workspace/ui/components/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@workspace/ui/components/dialog';
import { Label } from '@workspace/ui/components/label';
import { Progress } from '@workspace/ui/components/progress';
import { Textarea } from '@workspace/ui/components/textarea';
import { Icon } from '@workspace/ui/custom-components/icon';
import yaml from 'js-yaml';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
import { toast } from 'sonner';
interface ImportYamlRulesProps {
onImportSuccess?: () => void;
}
interface RuleGroup {
name: string;
rules: string[];
}
export default function ImportYamlRules({ onImportSuccess }: ImportYamlRulesProps) {
const t = useTranslations('rules');
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [yamlContent, setYamlContent] = useState('');
const [importProgress, setImportProgress] = useState(0);
const [importTotal, setImportTotal] = useState(0);
const [analyzing, setAnalyzing] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const content = event.target?.result as string;
setYamlContent(content);
setOpen(true);
};
reader.readAsText(file);
e.target.value = '';
};
const processRule = (rule: string): { policyGroup: string; cleanRule: string } | null => {
const parts = rule.split(',');
if (parts.length === 1) {
return null;
}
let policyGroup = 'default';
let cleanRule = rule;
if (parts.length >= 3) {
const thirdPart = parts[2]?.trim();
if (thirdPart) {
policyGroup = thirdPart;
}
}
cleanRule = parts.slice(0, 2).join(',');
return { policyGroup, cleanRule };
};
const parseRulesIntoGroups = (rules: string[]): Record<string, string[]> => {
const groups: Record<string, string[]> = {};
for (const rule of rules) {
if (!rule.trim()) continue;
const result = processRule(rule);
if (result === null) continue;
const { policyGroup, cleanRule } = result;
if (!groups[policyGroup]) {
groups[policyGroup] = [];
}
groups[policyGroup].push(cleanRule);
}
return groups;
};
const handleImport = async () => {
if (!yamlContent) {
toast.error(t('pleaseUploadFile'));
return;
}
setLoading(true);
setAnalyzing(true);
try {
const parsedYaml = yaml.load(yamlContent) as any;
if (!parsedYaml || !parsedYaml.rules) {
throw new Error(t('invalidYamlFormat'));
}
let allRules: string[] = [];
if (Array.isArray(parsedYaml.rules)) {
allRules = parsedYaml.rules.filter((rule: string) => rule.trim());
}
if (allRules.length === 0) {
throw new Error(t('noValidRules'));
}
const ruleGroups = parseRulesIntoGroups(allRules);
const groups = Object.entries(ruleGroups).map(([name, rules]) => ({
name,
rules,
}));
setImportTotal(groups.length);
setAnalyzing(false);
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
if (!group?.name || !group?.rules.length) continue;
await createRuleGroup({
name: group.name,
rules: group?.rules.join('\n'),
enable: false,
tags: [],
icon: '',
});
setImportProgress(i + 1);
}
toast.success(t('importSuccess'));
setOpen(false);
setYamlContent('');
setImportProgress(0);
setImportTotal(0);
onImportSuccess?.();
} catch (error) {
console.error('Import error:', error);
toast.error(error instanceof Error ? error.message : t('importFailed'));
} finally {
setLoading(false);
setAnalyzing(false);
}
};
return (
<>
<input
ref={fileInputRef}
type='file'
accept='.yml,.yaml'
style={{ display: 'none' }}
onChange={handleFileUpload}
/>
<Button variant='default' onClick={() => fileInputRef.current?.click()}>
{t('import')}
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='sm:max-w-[500px]'>
<DialogHeader>
<DialogTitle>{t('importYamlRules')}</DialogTitle>
<DialogDescription>{t('importYamlDescription')}</DialogDescription>
</DialogHeader>
<div className='grid gap-4 py-4'>
{yamlContent && (
<div className='grid gap-2'>
<Label htmlFor='preview'>{t('preview')}</Label>
<Textarea
id='preview'
value={yamlContent}
readOnly
rows={10}
className='font-mono text-xs'
/>
</div>
)}
{importTotal > 0 && (
<div className='grid gap-2'>
<div className='flex justify-between text-sm'>
<span>{analyzing ? t('analyzing') : t('importing')}</span>
<span>
{importProgress} / {importTotal}
</span>
</div>
<Progress value={(importProgress / importTotal) * 100} />
</div>
)}
</div>
<DialogFooter>
<Button variant='outline' onClick={() => setOpen(false)} disabled={loading}>
{t('cancel')}
</Button>
<Button onClick={handleImport} disabled={loading || !yamlContent}>
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
{t('import')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}
+212
View File
@@ -0,0 +1,212 @@
'use client';
import { ProTable, ProTableActions } from '@/components/pro-table';
import {
createRuleGroup,
deleteRuleGroup,
getRuleGroupList,
updateRuleGroup,
} from '@/services/admin/server';
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 Image from 'next/legacy/image';
import Link from 'next/link';
import { useRef, useState } from 'react';
import { toast } from 'sonner';
import ImportYamlRules from './import-yaml-rules';
import RuleForm from './rule-form';
export default function Page() {
const t = useTranslations('rules');
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
return (
<ProTable<API.ServerRuleGroup, { query: string }>
action={ref}
header={{
toolbar: (
<div className='flex gap-2'>
<Button variant='default' asChild>
<Link href='/template/rules.yml' target='_blank' download>
{t('downloadTemplate')}
</Link>
</Button>
<ImportYamlRules onImportSuccess={() => ref.current?.refresh()} />
<RuleForm<API.CreateRuleGroupRequest>
trigger={t('create')}
title={t('createRule')}
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await createRuleGroup({
name: values.name,
rules: values.rules || '',
enable: false,
tags: values.tags || [],
icon: values.icon || '',
});
toast.success(t('createSuccess'));
ref.current?.refresh();
setLoading(false);
return true;
} catch (error) {
setLoading(false);
return false;
}
}}
/>
</div>
),
}}
params={[
{
key: 'search',
placeholder: t('searchRule'),
},
]}
request={async (pagination, filters) => {
const { data } = await getRuleGroupList({
...pagination,
...filters,
});
return {
list: data.data?.list || [],
total: data.data?.total || 0,
};
}}
columns={[
{
accessorKey: 'enable',
header: t('enable'),
cell: ({ row }) => {
return (
<Switch
defaultChecked={row.getValue('enable')}
onCheckedChange={async (checked) => {
await updateRuleGroup({
...row.original,
enable: checked,
} as API.UpdateRuleGroupRequest);
ref.current?.refresh();
}}
/>
);
},
},
{
accessorKey: 'icon',
header: t('appIcon'),
cell: ({ row }) =>
row.getValue('icon') ? (
<Image
src={row.getValue('icon')}
alt={row.getValue('name')}
className='h-8 w-8 rounded-md'
width={32}
height={32}
/>
) : (
<div className='bg-muted flex h-8 w-8 items-center justify-center rounded-md'>
{row.original.name?.slice(0, 2)}
</div>
),
},
{
accessorKey: 'name',
header: t('name'),
},
{
accessorKey: 'tags',
header: t('tags'),
cell: ({ row }) => {
const tags = row.original.tags.filter((item) => item) || [];
if (!tags.length) return '--';
return (
<>
{tags.map((tag) => (
<Badge key={tag} variant='outline' className='mr-1'>
{tag}
</Badge>
))}
</>
);
},
},
{
accessorKey: 'created_at',
header: t('createdAt'),
cell: ({ row }) => formatDate(row.original.created_at),
},
]}
actions={{
render: (row) => [
<RuleForm<API.UpdateRuleGroupRequest>
key='edit'
trigger={t('edit')}
title={t('editRule')}
loading={loading}
initialValues={row}
onSubmit={async (values) => {
setLoading(true);
try {
await updateRuleGroup({
id: row.id,
name: values.name,
tags: values.tags,
rules: values.rules,
enable: row.enable,
icon: values.icon,
});
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 deleteRuleGroup({ id: row.id });
toast.success(t('deleteSuccess'));
ref.current?.refresh();
}}
cancelText={t('cancel')}
confirmText={t('confirm')}
/>,
],
batchRender: (rows) => [
<ConfirmButton
key='delete'
trigger={<Button variant='destructive'>{t('delete')}</Button>}
title={t('confirmDelete')}
description={t('deleteWarning')}
onConfirm={async () => {
for (const row of rows) {
await deleteRuleGroup({ id: row.id });
}
toast.success(t('deleteSuccess'));
ref.current?.reset();
ref.current?.refresh();
}}
cancelText={t('cancel')}
confirmText={t('confirm')}
/>,
],
}}
/>
);
}
@@ -0,0 +1,231 @@
'use client';
import { getNodeTagList } from '@/services/admin/server';
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 { ScrollArea } from '@workspace/ui/components/scroll-area';
import {
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import { Textarea } from '@workspace/ui/components/textarea';
import { Combobox } from '@workspace/ui/custom-components/combobox';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon';
import { UploadImage } from '@workspace/ui/custom-components/upload-image';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const formSchema = z.object({
name: z.string().min(1, { message: '请输入规则名称' }),
tags: z.array(z.string()).default([]),
rules: z.string().default(''),
icon: z.string().default(''),
});
interface RuleFormProps<T> {
onSubmit: (data: T) => Promise<boolean> | boolean;
initialValues?: T;
loading?: boolean;
trigger: string;
title: string;
}
export default function RuleForm<T extends Record<string, any>>({
onSubmit,
initialValues,
loading,
trigger,
title,
}: RuleFormProps<T>) {
const t = useTranslations('rules');
const [open, setOpen] = useState(false);
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
...initialValues,
} as any,
});
useEffect(() => {
if (initialValues) {
form.reset(initialValues);
}
}, [form, initialValues]);
async function handleSubmit(data: { [x: string]: any }) {
const bool = await onSubmit(data as T);
if (bool) setOpen(false);
}
const { data: tags } = useQuery({
queryKey: ['getNodeTagList'],
queryFn: async () => {
const { data } = await getNodeTagList();
return data.data?.tags || [];
},
});
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='icon'
render={({ field }) => (
<FormItem>
<FormLabel>{t('appIcon')}</FormLabel>
<FormControl>
<EnhancedInput
placeholder={t('enterIconUrl')}
value={field.value}
suffix={
<UploadImage
className='bg-muted h-9 rounded-none border-none px-2'
onChange={(value) => {
form.setValue(field.name, value as string);
}}
/>
}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<EnhancedInput
placeholder={t('enterRuleName')}
value={field.value}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='tags'
render={({ field }) => (
<FormItem>
<FormLabel>{t('tagsLabel')}</FormLabel>
<FormControl>
<Combobox<string, true>
multiple
placeholder={t('selectTags')}
value={field.value}
onChange={(value) => {
form.setValue(field.name, value);
}}
options={tags?.map((item: string) => ({
value: item,
label: item,
}))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='rules'
render={({ field }) => (
<FormItem>
<FormLabel>{t('rulesLabel')}</FormLabel>
<FormControl>
<Textarea
placeholder={t('enterRules')}
value={field.value}
rows={10}
onChange={(e) => {
form.setValue(field.name, e.target.value);
}}
/>
</FormControl>
<div className='text-muted-foreground mt-1 text-xs'>
<pre>{t('rulesFormat')}</pre>
<div className='border-muted mt-2 space-y-1 border-l-2 pl-2'>
<p className='font-mono'>DOMAIN,example.com</p>
<p className='font-mono'>DOMAIN-SUFFIX,google.com,DIRECT</p>
<p className='font-mono'>DOMAIN-KEYWORD,amazon,REJECT</p>
<p className='font-mono'>IP-CIDR,192.168.0.0/16</p>
<p className='font-mono'>IP-CIDR6,2001:db8::/32,REJECT</p>
<p className='font-mono'>SRC-IP-CIDR,192.168.1.201/32</p>
<p className='font-mono'>GEOIP,CN,DIRECT</p>
<p className='font-mono'>GEOIP,US</p>
<p className='font-mono'>DST-PORT,80,DIRECT</p>
<p className='font-mono'>SRC-PORT,7777,REJECT</p>
<p className='font-mono'>PROCESS-NAME,telegram</p>
</div>
</div>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</ScrollArea>
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
<Button
variant='outline'
disabled={loading}
onClick={() => {
setOpen(false);
}}
>
{t('cancel')}
</Button>
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
{t('confirm')}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
+14 -2
View File
@@ -1,6 +1,6 @@
import { z } from 'zod';
export const protocols = ['shadowsocks', 'vmess', 'vless', 'trojan', 'hysteria2', 'tuic'];
export const protocols = ['shadowsocks', 'vmess', 'vless', 'trojan', 'hysteria2', 'tuic', 'anytls'];
const nullableString = z.string().nullish();
const portSchema = z.number().max(65535).nullish();
@@ -58,7 +58,15 @@ const hysteria2Schema = z.object({
const tuicSchema = z.object({
port: portSchema,
security: z.string(),
disable_sni: z.boolean().default(false),
reduce_rtt: z.boolean().default(false),
udp_relay_mode: z.string().default('native'),
congestion_controller: z.string().default('bbr'),
security_config: securityConfigSchema,
});
const anytlsSchema = z.object({
port: portSchema,
security_config: securityConfigSchema,
});
@@ -87,6 +95,10 @@ const protocolConfigSchema = z.discriminatedUnion('protocol', [
protocol: z.literal('tuic'),
config: tuicSchema,
}),
z.object({
protocol: z.literal('anytls'),
config: anytlsSchema,
}),
]);
const baseFormSchema = z.object({
+133 -11
View File
@@ -110,7 +110,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
{trigger}
</Button>
</SheetTrigger>
<SheetContent className='w-[520px] max-w-full md:max-w-screen-md'>
<SheetContent className='w-[580px] max-w-full md:max-w-screen-md'>
<SheetHeader>
<SheetTitle>{title}</SheetTitle>
</SheetHeader>
@@ -290,7 +290,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
value={field.value}
onValueChange={(value) => {
form.setValue(field.name, value);
if (['trojan', 'hysteria2', 'tuic'].includes(value)) {
if (['trojan', 'hysteria2', 'tuic', 'anytls'].includes(value)) {
form.setValue('config.security', 'tls');
}
}}
@@ -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);
}}
@@ -401,11 +402,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
</div>
)}
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic'].includes(protocol) && (
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic', 'anytls'].includes(protocol) && (
<div className='grid gap-4'>
<div
className={cn('flex gap-4 *:flex-1', {
'grid grid-cols-2': ['hysteria2'].includes(protocol),
'grid grid-cols-2': ['hysteria2', 'tuic'].includes(protocol),
})}
>
<FormField
@@ -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);
@@ -520,6 +524,109 @@ export default function NodeForm<T extends { [x: string]: any }>({
/>
</>
)}
{protocol === 'tuic' && (
<>
<FormField
control={form.control}
name='config.udp_relay_mode'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.udpRelayMode')}</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t('form.pleaseSelect')} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value='native'>Native</SelectItem>
<SelectItem value='quic'>QUIC</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='config.congestion_controller'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.congestionController')}</FormLabel>
<FormControl>
<Select
value={field.value}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t('form.pleaseSelect')} />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value='bbr'>BBR</SelectItem>
<SelectItem value='cubic'>Cubic</SelectItem>
<SelectItem value='reno'>Reno</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='flex gap-2'>
<FormField
control={form.control}
name='config.disable_sni'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.disableSni')}</FormLabel>
<FormControl>
<div className='pt-2'>
<Switch
checked={field.value}
onCheckedChange={(checked) => {
form.setValue(field.name, checked);
}}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='config.reduce_rtt'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.reduceRtt')}</FormLabel>
<FormControl>
<div className='pt-2'>
<Switch
checked={field.value}
onCheckedChange={(checked) => {
form.setValue(field.name, checked);
}}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</>
)}
</div>
{['vmess', 'vless', 'trojan'].includes(protocol) && (
<Card>
@@ -626,9 +733,12 @@ export default function NodeForm<T extends { [x: string]: any }>({
)}
</Card>
)}
{(['vmess', 'vless', 'trojan'].includes(protocol) ||
['anytls', 'tuic', 'hysteria2'].includes(protocol)) && (
<Card>
<CardHeader className='flex flex-row items-center justify-between p-3'>
<CardTitle>{t('form.securityConfig')}</CardTitle>
{['vmess', 'vless', 'trojan'].includes(protocol) && (
<FormField
control={form.control}
name='config.security'
@@ -659,8 +769,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
</FormItem>
)}
/>
)}
</CardHeader>
{security !== 'none' && (
{(['anytls', 'tuic', 'hysteria2'].includes(protocol) ||
(['vmess', 'vless', 'trojan'].includes(protocol) &&
security !== 'none')) && (
<CardContent className='grid grid-cols-2 gap-4 p-3'>
<FormField
control={form.control}
@@ -680,6 +793,8 @@ export default function NodeForm<T extends { [x: string]: any }>({
</FormItem>
)}
/>
{/* Reality 特殊配置只在 vless + reality 时显示 */}
{protocol === 'vless' && security === 'reality' && (
<>
<FormField
@@ -715,7 +830,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
type='number'
min={1}
max={65535}
placeholder={t('form.security_config.serverPortPlaceholder')}
placeholder={t(
'form.security_config.serverPortPlaceholder',
)}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
@@ -734,7 +851,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
<FormControl>
<EnhancedInput
{...field}
placeholder={t('form.security_config.privateKeyPlaceholder')}
placeholder={t(
'form.security_config.privateKeyPlaceholder',
)}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
@@ -784,6 +903,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
/>
</>
)}
{protocol === 'vless' && (
<FormField
control={form.control}
@@ -814,11 +934,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
</SelectContent>
</Select>
<FormMessage />
<FormMessage />
</FormItem>
)}
/>
)}
<FormField
control={form.control}
name='config.security_config.allow_insecure'
@@ -829,8 +949,8 @@ export default function NodeForm<T extends { [x: string]: any }>({
<div className='pt-2'>
<Switch
checked={!!field.value}
onCheckedChange={(value) => {
form.setValue(field.name, value);
onCheckedChange={(checked) => {
form.setValue(field.name, checked);
}}
/>
</div>
@@ -842,6 +962,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
</CardContent>
)}
</Card>
)}
</div>
)}
@@ -899,6 +1020,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
{
name: 'port',
type: 'number',
step: 1,
min: 1,
max: 65535,
placeholder: t('form.relayPort'),
+42 -29
View File
@@ -1,5 +1,11 @@
'use client';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@workspace/ui/components/accordion';
import { Badge } from '@workspace/ui/components/badge';
import { Progress } from '@workspace/ui/components/progress';
import { ScrollArea } from '@workspace/ui/components/scroll-area';
@@ -11,6 +17,8 @@ import {
} from '@workspace/ui/components/tooltip';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { UserSubscribeDetail } from '../user/user-detail';
export function formatPercentage(value: number): string {
return `${value.toFixed(1)}%`;
@@ -18,25 +26,19 @@ export function formatPercentage(value: number): string {
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
const t = useTranslations('server.node');
const [openItem, setOpenItem] = useState<string | null>(null);
const {
last_at,
online_users,
status: serverStatus,
} = status || {
online_users: [],
status: {
const { online, cpu, mem, disk, updated_at } = status || {
online: {},
cpu: 0,
mem: 0,
disk: 0,
updated_at: 0,
},
last_at: 0,
};
const isOnline = last_at > 0;
const isOnline = updated_at > 0;
const badgeVariant = isOnline ? 'default' : 'destructive';
const badgeText = isOnline ? t('normal') : t('abnormal');
const onlineCount = Array.isArray(online_users) ? online_users?.length : 0;
const onlineCount = (online && Object.keys(online).length) || 0;
return (
<TooltipProvider>
@@ -52,45 +54,56 @@ export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
<div className='flex flex-col space-y-1'>
<div className='flex justify-between'>
<span>CPU</span>
<span>{formatPercentage(serverStatus?.cpu ?? 0)}</span>
<span>{formatPercentage(cpu ?? 0)}</span>
</div>
<Progress value={serverStatus?.cpu ?? 0} className='h-2' max={100} />
<Progress value={cpu ?? 0} className='h-2' max={100} />
</div>
<div className='flex flex-col space-y-1'>
<div className='flex justify-between'>
<span>{t('memory')}</span>
<span>{formatPercentage(serverStatus?.mem ?? 0)}</span>
<span>{formatPercentage(mem ?? 0)}</span>
</div>
<Progress value={serverStatus?.mem ?? 0} className='h-2' max={100} />
<Progress value={mem ?? 0} className='h-2' max={100} />
</div>
<div className='flex flex-col space-y-1'>
<div className='flex justify-between'>
<span>{t('disk')}</span>
<span>{formatPercentage(serverStatus?.disk ?? 0)}</span>
<span>{formatPercentage(disk ?? 0)}</span>
</div>
<Progress value={serverStatus?.disk ?? 0} className='h-2' max={100} />
<Progress value={disk ?? 0} className='h-2' max={100} />
</div>
{isOnline && (
<div>
{t('lastUpdated')}: {formatDate(serverStatus?.updated_at ?? 0)}
{t('lastUpdated')}: {formatDate(updated_at ?? 0)}
</div>
)}
</div>
</TooltipTrigger>
{isOnline && onlineCount > 0 && (
<TooltipContent className='bg-muted text-foreground w-80'>
<div className='space-y-4'>
<div className='space-y-2'>
<h4 className='text-sm font-semibold'>{t('onlineUsers')}</h4>
<ScrollArea className='h-[400px] rounded-md border p-2'>
{online_users.map((user, index) => (
<div key={user.uid} className='py-1 text-xs'>
{user.ip} (UID: {user.uid})
</div>
<TooltipContent className='bg-card text-foreground w-96'>
<ScrollArea className='h-[540px] rounded-md border px-4 py-2'>
<h4 className='py-1 text-sm font-semibold'>{t('onlineUsers')}</h4>
<Accordion
type='single'
collapsible
className='w-full'
onValueChange={(value) => setOpenItem(value)}
>
{Object.entries(online).map(([uid, ips]) => (
<AccordionItem key={uid} value={uid}>
<AccordionTrigger>{`[UID: ${uid}] - ${ips[0]}`}</AccordionTrigger>
<AccordionContent>
<ul>
{ips.map((ip: string) => (
<li key={ip}>{ip}</li>
))}
</ul>
<UserSubscribeDetail id={Number(uid)} enabled={openItem === uid} />
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</ScrollArea>
</div>
</div>
</TooltipContent>
)}
</Tooltip>
@@ -87,6 +87,7 @@ export default function NodeTable() {
'bg-yellow-500': row.original.protocol === 'trojan',
'bg-purple-500': row.original.protocol === 'hysteria2',
'bg-cyan-500': row.original.protocol === 'tuic',
'bg-gray-500': row.original.protocol === 'anytls',
})}
>
{row.getValue('id')}
@@ -2,7 +2,6 @@ import { getTranslations } from 'next-intl/server';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import SubscribeApp from './app/table';
import GroupTable from './group/table';
import SubscribeConfig from './subscribe-config';
import SubscribeTable from './subscribe-table';
@@ -16,7 +15,6 @@ export default async function Page() {
<TabsTrigger value='subscribe'>{t('tabs.subscribe')}</TabsTrigger>
<TabsTrigger value='group'>{t('tabs.subscribeGroup')}</TabsTrigger>
<TabsTrigger value='config'>{t('tabs.subscribeConfig')}</TabsTrigger>
<TabsTrigger value='app'>{t('tabs.subscribeApp')}</TabsTrigger>
</TabsList>
<TabsContent value='subscribe'>
<SubscribeTable />
@@ -27,9 +25,6 @@ export default async function Page() {
<TabsContent value='config'>
<SubscribeConfig />
</TabsContent>
<TabsContent value='app'>
<SubscribeApp />
</TabsContent>
</Tabs>
);
}
@@ -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);
@@ -157,6 +157,32 @@ export default function SubscribeForm<T extends Record<string, any>>({
});
const unit_time = form.watch('unit_time');
const unit_price = form.watch('unit_price');
const discounts = form.watch('discount');
useEffect(() => {
if (!discounts?.length || !unit_price) return;
const calculatedValues = discounts.map((item: any) => {
const result = { ...item };
if (item.quantity && item.discount) {
result.price = evaluateWithPrecision(
`${unit_price || 0} * ${item.quantity} * ${item.discount} / 100`,
);
} else if (item.quantity && item.price && !item.discount) {
result.discount = evaluateWithPrecision(
`${item.price} / ${item.quantity} / ${unit_price} * 100`,
);
}
return result;
});
if (JSON.stringify(calculatedValues) !== JSON.stringify(discounts)) {
form.setValue('discount', calculatedValues);
}
}, [unit_price, discounts, form]);
return (
<Sheet open={open} onOpenChange={setOpen}>
@@ -298,6 +324,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 +348,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 +371,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);
@@ -540,11 +569,12 @@ export default function SubscribeForm<T extends Record<string, any>>({
<FormItem>
<FormLabel>{t('form.discount')}</FormLabel>
<FormControl>
<ArrayInput<API.SubscribeDiscount>
<ArrayInput<API.SubscribeDiscount & { price?: number }>
fields={[
{
name: 'quantity',
type: 'number',
step: 1,
min: 1,
suffix: unit_time && t(`form.${unit_time}`),
},
@@ -555,38 +585,56 @@ export default function SubscribeForm<T extends Record<string, any>>({
max: 100,
placeholder: t('form.discountPercent'),
suffix: '%',
calculateValue: function (data) {
const { unit_price } = form.getValues();
return {
...data,
price: evaluateWithPrecision(
`${unit_price} * ${data.quantity} * ${data.discount} / 100`,
),
};
},
},
{
name: 'price',
placeholder: t('form.discount_price'),
type: 'number',
min: 0,
formatInput: (value) => unitConversion('centsToDollars', value),
formatOutput: (value) => unitConversion('dollarsToCents', value),
internal: true,
calculateValue: (data) => {
const { unit_price } = form.getValues();
return {
...data,
discount: evaluateWithPrecision(
`${data.price} / ${data.quantity} / ${unit_price} * 100`,
),
};
},
},
]}
value={field.value}
onChange={(value) => {
form.setValue(field.name, value);
onChange={(newValues) => {
const oldValues = field.value || [];
const { unit_price } = form.getValues();
const calculatedValues = newValues.map((newItem, index) => {
const oldItem = oldValues[index] || {};
const result = { ...newItem };
const quantityChanged = newItem.quantity !== oldItem.quantity;
const discountChanged = newItem.discount !== oldItem.discount;
const priceChanged = newItem.price !== oldItem.price;
if (
(quantityChanged || discountChanged) &&
!priceChanged &&
newItem.quantity &&
newItem.discount
) {
result.price = evaluateWithPrecision(
`${unit_price || 0} * ${newItem.quantity} * ${newItem.discount} / 100`,
);
}
if (
priceChanged &&
!discountChanged &&
newItem.price &&
newItem.quantity &&
unit_price
) {
result.discount = evaluateWithPrecision(
`${newItem.price} / ${newItem.quantity} / ${unit_price} * 100`,
);
}
return result;
});
form.setValue(field.name, calculatedValues);
}}
/>
</FormControl>
+5
View File
@@ -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>
)
);
}
+78 -3
View File
@@ -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>
);
+20 -18
View File
@@ -10,7 +10,6 @@ import {
} from '@workspace/ui/components/accordion';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
@@ -19,7 +18,6 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from '@workspace/ui/components/alert-dialog';
import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button';
import {
Card,
@@ -67,7 +65,7 @@ export default function Page() {
<CardDescription>{t('viewLogsAndManage')}</CardDescription>
</div>
<div className='mt-4 flex flex-col space-y-2 sm:mt-0 sm:flex-row sm:space-x-2 sm:space-y-0'>
<AlertDialog>
{/* <AlertDialog>
<AlertDialogTrigger asChild>
<Button>{t('systemUpgrade')}</Button>
</AlertDialogTrigger>
@@ -81,7 +79,7 @@ export default function Page() {
<AlertDialogAction>{t('confirmUpgrade')}</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</AlertDialog> */}
<AlertDialog open={openRestart} onOpenChange={setOpenRestart}>
<AlertDialogTrigger asChild>
<Button variant='destructive'>{t('systemReboot')}</Button>
@@ -113,14 +111,14 @@ export default function Page() {
</CardHeader>
<CardContent>
<div className='space-y-6'>
<div className='flex items-center justify-between'>
{/* <div className='flex items-center justify-between'>
<div className='text-lg font-semibold'>
{t('currentVersion')} <span>V1.0.0</span>
</div>
<div className='text-muted-foreground text-sm'>
{t('lastUpdated')} <span>2024-12-16 12:00:00</span>
</div>
</div>
</div> */}
<Card className='overflow-hidden'>
<CardHeader className='bg-secondary py-1'>
<div className='flex items-center justify-between'>
@@ -146,20 +144,24 @@ export default function Page() {
<AccordionItem key={index} value={`item-${index}`} className='px-4'>
<AccordionTrigger className='hover:no-underline'>
<div className='flex w-full flex-col items-start space-y-2 sm:flex-row sm:items-center sm:space-x-4 sm:space-y-0'>
<Badge
variant='outline'
className={`${getLogLevelColor(log.level)} mb-2 sm:mb-0`}
>
{log.level}
</Badge>
<span className='text-xs font-medium sm:text-sm'>{log.time}</span>
<span className='text-muted-foreground flex-1 truncate text-xs sm:text-sm'>
{log.message}
</span>
<span className='text-xs font-medium sm:text-sm'>{log.timestamp}</span>
</div>
</AccordionTrigger>
<AccordionContent className='px-2'>
<div className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'>
{
// 直接渲染 key: value
Object.entries(log).map(([key, value]) => (
<div
key={key}
className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'
>
<span className='font-medium'>{key}:</span>
<span className='break-all'>{value as string}</span>
</div>
))
}
{/* <div className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'>
<div className='font-medium'>{t('ip')}:</div>
<div>{log.ip}</div>
<div className='font-medium'>{t('request')}:</div>
@@ -174,7 +176,7 @@ export default function Page() {
<div className='break-all'>{log.query || t('none')}</div>
<div className='font-medium'>{t('userAgent')}:</div>
<div className='break-all'>{log['user-agent']}</div>
</div>
</div> */}
</AccordionContent>
</AccordionItem>
))}
@@ -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,
@@ -201,6 +204,7 @@ export function SubscriptionDetail({
}}
/>
</TabsContent>
</div>
</Tabs>
</div>
</DialogContent>
@@ -45,6 +45,9 @@ const formSchema = z.object({
speed_limit: z.number().optional(),
device_limit: z.number().optional(),
expired_at: z.number().nullish().optional(),
upload: z.number().optional(),
download: z.number().optional(),
id: z.number().optional(),
});
export function SubscriptionForm({ trigger, title, loading, initialData, onSubmit }: Props) {
+55 -4
View File
@@ -2,7 +2,9 @@
import { Display } from '@/components/display';
import { ProTable, ProTableActions } from '@/components/pro-table';
import { getSubscribeList } from '@/services/admin/subscribe';
import { createUser, deleteUser, getUserList, updateUserBasicInfo } from '@/services/admin/user';
import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button';
import { Switch } from '@workspace/ui/components/switch';
@@ -20,8 +22,19 @@ export default function Page() {
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
const { data: subscribeList } = useQuery({
queryKey: ['getSubscribeList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeList({
page: 1,
size: 9999,
});
return data.data?.list as API.SubscribeGroup[];
},
});
return (
<ProTable<API.User, Record<string, unknown>>
<ProTable<API.User, API.GetUserListParams>
action={ref}
header={{
title: t('userList'),
@@ -58,8 +71,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'));
@@ -119,13 +145,38 @@ export default function Page() {
cell: ({ row }) => formatDate(row.getValue('created_at')),
},
]}
request={async (pagination) => {
const { data } = await getUserList(pagination);
request={async (pagination, filter) => {
const { data } = await getUserList({
...pagination,
...filter,
});
return {
list: data.data?.list || [],
total: data.data?.total || 0,
};
}}
params={[
{
key: 'subscribe_id',
placeholder: t('subscription'),
options: subscribeList?.map((item) => ({
label: item.name,
value: String(item.id),
})),
},
{
key: 'search',
placeholder: 'Search',
},
{
key: 'user_id',
placeholder: t('userId'),
},
{
key: 'user_subscribe_id',
placeholder: t('subscriptionId'),
},
]}
actions={{
render: (row) => {
return [
+108 -2
View File
@@ -1,15 +1,121 @@
'use client';
import { Display } from '@/components/display';
import { getUserDetail } from '@/services/admin/user';
import { getUserDetail, getUserSubscribeById } from '@/services/admin/user';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@workspace/ui/components/hover-card';
import { formatDate } from '@workspace/ui/utils';
import { formatBytes, formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { useState } from 'react';
export function UserSubscribeDetail({ id, enabled }: { id: number; enabled: boolean }) {
const t = useTranslations('user');
const { data } = useQuery({
enabled: id !== 0 && enabled,
queryKey: ['getUserSubscribeById', id],
queryFn: async () => {
const { data } = await getUserSubscribeById({ id });
return data.data;
},
});
if (!id) return '--';
const usedTraffic = data ? data.upload + data.download : 0;
const totalTraffic = data?.traffic || 0;
return (
<div className='space-y-4'>
<div>
<h3 className='mb-2 text-sm font-medium'>{t('subscriptionInfo')}</h3>
<div className='bg-muted/30 rounded-lg p-3'>
<ul className='grid gap-3'>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('subscriptionId')}</span>
<span>{data?.id || '--'}</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('subscriptionName')}</span>
<span>{data?.subscribe?.name || '--'}</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('token')}</span>
<div className='font-mono text-xs' title={data?.token || ''}>
{data?.token || '--'}
</div>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('trafficUsage')}</span>
<span>
{data
? totalTraffic === 0
? `${formatBytes(usedTraffic)} / ${t('unlimited')}`
: `${formatBytes(usedTraffic)} / ${formatBytes(totalTraffic)}`
: '--'}
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('startTime')}</span>
<span>{data?.start_time ? formatDate(data.start_time) : '--'}</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('expireTime')}</span>
<span>{data?.expire_time ? formatDate(data.expire_time) : '--'}</span>
</li>
</ul>
</div>
</div>
<div>
<h3 className='mb-2 text-sm font-medium'>
{t('userInfo')}
{data?.user_id && (
<Button
variant='link'
size='sm'
className='text-primary ml-2 h-auto p-0 text-xs'
asChild
>
<Link href={`/dashboard/user/${data.user_id}`}>{t('viewDetails')}</Link>
</Button>
)}
</h3>
<ul className='grid gap-3'>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('userId')}</span>
<span>{data?.user_id}</span>
</li>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('balance')}</span>
<span>
<Display type='currency' value={data?.user.balance} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('giftAmount')}</span>
<span>
<Display type='currency' value={data?.user?.gift_amount} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('commission')}</span>
<span>
<Display type='currency' value={data?.user?.commission} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('createdAt')}</span>
<span>{data?.user?.created_at && formatDate(data?.user?.created_at)}</span>
</li>
</ul>
</div>
</div>
);
}
export function UserDetail({ id }: { id: number }) {
const t = useTranslations('user');
const [shouldFetch, setShouldFetch] = useState(false);
+11 -10
View File
@@ -9,20 +9,20 @@ import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
import { PublicEnvScript } from 'next-runtime-env';
import { unstable_noStore as noStore } from 'next/cache';
import { Geist, Geist_Mono } from 'next/font/google';
// import { Geist, Geist_Mono } from 'next/font/google';
import { cookies } from 'next/headers';
import NextTopLoader from 'nextjs-toploader';
import React from 'react';
const fontSans = Geist({
subsets: ['latin'],
variable: '--font-sans',
});
// const fontSans = Geist({
// subsets: ['latin'],
// variable: '--font-sans',
// });
const fontMono = Geist_Mono({
subsets: ['latin'],
variable: '--font-mono',
});
// const fontMono = Geist_Mono({
// subsets: ['latin'],
// variable: '--font-mono',
// });
export async function generateMetadata(): Promise<Metadata> {
noStore();
@@ -111,7 +111,8 @@ export default async function RootLayout({
</head>
<body
suppressHydrationWarning
className={`${fontSans.variable} ${fontMono.variable} size-full min-h-[calc(100dvh-env(safe-area-inset-top))] font-sans antialiased`}
// ${fontSans.variable} ${fontMono.variable}
className={`size-full min-h-[calc(100dvh-env(safe-area-inset-top))] font-sans antialiased`}
>
<NextIntlClientProvider messages={messages}>
<NextTopLoader showSpinner={false} />
+17 -12
View File
@@ -1,5 +1,6 @@
'use client';
import { UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { queryServerTotalData, queryTicketWaitReply } from '@/services/admin/console';
import { useQuery } from '@tanstack/react-query';
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
@@ -11,6 +12,7 @@ import {
SelectTrigger,
SelectValue,
} from '@workspace/ui/components/select';
import { Separator } from '@workspace/ui/components/separator';
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { Icon } from '@workspace/ui/custom-components/icon';
import { formatBytes } from '@workspace/ui/utils';
@@ -59,15 +61,13 @@ export default function Statistics() {
users: {
today:
ServerTotal?.user_traffic_ranking_today?.map((item) => ({
name: item.user_id,
name: item.sid,
traffic: item.download + item.upload,
email: item.email,
})) || [],
yesterday:
ServerTotal?.user_traffic_ranking_yesterday?.map((item) => ({
name: item.user_id,
name: item.sid,
traffic: item.download + item.upload,
email: item.email,
})) || [],
},
};
@@ -175,10 +175,6 @@ export default function Statistics() {
label: t('type'),
color: 'hsl(var(--muted))',
},
email: {
label: t('email'),
color: 'hsl(var(--muted))',
},
label: {
color: 'hsl(var(--foreground))',
},
@@ -204,13 +200,22 @@ export default function Statistics() {
tickFormatter={(value, index) => String(index + 1)}
/>
<ChartTooltip
trigger='hover'
content={
<ChartTooltipContent
label={true}
labelFormatter={(label) =>
dataType === 'nodes'
? `${t('nodes')}: ${label}`
: `${t('users')}: ${label}`
labelFormatter={(label, [payload]) =>
dataType === 'nodes' ? (
`${t('nodes')}: ${label}`
) : (
<>
<div className='w-80'>
<UserSubscribeDetail id={payload?.payload.name} enabled={true} />
</div>
<Separator className='my-2' />
<div>{`${t('users')}: ${label}`}</div>
</>
)
}
formatter={(value) => {
return formatBytes(Number(value) || 0);
+29 -4
View File
@@ -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>
))}
+19 -4
View File
@@ -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',
@@ -76,16 +81,26 @@ export const navs = [
url: '/dashboard/server',
icon: 'flat-color-icons:data-protection',
},
],
{
title: 'Application Management',
url: '/dashboard/application',
icon: 'flat-color-icons:touchscreen-smartphone',
},
{
title: 'Finance',
items: [
title: 'Rule Management',
url: '/dashboard/rules',
icon: 'flat-color-icons:ruler',
},
{
title: 'Subscribe Management',
url: '/dashboard/subscribe',
icon: 'flat-color-icons:shop',
},
],
},
{
title: 'Finance',
items: [
{
title: 'Order Management',
url: '/dashboard/order',
@@ -115,7 +130,7 @@ export const navs = [
{
title: 'Announcement Management',
url: '/dashboard/announcement',
icon: 'flat-color-icons:news',
icon: 'flat-color-icons:advertising',
},
{
title: 'Ticket Management',
+4
View File
@@ -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) =>
+40
View File
@@ -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"
}
+1 -1
View File
@@ -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!",
+11 -1
View File
@@ -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"
}
+2
View File
@@ -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",
-25
View File
@@ -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í)"
}
+3
View File
@@ -1,6 +1,8 @@
{
"ADS Config": "Konfigurace ADS",
"Announcement Management": "Správa oznámení",
"Apple": "Apple ID",
"Application Management": "Správa aplikací",
"Auth Control": "Řízení ověřování",
"Coupon Management": "Správa kupónů",
"Dashboard": "Přístrojová deska",
@@ -15,6 +17,7 @@
"Order Management": "Správa objednávek",
"Payment Config": "Konfigurace platby",
"Phone Number": "Telefonní číslo",
"Rule Management": "Správa pravidel",
"Server": "Server",
"Server Management": "Správa serveru",
"Settings": "Nastavení",
+45 -38
View File
@@ -1,44 +1,51 @@
{
"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",
"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"
}
+43
View File
@@ -0,0 +1,43 @@
{
"analyzing": "Analyzuji...",
"appIcon": "Ikona pravidla",
"cancel": "Zrušit",
"confirm": "Potvrdit",
"confirmDelete": "Potvrdit smazání",
"create": "Přidat pravidlo",
"createRule": "Přidat pravidlo",
"createSuccess": "Pravidlo bylo úspěšně vytvořeno",
"createdAt": "Vytvořeno dne",
"delete": "Smazat",
"deleteSuccess": "Pravidlo bylo úspěšně smazáno",
"deleteWarning": "Opravdu chcete smazat toto pravidlo? Tato akce není vratná.",
"description": "Popis pravidla",
"downloadTemplate": "Stáhnout šablonu",
"edit": "Upravit",
"editRule": "Upravit pravidlo",
"enable": "Povolit",
"enterIconUrl": "Zadejte nebo nahrajte ikonu",
"enterRuleName": "Zadejte název pravidla",
"enterRules": "Zadejte obsah pravidla, jedno na řádek",
"import": "Importovat",
"importFailed": "Import se nezdařil",
"importSuccess": "Import byl úspěšný",
"importYaml": "Importovat YAML",
"importYamlDescription": "Importujte pravidla ze souboru YAML, systém automaticky vytvoří skupiny pravidel na základě názvů skupin politiky",
"importYamlRules": "Importovat YAML pravidla",
"importing": "Importuji...",
"invalidYamlFormat": "Neplatný formát YAML, chybí pole pravidel",
"name": "Název pravidla",
"noValidRules": "Nebyly nalezeny žádné platné pravidla",
"pleaseUploadFile": "Prosím, nahrajte soubor YAML",
"preview": "Náhled",
"rules": "Obsah pravidla",
"rulesFormat": "Formát pravidla: typ pravidla, obsah shody, [politika], kde politika je volitelná.\nPokud politika není specifikována, bude automaticky použito aktuální jméno skupiny pravidel. Příklady:",
"rulesLabel": "Obsah pravidla",
"searchRule": "Hledat název pravidla",
"selectFile": "Vybrat soubor",
"selectTags": "Vybrat značky uzlu",
"tags": "Značky uzlu",
"tagsLabel": "Značky uzlu",
"updateSuccess": "Pravidlo bylo úspěšně aktualizováno"
}
+10
View File
@@ -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 '/'",
+15 -1
View File
@@ -1,6 +1,7 @@
{
"accountEnable": "Povolení účtu",
"actions": "akce",
"active": "Aktivní",
"add": "Přidat",
"administrator": "Administrátor",
"areaCode": "Směrový kód",
@@ -41,10 +42,12 @@
"email": "e-mail",
"enable": "Povolit",
"expireTime": "Čas vypršení",
"expired": "Vypršelo",
"expiredAt": "Platnost vypršela",
"failed": "Neúspěch",
"giftAmount": "Částka dárku",
"giftAmountPlaceholder": "Zadejte částku dárku",
"inactive": "Neaktivní",
"invalidEmailFormat": "Neplatný formát e-mailu",
"inviteCode": "Pozvánkový kód",
"inviteCodePlaceholder": "Zadejte kód pozvánky (ponechte prázdné pro vygenerování)",
@@ -63,6 +66,7 @@
"passwordPlaceholder": "Zadejte nové heslo (volitelné)",
"permanent": "Trvalý",
"pleaseEnterEmail": "Prosím, zadejte e-mail",
"price": "Cena",
"referer": "Doporučitel",
"refererId": "ID doporučitele",
"refererIdPlaceholder": "Zadejte ID doporučitele",
@@ -74,8 +78,12 @@
"searchIp": "Vyhledat IP adresu",
"selectAuthType": "Vyberte typ ověření",
"speedLimit": "Rychlostní limit",
"startTime": "Čas zahájení",
"status": "Stav",
"subscription": "Předplatné",
"subscriptionDetails": "Podrobnosti o předplatném",
"subscriptionId": "ID předplatného",
"subscriptionInfo": "Informace o předplatném",
"subscriptionList": "Seznam předplatných",
"subscriptionLogs": "Záznamy o předplatném",
"subscriptionName": "Název předplatného",
@@ -89,6 +97,8 @@
"tradeNotifications": "Obchodní oznámení",
"trafficLimit": "Limit provozu",
"trafficLogs": "Protokoly provozu",
"trafficUsage": "Využití dat",
"unknown": "Neznámé",
"unlimited": "Neomezený",
"unverified": "Neověřeno",
"update": "Aktualizovat",
@@ -98,11 +108,15 @@
"userAgent": "Uživatelský agent",
"userEmail": "Uživatelský e-mail",
"userEmailPlaceholder": "Zadejte e-mail uživatele",
"userId": "ID uživatele",
"userInfo": "Informace o uživateli",
"userList": "Seznam uživatelů",
"userLogs": "Historie přihlášení",
"userName": "Identifikátor uživatele",
"userOrders": "Objednávky",
"userProfile": "Profil",
"userSubscriptions": "Předplatné",
"verified": "Ověřeno"
"username": "Uživatelské jméno",
"verified": "Ověřeno",
"viewDetails": "Zobrazit podrobnosti"
}
+40
View File
@@ -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"
}
+1 -1
View File
@@ -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!",
+11 -1
View File
@@ -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."
}
+2
View File
@@ -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",
-25
View File
@@ -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)"
}
+3
View File
@@ -1,6 +1,8 @@
{
"ADS Config": "ADS-Konfiguration",
"Announcement Management": "Ankündigungsverwaltung",
"Apple": "Apple-ID",
"Application Management": "Anwendungsverwaltung",
"Auth Control": "Authentifizierungskontrolle",
"Coupon Management": "Gutscheinverwaltung",
"Dashboard": "Armaturenbrett",
@@ -15,6 +17,7 @@
"Order Management": "Bestellverwaltung",
"Payment Config": "Zahlungskonfiguration",
"Phone Number": "Telefonnummer",
"Rule Management": "Regelverwaltung",
"Server": "Dienst",
"Server Management": "Serververwaltung",
"Settings": "Einstellungen",
+45 -38
View File
@@ -1,44 +1,51 @@
{
"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",
"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"
}
+43
View File
@@ -0,0 +1,43 @@
{
"analyzing": "Analysiere...",
"appIcon": "Regel-Icon",
"cancel": "Abbrechen",
"confirm": "Bestätigen",
"confirmDelete": "Löschen bestätigen",
"create": "Regel hinzufügen",
"createRule": "Regel hinzufügen",
"createSuccess": "Regel erfolgreich erstellt",
"createdAt": "Erstellt am",
"delete": "Löschen",
"deleteSuccess": "Regel erfolgreich gelöscht",
"deleteWarning": "Sind Sie sicher, dass Sie diese Regel löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
"description": "Regelbeschreibung",
"downloadTemplate": "Vorlage herunterladen",
"edit": "Bearbeiten",
"editRule": "Regel bearbeiten",
"enable": "Aktivieren",
"enterIconUrl": "Geben Sie eine URL für das Icon ein oder laden Sie es hoch",
"enterRuleName": "Geben Sie den Regelname ein",
"enterRules": "Geben Sie den Regelinhalt ein, jeweils eine Regel pro Zeile",
"import": "Importieren",
"importFailed": "Import fehlgeschlagen",
"importSuccess": "Import erfolgreich",
"importYaml": "YAML importieren",
"importYamlDescription": "Regeln aus einer YAML-Datei importieren, das System erstellt automatisch Regelgruppen basierend auf den Namen der Richtliniengruppen",
"importYamlRules": "YAML-Regeln importieren",
"importing": "Importiere...",
"invalidYamlFormat": "Ungültiges YAML-Format, das Feld 'Regeln' fehlt",
"name": "Regelname",
"noValidRules": "Keine gültigen Regeln gefunden",
"pleaseUploadFile": "Bitte laden Sie eine YAML-Datei hoch",
"preview": "Vorschau",
"rules": "Regelinhalt",
"rulesFormat": "Regelformat: Regeltyp, Übereinstimmungsinhalt, [Richtlinie], wobei die Richtlinie optional ist.\nWenn keine Richtlinie angegeben ist, wird automatisch der aktuelle Regelgruppenname verwendet. Beispiele:",
"rulesLabel": "Regelinhalt",
"searchRule": "Regelname suchen",
"selectFile": "Datei auswählen",
"selectTags": "Knoten-Tags auswählen",
"tags": "Knoten-Tags",
"tagsLabel": "Knoten-Tags",
"updateSuccess": "Regel erfolgreich aktualisiert"
}
+10
View File
@@ -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",
+15 -1
View File
@@ -1,6 +1,7 @@
{
"accountEnable": "Konto aktivieren",
"actions": "Aktionen",
"active": "Aktiv",
"add": "Hinzufügen",
"administrator": "Administrator",
"areaCode": "Vorwahl",
@@ -41,10 +42,12 @@
"email": "E-Mail",
"enable": "Aktivieren",
"expireTime": "Ablaufzeit",
"expired": "Abgelaufen",
"expiredAt": "Abgelaufen am",
"failed": "Fehlgeschlagen",
"giftAmount": "Geschenkbetrag",
"giftAmountPlaceholder": "Geben Sie den Geschenkbetrag ein",
"inactive": "Inaktiv",
"invalidEmailFormat": "Ungültiges E-Mail-Format",
"inviteCode": "Einladungscode",
"inviteCodePlaceholder": "Einladungscode eingeben (leer lassen, um zu generieren)",
@@ -63,6 +66,7 @@
"passwordPlaceholder": "Neues Passwort eingeben (optional)",
"permanent": "Dauerhaft",
"pleaseEnterEmail": "Bitte E-Mail eingeben",
"price": "Preis",
"referer": "Empfehler",
"refererId": "Referenz-ID",
"refererIdPlaceholder": "Geben Sie die Referenz-ID ein",
@@ -74,8 +78,12 @@
"searchIp": "IP-Adresse suchen",
"selectAuthType": "Authentifizierungstyp auswählen",
"speedLimit": "Geschwindigkeitsbegrenzung",
"startTime": "Startzeit",
"status": "Status",
"subscription": "Abonnement",
"subscriptionDetails": "Abonnementdetails",
"subscriptionId": "Abonnement-ID",
"subscriptionInfo": "Abonnement-Informationen",
"subscriptionList": "Abonnementsliste",
"subscriptionLogs": "Abonnementprotokolle",
"subscriptionName": "Abonnementname",
@@ -89,6 +97,8 @@
"tradeNotifications": "Handelsbenachrichtigungen",
"trafficLimit": "Verkehrslimit",
"trafficLogs": "Verkehrsprotokolle",
"trafficUsage": "Datenverbrauch",
"unknown": "Unbekannt",
"unlimited": "Unbegrenzt",
"unverified": "Unbestätigt",
"update": "Aktualisieren",
@@ -98,11 +108,15 @@
"userAgent": "Benutzeragent",
"userEmail": "Benutzer-E-Mail",
"userEmailPlaceholder": "Benutzer-E-Mail eingeben",
"userId": "Benutzer-ID",
"userInfo": "Benutzerinformationen",
"userList": "Benutzerliste",
"userLogs": "Anmeldeverlauf",
"userName": "Benutzerkennung",
"userOrders": "Bestellungen",
"userProfile": "Profil",
"userSubscriptions": "Abonnements",
"verified": "Verifiziert"
"username": "Benutzername",
"verified": "Verifiziert",
"viewDetails": "Details anzeigen"
}
+40
View File
@@ -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"
}
+1 -1
View File
@@ -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!",
+11 -1
View File
@@ -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"
}
+2
View File
@@ -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",
-25
View File
@@ -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)"
}
+3
View File
@@ -1,6 +1,8 @@
{
"ADS Config": "ADS Config",
"Announcement Management": "Announcement Management",
"Apple": "Apple ID",
"Application Management": "Application Management",
"Auth Control": "Auth Control",
"Coupon Management": "Coupon Management",
"Dashboard": "Dashboard",
@@ -15,6 +17,7 @@
"Order Management": "Order Management",
"Payment Config": "Payment Config",
"Phone Number": "Phone Number",
"Rule Management": "Rule Management",
"Server": "Server",
"Server Management": "Server Management",
"Settings": "Settings",
+47 -40
View File
@@ -1,44 +1,51 @@
{
"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",
"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"
}
+43
View File
@@ -0,0 +1,43 @@
{
"analyzing": "Analyzing...",
"appIcon": "Rule Icon",
"cancel": "Cancel",
"confirm": "Confirm",
"confirmDelete": "Confirm Deletion",
"create": "Add Rule",
"createRule": "Add Rule",
"createSuccess": "Rule created successfully",
"createdAt": "Created At",
"delete": "Delete",
"deleteSuccess": "Rule deleted successfully",
"deleteWarning": "Are you sure you want to delete this rule? This action cannot be undone.",
"description": "Rule Description",
"downloadTemplate": "Download Template",
"edit": "Edit",
"editRule": "Edit Rule",
"enable": "Enable",
"enterIconUrl": "Enter or upload an icon",
"enterRuleName": "Enter rule name",
"enterRules": "Enter rule content, one per line",
"import": "Import",
"importFailed": "Import failed",
"importSuccess": "Import successful",
"importYaml": "Import YAML",
"importYamlDescription": "Import rules from YAML file, the system will automatically create rule groups based on policy group names",
"importYamlRules": "Import YAML Rules",
"importing": "Importing...",
"invalidYamlFormat": "Invalid YAML format, missing rules field",
"name": "Rule Name",
"noValidRules": "No valid rules found",
"pleaseUploadFile": "Please upload a YAML file",
"preview": "Preview",
"rules": "Rule Content",
"rulesFormat": "Rule format: rule type,match content,[policy], where policy is optional.\nIf policy is not specified, the current rule group name will be used automatically. Examples:",
"rulesLabel": "Rule Content",
"searchRule": "Search rule name",
"selectFile": "Select File",
"selectTags": "Select node tags",
"tags": "Node Tags",
"tagsLabel": "Node Tags",
"updateSuccess": "Rule updated successfully"
}
+5
View File
@@ -68,11 +68,14 @@
"cancel": "Cancel",
"city": "City",
"confirm": "Confirm",
"congestionController": "Congestion Controller",
"country": "Country",
"disableSni": "Disable SNI",
"edit": "Edit",
"editSecurity": "Edit Security Configuration",
"enableTLS": "Enable TLS",
"encryptionMethod": "Encryption Method",
"fingerprint": "Fingerprint",
"flow": "Flow Control Algorithm",
"groupId": "Node Group",
"hopInterval": "Hop Interval",
@@ -85,6 +88,7 @@
"pleaseSelect": "Please Select",
"port": "Server Port",
"protocol": "Protocol",
"reduceRtt": "Reduce RTT",
"relayHost": "Relay Host",
"relayMode": "Relay Mode",
"relayModeOptions": {
@@ -128,6 +132,7 @@
"transport": "Transport Protocol Configuration",
"transportConfig": "Transport Protocol Configuration",
"transportHost": "Transport Server Address",
"udpRelayMode": "UDP Relay Mode",
"transportPath": "Transport Path",
"transportServerName": "Transport Server Name"
},
+10
View File
@@ -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 '/'",
+15 -1
View File
@@ -1,6 +1,7 @@
{
"accountEnable": "Account Enable",
"actions": "Actions",
"active": "Active",
"add": "Add",
"administrator": "Administrator",
"areaCode": "Area Code",
@@ -41,10 +42,12 @@
"email": "Email",
"enable": "Enable",
"expireTime": "Expire Time",
"expired": "Expired",
"expiredAt": "Expired At",
"failed": "Failed",
"giftAmount": "Gift Amount",
"giftAmountPlaceholder": "Enter gift amount",
"inactive": "Inactive",
"invalidEmailFormat": "Invalid email format",
"inviteCode": "Invite Code",
"inviteCodePlaceholder": "Enter invite code (leave blank to generate)",
@@ -63,6 +66,7 @@
"passwordPlaceholder": "Enter new password (optional)",
"permanent": "Permanent",
"pleaseEnterEmail": "Please enter email",
"price": "Price",
"referer": "Referer",
"refererId": "Referer ID",
"refererIdPlaceholder": "Enter referer ID",
@@ -74,8 +78,12 @@
"searchIp": "Search IP address",
"selectAuthType": "Select auth type",
"speedLimit": "Speed Limit",
"startTime": "Start Time",
"status": "Status",
"subscription": "Subscription",
"subscriptionDetails": "Subscription Details",
"subscriptionId": "Subscription ID",
"subscriptionInfo": "Subscription Info",
"subscriptionList": "Subscription List",
"subscriptionLogs": "Subscription Logs",
"subscriptionName": "Subscription Name",
@@ -89,6 +97,8 @@
"tradeNotifications": "Trade Notifications",
"trafficLimit": "Traffic Limit",
"trafficLogs": "Traffic Logs",
"trafficUsage": "Traffic Usage",
"unknown": "Unknown",
"unlimited": "Unlimited",
"unverified": "Unverified",
"update": "Update",
@@ -98,11 +108,15 @@
"userAgent": "User Agent",
"userEmail": "User Email",
"userEmailPlaceholder": "Enter user email",
"userId": "User ID",
"userInfo": "User Info",
"userList": "User List",
"userLogs": "Login History",
"userName": "User Identifier",
"userOrders": "Orders",
"userProfile": "Profile",
"userSubscriptions": "Subscriptions",
"verified": "Verified"
"username": "Username",
"verified": "Verified",
"viewDetails": "View Details"
}
+40
View File
@@ -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"
}
+1 -1
View File
@@ -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!",
+11 -1
View File
@@ -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"
}
+2
View File
@@ -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",
-25
View File
@@ -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)"
}
+3
View File
@@ -1,6 +1,8 @@
{
"ADS Config": "Configuración de ADS",
"Announcement Management": "Gestión de Anuncios",
"Apple": "ID de Apple",
"Application Management": "Gestión de Aplicaciones",
"Auth Control": "Control de Autenticación",
"Coupon Management": "Gestión de Cupones",
"Dashboard": "Tablero",
@@ -15,6 +17,7 @@
"Order Management": "Gestión de Pedidos",
"Payment Config": "Configuración de Pago",
"Phone Number": "Número de Teléfono",
"Rule Management": "Gestión de Reglas",
"Server": "Servidor",
"Server Management": "Gestión de Servidores",
"Settings": "Configuración",
+45 -38
View File
@@ -1,44 +1,51 @@
{
"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",
"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"
}
+43
View File
@@ -0,0 +1,43 @@
{
"analyzing": "Analizando...",
"appIcon": "Icono de Regla",
"cancel": "Cancelar",
"confirm": "Confirmar",
"confirmDelete": "Confirmar Eliminación",
"create": "Agregar Regla",
"createRule": "Agregar Regla",
"createSuccess": "Regla creada con éxito",
"createdAt": "Creado En",
"delete": "Eliminar",
"deleteSuccess": "Regla eliminada con éxito",
"deleteWarning": "¿Está seguro de que desea eliminar esta regla? Esta acción no se puede deshacer.",
"description": "Descripción de la Regla",
"downloadTemplate": "Descargar Plantilla",
"edit": "Editar",
"editRule": "Editar Regla",
"enable": "Habilitar",
"enterIconUrl": "Ingrese o suba un icono",
"enterRuleName": "Ingrese el nombre de la regla",
"enterRules": "Ingrese el contenido de la regla, uno por línea",
"import": "Importar",
"importFailed": "Importación fallida",
"importSuccess": "Importación exitosa",
"importYaml": "Importar YAML",
"importYamlDescription": "Importar reglas desde un archivo YAML, el sistema creará automáticamente grupos de reglas basados en los nombres de los grupos de políticas",
"importYamlRules": "Importar Reglas YAML",
"importing": "Importando...",
"invalidYamlFormat": "Formato YAML inválido, falta el campo de reglas",
"name": "Nombre de la Regla",
"noValidRules": "No se encontraron reglas válidas",
"pleaseUploadFile": "Por favor, suba un archivo YAML",
"preview": "Vista Previa",
"rules": "Contenido de la Regla",
"rulesFormat": "Formato de regla: tipo de regla, contenido de coincidencia, [política], donde la política es opcional.\nSi no se especifica la política, se utilizará automáticamente el nombre del grupo de reglas actual. Ejemplos:",
"rulesLabel": "Contenido de la Regla",
"searchRule": "Buscar nombre de regla",
"selectFile": "Seleccionar Archivo",
"selectTags": "Seleccionar etiquetas de nodo",
"tags": "Etiquetas de Nodo",
"tagsLabel": "Etiquetas de Nodo",
"updateSuccess": "Regla actualizada con éxito"
}
+10
View File
@@ -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 '/'",
+15 -1
View File
@@ -1,6 +1,7 @@
{
"accountEnable": "Habilitar cuenta",
"actions": "acciones",
"active": "Activo",
"add": "Agregar",
"administrator": "Administrador",
"areaCode": "Código de Área",
@@ -41,10 +42,12 @@
"email": "correo electrónico",
"enable": "Habilitar",
"expireTime": "Tiempo de Expiración",
"expired": "Caducado",
"expiredAt": "Fecha de vencimiento",
"failed": "Fallido",
"giftAmount": "Monto del Regalo",
"giftAmountPlaceholder": "Ingrese el monto del regalo",
"inactive": "Inactivo",
"invalidEmailFormat": "Formato de correo electrónico no válido",
"inviteCode": "Código de invitación",
"inviteCodePlaceholder": "Introduce el código de invitación (déjalo en blanco para generar uno)",
@@ -63,6 +66,7 @@
"passwordPlaceholder": "Ingrese nueva contraseña (opcional)",
"permanent": "Permanente",
"pleaseEnterEmail": "Por favor, introduce el correo electrónico",
"price": "Precio",
"referer": "Recomendador",
"refererId": "ID del Referente",
"refererIdPlaceholder": "Ingrese el ID del referente",
@@ -74,8 +78,12 @@
"searchIp": "Buscar dirección IP",
"selectAuthType": "Seleccionar tipo de autenticación",
"speedLimit": "Límite de Velocidad",
"startTime": "Hora de Inicio",
"status": "Estado",
"subscription": "Suscripción",
"subscriptionDetails": "Detalles de la suscripción",
"subscriptionId": "ID de Suscripción",
"subscriptionInfo": "Información de Suscripción",
"subscriptionList": "Lista de Suscripción",
"subscriptionLogs": "Registros de Suscripción",
"subscriptionName": "Nombre de la Suscripción",
@@ -89,6 +97,8 @@
"tradeNotifications": "Notificaciones de Comercio",
"trafficLimit": "Límite de Tráfico",
"trafficLogs": "Registros de Tráfico",
"trafficUsage": "Uso de Tráfico",
"unknown": "Desconocido",
"unlimited": "Ilimitado",
"unverified": "No verificado",
"update": "Actualizar",
@@ -98,11 +108,15 @@
"userAgent": "Agente de Usuario",
"userEmail": "Correo Electrónico del Usuario",
"userEmailPlaceholder": "Introduce el correo electrónico del usuario",
"userId": "ID de Usuario",
"userInfo": "Información de Usuario",
"userList": "Lista de usuarios",
"userLogs": "Historial de inicio de sesión",
"userName": "Identificador de Usuario",
"userOrders": "Pedidos",
"userProfile": "Perfil",
"userSubscriptions": "Suscripciones",
"verified": "Verificado"
"username": "Nombre de Usuario",
"verified": "Verificado",
"viewDetails": "Ver Detalles"
}
+40
View File
@@ -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"
}
+1 -1
View File
@@ -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!",
+11 -1
View File
@@ -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"
}
+2
View File
@@ -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",
-25
View File
@@ -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)"
}
+3
View File
@@ -1,6 +1,8 @@
{
"ADS Config": "Configuración de ADS",
"Announcement Management": "Gestión de Anuncios",
"Apple": "ID de Apple",
"Application Management": "Gestión de Aplicaciones",
"Auth Control": "Control de Autenticación",
"Coupon Management": "Gestión de Cupones",
"Dashboard": "Tablero",
@@ -15,6 +17,7 @@
"Order Management": "Gestión de Pedidos",
"Payment Config": "Configuración de Pago",
"Phone Number": "Número de Teléfono",
"Rule Management": "Gestión de Reglas",
"Server": "Servidor",
"Server Management": "Gestión de Servidores",
"Settings": "Configuración",
+45 -38
View File
@@ -1,44 +1,51 @@
{
"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",
"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"
}
+43
View File
@@ -0,0 +1,43 @@
{
"analyzing": "Analizando...",
"appIcon": "Ícono de Regla",
"cancel": "Cancelar",
"confirm": "Confirmar",
"confirmDelete": "Confirmar Eliminación",
"create": "Agregar Regla",
"createRule": "Agregar Regla",
"createSuccess": "Regla creada con éxito",
"createdAt": "Creado En",
"delete": "Eliminar",
"deleteSuccess": "Regla eliminada con éxito",
"deleteWarning": "¿Está seguro de que desea eliminar esta regla? Esta acción no se puede deshacer.",
"description": "Descripción de la Regla",
"downloadTemplate": "Descargar Plantilla",
"edit": "Editar",
"editRule": "Editar Regla",
"enable": "Habilitar",
"enterIconUrl": "Ingrese o suba un ícono",
"enterRuleName": "Ingrese el nombre de la regla",
"enterRules": "Ingrese el contenido de la regla, uno por línea",
"import": "Importar",
"importFailed": "Importación fallida",
"importSuccess": "Importación exitosa",
"importYaml": "Importar YAML",
"importYamlDescription": "Importar reglas desde un archivo YAML, el sistema creará automáticamente grupos de reglas basados en los nombres de los grupos de políticas",
"importYamlRules": "Importar Reglas YAML",
"importing": "Importando...",
"invalidYamlFormat": "Formato YAML inválido, falta el campo de reglas",
"name": "Nombre de la Regla",
"noValidRules": "No se encontraron reglas válidas",
"pleaseUploadFile": "Por favor, suba un archivo YAML",
"preview": "Vista Previa",
"rules": "Contenido de la Regla",
"rulesFormat": "Formato de regla: tipo de regla, contenido de coincidencia, [política], donde la política es opcional.\nSi no se especifica la política, se utilizará automáticamente el nombre del grupo de reglas actual. Ejemplos:",
"rulesLabel": "Contenido de la Regla",
"searchRule": "Buscar nombre de regla",
"selectFile": "Seleccionar Archivo",
"selectTags": "Seleccionar etiquetas de nodo",
"tags": "Etiquetas de Nodo",
"tagsLabel": "Etiquetas de Nodo",
"updateSuccess": "Regla actualizada con éxito"
}
+10
View File
@@ -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 '/'",
+15 -1
View File
@@ -1,6 +1,7 @@
{
"accountEnable": "Habilitar Cuenta",
"actions": "acciones",
"active": "Activo",
"add": "Agregar",
"administrator": "Administrador",
"areaCode": "Código de Área",
@@ -41,10 +42,12 @@
"email": "correo electrónico",
"enable": "Habilitar",
"expireTime": "Tiempo de Expiración",
"expired": "Expirado",
"expiredAt": "Fecha de vencimiento",
"failed": "Fallido",
"giftAmount": "Monto del Regalo",
"giftAmountPlaceholder": "Ingrese el monto del regalo",
"inactive": "Inactivo",
"invalidEmailFormat": "Formato de correo electrónico no válido",
"inviteCode": "Código de Invitación",
"inviteCodePlaceholder": "Ingresa el código de invitación (déjalo en blanco para generar uno)",
@@ -63,6 +66,7 @@
"passwordPlaceholder": "Ingresa nueva contraseña (opcional)",
"permanent": "Permanente",
"pleaseEnterEmail": "Por favor, ingresa el correo electrónico",
"price": "Precio",
"referer": "Recomendador",
"refererId": "ID del Referente",
"refererIdPlaceholder": "Ingrese ID del referente",
@@ -74,8 +78,12 @@
"searchIp": "Buscar dirección IP",
"selectAuthType": "Seleccionar tipo de autenticación",
"speedLimit": "Límite de Velocidad",
"startTime": "Hora de Inicio",
"status": "Estado",
"subscription": "Suscripción",
"subscriptionDetails": "Detalles de la Suscripción",
"subscriptionId": "ID de Suscripción",
"subscriptionInfo": "Información de Suscripción",
"subscriptionList": "Lista de Suscripción",
"subscriptionLogs": "Registros de Suscripción",
"subscriptionName": "Nombre de la Suscripción",
@@ -89,6 +97,8 @@
"tradeNotifications": "Notificaciones de Comercio",
"trafficLimit": "Límite de Tráfico",
"trafficLogs": "Registros de Tráfico",
"trafficUsage": "Uso de Tráfico",
"unknown": "Desconocido",
"unlimited": "Ilimitado",
"unverified": "No verificado",
"update": "Actualizar",
@@ -98,11 +108,15 @@
"userAgent": "Agente de Usuario",
"userEmail": "Correo Electrónico del Usuario",
"userEmailPlaceholder": "Ingresa el correo electrónico del usuario",
"userId": "ID de Usuario",
"userInfo": "Información de Usuario",
"userList": "Lista de usuarios",
"userLogs": "Historial de Inicio de Sesión",
"userName": "Identificador de Usuario",
"userOrders": "Pedidos",
"userProfile": "Perfil",
"userSubscriptions": "Suscripciones",
"verified": "Verificado"
"username": "Nombre de Usuario",
"verified": "Verificado",
"viewDetails": "Ver Detalles"
}
+40
View File
@@ -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": "مدت اعتبار"
}
+1 -1
View File
@@ -28,7 +28,7 @@
"email": "لطفاً یک آدرس ایمیل معتبر وارد کنید.",
"existingAccount": "آیا قبلاً حساب کاربری دارید؟",
"get": "دریافت",
"invite": "کد دعوت",
"invite": "کد دعوت (اختیاری)",
"message": "#### کاربر گرامی، سلام!\n\nاز توجه و حمایت شما سپاسگزاریم. به دلیل تغییرات در استراتژی عملیاتی سایت، عملکرد ثبت‌نام کاربران جدید را متوقف کرده‌ایم. در این مدت، کاربران فعلی تحت تأثیر قرار نخواهند گرفت.\n\nما متعهد به ارائه خدمات و تجربه بهتر به شما هستیم، بنابراین در طول این دوره، بهینه‌سازی جامع سیستم و ارتقاء ویژگی‌ها را انجام خواهیم داد. در آینده، با محتوای و خدمات بهتر از شما استقبال خواهیم کرد.\n\nلطفاً برای دریافت آخرین به‌روزرسانی‌ها و اطلاعیه‌ها، وب‌سایت و پلتفرم‌های اجتماعی ما را دنبال کنید. از درک و حمایت شما سپاسگزاریم.\n\nاگر سوالی دارید یا به کمک نیاز دارید، لطفاً با تیم خدمات مشتری ما تماس بگیرید.\n\n**بار دیگر از حمایت و درک شما سپاسگزاریم.**",
"passwordMismatch": "رمزهای عبور مطابقت ندارند",
"success": "ثبت‌نام موفقیت‌آمیز بود، به‌طور خودکار وارد شدید!",
+11 -1
View File
@@ -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": "هنگامی که فعال باشد، تبلیغات بر روی دستگاه‌ها نمایش داده خواهد شد"
}

Some files were not shown because too many files have changed in this diff Show More