From 4565d59a51ef1e18e555aa6d0136d40e485e4d65 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 7 Aug 2023 00:11:54 +0000 Subject: [PATCH 1/7] =?UTF-8?q?Merged=20PR=20301:=20=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E8=BF=BD=E5=8A=A0=E4=BF=AE=E6=AD=A3=EF=BC=88?= =?UTF-8?q?API/=E7=94=BB=E9=9D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2327: ユーザー追加修正(API/画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2327) - ユーザー追加API - リクエストにencryption,encryptionPassword,promptを追加 - リクエストからtypistGroupIdを削除 - ロールに応じてDBに保存するデータを作成する処理を追加 - リクエストパラメータのバリデーションチェックを追加 - ユーザー追加画面 - TypistGroupの選択欄を削除 - RoleがAuthorの場合、encryption,encryptionPassword,promptを追加 ## レビューポイント - 修正に不足はないか - 画面のユーザー追加処理を引数を渡さずにstoreから取得するようにしたが問題ないか - 画面に必要な値をまとめて取るようにしたが問題ないか - デザインに差異はないか ## UIの変更 - https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task2327?csf=1&web=1&e=uvTYlb ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_client/src/api/api.ts | 158 +++++++++++- .../src/features/user/operations.ts | 44 ++-- .../src/features/user/selectors.ts | 54 ++++- dictation_client/src/features/user/state.ts | 5 +- dictation_client/src/features/user/types.ts | 12 + .../src/features/user/userSlice.ts | 37 ++- .../src/pages/UserListPage/popup.tsx | 193 ++++++++------- dictation_client/src/styles/app.module.scss | 227 ++++++++++++++++-- .../src/styles/app.module.scss.d.ts | 10 +- dictation_client/src/translation/de.json | 7 +- dictation_client/src/translation/en.json | 7 +- dictation_client/src/translation/es.json | 7 +- dictation_client/src/translation/fr.json | 7 +- dictation_server/src/api/odms/openapi.json | 6 +- .../src/common/types/role/index.ts | 2 + .../encryptionPassword.validator.ts | 33 ++- .../common/validators/roleAuthor.validator.ts | 8 +- .../src/features/users/types/types.ts | 22 +- .../src/features/users/users.controller.ts | 11 +- .../src/features/users/users.service.spec.ts | 68 +++++- .../src/features/users/users.service.ts | 108 +++++++-- .../repositories/users/entity/user.entity.ts | 26 +- .../users/users.repository.service.ts | 59 ++--- 23 files changed, 856 insertions(+), 255 deletions(-) diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index dcd3d1d..ebfe077 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -880,6 +880,67 @@ export interface PostSortCriteriaRequest { */ 'paramName': string; } +/** + * + * @export + * @interface PostUpdateUserRequest + */ +export interface PostUpdateUserRequest { + /** + * + * @type {number} + * @memberof PostUpdateUserRequest + */ + 'id': number; + /** + * none/author/typist + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'role': string; + /** + * + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'authorId'?: string; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'autoRenew': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'licenseAlart': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'notification': boolean; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'encryption'?: boolean; + /** + * + * @type {string} + * @memberof PostUpdateUserRequest + */ + 'encryptionPassword'?: string; + /** + * + * @type {boolean} + * @memberof PostUpdateUserRequest + */ + 'prompt'?: boolean; +} /** * * @export @@ -923,12 +984,6 @@ export interface SignupRequest { * @memberof SignupRequest */ 'authorId'?: string; - /** - * - * @type {number} - * @memberof SignupRequest - */ - 'typistGroupId'?: number; /** * * @type {string} @@ -953,6 +1008,24 @@ export interface SignupRequest { * @memberof SignupRequest */ 'notification': boolean; + /** + * + * @type {boolean} + * @memberof SignupRequest + */ + 'encryption'?: boolean; + /** + * + * @type {string} + * @memberof SignupRequest + */ + 'encryptionPassword'?: string; + /** + * + * @type {boolean} + * @memberof SignupRequest + */ + 'prompt'?: boolean; } /** * @@ -3852,6 +3925,46 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; localVarRequestOptions.data = serializeDataIfNeeded(postSortCriteriaRequest, localVarRequestOptions, configuration) + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateUser: async (postUpdateUserRequest: PostUpdateUserRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'postUpdateUserRequest' is not null or undefined + assertParamExists('updateUser', 'postUpdateUserRequest', postUpdateUserRequest) + const localVarPath = `/users/update`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(postUpdateUserRequest, localVarRequestOptions, configuration) + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -3941,6 +4054,17 @@ export const UsersApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateSortCriteria(postSortCriteriaRequest, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateUser(postUpdateUserRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, } }; @@ -4018,6 +4142,16 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath updateSortCriteria(postSortCriteriaRequest: PostSortCriteriaRequest, options?: any): AxiosPromise { return localVarFp.updateSortCriteria(postSortCriteriaRequest, options).then((request) => request(axios, basePath)); }, + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: any): AxiosPromise { + return localVarFp.updateUser(postUpdateUserRequest, options).then((request) => request(axios, basePath)); + }, }; }; @@ -4108,6 +4242,18 @@ export class UsersApi extends BaseAPI { public updateSortCriteria(postSortCriteriaRequest: PostSortCriteriaRequest, options?: AxiosRequestConfig) { return UsersApiFp(this.configuration).updateSortCriteria(postSortCriteriaRequest, options).then((request) => request(this.axios, this.basePath)); } + + /** + * ユーザーの情報を更新します + * @summary + * @param {PostUpdateUserRequest} postUpdateUserRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UsersApi + */ + public updateUser(postUpdateUserRequest: PostUpdateUserRequest, options?: AxiosRequestConfig) { + return UsersApiFp(this.configuration).updateUser(postUpdateUserRequest, options).then((request) => request(this.axios, this.basePath)); + } } diff --git a/dictation_client/src/features/user/operations.ts b/dictation_client/src/features/user/operations.ts index c364a0c..0616cac 100644 --- a/dictation_client/src/features/user/operations.ts +++ b/dictation_client/src/features/user/operations.ts @@ -1,8 +1,9 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import type { RootState } from "app/store"; -import { getTranslationID } from "translation"; +import { USER_ROLES } from "components/auth/constants"; import { openSnackbar } from "features/ui/uiSlice"; -import { SignupRequest, UsersApi, GetUsersResponse } from "../../api/api"; +import { getTranslationID } from "translation"; +import { GetUsersResponse, UsersApi } from "../../api/api"; import { Configuration } from "../../api/configuration"; import { ErrorObject, createErrorObject } from "../../common/errors"; @@ -43,7 +44,7 @@ export const addUserAsync = createAsyncThunk< { /* Empty Object */ }, - SignupRequest, + void, { // rejectした時の返却値の型 rejectValue: { @@ -51,40 +52,25 @@ export const addUserAsync = createAsyncThunk< }; } >("users/addUserAsync", async (args, thunkApi) => { - const { - name, - email, - role, - authorId, - typistGroupId, - autoRenew, - licenseAlert, - notification, - } = args; - // apiのConfigurationを取得する const { getState } = thunkApi; const state = getState() as RootState; const { configuration, accessToken } = state.auth; const config = new Configuration(configuration); const usersApi = new UsersApi(config); + const { addUser } = state.user.apps; + // roleがAUTHOR以外の場合、不要なプロパティをundefinedにする + if (addUser.role !== USER_ROLES.AUTHOR) { + addUser.authorId = undefined; + addUser.encryption = undefined; + addUser.prompt = undefined; + addUser.encryptionPassword = undefined; + } try { - await usersApi.signup( - { - name, - email, - role, - authorId, - typistGroupId, - autoRenew, - licenseAlert, - notification, - }, - { - headers: { authorization: `Bearer ${accessToken}` }, - } - ); + await usersApi.signup(addUser, { + headers: { authorization: `Bearer ${accessToken}` }, + }); thunkApi.dispatch( openSnackbar({ level: "info", diff --git a/dictation_client/src/features/user/selectors.ts b/dictation_client/src/features/user/selectors.ts index 6f2029a..a416b9f 100644 --- a/dictation_client/src/features/user/selectors.ts +++ b/dictation_client/src/features/user/selectors.ts @@ -1,15 +1,24 @@ import { RootState } from "app/store"; import { USER_ROLES } from "components/auth/constants"; -import { RoleType, UserView, isLicenseStatusType, isRoleType } from "./types"; +import { + AddUser, + RoleType, + UserView, + isLicenseStatusType, + isRoleType, +} from "./types"; import { LICENSE_STATUS } from "./constants"; export const selectInputValidationErrors = (state: RootState) => { - const { name, email, role, authorId } = state.user.apps.addUser; + const { name, email, role, authorId, encryption, encryptionPassword } = + state.user.apps.addUser; // 必須項目のチェック const hasErrorEmptyName = name === ""; const hasErrorEmptyEmail = email === ""; - const hasErrorEmptyAuthorId = role === USER_ROLES.AUTHOR && authorId === ""; + // Authorの場合、AuthorIDが必須(空文字,undefinedは不可) + const hasErrorEmptyAuthorId = + role === USER_ROLES.AUTHOR && (authorId === "" || !authorId); const hasErrorIncorrectAuthorId = checkErrorIncorrectAuthorId( authorId ?? undefined, @@ -18,14 +27,46 @@ export const selectInputValidationErrors = (state: RootState) => { const hasErrorIncorrectEmail = email.match(/^[^@]+@[^@]+$/)?.length !== 1; + const hasErrorIncorrectEncryptionPassword = + checkErrorIncorrectEncryptionPassword(encryptionPassword, role, encryption); + return { hasErrorEmptyName, hasErrorEmptyEmail, hasErrorEmptyAuthorId, hasErrorIncorrectEmail, hasErrorIncorrectAuthorId, + hasErrorIncorrectEncryptionPassword, }; }; + +// encreyptionPasswordのチェック +const checkErrorIncorrectEncryptionPassword = ( + encryptionPassword: string | undefined, + role: RoleType, + encryption: boolean | undefined +): boolean => { + // roleがAuthor以外の場合、チェックしない + if (role !== USER_ROLES.AUTHOR) { + return false; + } + // roleがAuthorかつencryptionがfalseの場合、チェックしない + if (!encryption) { + return false; + } + // encryptionPasswordがundefined,空文字の場合、エラー + if (!encryptionPassword || encryptionPassword === "") { + return true; + } + // encryptionPasswordがルールに則していない場合、エラー + const regex = /^[!-~]{4,16}$/; + if (!regex.test(encryptionPassword)) { + return true; + } + // チェックを通ったらエラーではない + return false; +}; + export const checkErrorIncorrectAuthorId = ( authorId: string | undefined, role: string @@ -46,14 +87,15 @@ export const selectEmail = (state: RootState) => state.user.apps.addUser.email; export const selectRole = (state: RootState) => state.user.apps.addUser.role; export const selectAuthorId = (state: RootState) => state.user.apps.addUser.authorId; -export const selectTypistGroupId = (state: RootState) => - state.user.apps.addUser.typistGroupId; export const selectAutoRenew = (state: RootState) => state.user.apps.addUser.autoRenew; export const selectLicenseAlert = (state: RootState) => state.user.apps.addUser.licenseAlert; -export const selectNtotification = (state: RootState) => +export const selectNotification = (state: RootState) => state.user.apps.addUser.notification; +// AddUserを返却する +export const selectAddUser = (state: RootState): AddUser => + state.user.apps.addUser; // usersからUserViewに変換して返却する export const selectUserViews = (state: RootState): UserView[] => { const { users } = state.user.domain; diff --git a/dictation_client/src/features/user/state.ts b/dictation_client/src/features/user/state.ts index 1a0933a..9b6f3ee 100644 --- a/dictation_client/src/features/user/state.ts +++ b/dictation_client/src/features/user/state.ts @@ -1,4 +1,5 @@ import { User } from "../../api/api"; +import { AddUser } from "./types"; export interface UsersState { domain: Domain; @@ -13,7 +14,3 @@ export interface Apps { addUser: AddUser; isLoading: boolean; } - -export interface AddUser extends Omit { - typistGroupId?: number | undefined; -} diff --git a/dictation_client/src/features/user/types.ts b/dictation_client/src/features/user/types.ts index ba98cb5..4af56bb 100644 --- a/dictation_client/src/features/user/types.ts +++ b/dictation_client/src/features/user/types.ts @@ -23,6 +23,18 @@ export interface UserView expiration: string; remaining: number | string; } +export interface AddUser { + name: string; + role: RoleType; + email: string; + autoRenew: boolean; + licenseAlert: boolean; + notification: boolean; + authorId?: string; + encryption?: boolean; + encryptionPassword?: string; + prompt?: boolean; +} export type RoleType = typeof USER_ROLES[keyof typeof USER_ROLES]; diff --git a/dictation_client/src/features/user/userSlice.ts b/dictation_client/src/features/user/userSlice.ts index 8cdf90f..b7f6d2c 100644 --- a/dictation_client/src/features/user/userSlice.ts +++ b/dictation_client/src/features/user/userSlice.ts @@ -10,16 +10,14 @@ const initialState: UsersState = { addUser: { name: "", role: USER_ROLES.NONE, - authorId: "", - typistGroupName: [], email: "", - emailVerified: true, autoRenew: true, licenseAlert: true, notification: true, - encryption: true, - licenseStatus: "", + authorId: "", + encryption: false, prompt: false, + encryptionPassword: "", }, isLoading: false, }, @@ -48,13 +46,6 @@ export const userSlice = createSlice({ const { authorId } = action.payload; state.apps.addUser.authorId = authorId; }, - changeTypistGroupId: ( - state, - action: PayloadAction<{ typistGroupId: number | undefined }> - ) => { - const { typistGroupId } = action.payload; - state.apps.addUser.typistGroupId = typistGroupId; - }, changeAutoRenew: (state, action: PayloadAction<{ autoRenew: boolean }>) => { const { autoRenew } = action.payload; state.apps.addUser.autoRenew = autoRenew; @@ -66,6 +57,24 @@ export const userSlice = createSlice({ const { licenseAlert } = action.payload; state.apps.addUser.licenseAlert = licenseAlert; }, + changeEncryption: ( + state, + action: PayloadAction<{ encryption: boolean }> + ) => { + const { encryption } = action.payload; + state.apps.addUser.encryption = encryption; + }, + changePrompt: (state, action: PayloadAction<{ prompt: boolean }>) => { + const { prompt } = action.payload; + state.apps.addUser.prompt = prompt; + }, + changeEncryptionPassword: ( + state, + action: PayloadAction<{ encryptionPassword: string }> + ) => { + const { encryptionPassword } = action.payload; + state.apps.addUser.encryptionPassword = encryptionPassword; + }, changeNotification: ( state, action: PayloadAction<{ notification: boolean }> @@ -105,11 +114,13 @@ export const { changeEmail, changeRole, changeAuthorId, - changeTypistGroupId, changeAutoRenew, changeLicenseAlert, changeNotification, cleanupAddUser, + changeEncryption, + changePrompt, + changeEncryptionPassword, } = userSlice.actions; export default userSlice.reducer; diff --git a/dictation_client/src/pages/UserListPage/popup.tsx b/dictation_client/src/pages/UserListPage/popup.tsx index b469c40..9e0328f 100644 --- a/dictation_client/src/pages/UserListPage/popup.tsx +++ b/dictation_client/src/pages/UserListPage/popup.tsx @@ -9,22 +9,17 @@ import { changeName, changeRole, changeAuthorId, - changeTypistGroupId, changeAutoRenew, changeLicenseAlert, changeNotification, cleanupAddUser, - selectName, - selectEmail, - selectRole, - selectAuthorId, - selectTypistGroupId, - selectInputValidationErrors, - selectAutoRenew, - selectLicenseAlert, - selectNtotification, addUserAsync, + selectAddUser, + selectInputValidationErrors, selectIsLoading, + changePrompt, + changeEncryption, + changeEncryptionPassword, } from "features/user"; import { USER_ROLES } from "components/auth/constants"; import close from "../../assets/images/close.svg"; @@ -39,6 +34,9 @@ export const UserAddPopup: React.FC = (props) => { const { isOpen, onClose } = props; const dispatch: AppDispatch = useDispatch(); const { t } = useTranslation(); + const [isPasswordHide, setIsPasswordHide] = useState(true); + // AddUserの情報を取得 + const addUser = useSelector(selectAddUser); const closePopup = useCallback(() => { setIsPushCreateButton(false); @@ -52,17 +50,9 @@ export const UserAddPopup: React.FC = (props) => { hasErrorEmptyAuthorId, hasErrorIncorrectEmail, hasErrorIncorrectAuthorId, + hasErrorIncorrectEncryptionPassword, } = useSelector(selectInputValidationErrors); - const name = useSelector(selectName); - const email = useSelector(selectEmail); - const role = useSelector(selectRole); - const authorId = useSelector(selectAuthorId); - const typistGroupId = useSelector(selectTypistGroupId); - const autoRenew = useSelector(selectAutoRenew); - const licenseAlert = useSelector(selectLicenseAlert); - const notification = useSelector(selectNtotification); - const [isPushCreateButton, setIsPushCreateButton] = useState(false); const isLoading = useSelector(selectIsLoading); @@ -74,51 +64,27 @@ export const UserAddPopup: React.FC = (props) => { hasErrorEmptyEmail || hasErrorEmptyAuthorId || hasErrorIncorrectEmail || - hasErrorIncorrectAuthorId + hasErrorIncorrectAuthorId || + hasErrorIncorrectEncryptionPassword ) { return; } - if (role !== USER_ROLES.AUTHOR) { - changeAuthorId({ authorId: undefined }); - } - if (role !== USER_ROLES.TYPIST) { - changeTypistGroupId({ typistGroupId: undefined }); - } - const { meta } = await dispatch( - addUserAsync({ - name, - email, - role, - authorId: - role === USER_ROLES.AUTHOR ? authorId ?? undefined : undefined, - typistGroupId: role === USER_ROLES.TYPIST ? typistGroupId : undefined, - autoRenew, - licenseAlert, - notification, - }) - ); + const { meta } = await dispatch(addUserAsync()); setIsPushCreateButton(false); if (meta.requestStatus === "fulfilled") { closePopup(); } }, [ - dispatch, - closePopup, hasErrorEmptyName, hasErrorEmptyEmail, hasErrorEmptyAuthorId, hasErrorIncorrectEmail, hasErrorIncorrectAuthorId, - name, - email, - role, - authorId, - typistGroupId, - autoRenew, - licenseAlert, - notification, + hasErrorIncorrectEncryptionPassword, + dispatch, + closePopup, ]); return ( @@ -142,7 +108,7 @@ export const UserAddPopup: React.FC = (props) => { size={40} maxLength={255} className={styles.formInput} - value={name} + value={addUser.name} onChange={(e) => { dispatch(changeName({ name: e.target.value })); }} @@ -160,7 +126,7 @@ export const UserAddPopup: React.FC = (props) => { size={40} maxLength={255} className={styles.formInput} - value={email} + value={addUser.email} onChange={(e) => { dispatch(changeEmail({ email: e.target.value })); }} @@ -182,10 +148,11 @@ export const UserAddPopup: React.FC = (props) => {
{/** Author 選択時に表示 */} - {role === USER_ROLES.AUTHOR && ( + {addUser.role === USER_ROLES.AUTHOR && (
{t(getTranslationID("userListPage.label.authorID"))}
@@ -227,7 +196,7 @@ export const UserAddPopup: React.FC = (props) => { size={40} maxLength={16} className={styles.formInput} - value={authorId ?? undefined} + value={addUser.authorId ?? undefined} onChange={(e) => { dispatch(changeAuthorId({ authorId: e.target.value })); }} @@ -249,38 +218,88 @@ export const UserAddPopup: React.FC = (props) => { )}
-
- )} - - {/** Transcriptionist 選択時に表示 */} - {role === USER_ROLES.TYPIST && ( -
-
- {t(getTranslationID("userListPage.label.addToGroup"))} -
+
{t(getTranslationID("userListPage.label.encryption"))}
- + + {addUser.encryption && ( +

+ {t( + getTranslationID( + "userListPage.label.encryptionPassword" + ) + )} + { + dispatch( + changeEncryptionPassword({ + encryptionPassword: e.target.value, + }) + ); + }} + /> + { + setIsPasswordHide(!isPasswordHide); + }} + onKeyDown={() => { + setIsPasswordHide(!isPasswordHide); + }} + role="button" + tabIndex={0} + aria-label="IconEye" + /> + {isPushCreateButton && + hasErrorIncorrectEncryptionPassword && ( + + {t( + getTranslationID( + "userListPage.message.encryptionPasswordCorrectError" + ) + )} + + )} + + {t( + getTranslationID( + "userListPage.label.encryptionPasswordTerm" + ) + )} + +

+ )} +
+
{t(getTranslationID("userListPage.label.prompt"))}
+
+
)} -
{t(getTranslationID("userListPage.label.setting"))}

@@ -288,7 +307,7 @@ export const UserAddPopup: React.FC = (props) => { { dispatch( changeAutoRenew({ autoRenew: e.target.checked }) @@ -302,7 +321,7 @@ export const UserAddPopup: React.FC = (props) => {