Added additional translations.

This commit is contained in:
Patrick Fic
2020-01-06 08:19:58 -08:00
parent b72665fc81
commit 1e24f8679a
11 changed files with 213 additions and 127 deletions

View File

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

View File

@@ -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();

View File

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

View File

@@ -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 (
<Form onSubmit={handleSubmit} {...formItemLayout}>
<PageHeader
style={{
border: "1px solid rgb(235, 237, 240)"
}}
title={tombstoneTitle}
subTitle={
jobContext.owner
? (jobContext.owner?.first_name ?? "") +
" " +
(jobContext.owner?.last_name ?? "")
: t("jobs.labels.no_owner")
}
tags={<Tag color='blue'>{jobContext?.job_status?.name}</Tag>}
extra={[
<Form.Item key='1'>
<Button type='primary' htmlType='submit'>
{t("general.labels.save")}
</Button>
</Form.Item>
]}>
<Descriptions size='small' column={5}>
<Descriptions.Item label={t("jobs.labels.vehicle_info")}>
<Link to={`/manage/vehicles/${jobContext.vehicle?.id}`}>
{jobContext.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
{jobContext.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
{jobContext.vehicle?.v_model_desc ?? t("general.labels.na")} |{" "}
{jobContext.vehicle?.plate_no ?? t("general.labels.na")}
</Link>
</Descriptions.Item>
<Content>
<Form onSubmit={handleSubmit} {...formItemLayout}>
<PageHeader
style={{
border: "1px solid rgb(235, 237, 240)"
}}
title={tombstoneTitle}
subTitle={
jobContext.owner
? (jobContext.owner?.first_name ?? "") +
" " +
(jobContext.owner?.last_name ?? "")
: t("jobs.labels.no_owner")
}
tags={<Tag color='blue'>{jobContext?.job_status?.name}</Tag>}
extra={[
<Form.Item key='1'>
<Button type='primary' htmlType='submit'>
{t("general.labels.save")}
</Button>
</Form.Item>
]}>
<Descriptions size='small' column={5}>
<Descriptions.Item label={t("jobs.labels.vehicle_info")}>
<Link to={`/manage/vehicles/${jobContext.vehicle?.id}`}>
{jobContext.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
{jobContext.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
{jobContext.vehicle?.v_model_desc ?? t("general.labels.na")} |{" "}
{jobContext.vehicle?.plate_no ?? t("general.labels.na")}
</Link>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.est_number")}>
{jobContext.est_number}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.est_number")}>
{jobContext.est_number}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.claim_total")}>
$ {jobContext.claim_total?.toFixed(2)}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.claim_total")}>
$ {jobContext.claim_total?.toFixed(2)}
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.deductible")}>
$ {jobContext.deductible?.toFixed(2)}
</Descriptions.Item>
</Descriptions>
</PageHeader>
<Descriptions.Item label={t("jobs.fields.deductible")}>
$ {jobContext.deductible?.toFixed(2)}
</Descriptions.Item>
</Descriptions>
</PageHeader>
<Row>
<Typography.Title level={4}>Information</Typography.Title>
{
// <Form.Item label='Estimate #'>
// {getFieldDecorator("est_number", {
// initialValue: jobContext.est_number
// })(<Input name='est_number' readOnly onChange={handleChange} />)}
// </Form.Item>
}
</Row>
<Row>
<Typography.Title level={4}>Information</Typography.Title>
{
// <Form.Item label='Estimate #'>
// {getFieldDecorator("est_number", {
// initialValue: jobContext.est_number
// })(<Input name='est_number' readOnly onChange={handleChange} />)}
// </Form.Item>
}
</Row>
<Row>
<Typography.Title level={4}>Insurance Information</Typography.Title>
<Form.Item label='Insurance Company'>
{getFieldDecorator("est_co_nm", {
initialValue: jobContext.est_co_nm
})(<Input name='est_co_nm' onChange={handleChange} />)}
</Form.Item>
<Col span={8}>
<Form.Item label='Estimator Last Name'>
{getFieldDecorator("est_ct_ln", {
initialValue: jobContext.est_ct_ln
})(<Input name='est_ct_ln' onChange={handleChange} />)}
<Row>
<Typography.Title level={4}>Insurance Information</Typography.Title>
<Form.Item label='Insurance Company'>
{getFieldDecorator("est_co_nm", {
initialValue: jobContext.est_co_nm
})(<Input name='est_co_nm' onChange={handleChange} />)}
</Form.Item>
<Form.Item label='Estimator First Name'>
{getFieldDecorator("est_ct_fn", {
initialValue: jobContext.est_ct_fn
})(<Input name='est_ct_fn' onChange={handleChange} />)}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label='Estimator Phone #'>
{getFieldDecorator("est_ph1", {
initialValue: jobContext.est_ph1
})(
<FormItemPhone
customInput={Input}
name='est_ph1'
onValueChange={handleChange}
/>
)}
</Form.Item>
<Form.Item label='Estimator Email'>
{getFieldDecorator("est_ea", {
initialValue: jobContext.est_ea,
rules: [
{
type: "email",
message: "This is not a valid email address."
}
]
})(<Input name='est_ea' onChange={handleChange} />)}
</Form.Item>
</Col>
</Row>
</Form>
<Col span={8}>
<Form.Item label='Estimator Last Name'>
{getFieldDecorator("est_ct_ln", {
initialValue: jobContext.est_ct_ln
})(<Input name='est_ct_ln' onChange={handleChange} />)}
</Form.Item>
<Form.Item label='Estimator First Name'>
{getFieldDecorator("est_ct_fn", {
initialValue: jobContext.est_ct_fn
})(<Input name='est_ct_fn' onChange={handleChange} />)}
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label='Estimator Phone #'>
{getFieldDecorator("est_ph1", {
initialValue: jobContext.est_ph1
})(
<FormItemPhone
customInput={Input}
name='est_ph1'
onValueChange={handleChange}
/>
)}
</Form.Item>
<Form.Item label='Estimator Email'>
{getFieldDecorator("est_ea", {
initialValue: jobContext.est_ea,
rules: [
{
type: "email",
message: "This is not a valid email address."
}
]
})(<Input name='est_ea' onChange={handleChange} />)}
</Form.Item>
</Col>
</Row>
</Form>
</Content>
);
}
export default Form.create({ name: "JobTombstone" })(JobTombstone);
export default Form.create({ name: "JobTombstone" })(JobTombstone);

View File

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

View File

@@ -20,6 +20,7 @@ export function shouldRefreshToken(minutesBeforeShouldRefresh = 45) {
return aboutToExpire;
}
export async function refreshToken() {
try {
if (auth.user) {

View File

@@ -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 <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (error) return <AlertComponent message={error.message} type='error' />;
return (
<div>
@@ -22,38 +33,35 @@ function JobsDetailPage({ match }) {
<JobTombstone job={data.jobs_by_pk} />
</Row>
<Row>
<Tabs defaultActiveKey="lines">
<Tabs defaultActiveKey='lines'>
<Tabs.TabPane
tab={
<span>
<Icon type="bars" />
<Icon type='bars' />
Lines
</span>
}
key="lines"
>
key='lines'>
<JobLinesContainer match={match} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon type="dollar" />
<Icon type='dollar' />
Rates
</span>
}
key="rates"
>
key='rates'>
Estimate Rates
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon type="tool1" />
<Icon type='tool1' />
Parts
</span>
}
key="parts"
>
key='parts'>
Estimate Parts
</Tabs.TabPane>
</Tabs>

View File

@@ -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 <AlertComponent message={error.message} />;

View File

@@ -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 (
<Layout>
<Header>
@@ -23,8 +30,7 @@ export default function Manage({ match }) {
<Content>
<ErrorBoundary>
<Suspense
fallback={<div>TODO: Suspended Loading in Manage Page...</div>}
>
fallback={<div>TODO: Suspended Loading in Manage Page...</div>}>
<Route exact path={`${match.path}`} component={WhiteBoardPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />

View File

@@ -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": {

View File

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