Compare commits

..

13 Commits

Author SHA1 Message Date
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
45 changed files with 348 additions and 115 deletions
+49 -4
View File
@@ -1,19 +1,64 @@
<a name="readme-top"></a> <a name="readme-top"></a>
# Changelog # 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.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 ### 🐛 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)) * **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) # [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 ### 🐛 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> <a name="readme-top"></a>
+28 -31
View File
@@ -61,45 +61,42 @@ export default function ResetForm({
/> />
<FormField <FormField
control={form.control} control={form.control}
name='password' name='code'
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<Input placeholder='Enter your password...' type='password' {...field} /> <div className='flex items-center gap-2'>
<Input
placeholder='Enter code...'
type='text'
{...field}
value={field.value as string}
/>
<SendCode
type='email'
params={{
...form.getValues(),
type: 2,
}}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormControl>
<Input placeholder='Enter your New password...' type='password' {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
{auth?.email?.enable_verify && (
<FormField
control={form.control}
name='code'
render={({ field }) => (
<FormItem>
<FormControl>
<div className='flex items-center gap-2'>
<Input
placeholder='Enter code...'
type='text'
{...field}
value={field.value as string}
/>
<SendCode
type='email'
params={{
...form.getValues(),
type: 2,
}}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{verify.enable_reset_password_verify && ( {verify.enable_reset_password_verify && (
<FormField <FormField
control={form.control} control={form.control}
@@ -50,7 +50,7 @@ const defaultValues = {
const versionSchema = z.object({ const versionSchema = z.object({
url: z.string(), url: z.string(),
version: z.string(), version: z.string().optional(),
description: z.string().optional(), description: z.string().optional(),
is_default: z.boolean().optional(), is_default: z.boolean().optional(),
}); });
@@ -221,6 +221,7 @@ export default function SubscribeAppForm<
name: 'version', name: 'version',
type: 'text', type: 'text',
placeholder: t('version'), placeholder: t('version'),
defaultValue: '1.0.0',
}, },
{ {
name: 'description', name: 'description',
@@ -251,7 +252,10 @@ export default function SubscribeAppForm<
]} ]}
value={field.value} value={field.value}
onChange={(value) => { onChange={(value) => {
form.setValue(field.name, value); form.setValue(
field.name,
value.filter((item) => item.url),
);
}} }}
/> />
</FormControl> </FormControl>
+30 -1
View File
@@ -85,6 +85,35 @@ export default function Site() {
/> />
</TableCell> </TableCell>
</TableRow> </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> <TableRow>
<TableCell className='align-top'> <TableCell className='align-top'>
<Label>{t('siteDomain')}</Label> <Label>{t('siteDomain')}</Label>
@@ -93,7 +122,7 @@ export default function Site() {
<TableCell className='text-right'> <TableCell className='text-right'>
<Textarea <Textarea
className='h-52' className='h-52'
placeholder={t('siteDomainPlaceholder')} placeholder={`${t('siteDomainPlaceholder')}\nexample.com\nwww.example.com`}
defaultValue={data?.host} defaultValue={data?.host}
onBlur={(e) => { onBlur={(e) => {
updateConfig('host', e.target.value); updateConfig('host', e.target.value);
+2
View File
@@ -14,6 +14,8 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
site_name: '', site_name: '',
site_desc: '', site_desc: '',
site_logo: '', site_logo: '',
keywords: '',
custom_html: '',
}, },
verify: { verify: {
turnstile_site_key: '', turnstile_site_key: '',
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Úspěšně uloženo" "saveSuccess": "Úspěšně uloženo"
}, },
"site": { "site": {
"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", "logo": "LOGO",
"logoDescription": "Používá se k zobrazení místa, kde je potřeba zobrazit LOGO", "logoDescription": "Používá se k zobrazení místa, kde je potřeba zobrazit LOGO",
"logoPlaceholder": "Zadejte URL adresu LOGA, nekončete '/'", "logoPlaceholder": "Zadejte URL adresu LOGA, nekončete '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Erfolgreich gespeichert" "saveSuccess": "Erfolgreich gespeichert"
}, },
"site": { "site": {
"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", "logo": "LOGO",
"logoDescription": "Position zur Anzeige des LOGOs", "logoDescription": "Position zur Anzeige des LOGOs",
"logoPlaceholder": "Bitte geben Sie die URL-Adresse des LOGOs ein, ohne '/' am Ende", "logoPlaceholder": "Bitte geben Sie die URL-Adresse des LOGOs ein, ohne '/' am Ende",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Save Successful" "saveSuccess": "Save Successful"
}, },
"site": { "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", "logo": "Logo",
"logoDescription": "Used for displaying the logo in designated locations", "logoDescription": "Used for displaying the logo in designated locations",
"logoPlaceholder": "Enter the URL of the logo, without ending with '/'", "logoPlaceholder": "Enter the URL of the logo, without ending with '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Guardado exitosamente" "saveSuccess": "Guardado exitosamente"
}, },
"site": { "site": {
"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", "logo": "LOGO",
"logoDescription": "Se utiliza para mostrar la ubicación donde se debe mostrar el 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 '/'", "logoPlaceholder": "Por favor, introduce la URL del LOGO, no termines con '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Guardado exitosamente" "saveSuccess": "Guardado exitosamente"
}, },
"site": { "site": {
"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", "logo": "LOGO",
"logoDescription": "Usado para mostrar la ubicación donde se debe exhibir el 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 '/'", "logoPlaceholder": "Por favor, ingresa la URL del LOGO, no termines con '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "ذخیره‌سازی موفقیت‌آمیز" "saveSuccess": "ذخیره‌سازی موفقیت‌آمیز"
}, },
"site": { "site": {
"customHtml": "HTML سفارشی",
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ <body> سایت وارد شود.",
"keywords": "کلمات کلیدی",
"keywordsDescription": "برای اهداف سئو استفاده می‌شود",
"logo": "لوگو", "logo": "لوگو",
"logoDescription": "برای نمایش لوگو در مکان‌های تعیین‌شده استفاده می‌شود", "logoDescription": "برای نمایش لوگو در مکان‌های تعیین‌شده استفاده می‌شود",
"logoPlaceholder": "آدرس URL لوگو را وارد کنید، بدون '/' در انتها", "logoPlaceholder": "آدرس URL لوگو را وارد کنید، بدون '/' در انتها",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Tallennus onnistui" "saveSuccess": "Tallennus onnistui"
}, },
"site": { "site": {
"customHtml": "Mukautettu HTML",
"customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston <body> -tagin loppuun.",
"keywords": "Avainsanat",
"keywordsDescription": "Käytetään SEO-tarkoituksiin",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Näyttääksesi paikan, jossa LOGO tulee näkyä", "logoDescription": "Näyttääksesi paikan, jossa LOGO tulee näkyä",
"logoPlaceholder": "Syötä LOGO:n URL-osoite, älä lopeta '/'-merkillä", "logoPlaceholder": "Syötä LOGO:n URL-osoite, älä lopeta '/'-merkillä",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Enregistrement réussi" "saveSuccess": "Enregistrement réussi"
}, },
"site": { "site": {
"customHtml": "HTML personnalisé",
"customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise <body> du site.",
"keywords": "Mots-clés",
"keywordsDescription": "Utilisé à des fins de référencement",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Utilisé pour afficher l'emplacement où le LOGO doit être montré", "logoDescription": "Utilisé pour afficher l'emplacement où le LOGO doit être montré",
"logoPlaceholder": "Veuillez entrer l'URL du LOGO, ne pas terminer par '/'", "logoPlaceholder": "Veuillez entrer l'URL du LOGO, ne pas terminer par '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "सफलतापूर्वक सहेजा गया" "saveSuccess": "सफलतापूर्वक सहेजा गया"
}, },
"site": { "site": {
"customHtml": "कस्टम HTML",
"customHtmlDescription": "साइट के <body> टैग के नीचे इंजेक्ट करने के लिए कस्टम HTML कोड।",
"keywords": "कीवर्ड",
"keywordsDescription": "SEO उद्देश्यों के लिए उपयोग किया जाता है",
"logo": "लोगो", "logo": "लोगो",
"logoDescription": "लोगो प्रदर्शित करने के लिए आवश्यक स्थान", "logoDescription": "लोगो प्रदर्शित करने के लिए आवश्यक स्थान",
"logoPlaceholder": "कृपया लोगो का URL पता दर्ज करें, '/' के साथ समाप्त न करें", "logoPlaceholder": "कृपया लोगो का URL पता दर्ज करें, '/' के साथ समाप्त न करें",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Sikeres mentés" "saveSuccess": "Sikeres mentés"
}, },
"site": { "site": {
"customHtml": "Egyedi HTML",
"customHtmlDescription": "Egyedi HTML kód, amelyet a webhely <body> címkéjének aljára kell beilleszteni.",
"keywords": "Kulcsszavak",
"keywordsDescription": "SEO célokra használva",
"logo": "LOGÓ", "logo": "LOGÓ",
"logoDescription": "A LOGÓ megjelenítésére szolgáló hely", "logoDescription": "A LOGÓ megjelenítésére szolgáló hely",
"logoPlaceholder": "Kérjük, adja meg a LOGÓ URL-címét, ne végződjön '/' jellel", "logoPlaceholder": "Kérjük, adja meg a LOGÓ URL-címét, ne végződjön '/' jellel",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "保存に成功しました" "saveSuccess": "保存に成功しました"
}, },
"site": { "site": {
"customHtml": "カスタムHTML",
"customHtmlDescription": "サイトの <body> タグの下部に挿入されるカスタム HTML コード。",
"keywords": "キーワード",
"keywordsDescription": "SEO目的で使用されます",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "LOGOを表示する必要がある場所に使用されます", "logoDescription": "LOGOを表示する必要がある場所に使用されます",
"logoPlaceholder": "LOGOのURLアドレスを入力してください。末尾に'/'を付けないでください", "logoPlaceholder": "LOGOのURLアドレスを入力してください。末尾に'/'を付けないでください",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "저장 성공" "saveSuccess": "저장 성공"
}, },
"site": { "site": {
"customHtml": "사용자 정의 HTML",
"customHtmlDescription": "사이트의 <body> 태그 하단에 삽입될 사용자 정의 HTML 코드입니다.",
"keywords": "키워드",
"keywordsDescription": "SEO 목적으로 사용됨",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "LOGO를 표시해야 하는 위치에 사용됩니다", "logoDescription": "LOGO를 표시해야 하는 위치에 사용됩니다",
"logoPlaceholder": "LOGO의 URL 주소를 입력하세요. '/'로 끝나지 않도록 하세요", "logoPlaceholder": "LOGO의 URL 주소를 입력하세요. '/'로 끝나지 않도록 하세요",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Lagring vellykket" "saveSuccess": "Lagring vellykket"
}, },
"site": { "site": {
"customHtml": "Egendefinert HTML",
"customHtmlDescription": "Egendefinert HTML-kode som skal settes inn i bunnen av nettstedets <body>-tag.",
"keywords": "Nøkkelord",
"keywordsDescription": "Brukes til SEO-formål",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Brukes til å vise hvor LOGO skal vises", "logoDescription": "Brukes til å vise hvor LOGO skal vises",
"logoPlaceholder": "Vennligst skriv inn URL-adressen til LOGO, ikke avslutt med '/'", "logoPlaceholder": "Vennligst skriv inn URL-adressen til LOGO, ikke avslutt med '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Zapisano pomyślnie" "saveSuccess": "Zapisano pomyślnie"
}, },
"site": { "site": {
"customHtml": "Niestandardowy HTML",
"customHtmlDescription": "Niestandardowy kod HTML, który ma być wstrzyknięty na końcu tagu <body> witryny.",
"keywords": "Słowa kluczowe",
"keywordsDescription": "Używane do celów SEO",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Miejsce do wyświetlania LOGO", "logoDescription": "Miejsce do wyświetlania LOGO",
"logoPlaceholder": "Wprowadź adres URL LOGO, nie kończ na '/'", "logoPlaceholder": "Wprowadź adres URL LOGO, nie kończ na '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Salvo com sucesso" "saveSuccess": "Salvo com sucesso"
}, },
"site": { "site": {
"customHtml": "HTML Personalizado",
"customHtmlDescription": "Código HTML personalizado a ser inserido no final da tag <body> do site.",
"keywords": "Palavras-chave",
"keywordsDescription": "Usado para fins de SEO",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Usado para exibir a posição onde o LOGO precisa ser mostrado", "logoDescription": "Usado para exibir a posição onde o LOGO precisa ser mostrado",
"logoPlaceholder": "Por favor, insira o URL do LOGO, não termine com '/'", "logoPlaceholder": "Por favor, insira o URL do LOGO, não termine com '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Salvare reușită" "saveSuccess": "Salvare reușită"
}, },
"site": { "site": {
"customHtml": "HTML personalizat",
"customHtmlDescription": "Cod HTML personalizat care va fi injectat în partea de jos a etichetei <body> a site-ului.",
"keywords": "Cuvinte cheie",
"keywordsDescription": "Utilizate în scopuri SEO",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Utilizat pentru a afișa locația unde trebuie să fie prezentat LOGO-ul", "logoDescription": "Utilizat pentru a afișa locația unde trebuie să fie prezentat LOGO-ul",
"logoPlaceholder": "Vă rugăm să introduceți adresa URL a LOGO-ului, fără să se termine cu '/'", "logoPlaceholder": "Vă rugăm să introduceți adresa URL a LOGO-ului, fără să se termine cu '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Успешно сохранено" "saveSuccess": "Успешно сохранено"
}, },
"site": { "site": {
"customHtml": "Пользовательский HTML",
"customHtmlDescription": "Пользовательский HTML-код, который будет вставлен в конец тега <body> сайта.",
"keywords": "Ключевые слова",
"keywordsDescription": "Используется для SEO",
"logo": "ЛОГОТИП", "logo": "ЛОГОТИП",
"logoDescription": "Используется для отображения места, где необходимо показать ЛОГОТИП", "logoDescription": "Используется для отображения места, где необходимо показать ЛОГОТИП",
"logoPlaceholder": "Введите URL-адрес ЛОГОТИПА, не заканчивайте на '/'", "logoPlaceholder": "Введите URL-адрес ЛОГОТИПА, не заканчивайте на '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "บันทึกสำเร็จ" "saveSuccess": "บันทึกสำเร็จ"
}, },
"site": { "site": {
"customHtml": "HTML ที่กำหนดเอง",
"customHtmlDescription": "รหัส HTML ที่กำหนดเองเพื่อแทรกลงในส่วนล่างของแท็ก <body> ของเว็บไซต์.",
"keywords": "คำสำคัญ",
"keywordsDescription": "ใช้สำหรับวัตถุประสงค์ SEO",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "ใช้สำหรับแสดงตำแหน่งที่ต้องการแสดง LOGO", "logoDescription": "ใช้สำหรับแสดงตำแหน่งที่ต้องการแสดง LOGO",
"logoPlaceholder": "กรุณาใส่ URL ของ LOGO โดยไม่ต้องลงท้ายด้วย '/'", "logoPlaceholder": "กรุณาใส่ URL ของ LOGO โดยไม่ต้องลงท้ายด้วย '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Başarıyla kaydedildi" "saveSuccess": "Başarıyla kaydedildi"
}, },
"site": { "site": {
"customHtml": "Özel HTML",
"customHtmlDescription": "Site'in <body> etiketinin altına yerleştirilecek özel HTML kodu.",
"keywords": "Anahtar Kelimeler",
"keywordsDescription": "SEO amaçları için kullanılır",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "LOGO'nun gösterilmesi gereken yeri belirtir", "logoDescription": "LOGO'nun gösterilmesi gereken yeri belirtir",
"logoPlaceholder": "Lütfen LOGO'nun URL adresini girin, '/' ile bitmemelidir", "logoPlaceholder": "Lütfen LOGO'nun URL adresini girin, '/' ile bitmemelidir",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Успішно збережено" "saveSuccess": "Успішно збережено"
}, },
"site": { "site": {
"customHtml": "Користувацький HTML",
"customHtmlDescription": "Користувацький HTML-код, який буде вставлено в кінець тегу <body> сайту.",
"keywords": "Ключові слова",
"keywordsDescription": "Використовується для SEO",
"logo": "ЛОГО", "logo": "ЛОГО",
"logoDescription": "Використовується для відображення місця, де потрібно показати ЛОГО", "logoDescription": "Використовується для відображення місця, де потрібно показати ЛОГО",
"logoPlaceholder": "Будь ласка, введіть URL-адресу ЛОГО, не закінчуйте на '/'", "logoPlaceholder": "Будь ласка, введіть URL-адресу ЛОГО, не закінчуйте на '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "Lưu thành công" "saveSuccess": "Lưu thành công"
}, },
"site": { "site": {
"customHtml": "HTML tùy chỉnh",
"customHtmlDescription": "Mã HTML tùy chỉnh để được chèn vào cuối thẻ <body> của trang.",
"keywords": "Từ khóa",
"keywordsDescription": "Dùng cho mục đích SEO",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "Dùng để hiển thị vị trí cần hiển thị LOGO", "logoDescription": "Dùng để hiển thị vị trí cần hiển thị LOGO",
"logoPlaceholder": "Vui lòng nhập địa chỉ URL của LOGO, không kết thúc bằng '/'", "logoPlaceholder": "Vui lòng nhập địa chỉ URL của LOGO, không kết thúc bằng '/'",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "保存成功" "saveSuccess": "保存成功"
}, },
"site": { "site": {
"customHtml": "自定义HTML",
"customHtmlDescription": "要注入到网站 <body> 标签底部的自定义 HTML 代码。",
"keywords": "关键词",
"keywordsDescription": "用于搜索引擎优化",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "用于显示需要展示 LOGO 的位置", "logoDescription": "用于显示需要展示 LOGO 的位置",
"logoPlaceholder": "请输入 LOGO 的 URL 地址,不要以 '/' 结尾", "logoPlaceholder": "请输入 LOGO 的 URL 地址,不要以 '/' 结尾",
+4
View File
@@ -9,6 +9,10 @@
"saveSuccess": "保存成功" "saveSuccess": "保存成功"
}, },
"site": { "site": {
"customHtml": "自定義HTML",
"customHtmlDescription": "自定義 HTML 代碼,將注入到網站 <body> 標籤的底部。",
"keywords": "關鍵字",
"keywordsDescription": "用於SEO目的",
"logo": "LOGO", "logo": "LOGO",
"logoDescription": "用於顯示需要展示 LOGO 的位置", "logoDescription": "用於顯示需要展示 LOGO 的位置",
"logoPlaceholder": "請輸入 LOGO 的 URL 地址,不要以 '/' 結尾", "logoPlaceholder": "請輸入 LOGO 的 URL 地址,不要以 '/' 結尾",
+3 -1
View File
@@ -37,7 +37,7 @@ declare namespace API {
type ApplicationPlatform = { type ApplicationPlatform = {
ios?: ApplicationVersion[]; ios?: ApplicationVersion[];
mac?: ApplicationVersion[]; macos?: ApplicationVersion[];
linux?: ApplicationVersion[]; linux?: ApplicationVersion[];
android?: ApplicationVersion[]; android?: ApplicationVersion[];
windows?: ApplicationVersion[]; windows?: ApplicationVersion[];
@@ -1010,6 +1010,8 @@ declare namespace API {
site_name: string; site_name: string;
site_desc: string; site_desc: string;
site_logo: string; site_logo: string;
keywords: string;
custom_html: string;
}; };
type SortItem = { type SortItem = {
+3 -1
View File
@@ -35,7 +35,7 @@ declare namespace API {
type ApplicationPlatform = { type ApplicationPlatform = {
ios?: ApplicationVersion[]; ios?: ApplicationVersion[];
mac?: ApplicationVersion[]; macos?: ApplicationVersion[];
linux?: ApplicationVersion[]; linux?: ApplicationVersion[];
android?: ApplicationVersion[]; android?: ApplicationVersion[];
windows?: ApplicationVersion[]; windows?: ApplicationVersion[];
@@ -431,6 +431,8 @@ declare namespace API {
site_name: string; site_name: string;
site_desc: string; site_desc: string;
site_logo: string; site_logo: string;
keywords: string;
custom_html: string;
}; };
type SortItem = { type SortItem = {
@@ -45,7 +45,7 @@ import Subscribe from '../subscribe/page';
const platforms: (keyof API.ApplicationPlatform)[] = [ const platforms: (keyof API.ApplicationPlatform)[] = [
'windows', 'windows',
'mac', 'macos',
'linux', 'linux',
'ios', 'ios',
'android', 'android',
@@ -106,7 +106,7 @@ export default function Content() {
icon={`${ icon={`${
{ {
windows: 'mdi:microsoft-windows', windows: 'mdi:microsoft-windows',
mac: 'uil:apple', macos: 'uil:apple',
linux: 'uil:linux', linux: 'uil:linux',
ios: 'simple-icons:ios', ios: 'simple-icons:ios',
android: 'uil:android', android: 'uil:android',
@@ -261,6 +261,7 @@ export default function Content() {
toast.success( toast.success(
<> <>
<p>{t('copySuccess')}</p> <p>{t('copySuccess')}</p>
<br />
<p>{t('manualImportMessage')}</p> <p>{t('manualImportMessage')}</p>
</>, </>,
); );
@@ -307,7 +308,10 @@ export default function Content() {
<Link href={app.url}>{t('download')}</Link> <Link href={app.url}>{t('download')}</Link>
</Button> </Button>
<CopyToClipboard text={url} onCopy={handleCopy}> <CopyToClipboard
text={getAppSubLink(application.subscribe_type, url) || url}
onCopy={handleCopy}
>
<Button size='sm' className='rounded-l-none p-2'> <Button size='sm' className='rounded-l-none p-2'>
{t('import')} {t('import')}
</Button> </Button>
@@ -202,8 +202,7 @@ export default function ThirdPartyAccounts() {
name: 'Device', name: 'Device',
type: 'OAuth', type: 'OAuth',
}, },
]; ].filter((account) => oauth_methods?.includes(account.id));
// .filter((account) => oauth_methods?.includes(account.id));
const [editValues, setEditValues] = useState<Record<string, any>>({}); const [editValues, setEditValues] = useState<Record<string, any>>({});
+10 -4
View File
@@ -5,10 +5,10 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@workspace/
import { Input } from '@workspace/ui/components/input'; import { Input } from '@workspace/ui/components/input';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction, useRef } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function LoginForm({ export default function LoginForm({
loading, loading,
@@ -38,10 +38,16 @@ export default function LoginForm({
defaultValues: initialValues, defaultValues: initialValues,
}); });
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='email' name='email'
@@ -73,7 +79,7 @@ export default function LoginForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='login' {...field} /> <CloudFlareTurnstile id='login' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+10 -4
View File
@@ -6,11 +6,11 @@ import { Input } from '@workspace/ui/components/input';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { Markdown } from '@workspace/ui/custom-components/markdown'; import { Markdown } from '@workspace/ui/custom-components/markdown';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction, useRef } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import SendCode from '../send-code'; import SendCode from '../send-code';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function RegisterForm({ export default function RegisterForm({
loading, loading,
@@ -76,13 +76,19 @@ export default function RegisterForm({
}, },
}); });
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
{auth.register.stop_register ? ( {auth.register.stop_register ? (
<Markdown>{t('message')}</Markdown> <Markdown>{t('message')}</Markdown>
) : ( ) : (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='email' name='email'
@@ -177,7 +183,7 @@ export default function RegisterForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='register' {...field} /> <CloudFlareTurnstile id='register' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+37 -34
View File
@@ -5,11 +5,11 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@workspace/
import { Input } from '@workspace/ui/components/input'; import { Input } from '@workspace/ui/components/input';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction, useRef } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import SendCode from '../send-code'; import SendCode from '../send-code';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function ResetForm({ export default function ResetForm({
loading, loading,
@@ -43,10 +43,16 @@ export default function ResetForm({
defaultValues: initialValues, defaultValues: initialValues,
}); });
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='email' name='email'
@@ -59,36 +65,33 @@ export default function ResetForm({
</FormItem> </FormItem>
)} )}
/> />
<FormField
{auth?.email?.enable_verify && ( control={form.control}
<FormField name='code'
control={form.control} render={({ field }) => (
name='code' <FormItem>
render={({ field }) => ( <FormControl>
<FormItem> <div className='flex items-center gap-2'>
<FormControl> <Input
<div className='flex items-center gap-2'> disabled={loading}
<Input placeholder='Enter code...'
disabled={loading} type='text'
placeholder='Enter code...' {...field}
type='text' value={field.value as string}
{...field} />
value={field.value as string} <SendCode
/> type='email'
<SendCode params={{
type='email' ...form.getValues(),
params={{ type: 2,
...form.getValues(), }}
type: 2, />
}} </div>
/> </FormControl>
</div> <FormMessage />
</FormControl> </FormItem>
<FormMessage /> )}
</FormItem> />
)}
/>
)}
<FormField <FormField
control={form.control} control={form.control}
name='password' name='password'
@@ -108,7 +111,7 @@ export default function ResetForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='reset' {...field} /> <CloudFlareTurnstile id='reset' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+10 -4
View File
@@ -7,11 +7,11 @@ import { AreaCodeSelect } from '@workspace/ui/custom-components/area-code-select
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction, useState } from 'react'; import { Dispatch, SetStateAction, useRef, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import SendCode from '../send-code'; import SendCode from '../send-code';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function LoginForm({ export default function LoginForm({
loading, loading,
@@ -45,10 +45,16 @@ export default function LoginForm({
const [mode, setMode] = useState<'password' | 'code'>('password'); const [mode, setMode] = useState<'password' | 'code'>('password');
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='telephone' name='telephone'
@@ -138,7 +144,7 @@ export default function LoginForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='login' {...field} /> <CloudFlareTurnstile id='login' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+10 -4
View File
@@ -7,11 +7,11 @@ import { AreaCodeSelect } from '@workspace/ui/custom-components/area-code-select
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { Markdown } from '@workspace/ui/custom-components/markdown'; import { Markdown } from '@workspace/ui/custom-components/markdown';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction, useRef } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import SendCode from '../send-code'; import SendCode from '../send-code';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function RegisterForm({ export default function RegisterForm({
loading, loading,
@@ -63,13 +63,19 @@ export default function RegisterForm({
}, },
}); });
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
{auth.register.stop_register ? ( {auth.register.stop_register ? (
<Markdown>{t('message')}</Markdown> <Markdown>{t('message')}</Markdown>
) : ( ) : (
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='telephone' name='telephone'
@@ -193,7 +199,7 @@ export default function RegisterForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='register' {...field} /> <CloudFlareTurnstile id='register' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+10 -4
View File
@@ -6,11 +6,11 @@ import { Input } from '@workspace/ui/components/input';
import { AreaCodeSelect } from '@workspace/ui/custom-components/area-code-select'; import { AreaCodeSelect } from '@workspace/ui/custom-components/area-code-select';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Dispatch, SetStateAction, useState } from 'react'; import { Dispatch, SetStateAction, useRef, useState } from 'react';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import SendCode from '../send-code'; import SendCode from '../send-code';
import CloudFlareTurnstile from '../turnstile'; import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
export default function ResetForm({ export default function ResetForm({
loading, loading,
@@ -46,10 +46,16 @@ export default function ResetForm({
defaultValues: initialValues, defaultValues: initialValues,
}); });
const turnstile = useRef<TurnstileRef>(null);
const handleSubmit = form.handleSubmit((data) => {
onSubmit(data);
turnstile.current?.reset();
});
return ( return (
<> <>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className='grid gap-6'> <form onSubmit={handleSubmit} className='grid gap-6'>
<FormField <FormField
control={form.control} control={form.control}
name='telephone' name='telephone'
@@ -136,7 +142,7 @@ export default function ResetForm({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<CloudFlareTurnstile id='reset' {...field} /> <CloudFlareTurnstile id='reset' {...field} ref={turnstile} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
+24 -11
View File
@@ -2,26 +2,37 @@
import { useLocale } from 'next-intl'; import { useLocale } from 'next-intl';
import { useTheme } from 'next-themes'; import { useTheme } from 'next-themes';
import { useEffect } from 'react'; import { forwardRef, useEffect, useImperativeHandle } from 'react';
import Turnstile, { useTurnstile } from 'react-turnstile'; import Turnstile, { useTurnstile } from 'react-turnstile';
import useGlobalStore from '@/config/use-global'; import useGlobalStore from '@/config/use-global';
export default function CloudFlareTurnstile({ export type TurnstileRef = {
id, reset: () => void;
value, };
onChange,
}: { const CloudFlareTurnstile = forwardRef<
id?: string; TurnstileRef,
value?: null | string; {
onChange: (value?: string) => void; id?: string;
}) { value?: null | string;
onChange: (value?: string) => void;
}
>(function CloudFlareTurnstile({ id, value, onChange }, ref) {
const { common } = useGlobalStore(); const { common } = useGlobalStore();
const { verify } = common; const { verify } = common;
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const locale = useLocale(); const locale = useLocale();
const turnstile = useTurnstile(); const turnstile = useTurnstile();
useImperativeHandle(
ref,
() => ({
reset: () => turnstile.reset(),
}),
[turnstile],
);
useEffect(() => { useEffect(() => {
if (value === '') { if (value === '') {
turnstile.reset(); turnstile.reset();
@@ -52,4 +63,6 @@ export default function CloudFlareTurnstile({
/> />
) )
); );
} });
export default CloudFlareTurnstile;
+5
View File
@@ -43,6 +43,7 @@ export async function generateMetadata(): Promise<Metadata> {
template: `%s | ${site?.site_name || 'PPanel'}`, template: `%s | ${site?.site_name || 'PPanel'}`,
}, },
description: site?.site_desc || '', description: site?.site_desc || '',
keywords: site?.keywords || '',
icons: { icons: {
icon: site?.site_logo icon: site?.site_logo
? [ ? [
@@ -114,6 +115,10 @@ export default async function RootLayout({
{children} {children}
</Providers> </Providers>
</NextIntlClientProvider> </NextIntlClientProvider>
<div
id='custom_html'
dangerouslySetInnerHTML={{ __html: config?.site.custom_html || '' }}
/>
</body> </body>
</html> </html>
); );
+2
View File
@@ -20,6 +20,8 @@ export const useGlobalStore = create<GlobalStore>((set, get) => ({
site_name: '', site_name: '',
site_desc: '', site_desc: '',
site_logo: '', site_logo: '',
keywords: '',
custom_html: '',
}, },
verify: { verify: {
turnstile_site_key: '', turnstile_site_key: '',
+3 -1
View File
@@ -35,7 +35,7 @@ declare namespace API {
type ApplicationPlatform = { type ApplicationPlatform = {
ios?: ApplicationVersion[]; ios?: ApplicationVersion[];
mac?: ApplicationVersion[]; macos?: ApplicationVersion[];
linux?: ApplicationVersion[]; linux?: ApplicationVersion[];
android?: ApplicationVersion[]; android?: ApplicationVersion[];
windows?: ApplicationVersion[]; windows?: ApplicationVersion[];
@@ -431,6 +431,8 @@ declare namespace API {
site_name: string; site_name: string;
site_desc: string; site_desc: string;
site_logo: string; site_logo: string;
keywords: string;
custom_html: string;
}; };
type SortItem = { type SortItem = {
+3 -1
View File
@@ -29,7 +29,7 @@ declare namespace API {
type ApplicationPlatform = { type ApplicationPlatform = {
ios?: ApplicationVersion[]; ios?: ApplicationVersion[];
mac?: ApplicationVersion[]; macos?: ApplicationVersion[];
linux?: ApplicationVersion[]; linux?: ApplicationVersion[];
android?: ApplicationVersion[]; android?: ApplicationVersion[];
windows?: ApplicationVersion[]; windows?: ApplicationVersion[];
@@ -625,6 +625,8 @@ declare namespace API {
site_name: string; site_name: string;
site_desc: string; site_desc: string;
site_logo: string; site_logo: string;
keywords: string;
custom_html: string;
}; };
type SortItem = { type SortItem = {
+2 -2
View File
@@ -56,13 +56,13 @@ export function Logout() {
} }
} }
export function getPlatform(): 'windows' | 'mac' | 'linux' | 'android' | 'ios' | 'harmony' { export function getPlatform(): 'windows' | 'macos' | 'linux' | 'android' | 'ios' | 'harmony' {
const parser = new UAParser(); const parser = new UAParser();
const os = parser.getOS(); const os = parser.getOS();
const osName = os.name?.toLowerCase() || ''; const osName = os.name?.toLowerCase() || '';
if (osName.includes('windows')) return 'windows'; if (osName.includes('windows')) return 'windows';
if (osName.includes('mac')) return 'mac'; if (osName.includes('mac')) return 'macos';
if ( if (
osName.includes('linux') || osName.includes('linux') ||
osName.includes('ubuntu') || osName.includes('ubuntu') ||
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ppanel-web", "name": "ppanel-web",
"version": "1.0.0-beta.14", "version": "1.0.0-beta.19",
"private": true, "private": true,
"homepage": "https://github.com/perfect-panel/ppanel-web", "homepage": "https://github.com/perfect-panel/ppanel-web",
"bugs": { "bugs": {