diff --git a/client/package.json b/client/package.json
index 46275f81a..64baff2a2 100644
--- a/client/package.json
+++ b/client/package.json
@@ -7,6 +7,7 @@
"antd": "^3.26.0",
"apollo-boost": "^0.4.4",
"apollo-link-context": "^1.0.19",
+ "apollo-link-error": "^1.1.12",
"apollo-link-logger": "^1.2.3",
"apollo-link-ws": "^1.0.19",
"dotenv": "^8.2.0",
diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx
index c91f582e0..0c56c2748 100644
--- a/client/src/App/App.container.jsx
+++ b/client/src/App/App.container.jsx
@@ -17,6 +17,7 @@ import { ApolloProvider } from "react-apollo";
import { persistCache } from "apollo-cache-persist";
import initialState from "../graphql/initial-state";
import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
+import errorLink from "../graphql/apollo-error-handling";
class AppContainer extends Component {
state = {
@@ -72,9 +73,9 @@ class AppContainer extends Component {
const token = localStorage.getItem("token");
// return the headers to the context so httpLink can read them
if (token) {
- if (shouldRefreshToken) {
- refreshToken();
- }
+ // if (shouldRefreshToken) {
+ // refreshToken();
+ // }
return {
headers: {
@@ -91,7 +92,7 @@ class AppContainer extends Component {
if (process.env.NODE_ENV === "development") {
middlewares.push(apolloLogger);
}
- middlewares.push(authLink.concat(link));
+ middlewares.push(errorLink.concat(authLink.concat(link)));
const cache = new InMemoryCache();
diff --git a/client/src/App/App.js b/client/src/App/App.js
index 36c1a3956..b9eeb58cc 100644
--- a/client/src/App/App.js
+++ b/client/src/App/App.js
@@ -52,6 +52,7 @@ export default () => {
localStorage.setItem("token", token);
const now = new Date();
window.sessionStorage.setItem(`lastTokenRefreshTime`, now);
+ // window.sessionStorage.setItem("user", user);
apolloClient
.mutate({
diff --git a/client/src/components/job-tombstone/job-tombstone.component.jsx b/client/src/components/job-tombstone/job-tombstone.component.jsx
index 64732411f..8a4d34afb 100644
--- a/client/src/components/job-tombstone/job-tombstone.component.jsx
+++ b/client/src/components/job-tombstone/job-tombstone.component.jsx
@@ -12,7 +12,8 @@ import {
Descriptions,
Tag,
notification,
- Avatar
+ Avatar,
+ Layout
} from "antd";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { useMutation } from "@apollo/react-hooks";
@@ -20,15 +21,16 @@ import FormItemPhone from "../form-items-formatted/phone-form-item.component";
import { useTranslation } from "react-i18next";
import CarImage from "../../assets/car.svg";
+const { Content } = Layout;
const formItemLayout = {
- labelCol: {
- xs: { span: 24 },
- sm: { span: 5 }
- },
- wrapperCol: {
- xs: { span: 24 },
- sm: { span: 12 }
- }
+ // labelCol: {
+ // xs: { span: 12 },
+ // sm: { span: 5 }
+ // },
+ // wrapperCol: {
+ // xs: { span: 24 },
+ // sm: { span: 12 }
+ // }
};
function JobTombstone({ job, ...otherProps }) {
@@ -77,108 +79,110 @@ function JobTombstone({ job, ...otherProps }) {
);
return (
-
+
+
+ {getFieldDecorator("est_ct_ln", {
+ initialValue: jobContext.est_ct_ln
+ })()}
+
+
+ {getFieldDecorator("est_ct_fn", {
+ initialValue: jobContext.est_ct_fn
+ })()}
+
+
+
+
+ {getFieldDecorator("est_ph1", {
+ initialValue: jobContext.est_ph1
+ })(
+
+ )}
+
+
+ {getFieldDecorator("est_ea", {
+ initialValue: jobContext.est_ea,
+ rules: [
+ {
+ type: "email",
+ message: "This is not a valid email address."
+ }
+ ]
+ })()}
+
+
+
+
+
);
}
-export default Form.create({ name: "JobTombstone" })(JobTombstone);
+export default Form.create({ name: "JobTombstone" })(JobTombstone);
\ No newline at end of file
diff --git a/client/src/graphql/apollo-error-handling.js b/client/src/graphql/apollo-error-handling.js
new file mode 100644
index 000000000..7ebd8754e
--- /dev/null
+++ b/client/src/graphql/apollo-error-handling.js
@@ -0,0 +1,58 @@
+import { onError } from "apollo-link-error";
+import { Observable } from "apollo-link";
+import { auth } from "../firebase/firebase.utils";
+//https://stackoverflow.com/questions/57163454/refreshing-a-token-with-apollo-client-firebase-auth
+const errorLink = onError(
+ ({ graphQLErrors, networkError, operation, forward }) => {
+ let access_token = window.localStorage.getItem("token");
+ if (graphQLErrors) {
+ // User access token has expired
+ if (graphQLErrors[0].message.includes("JWTExpired")) {
+ if (access_token && access_token !== "undefined") {
+ // Let's refresh token through async request
+ return new Observable(observer => {
+ auth.currentUser
+ .getIdToken(true)
+ .then(function(idToken) {
+ if (!idToken) {
+ window.localStorage.removeItem("token");
+ return console.log("Refresh token has expired");
+ }
+
+ window.localStorage.setItem("token", idToken);
+
+ // reset the headers
+ operation.setContext(({ headers = {} }) => ({
+ headers: {
+ // Re-add old headers
+ ...headers,
+ // Switch out old access token for new one
+ authorization: idToken ? `Bearer ${idToken}` : ""
+ }
+ }));
+ const subscriber = {
+ next: observer.next.bind(observer),
+ error: observer.error.bind(observer),
+ complete: observer.complete.bind(observer)
+ };
+
+ // Retry last failed request
+ forward(operation).subscribe(subscriber);
+ })
+ .catch(error => {
+ // No refresh or client token available, we force user to login
+ observer.error(error);
+ });
+ });
+ }
+ }
+ }
+
+ if (networkError) {
+ console.log(`[Network error]: ${networkError}`);
+ //props.history.push("/network-error");
+ }
+ }
+);
+
+export default errorLink;
diff --git a/client/src/graphql/middleware.js b/client/src/graphql/middleware.js
index 3afdbf5fc..bad7a938a 100644
--- a/client/src/graphql/middleware.js
+++ b/client/src/graphql/middleware.js
@@ -20,6 +20,7 @@ export function shouldRefreshToken(minutesBeforeShouldRefresh = 45) {
return aboutToExpire;
}
+
export async function refreshToken() {
try {
if (auth.user) {
diff --git a/client/src/pages/jobs-detail/jobs-detail.page.jsx b/client/src/pages/jobs-detail/jobs-detail.page.jsx
index abe320abe..831581343 100644
--- a/client/src/pages/jobs-detail/jobs-detail.page.jsx
+++ b/client/src/pages/jobs-detail/jobs-detail.page.jsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useEffect } from "react";
import { useSubscription } from "@apollo/react-hooks";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
@@ -6,15 +6,26 @@ import JobTombstone from "../../components/job-tombstone/job-tombstone.component
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
import { Tabs, Icon, Row } from "antd";
import JobLinesContainer from "../../components/job-lines/job-lines.container.component";
+import { useTranslation } from "react-i18next";
function JobsDetailPage({ match }) {
const { jobId } = match.params;
+ const { t } = useTranslation();
const { loading, error, data } = useSubscription(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only"
});
+
+ useEffect(() => {
+ document.title = loading
+ ? "..."
+ : t("titles.jobsdetail", {
+ ro_number: data.jobs_by_pk.ro_number
+ });
+ }, [loading]);
+
if (loading) return ;
- if (error) return ;
+ if (error) return ;
return (
@@ -22,38 +33,35 @@ function JobsDetailPage({ match }) {
-
+
-
+
Lines
}
- key="lines"
- >
+ key='lines'>
-
+
Rates
}
- key="rates"
- >
+ key='rates'>
Estimate Rates
-
+
Parts
}
- key="parts"
- >
+ key='parts'>
Estimate Parts
diff --git a/client/src/pages/jobs/jobs.page.jsx b/client/src/pages/jobs/jobs.page.jsx
index 4a30fc3f5..615e34d38 100644
--- a/client/src/pages/jobs/jobs.page.jsx
+++ b/client/src/pages/jobs/jobs.page.jsx
@@ -3,16 +3,17 @@ import { useSubscription } from "@apollo/react-hooks";
import AlertComponent from "../../components/alert/alert.component";
import { Col } from "antd";
import { SUBSCRIPTION_ALL_OPEN_JOBS } from "../../graphql/jobs.queries";
-
+import { useTranslation } from "react-i18next";
import JobsList from "../../components/jobs-list/jobs-list.component";
export default function JobsPage() {
const { loading, error, data } = useSubscription(SUBSCRIPTION_ALL_OPEN_JOBS, {
fetchPolicy: "network-only"
});
+ const { t } = useTranslation();
useEffect(() => {
- document.title = "new title";
+ document.title = t("titles.jobs");
}, []);
if (error) return ;
diff --git a/client/src/pages/manage/manage.page.jsx b/client/src/pages/manage/manage.page.jsx
index 2ea7d207d..56ad79bc7 100644
--- a/client/src/pages/manage/manage.page.jsx
+++ b/client/src/pages/manage/manage.page.jsx
@@ -1,6 +1,7 @@
-import React, { lazy, Suspense } from "react";
+import React, { lazy, Suspense, useEffect } from "react";
import { Route } from "react-router";
import { Layout, BackTop } from "antd";
+import { useTranslation } from "react-i18next";
//Component Imports
import HeaderContainer from "../../components/header/header.container";
@@ -14,6 +15,12 @@ const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page"));
const { Header, Content, Footer } = Layout;
//This page will handle all routing for the entire application.
export default function Manage({ match }) {
+ const { t } = useTranslation();
+
+ useEffect(() => {
+ document.title = t("titles.app");
+ }, []);
+
return (
@@ -23,8 +30,7 @@ export default function Manage({ match }) {
TODO: Suspended Loading in Manage Page...
}
- >
+ fallback={TODO: Suspended Loading in Manage Page...
}>
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 5b3049124..0b627c30d 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -13,6 +13,11 @@
"save": "Save"
}
},
+ "titles": {
+ "app": "Bodyshop by ImEX Systems",
+ "jobs": "All Jobs | $t(titles.app)",
+ "jobsdetail": "Job {{ro_number}} | $t(titles.app)"
+ },
"jobs": {
"labels": {
diff --git a/client/yarn.lock b/client/yarn.lock
index 277d11d56..21bf558a7 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -2313,7 +2313,7 @@ apollo-link-context@^1.0.19:
apollo-link "^1.2.13"
tslib "^1.9.3"
-apollo-link-error@^1.0.3:
+apollo-link-error@^1.0.3, apollo-link-error@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.12.tgz#e24487bb3c30af0654047611cda87038afbacbf9"
integrity sha512-psNmHyuy3valGikt/XHJfe0pKJnRX19tLLs6P6EHRxg+6q6JMXNVLYPaQBkL0FkwdTCB0cbFJAGRYCBviG8TDA==