Merged PR 65: 画面実装(ユーザー認証画面)
## 概要 [Task1495: 画面実装(ユーザー認証画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1495) - メールの認証URLから、認証を実行して結果を表示するまでの画面を実装しました。 - 認証画面 - 認証完了画面 - 成功 - 失敗 - 認証済み - エラーハンドリング用のメソッドを`common`に追加しました。 - メールに送信される認証URLのパスを認証画面のパスに修正しました。 ## レビューポイント - エラーハンドリング用の処理は適切でしょうか? - 改行を画面に対応させるために暫定の処置を入れています。対応に問題はないでしょうか。 - アカウント登録のメール送信について、パスを対象となる画面のパス`mail-confirm/`に変更しました。 - 対応として適切でしょうか? ## UIの変更 - [Task1495](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/Task1495?csf=1&web=1&e=bqT7nz) ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
bd4aaa8ae1
commit
50f4cf5070
@ -2,6 +2,6 @@
|
||||
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||
"spaces": 2,
|
||||
"generator-cli": {
|
||||
"version": "6.4.0"
|
||||
"version": "6.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,10 @@ import { AuthErrorPage } from "pages/ErrorPage";
|
||||
import { NotFoundPage } from "pages/ErrorPage/notFound";
|
||||
import { RouteAuthGuard } from "components/auth/routeAuthGuard";
|
||||
import SignupPage from "pages/SignupPage";
|
||||
import VerifyPage from "pages/VerifyPage";
|
||||
import VerifySuccessPage from "pages/VerifySuccessPage";
|
||||
import VerifyFailedPage from "pages/VerifyFailedPage";
|
||||
import VerifyAlreadyExistPage from "pages/VerifyAlreadyExistPage";
|
||||
import SignupCompletePage from "pages/SignupCompletePage";
|
||||
|
||||
const AppRouter: React.FC = () => (
|
||||
@ -18,6 +22,13 @@ const AppRouter: React.FC = () => (
|
||||
element={<SignupPage completeTo="/signup/complete" />}
|
||||
/>
|
||||
<Route path="/signup/complete" element={<SignupCompletePage />} />
|
||||
<Route path="/mail-confirm/" element={<VerifyPage />} />
|
||||
<Route path="/mail-confirm/success" element={<VerifySuccessPage />} />
|
||||
<Route path="/mail-confirm/failed" element={<VerifyFailedPage />} />
|
||||
<Route
|
||||
path="/mail-confirm/alreadyExist"
|
||||
element={<VerifyAlreadyExistPage />}
|
||||
/>
|
||||
<Route
|
||||
path="/xxx"
|
||||
element={<RouteAuthGuard component={<SamplePage />} />}
|
||||
|
||||
@ -1 +1 @@
|
||||
6.4.0
|
||||
6.5.0
|
||||
@ -123,6 +123,25 @@ export interface ErrorResponse {
|
||||
*/
|
||||
'code': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface RegisterRequest
|
||||
*/
|
||||
export interface RegisterRequest {
|
||||
/**
|
||||
* wns or apns
|
||||
* @type {string}
|
||||
* @memberof RegisterRequest
|
||||
*/
|
||||
'pns': string;
|
||||
/**
|
||||
* wnsのチャネルURI or apnsのデバイストークン
|
||||
* @type {string}
|
||||
* @memberof RegisterRequest
|
||||
*/
|
||||
'handler': string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@ -186,17 +205,17 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(createAccountRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
@ -211,7 +230,7 @@ export const AccountsApiAxiosParamCreator = function (configuration?: Configurat
|
||||
* AccountsApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const AccountsApiFp = function(configuration?: Configuration) {
|
||||
export const AccountsApiFp = function (configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = AccountsApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
@ -290,7 +309,7 @@ export const AuthApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
@ -299,10 +318,10 @@ export const AuthApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
@ -327,17 +346,17 @@ export const AuthApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(tokenRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
@ -352,7 +371,7 @@ export const AuthApiAxiosParamCreator = function (configuration?: Configuration)
|
||||
* AuthApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const AuthApiFp = function(configuration?: Configuration) {
|
||||
export const AuthApiFp = function (configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = AuthApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
@ -461,15 +480,15 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options };
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
@ -483,7 +502,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
|
||||
* DefaultApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const DefaultApiFp = function(configuration?: Configuration) {
|
||||
export const DefaultApiFp = function (configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
@ -538,6 +557,117 @@ export class DefaultApi extends BaseAPI {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NotificationApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const NotificationApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {RegisterRequest} registerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
register: async (registerRequest: RegisterRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'registerRequest' is not null or undefined
|
||||
assertParamExists('register', 'registerRequest', registerRequest)
|
||||
const localVarPath = `/notification/register`;
|
||||
// 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(registerRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NotificationApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const NotificationApiFp = function (configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = NotificationApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {RegisterRequest} registerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async register(registerRequest: RegisterRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.register(registerRequest, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* NotificationApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const NotificationApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = NotificationApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {RegisterRequest} registerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
register(registerRequest: RegisterRequest, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.register(registerRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* NotificationApi - object-oriented interface
|
||||
* @export
|
||||
* @class NotificationApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class NotificationApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @summary
|
||||
* @param {RegisterRequest} registerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof NotificationApi
|
||||
*/
|
||||
public register(registerRequest: RegisterRequest, options?: AxiosRequestConfig) {
|
||||
return NotificationApiFp(this.configuration).register(registerRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* UsersApi - axios parameter creator
|
||||
* @export
|
||||
@ -562,17 +692,17 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options };
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(confirmRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
@ -587,7 +717,7 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||
* UsersApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const UsersApiFp = function(configuration?: Configuration) {
|
||||
export const UsersApiFp = function (configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = UsersApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
|
||||
@ -2,12 +2,14 @@ import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
|
||||
import login from "features/login/loginSlice";
|
||||
import auth from "features/auth/authSlice";
|
||||
import signup from "features/signup/signupSlice";
|
||||
import verify from "features/verify/verifySlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
login,
|
||||
auth,
|
||||
signup,
|
||||
verify,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
12
dictation_client/src/assets/images/report.svg
Normal file
12
dictation_client/src/assets/images/report.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#A5A5A5;}
|
||||
</style>
|
||||
<path class="st0" d="M24,36.1c0.5,0,1-0.2,1.5-0.6s0.6-0.9,0.6-1.5c0-0.6-0.2-1-0.6-1.5s-0.9-0.6-1.5-0.6s-1,0.2-1.5,0.6
|
||||
c-0.5,0.4-0.6,0.9-0.6,1.5c0,0.6,0.2,1,0.6,1.5C23,35.9,23.5,36.1,24,36.1z M22.2,27.6h3.5V12.1h-3.5V27.6z M15.1,45.6L2.8,33.2
|
||||
V15.5L15.1,3.1h17.7l12.4,12.4v17.7L32.8,45.6H15.1z M16.6,42.1h14.8l10.3-10.4V17L31.4,6.6H16.6L6.2,17v14.8L16.6,42.1L16.6,42.1z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 789 B |
25
dictation_client/src/common/errors/code.ts
Normal file
25
dictation_client/src/common/errors/code.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
エラーコード作成方針
|
||||
E+6桁(数字)で構成する。
|
||||
- 1~2桁目の値は種類(業務エラー、システムエラー...)
|
||||
- 3~4桁目の値は原因箇所(トークン、DB、...)
|
||||
- 5~6桁目の値は任意の重複しない値
|
||||
ex)
|
||||
E00XXXX : システムエラー(通信エラーやDB接続失敗など)
|
||||
E01XXXX : 業務エラー
|
||||
EXX00XX : 内部エラー(内部プログラムのエラー)
|
||||
EXX01XX : トークンエラー(トークン認証関連)
|
||||
EXX02XX : DBエラー(DB関連)
|
||||
EXX03XX : ADB2Cエラー(DB関連)
|
||||
*/
|
||||
export const errorCodes = [
|
||||
"E009999", // 汎用エラー
|
||||
"E000101", // トークン形式不正エラー
|
||||
"E000102", // トークン有効期限切れエラー
|
||||
"E000103", // トークン非アクティブエラー
|
||||
"E000104", // トークン署名エラー
|
||||
"E000105", // トークン発行元エラー
|
||||
"E000106", // トークンアルゴリズムエラー
|
||||
"E010201", // 未認証ユーザエラー
|
||||
"E010301", // メールアドレス登録済みエラー
|
||||
] as const;
|
||||
3
dictation_client/src/common/errors/index.ts
Normal file
3
dictation_client/src/common/errors/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./code";
|
||||
export * from "./types";
|
||||
export * from "./utils";
|
||||
9
dictation_client/src/common/errors/types.ts
Normal file
9
dictation_client/src/common/errors/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { errorCodes } from "./code";
|
||||
|
||||
export type ErrorObject = {
|
||||
message: string;
|
||||
code: ErrorCodeType;
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
export type ErrorCodeType = typeof errorCodes[number];
|
||||
83
dictation_client/src/common/errors/utils.ts
Normal file
83
dictation_client/src/common/errors/utils.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { isError } from "lodash";
|
||||
import { ErrorResponse } from "../../api";
|
||||
import { errorCodes } from "./code";
|
||||
import { ErrorCodeType, ErrorObject } from "./types";
|
||||
|
||||
export const createErrorObject = (error: unknown): ErrorObject => {
|
||||
// 最低限通常のエラーかを判定
|
||||
// Error以外のものがthrowされた場合
|
||||
// 基本的にないはずだがプログラム上あるので拾う
|
||||
if (!isError(error)) {
|
||||
return {
|
||||
message: "not error type.",
|
||||
code: "E009999",
|
||||
};
|
||||
}
|
||||
|
||||
// Axiosエラー 通信してのエラーであるかを判定
|
||||
if (!isAxiosError(error)) {
|
||||
return {
|
||||
message: "not axios error.",
|
||||
code: "E009999",
|
||||
};
|
||||
}
|
||||
|
||||
const errorResponse = error.response;
|
||||
if (!errorResponse) {
|
||||
return {
|
||||
message: error.message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse,
|
||||
};
|
||||
}
|
||||
|
||||
const { data } = errorResponse;
|
||||
|
||||
// 想定しているエラーレスポンスの型か判定
|
||||
if (!isErrorResponse(data)) {
|
||||
return {
|
||||
message: error.message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
}
|
||||
|
||||
const { message, code } = data;
|
||||
|
||||
// 想定しているエラーコードかを判定
|
||||
if (!isErrorCode(code)) {
|
||||
return {
|
||||
message,
|
||||
code: "E009999",
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
code,
|
||||
statusCode: errorResponse.status,
|
||||
};
|
||||
};
|
||||
|
||||
const isAxiosError = (e: unknown): e is AxiosError => {
|
||||
const error = e as AxiosError;
|
||||
return error?.isAxiosError ?? false;
|
||||
};
|
||||
|
||||
const isErrorResponse = (error: unknown): error is ErrorResponse => {
|
||||
const errorResponse = error as ErrorResponse;
|
||||
if (
|
||||
errorResponse === undefined ||
|
||||
errorResponse.message === undefined ||
|
||||
errorResponse.code === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const isErrorCode = (errorCode: string): errorCode is ErrorCodeType =>
|
||||
errorCodes.includes(errorCode as ErrorCodeType);
|
||||
4
dictation_client/src/features/verify/index.ts
Normal file
4
dictation_client/src/features/verify/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./verifySlice";
|
||||
export * from "./state";
|
||||
export * from "./operations";
|
||||
export * from "./selectors";
|
||||
38
dictation_client/src/features/verify/operations.ts
Normal file
38
dictation_client/src/features/verify/operations.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import type { RootState } from "app/store";
|
||||
import { UsersApi } from "../../api/api";
|
||||
import { Configuration } from "../../api/configuration";
|
||||
import { ErrorObject, createErrorObject } from "../../common/errors";
|
||||
|
||||
export const verifyAsync = createAsyncThunk<
|
||||
{
|
||||
/* Empty Object */
|
||||
},
|
||||
{
|
||||
jwt: string;
|
||||
},
|
||||
{
|
||||
// rejectした時の返却値の型
|
||||
rejectValue: {
|
||||
error: ErrorObject;
|
||||
};
|
||||
}
|
||||
>("verify/verifyAsync", async (args, thunkApi) => {
|
||||
const { jwt } = args;
|
||||
// apiのConfigurationを取得する
|
||||
const { getState } = thunkApi;
|
||||
const state = getState() as RootState;
|
||||
const { configuration } = state.auth;
|
||||
const config = new Configuration(configuration);
|
||||
const usersApi = new UsersApi(config);
|
||||
|
||||
try {
|
||||
await usersApi.confirmUser({ token: jwt });
|
||||
return {};
|
||||
} catch (e) {
|
||||
// e ⇒ errorObjectに変換
|
||||
const error = createErrorObject(e);
|
||||
|
||||
return thunkApi.rejectWithValue({ error });
|
||||
}
|
||||
});
|
||||
6
dictation_client/src/features/verify/selectors.ts
Normal file
6
dictation_client/src/features/verify/selectors.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { RootState } from "app/store";
|
||||
|
||||
export const VerifyStateSelector = (
|
||||
state: RootState
|
||||
): "duringVerify" | "success" | "alreadySuccess" | "failed" =>
|
||||
state.verify.apps.VerifyState;
|
||||
7
dictation_client/src/features/verify/state.ts
Normal file
7
dictation_client/src/features/verify/state.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface VerifyState {
|
||||
apps: Apps;
|
||||
}
|
||||
|
||||
export interface Apps {
|
||||
VerifyState: "duringVerify" | "success" | "alreadySuccess" | "failed";
|
||||
}
|
||||
35
dictation_client/src/features/verify/verifySlice.ts
Normal file
35
dictation_client/src/features/verify/verifySlice.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { VerifyState } from "./state";
|
||||
import { verifyAsync } from "./operations";
|
||||
|
||||
const initialState: VerifyState = {
|
||||
apps: {
|
||||
VerifyState: "duringVerify",
|
||||
},
|
||||
};
|
||||
|
||||
export const verifySlice = createSlice({
|
||||
name: "verify",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(verifyAsync.pending, (state) => {
|
||||
state.apps.VerifyState = "duringVerify";
|
||||
});
|
||||
builder.addCase(verifyAsync.fulfilled, (state) => {
|
||||
state.apps.VerifyState = "success";
|
||||
});
|
||||
builder.addCase(verifyAsync.rejected, (state, action) => {
|
||||
const { payload } = action;
|
||||
|
||||
// メール認証済みかをエラーコードから判定
|
||||
if (payload?.error.code === "E010301") {
|
||||
state.apps.VerifyState = "alreadySuccess";
|
||||
} else {
|
||||
state.apps.VerifyState = "failed";
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default verifySlice.reducer;
|
||||
@ -246,7 +246,13 @@ const SignupInput: React.FC = (): JSX.Element => {
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<span className={styles.formComment}>
|
||||
{/** XXX: 改行に対応するためにここでstyleを当てている。
|
||||
* デザインを修正していただいたらもとに戻す
|
||||
*/}
|
||||
<span
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
className={styles.formComment}
|
||||
>
|
||||
{t(getTranslationID("signupPage.text.passwordTerms"))}
|
||||
</span>
|
||||
</dd>
|
||||
|
||||
51
dictation_client/src/pages/VerifyAlreadyExistPage/index.tsx
Normal file
51
dictation_client/src/pages/VerifyAlreadyExistPage/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import check_circle from "../../assets/images/check_circle.svg";
|
||||
|
||||
const VerifyPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.mainSmall}>
|
||||
<div>
|
||||
<h1 className={styles.marginBtm1}>
|
||||
{t(getTranslationID("signupVerifyPage.label.alreadySuccess"))}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<section className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<img
|
||||
src={check_circle}
|
||||
alt="chedk"
|
||||
className={styles.formDone}
|
||||
/>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
{t(getTranslationID("signupVerifyPage.text.alreadySuccess"))}
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<a href="./" className={styles.linkTx}>
|
||||
{t(getTranslationID("signupVerifyPage.label.returnToSignIn"))}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyPage;
|
||||
53
dictation_client/src/pages/VerifyFailedPage/index.tsx
Normal file
53
dictation_client/src/pages/VerifyFailedPage/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useEffect } from "react";
|
||||
import Header from "components/header";
|
||||
import Footer from "components/footer";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import report from "../../assets/images/report.svg";
|
||||
|
||||
const VerifyFailedPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// 初期表示時の処理を記述
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.mainSmall}>
|
||||
<div>
|
||||
<h1 className={styles.marginBtm1}>
|
||||
{t(getTranslationID("signupVerifyPage.label.faild"))}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<section className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<img src={report} alt="chedk" className={styles.formDone} />
|
||||
</dd>
|
||||
{/** XXX: 改行に対応するためにここでstyleを当てている。
|
||||
* デザインを修正していただいたらもとに戻す
|
||||
*/}
|
||||
<dd
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
className={`${styles.full} ${styles.alignCenter}`}
|
||||
>
|
||||
{t(getTranslationID("signupVerifyPage.text.faild"))}
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyFailedPage;
|
||||
53
dictation_client/src/pages/VerifyPage/index.tsx
Normal file
53
dictation_client/src/pages/VerifyPage/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { AppDispatch } from "app/store";
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import React, { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { verifyAsync } from "features/verify";
|
||||
import { VerifyStateSelector } from "features/verify/selectors";
|
||||
|
||||
const VerifyPage: React.FC = (): JSX.Element => {
|
||||
const dispatch: AppDispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { search } = useLocation();
|
||||
const query = new URLSearchParams(search);
|
||||
const jwt = query.get("verify") ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(verifyAsync({ jwt }));
|
||||
}, [dispatch, jwt]);
|
||||
|
||||
const verifystate = useSelector(VerifyStateSelector);
|
||||
|
||||
useEffect(() => {
|
||||
switch (verifystate) {
|
||||
case "duringVerify":
|
||||
// 認証中は処理なし
|
||||
break;
|
||||
case "success":
|
||||
navigate("/mail-confirm/success");
|
||||
break;
|
||||
case "alreadySuccess":
|
||||
navigate("/mail-confirm/alreadyExist");
|
||||
break;
|
||||
case "failed":
|
||||
navigate("/mail-confirm/failed");
|
||||
break;
|
||||
default:
|
||||
// verifystateが列挙型のため到達しない
|
||||
break;
|
||||
}
|
||||
}, [verifystate, navigate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<h3>loading ...</h3>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyPage;
|
||||
51
dictation_client/src/pages/VerifySuccessPage/index.tsx
Normal file
51
dictation_client/src/pages/VerifySuccessPage/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import Footer from "components/footer";
|
||||
import Header from "components/header";
|
||||
import styles from "styles/app.module.scss";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getTranslationID } from "translation";
|
||||
import check_circle from "../../assets/images/check_circle.svg";
|
||||
|
||||
const VerifySuccessPage: React.FC = (): JSX.Element => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<Header />
|
||||
|
||||
<main className={styles.main}>
|
||||
<div className={styles.mainSmall}>
|
||||
<div>
|
||||
<h1 className={styles.marginBtm1}>
|
||||
{t(getTranslationID("signupVerifyPage.label.verifySuccess"))}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<section className={styles.form}>
|
||||
<dl className={`${styles.formList} ${styles.hasbg}`}>
|
||||
<dt className={styles.formTitle} />
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<img
|
||||
src={check_circle}
|
||||
alt="chedk"
|
||||
className={styles.formDone}
|
||||
/>
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
{t(getTranslationID("signupVerifyPage.text.verifySuccess"))}
|
||||
</dd>
|
||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||
<a href="/" className={styles.linkTx}>
|
||||
{t(getTranslationID("signupVerifyPage.label.returnToSignIn"))}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifySuccessPage;
|
||||
@ -32,7 +32,7 @@
|
||||
"countryExplanation": "(de) Please select your country or the nearest country.",
|
||||
"dealerExplanation": "(de)Please select the dealer to purchase a license.",
|
||||
"adminInfoTitle": "(de)Register primary administrator's information",
|
||||
"passwordTerms": "(de) Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
"passwordTerms": "(de)Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
},
|
||||
"label": {
|
||||
"company": "(de)Company Name",
|
||||
@ -52,10 +52,8 @@
|
||||
"title": "(de)Confirmation",
|
||||
"pageExplanation": "(de)Explanation ......",
|
||||
"accountInfoTitle": "(de)Your account information",
|
||||
"adminInfoTitle": "(de)Primary administrator's information"
|
||||
},
|
||||
"message": {
|
||||
"emailConflictError": "(仮)"
|
||||
"adminInfoTitle": "(de)Primary administrator's information",
|
||||
"createdInfo": "(de)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
},
|
||||
"label": {
|
||||
"company": "(de)Company Name",
|
||||
@ -64,15 +62,25 @@
|
||||
"adminName": "(de)Admin Name",
|
||||
"email": "(de)Email",
|
||||
"password": "(de)Password",
|
||||
"signupButton": "(de)Sign up"
|
||||
"title": "(de)Account created"
|
||||
}
|
||||
},
|
||||
"signupCompletePage": {
|
||||
"label": {
|
||||
"signupButton": "(de)Sign up"
|
||||
}
|
||||
},
|
||||
"signupVerifyPage": {
|
||||
"text": {
|
||||
"createdInfo": "(de)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
"verifySuccess": "(de)You have successfully verified the account.",
|
||||
"faild": "(de)The verification url does not match. Please try again,\nor click on the link below to receive a new verification mail.",
|
||||
"alreadySuccess": "(de)Your account has already been verified."
|
||||
},
|
||||
"label": {
|
||||
"title": "(de)Account created"
|
||||
"verifySuccess": "(de)Verified!",
|
||||
"faild": "(de)Verification failed",
|
||||
"alreadySuccess": "(de)Already Verified!",
|
||||
"returnToSignIn": "(de)Return to Sign in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,10 +32,7 @@
|
||||
"countryExplanation": " Please select your country or the nearest country.",
|
||||
"dealerExplanation": "Please select the dealer to purchase a license.",
|
||||
"adminInfoTitle": "Register primary administrator's information",
|
||||
"passwordTerms": " Please set a password or issue an initial password. \nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
},
|
||||
"message": {
|
||||
"emailConflictError": "(仮)"
|
||||
"passwordTerms": "Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
},
|
||||
"label": {
|
||||
"company": "Company Name",
|
||||
@ -47,8 +44,7 @@
|
||||
"termsLink": "Click here to read the terms of use",
|
||||
"termsLinkFor": "for ODDS.",
|
||||
"termsCheckBox": "Yes, I agree to the terms of use.",
|
||||
"createAccountButton": "Create account",
|
||||
"signupButton": "Sign up"
|
||||
"createAccountButton": "Create account"
|
||||
}
|
||||
},
|
||||
"signupConfirmPage": {
|
||||
@ -56,7 +52,8 @@
|
||||
"title": "Confirmation",
|
||||
"pageExplanation": "Explanation ......",
|
||||
"accountInfoTitle": "Your account information",
|
||||
"adminInfoTitle": "Primary administrator's information"
|
||||
"adminInfoTitle": "Primary administrator's information",
|
||||
"createdInfo": "Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
},
|
||||
"label": {
|
||||
"company": "Company Name",
|
||||
@ -65,15 +62,25 @@
|
||||
"adminName": "Admin Name",
|
||||
"email": "Email",
|
||||
"password": "Password",
|
||||
"signupButton": "Sign up"
|
||||
"title": "Account created"
|
||||
}
|
||||
},
|
||||
"signupCompletePage": {
|
||||
"label": {
|
||||
"signupButton": "Sign up"
|
||||
}
|
||||
},
|
||||
"signupVerifyPage": {
|
||||
"text": {
|
||||
"createdInfo": "Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
"verifySuccess": "You have successfully verified the account.",
|
||||
"faild": "The verification url does not match. Please try again,\nor click on the link below to receive a new verification mail.",
|
||||
"alreadySuccess": "Your account has already been verified."
|
||||
},
|
||||
"label": {
|
||||
"title": "Account created"
|
||||
"verifySuccess": "Verified!",
|
||||
"faild": "Verification failed",
|
||||
"alreadySuccess": "Already Verified!",
|
||||
"returnToSignIn": "Return to Sign in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"countryExplanation": "(es) Please select your country or the nearest country.",
|
||||
"dealerExplanation": "(es)Please select the dealer to purchase a license.",
|
||||
"adminInfoTitle": "(es)Register primary administrator's information",
|
||||
"passwordTerms": "(es) Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
"passwordTerms": "(es)Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
},
|
||||
"label": {
|
||||
"company": "(es)Company Name",
|
||||
@ -52,10 +52,8 @@
|
||||
"title": "(es)Confirmation",
|
||||
"pageExplanation": "(es)Explanation ......",
|
||||
"accountInfoTitle": "(es)Your account information",
|
||||
"adminInfoTitle": "(es)Primary administrator's information"
|
||||
},
|
||||
"message": {
|
||||
"emailConflictError": "(仮)"
|
||||
"adminInfoTitle": "(es)Primary administrator's information",
|
||||
"createdInfo": "(es)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
},
|
||||
"label": {
|
||||
"company": "(es)Company Name",
|
||||
@ -64,15 +62,25 @@
|
||||
"adminName": "(es)Admin Name",
|
||||
"email": "(es)Email",
|
||||
"password": "(es)Password",
|
||||
"signupButton": "(es)Sign up"
|
||||
"title": "(es)Account created"
|
||||
}
|
||||
},
|
||||
"signupCompletePage": {
|
||||
"label": {
|
||||
"signupButton": "(es)Sign up"
|
||||
}
|
||||
},
|
||||
"signupVerifyPage": {
|
||||
"text": {
|
||||
"createdInfo": "(es)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
"verifySuccess": "(es)You have successfully verified the account.",
|
||||
"faild": "(es)The verification url does not match. Please try again,\nor click on the link below to receive a new verification mail.",
|
||||
"alreadySuccess": "(es)Your account has already been verified."
|
||||
},
|
||||
"label": {
|
||||
"title": "(es)Account created"
|
||||
"verifySuccess": "(es)Verified!",
|
||||
"faild": "(es)Verification failed",
|
||||
"alreadySuccess": "(es)Already Verified!",
|
||||
"returnToSignIn": "(es)Return to Sign in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"countryExplanation": "(fr) Please select your country or the nearest country.",
|
||||
"dealerExplanation": "(fr)Please select the dealer to purchase a license.",
|
||||
"adminInfoTitle": "(fr)Register primary administrator's information",
|
||||
"passwordTerms": "(fr) Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
"passwordTerms": "(fr)Please set a password or issue an initial password.\nThe password must be more than 8 or less than 25 letters,numbers and symbols."
|
||||
},
|
||||
"label": {
|
||||
"company": "(fr)Company Name",
|
||||
@ -52,10 +52,8 @@
|
||||
"title": "(fr)Confirmation",
|
||||
"pageExplanation": "(fr)Explanation ......",
|
||||
"accountInfoTitle": "(fr)Your account information",
|
||||
"adminInfoTitle": "(fr)Primary administrator's information"
|
||||
},
|
||||
"message": {
|
||||
"emailConflictError": "(仮)"
|
||||
"adminInfoTitle": "(fr)Primary administrator's information",
|
||||
"createdInfo": "(fr)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
},
|
||||
"label": {
|
||||
"company": "(fr)Company Name",
|
||||
@ -64,15 +62,25 @@
|
||||
"adminName": "(fr)Admin Name",
|
||||
"email": "(fr)Email",
|
||||
"password": "(fr)Password",
|
||||
"signupButton": "(fr)Sign up"
|
||||
"title": "(fr)Account created"
|
||||
}
|
||||
},
|
||||
"signupCompletePage": {
|
||||
"label": {
|
||||
"signupButton": "(fr)Sign up"
|
||||
}
|
||||
},
|
||||
"signupVerifyPage": {
|
||||
"text": {
|
||||
"createdInfo": "(fr)Your account has been created and a verification email has been set to your registered email address. Please click on the verification link included in the email to activate your account."
|
||||
"verifySuccess": "(fr)You have successfully verified the account.",
|
||||
"faild": "(fr)The verification url does not match. Please try again,\nor click on the link below to receive a new verification mail.",
|
||||
"alreadySuccess": "(fr)Your account has already been verified."
|
||||
},
|
||||
"label": {
|
||||
"title": "(fr)Account created"
|
||||
"verifySuccess": "(fr)Verified!",
|
||||
"faild": "(fr)Verification failed",
|
||||
"alreadySuccess": "(fr)Already Verified!",
|
||||
"returnToSignIn": "(fr)Return to Sign in"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export class SendGridService {
|
||||
privateKey,
|
||||
);
|
||||
const domains = 'http://127.0.0.1/';
|
||||
const path = '';
|
||||
const path = 'mail-confirm/';
|
||||
|
||||
return {
|
||||
subject: 'Verify your new account',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user