Merged PR 81: 画面実装(ユーザー認証画面/認証完了画面)

## 概要
[Task1597: 画面実装(ユーザー認証画面/認証完了画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1597)

- アカウントへのユーザ追加でメール認証URLから認証を実行した際の画面を実装しました。
  - 完了後の画面はアカウント登録のものをそのまま流用しています。

## レビューポイント
- 認証APIからのレスポンスはアカウント登録と同様のものを想定して、完了画面をそのまま流用していますが問題ないでしょうか。
- 画面のパスを`/mail-confirm/user`としましたが問題ないでしょうか?

## UIの変更
- アカウント登録の認証完了画面と同様

## 動作確認状況
- ローカルで確認
  - 認証APIでアカウント登録と同様のレスポンスを想定
This commit is contained in:
makabe.t 2023-04-19 00:52:47 +00:00
parent 0a970e814f
commit a2a0778dfb
5 changed files with 646 additions and 1 deletions

View File

@ -7,6 +7,7 @@ import { NotFoundPage } from "pages/ErrorPage/notFound";
import { RouteAuthGuard } from "components/auth/routeAuthGuard"; import { RouteAuthGuard } from "components/auth/routeAuthGuard";
import SignupPage from "pages/SignupPage"; import SignupPage from "pages/SignupPage";
import VerifyPage from "pages/VerifyPage"; import VerifyPage from "pages/VerifyPage";
import UserVerifyPage from "pages/UserVerifyPage";
import VerifySuccessPage from "pages/VerifySuccessPage"; import VerifySuccessPage from "pages/VerifySuccessPage";
import VerifyFailedPage from "pages/VerifyFailedPage"; import VerifyFailedPage from "pages/VerifyFailedPage";
import VerifyAlreadyExistPage from "pages/VerifyAlreadyExistPage"; import VerifyAlreadyExistPage from "pages/VerifyAlreadyExistPage";
@ -23,6 +24,7 @@ const AppRouter: React.FC = () => (
/> />
<Route path="/signup/complete" element={<SignupCompletePage />} /> <Route path="/signup/complete" element={<SignupCompletePage />} />
<Route path="/mail-confirm/" element={<VerifyPage />} /> <Route path="/mail-confirm/" element={<VerifyPage />} />
<Route path="/mail-confirm/user" element={<UserVerifyPage />} />
<Route path="/mail-confirm/success" element={<VerifySuccessPage />} /> <Route path="/mail-confirm/success" element={<VerifySuccessPage />} />
<Route path="/mail-confirm/failed" element={<VerifyFailedPage />} /> <Route path="/mail-confirm/failed" element={<VerifyFailedPage />} />
<Route <Route

View File

@ -36,6 +36,32 @@ export interface AccessTokenResponse {
*/ */
'accessToken': string; 'accessToken': string;
} }
/**
*
* @export
* @interface AudioDownloadLocationResponse
*/
export interface AudioDownloadLocationResponse {
/**
*
* @type {string}
* @memberof AudioDownloadLocationResponse
*/
'url': string;
}
/**
*
* @export
* @interface AudioUploadLocationResponse
*/
export interface AudioUploadLocationResponse {
/**
*
* @type {string}
* @memberof AudioUploadLocationResponse
*/
'url': string;
}
/** /**
* *
* @export * @export
@ -123,6 +149,19 @@ export interface ErrorResponse {
*/ */
'code': string; 'code': string;
} }
/**
*
* @export
* @interface GetUsersResponse
*/
export interface GetUsersResponse {
/**
*
* @type {Array<User>}
* @memberof GetUsersResponse
*/
'users': Array<User>;
}
/** /**
* *
* @export * @export
@ -142,6 +181,61 @@ export interface RegisterRequest {
*/ */
'handler': string; 'handler': string;
} }
/**
*
* @export
* @interface SignupRequest
*/
export interface SignupRequest {
/**
*
* @type {string}
* @memberof SignupRequest
*/
'name': string;
/**
* none/author/typist
* @type {string}
* @memberof SignupRequest
*/
'role': string;
/**
*
* @type {string}
* @memberof SignupRequest
*/
'authorId'?: string;
/**
*
* @type {number}
* @memberof SignupRequest
*/
'typistGroupId'?: number;
/**
*
* @type {string}
* @memberof SignupRequest
*/
'email': string;
/**
*
* @type {boolean}
* @memberof SignupRequest
*/
'autoRenew': boolean;
/**
*
* @type {boolean}
* @memberof SignupRequest
*/
'licenseAlert': boolean;
/**
*
* @type {boolean}
* @memberof SignupRequest
*/
'notification': boolean;
}
/** /**
* *
* @export * @export
@ -180,6 +274,67 @@ export interface TokenResponse {
*/ */
'accessToken': string; 'accessToken': string;
} }
/**
*
* @export
* @interface User
*/
export interface User {
/**
*
* @type {string}
* @memberof User
*/
'name': string;
/**
* none/author/typist
* @type {string}
* @memberof User
*/
'role': string;
/**
*
* @type {string}
* @memberof User
*/
'authorId': string | null;
/**
*
* @type {string}
* @memberof User
*/
'typistGroupName': string | null;
/**
*
* @type {string}
* @memberof User
*/
'email': string;
/**
*
* @type {boolean}
* @memberof User
*/
'emailVerified': boolean;
/**
*
* @type {boolean}
* @memberof User
*/
'autoRenew': boolean;
/**
*
* @type {boolean}
* @memberof User
*/
'licenseAlert': boolean;
/**
*
* @type {boolean}
* @memberof User
*/
'notification': boolean;
}
/** /**
* AccountsApi - axios parameter creator * AccountsApi - axios parameter creator
@ -557,6 +712,182 @@ export class DefaultApi extends BaseAPI {
} }
/**
* FilesApi - axios parameter creator
* @export
*/
export const FilesApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @summary
* @param {string} id DBから取得するためのID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadLocation: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'id' is not null or undefined
assertParamExists('downloadLocation', 'id', id)
const localVarPath = `/files/audio/download-location`;
// 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: 'GET', ...baseOptions, ...options };
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (id !== undefined) {
localVarQueryParameter['id'] = id;
}
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadLocation: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/files/audio/upload-location`;
// 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: 'GET', ...baseOptions, ...options };
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* FilesApi - functional programming interface
* @export
*/
export const FilesApiFp = function (configuration?: Configuration) {
const localVarAxiosParamCreator = FilesApiAxiosParamCreator(configuration)
return {
/**
*
* @summary
* @param {string} id DBから取得するためのID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async downloadLocation(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioDownloadLocationResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadLocation(id, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async uploadLocation(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AudioUploadLocationResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.uploadLocation(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
}
};
/**
* FilesApi - factory interface
* @export
*/
export const FilesApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = FilesApiFp(configuration)
return {
/**
*
* @summary
* @param {string} id DBから取得するためのID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
downloadLocation(id: string, options?: any): AxiosPromise<AudioDownloadLocationResponse> {
return localVarFp.downloadLocation(id, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
uploadLocation(options?: any): AxiosPromise<AudioUploadLocationResponse> {
return localVarFp.uploadLocation(options).then((request) => request(axios, basePath));
},
};
};
/**
* FilesApi - object-oriented interface
* @export
* @class FilesApi
* @extends {BaseAPI}
*/
export class FilesApi extends BaseAPI {
/**
*
* @summary
* @param {string} id DBから取得するためのID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FilesApi
*/
public downloadLocation(id: string, options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).downloadLocation(id, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof FilesApi
*/
public uploadLocation(options?: AxiosRequestConfig) {
return FilesApiFp(this.configuration).uploadLocation(options).then((request) => request(this.axios, this.basePath));
}
}
/** /**
* NotificationApi - axios parameter creator * NotificationApi - axios parameter creator
* @export * @export
@ -705,6 +1036,116 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers }; localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
localVarRequestOptions.data = serializeDataIfNeeded(confirmRequest, localVarRequestOptions, configuration) localVarRequestOptions.data = serializeDataIfNeeded(confirmRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {ConfirmRequest} confirmRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
confirmUserAndInitPassword: async (confirmRequest: ConfirmRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'confirmRequest' is not null or undefined
assertParamExists('confirmUserAndInitPassword', 'confirmRequest', confirmRequest)
const localVarPath = `/users/confirm/initpassword`;
// 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;
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
localVarRequestOptions.data = serializeDataIfNeeded(confirmRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUsers: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/users`;
// 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: 'GET', ...baseOptions, ...options };
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = { ...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers };
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @summary
* @param {SignupRequest} signupRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
signup: async (signupRequest: SignupRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'signupRequest' is not null or undefined
assertParamExists('signup', 'signupRequest', signupRequest)
const localVarPath = `/users/signup`;
// 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(signupRequest, localVarRequestOptions, configuration)
return { return {
url: toPathString(localVarUrlObj), url: toPathString(localVarUrlObj),
options: localVarRequestOptions, options: localVarRequestOptions,
@ -731,6 +1172,38 @@ export const UsersApiFp = function (configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.confirmUser(confirmRequest, options); const localVarAxiosArgs = await localVarAxiosParamCreator.confirmUser(confirmRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
}, },
/**
*
* @summary
* @param {ConfirmRequest} confirmRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async confirmUserAndInitPassword(confirmRequest: ConfirmRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.confirmUserAndInitPassword(confirmRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getUsers(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<GetUsersResponse>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getUsers(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @summary
* @param {SignupRequest} signupRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async signup(signupRequest: SignupRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.signup(signupRequest, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
} }
}; };
@ -751,6 +1224,35 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
confirmUser(confirmRequest: ConfirmRequest, options?: any): AxiosPromise<object> { confirmUser(confirmRequest: ConfirmRequest, options?: any): AxiosPromise<object> {
return localVarFp.confirmUser(confirmRequest, options).then((request) => request(axios, basePath)); return localVarFp.confirmUser(confirmRequest, options).then((request) => request(axios, basePath));
}, },
/**
*
* @summary
* @param {ConfirmRequest} confirmRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
confirmUserAndInitPassword(confirmRequest: ConfirmRequest, options?: any): AxiosPromise<object> {
return localVarFp.confirmUserAndInitPassword(confirmRequest, options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getUsers(options?: any): AxiosPromise<GetUsersResponse> {
return localVarFp.getUsers(options).then((request) => request(axios, basePath));
},
/**
*
* @summary
* @param {SignupRequest} signupRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
signup(signupRequest: SignupRequest, options?: any): AxiosPromise<object> {
return localVarFp.signup(signupRequest, options).then((request) => request(axios, basePath));
},
}; };
}; };
@ -772,6 +1274,41 @@ export class UsersApi extends BaseAPI {
public confirmUser(confirmRequest: ConfirmRequest, options?: AxiosRequestConfig) { public confirmUser(confirmRequest: ConfirmRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).confirmUser(confirmRequest, options).then((request) => request(this.axios, this.basePath)); return UsersApiFp(this.configuration).confirmUser(confirmRequest, options).then((request) => request(this.axios, this.basePath));
} }
/**
*
* @summary
* @param {ConfirmRequest} confirmRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UsersApi
*/
public confirmUserAndInitPassword(confirmRequest: ConfirmRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).confirmUserAndInitPassword(confirmRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UsersApi
*/
public getUsers(options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).getUsers(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @summary
* @param {SignupRequest} signupRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UsersApi
*/
public signup(signupRequest: SignupRequest, options?: AxiosRequestConfig) {
return UsersApiFp(this.configuration).signup(signupRequest, options).then((request) => request(this.axios, this.basePath));
}
} }

View File

@ -36,3 +36,36 @@ export const verifyAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error }); return thunkApi.rejectWithValue({ error });
} }
}); });
export const userVerifyAsync = createAsyncThunk<
{
/* Empty Object */
},
{
jwt: string;
},
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("verify/userVerifyAsync", 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.confirmUserAndInitPassword({ token: jwt });
return {};
} catch (e) {
// e ⇒ errorObjectに変換
const error = createErrorObject(e);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -1,6 +1,6 @@
import { createSlice } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit";
import { VerifyState } from "./state"; import { VerifyState } from "./state";
import { verifyAsync } from "./operations"; import { userVerifyAsync, verifyAsync } from "./operations";
const initialState: VerifyState = { const initialState: VerifyState = {
apps: { apps: {
@ -13,6 +13,7 @@ export const verifySlice = createSlice({
initialState, initialState,
reducers: {}, reducers: {},
extraReducers: (builder) => { extraReducers: (builder) => {
// アカウント登録
builder.addCase(verifyAsync.pending, (state) => { builder.addCase(verifyAsync.pending, (state) => {
state.apps.VerifyState = "duringVerify"; state.apps.VerifyState = "duringVerify";
}); });
@ -22,6 +23,23 @@ export const verifySlice = createSlice({
builder.addCase(verifyAsync.rejected, (state, action) => { builder.addCase(verifyAsync.rejected, (state, action) => {
const { payload } = action; const { payload } = action;
// メール認証済みかをエラーコードから判定
if (payload?.error.code === "E010202") {
state.apps.VerifyState = "alreadySuccess";
} else {
state.apps.VerifyState = "failed";
}
});
// ユーザ追加
builder.addCase(userVerifyAsync.pending, (state) => {
state.apps.VerifyState = "duringVerify";
});
builder.addCase(userVerifyAsync.fulfilled, (state) => {
state.apps.VerifyState = "success";
});
builder.addCase(userVerifyAsync.rejected, (state, action) => {
const { payload } = action;
// メール認証済みかをエラーコードから判定 // メール認証済みかをエラーコードから判定
if (payload?.error.code === "E010202") { if (payload?.error.code === "E010202") {
state.apps.VerifyState = "alreadySuccess"; state.apps.VerifyState = "alreadySuccess";

View File

@ -0,0 +1,55 @@
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 { userVerifyAsync, VerifyStateSelector } from "features/verify";
const UserVerifyPage: 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(() => {
if (!jwt) {
navigate("/mail-confirm/failed");
}
dispatch(userVerifyAsync({ jwt }));
}, [navigate, 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 UserVerifyPage;