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:
parent
f3baaf919f
commit
bc5f4b541a
28 changed files with 196 additions and 216 deletions
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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],
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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: [],
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
86
packages/schemas/alterations/next-1671080370-terms-of-use.ts
Normal file
86
packages/schemas/alterations/next-1671080370-terms-of-use.ts
Normal 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;
|
|
@ -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,
|
||||
|
|
|
@ -22,9 +22,7 @@ export const defaultSignInExperience: Readonly<CreateSignInExperience> = {
|
|||
autoDetect: true,
|
||||
fallbackLanguage: 'en',
|
||||
},
|
||||
termsOfUse: {
|
||||
enabled: false,
|
||||
},
|
||||
termsOfUseUrl: null,
|
||||
signUp: {
|
||||
identifiers: [SignInIdentifier.Username],
|
||||
password: true,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue