Skip to content

Commit

Permalink
update app title in appmetadata and text resource only from settings
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren committed Jan 23, 2025
1 parent d2043cc commit b9bf08b
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 213 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { TabHeader } from '../../TabHeader';
import type { AppConfig } from 'app-shared/types/AppConfig';
import { ErrorMessage } from '@digdir/designsystemet-react';
import { getRepositoryType } from 'app-shared/utils/repository';
import { useAppConfigMutation } from 'app-development/hooks/mutations';
import { useAppConfigQuery } from 'app-development/hooks/queries';
import { useAppMetadataQuery, useRepoMetadataQuery } from 'app-shared/hooks/queries';
import { mergeQueryStatuses } from 'app-shared/utils/tanstackQueryUtils';
import { LoadingTabData } from '../../LoadingTabData';
Expand All @@ -15,20 +12,24 @@ import { CreatedFor } from './CreatedFor';
import { TabContent } from '../../TabContent';
import { usePreviewContext } from '../../../../../../../../contexts/PreviewContext';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import type { ServiceNames } from './InputFields/InputFields';
import { RecommendedLanguageFlags } from './InputFields/InputFields';
import { useUpdateAppTitle } from '../../../hooks/useUpdateAppTitle';
import { useLanguagesQuery } from '../../../../../../../../hooks/queries';
import { ArrayUtils } from '@studio/pure-functions';

