diff --git a/apps/admin/app/dashboard/subscribe/app/config.tsx b/apps/admin/app/dashboard/subscribe/app/config.tsx index 776a1e5..d8359e3 100644 --- a/apps/admin/app/dashboard/subscribe/app/config.tsx +++ b/apps/admin/app/dashboard/subscribe/app/config.tsx @@ -1,6 +1,10 @@ 'use client'; -import { getApplicationConfig, updateApplicationConfig } from '@/services/admin/system'; +import { + getApplication, + getApplicationConfig, + updateApplicationConfig, +} from '@/services/admin/system'; import { zodResolver } from '@hookform/resolvers/zod'; import { Icon } from '@iconify/react'; import { useQuery } from '@tanstack/react-query'; @@ -24,18 +28,24 @@ import { 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 { UploadImage } from '@workspace/ui/custom-components/upload-image'; +import { DicesIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; +import { uid } from 'radash'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; const formSchema = z.object({ - startup_picture: z.string(), - startup_picture_skip_time: z.number(), - domains: z.array(z.string()), + app_id: z.number().optional(), + app_key: z.string().optional(), + encryption: z.string().optional(), + startup_picture: z.string().optional(), + startup_picture_skip_time: z.number().optional(), + domains: z.array(z.string()).optional(), }); type FormSchema = z.infer; @@ -48,6 +58,9 @@ export default function ConfigForm() { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { + app_id: 0, + app_key: '', + encryption: '', startup_picture: '', startup_picture_skip_time: 0, domains: [], @@ -62,6 +75,14 @@ export default function ConfigForm() { }, }); + const { data: applications } = useQuery({ + queryKey: ['getApplication'], + queryFn: async () => { + const { data } = await getApplication(); + return data.data?.applications || []; + }, + }); + useEffect(() => { if (data) { form.reset({ @@ -100,6 +121,80 @@ export default function ConfigForm() {
+ ( + + {t('selectApp')} + {t('selectAppDescription')} + + ({ + label: app.name, + value: app.id, + })) || [] + } + value={field.value} + onChange={(value) => form.setValue(field.name, value)} + /> + + + + )} + /> + ( + + {t('communicationKey')} + {t('communicationKeyDescription')} + + form.setValue(field.name, value as string)} + suffix={ +
+ { + 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)}`; + form.setValue(field.name, formatted); + }} + className='cursor-pointer' + /> +
+ } + /> +
+ +
+ )} + /> + ( + + {t('encryption')} + {t('encryptionDescription')} + + form.setValue(field.name, value)} + /> + + + + )} + /> {t('startupPictureDescription')} form.setValue(field.name, value as string)} suffix={ form.setValue( 'domains', diff --git a/apps/admin/components/billing.tsx b/apps/admin/components/billing.tsx index 806abd8..7de357a 100644 --- a/apps/admin/components/billing.tsx +++ b/apps/admin/components/billing.tsx @@ -3,8 +3,6 @@ import { Card, CardDescription, CardHeader, CardTitle } from '@workspace/ui/comp import { getTranslations } from 'next-intl/server'; import Link from 'next/link'; -const BASE_URL = 'https://cdn.jsdelivr.net/gh/perfect-panel/ppanel-assets/billing/index.json'; - interface BillingProps { type: 'dashboard' | 'payment'; } @@ -17,23 +15,48 @@ interface ItemType { href: string; } +async function getBillingURL() { + try { + const response = await fetch( + 'https://api.github.com/repos/perfect-panel/ppanel-assets/commits', + ); + const json = await response.json(); + const version = json[0]?.sha; + const url = new URL('https://cdn.jsdelivr.net/gh/perfect-panel/ppanel-assets'); + url.pathname += `@${version}/billing/index.json`; + return url.toString(); + } catch (error) { + return 'https://cdn.jsdelivr.net/gh/perfect-panel/ppanel-assets/billing/index.json'; + } +} + export default async function Billing({ type }: BillingProps) { const t = await getTranslations('common.billing'); let list: ItemType[] = []; + try { - const response = await fetch(BASE_URL, { cache: 'no-store' }); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - const data = await response.json(); - const now = new Date().getTime(); - list = data[type].filter((item: { expiryDate: string }) => { - const expiryDate = Date.parse(item.expiryDate); - return !isNaN(expiryDate) && expiryDate > now; + const url = await getBillingURL(); + const response = await fetch(url, { + headers: { + Accept: 'application/json', + }, }); + const data = await response.json(); + const now = Date.now(); + + list = Array.isArray(data[type]) + ? data[type].filter((item: { expiryDate: string }) => { + const expiryDate = Date.parse(item.expiryDate); + return !isNaN(expiryDate) && expiryDate > now; + }) + : []; } catch (error) { - console.log('Error fetching billing data:', error); + console.log(error); return null; } - if (list && list.length === 0) return null; + + if (!list?.length) return null; + return ( <>

diff --git a/apps/admin/locales/en-US/subscribe.json b/apps/admin/locales/en-US/subscribe.json index b9b85e0..a21fc72 100644 --- a/apps/admin/locales/en-US/subscribe.json +++ b/apps/admin/locales/en-US/subscribe.json @@ -26,6 +26,12 @@ "editApp": "Edit App", "nameDescription": "Application name, displayed in the app list", "platform": "Platform", + "selectApp": "Select App", + "selectAppDescription": "Select the app to configure, all settings will apply to the selected app", + "communicationKey": "Communication Key", + "communicationKeyDescription": "Key used for client communication", + "encryption": "Encryption Method", + "encryptionDescription": "Choose the encryption method for client communication. If selected, the client will use this method to communicate with the server", "startupPicture": "Startup Picture", "startupPictureDescription": "Startup picture, supports network and local images. For network images, please enter the complete image URL", "startupPicturePreview": "Startup Picture Preview", diff --git a/apps/admin/services/admin/typings.d.ts b/apps/admin/services/admin/typings.d.ts index a5aff95..a87b776 100644 --- a/apps/admin/services/admin/typings.d.ts +++ b/apps/admin/services/admin/typings.d.ts @@ -27,7 +27,9 @@ declare namespace API { }; type ApplicationConfig = { - encryption: boolean; + app_id: number; + encryption_key: string; + encryption_method: string; domains: string[]; startup_picture: string; startup_picture_skip_time: number; diff --git a/apps/admin/services/common/typings.d.ts b/apps/admin/services/common/typings.d.ts index 7d461d8..322496c 100644 --- a/apps/admin/services/common/typings.d.ts +++ b/apps/admin/services/common/typings.d.ts @@ -19,7 +19,9 @@ declare namespace API { }; type ApplicationConfig = { - encryption: boolean; + app_id: number; + encryption_key: string; + encryption_method: string; domains: string[]; startup_picture: string; startup_picture_skip_time: number;