Merged PR 229: 再コミット_画面実装(カードライセンス取り込みPU)
## 概要 [Task2171: 再コミット_画面実装(カードライセンス取り込みPU)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2171) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
3584a65682
commit
883224c914
@ -7,6 +7,7 @@ import ui from "features/ui/uiSlice";
|
||||
import user from "features/user/userSlice";
|
||||
import license from "features/license/licenseOrder/licenseSlice";
|
||||
import licenseCardIssue from "features/license/licenseCardIssue/licenseCardIssueSlice";
|
||||
import licenseCardActivate from "features/license/licenseCardActivate/licenseCardActivateSlice";
|
||||
import licenseSummary from "features/license/licenseSummary/licenseSummarySlice";
|
||||
import dictation from "features/dictation/dictationSlice";
|
||||
|
||||
@ -20,6 +21,7 @@ export const store = configureStore({
|
||||
user,
|
||||
license,
|
||||
licenseCardIssue,
|
||||
licenseCardActivate,
|
||||
licenseSummary,
|
||||
dictation,
|
||||
},
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
export * from "./licenseCardActivateSlice";
|
||||
@ -0,0 +1,31 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
import { LicenseCardActivateState } from "./state";
|
||||
import { activateCardLicenseAsync } from "./operations";
|
||||
|
||||
const initialState: LicenseCardActivateState = {
|
||||
apps: {
|
||||
keyLicense: "",
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
export const licenseCardActivateSlice = createSlice({
|
||||
name: "licenseCardActivate",
|
||||
initialState,
|
||||
reducers: {
|
||||
changeKeyLicense: (
|
||||
state,
|
||||
action: PayloadAction<{ keyLicense: string }>
|
||||
) => {
|
||||
const { keyLicense } = action.payload;
|
||||
state.apps.keyLicense = keyLicense.toUpperCase();
|
||||
},
|
||||
cleanupApps: (state) => {
|
||||
state.apps = initialState.apps;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { changeKeyLicense, cleanupApps } =
|
||||
licenseCardActivateSlice.actions;
|
||||
|
||||
export default licenseCardActivateSlice.reducer;
|
||||
@ -0,0 +1,64 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { openSnackbar } from "../../ui/uiSlice";
|
||||
import type { RootState } from "../../../app/store";
|
||||
import { getTranslationID } from "../../../translation";
|
||||
import { LicensesApi } from "../../../api/api";
|
||||
import { Configuration } from "../../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../../common/errors";
|
||||
|
||||
export const activateCardLicenseAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
// パラメータ
|
||||
cardLicenseKey: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型s
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("licenses/activateCardLicenseAsync", async (args, thunkApi) => {
|
||||
const { cardLicenseKey } = args;
|
||||
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration, accessToken } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const licensesApi = new LicensesApi(config);
|
||||
|
||||
try {
|
||||
await licensesApi.activateCardLicenses(
|
||||
{
|
||||
cardLicenseKey,
|
||||
},
|
||||
{
|
||||
headers: { authorization: `Bearer ${accessToken}` },
|
||||
}
|
||||
);
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "info",
|
||||
message: getTranslationID("common.message.success"),
|
||||
})
|
||||
);
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換"
|
||||
const error = createErrorObject(e);
|
||||
|
||||
const errorMessage = getTranslationID("common.message.internalServerError");
|
||||
|
||||
thunkApi.dispatch(
|
||||
openSnackbar({
|
||||
level: "error",
|
||||
message: errorMessage,
|
||||
})
|
||||
);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import { RootState } from "../../../app/store";
|
||||
|
||||
export const selectInputValidationErrors = (state: RootState) => {
|
||||
const { keyLicense } = state.licenseCardActivate.apps;
|
||||
const hasErrorIncorrectKeyNumber = checkErrorIncorrectKeyNumber(keyLicense);
|
||||
return {
|
||||
hasErrorIncorrectKeyNumber,
|
||||
};
|
||||
};
|
||||
export const checkErrorIncorrectKeyNumber = (keyLicense: string): boolean =>
|
||||
// // 20+4(20文字+space4個)以外の場合はエラー
|
||||
keyLicense.length !== 24;
|
||||
|
||||
export const selectKeyLicense = (state: RootState) =>
|
||||
state.licenseCardActivate.apps.keyLicense;
|
||||
|
||||
export const selectIsLoading = (state: RootState) =>
|
||||
state.licenseCardActivate.apps.isLoading;
|
||||
@ -0,0 +1,8 @@
|
||||
export interface LicenseCardActivateState {
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
keyLicense: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
@ -1,6 +1,20 @@
|
||||
import React, { useCallback } from "react";
|
||||
import styles from "styles/app.module.scss";
|
||||
import React, { useState, useCallback, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AppDispatch } from "app/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import _ from "lodash";
|
||||
import styles from "../../styles/app.module.scss";
|
||||
import { getTranslationID } from "../../translation";
|
||||
import close from "../../assets/images/close.svg";
|
||||
import {
|
||||
activateCardLicenseAsync,
|
||||
selectKeyLicense,
|
||||
cleanupApps,
|
||||
selectIsLoading,
|
||||
changeKeyLicense,
|
||||
selectInputValidationErrors,
|
||||
} from "../../features/license/licenseCardActivate/index";
|
||||
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||
|
||||
interface CardLicenseActivatePopupProps {
|
||||
onClose: () => void;
|
||||
@ -10,18 +24,92 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
CardLicenseActivatePopupProps
|
||||
> = (props) => {
|
||||
const { onClose } = props;
|
||||
const { t } = useTranslation();
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const cardLicenseKey = useSelector(selectKeyLicense);
|
||||
const [keyNumber, setKeyNumber] = useState<string>(cardLicenseKey);
|
||||
const isLoading = useSelector(selectIsLoading);
|
||||
|
||||
// ポップアップを閉じる処理
|
||||
const closePopup = useCallback(() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
}, [isLoading, onClose]);
|
||||
|
||||
// ブラウザのウィンドウが閉じられようとしている場合に発火するイベントハンドラ
|
||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
// isLoadingがtrueの場合は確認ダイアログを表示する
|
||||
if (isLoading) {
|
||||
e.preventDefault();
|
||||
// ChromeではreturnValueが必要
|
||||
e.returnValue = "";
|
||||
}
|
||||
};
|
||||
// コンポーネントがマウントされた時にイベントハンドラを登録する
|
||||
useEffect(() => {
|
||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||
// コンポーネントがアンマウントされるときにイベントハンドラを解除する
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
// useEffectのreturnとしてcleanupAppsを実行することで、ポップアップのアンマウント時に初期化を行う
|
||||
dispatch(cleanupApps());
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const [isPushActivateButton, setIsPushActivateButton] =
|
||||
useState<boolean>(false);
|
||||
|
||||
// エラー宣言
|
||||
const { hasErrorIncorrectKeyNumber } = useSelector(
|
||||
selectInputValidationErrors
|
||||
);
|
||||
|
||||
// activateボタン押下時
|
||||
const onActivateLicense = useCallback(async () => {
|
||||
setIsPushActivateButton(true);
|
||||
|
||||
if (keyNumber.length !== 24) {
|
||||
const inputBox = document.getElementById("inputBox");
|
||||
// カーソルをテキストボックスに戻す
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// activateAPIの呼び出し
|
||||
const cardLicenseKeyWithoutSpaces = keyNumber.replace(/\s/g, "");
|
||||
const { meta } = await dispatch(
|
||||
activateCardLicenseAsync({ cardLicenseKey: cardLicenseKeyWithoutSpaces })
|
||||
);
|
||||
setIsPushActivateButton(false);
|
||||
|
||||
// カーソルをテキストボックスに戻す
|
||||
const inputBox = document.getElementById("inputBox");
|
||||
if (inputBox) {
|
||||
inputBox.focus();
|
||||
}
|
||||
|
||||
if (meta.requestStatus === "fulfilled") {
|
||||
dispatch(cleanupApps());
|
||||
setKeyNumber("");
|
||||
}
|
||||
}, [keyNumber, dispatch]);
|
||||
|
||||
// HTML
|
||||
return (
|
||||
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||
<div className={styles.modalBox}>
|
||||
<p className={styles.modalTitle}>
|
||||
Activate License Key
|
||||
{t(getTranslationID("cardLicenseActivatePopupPage.label.title"))}
|
||||
<button type="button" onClick={closePopup}>
|
||||
<img src={close} className={styles.modalTitleIcon} alt="close" />
|
||||
</button>
|
||||
@ -30,24 +118,93 @@ export const CardLicenseActivatePopup: React.FC<
|
||||
<form className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dt>Key number</dt>
|
||||
<dt>
|
||||
<label htmlFor="inputBox">
|
||||
{t(
|
||||
getTranslationID(
|
||||
"cardLicenseActivatePopupPage.label.keyNumber"
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
</dt>
|
||||
<dd className="">
|
||||
<input
|
||||
id="inputBox"
|
||||
type="text"
|
||||
size={40}
|
||||
size={48}
|
||||
name=""
|
||||
value=""
|
||||
maxLength={20}
|
||||
value={keyNumber}
|
||||
maxLength={24} // 20+4(20文字+space4個)
|
||||
className={styles.formInput}
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus
|
||||
onChange={(e) => {
|
||||
let input = e.target.value.toUpperCase();
|
||||
input = input.replace(/[^A-Z0-9]/g, "");
|
||||
// _.chunk関数で、配列の要素を一定の要素数ごとに分ける
|
||||
input = _.chunk(input, 4)
|
||||
.map((a) => a.join(""))
|
||||
.join(" ");
|
||||
setKeyNumber(input);
|
||||
if (input.includes("\n")) {
|
||||
dispatch(
|
||||
changeKeyLicense({
|
||||
keyLicense: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
onActivateLicense();
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
dispatch(
|
||||
changeKeyLicense({
|
||||
keyLicense: e.target.value.toUpperCase(),
|
||||
})
|
||||
);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const input = e.target.value.toUpperCase();
|
||||
setKeyNumber(input);
|
||||
const button = document.getElementById("button");
|
||||
if (button) {
|
||||
button.focus();
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isPushActivateButton && hasErrorIncorrectKeyNumber && (
|
||||
<span className={styles.formError}>
|
||||
{t(
|
||||
getTranslationID(
|
||||
"cardLicenseActivatePopupPage.label.keyNumberIncorrectError"
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<input
|
||||
id="button"
|
||||
type="button"
|
||||
name="submit"
|
||||
value="Activate"
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
||||
onClick={closePopup}
|
||||
value={t(
|
||||
getTranslationID(
|
||||
"cardLicenseActivatePopupPage.label.activateButton"
|
||||
)
|
||||
)}
|
||||
onClick={onActivateLicense}
|
||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||
!isLoading ? styles.isActive : ""
|
||||
}`}
|
||||
/>
|
||||
<img
|
||||
style={{ display: isLoading ? "inline" : "none" }}
|
||||
src={progress_activit}
|
||||
className={styles.icLoading}
|
||||
alt="Loading"
|
||||
/>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
@ -238,5 +238,17 @@
|
||||
"label": {
|
||||
"cardLicenseButton": "(de)License Card"
|
||||
}
|
||||
},
|
||||
"cardLicenseActivatePopupPage": {
|
||||
"label": {
|
||||
"title": "(de)Activate License Key",
|
||||
"keyNumber": "(de)Key number",
|
||||
"activateButton": "(de)activate",
|
||||
"keyNumberIncorrectError": "(de)Key Numberには20桁の半角大文字英数字を入力してください。"
|
||||
},
|
||||
"message": {
|
||||
"LicenseKeyNotExistError": "(de)入力されたライセンスキーは存在しません。ライセンスキーを再度お確かめください。",
|
||||
"LicenseKeyAlreadyActivatedError": "(de)入力されたライセンスキーは、既に有効化されています。ライセンスキーを再度お確かめください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,5 +238,17 @@
|
||||
"label": {
|
||||
"cardLicenseButton": "License Card"
|
||||
}
|
||||
},
|
||||
"cardLicenseActivatePopupPage": {
|
||||
"label": {
|
||||
"title": "Activate License Key",
|
||||
"keyNumber": "Key number",
|
||||
"activateButton": "activate",
|
||||
"keyNumberIncorrectError": "Key Numberには20桁の半角大文字英数字を入力してください。"
|
||||
},
|
||||
"message": {
|
||||
"LicenseKeyNotExistError": "入力されたライセンスキーは存在しません。ライセンスキーを再度お確かめください。",
|
||||
"LicenseKeyAlreadyActivatedError": "入力されたライセンスキーは、既に有効化されています。ライセンスキーを再度お確かめください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,5 +238,17 @@
|
||||
"label": {
|
||||
"cardLicenseButton": "(es)License Card"
|
||||
}
|
||||
},
|
||||
"cardLicenseActivatePopupPage": {
|
||||
"label": {
|
||||
"title": "(es)Activate License Key",
|
||||
"keyNumber": "(es)Key number",
|
||||
"activateButton": "(es)activate",
|
||||
"keyNumberIncorrectError": "(es)Key Numberには20桁の半角大文字英数字を入力してください。"
|
||||
},
|
||||
"message": {
|
||||
"LicenseKeyNotExistError": "(es)入力されたライセンスキーは存在しません。ライセンスキーを再度お確かめください。",
|
||||
"LicenseKeyAlreadyActivatedError": "(es)入力されたライセンスキーは、既に有効化されています。ライセンスキーを再度お確かめください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,5 +238,17 @@
|
||||
"label": {
|
||||
"cardLicenseButton": "(fr)License Card"
|
||||
}
|
||||
},
|
||||
"cardLicenseActivatePopupPage": {
|
||||
"label": {
|
||||
"title": "(fr)Activate License Key",
|
||||
"keyNumber": "(fr)Key number",
|
||||
"activateButton": "(fr)activate",
|
||||
"keyNumberIncorrectError": "(fr)Key Numberには20桁の半角大文字英数字を入力してください。"
|
||||
},
|
||||
"message": {
|
||||
"LicenseKeyNotExistError": "(fr)入力されたライセンスキーは存在しません。ライセンスキーを再度お確かめください。",
|
||||
"LicenseKeyAlreadyActivatedError": "(fr)入力されたライセンスキーは、既に有効化されています。ライセンスキーを再度お確かめください。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user