feat: add customer creation modal
This commit is contained in:
parent
cfb360ab4e
commit
94eed283cb
8 changed files with 153 additions and 9 deletions
|
|
@ -49,7 +49,8 @@ const resolvers = {
|
|||
},
|
||||
deleteCustomerNote: (...[, { noteId }]) => {
|
||||
return customerNotes.deleteCustomerNote(noteId)
|
||||
}
|
||||
},
|
||||
createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
|||
5
new-lamassu-admin/package-lock.json
generated
5
new-lamassu-admin/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ const TitleSection = ({
|
|||
buttons = [],
|
||||
children,
|
||||
appendix,
|
||||
appendixClassName
|
||||
appendixRight
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<div className={classnames(classes.titleWrapper, className)}>
|
||||
<div className={classes.titleAndButtonsContainer}>
|
||||
<Title>{title}</Title>
|
||||
{appendix && <div className={appendixClassName}>{appendix}</div>}
|
||||
{!!appendix && appendix}
|
||||
{error && (
|
||||
<ErrorMessage className={classes.error}>Failed to save</ErrorMessage>
|
||||
)}
|
||||
|
|
@ -46,13 +46,14 @@ const TitleSection = ({
|
|||
</>
|
||||
)}
|
||||
</div>
|
||||
<Box display="flex" flexDirection="row">
|
||||
<Box display="flex" flexDirection="row" alignItems="center">
|
||||
{(labels ?? []).map(({ icon, label }, idx) => (
|
||||
<Box key={idx} display="flex" alignItems="center">
|
||||
<div className={classes.icon}>{icon}</div>
|
||||
<Label1 className={classes.label}>{label}</Label1>
|
||||
</Box>
|
||||
))}
|
||||
{appendixRight}
|
||||
</Box>
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
|||
<TitleSection
|
||||
title="Customers"
|
||||
appendix={
|
||||
<div>
|
||||
<div className={baseStyles.buttonsWrapper}>
|
||||
<SearchBox
|
||||
loading={loadingFilters}
|
||||
filters={filters}
|
||||
|
|
@ -149,7 +165,13 @@ const Customers = () => {
|
|||
/>
|
||||
</div>
|
||||
}
|
||||
appendixClassName={baseStyles.buttonsWrapper}
|
||||
appendixRight={
|
||||
<Box display="flex">
|
||||
<Link color="primary" onClick={() => setShowCreationModal(true)}>
|
||||
Add new user
|
||||
</Link>
|
||||
</Box>
|
||||
}
|
||||
labels={[
|
||||
{ label: 'Cash-in', icon: <TxInIcon /> },
|
||||
{ label: 'Cash-out', icon: <TxOutIcon /> }
|
||||
|
|
@ -169,6 +191,11 @@ const Customers = () => {
|
|||
onClick={handleCustomerClicked}
|
||||
loading={customerLoading}
|
||||
/>
|
||||
<CreateCustomerModal
|
||||
showModal={showCreationModal}
|
||||
handleClose={() => setShowCreationModal(false)}
|
||||
onSubmit={createNewCustomer}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Modal
|
||||
closeOnBackdropClick={true}
|
||||
width={600}
|
||||
height={300}
|
||||
handleClose={handleClose}
|
||||
open={showModal}>
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
initialValues={initialValues}
|
||||
validateOnChange={false}
|
||||
onSubmit={values => {
|
||||
onSubmit({
|
||||
variables: { phoneNumber: values.phoneNumber }
|
||||
})
|
||||
}}>
|
||||
{({ errors, touched }) => (
|
||||
<Form id="customer-registration-form" className={classes.form}>
|
||||
<H1 className={classes.modalTitle}>Create new customer</H1>
|
||||
<Field
|
||||
component={TextInput}
|
||||
name="phoneNumber"
|
||||
width={338}
|
||||
autoFocus
|
||||
label="Phone number"
|
||||
/>
|
||||
<div className={classes.footer}>
|
||||
{getErrorMsg(errors, touched) && (
|
||||
<ErrorMessage>{getErrorMsg(errors, touched)}</ErrorMessage>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
form="customer-registration-form"
|
||||
className={classes.submit}>
|
||||
Finish
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCustomerModal
|
||||
Loading…
Add table
Add a link
Reference in a new issue