Skip to content

Commit 8905339

Browse files
committed
feat(frontend): Add counter text length for inputs inside main settings AdminCP
1 parent 848e9f0 commit 8905339

File tree

10 files changed

+149
-30
lines changed

10 files changed

+149
-30
lines changed

apps/frontend/src/plugins/admin/langs/en.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,16 @@
7777
"title": "Main Settings",
7878
"desc": "These settings are used to configure the main features of your website.",
7979
"name": {
80-
"label": "Name Site"
80+
"label": "Name Site",
81+
"seo": "Recommended between 50-60 characters."
8182
},
8283
"short_name": {
83-
"label": "Short Name Site"
84+
"label": "Short Name Site",
85+
"seo": "Recommended max 20 characters."
8486
},
8587
"description": {
86-
"label": "Description Site"
88+
"label": "Description Site",
89+
"seo": "Recommended between 120-160 characters."
8790
},
8891
"contact_email": {
8992
"label": "Contact Email",

packages/backend/src/core/admin/languages/services/create.service.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {
88
} from '@nestjs/common';
99
import { ConfigService } from '@nestjs/config';
1010
import { existsSync } from 'fs';
11-
import { cp } from 'fs/promises';
11+
import { cp, readFile, writeFile } from 'fs/promises';
1212
import { join } from 'path';
1313
import {
1414
CreateLanguagesAdminBody,
1515
LanguagesAdminObj,
1616
} from 'vitnode-shared/admin/language.dto';
17+
import { ManifestWithLang } from 'vitnode-shared/manifest.dto';
1718

1819
@Injectable()
1920
export class CreateLanguagesAdminService {
@@ -77,6 +78,12 @@ export class CreateLanguagesAdminService {
7778
}
7879

7980
await this.cloneLangInPlugins(code);
81+
const manifestPath = join(
82+
ABSOLUTE_PATHS.uploads.public,
83+
'assets',
84+
code,
85+
'manifest.webmanifest',
86+
);
8087

8188
// Clone JSON for manifest
8289
await cp(
@@ -86,14 +93,16 @@ export class CreateLanguagesAdminService {
8693
'en',
8794
'manifest.webmanifest',
8895
),
89-
join(
90-
ABSOLUTE_PATHS.uploads.public,
91-
'assets',
92-
code,
93-
'manifest.webmanifest',
94-
),
96+
manifestPath,
9597
);
9698

99+
// Change language code in manifest
100+
const manifest = JSON.parse(
101+
await readFile(manifestPath, 'utf8'),
102+
) as ManifestWithLang;
103+
manifest.lang = code;
104+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
105+
97106
const [newLanguage] = await this.databaseService.db
98107
.insert(core_languages)
99108
.values({

packages/backend/src/core/middleware/services/show.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class ShowMiddlewareService {
2323
private readonly configService: ConfigHelperService,
2424
) {}
2525

26-
protected async getManifest({
26+
protected async getManifests({
2727
langCodes,
2828
}: {
2929
langCodes: string[];
@@ -72,7 +72,7 @@ export class ShowMiddlewareService {
7272
if (!plugin_code_default) {
7373
throw new InternalServerErrorException('Plugin not found');
7474
}
75-
const manifest = await this.getManifest({
75+
const manifest = await this.getManifests({
7676
langCodes: langs.map(lang => lang.code),
7777
});
7878
const SSOs = await this.ssoHelper.getActiveSSOs();

packages/frontend/src/components/form/fields/checkbox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function AutoFormCheckbox({
2626
return (
2727
<div
2828
className={cn({
29-
'flex items-start space-x-3 space-y-0 rounded-md border p-4':
29+
'flex items-start space-x-3 rounded-md border p-4':
3030
label && description && theme === 'vertical',
3131
'@xs:flex-row @xs:gap-6 flex w-full flex-col items-start gap-2':
3232
theme === 'horizontal',

packages/frontend/src/components/form/fields/common/wrapper.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const AutoFormWrapper = ({
1414
<FormItem
1515
className={cn(
1616
{
17-
'@xs:flex-row @xs:gap-6 flex w-full flex-col items-start gap-2 space-y-0':
17+
'@xs:flex-row @xs:gap-6 flex w-full flex-col items-start gap-2':
1818
theme === 'horizontal',
1919
},
2020
className,

packages/frontend/src/components/form/fields/radio-group.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,11 @@ export function AutoFormRadioGroup({
7676
const description = labels?.[value[1]]?.description;
7777

7878
return (
79-
<FormItem
80-
className="flex items-center gap-3 space-y-0"
81-
key={value}
82-
>
79+
<FormItem className="flex items-center gap-3" key={value}>
8380
<FormControl>
8481
<RadioGroupItem value={value[1]} />
8582
</FormControl>
86-
<FormLabel className="flex items-center space-y-0 font-normal">
83+
<FormLabel className="flex items-center font-normal">
8784
<span>{label}</span>
8885

8986
{description && (

packages/frontend/src/components/ui/text-language-input.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,23 @@ interface Props
2121
> {
2222
className?: string;
2323
onChange: (value: StringLanguage[]) => void;
24+
onLanguageChange?: (language: string) => void;
2425
value: StringLanguage[];
2526
}
2627

2728
export const StringLanguageInput = ({
2829
className,
2930
onChange,
3031
value,
32+
onLanguageChange,
3133
...props
3234
}: Props) => {
3335
const locale = useLocale();
34-
const { languages: languagesFromGlobal } = useMiddlewareData();
35-
const defaultLanguage =
36-
languagesFromGlobal.find(item => item.default)?.code ?? 'en';
36+
const { languages: languagesFromGlobal, languages_code_default } =
37+
useMiddlewareData();
3738
const languages = languagesFromGlobal.filter(item => item.allow_in_input);
3839
const [selectedLanguage, setSelectedLanguage] = React.useState(
39-
locale || defaultLanguage,
40+
locale || languages_code_default,
4041
);
4142
const valueAsArray = Array.isArray(value) ? value : [];
4243
const currentValue =
@@ -76,7 +77,13 @@ export const StringLanguageInput = ({
7677
/>
7778

7879
{languages.length > 1 && (
79-
<Select onValueChange={setSelectedLanguage} value={selectedLanguage}>
80+
<Select
81+
onValueChange={val => {
82+
setSelectedLanguage(val);
83+
onLanguageChange?.(val);
84+
}}
85+
value={selectedLanguage}
86+
>
8087
<FormControl>
8188
<SelectTrigger className="w-40">
8289
<SelectValue />
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { AutoFormComponentProps } from '@/components/form/auto-form';
2+
import { AutoFormStringLanguageInput } from '@/components/form/fields/text-language-input';
3+
import { Badge } from '@/components/ui/badge';
4+
import { TooltipWrapper } from '@/components/ui/tooltip';
5+
import { useLocale, useTranslations } from 'next-intl';
6+
import React from 'react';
7+
import { StringLanguage } from 'vitnode-shared/string-language.dto';
8+
9+
export const DescFieldContentMainSettingsCoreAdmin = ({
10+
defaultLanguage,
11+
...props
12+
}: AutoFormComponentProps & {
13+
defaultLanguage: string;
14+
}) => {
15+
const t = useTranslations('admin.core.settings.main');
16+
const locale = useLocale();
17+
const [selectedLanguage, setSelectedLanguage] = React.useState(
18+
locale || defaultLanguage,
19+
);
20+
const current: StringLanguage = props.field.value.find(
21+
(item: StringLanguage) => item.language_code === selectedLanguage,
22+
) ?? {
23+
language_code: selectedLanguage,
24+
value: '',
25+
};
26+
27+
return (
28+
<>
29+
<AutoFormStringLanguageInput
30+
{...props}
31+
onLanguageChange={setSelectedLanguage}
32+
/>
33+
34+
<div className="flex w-32 items-center justify-center">
35+
<TooltipWrapper content={t('description.seo')}>
36+
<Badge
37+
className="mt-1"
38+
variant={
39+
current.value.length >= 50 && current.value.length < 120
40+
? 'outline'
41+
: current.value.length > 160 || current.value.length < 50
42+
? 'destructive'
43+
: 'default'
44+
}
45+
>
46+
{current.value.length}/160
47+
</Badge>
48+
</TooltipWrapper>
49+
</div>
50+
</>
51+
);
52+
};

packages/frontend/src/views/admin/views/core/settings/main/content.tsx

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import { AutoForm } from '@/components/form/auto-form';
44
import { AutoFormInput } from '@/components/form/fields/input';
55
import { AutoFormSelect } from '@/components/form/fields/select';
6-
import { AutoFormStringLanguageInput } from '@/components/form/fields/text-language-input';
6+
import { Badge } from '@/components/ui/badge';
7+
import { TooltipWrapper } from '@/components/ui/tooltip';
78
import { useTranslations } from 'next-intl';
89
import { ShowMiddlewareObj } from 'vitnode-shared/middleware.dto';
910

11+
import { DescFieldContentMainSettingsCoreAdmin } from './components/desc';
1012
import { useSettingsCoreAdmin } from './hooks/use-settings-core-admin';
1113

1214
export const ContentMainSettingsCoreAdmin = (props: ShowMiddlewareObj) => {
@@ -18,17 +20,66 @@ export const ContentMainSettingsCoreAdmin = (props: ShowMiddlewareObj) => {
1820
fields={[
1921
{
2022
id: 'site_name',
21-
component: AutoFormInput,
23+
component: props => (
24+
<>
25+
<AutoFormInput {...props} />
26+
<div className="flex w-32 items-center justify-center">
27+
<TooltipWrapper content={t('name.seo')}>
28+
<Badge
29+
className="mt-1"
30+
variant={
31+
props.field.value.length <= 50 &&
32+
props.field.value.length >= 3
33+
? 'outline'
34+
: props.field.value.length > 60 ||
35+
props.field.value.length < 3
36+
? 'destructive'
37+
: 'default'
38+
}
39+
>
40+
{props.field.value.length}/60
41+
</Badge>
42+
</TooltipWrapper>
43+
</div>
44+
</>
45+
),
2246
label: t('name.label'),
2347
},
2448
{
2549
id: 'site_short_name',
26-
component: AutoFormInput,
50+
component: props => (
51+
<>
52+
<AutoFormInput {...props} />
53+
<div className="flex w-32 items-center justify-center">
54+
<TooltipWrapper content={t('short_name.seo')}>
55+
<Badge
56+
className="mt-1"
57+
variant={
58+
props.field.value.length <= 5 &&
59+
props.field.value.length >= 3
60+
? 'outline'
61+
: props.field.value.length > 20 ||
62+
props.field.value.length < 3
63+
? 'destructive'
64+
: 'default'
65+
}
66+
>
67+
{props.field.value.length}/20
68+
</Badge>
69+
</TooltipWrapper>
70+
</div>
71+
</>
72+
),
2773
label: t('short_name.label'),
2874
},
2975
{
3076
id: 'site_description',
31-
component: AutoFormStringLanguageInput,
77+
component: componentProps => (
78+
<DescFieldContentMainSettingsCoreAdmin
79+
{...componentProps}
80+
defaultLanguage={props.languages_code_default}
81+
/>
82+
),
3283
label: t('description.label'),
3384
},
3485
{

packages/frontend/src/views/admin/views/core/settings/main/hooks/use-settings-core-admin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export const useSettingsCoreAdmin = (data: ShowMiddlewareObj) => {
1111
const t = useTranslations('core.global');
1212

1313
const formSchema = z.object({
14-
site_name: z.string().min(1).default(data.site_name),
15-
site_short_name: z.string().min(1).default(data.site_short_name),
14+
site_name: z.string().min(3).default(data.site_name),
15+
site_short_name: z.string().min(3).default(data.site_short_name),
1616
site_description: zodLanguageInput
1717
.default(data.site_description ?? [])
1818
.optional(),

0 commit comments

Comments
 (0)