diff --git a/.circleci/config.yml b/.circleci/config.yml index 904d2817d..948ee5f44 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: app-build: docker: - image: cimg/node:16.15.0 - + resource_class: large working_directory: ~/repo/client steps: @@ -106,7 +106,7 @@ jobs: test-app-build: docker: - image: cimg/node:16.15.0 - + resource_class: large working_directory: ~/repo/client steps: @@ -217,4 +217,4 @@ workflows: #- admin-app-build: #filters: #branches: - #only: master \ No newline at end of file + #only: master diff --git a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx index ebcf9b133..4838c4827 100644 --- a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx +++ b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx @@ -88,6 +88,7 @@ function BillEnterModalContainer({ location, outstanding_returns, inventory, + federal_tax_exempt, ...remainingValues } = values; diff --git a/client/src/components/csi-response-form/csi-response-form.container.jsx b/client/src/components/csi-response-form/csi-response-form.container.jsx index 0bc418ef4..be8d04faf 100644 --- a/client/src/components/csi-response-form/csi-response-form.container.jsx +++ b/client/src/components/csi-response-form/csi-response-form.container.jsx @@ -5,6 +5,7 @@ import React, {useEffect} from "react"; import {useTranslation} from "react-i18next"; import {useLocation} from "react-router-dom"; import {QUERY_CSI_RESPONSE_BY_PK} from "../../graphql/csi.queries"; +import {DateFormatter} from "../../utils/DateFormatter"; import AlertComponent from "../alert/alert.component"; import ConfigFormComponents from "../config-form-components/config-form-components.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component"; @@ -44,7 +45,13 @@ export default function CsiResponseFormContainer() { readOnly componentList={data.csi_by_pk.csiquestion.config} /> - + {data.csi_by_pk.validuntil ? ( + <> + {t("csi.fields.validuntil")} + {": "} + {data.csi_by_pk.validuntil} + + ) : null} ); } diff --git a/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx b/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx index 2baccd5ab..e78dbbd3a 100644 --- a/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx +++ b/client/src/components/csi-response-list-paginated/csi-response-list-paginated.component.jsx @@ -5,9 +5,9 @@ import React, {useState} from "react"; import {useTranslation} from "react-i18next"; import {Link, useLocation, useNavigate} from "react-router-dom"; import {DateFormatter} from "../../utils/DateFormatter"; -import {alphaSort} from "../../utils/sorters"; -import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import {pageLimit} from "../../utils/config"; +import {alphaSort, dateSort} from "../../utils/sorters"; +import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component"; export default function CsiResponseListPaginated({ refetch, @@ -16,23 +16,25 @@ export default function CsiResponseListPaginated({ total, }) { const search = queryString.parse(useLocation().search); - const {responseid, page, sortcolumn, sortorder} = search; + const {responseid} = search; const history = useNavigate(); + const {t} = useTranslation(); const [state, setState] = useState({ sortedInfo: {}, filteredInfo: {text: ""}, + page: "", }); - const {t} = useTranslation(); + const columns = [ { title: t("jobs.fields.ro_number"), dataIndex: "ro_number", key: "ro_number", - width: "8%", - sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), - sortOrder: sortcolumn === "ro_number" && sortorder, + sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), + sortOrder: + state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, render: (text, record) => ( {record.job.ro_number || t("general.labels.na")} @@ -41,15 +43,15 @@ export default function CsiResponseListPaginated({ }, { title: t("jobs.fields.owner"), - dataIndex: "owner", - key: "owner", - ellipsis: true, - sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), - width: "25%", - sortOrder: sortcolumn === "owner" && sortorder, + dataIndex: "owner_name", + key: "owner_name", + sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a.job), + OwnerNameDisplayFunction(b.job) + ), + sortOrder: state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order, render: (text, record) => { - return record.job.owner ? ( - + return record.job.ownerid ? ( + ) : ( @@ -64,9 +66,8 @@ export default function CsiResponseListPaginated({ dataIndex: "completedon", key: "completedon", ellipsis: true, - sorter: (a, b) => a.completedon - b.completedon, - width: "25%", - sortOrder: sortcolumn === "completedon" && sortorder, + sorter: (a, b) => dateSort(a.completedon, b.completedon), + sortOrder: state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order, render: (text, record) => { return record.completedon ? ( {record.completedon} @@ -76,11 +77,12 @@ export default function CsiResponseListPaginated({ ]; const handleTableChange = (pagination, filters, sorter) => { - setState({...state, filteredInfo: filters, sortedInfo: sorter}); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history({search: queryString.stringify(search)}); + setState({ + ...state, + filteredInfo: filters, + sortedInfo: sorter, + page: pagination.current, + }); }; const handleOnRowClick = (record) => { @@ -108,7 +110,7 @@ export default function CsiResponseListPaginated({ pagination={{ position: "top", pageSize: pageLimit, - current: parseInt(page || 1), + current: parseInt(state.page || 1), total: total, }} columns={columns} @@ -121,13 +123,7 @@ export default function CsiResponseListPaginated({ }, selectedRowKeys: [responseid], type: "radio", - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, // click row - }; + }} /> diff --git a/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx b/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx index 95e882fdd..603643c94 100644 --- a/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx +++ b/client/src/components/shop-csi-config-form/shop-csi-config-form.component.jsx @@ -1,5 +1,5 @@ -import React from "react"; import {Form} from "antd"; +import React from "react"; import ConfigFormComponents from "../config-form-components/config-form-components.component"; export default function ShopCsiConfigForm({selectedCsi}) { @@ -10,7 +10,7 @@ export default function ShopCsiConfigForm({selectedCsi}) { return (
- The Config Form {readOnly} + {readOnly} {selectedCsi && (
; return (
- The Config Form + - + + diff --git a/client/src/graphql/csi.queries.js b/client/src/graphql/csi.queries.js index 9f334a64b..653b96b32 100644 --- a/client/src/graphql/csi.queries.js +++ b/client/src/graphql/csi.queries.js @@ -57,18 +57,15 @@ export const INSERT_CSI = gql` `; export const QUERY_CSI_RESPONSE_PAGINATED = gql` - query QUERY_CSI_RESPONSE_PAGINATED( - $offset: Int - $limit: Int - $order: [csi_order_by!]! - ) { - csi(offset: $offset, limit: $limit, order_by: $order) { + query QUERY_CSI_RESPONSE_PAGINATED{ + + csi(order_by: { completedon: desc_nulls_last }) { id completedon job { ownr_fn ownr_ln - ro_number + owneridro_number id } @@ -83,7 +80,7 @@ export const QUERY_CSI_RESPONSE_PAGINATED = gql` export const QUERY_CSI_RESPONSE_BY_PK = gql` query QUERY_CSI_RESPONSE_BY_PK($id: uuid!) { csi_by_pk(id: $id) { - relateddata + completedonrelateddata valid validuntil id diff --git a/client/src/pages/csi/csi.container.page.jsx b/client/src/pages/csi/csi.container.page.jsx index d066c788f..7453a00c9 100644 --- a/client/src/pages/csi/csi.container.page.jsx +++ b/client/src/pages/csi/csi.container.page.jsx @@ -1,88 +1,75 @@ -import {useMutation, useQuery} from "@apollo/client"; +//import {useMutation, useQuery } from "@apollo/client"; import {Button, Form, Layout, Result, Typography} from "antd"; -import React, {useState} from "react"; +import axios from "axios"; +import React, {useCallback, useEffect, useState} from "react"; import {useTranslation} from "react-i18next"; +import {connect} from "react-redux"; import {useParams} from "react-router-dom"; +import {createStructuredSelector} from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import ConfigFormComponents from "../../components/config-form-components/config-form-components.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import {COMPLETE_SURVEY, QUERY_SURVEY} from "../../graphql/csi.queries"; -import {connect} from "react-redux"; -import {createStructuredSelector} from "reselect"; import {selectCurrentUser} from "../../redux/user/user.selectors"; +import {DateTimeFormat} from "./../../utils/DateFormatter"; const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = (dispatch) => ({}); + export default connect(mapStateToProps, mapDispatchToProps)(CsiContainerPage); export function CsiContainerPage({currentUser}) { const {surveyId} = useParams(); const [form] = Form.useForm(); + const [axiosResponse, setAxiosResponse] = useState(null); const [submitting, setSubmitting] = useState({ loading: false, submitted: false, }); - const {loading, error, data} = useQuery(QUERY_SURVEY, { - variables: {surveyId}, - fetchPolicy: "network-only", - nextFetchPolicy: "network-only", - }); + const {t} = useTranslation(); - const [completeSurvey] = useMutation(COMPLETE_SURVEY); - if (loading) return ; - if (error || !!!data.csi_by_pk) - return ( -
- - {error ? ( -
ERROR: {error.graphQLErrors.map((e) => e.message)}
- ) : null} -
-
- ); - const handleFinish = async (values) => { - setSubmitting({...submitting, loading: true}); + const getAxiosData = useCallback(async () => { + try { + try { + window.$crisp.push(["do", "chat:hide"]); + } catch { + console.log("Unable to attach to crisp instance. "); + } + setSubmitting((prevSubmitting) => ({...prevSubmitting, loading: true})); - const result = await completeSurvey({ - variables: { - surveyId, - survey: { - response: values, - valid: false, - completedon: new Date(), - }, - }, - }); - - if (!!!result.errors) { - setSubmitting({...submitting, loading: false, submitted: true}); - } else { - setSubmitting({ - ...submitting, + const response = await axios.post("/csi/lookup", { + surveyId + }); + setSubmitting((prevSubmitting) => ({ + ...prevSubmitting, loading: false, - error: JSON.stringify(result.errors), + })); + setAxiosResponse(response.data); + } catch (error) { + console.error(`Something went wrong...: ${error.message}`); + console.dir({ + stack: error?.stack, + message: + error?.message, }); } - }; + }, [setAxiosResponse, surveyId]); - const { - relateddata: {bodyshop, job}, - csiquestion: {config: csiquestions}, - } = data.csi_by_pk; + useEffect(() => { + getAxiosData().catch((err) => + console.error( + `Something went wrong fetching axios data: ${err.message || ""}` + ) + ); + }, [getAxiosData]); - if (currentUser && currentUser.authorized) + // Return if authorized + if (currentUser && currentUser.authorized) { return ( ); + } + + if (submitting.loading) return ; + + const handleFinish = async (values) => { + try { + setSubmitting({...submitting, loading: true, submitting: true}); + const result = await axios.post("/csi/submit", {surveyId, values}); + console.log("result", result); + if (!!!result.errors && result.data.update_csi.affected_rows > 0) { + setSubmitting({...submitting, loading: false, submitted: true}); + } + } catch (error) { + console.error(`Something went wrong...: ${error.message}`); + console.dir({ + stack: error?.stack, + message: error?.message, + }); + } + }; + + if (!axiosResponse || axiosResponse.csi_by_pk === null) { + // Do something here , this is where you would return a loading box or something + return ( + <> + + + + + + + + {t("csi.labels.copyright")}{" "} + {t("csi.fields.surveyid", {surveyId: surveyId})} + + + + ); + } else { + const { + relateddata: {bodyshop, job}, + csiquestion: {config: csiquestions}, + } = axiosResponse.csi_by_pk; return ( - + +
{bodyshop.logo_img_path && bodyshop.logo_img_path.src ? ( - Logo - ) : null} -
- {bodyshop.shopname || ""} -
{`${bodyshop.address1 || ""}`}
-
{`${bodyshop.address2 || ""}`}
-
{`${bodyshop.city || ""} ${bodyshop.state || ""} ${ - bodyshop.zip_post || "" - }`}
+ {bodyshop.shopname.concat("Logo")}) : null} +
+ + {bodyshop.shopname || ""} + + + {`${bodyshop.address1 || ""}${bodyshop.address2 ? ", " : ""}${ + bodyshop.address2 || "" + }`.trim()} + + + {`${bodyshop.city || ""}${ + bodyshop.city && bodyshop.state ? ", " : "" + }${bodyshop.state || ""} ${bodyshop.zip_post || ""}`.trim()} +
{t("csi.labels.title")} - {`Hi ${job.ownr_co_nm || job.ownr_fn || ""}!`} + {t("csi.labels.greeting", { + name: job.ownr_co_nm || job.ownr_fn || "", + })} - {`At ${ + {t("csi.labels.intro", { + shopname: bodyshop.shopname || "" - }, we value your feedback. We would love to - hear what you have to say. Please fill out the form below.`} + })}
@@ -158,21 +213,42 @@ export function CsiContainerPage({currentUser}) { }} >
- + {axiosResponse.csi_by_pk.valid ? ( + <> + + ) : ( + <> + + + {t("csi.successes.submittedsub")} + + + )} - )} + )} - {`Copyright ImEX.Online. Survey ID: ${surveyId}`} + {t("csi.labels.copyright")}{" "} + {t("csi.fields.surveyid", {surveyId: surveyId})} ); + } } diff --git a/client/src/pages/shop-csi/shop-csi.container.page.jsx b/client/src/pages/shop-csi/shop-csi.container.page.jsx index 25a61f7b1..be3cc2d64 100644 --- a/client/src/pages/shop-csi/shop-csi.container.page.jsx +++ b/client/src/pages/shop-csi/shop-csi.container.page.jsx @@ -1,20 +1,17 @@ -import {Col, Row} from "antd"; import {useQuery} from "@apollo/client"; +import {Col, Row} from "antd"; import React, {useEffect} from "react"; import {useTranslation} from "react-i18next"; import {connect} from "react-redux"; -import {useLocation} from "react-router-dom"; import {createStructuredSelector} from "reselect"; import AlertComponent from "../../components/alert/alert.component"; import CsiResponseFormContainer from "../../components/csi-response-form/csi-response-form.container"; import CsiResponseListPaginated - from "../../components/csi-response-list-paginated/csi-response-list-paginated.component"; -import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries"; -import {setBreadcrumbs, setSelectedHeader} from "../../redux/application/application.actions"; -import {selectBodyshop} from "../../redux/user/user.selectors"; + from "../../components/csi-response-list-paginated/csi-response-list-paginated.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; -import {pageLimit} from "../../utils/config"; -import queryString from "query-string"; +import {QUERY_CSI_RESPONSE_PAGINATED} from "../../graphql/csi.queries"; +import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions"; +import {selectBodyshop} from "../../redux/user/user.selectors"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, @@ -25,29 +22,18 @@ const mapDispatchToProps = (dispatch) => ({ setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), }); -export function ShopCsiContainer({bodyshop, setBreadcrumbs, setSelectedHeader}) { +export function ShopCsiContainer({ + bodyshop, + setBreadcrumbs, + setSelectedHeader, + }) { const {t} = useTranslation(); - const searchParams = queryString.parse(useLocation().search); - const {page, sortcolumn, sortorder} = searchParams; const {loading, error, data, refetch} = useQuery( QUERY_CSI_RESPONSE_PAGINATED, { fetchPolicy: "network-only", nextFetchPolicy: "network-only", - variables: { - offset: page ? (page - 1) * pageLimit : 0, - limit: pageLimit, - order: [ - { - [sortcolumn || "completedon"]: sortorder - ? sortorder === "descend" - ? "desc_nulls_last" - : "asc" - : "desc_nulls_last", - }, - ], - }, } ); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 5b9032ecd..03a42e18e 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -838,17 +838,24 @@ "creating": "Error creating survey {{message}}", "notconfigured": "You do not have any current CSI Question Sets configured.", "notfoundsubtitle": "We were unable to find a survey using the link you provided. Please ensure the URL is correct or reach out to your shop for more help.", - "notfoundtitle": "No survey found." + "notfoundtitle": "No survey found.", + "surveycompletetitle": "Survey previously completed", + "surveycompletesubtitle": "This survey was already completed on {{date}}." }, "fields": { "completedon": "Completed On", - "created_at": "Created At" + "created_at": "Created At", + "surveyid": "Survey ID {{surveyId}}", + "validuntil": "Valid Until" }, "labels": { - "nologgedinuser": "Please log out of ImEX Online", - "nologgedinuser_sub": "Users of ImEX Online cannot complete CSI surveys while logged in. Please log out and try again.", + "nologgedinuser": "Please log out of $t(titles.app)", + "nologgedinuser_sub": "Users of $t(titles.app) cannot complete CSI surveys while logged in. Please log out and try again.", "noneselected": "No response selected.", - "title": "Customer Satisfaction Survey" + "title": "Customer Satisfaction Survey", + "greeting": "Hi {{name}}!", + "intro": "At {{shopname}}, we value your feedback. We would love to hear what you have to say. Please fill out the form below.", + "copyright": "Copyright © $t(titles.app). All Rights Reserved." }, "successes": { "created": "CSI created successfully. ", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index db92c9d66..295dec43e 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -838,17 +838,24 @@ "creating": "", "notconfigured": "", "notfoundsubtitle": "", - "notfoundtitle": "" + "notfoundtitle": "", + "surveycompletetitle": "", + "surveycompletesubtitle": "" }, "fields": { "completedon": "", - "created_at": "" + "created_at": "", + "surveyid": "", + "validuntil": "" }, "labels": { "nologgedinuser": "", "nologgedinuser_sub": "", "noneselected": "", - "title": "" + "title": "", + "greeting": "", + "intro": "", + "copyright": "" }, "successes": { "created": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 5ae7ba73d..3625b8805 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -838,17 +838,24 @@ "creating": "", "notconfigured": "", "notfoundsubtitle": "", - "notfoundtitle": "" + "notfoundtitle": "", + "surveycompletetitle": "", + "surveycompletesubtitle": "" }, "fields": { "completedon": "", - "created_at": "" + "created_at": "", + "surveyid": "", + "validuntil": "" }, "labels": { "nologgedinuser": "", "nologgedinuser_sub": "", "noneselected": "", - "title": "" + "title": "", + "greeting": "", + "intro": "", + "copyright": "" }, "successes": { "created": "", diff --git a/server.js b/server.js index 1eaa4b8ec..dbb0d0a5e 100644 --- a/server.js +++ b/server.js @@ -74,6 +74,7 @@ app.use('/adm', require("./server/routes/adminRoutes")); app.use('/tech', require("./server/routes/techRoutes")); app.use('/intellipay', require("./server/routes/intellipayRoutes")); app.use('/cdk', require("./server/routes/cdkRoutes")); +app.use('/csi', require("./server/routes/csiRoutes")); // Default route for forbidden access app.get("/", (req, res) => { diff --git a/server/csi/csi.js b/server/csi/csi.js new file mode 100644 index 000000000..819a9ebc7 --- /dev/null +++ b/server/csi/csi.js @@ -0,0 +1,2 @@ +exports.lookup = require("./lookup").default; +exports.submit = require("./submit").default; \ No newline at end of file diff --git a/server/csi/lookup.js b/server/csi/lookup.js new file mode 100644 index 000000000..a3c156e96 --- /dev/null +++ b/server/csi/lookup.js @@ -0,0 +1,24 @@ +const path = require("path"); +const queries = require("../graphql-client/queries"); +const logger = require("../utils/logger"); +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); + +const client = require("../graphql-client/graphql-client").client; + +exports.default = async (req, res) => { + try { + logger.log("csi-surveyID-lookup", "DEBUG", "csi", req.body.surveyId, null); + const gql_response = await client.request(queries.QUERY_SURVEY, { + surveyId: req.body.surveyId, + }); + res.status(200).json(gql_response); + } catch (error) { + logger.log("csi-surveyID-lookup", "ERROR", "csi", req.body.surveyId, error); + res.status(400).json(error); + } +}; diff --git a/server/csi/submit.js b/server/csi/submit.js new file mode 100644 index 000000000..ea6e54164 --- /dev/null +++ b/server/csi/submit.js @@ -0,0 +1,29 @@ +const path = require("path"); +const queries = require("../graphql-client/queries"); +const logger = require("../utils/logger"); +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); + +const client = require("../graphql-client/graphql-client").client; + +exports.default = async (req, res) => { + try { + logger.log("csi-surveyID-submit", "DEBUG", "csi", req.body.surveyId, null); + const gql_response = await client.request(queries.COMPLETE_SURVEY, { + surveyId: req.body.surveyId, + survey: { + response: req.body.values, + valid: false, + completedon: new Date(), + }, + }); + res.status(200).json(gql_response); + } catch (error) { + logger.log("csi-surveyID-submit", "ERROR", "csi", req.body.surveyId, error); + res.status(400).json(error); + } +}; diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 59bbaf9c1..369b394b4 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -2156,3 +2156,23 @@ exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) { shopid } }`; + +exports.QUERY_SURVEY = `query QUERY_SURVEY($surveyId: uuid!) { + csi_by_pk(id: $surveyId) { + completedon + csiquestion { + id + config + } + id + relateddata + valid + validuntil + } +}`; + +exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: csi_set_input) { + update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { + affected_rows + } + }`; \ No newline at end of file diff --git a/server/routes/csiRoutes.js b/server/routes/csiRoutes.js new file mode 100644 index 000000000..11993ff8f --- /dev/null +++ b/server/routes/csiRoutes.js @@ -0,0 +1,8 @@ +const express = require("express"); +const router = express.Router(); +const {lookup, submit} = require("../csi/csi"); + +router.post("/lookup", lookup); +router.post("/submit", submit); + +module.exports = router;