Added fingerprinting fixed + update firebase fingerprint on log in BOD-132
This commit is contained in:
@@ -12,4 +12,5 @@
|
|||||||
6. Deploy the function
|
6. Deploy the function
|
||||||
1. $ firebase deploy --only functions
|
1. $ firebase deploy --only functions
|
||||||
7. Add the allowed domains.
|
7. Add the allowed domains.
|
||||||
8. Update server variables including FIREBASE_ADMINSDK_JSON, FIREBASE_DATABASE_URL
|
8. Update server variables including FIREBASE_ADMINSDK_JSON, FIREBASE_DATABASE_URL
|
||||||
|
9. Create the firestore and copy the rules from dev for userinstances.
|
||||||
@@ -4573,6 +4573,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>refresh</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>reset</name>
|
<name>reset</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -4746,6 +4767,48 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>instanceconflictext</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>instanceconflictitle</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>loading</name>
|
<name>loading</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
27
client/src/components/conflict/conflict.component.jsx
Normal file
27
client/src/components/conflict/conflict.component.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Result, Button } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function ConflictComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Result
|
||||||
|
status="warning"
|
||||||
|
title={t("general.labels.instanceconflictitle")}
|
||||||
|
extra={
|
||||||
|
<div>
|
||||||
|
<div>{t("general.labels.instanceconflictext")}</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
window.location.reload();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.refresh")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,10 +7,15 @@ import ChatAffixContainer from "../../components/chat-affix/chat-affix.container
|
|||||||
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
||||||
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
|
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
|
||||||
import FooterComponent from "../../components/footer/footer.component";
|
import FooterComponent from "../../components/footer/footer.component";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectInstanceConflict } from "../../redux/user/user.selectors";
|
||||||
//Component Imports
|
//Component Imports
|
||||||
import HeaderContainer from "../../components/header/header.container";
|
import HeaderContainer from "../../components/header/header.container";
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||||
|
import ConflictComponent from '../../components/conflict/conflict.component'
|
||||||
|
|
||||||
import "./manage.page.styles.scss";
|
import "./manage.page.styles.scss";
|
||||||
const ManageRootPage = lazy(() =>
|
const ManageRootPage = lazy(() =>
|
||||||
import("../manage-root/manage-root.page.container")
|
import("../manage-root/manage-root.page.container")
|
||||||
@@ -91,7 +96,15 @@ const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
|
|||||||
|
|
||||||
const { Header, Content, Footer } = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
|
|
||||||
export default function Manage({ match }) {
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
conflict: selectInstanceConflict,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
export function Manage({ match, conflict }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -105,145 +118,159 @@ export default function Manage({ match }) {
|
|||||||
</Header>
|
</Header>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Content
|
<Content
|
||||||
className='content-container'
|
className="content-container"
|
||||||
style={{ padding: "0em 4em 4em" }}>
|
style={{ padding: "0em 4em 4em" }}
|
||||||
|
>
|
||||||
<FcmNotification />
|
<FcmNotification />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Suspense
|
{conflict ? (
|
||||||
fallback={
|
<ConflictComponent />
|
||||||
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
) : (
|
||||||
}>
|
<Suspense
|
||||||
<BreadCrumbs />
|
fallback={
|
||||||
<EnterInvoiceModalContainer />
|
<LoadingSpinner message={t("general.labels.loadingapp")} />
|
||||||
<EmailOverlayContainer />
|
}
|
||||||
<TimeTicketModalContainer />
|
>
|
||||||
<PrintCenterModalContainer />
|
<BreadCrumbs />
|
||||||
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
<EnterInvoiceModalContainer />
|
||||||
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
<EmailOverlayContainer />
|
||||||
|
<TimeTicketModalContainer />
|
||||||
|
<PrintCenterModalContainer />
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}`}
|
||||||
|
component={ManageRootPage}
|
||||||
|
/>
|
||||||
|
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/jobs/:jobId/intake`}
|
||||||
|
component={JobIntake}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/jobs/:jobId/close`}
|
||||||
|
component={JobsClose}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/jobs/all`}
|
||||||
|
component={AllJobs}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/jobs/new`}
|
||||||
|
component={JobsCreateContainerPage}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${match.path}/jobs/:jobId`}
|
||||||
|
component={JobsDetailPage}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/jobs/:jobId/intake`}
|
path={`${match.path}/courtesycars/`}
|
||||||
component={JobIntake}
|
component={CourtesyCarsPage}
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/jobs/:jobId/close`}
|
|
||||||
component={JobsClose}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/jobs/all`}
|
|
||||||
component={AllJobs}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/jobs/new`}
|
|
||||||
component={JobsCreateContainerPage}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path={`${match.path}/jobs/:jobId`}
|
|
||||||
component={JobsDetailPage}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/courtesycars/`}
|
|
||||||
component={CourtesyCarsPage}
|
|
||||||
/>
|
|
||||||
<Switch>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={`${match.path}/courtesycars/new`}
|
|
||||||
component={CourtesyCarCreateContainer}
|
|
||||||
/>
|
/>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/courtesycars/new`}
|
||||||
|
component={CourtesyCarCreateContainer}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/courtesycars/contracts`}
|
path={`${match.path}/courtesycars/contracts`}
|
||||||
component={ContractsList}
|
component={ContractsList}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/courtesycars/contracts/new`}
|
path={`${match.path}/courtesycars/contracts/new`}
|
||||||
component={ContractCreatePage}
|
component={ContractCreatePage}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/courtesycars/contracts/:contractId`}
|
path={`${match.path}/courtesycars/contracts/:contractId`}
|
||||||
component={ContractDetailPage}
|
component={ContractDetailPage}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/courtesycars/:ccId`}
|
||||||
|
component={CourtesyCarDetailContainer}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/courtesycars/:ccId`}
|
path={`${match.path}/profile`}
|
||||||
component={CourtesyCarDetailContainer}
|
component={ProfilePage}
|
||||||
/>
|
/>
|
||||||
</Switch>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/vehicles`}
|
||||||
path={`${match.path}/profile`}
|
component={VehiclesContainer}
|
||||||
component={ProfilePage}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/production/list`}
|
||||||
path={`${match.path}/vehicles`}
|
component={ProductionListPage}
|
||||||
component={VehiclesContainer}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/production/board`}
|
||||||
path={`${match.path}/production/list`}
|
component={ProductionBoardPage}
|
||||||
component={ProductionListPage}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/vehicles/:vehId`}
|
||||||
path={`${match.path}/production/board`}
|
component={VehiclesDetailContainer}
|
||||||
component={ProductionBoardPage}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/invoices`}
|
||||||
path={`${match.path}/vehicles/:vehId`}
|
component={InvoicesListPage}
|
||||||
component={VehiclesDetailContainer}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/owners`}
|
||||||
path={`${match.path}/invoices`}
|
component={OwnersContainer}
|
||||||
component={InvoicesListPage}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/owners/:ownerId`}
|
||||||
path={`${match.path}/owners`}
|
component={OwnersDetailContainer}
|
||||||
component={OwnersContainer}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/schedule`}
|
||||||
path={`${match.path}/owners/:ownerId`}
|
component={ScheduleContainer}
|
||||||
component={OwnersDetailContainer}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/available`}
|
||||||
path={`${match.path}/schedule`}
|
component={JobsAvailablePage}
|
||||||
component={ScheduleContainer}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route
|
exact
|
||||||
exact
|
path={`${match.path}/shop/`}
|
||||||
path={`${match.path}/available`}
|
component={ShopPage}
|
||||||
component={JobsAvailablePage}
|
/>
|
||||||
/>
|
<Route
|
||||||
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
|
exact
|
||||||
<Route
|
path={`${match.path}/shop/templates`}
|
||||||
exact
|
component={ShopTemplates}
|
||||||
path={`${match.path}/shop/templates`}
|
/>
|
||||||
component={ShopTemplates}
|
<Route
|
||||||
/>
|
exact
|
||||||
<Route
|
path={`${match.path}/shop/vendors`}
|
||||||
exact
|
component={ShopVendorPageContainer}
|
||||||
path={`${match.path}/shop/vendors`}
|
/>
|
||||||
component={ShopVendorPageContainer}
|
</Suspense>
|
||||||
/>
|
)}
|
||||||
</Suspense>
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
@@ -255,3 +282,4 @@ export default function Manage({ match }) {
|
|||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Manage);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const INITIAL_STATE = {
|
|||||||
|
|
||||||
const userReducer = (state = INITIAL_STATE, action) => {
|
const userReducer = (state = INITIAL_STATE, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case UserActionTypes.SET_INSTANCE_ID:
|
||||||
|
return { ...state, conflict: false };
|
||||||
case UserActionTypes.SET_INSTANCE_CONFLICT:
|
case UserActionTypes.SET_INSTANCE_CONFLICT:
|
||||||
return { ...state, conflict: true };
|
return { ...state, conflict: true };
|
||||||
case UserActionTypes.SIGN_IN_SUCCESS:
|
case UserActionTypes.SIGN_IN_SUCCESS:
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function* signInWithEmail({ payload: { email, password } }) {
|
|||||||
const { user } = yield auth.signInWithEmailAndPassword(email, password);
|
const { user } = yield auth.signInWithEmailAndPassword(email, password);
|
||||||
LogRocket.identify(user.email);
|
LogRocket.identify(user.email);
|
||||||
|
|
||||||
yield put(setInstanceId("123"));
|
|
||||||
yield put(
|
yield put(
|
||||||
signInSuccess({
|
signInSuccess({
|
||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
@@ -51,7 +50,7 @@ export function* isUserAuthenticated() {
|
|||||||
yield put(unauthorizedUser());
|
yield put(unauthorizedUser());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
yield put(setInstanceId(user.uid));
|
|
||||||
LogRocket.identify(user.email);
|
LogRocket.identify(user.email);
|
||||||
yield put(
|
yield put(
|
||||||
signInSuccess({
|
signInSuccess({
|
||||||
@@ -101,16 +100,15 @@ export function* setInstanceIdSaga({ payload: uid }) {
|
|||||||
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
||||||
|
|
||||||
const fingerprint = Fingerprint2.x64hash128(
|
const fingerprint = Fingerprint2.x64hash128(
|
||||||
(yield Fingerprint2.getPromise({
|
(yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
|
||||||
excludes: { fonts: true, audio: true, webgl: true },
|
|
||||||
})).join(""),
|
|
||||||
31
|
31
|
||||||
);
|
);
|
||||||
|
var result = window.confirm("Press a button!");
|
||||||
yield userInstanceRef.set({
|
if (result)
|
||||||
timestamp: new Date(),
|
yield userInstanceRef.set({
|
||||||
fingerprint,
|
timestamp: new Date(),
|
||||||
});
|
fingerprint,
|
||||||
|
});
|
||||||
|
|
||||||
yield delay(5000);
|
yield delay(5000);
|
||||||
yield put(checkInstanceId(uid));
|
yield put(checkInstanceId(uid));
|
||||||
@@ -129,22 +127,17 @@ export function* checkInstanceIdSaga({ payload: uid }) {
|
|||||||
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
const userInstanceRef = firestore.doc(`userInstance/${uid}`);
|
||||||
|
|
||||||
const fingerprint = Fingerprint2.x64hash128(
|
const fingerprint = Fingerprint2.x64hash128(
|
||||||
(yield Fingerprint2.getPromise({
|
(yield Fingerprint2.getPromise({})).map((c) => c.value).join(""),
|
||||||
excludes: { fonts: true, audio: true, webgl: true },
|
|
||||||
})).join(""),
|
|
||||||
31
|
31
|
||||||
);
|
);
|
||||||
|
|
||||||
const snapshot = yield userInstanceRef.get();
|
const snapshot = yield userInstanceRef.get();
|
||||||
console.log("function*checkInstanceIdSaga -> snapshot", snapshot.data());
|
|
||||||
console.log("fingerprint", fingerprint);
|
|
||||||
|
|
||||||
if (snapshot.data().fingerprint === fingerprint) {
|
if (snapshot.data().fingerprint === fingerprint) {
|
||||||
console.log("Waiting and checking.");
|
yield delay(30000);
|
||||||
yield delay(5000);
|
|
||||||
yield put(checkInstanceId(uid));
|
yield put(checkInstanceId(uid));
|
||||||
} else {
|
} else {
|
||||||
console.log("Didnt match");
|
console.log("ERROR: Fingerprints do not match. Conflict detected.");
|
||||||
yield put(setInstanceConflict());
|
yield put(setInstanceConflict());
|
||||||
}
|
}
|
||||||
// yield userInstanceRef.set({
|
// yield userInstanceRef.set({
|
||||||
@@ -158,6 +151,14 @@ export function* checkInstanceIdSaga({ payload: uid }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* onSignInSuccess() {
|
||||||
|
yield takeLatest(UserActionTypes.SIGN_IN_SUCCESS, signInSuccessSaga);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* signInSuccessSaga({ payload }) {
|
||||||
|
yield put(setInstanceId(payload.uid));
|
||||||
|
}
|
||||||
|
|
||||||
export function* userSagas() {
|
export function* userSagas() {
|
||||||
yield all([
|
yield all([
|
||||||
call(onEmailSignInStart),
|
call(onEmailSignInStart),
|
||||||
@@ -166,5 +167,6 @@ export function* userSagas() {
|
|||||||
call(onUpdateUserDetails),
|
call(onUpdateUserDetails),
|
||||||
call(onSetInstanceId),
|
call(onSetInstanceId),
|
||||||
call(onCheckInstanceId),
|
call(onCheckInstanceId),
|
||||||
|
call(onSignInSuccess),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,7 @@
|
|||||||
"create": "Create",
|
"create": "Create",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
"refresh": "Refresh",
|
||||||
"reset": "Reset to original.",
|
"reset": "Reset to original.",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveandnew": "Save and New",
|
"saveandnew": "Save and New",
|
||||||
@@ -322,6 +323,8 @@
|
|||||||
"areyousure": "Are you sure?",
|
"areyousure": "Are you sure?",
|
||||||
"barcode": "Barcode",
|
"barcode": "Barcode",
|
||||||
"in": "In",
|
"in": "In",
|
||||||
|
"instanceconflictext": "Your $t(titles.app) account can only be used on one device at any given time. Refresh your session to take control.",
|
||||||
|
"instanceconflictitle": "Your account is being used elsewhere.",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"loadingapp": "Loading Bodyshop.app",
|
"loadingapp": "Loading Bodyshop.app",
|
||||||
"loadingshop": "Loading shop data...",
|
"loadingshop": "Loading shop data...",
|
||||||
|
|||||||
@@ -312,6 +312,7 @@
|
|||||||
"create": "",
|
"create": "",
|
||||||
"delete": "Borrar",
|
"delete": "Borrar",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
|
"refresh": "",
|
||||||
"reset": "Restablecer a original.",
|
"reset": "Restablecer a original.",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"saveandnew": "",
|
"saveandnew": "",
|
||||||
@@ -322,6 +323,8 @@
|
|||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "código de barras",
|
"barcode": "código de barras",
|
||||||
"in": "en",
|
"in": "en",
|
||||||
|
"instanceconflictext": "",
|
||||||
|
"instanceconflictitle": "",
|
||||||
"loading": "Cargando...",
|
"loading": "Cargando...",
|
||||||
"loadingapp": "Cargando Bodyshop.app",
|
"loadingapp": "Cargando Bodyshop.app",
|
||||||
"loadingshop": "Cargando datos de la tienda ...",
|
"loadingshop": "Cargando datos de la tienda ...",
|
||||||
|
|||||||
@@ -312,6 +312,7 @@
|
|||||||
"create": "",
|
"create": "",
|
||||||
"delete": "Effacer",
|
"delete": "Effacer",
|
||||||
"edit": "modifier",
|
"edit": "modifier",
|
||||||
|
"refresh": "",
|
||||||
"reset": "Rétablir l'original.",
|
"reset": "Rétablir l'original.",
|
||||||
"save": "sauvegarder",
|
"save": "sauvegarder",
|
||||||
"saveandnew": "",
|
"saveandnew": "",
|
||||||
@@ -322,6 +323,8 @@
|
|||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "code à barre",
|
"barcode": "code à barre",
|
||||||
"in": "dans",
|
"in": "dans",
|
||||||
|
"instanceconflictext": "",
|
||||||
|
"instanceconflictitle": "",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"loadingapp": "Chargement de Bodyshop.app",
|
"loadingapp": "Chargement de Bodyshop.app",
|
||||||
"loadingshop": "Chargement des données de la boutique ...",
|
"loadingshop": "Chargement des données de la boutique ...",
|
||||||
|
|||||||
Reference in New Issue
Block a user