mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat(console): : add application branding tab (#5176)
* feat(console,phrases): add new third-party applicaiton to the guide library add new third-party applicaiton to the guide library * fix(test): fix rebase issue fix rebase issue * feat(console): hide some fields for third-party apps hide some form fields for third-party apps * feat(console): add application branding tab add application branding tab * fix(console): fix fields not updated after logo deletion bug fix fields not updated after logo deletion bug * fix(console): fix the third-party hidden fields fix the third-party hidden fields logic * fix(console): fix style class not found error fix style class not found error * fix(console): force bool type for the thirdPartyApplicaitonEnabled variable force bool type for the thirdPartyApplicaitonEnabled variable
This commit is contained in:
parent
5538946a9b
commit
e9e0f18dcf
26 changed files with 711 additions and 10 deletions
|
@ -2,6 +2,7 @@ export enum ApplicationDetailsTabs {
|
||||||
Settings = 'settings',
|
Settings = 'settings',
|
||||||
Roles = 'roles',
|
Roles = 'roles',
|
||||||
Logs = 'logs',
|
Logs = 'logs',
|
||||||
|
Branding = 'branding',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApiResourceDetailsTabs {
|
export enum ApiResourceDetailsTabs {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { type ApplicationSignInExperience } from '@logto/schemas';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import ImageUploader from '@/ds-components/Uploader/ImageUploader';
|
||||||
|
import useImageMimeTypes from '@/hooks/use-image-mime-types';
|
||||||
|
|
||||||
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isDarkModeEnabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function LogoUploader({ isDarkModeEnabled }: Props) {
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
const [uploadLogoError, setUploadLogoError] = useState<string>();
|
||||||
|
const [uploadDarkLogoError, setUploadDarkLogoError] = useState<string>();
|
||||||
|
const { description } = useImageMimeTypes();
|
||||||
|
const { control } = useFormContext<ApplicationSignInExperience>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Controller
|
||||||
|
name="branding.logoUrl"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value, name } }) => (
|
||||||
|
<ImageUploader
|
||||||
|
className={isDarkModeEnabled ? styles.multiColumn : undefined}
|
||||||
|
name={name}
|
||||||
|
value={value ?? ''}
|
||||||
|
actionDescription={t('sign_in_exp.branding.logo_image_url')}
|
||||||
|
onCompleted={onChange}
|
||||||
|
onUploadErrorChange={setUploadLogoError}
|
||||||
|
onDelete={() => {
|
||||||
|
onChange('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{/* Show the dark mode logto uploader only if dark mode is enabled in the global sign-in-experience */}
|
||||||
|
{isDarkModeEnabled && (
|
||||||
|
<Controller
|
||||||
|
name="branding.darkLogoUrl"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value, name } }) => (
|
||||||
|
<ImageUploader
|
||||||
|
name={name}
|
||||||
|
value={value ?? ''}
|
||||||
|
className={value ? styles.darkMode : undefined}
|
||||||
|
actionDescription={t('sign_in_exp.branding.dark_logo_image_url')}
|
||||||
|
onCompleted={onChange}
|
||||||
|
onUploadErrorChange={setUploadDarkLogoError}
|
||||||
|
onDelete={() => {
|
||||||
|
onChange('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{uploadLogoError && (
|
||||||
|
<div className={classNames(styles.description, styles.error)}>
|
||||||
|
{t('sign_in_exp.branding.logo_image_error', { error: uploadLogoError })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{uploadDarkLogoError && (
|
||||||
|
<div className={classNames(styles.description, styles.error)}>
|
||||||
|
{t('sign_in_exp.branding.logo_image_error', { error: uploadDarkLogoError })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.description}>{description}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogoUploader;
|
|
@ -0,0 +1,33 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.darkMode {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiColumn {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font: var(--font-body-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: _.unit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
import { type Application, type ApplicationSignInExperience } from '@logto/schemas';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import { useForm, FormProvider } from 'react-hook-form';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import DetailsForm from '@/components/DetailsForm';
|
||||||
|
import FormCard, { FormCardSkeleton } from '@/components/FormCard';
|
||||||
|
import RequestDataError from '@/components/RequestDataError';
|
||||||
|
import FormField from '@/ds-components/FormField';
|
||||||
|
import TextInput from '@/ds-components/TextInput';
|
||||||
|
import useApi from '@/hooks/use-api';
|
||||||
|
import useUserAssetsService from '@/hooks/use-user-assets-service';
|
||||||
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
|
import { uriValidator } from '@/utils/validator';
|
||||||
|
|
||||||
|
import LogoUploader from './LogoUploader';
|
||||||
|
import useApplicationSignInExperienceSWR from './use-application-sign-in-experience-swr';
|
||||||
|
import useSignInExperienceSWR from './use-sign-in-experience-swr';
|
||||||
|
import { formatFormToSubmitData, formatResponseDataToForm } from './utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: Application;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Branding({ application }: Props) {
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
|
const formMethods = useForm<ApplicationSignInExperience>({
|
||||||
|
defaultValues: {
|
||||||
|
tenantId: application.tenantId,
|
||||||
|
applicationId: application.id,
|
||||||
|
branding: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
register,
|
||||||
|
reset,
|
||||||
|
formState: { isDirty, isSubmitting, errors },
|
||||||
|
} = formMethods;
|
||||||
|
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const { data, error, mutate } = useApplicationSignInExperienceSWR(application.id);
|
||||||
|
const { data: sieData, error: sieError, mutate: sieMutate } = useSignInExperienceSWR();
|
||||||
|
const { isReady: isUserAssetsServiceReady, isLoading: isUserAssetsServiceLoading } =
|
||||||
|
useUserAssetsService();
|
||||||
|
|
||||||
|
const isApplicationSieLoading = !data && !error;
|
||||||
|
const isSieLoading = !sieData && !sieError;
|
||||||
|
const isLoading = isApplicationSieLoading || isSieLoading || isUserAssetsServiceLoading;
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit(
|
||||||
|
trySubmitSafe(async (data) => {
|
||||||
|
if (isSubmitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api
|
||||||
|
.put(`api/applications/${application.id}/sign-in-experience`, {
|
||||||
|
json: formatFormToSubmitData(data),
|
||||||
|
})
|
||||||
|
.json<ApplicationSignInExperience>();
|
||||||
|
|
||||||
|
void mutate(response);
|
||||||
|
toast.success(t('general.saved'));
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRetryFetch = useCallback(() => {
|
||||||
|
void mutate();
|
||||||
|
void sieMutate();
|
||||||
|
}, [mutate, sieMutate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(formatResponseDataToForm(data));
|
||||||
|
}, [data, reset]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <FormCardSkeleton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error details if the error is not 404
|
||||||
|
if (error && error.status !== 404) {
|
||||||
|
return <RequestDataError error={error} onRetry={onRetryFetch} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDarkModeEnabled = sieData?.color.isDarkModeEnabled;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormProvider {...formMethods}>
|
||||||
|
<DetailsForm
|
||||||
|
isDirty={isDirty}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
onDiscard={reset}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
>
|
||||||
|
<FormCard
|
||||||
|
title="application_details.branding.branding"
|
||||||
|
description="application_details.branding.branding_description"
|
||||||
|
>
|
||||||
|
<FormField title="application_details.branding.display_name">
|
||||||
|
<TextInput {...register('displayName')} />
|
||||||
|
</FormField>
|
||||||
|
{isUserAssetsServiceReady && (
|
||||||
|
<FormField title="application_details.branding.display_logo">
|
||||||
|
<LogoUploader isDarkModeEnabled={isDarkModeEnabled} />
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
{/* Display the TextInput field if image upload service is not available */}
|
||||||
|
{!isUserAssetsServiceReady && (
|
||||||
|
<FormField title="application_details.branding.display_logo">
|
||||||
|
<TextInput
|
||||||
|
{...register('branding.logoUrl', {
|
||||||
|
validate: (value) =>
|
||||||
|
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||||
|
})}
|
||||||
|
error={errors.branding?.logoUrl?.message}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
{/* Display the Dark logo field only if the dark mode is enabled in the global sign-in-experience */}
|
||||||
|
{!isUserAssetsServiceReady && isDarkModeEnabled && (
|
||||||
|
<FormField title="application_details.branding.display_logo_dark">
|
||||||
|
<TextInput
|
||||||
|
{...register('branding.darkLogoUrl', {
|
||||||
|
validate: (value) =>
|
||||||
|
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||||
|
})}
|
||||||
|
error={errors.branding?.darkLogoUrl?.message}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
</FormCard>
|
||||||
|
<FormCard
|
||||||
|
title="application_details.branding.more_info"
|
||||||
|
description="application_details.branding.more_info_description"
|
||||||
|
>
|
||||||
|
<FormField title="application_details.branding.terms_of_use_url">
|
||||||
|
<TextInput
|
||||||
|
{...register('termsOfUseUrl', {
|
||||||
|
validate: (value) =>
|
||||||
|
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||||
|
})}
|
||||||
|
error={errors.termsOfUseUrl?.message}
|
||||||
|
placeholder="https://"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
<FormField title="application_details.branding.privacy_policy_url">
|
||||||
|
<TextInput
|
||||||
|
{...register('privacyPolicyUrl', {
|
||||||
|
validate: (value) =>
|
||||||
|
!value || uriValidator(value) || t('errors.invalid_uri_format'),
|
||||||
|
})}
|
||||||
|
error={errors.privacyPolicyUrl?.message}
|
||||||
|
placeholder="https://"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
</FormCard>
|
||||||
|
</DetailsForm>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Branding;
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { type ApplicationSignInExperience } from '@logto/schemas';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import useApi, { RequestError } from '@/hooks/use-api';
|
||||||
|
import useSwrFetcher from '@/hooks/use-swr-fetcher';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SWR fetcher for application sign-in experience
|
||||||
|
*
|
||||||
|
* hide error toast, because we will handle the error in the component
|
||||||
|
* allow 404 error, if the sign-in experience is not set
|
||||||
|
*/
|
||||||
|
const useApplicationSignInExperienceSWR = (applicationId: string) => {
|
||||||
|
const fetchApi = useApi({ hideErrorToast: true });
|
||||||
|
const fetcher = useSwrFetcher<ApplicationSignInExperience>(fetchApi);
|
||||||
|
|
||||||
|
return useSWR<ApplicationSignInExperience, RequestError>(
|
||||||
|
`api/applications/${applicationId}/sign-in-experience`,
|
||||||
|
{
|
||||||
|
fetcher,
|
||||||
|
shouldRetryOnError: (error: unknown) => {
|
||||||
|
if (error instanceof RequestError) {
|
||||||
|
return error.status !== 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useApplicationSignInExperienceSWR;
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { type SignInExperience } from '@logto/schemas';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
import { type RequestError } from '@/hooks/use-api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need SIE isDarkModeEnabled to determine if we should show the dark mode logo forms
|
||||||
|
*/
|
||||||
|
const useSignInExperienceSWR = () => {
|
||||||
|
return useSWR<SignInExperience, RequestError>('api/sign-in-exp');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSignInExperienceSWR;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { type ApplicationSignInExperience } from '@logto/schemas';
|
||||||
|
import { conditional } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the form data to match the API request body
|
||||||
|
* - Omit `applicationId` and `tenantId` from the request body
|
||||||
|
* - Remove the empty `logoUrl` and `darkLogoUrl` fields in the `branding` object
|
||||||
|
**/
|
||||||
|
export const formatFormToSubmitData = (
|
||||||
|
data: ApplicationSignInExperience
|
||||||
|
): Omit<ApplicationSignInExperience, 'applicationId' | 'tenantId'> => {
|
||||||
|
const { branding, applicationId, tenantId, ...rest } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
branding: {
|
||||||
|
...conditional(branding.logoUrl && { logoUrl: branding.logoUrl }),
|
||||||
|
...conditional(branding.darkLogoUrl && { darkLogoUrl: branding.darkLogoUrl }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the response data to match the form data
|
||||||
|
*
|
||||||
|
* Fulfill the branding object with empty string if the `logoUrl` or `darkLogoUrl` is not set.
|
||||||
|
* Otherwise, the RHF won't update the branding fields properly with the undefined value.
|
||||||
|
*/
|
||||||
|
export const formatResponseDataToForm = (
|
||||||
|
data: ApplicationSignInExperience
|
||||||
|
): ApplicationSignInExperience => {
|
||||||
|
const { branding, ...rest } = data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
branding: {
|
||||||
|
logoUrl: branding.logoUrl ?? '',
|
||||||
|
darkLogoUrl: branding.darkLogoUrl ?? '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -11,6 +11,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||||
import CaretDown from '@/assets/icons/caret-down.svg';
|
import CaretDown from '@/assets/icons/caret-down.svg';
|
||||||
import CaretUp from '@/assets/icons/caret-up.svg';
|
import CaretUp from '@/assets/icons/caret-up.svg';
|
||||||
import FormCard from '@/components/FormCard';
|
import FormCard from '@/components/FormCard';
|
||||||
|
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||||
import { openIdProviderConfigPath } from '@/consts/oidc';
|
import { openIdProviderConfigPath } from '@/consts/oidc';
|
||||||
import { AppDataContext } from '@/contexts/AppDataProvider';
|
import { AppDataContext } from '@/contexts/AppDataProvider';
|
||||||
import Button from '@/ds-components/Button';
|
import Button from '@/ds-components/Button';
|
||||||
|
@ -27,7 +28,7 @@ type Props = {
|
||||||
oidcConfig: SnakeCaseOidcConfig;
|
oidcConfig: SnakeCaseOidcConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
function EndpointsAndCredentials({ app: { type, secret, id }, oidcConfig }: Props) {
|
function EndpointsAndCredentials({ app: { type, secret, id, isThirdParty }, oidcConfig }: Props) {
|
||||||
const { tenantEndpoint } = useContext(AppDataContext);
|
const { tenantEndpoint } = useContext(AppDataContext);
|
||||||
const [showMoreEndpoints, setShowMoreEndpoints] = useState(false);
|
const [showMoreEndpoints, setShowMoreEndpoints] = useState(false);
|
||||||
|
|
||||||
|
@ -50,7 +51,8 @@ function EndpointsAndCredentials({ app: { type, secret, id }, oidcConfig }: Prop
|
||||||
targetBlank: true,
|
targetBlank: true,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tenantEndpoint && (
|
{/* Hide logto endpoint field in third-party application's form. @simeng-li FIXME: remove isDevFeatureEnabled flag */}
|
||||||
|
{tenantEndpoint && (!isDevFeaturesEnabled || !isThirdParty) && (
|
||||||
<FormField title="application_details.logto_endpoint">
|
<FormField title="application_details.logto_endpoint">
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
isFullWidth
|
isFullWidth
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import FormCard from '@/components/FormCard';
|
import FormCard from '@/components/FormCard';
|
||||||
import MultiTextInputField from '@/components/MultiTextInputField';
|
import MultiTextInputField from '@/components/MultiTextInputField';
|
||||||
|
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||||
import FormField from '@/ds-components/FormField';
|
import FormField from '@/ds-components/FormField';
|
||||||
import type { MultiTextInputRule } from '@/ds-components/MultiTextInput/types';
|
import type { MultiTextInputRule } from '@/ds-components/MultiTextInput/types';
|
||||||
import {
|
import {
|
||||||
|
@ -29,7 +30,7 @@ function Settings({ data }: Props) {
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useFormContext<Application>();
|
} = useFormContext<Application>();
|
||||||
|
|
||||||
const { type: applicationType } = data;
|
const { type: applicationType, isThirdParty } = data;
|
||||||
|
|
||||||
const isNativeApp = applicationType === ApplicationType.Native;
|
const isNativeApp = applicationType === ApplicationType.Native;
|
||||||
const uriPatternRules: MultiTextInputRule = {
|
const uriPatternRules: MultiTextInputRule = {
|
||||||
|
@ -55,12 +56,16 @@ function Settings({ data }: Props) {
|
||||||
placeholder={t('application_details.application_name_placeholder')}
|
placeholder={t('application_details.application_name_placeholder')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{/* Hide description field in third-party application's form. @simeng-li FIXME: remove isDevFeatureEnabled flag */}
|
||||||
|
{(!isDevFeaturesEnabled || !isThirdParty) && (
|
||||||
<FormField title="application_details.description">
|
<FormField title="application_details.description">
|
||||||
<TextInput
|
<TextInput
|
||||||
{...register('description')}
|
{...register('description')}
|
||||||
placeholder={t('application_details.description_placeholder')}
|
placeholder={t('application_details.description_placeholder')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
)}
|
||||||
|
|
||||||
{applicationType !== ApplicationType.MachineToMachine && (
|
{applicationType !== ApplicationType.MachineToMachine && (
|
||||||
<Controller
|
<Controller
|
||||||
name="oidcClientMetadata.redirectUris"
|
name="oidcClientMetadata.redirectUris"
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Drawer from '@/components/Drawer';
|
||||||
import PageMeta from '@/components/PageMeta';
|
import PageMeta from '@/components/PageMeta';
|
||||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||||
import { ApplicationDetailsTabs } from '@/consts';
|
import { ApplicationDetailsTabs } from '@/consts';
|
||||||
|
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||||
import { openIdProviderConfigPath } from '@/consts/oidc';
|
import { openIdProviderConfigPath } from '@/consts/oidc';
|
||||||
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
|
import DeleteConfirmModal from '@/ds-components/DeleteConfirmModal';
|
||||||
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
||||||
|
@ -33,6 +34,7 @@ import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||||
import { applicationTypeI18nKey } from '@/types/applications';
|
import { applicationTypeI18nKey } from '@/types/applications';
|
||||||
import { trySubmitSafe } from '@/utils/form';
|
import { trySubmitSafe } from '@/utils/form';
|
||||||
|
|
||||||
|
import Branding from './components/Branding';
|
||||||
import EndpointsAndCredentials from './components/EndpointsAndCredentials';
|
import EndpointsAndCredentials from './components/EndpointsAndCredentials';
|
||||||
import GuideDrawer from './components/GuideDrawer';
|
import GuideDrawer from './components/GuideDrawer';
|
||||||
import GuideModal from './components/GuideModal';
|
import GuideModal from './components/GuideModal';
|
||||||
|
@ -57,11 +59,13 @@ function ApplicationDetails() {
|
||||||
const { data, error, mutate } = useSWR<ApplicationResponse, RequestError>(
|
const { data, error, mutate } = useSWR<ApplicationResponse, RequestError>(
|
||||||
id && `api/applications/${id}`
|
id && `api/applications/${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: oidcConfig,
|
data: oidcConfig,
|
||||||
error: fetchOidcConfigError,
|
error: fetchOidcConfigError,
|
||||||
mutate: mutateOidcConfig,
|
mutate: mutateOidcConfig,
|
||||||
} = useSWR<SnakeCaseOidcConfig, RequestError>(openIdProviderConfigPath);
|
} = useSWR<SnakeCaseOidcConfig, RequestError>(openIdProviderConfigPath);
|
||||||
|
|
||||||
const isLoading = (!data && !error) || (!oidcConfig && !fetchOidcConfigError);
|
const isLoading = (!data && !error) || (!oidcConfig && !fetchOidcConfigError);
|
||||||
const requestError = error ?? fetchOidcConfigError;
|
const requestError = error ?? fetchOidcConfigError;
|
||||||
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
|
const [isReadmeOpen, setIsReadmeOpen] = useState(false);
|
||||||
|
@ -227,6 +231,11 @@ function ApplicationDetails() {
|
||||||
</TabNavItem>
|
</TabNavItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{isDevFeaturesEnabled && data.isThirdParty && (
|
||||||
|
<TabNavItem href={`/applications/${data.id}/${ApplicationDetailsTabs.Branding}`}>
|
||||||
|
{t('application_details.branding.branding')}
|
||||||
|
</TabNavItem>
|
||||||
|
)}
|
||||||
</TabNav>
|
</TabNav>
|
||||||
<TabWrapper
|
<TabWrapper
|
||||||
isActive={tab === ApplicationDetailsTabs.Settings}
|
isActive={tab === ApplicationDetailsTabs.Settings}
|
||||||
|
@ -264,6 +273,15 @@ function ApplicationDetails() {
|
||||||
</TabWrapper>
|
</TabWrapper>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isDevFeaturesEnabled && data.isThirdParty && (
|
||||||
|
<TabWrapper
|
||||||
|
isActive={tab === ApplicationDetailsTabs.Branding}
|
||||||
|
className={styles.tabContainer}
|
||||||
|
>
|
||||||
|
<Branding application={data} />
|
||||||
|
</TabWrapper>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleted && isDirty} onConfirm={reset} />
|
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleted && isDirty} onConfirm={reset} />
|
||||||
|
|
|
@ -88,7 +88,7 @@ const useApplicationsData = (isThirdParty = false) => {
|
||||||
|
|
||||||
const { data } = thirdPartyApplicationsData;
|
const { data } = thirdPartyApplicationsData;
|
||||||
const [_, totalCount] = data ?? [];
|
const [_, totalCount] = data ?? [];
|
||||||
const hasThirdPartyApplications = totalCount && totalCount > 0;
|
const hasThirdPartyApplications = Boolean(totalCount && totalCount > 0);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...(isThirdParty ? thirdPartyApplicationsData : firstPartyApplicationsData),
|
...(isThirdParty ? thirdPartyApplicationsData : firstPartyApplicationsData),
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Gib einen Anwendungsnamen ein',
|
enter_your_application_name: 'Gib einen Anwendungsnamen ein',
|
||||||
application_deleted: 'Anwendung {{name}} wurde erfolgreich gelöscht',
|
application_deleted: 'Anwendung {{name}} wurde erfolgreich gelöscht',
|
||||||
redirect_uri_required: 'Gib mindestens eine Umleitungs-URI an',
|
redirect_uri_required: 'Gib mindestens eine Umleitungs-URI an',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Rolle',
|
name_column: 'Rolle',
|
||||||
description_column: 'Beschreibung',
|
description_column: 'Beschreibung',
|
||||||
|
|
|
@ -61,6 +61,18 @@ const application_details = {
|
||||||
enter_your_application_name: 'Enter your application name',
|
enter_your_application_name: 'Enter your application name',
|
||||||
application_deleted: 'Application {{name}} has been successfully deleted',
|
application_deleted: 'Application {{name}} has been successfully deleted',
|
||||||
redirect_uri_required: 'You must enter at least one redirect URI',
|
redirect_uri_required: 'You must enter at least one redirect URI',
|
||||||
|
branding: {
|
||||||
|
branding: 'Branding',
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
more_info: 'More info',
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
display_name: 'Display name',
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Role',
|
name_column: 'Role',
|
||||||
description_column: 'Description',
|
description_column: 'Description',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Ingresa el nombre de tu aplicación',
|
enter_your_application_name: 'Ingresa el nombre de tu aplicación',
|
||||||
application_deleted: 'Se ha eliminado exitosamente la aplicación {{name}}',
|
application_deleted: 'Se ha eliminado exitosamente la aplicación {{name}}',
|
||||||
redirect_uri_required: 'Debes ingresar al menos un URI de Redireccionamiento',
|
redirect_uri_required: 'Debes ingresar al menos un URI de Redireccionamiento',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Rol',
|
name_column: 'Rol',
|
||||||
description_column: 'Descripción',
|
description_column: 'Descripción',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Entrez le nom de votre application',
|
enter_your_application_name: 'Entrez le nom de votre application',
|
||||||
application_deleted: "L'application {{name}} a été supprimée avec succès.",
|
application_deleted: "L'application {{name}} a été supprimée avec succès.",
|
||||||
redirect_uri_required: 'Vous devez entrer au moins un URI de redirection.',
|
redirect_uri_required: 'Vous devez entrer au moins un URI de redirection.',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Rôle',
|
name_column: 'Rôle',
|
||||||
description_column: 'Description',
|
description_column: 'Description',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Inserisci il nome della tua applicazione',
|
enter_your_application_name: 'Inserisci il nome della tua applicazione',
|
||||||
application_deleted: "L'applicazione {{name}} è stata eliminata con successo",
|
application_deleted: "L'applicazione {{name}} è stata eliminata con successo",
|
||||||
redirect_uri_required: 'Devi inserire almeno un URI di reindirizzamento',
|
redirect_uri_required: 'Devi inserire almeno un URI di reindirizzamento',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Ruolo',
|
name_column: 'Ruolo',
|
||||||
description_column: 'Descrizione',
|
description_column: 'Descrizione',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'アプリケーション名を入力してください',
|
enter_your_application_name: 'アプリケーション名を入力してください',
|
||||||
application_deleted: 'アプリケーション{{name}}が正常に削除されました',
|
application_deleted: 'アプリケーション{{name}}が正常に削除されました',
|
||||||
redirect_uri_required: 'リダイレクトURIを少なくとも1つ入力する必要があります',
|
redirect_uri_required: 'リダイレクトURIを少なくとも1つ入力する必要があります',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: '役割',
|
name_column: '役割',
|
||||||
description_column: '説明',
|
description_column: '説明',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: '어플리케이션 이름을 입력해 주세요.',
|
enter_your_application_name: '어플리케이션 이름을 입력해 주세요.',
|
||||||
application_deleted: '{{name}} 어플리케이션이 성공적으로 삭제되었어요.',
|
application_deleted: '{{name}} 어플리케이션이 성공적으로 삭제되었어요.',
|
||||||
redirect_uri_required: '반드시 최소 하나의 Redirect URI 를 입력해야 해요.',
|
redirect_uri_required: '반드시 최소 하나의 Redirect URI 를 입력해야 해요.',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: '역할',
|
name_column: '역할',
|
||||||
description_column: '설명',
|
description_column: '설명',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Wpisz nazwę swojej aplikacji',
|
enter_your_application_name: 'Wpisz nazwę swojej aplikacji',
|
||||||
application_deleted: 'Aplikacja {{name}} została pomyślnie usunięta',
|
application_deleted: 'Aplikacja {{name}} została pomyślnie usunięta',
|
||||||
redirect_uri_required: 'Musisz wpisać co najmniej jeden adres URL przekierowania',
|
redirect_uri_required: 'Musisz wpisać co najmniej jeden adres URL przekierowania',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Role',
|
name_column: 'Role',
|
||||||
description_column: 'Opis',
|
description_column: 'Opis',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Digite o nome do seu aplicativo',
|
enter_your_application_name: 'Digite o nome do seu aplicativo',
|
||||||
application_deleted: 'O aplicativo {{name}} foi excluído com sucesso',
|
application_deleted: 'O aplicativo {{name}} foi excluído com sucesso',
|
||||||
redirect_uri_required: 'Você deve inserir pelo menos um URI de redirecionamento',
|
redirect_uri_required: 'Você deve inserir pelo menos um URI de redirecionamento',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Função',
|
name_column: 'Função',
|
||||||
description_column: 'Descrição',
|
description_column: 'Descrição',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Insira o nome da aplicação',
|
enter_your_application_name: 'Insira o nome da aplicação',
|
||||||
application_deleted: 'Aplicação {{name}} eliminada com sucesso',
|
application_deleted: 'Aplicação {{name}} eliminada com sucesso',
|
||||||
redirect_uri_required: 'Deve inserir pelo menos um URI de redirecionamento',
|
redirect_uri_required: 'Deve inserir pelo menos um URI de redirecionamento',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Nome da função',
|
name_column: 'Nome da função',
|
||||||
description_column: 'Descrição',
|
description_column: 'Descrição',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Введите название своего приложения',
|
enter_your_application_name: 'Введите название своего приложения',
|
||||||
application_deleted: 'Приложение {{name}} успешно удалено',
|
application_deleted: 'Приложение {{name}} успешно удалено',
|
||||||
redirect_uri_required: 'Вы должны ввести по крайней мере один URI перенаправления',
|
redirect_uri_required: 'Вы должны ввести по крайней мере один URI перенаправления',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Роль',
|
name_column: 'Роль',
|
||||||
description_column: 'Описание',
|
description_column: 'Описание',
|
||||||
|
|
|
@ -67,6 +67,27 @@ const application_details = {
|
||||||
enter_your_application_name: 'Uygulama adı giriniz',
|
enter_your_application_name: 'Uygulama adı giriniz',
|
||||||
application_deleted: '{{name}} Uygulaması başarıyla silindi',
|
application_deleted: '{{name}} Uygulaması başarıyla silindi',
|
||||||
redirect_uri_required: 'En az 1 yönlendirme URIı girmelisiniz',
|
redirect_uri_required: 'En az 1 yönlendirme URIı girmelisiniz',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: 'Rol',
|
name_column: 'Rol',
|
||||||
description_column: 'Açıklama',
|
description_column: 'Açıklama',
|
||||||
|
|
|
@ -64,6 +64,27 @@ const application_details = {
|
||||||
enter_your_application_name: '输入你的应用名称',
|
enter_your_application_name: '输入你的应用名称',
|
||||||
application_deleted: '应用 {{name}} 成功删除。',
|
application_deleted: '应用 {{name}} 成功删除。',
|
||||||
redirect_uri_required: '至少需要输入一个重定向 URI。',
|
redirect_uri_required: '至少需要输入一个重定向 URI。',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: '角色',
|
name_column: '角色',
|
||||||
description_column: '描述',
|
description_column: '描述',
|
||||||
|
|
|
@ -64,6 +64,27 @@ const application_details = {
|
||||||
enter_your_application_name: '輸入你的應用程式名稱',
|
enter_your_application_name: '輸入你的應用程式名稱',
|
||||||
application_deleted: '應用 {{name}} 成功刪除。',
|
application_deleted: '應用 {{name}} 成功刪除。',
|
||||||
redirect_uri_required: '至少需要輸入一個重定向 URL。',
|
redirect_uri_required: '至少需要輸入一個重定向 URL。',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: '角色',
|
name_column: '角色',
|
||||||
description_column: '描述',
|
description_column: '描述',
|
||||||
|
|
|
@ -65,6 +65,27 @@ const application_details = {
|
||||||
enter_your_application_name: '輸入你的應用程式姓名',
|
enter_your_application_name: '輸入你的應用程式姓名',
|
||||||
application_deleted: '應用 {{name}} 成功刪除。',
|
application_deleted: '應用 {{name}} 成功刪除。',
|
||||||
redirect_uri_required: '至少需要輸入一個重定向 URL。',
|
redirect_uri_required: '至少需要輸入一個重定向 URL。',
|
||||||
|
branding: {
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding: 'Branding',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
branding_description:
|
||||||
|
"Customize your application's display name and logo on the consent screen.",
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info: 'More info',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
more_info_description: 'Offer users more details about your application on the consent screen.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_name: 'Display name',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo: 'Display logo',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
display_logo_dark: 'Display logo (dark)',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
terms_of_use_url: 'Application terms of use URL',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
privacy_policy_url: 'Application privacy policy URL',
|
||||||
|
},
|
||||||
roles: {
|
roles: {
|
||||||
name_column: '角色',
|
name_column: '角色',
|
||||||
description_column: '描述',
|
description_column: '描述',
|
||||||
|
|
Loading…
Add table
Reference in a new issue