Merge branch 'develop' into main

This commit is contained in:
maruyama.t 2023-11-01 16:47:41 +09:00
commit 5076e5143c
30 changed files with 9186 additions and 6434 deletions

View File

@ -12,6 +12,7 @@ import Snackbar from "components/snackbar";
import { selectSnackber } from "features/ui/selectors";
import { closeSnackbar } from "features/ui/uiSlice";
import { UNAUTHORIZED_TO_CONTINUE_ERROR_CODES } from "components/auth/constants";
import { clearUserInfo } from "features/login";
const App = (): JSX.Element => {
const dispatch = useDispatch();
@ -29,6 +30,7 @@ const App = (): JSX.Element => {
!UNAUTHORIZED_TO_CONTINUE_ERROR_CODES.includes(e.response.data.code)
) {
dispatch(clearToken());
dispatch(clearUserInfo());
instance.logoutRedirect({
postLogoutRedirectUri: "/?logout=true",
});

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ import account from "features/account/accountSlice";
import template from "features/workflow/template/templateSlice";
import workflow from "features/workflow/workflowSlice";
import terms from "features/terms/termsSlice";
import header from "features/login/headerSlice";
export const store = configureStore({
reducer: {
@ -42,6 +43,7 @@ export const store = configureStore({
template,
workflow,
terms,
header,
},
});

View File

@ -7,6 +7,7 @@ import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "app/store";
import { clearToken } from "features/auth";
import { useMsal } from "@azure/msal-react";
import { clearUserInfo } from "features/login";
type RouteAuthGuardProps = {
component: JSX.Element;
@ -22,6 +23,7 @@ export const RouteAuthGuard = (props: RouteAuthGuardProps) => {
useEffect(() => {
if (!isAuth || isExpired) {
dispatch(clearToken());
dispatch(clearUserInfo());
// B2Cからもログアウトする
instance.logoutRedirect({
postLogoutRedirectUri: "/?logout=true",

View File

@ -8,10 +8,8 @@ interface HeaderProps {
userName?: string;
}
// ヘッダー切り替え用のcomponent
const Header: React.FC<HeaderProps> = (props) => {
const { userName } = props;
const Header: React.FC<HeaderProps> = () => {
const location = useLocation();
const splitPaths = location.pathname.split("/");
let path = location.pathname;
@ -19,14 +17,14 @@ const Header: React.FC<HeaderProps> = (props) => {
path = `/${splitPaths[1]}`;
}
return getHeader(path, userName);
return getHeader(path);
};
export default Header;
const getHeader = (path: string, userName?: string) => {
if (isLoginPaths(path) && userName) {
return <LoginedHeader name={userName} activePath={path} />;
const getHeader = (path: string) => {
if (isLoginPaths(path)) {
return <LoginedHeader activePath={path} />;
}
return <NotLoginHeader isMobile={path === "/"} />;
};

View File

@ -1,20 +1,61 @@
import React from "react";
import React, { useCallback, useEffect } from "react";
import styles from "styles/app.module.scss";
import { useDispatch, useSelector } from "react-redux";
import { AppDispatch } from "app/store";
import { useMsal } from "@azure/msal-react";
import { clearToken } from "features/auth";
import { useTranslation } from "react-i18next";
import {
getUserInfoAsync,
selectUserName,
selectIsUserNameEmpty,
clearUserInfo,
} from "features/login";
import { getFilteredMenus } from "./utils";
import logo from "../../assets/images/OMS_logo_black.svg";
import ac from "../../assets/images/account_circle.svg";
import { LoginedPaths } from "./types";
import { HEADER_NAME } from "./constants";
import logout from "../../assets/images/logout.svg";
import { getTranslationID } from "../../translation";
interface HeaderProps {
name: string;
activePath: LoginedPaths;
}
// ログイン後のヘッダー
const LoginedHeader: React.FC<HeaderProps> = (props: HeaderProps) => {
const { name, activePath } = props;
const { activePath } = props;
const filterMenus = getFilteredMenus();
const dispatch: AppDispatch = useDispatch();
const { instance } = useMsal();
const { t } = useTranslation();
// Headerのユーザー情報を取得する
const isUserNameEmpty = useSelector(selectIsUserNameEmpty);
const userName = useSelector(selectUserName);
useEffect(() => {
if (isUserNameEmpty) {
dispatch(getUserInfoAsync());
}
}, [dispatch, isUserNameEmpty]);
// ログアウト
const onSignoutButton = useCallback(
async () => {
// ダイアログ確認
// eslint-disable-next-line no-alert
if (window.confirm(t(getTranslationID("common.message.displayDialog")))) {
instance.logoutRedirect({ postLogoutRedirectUri: "/" });
dispatch(clearToken());
dispatch(clearUserInfo());
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[dispatch]
);
return (
<header className={styles.header}>
<div className={styles.headerLogo}>
@ -38,10 +79,15 @@ const LoginedHeader: React.FC<HeaderProps> = (props: HeaderProps) => {
</li>
))}
</ul>
<p className={styles.accountInfo}>
<img src={ac} alt="" className={styles.accountIcon} />
<span>{name}</span>
<span>{userName}</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<span className={styles.accountSignout} onClick={onSignoutButton}>
<img src={logout} alt="" className={styles.accountIcon} />
{t(getTranslationID("common.label.signOutButton"))}
</span>
</p>
</div>
</header>

View File

@ -0,0 +1,28 @@
import { createSlice } from "@reduxjs/toolkit";
import { getUserInfoAsync } from "./operations";
import { HeaderState } from "./state";
const initialState: HeaderState = {
domain: {
getUserInfo: undefined,
},
};
export const headerSlice = createSlice({
name: "header",
initialState,
reducers: {
clearUserInfo: (state) => {
state.domain.getUserInfo = undefined;
},
},
extraReducers: (builder) => {
builder.addCase(getUserInfoAsync.fulfilled, (state, action) => {
state.domain.getUserInfo = action.payload;
});
},
});
export const { clearUserInfo } = headerSlice.actions;
export const headerReducer = headerSlice.reducer;
export default headerReducer;

View File

@ -2,3 +2,4 @@ export * from "./loginSlice";
export * from "./state";
export * from "./operations";
export * from "./selectors";
export * from "./headerSlice";

View File

@ -1,7 +1,7 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import type { RootState } from "app/store";
import { setToken } from "features/auth/authSlice";
import { AuthApi } from "../../api/api";
import { AuthApi, UsersApi, GetMyUserResponse } from "../../api/api";
import { Configuration } from "../../api/configuration";
import { ErrorObject, createErrorObject } from "../../common/errors";
@ -47,3 +47,34 @@ export const loginAsync = createAsyncThunk<
return thunkApi.rejectWithValue({ error });
}
});
export const getUserInfoAsync = createAsyncThunk<
// 正常時の戻り値の型
GetMyUserResponse,
// 関数の引数の型
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("header/getUserInfoAsync", async (_args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration, accessToken } = state.auth;
const config = new Configuration(configuration);
const usersApi = new UsersApi(config);
try {
const userInfo = await usersApi.getMyUser({
headers: { authorization: `Bearer ${accessToken}` },
});
return userInfo.data;
} catch (e) {
// e ⇒ errorObjectに変換"
const error = createErrorObject(e);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -8,3 +8,9 @@ export const selectLoginApiCallStatus = (
export const selectLocalStorageKeyforIdToken = (
state: RootState
): string | null => state.login.apps.localStorageKeyforIdToken;
export const selectUserName = (state: RootState) =>
state.header.domain.getUserInfo?.userName ?? "";
export const selectIsUserNameEmpty = (state: RootState) =>
state.header.domain.getUserInfo === undefined;

View File

@ -1,3 +1,5 @@
import { GetMyUserResponse } from "api";
export interface LoginState {
apps: Apps;
}
@ -6,3 +8,11 @@ export interface Apps {
LoginApiCallStatus: "fulfilled" | "rejected" | "none" | "pending";
localStorageKeyforIdToken: string | null;
}
export interface HeaderState {
domain: Domain;
}
export interface Domain {
getUserInfo: GetMyUserResponse | undefined;
}

View File

@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
import { clearToken } from "features/auth";
import { AppDispatch } from "app/store";
import { useDispatch } from "react-redux";
import { clearUserInfo } from "features/login";
export const AccountDeleteSuccess: React.FC = (): JSX.Element => {
const { t } = useTranslation();
@ -16,6 +17,7 @@ export const AccountDeleteSuccess: React.FC = (): JSX.Element => {
// アカウントの削除完了時に遷移するページなので、遷移と同時にログアウト状態とする
useEffect(() => {
dispatch(clearToken());
dispatch(clearUserInfo());
}, [dispatch]);
return (

View File

@ -22,8 +22,8 @@ import { useTranslation } from "react-i18next";
import { getTranslationID } from "translation";
import { TIERS } from "components/auth/constants";
import { isApproveTier } from "features/auth/utils";
import progress_activit from "../../assets/images/progress_activit.svg";
import { DeleteAccountPopup } from "./deleteAccountPopup";
import progress_activit from "../../assets/images/progress_activit.svg";
const AccountPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
@ -90,7 +90,7 @@ const AccountPage: React.FC = (): JSX.Element => {
/>
)}
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>

View File

@ -472,7 +472,7 @@ const DictationPage: React.FC = (): JSX.Element => {
onClose={onClosePopup}
/>
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<Header />
<main className={styles.main}>
<div className="">
<div className={styles.pageHeader}>

View File

@ -6,6 +6,7 @@ import { AppDispatch } from "app/store";
import { clearToken } from "features/auth";
import { useMsal } from "@azure/msal-react";
import { Token } from "common/token";
import { clearUserInfo } from "features/login";
import { LicenseSummary } from "./licenseSummary";
import PartnerLicense from "./partnerLicense";
@ -16,6 +17,7 @@ const LicensePage: React.FC = (): JSX.Element => {
const redirectToTopPage = useCallback(() => {
dispatch(clearToken());
dispatch(clearUserInfo());
instance.logoutRedirect({
postLogoutRedirectUri: "/",
});

View File

@ -27,11 +27,11 @@ import {
cancelIssueAsync,
} from "features/license/licenseOrderHistory";
import { selectSelectedRow } from "features/license/partnerLicense";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
import undo from "../../assets/images/undo.svg";
import history from "../../assets/images/history.svg";
import progress_activit from "../../assets/images/progress_activit.svg";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
interface LicenseOrderHistoryProps {
onReturn: () => void;
@ -163,11 +163,10 @@ export const LicenseOrderHistory: React.FC<LicenseOrderHistoryProps> = (
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && <DelegationBar delegatedCompanyName="XXXXXX" />
}
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>

View File

@ -12,6 +12,8 @@ import {
selecLicenseSummaryInfo,
} from "features/license/licenseSummary";
import { selectSelectedRow } from "features/license/partnerLicense";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
import postAdd from "../../assets/images/post_add.svg";
import history from "../../assets/images/history.svg";
import key from "../../assets/images/key.svg";
@ -22,8 +24,6 @@ import { LicenseOrderPopup } from "./licenseOrderPopup";
import { CardLicenseActivatePopup } from "./cardLicenseActivatePopup";
// eslint-disable-next-line import/no-named-as-default
import LicenseOrderHistory from "./licenseOrderHistory";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
interface LicenseSummaryProps {
onReturn?: () => void;
@ -107,12 +107,12 @@ export const LicenseSummary: React.FC<LicenseSummaryProps> = (
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>

View File

@ -250,7 +250,7 @@ const PartnerLicense: React.FC = (): JSX.Element => {
)}
{!islicenseOrderHistoryOpen && !isViewDetailsOpen && (
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div className="">

View File

@ -94,7 +94,7 @@ const PartnerPage: React.FC = (): JSX.Element => {
}}
/>
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div className={styles.pageHeader}>

View File

@ -4,6 +4,8 @@ import { UpdateTokenTimer } from "components/auth/updateTokenTimer";
import Footer from "components/footer";
import Header from "components/header";
import { clearToken } from "features/auth";
import { clearUserInfo } from "features/login";
import React from "react";
import { useDispatch } from "react-redux";
import styles from "styles/app.module.scss";
@ -11,9 +13,10 @@ import styles from "styles/app.module.scss";
const SamplePage: React.FC = (): JSX.Element => {
const { instance } = useMsal();
const dispatch: AppDispatch = useDispatch();
return (
<div className={styles.wrap}>
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<div>
<button
@ -22,6 +25,7 @@ const SamplePage: React.FC = (): JSX.Element => {
onClick={() => {
instance.logoutRedirect({ postLogoutRedirectUri: "/" });
dispatch(clearToken());
dispatch(clearUserInfo());
}}
>
sign out

View File

@ -14,9 +14,9 @@ import {
listTemplateAsync,
selectIsLoading,
} from "features/workflow/template";
import { AddTemplateFilePopup } from "./addTemplateFilePopup";
import { DelegationBar } from "components/delegate";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { AddTemplateFilePopup } from "./addTemplateFilePopup";
export const TemplateFilePage: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
@ -49,12 +49,12 @@ export const TemplateFilePage: React.FC = () => {
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>

View File

@ -15,10 +15,10 @@ import {
import { AppDispatch } from "app/store";
import { useTranslation } from "react-i18next";
import { getTranslationID } from "translation";
import { AddTypistGroupPopup } from "./addTypistGroupPopup";
import { EditTypistGroupPopup } from "./editTypistGroupPopup";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
import { EditTypistGroupPopup } from "./editTypistGroupPopup";
import { AddTypistGroupPopup } from "./addTypistGroupPopup";
const TypistGroupSettingPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
@ -31,6 +31,7 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => {
const [isAddPopupOpen, setIsAddPopupOpen] = useState(false);
const [isEditPopupOpen, setIsEditPopupOpen] = useState(false);
const [editTypistGroupId, setEditTypistGroupId] = useState<number>(NaN);
const onAddPopupOpen = useCallback(() => {
// typist一覧を取得
setIsAddPopupOpen(true);
@ -70,12 +71,12 @@ const TypistGroupSettingPage: React.FC = (): JSX.Element => {
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>

View File

@ -21,6 +21,8 @@ import {
changeUpdateUser,
changeLicenseAllocateUser,
} from "features/user/userSlice";
import { DelegationBar } from "components/delegate";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import personAdd from "../../assets/images/person_add.svg";
import checkFill from "../../assets/images/check_fill.svg";
import checkOutline from "../../assets/images/check_outline.svg";
@ -28,8 +30,6 @@ import progress_activit from "../../assets/images/progress_activit.svg";
import { UserAddPopup } from "./popup";
import { UserUpdatePopup } from "./updatePopup";
import { AllocateLicensePopup } from "./allocateLicensePopup";
import { DelegationBar } from "components/delegate";
import { selectDelegatedAccessToken } from "features/auth/selectors";
const UserListPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
@ -117,12 +117,12 @@ const UserListPage: React.FC = (): JSX.Element => {
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div className="">

View File

@ -21,11 +21,11 @@ import {
deleteWorktypeAsync,
} from "features/workflow/worktype";
import { AppDispatch } from "app/store";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
import { AddWorktypeIdPopup } from "./addWorktypeIdPopup";
import { EditWorktypeIdPopup } from "./editWorktypeIdPopup";
import { EditOptionItemsPopup } from "./editOptionItemsPopup";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { DelegationBar } from "components/delegate";
const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
@ -37,6 +37,7 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
const activeWorktypeId = useSelector(selectActiveWorktypeId);
const [selectedRow, setSelectedRow] = useState<number>(NaN);
// 追加Popupの表示制御
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
// 編集Popupの表示制御
@ -135,12 +136,12 @@ const WorktypeIdSettingPage: React.FC = (): JSX.Element => {
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div>

View File

@ -19,14 +19,15 @@ import {
} from "features/workflow";
import progress_activit from "assets/images/progress_activit.svg";
import { getTranslationID } from "translation";
import { AddWorkflowPopup } from "./addworkflowPopup";
import { EditWorkflowPopup } from "./editworkflowPopup";
import { DelegationBar } from "components/delegate";
import { selectDelegatedAccessToken } from "features/auth/selectors";
import { EditWorkflowPopup } from "./editworkflowPopup";
import { AddWorkflowPopup } from "./addworkflowPopup";
const WorkflowPage: React.FC = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const [t] = useTranslation();
// 追加Popupの表示制御
const [isShowAddPopup, setIsShowAddPopup] = useState<boolean>(false);
// 編集Popupの表示制御
@ -80,12 +81,12 @@ const WorkflowPage: React.FC = (): JSX.Element => {
>
{
// 代行操作中の場合は、代行操作バーを表示する
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
// TODO 代行操作中の会社名と、代行操作用のトークンを取得があれば表示するという風にする(別タスクで)
delegatedAccessToken && (
<DelegationBar delegatedCompanyName="XXXXXX" />
)
}
<Header userName="XXXXXX" />
<Header />
<UpdateTokenTimer />
<main className={styles.main}>
<div className="">

View File

@ -7,7 +7,8 @@
"internalServerError": "Verarbeitung fehlgeschlagen. Bitte versuchen Sie es später noch einmal.",
"listEmpty": "Es gibt 0 Suchergebnisse.",
"dialogConfirm": "Möchten Sie die Operation durchführen?",
"success": "Erfolgreich verarbeitet"
"success": "Erfolgreich verarbeitet",
"displayDialog": "(de)サインアウトしてもよろしいですか?"
},
"label": {
"cancel": "Abbrechen",
@ -22,7 +23,8 @@
"tier3": "(de)Distributor",
"tier4": "(de)Dealer",
"tier5": "(de)Customer",
"notSelected": "(de)None"
"notSelected": "(de)None",
"signOutButton": "(de)Sign out"
}
},
"topPage": {

View File

@ -7,7 +7,8 @@
"internalServerError": "Processing failed. Please try again later. ",
"listEmpty": "There are 0 search results.",
"dialogConfirm": "Do you want to perform the operation?",
"success": "Successfully Processed"
"success": "Successfully Processed",
"displayDialog": "サインアウトしてもよろしいですか?"
},
"label": {
"cancel": "Cancel",
@ -22,7 +23,8 @@
"tier3": "Distributor",
"tier4": "Dealer",
"tier5": "Customer",
"notSelected": "None"
"notSelected": "None",
"signOutButton": "Sign out"
}
},
"topPage": {

View File

@ -7,7 +7,8 @@
"internalServerError": "El procesamiento falló. Por favor, inténtelo de nuevo más tarde.",
"listEmpty": "Hay 0 resultados de búsqueda.",
"dialogConfirm": "¿Quieres realizar la operación?",
"success": "Procesado con éxito"
"success": "Procesado con éxito",
"displayDialog": "(es)サインアウトしてもよろしいですか?"
},
"label": {
"cancel": "Cancelar",
@ -22,7 +23,8 @@
"tier3": "(es)Distributor",
"tier4": "(es)Dealer",
"tier5": "(es)Customer",
"notSelected": "(es)None"
"notSelected": "(es)None",
"signOutButton": "(es)Sign out"
}
},
"topPage": {

View File

@ -7,7 +7,8 @@
"internalServerError": "Le traitement a échoué. Veuillez réessayer plus tard.",
"listEmpty": "Il y a 0 résultats de recherche.",
"dialogConfirm": "Voulez-vous effectuer l'opération?",
"success": "Traité avec succès"
"success": "Traité avec succès",
"displayDialog": "(fr)サインアウトしてもよろしいですか?"
},
"label": {
"cancel": "Annuler",
@ -22,7 +23,8 @@
"tier3": "(fr)Distributor",
"tier4": "(fr)Dealer",
"tier5": "(fr)Customer",
"notSelected": "(fr)None"
"notSelected": "(fr)None",
"signOutButton": "(fr)Sign out"
}
},
"topPage": {

View File

@ -1,4 +1,7 @@
{
"globalHeaders": {
"X-Frame-Options": "SAMEORIGIN"
},
"navigationFallback": {
"rewrite": "/index.html"
}