export const AboutTab = (): React.ReactElement => {
const { t } = useTranslation();
const { org, app } = useStudioEnvironmentParams();

const repositoryType = getRepositoryType(org, app);

const { doReloadPreview } = usePreviewContext();

const {
status: appConfigStatus,
data: appConfigData,
error: appConfigError,
} = useAppConfigQuery(org, app);
status: appLangCodesStatus,
data: appLangCodesData,
error: appLangCodesError,
} = useLanguagesQuery(org, app);
const {
status: repositoryStatus,
data: repositoryData,
Expand All @@ -40,24 +41,24 @@ export const AboutTab = (): React.ReactElement => {
error: applicationMetadataError,
} = useAppMetadataQuery(org, app);

const { mutate: updateAppConfigMutation } = useAppConfigMutation(org, app);
const updateAppTitle = useUpdateAppTitle(applicationMetadataData);

const handleSaveAppConfig = (appConfig: AppConfig) => {
if (appConfigData.serviceName !== appConfig.serviceName) {
const handleSaveServiceName = (serviceName: string, language: string) => {
if (applicationMetadataData.title[language] !== serviceName) {
doReloadPreview();
}
updateAppConfigMutation(appConfig);
updateAppTitle(language, serviceName);
};

const displayContent = () => {
switch (mergeQueryStatuses(appConfigStatus, repositoryStatus, applicationMetadataStatus)) {
switch (mergeQueryStatuses(appLangCodesStatus, repositoryStatus, applicationMetadataStatus)) {
case 'pending': {
return <LoadingTabData />;
}
case 'error': {
return (
<TabDataError>
{appConfigError && <ErrorMessage>{appConfigError.message}</ErrorMessage>}
{appLangCodesError && <ErrorMessage>{appLangCodesError.message}</ErrorMessage>}
{repositoryError && <ErrorMessage>{repositoryError.message}</ErrorMessage>}
{applicationMetadataError && (
<ErrorMessage>{applicationMetadataError.message}</ErrorMessage>
Expand All @@ -66,9 +67,19 @@ export const AboutTab = (): React.ReactElement => {
);
}
case 'success': {
const appTitles: ServiceNames<(typeof appLangCodesData)[number]> = getAppTitlesToDisplay(
applicationMetadataData.title,
appLangCodesData,
);
debugger;
return (
<>
<InputFields appConfig={appConfigData} onSave={handleSaveAppConfig} />
<InputFields
appLangCodes={appLangCodesData}
onSave={handleSaveServiceName}
repositoryName={repositoryData.name}
serviceNames={appTitles}
/>
<CreatedFor
repositoryType={repositoryType}
repository={repositoryData}
Expand All @@ -86,3 +97,16 @@ export const AboutTab = (): React.ReactElement => {
</TabContent>
);
};

export const getAppTitlesToDisplay = (
appMetadataTitles: ServiceNames<(typeof appLangCodesData)[number]>,
appLangCodesData: string[],
): ServiceNames<(typeof appLangCodesData)[number]> => {
const recommendedLanguages: string[] = Object.keys(RecommendedLanguageFlags);
const appLangCodesIncludingRecommended: string[] = ArrayUtils.removeDuplicates(
recommendedLanguages.concat(Object.keys(appMetadataTitles)).concat(appLangCodesData),

Check failure on line 107 in frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx

View workflow job for this annotation

GitHub Actions / Testing

SettingsModal › opens the SettingsModal when the button is clicked

TypeError: Cannot convert undefined or null to object at Function.keys (<anonymous>) at keys (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:107:40) at getAppTitlesToDisplay (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:70:76) at displayContent (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:96:8) at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:2656:157) at updateFunctionComponent (../node_modules/react-dom/cjs/react-dom.development.js:3232:388) at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:3640:549) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4696:93) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:4504:159) at recoverFromConcurrentError (../node_modules/react-dom/cjs/react-dom.development.js:4383:170) at performSyncWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:4442:126) at flushSyncCallbacks (../node_modules/react-dom/cjs/react-dom.development.js:2155:108) at ../node_modules/react-dom/cjs/react-dom.development.js:4323:1 at invokeTheCallbackFunction (../node_modules/jsdom/lib/jsdom/living/generated/Function.js:19:26) at ../node_modules/jsdom/lib/jsdom/browser/Window.js:554:9

Check failure on line 107 in frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx

View workflow job for this annotation

GitHub Actions / Testing

SettingsModal › opens the SettingsModal when the button is clicked

Expected test not to call console.error(). If the error is expected, test for it explicitly by mocking it out using jest.spyOn(console, 'error').mockImplementation() and test that the warning occurs. Error: Uncaught [TypeError: Cannot convert undefined or null to object] at reportException (../node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4706:1) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:4504:159) at performSyncWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:4438:63) at flushSyncCallbacks (../node_modules/react-dom/cjs/react-dom.development.js:2155:108) at ../node_modules/react-dom/cjs/react-dom.development.js:4323:1 at invokeTheCallbackFunction (../node_modules/jsdom/lib/jsdom/living/generated/Function.js:19:26) at ../node_modules/jsdom/lib/jsdom/browser/Window.js:554:9 at processTimers (../node:internal/timers:519:7) { detail: TypeError: Cannot convert undefined or null to object at Function.keys (<anonymous>) at keys (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:107:40) at getAppTitlesToDisplay (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:70:76) at displayContent (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:96:8) at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:2656:157) at updateFunctionComponent (../node_modules/react-dom/cjs/react-dom.development.js:3232:388) at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:3640:549) at HTMLUnknownElement.callCallback (../node_modules/react-dom/cjs/react-dom.development.js:730:119) at HTMLUnknownElement.callTheUserObjectsOperation (../node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4706:1) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/reac

Check failure on line 107 in frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx

View workflow job for this annotation

GitHub Actions / Testing

SettingsModal › closes the SettingsModal when the modal is closed

TypeError: Cannot convert undefined or null to object at Function.keys (<anonymous>) at keys (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:107:40) at getAppTitlesToDisplay (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:70:76) at displayContent (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:96:8) at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:2656:157) at updateFunctionComponent (../node_modules/react-dom/cjs/react-dom.development.js:3232:388) at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:3640:549) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4696:93) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:4504:159) at recoverFromConcurrentError (../node_modules/react-dom/cjs/react-dom.development.js:4383:170) at performSyncWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:4442:126) at flushSyncCallbacks (../node_modules/react-dom/cjs/react-dom.development.js:2155:108) at ../node_modules/react-dom/cjs/react-dom.development.js:4323:1 at invokeTheCallbackFunction (../node_modules/jsdom/lib/jsdom/living/generated/Function.js:19:26) at ../node_modules/jsdom/lib/jsdom/browser/Window.js:554:9

