0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor: replace termsOfUse with termsOfUseUrl (#2665)

This commit is contained in:
Xiao Yijun 2022-12-15 17:04:42 +08:00 committed by GitHub
parent f3baaf919f
commit bc5f4b541a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 196 additions and 216 deletions

View file

@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next';
import Card from '@/components/Card';
import FormField from '@/components/FormField';
import Switch from '@/components/Switch';
import TextInput from '@/components/TextInput';
import { uriValidator } from '@/utilities/validator';
@ -13,38 +12,26 @@ import * as styles from '../index.module.scss';
const TermsForm = () => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
watch,
register,
formState: { errors },
} = useFormContext<SignInExperienceForm>();
const enabled = watch('termsOfUse.enabled');
return (
<Card>
<div className={styles.title}>{t('sign_in_exp.others.terms_of_use.title')}</div>
<FormField title="sign_in_exp.others.terms_of_use.enable">
<Switch
{...register('termsOfUse.enabled')}
label={t('sign_in_exp.others.terms_of_use.description')}
<FormField
title="sign_in_exp.others.terms_of_use.terms_of_use"
tip={t('sign_in_exp.others.terms_of_use.terms_of_use_tip')}
>
<TextInput
{...register('termsOfUseUrl', {
validate: (value) => !value || uriValidator(value) || t('errors.invalid_uri_format'),
})}
hasError={Boolean(errors.termsOfUseUrl)}
errorMessage={errors.termsOfUseUrl?.message}
placeholder={t('sign_in_exp.others.terms_of_use.terms_of_use_placeholder')}
/>
</FormField>
{enabled && (
<FormField
isRequired
title="sign_in_exp.others.terms_of_use.terms_of_use"
tip={t('sign_in_exp.others.terms_of_use.terms_of_use_tip')}
>
<TextInput
{...register('termsOfUse.contentUrl', {
required: true,
validate: (value) => !value || uriValidator(value) || t('errors.invalid_uri_format'),
})}
hasError={Boolean(errors.termsOfUse)}
errorMessage={errors.termsOfUse?.contentUrl?.message}
placeholder={t('sign_in_exp.others.terms_of_use.terms_of_use_placeholder')}
/>
</FormField>
)}
</Card>
);
};

View file

@ -113,12 +113,5 @@ export const getSignUpAndSignInErrorCount = (
return signUpErrorCount + signInMethodErrorCount;
};
export const getOthersErrorCount = (
errors: FieldErrorsImpl<DeepRequired<SignInExperienceForm>>
) => {
const { termsOfUse } = errors;
const termsOfUseErrorCount = termsOfUse ? Object.keys(termsOfUse).length : 0;
return termsOfUseErrorCount;
};
export const getOthersErrorCount = (errors: FieldErrorsImpl<DeepRequired<SignInExperienceForm>>) =>
errors.termsOfUseUrl ? 1 : 0;

View file

