Manage Profile Pages

This commit is contained in:
Patrick Fic
2020-01-06 15:42:16 -08:00
parent 1e24f8679a
commit b51b2d2b76
16 changed files with 264 additions and 101 deletions

View File

@@ -16,7 +16,7 @@ import { ApolloLink } from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import { persistCache } from "apollo-cache-persist";
import initialState from "../graphql/initial-state";
import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
//import { shouldRefreshToken, refreshToken } from "../graphql/middleware";
import errorLink from "../graphql/apollo-error-handling";
class AppContainer extends Component {

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 -256 1792 1792">
<g transform="matrix(1,0,0,-1,197.42373,1300.6102)">
<path d="M 1408,131 Q 1408,11 1335,-58.5 1262,-128 1141,-128 H 267 Q 146,-128 73,-58.5 0,11 0,131 0,184 3.5,234.5 7,285 17.5,343.5 28,402 44,452 q 16,50 43,97.5 27,47.5 62,81 35,33.5 85.5,53.5 50.5,20 111.5,20 9,0 42,-21.5 33,-21.5 74.5,-48 41.5,-26.5 108,-48 Q 637,565 704,565 q 67,0 133.5,21.5 66.5,21.5 108,48 41.5,26.5 74.5,48 33,21.5 42,21.5 61,0 111.5,-20 50.5,-20 85.5,-53.5 35,-33.5 62,-81 27,-47.5 43,-97.5 16,-50 26.5,-108.5 10.5,-58.5 14,-109 Q 1408,184 1408,131 z m -320,893 Q 1088,865 975.5,752.5 863,640 704,640 545,640 432.5,752.5 320,865 320,1024 320,1183 432.5,1295.5 545,1408 704,1408 863,1408 975.5,1295.5 1088,1183 1088,1024 z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View File

@@ -0,0 +1,69 @@
import React from "react";
import { Link } from "react-router-dom";
import { Dropdown, Menu, Icon, Avatar, Typography, Row, Col } from "antd";
import { useTranslation } from "react-i18next";
import i18next from "i18next";
import { useQuery } from "@apollo/react-hooks";
import { GET_CURRENT_USER } from "../../graphql/local.queries";
import UserImage from "../../assets/User.svg";
import AlertComponent from "../alert/alert.component";
export default function CurrentUserDropdown() {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_CURRENT_USER);
const handleMenuClick = e => {
console.log("e", e);
if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => {
if (err)
return console.log("Error encountered when changing languages.", err);
});
}
};
const menu = (
<Menu mode="vertical" onClick={handleMenuClick}>
<Menu.Item>
<Link to="/manage/profile"> {t("menus.currentuser.profile")}</Link>
</Menu.Item>
<Menu.SubMenu
title={
<span>
<Icon type="global" />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en_us">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu>
);
if (loading) return null;
if (error) return <AlertComponent message={error.message} />;
const { currentUser } = data;
return (
<Dropdown overlay={menu}>
<Row>
<Col span={8}>
<Avatar size="large" alt="Avatar" src={UserImage} />
</Col>
<Col span={16}>
<Link to="/manage/profile">
{currentUser?.displayName ?? t("general.labels.unknown")}
</Link>
</Col>
</Row>
</Dropdown>
);
}

View File

@@ -1,51 +1,56 @@
import React from "react";
import { Link } from "react-router-dom";
import { useApolloClient } from "@apollo/react-hooks";
import { Menu, Icon } from "antd";
import { Menu, Icon, Row, Col } from "antd";
import "./header.styles.scss";
import SignOut from "../sign-out/sign-out.component";
import ManageSignInButton from "../manage-sign-in-button/manage-sign-in-button.component";
import GlobalSearch from "../global-search/global-search.component";
import LanguageSelector from "../language-selector/langauge-selector.component";
import CurrentUserDropdown from "../current-user-dropdown/current-user-dropdown.component";
export default ({ landingHeader, navItems, selectedNavItem }) => {
const apolloClient = useApolloClient();
const handleClick = e => {
apolloClient.writeData({ data: { selectedNavItem: e.key } });
};
return (
<Menu
theme='dark'
className='header'
onClick={handleClick}
selectedKeys={selectedNavItem}
mode='horizontal'>
<Menu.Item>
<GlobalSearch />
</Menu.Item>
{navItems.map(navItem => (
<Menu.Item key={navItem.title}>
<Link to={navItem.path}>
{navItem.icontype ? <Icon type={navItem.icontype} /> : null}
{navItem.title}
</Link>
</Menu.Item>
))}
<Row type="flex" justify="space-around">
<Col span={16}>
<Menu
theme="dark"
className="header"
onClick={handleClick}
selectedKeys={selectedNavItem}
mode="horizontal"
>
<Menu.Item>
<GlobalSearch />
</Menu.Item>
{navItems.map(navItem => (
<Menu.Item key={navItem.title}>
<Link to={navItem.path}>
{navItem.icontype ? <Icon type={navItem.icontype} /> : null}
{navItem.title}
</Link>
</Menu.Item>
))}
{!landingHeader ? (
<Menu.Item>
<SignOut />
</Menu.Item>
) : (
<Menu.Item>
<ManageSignInButton />
</Menu.Item>
)}
{!landingHeader ? <LanguageSelector /> : null}
</Menu>
{!landingHeader ? (
<Menu.Item>
<SignOut />
</Menu.Item>
) : (
<Menu.Item>
<ManageSignInButton />
</Menu.Item>
)}
</Menu>
</Col>
<Col span={6} offset={2}>
{!landingHeader ? <CurrentUserDropdown /> : null}
</Col>
</Row>
);
};

