From 94eed283cb63eb990a7afe01df1e4d27d09efa02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 15 Sep 2021 14:42:46 +0100 Subject: [PATCH] feat: add customer creation modal --- .../graphql/resolvers/customer.resolver.js | 3 +- lib/new-admin/graphql/types/customer.type.js | 1 + new-lamassu-admin/package-lock.json | 5 + new-lamassu-admin/package.json | 1 + .../src/components/layout/TitleSection.js | 7 +- .../src/pages/Customers/Customers.js | 35 +++++- .../src/pages/Customers/CustomersList.js | 2 +- .../components/CreateCustomerModal.js | 108 ++++++++++++++++++ 8 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index d3563669..58461fd8 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -49,7 +49,8 @@ const resolvers = { }, deleteCustomerNote: (...[, { noteId }]) => { return customerNotes.deleteCustomerNote(noteId) - } + }, + createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber }) } } diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index bdbf3a94..ce8cb3cb 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -103,6 +103,7 @@ const typeDef = gql` createCustomerNote(customerId: ID!, title: String!, content: String!): Boolean @auth editCustomerNote(noteId: ID!, newContent: String!): Boolean @auth deleteCustomerNote(noteId: ID!): Boolean @auth + createCustomer(phoneNumber: String): Customer @auth } ` diff --git a/new-lamassu-admin/package-lock.json b/new-lamassu-admin/package-lock.json index e64cc12a..b54297ef 100644 --- a/new-lamassu-admin/package-lock.json +++ b/new-lamassu-admin/package-lock.json @@ -13870,6 +13870,11 @@ "delegate": "^3.1.2" } }, + "google-libphonenumber": { + "version": "3.2.22", + "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.22.tgz", + "integrity": "sha512-lzEllxWc05n/HEv75SsDrA7zdEVvQzTZimItZm/TZ5XBs7cmx2NJmSlA5I0kZbdKNu8GFETBhSpo+SOhx0JslA==" + }, "graceful-fs": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json index bd97a32c..ab5f4f1c 100644 --- a/new-lamassu-admin/package.json +++ b/new-lamassu-admin/package.json @@ -26,6 +26,7 @@ "downshift": "3.3.4", "file-saver": "2.0.2", "formik": "2.2.0", + "google-libphonenumber": "^3.2.22", "graphql": "^14.5.8", "graphql-tag": "^2.10.3", "jss-plugin-extend": "^10.0.0", diff --git a/new-lamassu-admin/src/components/layout/TitleSection.js b/new-lamassu-admin/src/components/layout/TitleSection.js index 6858ed5f..f0777103 100644 --- a/new-lamassu-admin/src/components/layout/TitleSection.js +++ b/new-lamassu-admin/src/components/layout/TitleSection.js @@ -19,14 +19,14 @@ const TitleSection = ({ buttons = [], children, appendix, - appendixClassName + appendixRight }) => { const classes = useStyles() return (
{title} - {appendix &&
{appendix}
} + {!!appendix && appendix} {error && ( Failed to save )} @@ -46,13 +46,14 @@ const TitleSection = ({ )}
- + {(labels ?? []).map(({ icon, label }, idx) => (
{icon}
{label}
))} + {appendixRight}
{children}
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index 70e182f6..05c770c3 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -1,5 +1,5 @@ -import { useQuery } from '@apollo/react-hooks' -import { makeStyles } from '@material-ui/core/styles' +import { useQuery, useMutation } from '@apollo/react-hooks' +import { Box, makeStyles } from '@material-ui/core' import gql from 'graphql-tag' import * as R from 'ramda' import React, { useState } from 'react' @@ -7,6 +7,7 @@ import { useHistory } from 'react-router-dom' import SearchBox from 'src/components/SearchBox' import SearchFilter from 'src/components/SearchFilter' +import { Link } from 'src/components/buttons' import TitleSection from 'src/components/layout/TitleSection' import baseStyles from 'src/pages/Logs.styles' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' @@ -14,6 +15,7 @@ import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-ou import { fromNamespace, namespaces } from 'src/utils/config' import CustomersList from './CustomersList' +import CreateCustomerModal from './components/CreateCustomerModal' const GET_CUSTOMER_FILTERS = gql` query filters { @@ -49,6 +51,14 @@ const GET_CUSTOMERS = gql` } ` +const CREATE_CUSTOMER = gql` + mutation createCustomer($phoneNumber: String) { + createCustomer(phoneNumber: $phoneNumber) { + phone + } + } +` + const useBaseStyles = makeStyles(baseStyles) const getFiltersObj = filters => @@ -64,6 +74,7 @@ const Customers = () => { const [filteredCustomers, setFilteredCustomers] = useState([]) const [variables, setVariables] = useState({}) const [filters, setFilters] = useState([]) + const [showCreationModal, setShowCreationModal] = useState(false) const { data: customersResponse, @@ -78,6 +89,11 @@ const Customers = () => { GET_CUSTOMER_FILTERS ) + const [createNewCustomer] = useMutation(CREATE_CUSTOMER, { + onCompleted: () => setShowCreationModal(false), + refetchQueries: () => ['configAndCustomers'] + }) + const configData = R.path(['config'])(customersResponse) ?? [] const locale = configData && fromNamespace(namespaces.LOCALE, configData) const customersData = R.sortWith([ @@ -139,7 +155,7 @@ const Customers = () => { +
{ />
} - appendixClassName={baseStyles.buttonsWrapper} + appendixRight={ + + setShowCreationModal(true)}> + Add new user + + + } labels={[ { label: 'Cash-in', icon: }, { label: 'Cash-out', icon: } @@ -169,6 +191,11 @@ const Customers = () => { onClick={handleCustomerClicked} loading={customerLoading} /> + setShowCreationModal(false)} + onSubmit={createNewCustomer} + /> ) } diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js index 43e40c2a..6b9e4167 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomersList.js +++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js @@ -19,7 +19,7 @@ const CustomersList = ({ data, locale, onClick, loading }) => { const elements = [ { header: 'Phone', - width: 175, + width: 199, view: it => getFormattedPhone(it.phone, locale.country) }, { diff --git a/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js new file mode 100644 index 00000000..f36048a6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js @@ -0,0 +1,108 @@ +import { makeStyles } from '@material-ui/core/styles' +import { Field, Form, Formik } from 'formik' +import { PhoneNumberUtil } from 'google-libphonenumber' +import React from 'react' +import * as Yup from 'yup' + +import ErrorMessage from 'src/components/ErrorMessage' +import Modal from 'src/components/Modal' +import { Button } from 'src/components/buttons' +import { TextInput } from 'src/components/inputs/formik' +import { H1 } from 'src/components/typography' +import { spacer, primaryColor, fontPrimary } from 'src/styling/variables' + +const styles = { + modalTitle: { + marginTop: -5, + color: primaryColor, + fontFamily: fontPrimary + }, + footer: { + display: 'flex', + flexDirection: 'row', + margin: [['auto', 0, spacer * 3, 0]] + }, + form: { + display: 'flex', + flexDirection: 'column', + height: '100%' + }, + submit: { + margin: [['auto', 0, 0, 'auto']] + } +} + +const pnUtilInstance = PhoneNumberUtil.getInstance() + +const validationSchema = Yup.object().shape({ + phoneNumber: Yup.string() + .required('A phone number is required') + .test('is-valid-number', 'That is not a valid phone number', value => { + try { + const number = pnUtilInstance.parseAndKeepRawInput(value, 'US') + return pnUtilInstance.isValidNumber(number) + } catch (e) {} + }) +}) + +const initialValues = { + phoneNumber: '' +} + +const useStyles = makeStyles(styles) + +const getErrorMsg = (formikErrors, formikTouched) => { + if (!formikErrors || !formikTouched) return null + if (formikErrors.phoneNumber && formikTouched.phoneNumber) + return formikErrors.phoneNumber + return null +} + +const CreateCustomerModal = ({ showModal, handleClose, onSubmit }) => { + const classes = useStyles() + + return ( + + { + onSubmit({ + variables: { phoneNumber: values.phoneNumber } + }) + }}> + {({ errors, touched }) => ( +
+

Create new customer

+ +
+ {getErrorMsg(errors, touched) && ( + {getErrorMsg(errors, touched)} + )} + +
+ + )} +
+
+ ) +} + +export default CreateCustomerModal