Check failure on line 107 in frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx

View workflow job for this annotation

GitHub Actions / Testing

SettingsModal › closes the SettingsModal when the modal is closed

Expected test not to call console.error(). If the error is expected, test for it explicitly by mocking it out using jest.spyOn(console, 'error').mockImplementation() and test that the warning occurs. Error: Uncaught [TypeError: Cannot convert undefined or null to object] at reportException (../node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:353:9) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4706:1) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/react-dom.development.js:4504:159) at performSyncWorkOnRoot (../node_modules/react-dom/cjs/react-dom.development.js:4438:63) at flushSyncCallbacks (../node_modules/react-dom/cjs/react-dom.development.js:2155:108) at ../node_modules/react-dom/cjs/react-dom.development.js:4323:1 at invokeTheCallbackFunction (../node_modules/jsdom/lib/jsdom/living/generated/Function.js:19:26) at ../node_modules/jsdom/lib/jsdom/browser/Window.js:554:9 at processTimers (../node:internal/timers:519:7) { detail: TypeError: Cannot convert undefined or null to object at Function.keys (<anonymous>) at keys (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:107:40) at getAppTitlesToDisplay (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:70:76) at displayContent (app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AboutTab/AboutTab.tsx:96:8) at renderWithHooks (../node_modules/react-dom/cjs/react-dom.development.js:2656:157) at updateFunctionComponent (../node_modules/react-dom/cjs/react-dom.development.js:3232:388) at beginWork (../node_modules/react-dom/cjs/react-dom.development.js:3640:549) at HTMLUnknownElement.callCallback (../node_modules/react-dom/cjs/react-dom.development.js:730:119) at HTMLUnknownElement.callTheUserObjectsOperation (../node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30) at innerInvokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25) at invokeEventListeners (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3) at HTMLUnknownElementImpl._dispatch (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9) at HTMLUnknownElementImpl.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17) at HTMLUnknownElement.dispatchEvent (../node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34) at Object.invokeGuardedCallbackDev (../node_modules/react-dom/cjs/react-dom.development.js:750:45) at invokeGuardedCallback (../node_modules/react-dom/cjs/react-dom.development.js:771:126) at beginWork$1 (../node_modules/react-dom/cjs/react-dom.development.js:4706:1) at performUnitOfWork (../node_modules/react-dom/cjs/react-dom.development.js:4522:270) at workLoopSync (../node_modules/react-dom/cjs/react-dom.development.js:4508:30) at renderRootSync (../node_modules/react-dom/cjs/reac
);
return Object.fromEntries(
appLangCodesIncludingRecommended.map((lang) => [lang, appMetadataTitles[lang]]),
);
};
Original file line number Diff line number Diff line change
@@ -1,66 +1,60 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import type { InputFieldsProps } from './InputFields';
import type { InputFieldsProps, ServiceNames } from './InputFields';
import { InputFields } from './InputFields';
import { mockAppConfig } from 'app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/appConfigMock';
import { textMock } from '@studio/testing/mocks/i18nMock';
import userEvent from '@testing-library/user-event';

const mockNewText: string = 'test';
const langNb = 'nb';
const appLangCodes: string[] = [langNb];
const onSave = jest.fn();
const repositoryName = 'repositoryName';
const serviceNames: ServiceNames<(typeof appLangCodes)[number]> = {
[langNb]: 'mockAppTitleNb',
};

const mockOnSave = jest.fn();

