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:
makabe.t 2023-04-10 09:03:21 +00:00
parent bd4aaa8ae1
commit 50f4cf5070
25 changed files with 667 additions and 57 deletions

View File

@ -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"
}
}

View File

@ -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 />} />}

View File

@ -1 +1 @@
6.4.0
6.5.0

View File

@ -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 {
/**

View File

@ -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,
},
});

View 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

View File

@ -0,0 +1,25 @@
/*
E+6
- 1~2...
- 3~4DB...
- 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;

View File

@ -0,0 +1,3 @@
export * from "./code";
export * from "./types";
export * from "./utils";

View File

@ -0,0 +1,9 @@
import { errorCodes } from "./code";
export type ErrorObject = {
message: string;
code: ErrorCodeType;
statusCode?: number;
};
export type ErrorCodeType = typeof errorCodes[number];

View 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);

View File

@ -0,0 +1,4 @@
export * from "./verifySlice";
export * from "./state";
export * from "./operations";
export * from "./selectors";

View 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 });
}
});

View File

@ -0,0 +1,6 @@
import { RootState } from "app/store";
export const VerifyStateSelector = (
state: RootState
): "duringVerify" | "success" | "alreadySuccess" | "failed" =>
state.verify.apps.VerifyState;

View File

@ -0,0 +1,7 @@
export interface VerifyState {
apps: Apps;
}
export interface Apps {
VerifyState: "duringVerify" | "success" | "alreadySuccess" | "failed";
}

View 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;

View File

@ -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>

View 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;

View 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;

View 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;

View 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;

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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',