View File

@@ -7,8 +7,6 @@ export default function LanguageSelector() {
const { t } = useTranslation();
const handleMenuClick = e => {
console.log("e", e);
i18next.changeLanguage(e.key, (err, t) => {
if (err)
return console.log("Error encountered when changing languages.", err);
@@ -16,14 +14,14 @@ export default function LanguageSelector() {
};
const menu = (
<Menu onClick={handleMenuClick}>
<Menu.Item key='en_us'>{t("general.languages.english")}</Menu.Item>
<Menu.Item key='fr'>{t("general.languages.french")}</Menu.Item>
<Menu.Item key='es'>{t("general.languages.spanish")}</Menu.Item>
<Menu.Item key="en_us">{t("general.languages.english")}</Menu.Item>
<Menu.Item key="fr">{t("general.languages.french")}</Menu.Item>
<Menu.Item key="es">{t("general.languages.spanish")}</Menu.Item>
</Menu>
);
return (
<Dropdown overlay={menu}>
<Icon type='global' />
<Icon type="global" />
</Dropdown>
);
}

View File

@@ -0,0 +1,18 @@
import React from "react";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
export default function ProfileContent({ sidebarSelection }) {
const { t } = useTranslation();
switch (sidebarSelection.key) {
case "profile":
return <div>Profile stuff</div>;
case "shop":
return <div>Shop stuff</div>;
default:
return (
<AlertComponent message={t("profile.errors.state")} type="error" />
);
}
}

View File

@@ -0,0 +1,34 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Layout, Menu, Icon } from "antd";
export default function ProfileSideBar({
sidebarSelection,
setSidebarSelection
}) {
const { t } = useTranslation();
const onMenuClick = e => {
setSidebarSelection({ ...sidebarSelection, key: e.key });
};
return (
<Layout.Sider>
<Menu
theme="dark"
selectedKeys={sidebarSelection.key}
onClick={onMenuClick}
mode="inline"
>
<Menu.Item key="profile">
<Icon type="user" />
<span>{t("menus.profilesidebar.profile")}</span>
</Menu.Item>
<Menu.Item key="shops">
<Icon type="bank" />
<span>{t("menus.profilesidebar.shops")}</span>
</Menu.Item>
</Menu>
</Layout.Sider>
);
}

View File

@@ -31,28 +31,28 @@ export default function WhiteBoardCard({ metadata }) {
const menu = (
<Menu>
<Menu.Item key='images'>
<Icon type='file-image' />
<Menu.Item key="images">
<Icon type="file-image" />
{t("whiteboard.viewJobImages")}
</Menu.Item>
<Menu.Item key='printing'>
<Icon type='printer' />
<Menu.Item key="printing">
<Icon type="printer" />
{t("whiteboard.printCenter")}
</Menu.Item>
<Menu.Item key='notes'>
<Icon type='edit' />
<Menu.Item key="notes">
<Icon type="edit" />
{t("whiteboard.notes")}
</Menu.Item>
<Menu.Item key='postinvoices'>
<Icon type='shopping-cart' />
<Menu.Item key="postinvoices">
<Icon type="shopping-cart" />
{t("whiteboard.postInvoices")}
</Menu.Item>
<Menu.Item key='receiveparts'>
<Icon type='inbox' />
<Menu.Item key="receiveparts">
<Icon type="inbox" />
{t("whiteboard.receiveParts")}
</Menu.Item>
<Menu.Item key='partstatus'>
<Icon type='tool' />
<Menu.Item key="partstatus">
<Icon type="tool" />
{t("whiteboard.partStatus")}
</Menu.Item>
</Menu>
@@ -72,28 +72,31 @@ export default function WhiteBoardCard({ metadata }) {
bodyStyle={{ padding: 10 }}
actions={[
<Link to={`/manage/jobs/${metadata.id}`}>
<Icon type='eye' key='view' />
<Icon type="eye" key="view" />
</Link>,
<Icon type='message' key='message' />,
<Icon type="message" key="message" />,
<Dropdown overlay={menu} trigger={["click"]}>
<Icon type='ellipsis' />
<Icon type="ellipsis" />
</Dropdown>
]}>
]}
>
<Row>
<Col span={6}>
<Avatar size='large' alt='Vehicle Image' src={CarImage} />
<Avatar size="large" alt="Vehicle Image" src={CarImage} />
</Col>
<Col span={18}>
<Row>
<WrappedSpan>
{`${metadata.vehicle?.v_model_yr ?? "N/A"} ${metadata.vehicle
?.v_make_desc ?? "N/A"} ${metadata.vehicle?.v_model_desc ??
"N/A"}`}
{metadata.vehicle?.v_model_yr ?? t("general.labels.na")}{" "}
{metadata.vehicle?.v_make_desc ?? t("general.labels.na")}{" "}
{metadata.vehicle?.v_model_desc ?? t("general.labels.na")}
</WrappedSpan>
</Row>
{metadata.vehicle?.v_vin ? (
<Row>
<WrappedSpan>{`VIN: ${metadata.vehicle?.v_vin}`}</WrappedSpan>
<WrappedSpan>
VIN: {metadata.vehicle?.v_vin ?? t("general.labels.na")}
</WrappedSpan>
</Row>
) : null}
</Col>
@@ -101,13 +104,11 @@ export default function WhiteBoardCard({ metadata }) {
<Row>
<Col span={12}>
{t("general.labels.in")}:
<Moment format='MM/DD/YYYY'> {metadata.actual_in}</Moment>
<Moment format="MM/DD/YYYY">{metadata.actual_in}</Moment>
</Col>
<Col span={12}>
{t("general.labels.out")}:
<Moment format='MM/DD/YYYY'>
{metadata.scheduled_completion}
</Moment>
<Moment format="MM/DD/YYYY">{metadata.scheduled_completion}</Moment>
</Col>
</Row>
</Card>

View File

@@ -4,6 +4,7 @@ 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 }) => {
console.log("In gql error")
let access_token = window.localStorage.getItem("token");
if (graphQLErrors) {
// User access token has expired

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { useSubscription } from "@apollo/react-hooks";
import { useQuery } from "@apollo/react-hooks";
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import AlertComponent from "../../components/alert/alert.component";
import JobTombstone from "../../components/job-tombstone/job-tombstone.component";
@@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next";
function JobsDetailPage({ match }) {
const { jobId } = match.params;
const { t } = useTranslation();
const { loading, error, data } = useSubscription(GET_JOB_BY_PK, {
const { loading, error, data } = useQuery(GET_JOB_BY_PK, {
variables: { id: jobId },
fetchPolicy: "network-only"
});
@@ -22,7 +22,7 @@ function JobsDetailPage({ match }) {
: t("titles.jobsdetail", {
ro_number: data.jobs_by_pk.ro_number
});
}, [loading]);
}, [loading,data,t]);
if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type='error' />;

View File

@@ -14,7 +14,7 @@ export default function JobsPage() {
useEffect(() => {
document.title = t("titles.jobs");
}, []);
}, [t]);
if (error) return <AlertComponent message={error.message} />;

View File

@@ -11,6 +11,7 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
const WhiteBoardPage = lazy(() => import("../white-board/white-board.page"));
const JobsPage = lazy(() => import("../jobs/jobs.page"));
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page"));
const ProfilePage = lazy(() => import("../profile/profile.container.page"));
const { Header, Content, Footer } = Layout;
//This page will handle all routing for the entire application.
@@ -19,7 +20,7 @@ export default function Manage({ match }) {
useEffect(() => {
document.title = t("titles.app");
}, []);
}, [t]);
return (
<Layout>
@@ -30,7 +31,8 @@ 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} />
@@ -38,6 +40,12 @@ export default function Manage({ match }) {
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
<Route
exact
path={`${match.path}/profile`}
component={ProfilePage}
/>
</Suspense>
</ErrorBoundary>
</Content>

View File

@@ -0,0 +1,6 @@
import React from "react";
import ProfilePage from "./profile.page";
export default function ProfileContainerPage() {
return <ProfilePage />;
}

View File

@@ -0,0 +1,27 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Layout, Menu, Icon } from "antd";
import ProfileSideBar from "../../components/profile-sidebar/profile-sidebar.component";
import ProfileContent from "../../components/profile-content/profile-content.component";
export default function ProfilePage() {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.profile");
}, [t]);
const [sidebarSelection, setSidebarSelection] = useState({ key: "profile" });
return (
<Layout>
<ProfileSideBar
sidebarSelection={sidebarSelection}
setSidebarSelection={setSidebarSelection}
/>
<Layout.Content>
<ProfileContent sidebarSelection={sidebarSelection} />
</Layout.Content>
</Layout>
);
}