@ -2,13 +2,48 @@ import type {
Branding,
LanguageInfo,
SignInExperience,
TermsOfUse,
Color,
SignUp,
SignIn,
} from '@logto/schemas';
import { BrandingStyle, SignInMode, SignInIdentifier } from '@logto/schemas';
export const mockColor: Color = {
primaryColor: '#000',
isDarkModeEnabled: true,
darkPrimaryColor: '#fff',
};
export const mockBranding: Branding = {
style: BrandingStyle.Logo_Slogan,
logoUrl: 'http://silverhand.png',
slogan: 'Silverhand.',
};
export const mockTermsOfUseUrl = 'http://silverhand.com/terms';
export const mockLanguageInfo: LanguageInfo = {
autoDetect: true,
fallbackLanguage: 'en',
};
export const mockSignUp: SignUp = {
identifiers: [SignInIdentifier.Username],
password: true,
verify: false,
};
export const mockSignInMethod: SignIn['methods'][0] = {
identifier: SignInIdentifier.Username,
password: true,
verificationCode: false,
isPasswordPrimary: true,
};
export const mockSignIn = {
methods: [mockSignInMethod],
};
export const mockSignInExperience: SignInExperience = {
id: 'foo',
color: {
@ -21,9 +56,7 @@ export const mockSignInExperience: SignInExperience = {
logoUrl: 'http://logto.png',
slogan: 'logto',
},
termsOfUse: {
enabled: false,
},
termsOfUseUrl: mockTermsOfUseUrl,
languageInfo: {
autoDetect: true,
fallbackLanguage: 'en',
@ -58,42 +91,3 @@ export const mockSignInExperience: SignInExperience = {
socialSignInConnectorTargets: ['github', 'facebook', 'wechat'],
signInMode: SignInMode.SignInAndRegister,
};
export const mockColor: Color = {
primaryColor: '#000',
isDarkModeEnabled: true,
darkPrimaryColor: '#fff',
};
export const mockBranding: Branding = {
style: BrandingStyle.Logo_Slogan,
logoUrl: 'http://silverhand.png',
slogan: 'Silverhand.',
};
export const mockTermsOfUse: TermsOfUse = {
enabled: true,
contentUrl: 'http://silverhand.com/terms',
};
export const mockLanguageInfo: LanguageInfo = {
autoDetect: true,
fallbackLanguage: 'en',
};
export const mockSignUp: SignUp = {
identifiers: [SignInIdentifier.Username],
password: true,
verify: false,
};
export const mockSignInMethod: SignIn['methods'][0] = {
identifier: SignInIdentifier.Username,
password: true,
verificationCode: false,
isPasswordPrimary: true,
};
export const mockSignIn = {
methods: [mockSignInMethod],
};

View file

@ -35,12 +35,8 @@ const { findDefaultSignInExperience, updateDefaultSignInExperience } = mockEsm(
})
);
const {
validateBranding,
validateTermsOfUse,
validateLanguageInfo,
removeUnavailableSocialConnectorTargets,
} = await import('./index.js');
const { validateBranding, validateLanguageInfo, removeUnavailableSocialConnectorTargets } =
await import('./index.js');
beforeEach(() => {
jest.clearAllMocks();
@ -139,16 +135,6 @@ describe('validate language info', () => {
});
});
describe('validate terms of use', () => {
test('should throw when terms of use is enabled and content URL is empty', () => {
expect(() => {
validateTermsOfUse({
enabled: true,
});
}).toMatchError(new RequestError('sign_in_experiences.empty_content_url_of_terms_of_use'));
});
});
describe('remove unavailable social connector targets', () => {
test('should remove unavailable social connector targets in sign-in experience', async () => {
const mockSocialConnectorTargets = mockSocialConnectors.map(

View file

@ -1,5 +1,5 @@
import { builtInLanguages } from '@logto/phrases-ui';
import type { Branding, LanguageInfo, SignInExperience, TermsOfUse } from '@logto/schemas';
import type { Branding, LanguageInfo, SignInExperience } from '@logto/schemas';
import { SignInMode, ConnectorType, BrandingStyle } from '@logto/schemas';
import {
adminConsoleApplicationId,
@ -42,13 +42,6 @@ export const validateLanguageInfo = async (languageInfo: LanguageInfo) => {
);
};
export const validateTermsOfUse = (termsOfUse: TermsOfUse) => {
assertThat(
!termsOfUse.enabled || termsOfUse.contentUrl,
'sign_in_experiences.empty_content_url_of_terms_of_use'
);
};
export const removeUnavailableSocialConnectorTargets = async () => {
const connectors = await getLogtoConnectors();
const availableSocialConnectorTargets = deduplicate(
@ -78,6 +71,7 @@ export const getSignInExperienceForApplication = async (
...adminConsoleSignInExperience.branding,
slogan: i18next.t('admin_console.welcome.title'),
},
termsOfUseUrl: signInExperience.termsOfUseUrl,
languageInfo: signInExperience.languageInfo,
signInMode: (await hasActiveUsers()) ? SignInMode.SignIn : SignInMode.Register,
socialSignInConnectorTargets: [],

View file

@ -28,7 +28,7 @@ describe('sign-in-experience query', () => {
...mockSignInExperience,
color: JSON.stringify(mockSignInExperience.color),
branding: JSON.stringify(mockSignInExperience.branding),
termsOfUse: JSON.stringify(mockSignInExperience.termsOfUse),
termsOfUseUrl: mockSignInExperience.termsOfUseUrl,
languageInfo: JSON.stringify(mockSignInExperience.languageInfo),
signIn: JSON.stringify(mockSignInExperience.signIn),
signUp: JSON.stringify(mockSignInExperience.signUp),
@ -38,7 +38,7 @@ describe('sign-in-experience query', () => {
it('findDefaultSignInExperience', async () => {
/* eslint-disable sql/no-unsafe-query */
const expectSql = `
select "id", "color", "branding", "language_info", "terms_of_use", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode"
select "id", "color", "branding", "language_info", "terms_of_use_url", "sign_in", "sign_up", "social_sign_in_connector_targets", "sign_in_mode"
from "sign_in_experiences"
where "id"=$1
`;
@ -55,14 +55,12 @@ describe('sign-in-experience query', () => {
});
it('updateDefaultSignInExperience', async () => {
const termsOfUse = {
enabled: false,
};
const { termsOfUseUrl } = mockSignInExperience;
/* eslint-disable sql/no-unsafe-query */
const expectSql = `
update "sign_in_experiences"
set "terms_of_use"=$1
set "terms_of_use_url"=$1
where "id"=$2
returning *
`;
@ -70,11 +68,11 @@ describe('sign-in-experience query', () => {
mockQuery.mockImplementationOnce(async (sql, values) => {
expectSqlAssert(sql, expectSql);
expect(values).toEqual([JSON.stringify(termsOfUse), id]);
expect(values).toEqual([termsOfUseUrl, id]);
return createMockQueryResult([dbvalue]);
});
await expect(updateDefaultSignInExperience({ termsOfUse })).resolves.toEqual(dbvalue);
await expect(updateDefaultSignInExperience({ termsOfUseUrl })).resolves.toEqual(dbvalue);
});
});

View file

@ -9,7 +9,6 @@ import {
mockGoogleConnector,
mockLanguageInfo,
mockSignInExperience,
mockTermsOfUse,
} from '#src/__mocks__/index.js';
const { jest } = import.meta;
@ -59,44 +58,20 @@ beforeEach(() => {
jest.clearAllMocks();
});
describe('terms of use', () => {
describe('enabled', () => {
test.each(validBooleans)('%p should success', async (enabled) => {
const signInExperience = { termsOfUse: { ...mockTermsOfUse, enabled } };
await expectPatchResponseStatus(signInExperience, 200);
});
test.each(invalidBooleans)('%p should fail', async (enabled) => {
const signInExperience = { termsOfUse: { ...mockTermsOfUse, enabled } };
await expectPatchResponseStatus(signInExperience, 400);
});
});
describe('contentUrl', () => {
test.each([undefined, 'http://silverhand.com/terms', 'https://logto.dev/terms'])(
describe('terms of use url', () => {
describe('termsOfUseUrl', () => {
test.each([undefined, null, '', 'http://silverhand.com/terms', 'https://logto.dev/terms'])(
'%p should success',
async (contentUrl) => {
const signInExperience = { termsOfUse: { ...mockTermsOfUse, enabled: false, contentUrl } };
async (termsOfUseUrl) => {
const signInExperience = {
termsOfUseUrl,
};
await expectPatchResponseStatus(signInExperience, 200);
}
);
test.each([null, ' \t\n\r', 'non-url'])('%p should fail', async (contentUrl) => {
const signInExperience = { termsOfUse: { ...mockTermsOfUse, enabled: false, contentUrl } };
await expectPatchResponseStatus(signInExperience, 400);
});
test('should allow empty contentUrl if termsOfUse is disabled', async () => {
const signInExperience = {
termsOfUse: { ...mockTermsOfUse, enabled: false, contentUrl: '' },
};
await expectPatchResponseStatus(signInExperience, 200);
});
test('should not allow empty contentUrl if termsOfUse is enabled', async () => {
const signInExperience = {
termsOfUse: { ...mockTermsOfUse, enabled: true, contentUrl: '' },
};
test.each([' \t\n\r', 'non-url'])('%p should fail', async (termsOfUseUrl) => {
const signInExperience = { termsOfUseUrl };
await expectPatchResponseStatus(signInExperience, 400);
});
});

View file

@ -1,4 +1,4 @@
import type { SignInExperience, CreateSignInExperience, TermsOfUse } from '@logto/schemas';
import type { SignInExperience, CreateSignInExperience } from '@logto/schemas';
import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
import {
@ -13,24 +13,19 @@ import {
mockSignIn,
mockLanguageInfo,
mockAliyunSmsConnector,
mockTermsOfUseUrl,
} from '#src/__mocks__/index.js';
import { createRequester } from '#src/utils/test-utils.js';
const { jest } = import.meta;
const {
validateBranding,
validateLanguageInfo,
validateTermsOfUse,
validateSignIn,
validateSignUp,
} = await mockEsmWithActual('#src/libraries/sign-in-experience/index.js', () => ({
validateBranding: jest.fn(),
validateLanguageInfo: jest.fn(),
validateTermsOfUse: jest.fn(),
validateSignIn: jest.fn(),
validateSignUp: jest.fn(),
}));
const { validateBranding, validateLanguageInfo, validateSignIn, validateSignUp } =
await mockEsmWithActual('#src/libraries/sign-in-experience/index.js', () => ({
validateBranding: jest.fn(),
validateLanguageInfo: jest.fn(),
validateSignIn: jest.fn(),
validateSignUp: jest.fn(),
}));
const logtoConnectors = [
mockFacebookConnector,
@ -106,14 +101,13 @@ describe('PATCH /sign-in-exp', () => {
});
it('should succeed to update when the input is valid', async () => {
const termsOfUse: TermsOfUse = { enabled: false };
const socialSignInConnectorTargets = ['github', 'facebook', 'wechat'];
const response = await signInExperienceRequester.patch('/sign-in-exp').send({
color: mockColor,
branding: mockBranding,
languageInfo: mockLanguageInfo,
termsOfUse,
termsOfUseUrl: mockTermsOfUseUrl,
socialSignInConnectorTargets,
signUp: mockSignUp,
signIn: mockSignIn,
@ -121,7 +115,6 @@ describe('PATCH /sign-in-exp', () => {
expect(validateBranding).toHaveBeenCalledWith(mockBranding);
expect(validateLanguageInfo).toHaveBeenCalledWith(mockLanguageInfo);
expect(validateTermsOfUse).toHaveBeenCalledWith(termsOfUse);
expect(validateSignUp).toHaveBeenCalledWith(mockSignUp, logtoConnectors);
expect(validateSignIn).toHaveBeenCalledWith(mockSignIn, mockSignUp, logtoConnectors);
@ -131,7 +124,7 @@ describe('PATCH /sign-in-exp', () => {
...mockSignInExperience,
color: mockColor,
branding: mockBranding,
termsOfUse,
termsOfUseUrl: mockTermsOfUseUrl,
socialSignInConnectorTargets,
signIn: mockSignIn,
},

View file

@ -1,10 +1,10 @@
import { ConnectorType, SignInExperiences } from '@logto/schemas';
import { literal, object, string } from 'zod';
import { getLogtoConnectors } from '#src/connectors/index.js';
import {
validateBranding,
validateLanguageInfo,
validateTermsOfUse,
validateSignUp,
validateSignIn,
} from '#src/libraries/sign-in-experience/index.js';
@ -30,11 +30,18 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
router.patch(
'/sign-in-exp',
koaGuard({
body: SignInExperiences.createGuard.omit({ id: true }).partial(),
body: SignInExperiences.createGuard
.omit({ id: true, termsOfUseUrl: true })
.merge(
object({
termsOfUseUrl: string().url().optional().nullable().or(literal('')),
})
)
.partial(),
}),
async (ctx, next) => {
const { socialSignInConnectorTargets, ...rest } = ctx.guard.body;
const { branding, languageInfo, termsOfUse, signUp, signIn } = rest;
const { branding, languageInfo, signUp, signIn } = rest;
if (branding) {
validateBranding(branding);
@ -44,10 +51,6 @@ export default function signInExperiencesRoutes<T extends AuthedRouter>(router:
await validateLanguageInfo(languageInfo);
}
if (termsOfUse) {
validateTermsOfUse(termsOfUse);
}
const connectors = await getLogtoConnectors();
// Remove unavailable connectors

View file

@ -108,6 +108,7 @@ describe('GET /.well-known/sign-in-exp', () => {
...adminConsoleSignInExperience.branding,
slogan: 'admin_console.welcome.title',
},
termsOfUseUrl: mockSignInExperience.termsOfUseUrl,
languageInfo: mockSignInExperience.languageInfo,
socialConnectors: [],
signInMode: SignInMode.SignIn,

View file

@ -22,10 +22,7 @@ describe('admin console sign-in experience', () => {
logoUrl: 'https://logto.io/new-logo.png',
darkLogoUrl: 'https://logto.io/new-dark-logo.png',
},
termsOfUse: {
enabled: true,
contentUrl: 'https://logto.io/terms',
},
termsOfUseUrl: 'https://logto.io/terms',
};
const updatedSignInExperience = await updateSignInExperience(newSignInExperience);

View file

@ -98,8 +98,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: 'NUTZUNGSBEDINGUNGEN',
enable: 'Aktiviere Nutzungsbedingungen',
description: 'Füge die rechtlichen Vereinbarungen für die Nutzung deines Produkts hinzu',
terms_of_use: 'Nutzungsbedingungen',
terms_of_use_placeholder: 'https://beispiel.de/nutzungsbedingungen',
terms_of_use_tip: 'URL zu den Nutzungsbedingungen',

View file

@ -96,8 +96,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: 'TERMS OF USE',
enable: 'Enable terms of use',
description: 'Add the legal agreements for the use of your product',
terms_of_use: 'Terms of use',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'Terms of use URL',

View file

@ -98,8 +98,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: "CONDITIONS D'UTILISATION",
enable: "Activer les conditions d'utilisation",
description: "Ajouter les accords juridiques pour l'utilisation de votre produit",
terms_of_use: "Conditions d'utilisation",
terms_of_use_placeholder: 'https://vos.conditions.utilisation/',
terms_of_use_tip: "Conditions d'utilisation URL",

View file

@ -93,8 +93,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: '이용 약관',
enable: '이용 약관 활성화',
description: '서비스 사용을 위한 이용 약관을 추가해보세요.',
terms_of_use: '이용 약관',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: '이용 약관 URL',

View file

@ -98,8 +98,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: 'TERMOS DE USO',
enable: 'Habilitar termos de uso',
description: 'Adicione os acordos legais para o uso do seu produto',
terms_of_use: 'Termos de uso',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'URL dos termos de uso',

View file

@ -96,8 +96,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: 'TERMOS DE USO',
enable: 'Ativar termos de uso',
description: 'Adicione os termos legais para uso do seu produto',
terms_of_use: 'Termos de uso',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'URL dos termos de uso',

View file

@ -97,8 +97,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: 'KULLANIM KOŞULLARI',
enable: 'Kullanım koşullarını etkinleştir',
description: 'Ürününüzün kullanımına ilişkin yasal anlaşmaları ekleyin',
terms_of_use: 'Kullanım koşulları',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: 'Kullanım koşulları URLi',

View file

@ -89,8 +89,6 @@ const sign_in_exp = {
others: {
terms_of_use: {
title: '使用条款',
enable: '开启使用条款',
description: '添加使用产品的法律协议。',
terms_of_use: '使用条款',
terms_of_use_placeholder: 'https://your.terms.of.use/',
terms_of_use_tip: '使用条款 URL',

View file

@ -0,0 +1,86 @@
import type { DatabaseTransactionConnection } from 'slonik';
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
type DeprecatedTermsOfUse = {
enabled: boolean;
contentUrl?: string;
};
type DeprecatedSignInExperience = {
id: string;
termsOfUse: DeprecatedTermsOfUse;
};
type SignInExperience = {
id: string;
termsOfUseUrl?: string | null;
};
const alterTermsOfUse = async (
signInExperience: DeprecatedSignInExperience,
pool: DatabaseTransactionConnection
) => {
const {
id,
termsOfUse: { enabled, contentUrl },
} = signInExperience;
if (enabled && contentUrl) {
await pool.query(
sql`update sign_in_experiences set terms_of_use_url = ${contentUrl} where id = ${id}`
);
}
};
const rollbackTermsOfUse = async (
signInExperience: SignInExperience,
pool: DatabaseTransactionConnection
) => {
const { id, termsOfUseUrl } = signInExperience;
const termsOfUse: DeprecatedTermsOfUse = {
enabled: Boolean(termsOfUseUrl),
contentUrl: termsOfUseUrl ?? '',
};
await pool.query(
sql`update sign_in_experiences set terms_of_use = ${JSON.stringify(
termsOfUse
)} where id = ${id}`
);
};
const alteration: AlterationScript = {
up: async (pool) => {
const rows = await pool.many<DeprecatedSignInExperience>(
sql`select * from sign_in_experiences`
);
await pool.query(sql`
alter table sign_in_experiences add column terms_of_use_url varchar(2048)
`);
await Promise.all(rows.map(async (row) => alterTermsOfUse(row, pool)));
await pool.query(sql`
alter table sign_in_experiences drop column terms_of_use
`);
},
down: async (pool) => {
const rows = await pool.many<SignInExperience>(sql`select * from sign_in_experiences`);
await pool.query(sql`
alter table sign_in_experiences add column terms_of_use jsonb not null default '{}'::jsonb
`);
await Promise.all(rows.map(async (row) => rollbackTermsOfUse(row, pool)));
await pool.query(sql`
alter table sign_in_experiences drop column terms_of_use_url
`);
},
};
export default alteration;

View file

@ -119,13 +119,6 @@ export const brandingGuard = z.object({
export type Branding = z.infer<typeof brandingGuard>;
export const termsOfUseGuard = z.object({
enabled: z.boolean(),
contentUrl: z.string().url().optional().or(z.literal('')),
});
export type TermsOfUse = z.infer<typeof termsOfUseGuard>;
export const languageInfoGuard = z.object({
autoDetect: z.boolean(),
fallbackLanguage: languageTagGuard,

View file

@ -22,9 +22,7 @@ export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
autoDetect: true,
fallbackLanguage: 'en',
},
termsOfUse: {
enabled: false,
},
termsOfUseUrl: null,
signUp: {
identifiers: [SignInIdentifier.Username],
password: true,

View file

@ -5,7 +5,7 @@ create table sign_in_experiences (
color jsonb /* @use Color */ not null,
branding jsonb /* @use Branding */ not null,
language_info jsonb /* @use LanguageInfo */ not null,
terms_of_use jsonb /* @use TermsOfUse */ not null,
terms_of_use_url varchar(2048),
sign_in jsonb /* @use SignIn */ not null,
sign_up jsonb /* @use SignUp */ not null,
social_sign_in_connector_targets jsonb /* @use ConnectorTargets */ not null default '[]'::jsonb,

View file

@ -192,10 +192,7 @@ export const mockSignInExperience: SignInExperience = {
logoUrl: 'http://logto.png',
slogan: 'logto',
},
termsOfUse: {
enabled: true,
contentUrl: 'http://terms.of.use/',
},
termsOfUseUrl: 'http://terms.of.use/',
languageInfo: {
autoDetect: true,
fallbackLanguage: 'en',
@ -216,7 +213,7 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
id: mockSignInExperience.id,
color: mockSignInExperience.color,
branding: mockSignInExperience.branding,
termsOfUse: mockSignInExperience.termsOfUse,
termsOfUseUrl: mockSignInExperience.termsOfUseUrl,
languageInfo: mockSignInExperience.languageInfo,
signIn: mockSignInExperience.signIn,
signUp: {

View file

@ -2,6 +2,7 @@ import { useContext } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import TextLink from '@/components/TextLink';
import type { Props as TextLinkProps } from '@/components/TextLink';
import type { ModalContentRenderProps } from '@/hooks/use-confirm-modal';
import { PageContext } from '@/hooks/use-page-context';
import usePlatform from '@/hooks/use-platform';
@ -9,19 +10,19 @@ import { ConfirmModalMessage } from '@/types';
const TermsOfUseConfirmModalContent = ({ cancel }: ModalContentRenderProps) => {
const { experienceSettings } = useContext(PageContext);
const { termsOfUse } = experienceSettings ?? {};
const { termsOfUseUrl } = experienceSettings ?? {};
const { t } = useTranslation();
const { isMobile } = usePlatform();
const linkProps = isMobile
const linkProps: TextLinkProps = isMobile
? {
onClick: () => {
cancel(ConfirmModalMessage.SHOW_TERMS_DETAIL_MODAL);
},
}
: {
href: termsOfUse?.contentUrl,
href: termsOfUseUrl ?? undefined,
target: '_blank',
};

View file

@ -7,11 +7,11 @@ type Props = {
};
const TermsOfUse = ({ className }: Props) => {
const { termsAgreement, setTermsAgreement, termsSettings, termsOfUseIframeModalHandler } =
const { termsAgreement, setTermsAgreement, termsOfUseUrl, termsOfUseIframeModalHandler } =
useTerms();
const { isMobile } = usePlatform();
if (!termsSettings?.enabled || !termsSettings.contentUrl) {
if (!termsOfUseUrl) {
return null;
}
@ -19,7 +19,7 @@ const TermsOfUse = ({ className }: Props) => {
<TermsOfUseComponent
className={className}
name="termsAgreement"
termsUrl={termsSettings.contentUrl}
termsUrl={termsOfUseUrl}
isChecked={termsAgreement}
onChange={(checked) => {
setTermsAgreement(checked);

View file

@ -12,12 +12,12 @@ const useTerms = () => {
const { termsAgreement, setTermsAgreement, experienceSettings } = useContext(PageContext);
const { show } = useConfirmModal();
const { termsOfUse } = experienceSettings ?? {};
const { termsOfUseUrl } = experienceSettings ?? {};
const termsOfUseIframeModalHandler = useCallback(async () => {
const [result] = await show({
className: styles.iframeModal,
ModalContent: () => createIframeConfirmModalContent(termsOfUse?.contentUrl),
ModalContent: () => createIframeConfirmModalContent(termsOfUseUrl ?? undefined),
confirmText: 'action.agree',
});
@ -27,7 +27,7 @@ const useTerms = () => {
}
return result;
}, [setTermsAgreement, show, termsOfUse?.contentUrl]);
}, [setTermsAgreement, show, termsOfUseUrl]);
const termsOfUseConfirmModalHandler = useCallback(async () => {
const [result, data] = await show({
@ -51,15 +51,15 @@ const useTerms = () => {
}, [setTermsAgreement, show, termsOfUseIframeModalHandler]);
const termsValidation = useCallback(async () => {
if (termsAgreement || !termsOfUse?.enabled || !termsOfUse.contentUrl) {
if (termsAgreement || !termsOfUseUrl) {
return true;
}
return termsOfUseConfirmModalHandler();
}, [termsAgreement, termsOfUse, termsOfUseConfirmModalHandler]);
}, [termsAgreement, termsOfUseUrl, termsOfUseConfirmModalHandler]);
return {
termsSettings: termsOfUse,
termsOfUseUrl,
termsAgreement,
termsValidation,
setTermsAgreement,

View file

@ -16,7 +16,7 @@ describe('getSignInExperienceSettings', () => {
expect(settings.branding).toEqual(mockSignInExperience.branding);
expect(settings.languageInfo).toEqual(mockSignInExperience.languageInfo);
expect(settings.termsOfUse).toEqual(mockSignInExperience.termsOfUse);
expect(settings.termsOfUseUrl).toEqual(mockSignInExperience.termsOfUseUrl);
expect(settings.signUp.identifiers).toContain('username');
expect(settings.signIn.methods).toHaveLength(3);
});