const defaultProps: InputFieldsProps = {
appConfig: mockAppConfig,
onSave: mockOnSave,
const defaultProps: InputFieldsProps<(typeof appLangCodes)[number]> = {
appLangCodes,
onSave,
repositoryName,
serviceNames,
};

describe('InputFields', () => {
afterEach(jest.clearAllMocks);

it('displays the "repo" input as readonly', async () => {
it('displays the "repo" input as readonly', () => {
render(<InputFields {...defaultProps} />);

const repoNameInput = screen.getByLabelText(textMock('settings_modal.about_tab_repo_label'));
expect(repoNameInput).toHaveValue(mockAppConfig.repositoryName);
expect(repoNameInput).toHaveValue(repositoryName);
expect(repoNameInput).toHaveAttribute('readonly');
});

it('displays correct value in "name" input field, and updates the value on change', async () => {
it('displays correct value in nb "name" input field, and updates the value on change', async () => {
const user = userEvent.setup();
render(<InputFields {...defaultProps} />);

const appName = screen.getByLabelText(textMock('settings_modal.about_tab_name_label'));
expect(appName).toHaveValue(mockAppConfig.serviceName);

const appName = screen.getByLabelText(textMock('language.nb'));
expect(appName).toHaveValue(serviceNames.nb);
await user.clear(appName);
await user.type(appName, mockNewText);

expect(appName).toHaveValue(`${mockAppConfig.serviceName}${mockNewText}`);
});

it('displays correct value in "alternative id" input field, and updates the value on change', async () => {
const user = userEvent.setup();
render(<InputFields {...defaultProps} />);

const altId = screen.getByLabelText(textMock('settings_modal.about_tab_alt_id_label'));
expect(altId).toHaveValue(mockAppConfig.serviceId);

await user.type(altId, mockNewText);

expect(altId).toHaveValue(`${mockAppConfig.serviceId}${mockNewText}`);
expect(appName).toHaveValue(mockNewText);
});

describe('InputFields Validation', () => {
const user = userEvent.setup();
const appNameLabel = textMock('settings_modal.about_tab_name_label');
const appNameLabel = textMock('language.nb');

it('should save changes when the form is valid', async () => {
render(<InputFields {...defaultProps} />);
const appName = screen.getByLabelText(appNameLabel);

await user.type(appName, mockNewText);
await user.tab();
expect(mockOnSave).toHaveBeenCalledTimes(1);
expect(onSave).toHaveBeenCalledTimes(1);
});

it('should not save changes when form is invalid', async () => {
Expand All @@ -69,7 +63,7 @@ describe('InputFields', () => {

await user.clear(appName);
await user.tab();
expect(mockOnSave).toHaveBeenCalledTimes(0);
expect(onSave).toHaveBeenCalledTimes(0);
});

it('should toggle error message based on form validation', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,125 @@
import type { FormEvent, ReactNode } from 'react';
import type { ChangeEvent, ReactNode } from 'react';
import React, { useState } from 'react';
import classes from './InputFields.module.css';
import { useTranslation } from 'react-i18next';
import type { AppConfig } from 'app-shared/types/AppConfig';
import { Textfield } from '@digdir/designsystemet-react';
import {
StudioTextfield,
StudioLabelAsParagraph,
StudioParagraph,
StudioIconTextfield,
} from '@studio/components';

type AppConfigForm = Pick<AppConfig, 'serviceName' | 'serviceId'>;
export type ServiceNames<T extends string> = {
[key in T]: string | undefined;
};

export enum RecommendedLanguageFlags {
nb = '🇳🇴',
nn = '🇳🇴',
en = '🇬🇧',
}

export type InputFieldsProps = {
appConfig: AppConfig;
onSave: (appConfig: AppConfig) => void;
export type InputFieldsProps<T extends string> = {
appLangCodes: string[];
onSave: (serviceName: string, language: string) => void;
repositoryName: string;
serviceNames: ServiceNames<T>;
};

export const InputFields = ({ appConfig, onSave }: InputFieldsProps): ReactNode => {
export function InputFields<T extends string>({
repositoryName,
...rest
}: InputFieldsProps<T>): ReactNode {
const { t } = useTranslation();

const [appConfigFormErrors, setAppConfigFormErrors] = useState<
Pick<AppConfigForm, 'serviceName'>
>({ serviceName: '' });
debugger;
return (
<div className={classes.wrapper}>
<StudioTextfield
label={t('settings_modal.about_tab_repo_label')}
description={t('settings_modal.about_tab_repo_description')}
size='small'
value={repositoryName}
readOnly
/>
<EditServiceNames {...rest} />
</div>
);
}

const handleAppConfigFormBlur = (event: FormEvent<HTMLFormElement>) => {
const formData = new FormData(event.currentTarget);
const form = Object.fromEntries(formData) as AppConfigForm;
const isFormValid = validateForm(form);
if (isFormValid) {
onSave({ ...appConfig, ...form });
}
type EditServiceNamesNameProps<T extends string> = Omit<InputFieldsProps<T>, 'repositoryName'>;

function EditServiceNames<T extends string>({
serviceNames,
...rest
}: EditServiceNamesNameProps<T>): React.ReactElement {
const { t } = useTranslation();

const appTitleLanguages = Object.keys(serviceNames);

return (
<div>
<StudioLabelAsParagraph>{t('settings_modal.about_tab_name_label')}</StudioLabelAsParagraph>
<StudioParagraph size={'small'}>
{t('settings_modal.about_tab_name_description')}
</StudioParagraph>
{appTitleLanguages.map((lang: string) => (
<EditServiceNameForLanguage
key={lang}
language={lang}
serviceName={serviceNames[lang]}
{...rest}
/>
))}
</div>
);
}

type EditServiceNameForLanguageProps = {
appLangCodes: string[];
language: string;
onSave: (serviceName: string, language: string) => void;
serviceName: string;
};

function EditServiceNameForLanguage({
appLangCodes,
language,
onSave,
serviceName,
}: EditServiceNameForLanguageProps): React.ReactElement {
const { t } = useTranslation();
const [serviceNameError, setServiceNameError] = useState<string>('');

const appHasLanguageTranslation = appLangCodes.includes(language);

const handleOnBlur = (event: ChangeEvent<HTMLInputElement>) => {
const isValid = validateServiceName(event.target.value);
if (isValid) onSave(event.target.value, language);
};

const validateForm = (form: AppConfigForm): Boolean => {
if (form.serviceName.length <= 0) {
setAppConfigFormErrors({ serviceName: t('settings_modal.about_tab_name_error') });
const validateServiceName = (newServiceName: string): Boolean => {
if (newServiceName.length <= 0) {
setServiceNameError(t('settings_modal.about_tab_name_error'));
return false;
}
setAppConfigFormErrors({ serviceName: '' });
setServiceNameError('');
return true;
};

const description: string =
!appHasLanguageTranslation && t('settings_modal.about_tab_app_title_no_translation_file');

return (
<form className={classes.wrapper} onBlur={handleAppConfigFormBlur}>
<Textfield
label={t('settings_modal.about_tab_repo_label')}
description={t('settings_modal.about_tab_repo_description')}
size='small'
defaultValue={appConfig.repositoryName}
readOnly
/>
<Textfield
label={t('settings_modal.about_tab_name_label')}
description={t('settings_modal.about_tab_name_description')}
size='small'
name='serviceName'
error={appConfigFormErrors.serviceName}
defaultValue={appConfig.serviceName}
/>
<Textfield
label={t('settings_modal.about_tab_alt_id_label')}
description={t('settings_modal.about_tab_alt_id_description')}
size='small'
name='serviceId'
defaultValue={appConfig.serviceId}
/>
</form>
<StudioIconTextfield
icon={RecommendedLanguageFlags[language]}
size='small'
label={t(`language.${language}`)}
description={description}
error={serviceNameError}
value={serviceName}
onBlur={handleOnBlur}
readOnly={!appHasLanguageTranslation}
/>
);
};
}
Loading

0 comments on commit b9bf08b

Please sign in to comment.