View File

@@ -10,13 +10,32 @@
"in": "In",
"out": "Out",
"na": "N/A",
"unknown": "Unknown",
"save": "Save"
}
},
"menus": {
"currentuser": {
"profile": "Profile",
"languageselector": "Language"
},
"profilesidebar": {
"profile": "My Profile",
"shops": "My Shops"
}
},
"titles": {
"app": "Bodyshop by ImEX Systems",
"jobs": "All Jobs | $t(titles.app)",
"jobsdetail": "Job {{ro_number}} | $t(titles.app)"
"jobsdetail": "Job {{ro_number}} | $t(titles.app)",
"profile": "My Profile | $t(titles.app)"
},
"profile": {
"errors": {
"state": "Error reading page state. Please refresh."
}
},
"jobs": {

View File

@@ -1,30 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,1A1ACAB16FB7DBFA
OUyvmLtCOnxYqlxwgp+ObGz1vI3BTxGgoI1S3+VkLOj9qzucmhD4MqDYDKRlEo0l
4NWTGoK3GDu+Fl5J/Ps4fvYrNOdmsS+1wCOLEt5GV18CYEdRx4C9zMIu1+XYsY0N
2Y3Jb66Mewe3vuL4LgVfiw4iI2f/k3mLGsurL88430DTdW8MpifGjFhdBOxMQ/xA
XrTtkxMquynL3LGELwzevAvv4ewFn8XMkKT4ZriuZwp58IMHg3xoAlMjxgtKzsy0
zfgngqG6aOR6i8317erHTYSeCvF4BzP+s7Q9YPP8S2W2v4yiHGZ0XYiXgwrVQnRt
39PbU2g0l9iKzDsGnne3iLpQb4qrWM8ZJPZPcVgTbxAdZur6zNuifCJ+vrqlrtC7
fJlmEJpwAH1H7ym85edkjF23fgEmIvyFIA1p7hbxtf+Dy723CHjrV7W3OZ2ecM+K
7HxvfU+XygfQyP+gIscV2ExgANAp+NdOnflkv3CrZQbEFMyAAjOYn63Cdrq/BMfc
gLs2V6xCQnWnt+WmQdoiXJgEniV2ECGAtNxYLfatjybjNDciqm2C9vPAME5ol6pt
l/wUnufnVWEYmlPzF0txtVZti5RZa8pUOnHBsHUuj1F7ftZtx85vbi26j7hW8ViT
nr52aMcUVvagyikF5WGVFr9pyHyx9tmECRbE243Akl3mLPllo2tvJC4wkLlJmDpn
4DdDk+A7JpfZ11KH+HWskVm26SdPODgq2v8BFzlsPm8C/z4jPooAI6FwLzpQ7Y1x
7S0Q5wEGO+XsAfB4BXpUHpZxPzr3FDqjjymd/gBcEJzC65/p2iJO+gAwLYCCIYB6
fH3T5D6cUZ+84ijrF30wNHpkbGq877RValYm+DLI4Ui9oJrsQxl8QJw+BOhEfe+i
oeKGQESkMjwV+Ve/adHQENXtBGrZeTxvhyesTnT/R1k0VSLZrk030A5S+VhVMBiX
h3cvvD7gH0UFtL2viMqKXIw0GDSpk3K1/ygm5fLooONiPWz36dhoaZ6BYCksSemU
iyjIP4avCQ5N2zS4uWnIwHxdiM9/x4aMrXwZeAkufly8gFLXOrGbZEXaP3XtO/fP
+jeQUEx3O7/tjnrbhtVRzgC92B2G67Ay5MWj7nRuLaCXtDuKvbH3mkqDEiXmfpuL
ck9d36GsWUjRz7mf9sc/lIXJmLL/kX+ticogH9GsAKaFat9fshIWsyIhi9UYxMt0
wa471bxWGnZgnAXTgrVUjjPjzsU9arY9pIeRR5xM0hSBAbPyemRhkIl+jZ/xKArd
TnFXePQPuF/1Grh8LzjpipgDCs5/zwguWBzB+KmOKycVoTYfG66CWtVFTyhZDWZW
cBiBzUT5VIvPBQs6b0Mbx5R2WyIsIAZzWUHLiO7lFctUbRxTgVhgZuNJjaOI5IYi
1wCfACUOE2uBjkzxI+DHX0lywMmuy8vK4jsXVBxUdjbJouIfh/NBvVUFR0/st7G7
1qjo0ys1WBTfKFX0nGs+6zSxrMJMVxusc6Vt/sqKeLRZH3PkAZjUf8ABc0aIO6Lz
I3714iwiObVqS2xeZamzB/9IlyEmlkrFXF4E2YhWmKxN168merd1KybylTxqMs+l
-----END RSA PRIVATE KEY-----