payment-kit 1.13.19 → 1.13.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/src/routes/customers.ts +32 -0
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/checkout/form/address.tsx +7 -9
- package/src/components/checkout/form/index.tsx +1 -1
- package/src/components/checkout/form/phone.tsx +11 -5
- package/src/components/customer/edit.tsx +73 -0
- package/src/components/customer/form.tsx +104 -0
- package/src/components/metadata/editor.tsx +2 -3
- package/src/components/payment-link/after-pay.tsx +1 -1
- package/src/components/payment-link/rename.tsx +2 -2
- package/src/locales/en.tsx +1 -0
- package/src/pages/admin/customers/customers/detail.tsx +30 -8
- package/src/pages/customer/index.tsx +24 -0
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { user } from '@blocklet/sdk/lib/middlewares';
|
|
2
2
|
import { Router } from 'express';
|
|
3
3
|
import Joi from 'joi';
|
|
4
|
+
import pick from 'lodash/pick';
|
|
4
5
|
import type { WhereOptions } from 'sequelize';
|
|
5
6
|
|
|
6
7
|
import { authenticate } from '../libs/security';
|
|
8
|
+
import { formatMetadata } from '../libs/util';
|
|
7
9
|
import { Customer } from '../store/models/customer';
|
|
8
10
|
|
|
9
11
|
const router = Router();
|
|
10
12
|
const auth = authenticate<Customer>({ component: true, roles: ['owner', 'admin'] });
|
|
13
|
+
const authPortal = authenticate<Customer>({
|
|
14
|
+
component: true,
|
|
15
|
+
roles: ['owner', 'admin'],
|
|
16
|
+
record: {
|
|
17
|
+
// @ts-ignore
|
|
18
|
+
model: Customer,
|
|
19
|
+
field: 'id',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
11
22
|
|
|
12
23
|
const schema = Joi.object<{
|
|
13
24
|
page: number;
|
|
@@ -67,4 +78,25 @@ router.get('/:id', auth, async (req, res) => {
|
|
|
67
78
|
}
|
|
68
79
|
});
|
|
69
80
|
|
|
81
|
+
// eslint-disable-next-line consistent-return
|
|
82
|
+
router.put('/:id', authPortal, async (req, res) => {
|
|
83
|
+
try {
|
|
84
|
+
const doc = await Customer.findByPkOrDid(req.params.id as string);
|
|
85
|
+
if (!doc) {
|
|
86
|
+
return res.status(404).json({ error: 'Customer not found' });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const raw = pick(req.body, ['metadata', 'name', 'email', 'phone', 'address']);
|
|
90
|
+
if (raw.metadata) {
|
|
91
|
+
raw.metadata = formatMetadata(raw.metadata);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await doc.update(raw);
|
|
95
|
+
res.json(doc);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(err);
|
|
98
|
+
res.json(null);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
70
102
|
export default router;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.20",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"devDependencies": {
|
|
101
101
|
"@arcblock/eslint-config": "^0.2.4",
|
|
102
102
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
103
|
-
"@did-pay/types": "1.13.
|
|
103
|
+
"@did-pay/types": "1.13.20",
|
|
104
104
|
"@types/cookie-parser": "^1.4.4",
|
|
105
105
|
"@types/cors": "^2.8.14",
|
|
106
106
|
"@types/dotenv-flow": "^3.3.1",
|
|
@@ -137,5 +137,5 @@
|
|
|
137
137
|
"parser": "typescript"
|
|
138
138
|
}
|
|
139
139
|
},
|
|
140
|
-
"gitHead": "
|
|
140
|
+
"gitHead": "60955ccbcacc231990e00bacdc271a7c428adaf6"
|
|
141
141
|
}
|
|
@@ -61,15 +61,13 @@ export default function AddressForm({ mode }: Props) {
|
|
|
61
61
|
variant="outlined"
|
|
62
62
|
placeholder={t('checkout.billing.line1')}
|
|
63
63
|
/>
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/>
|
|
72
|
-
</Stack>
|
|
64
|
+
<FormInput
|
|
65
|
+
name="billing_address.city"
|
|
66
|
+
rules={{ required: t('checkout.required') }}
|
|
67
|
+
errorPosition="right"
|
|
68
|
+
variant="outlined"
|
|
69
|
+
placeholder={t('checkout.billing.city')}
|
|
70
|
+
/>
|
|
73
71
|
</Stack>
|
|
74
72
|
</Stack>
|
|
75
73
|
</Fade>
|
|
@@ -270,7 +270,7 @@ export default function PaymentForm({
|
|
|
270
270
|
errorPosition="right"
|
|
271
271
|
rules={{
|
|
272
272
|
required: t('checkout.required'),
|
|
273
|
-
validate: (x) => (isEmail(x) ? true : t('checkout.
|
|
273
|
+
validate: (x) => (isEmail(x) ? true : t('checkout.invalid')),
|
|
274
274
|
}}
|
|
275
275
|
InputProps={{
|
|
276
276
|
startAdornment: <InputAdornment position="start">{t('checkout.customer.email')}</InputAdornment>,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
1
2
|
import { InputAdornment, MenuItem, Select, Typography } from '@mui/material';
|
|
2
3
|
import { useEffect } from 'react';
|
|
3
4
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
@@ -5,18 +6,23 @@ import { CountryIso2, FlagEmoji, defaultCountries, parseCountry, usePhoneInput }
|
|
|
5
6
|
|
|
6
7
|
import FormInput from '../../input';
|
|
7
8
|
|
|
9
|
+
const isValidCountry = (code: string) => defaultCountries.some((x) => x[1] === code);
|
|
10
|
+
|
|
8
11
|
export default function PhoneInput({ ...props }) {
|
|
12
|
+
const countryFieldName = props.countryFieldName || 'billing_address.country';
|
|
13
|
+
|
|
9
14
|
const { control, getValues, setValue } = useFormContext();
|
|
15
|
+
const values = getValues();
|
|
10
16
|
const { phone, handlePhoneValueChange, inputRef, country, setCountry } = usePhoneInput({
|
|
11
|
-
defaultCountry: 'us',
|
|
12
|
-
value:
|
|
17
|
+
defaultCountry: isValidCountry(values[countryFieldName]) ? values[countryFieldName] : 'us',
|
|
18
|
+
value: values[props.name] || '',
|
|
13
19
|
countries: defaultCountries,
|
|
14
20
|
onChange: (data) => {
|
|
15
|
-
setValue(
|
|
21
|
+
setValue(props.name, data.phone);
|
|
16
22
|
},
|
|
17
23
|
});
|
|
18
24
|
|
|
19
|
-
const userCountry = useWatch({ control, name:
|
|
25
|
+
const userCountry = useWatch({ control, name: countryFieldName });
|
|
20
26
|
|
|
21
27
|
useEffect(() => {
|
|
22
28
|
if (userCountry !== country) {
|
|
@@ -27,7 +33,7 @@ export default function PhoneInput({ ...props }) {
|
|
|
27
33
|
|
|
28
34
|
const onCountryChange = (e: any) => {
|
|
29
35
|
setCountry(e.target.value as CountryIso2);
|
|
30
|
-
setValue(
|
|
36
|
+
setValue(countryFieldName, e.target.value);
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
return (
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import type { TCustomer } from '@did-pay/types';
|
|
4
|
+
import { Button, CircularProgress, Stack } from '@mui/material';
|
|
5
|
+
import type { EventHandler } from 'react';
|
|
6
|
+
import { FormProvider, useForm } from 'react-hook-form';
|
|
7
|
+
|
|
8
|
+
import CustomerForm from './form';
|
|
9
|
+
|
|
10
|
+
export default function EditCustomer({
|
|
11
|
+
data,
|
|
12
|
+
loading,
|
|
13
|
+
onSave,
|
|
14
|
+
onCancel,
|
|
15
|
+
}: {
|
|
16
|
+
data: TCustomer;
|
|
17
|
+
loading: boolean;
|
|
18
|
+
onSave: EventHandler<any>;
|
|
19
|
+
onCancel: EventHandler<any>;
|
|
20
|
+
}) {
|
|
21
|
+
const { t } = useLocaleContext();
|
|
22
|
+
const methods = useForm<TCustomer>({
|
|
23
|
+
defaultValues: {
|
|
24
|
+
name: data.name || '',
|
|
25
|
+
email: data.email || '',
|
|
26
|
+
phone: data.phone || '',
|
|
27
|
+
address: Object.assign(
|
|
28
|
+
{
|
|
29
|
+
country: '',
|
|
30
|
+
state: '',
|
|
31
|
+
city: '',
|
|
32
|
+
line1: '',
|
|
33
|
+
line2: '',
|
|
34
|
+
postal_code: '',
|
|
35
|
+
},
|
|
36
|
+
data.address || {},
|
|
37
|
+
{ country: data.address?.country || 'us' }
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { handleSubmit, reset } = methods;
|
|
43
|
+
const onSubmit = async () => {
|
|
44
|
+
await handleSubmit(onSave)();
|
|
45
|
+
reset();
|
|
46
|
+
onCancel(null);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Dialog
|
|
51
|
+
open
|
|
52
|
+
disableEscapeKeyDown
|
|
53
|
+
fullWidth
|
|
54
|
+
maxWidth="sm"
|
|
55
|
+
onClose={() => onCancel(null)}
|
|
56
|
+
showCloseButton={false}
|
|
57
|
+
title={t('customer.update')}
|
|
58
|
+
actions={
|
|
59
|
+
<Stack direction="row">
|
|
60
|
+
<Button size="small" sx={{ mr: 2 }} onClick={onCancel}>
|
|
61
|
+
{t('common.cancel')}
|
|
62
|
+
</Button>
|
|
63
|
+
<Button variant="contained" color="primary" size="small" disabled={loading} onClick={onSubmit}>
|
|
64
|
+
{loading && <CircularProgress size="small" />} {t('common.save')}
|
|
65
|
+
</Button>
|
|
66
|
+
</Stack>
|
|
67
|
+
}>
|
|
68
|
+
<FormProvider {...methods}>
|
|
69
|
+
<CustomerForm />
|
|
70
|
+
</FormProvider>
|
|
71
|
+
</Dialog>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import 'react-international-phone/style.css';
|
|
2
|
+
|
|
3
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
|
+
import { InputAdornment, Stack, Typography } from '@mui/material';
|
|
5
|
+
import { PhoneNumberUtil } from 'google-libphonenumber';
|
|
6
|
+
import isEmail from 'validator/es/lib/isEmail';
|
|
7
|
+
|
|
8
|
+
import PhoneInput from '../checkout/form/phone';
|
|
9
|
+
import FormInput from '../input';
|
|
10
|
+
|
|
11
|
+
const phoneUtil = PhoneNumberUtil.getInstance();
|
|
12
|
+
|
|
13
|
+
export default function CustomerForm() {
|
|
14
|
+
const { t } = useLocaleContext();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Stack direction="column" spacing={3}>
|
|
18
|
+
<Typography component="h6" sx={{ mb: 1, color: 'text.primary', fontWeight: 600 }}>
|
|
19
|
+
{t('checkout.contact')}
|
|
20
|
+
</Typography>
|
|
21
|
+
<FormInput
|
|
22
|
+
name="name"
|
|
23
|
+
variant="outlined"
|
|
24
|
+
errorPosition="right"
|
|
25
|
+
rules={{
|
|
26
|
+
required: t('checkout.required'),
|
|
27
|
+
}}
|
|
28
|
+
InputProps={{
|
|
29
|
+
startAdornment: <InputAdornment position="start">{t('checkout.customer.name')}</InputAdornment>,
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
<FormInput
|
|
33
|
+
name="email"
|
|
34
|
+
variant="outlined"
|
|
35
|
+
errorPosition="right"
|
|
36
|
+
rules={{
|
|
37
|
+
required: t('checkout.required'),
|
|
38
|
+
validate: (x) => (isEmail(x) ? true : t('checkout.invalid')),
|
|
39
|
+
}}
|
|
40
|
+
InputProps={{
|
|
41
|
+
startAdornment: <InputAdornment position="start">{t('checkout.customer.email')}</InputAdornment>,
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
<PhoneInput
|
|
45
|
+
name="phone"
|
|
46
|
+
variant="outlined"
|
|
47
|
+
countryFieldName="address.country"
|
|
48
|
+
errorPosition="right"
|
|
49
|
+
placeholder={t('checkout.customer.phonePlaceholder')}
|
|
50
|
+
rules={{
|
|
51
|
+
required: t('checkout.required'),
|
|
52
|
+
validate: (x: string) => {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = phoneUtil.parseAndKeepRawInput(x);
|
|
55
|
+
return phoneUtil.isValidNumber(parsed) ? true : t('checkout.invalid');
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(err, x);
|
|
58
|
+
return t('checkout.invalid');
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
<Typography component="h6" sx={{ mb: 1, color: 'text.primary', fontWeight: 600 }}>
|
|
65
|
+
{t('checkout.billing.required')}
|
|
66
|
+
</Typography>
|
|
67
|
+
<FormInput
|
|
68
|
+
name="address.state"
|
|
69
|
+
rules={{}}
|
|
70
|
+
variant="outlined"
|
|
71
|
+
errorPosition="right"
|
|
72
|
+
placeholder={t('checkout.billing.state')}
|
|
73
|
+
/>
|
|
74
|
+
<FormInput
|
|
75
|
+
name="address.city"
|
|
76
|
+
rules={{}}
|
|
77
|
+
variant="outlined"
|
|
78
|
+
errorPosition="right"
|
|
79
|
+
placeholder={t('checkout.billing.city')}
|
|
80
|
+
/>
|
|
81
|
+
<FormInput
|
|
82
|
+
name="address.line1"
|
|
83
|
+
rules={{}}
|
|
84
|
+
variant="outlined"
|
|
85
|
+
errorPosition="right"
|
|
86
|
+
placeholder={t('checkout.billing.line1')}
|
|
87
|
+
/>
|
|
88
|
+
<FormInput
|
|
89
|
+
name="address.line2"
|
|
90
|
+
rules={{}}
|
|
91
|
+
variant="outlined"
|
|
92
|
+
errorPosition="right"
|
|
93
|
+
placeholder={t('checkout.billing.line2')}
|
|
94
|
+
/>
|
|
95
|
+
<FormInput
|
|
96
|
+
name="address.postal_code"
|
|
97
|
+
errorPosition="right"
|
|
98
|
+
rules={{ required: t('checkout.required') }}
|
|
99
|
+
variant="outlined"
|
|
100
|
+
placeholder={t('checkout.billing.postal_code')}
|
|
101
|
+
/>
|
|
102
|
+
</Stack>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -31,9 +31,8 @@ export default function MetadataEditor({
|
|
|
31
31
|
reset();
|
|
32
32
|
onCancel(e);
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
await handleSubmit(onSave)(data);
|
|
34
|
+
const onSubmit = async () => {
|
|
35
|
+
await handleSubmit(onSave)();
|
|
37
36
|
reset();
|
|
38
37
|
onCancel(null);
|
|
39
38
|
};
|
|
@@ -9,7 +9,7 @@ export default function AfterPay() {
|
|
|
9
9
|
const type = useWatch({ control, name: 'after_completion.type' });
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<Stack spacing={2}>
|
|
12
|
+
<Stack spacing={2} sx={{ width: '100%' }}>
|
|
13
13
|
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
|
14
14
|
{t('admin.paymentLink.confirmPage')}
|
|
15
15
|
</Typography>
|
|
@@ -26,8 +26,8 @@ export default function RenamePaymentLink({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const { handleSubmit, reset } = methods;
|
|
29
|
-
const onSubmit = async (
|
|
30
|
-
await handleSubmit(onSave)(
|
|
29
|
+
const onSubmit = async () => {
|
|
30
|
+
await handleSubmit(onSave)();
|
|
31
31
|
reset();
|
|
32
32
|
onCancel(null);
|
|
33
33
|
};
|
package/src/locales/en.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
|
|
13
13
|
|
|
14
14
|
import Copyable from '../../../../components/copyable';
|
|
15
15
|
import CustomerActions from '../../../../components/customer/actions';
|
|
16
|
+
import EditCustomer from '../../../../components/customer/edit';
|
|
16
17
|
import EventList from '../../../../components/event/list';
|
|
17
18
|
import InfoMetric from '../../../../components/info-metric';
|
|
18
19
|
import InfoRow from '../../../../components/info-row';
|
|
@@ -37,12 +38,11 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
37
38
|
},
|
|
38
39
|
editing: {
|
|
39
40
|
metadata: false,
|
|
40
|
-
|
|
41
|
+
customer: false,
|
|
41
42
|
},
|
|
42
43
|
loading: {
|
|
43
44
|
metadata: false,
|
|
44
|
-
|
|
45
|
-
product: false,
|
|
45
|
+
customer: false,
|
|
46
46
|
},
|
|
47
47
|
});
|
|
48
48
|
|
|
@@ -56,9 +56,9 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
56
56
|
return <CircularProgress />;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const
|
|
59
|
+
const onUpdateMetadata = async (updates: TCustomerExpanded) => {
|
|
60
60
|
try {
|
|
61
|
-
setState((prev) => ({ loading: { ...prev.loading,
|
|
61
|
+
setState((prev) => ({ loading: { ...prev.loading, metadata: true } }));
|
|
62
62
|
await api.put(`/api/customers/${props.id}`, updates).then((res) => res.data);
|
|
63
63
|
Toast.success(t('common.saved'));
|
|
64
64
|
runAsync();
|
|
@@ -66,11 +66,24 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
66
66
|
console.error(err);
|
|
67
67
|
Toast.error(formatError(err));
|
|
68
68
|
} finally {
|
|
69
|
-
setState((prev) => ({ loading: { ...prev.loading,
|
|
69
|
+
setState((prev) => ({ loading: { ...prev.loading, metadata: false } }));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const onUpdateInfo = async (updates: TCustomerExpanded) => {
|
|
74
|
+
try {
|
|
75
|
+
setState((prev) => ({ loading: { ...prev.loading, customer: true } }));
|
|
76
|
+
await api.put(`/api/customers/${props.id}`, updates).then((res) => res.data);
|
|
77
|
+
Toast.success(t('common.saved'));
|
|
78
|
+
runAsync();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(err);
|
|
81
|
+
Toast.error(formatError(err));
|
|
82
|
+
} finally {
|
|
83
|
+
setState((prev) => ({ loading: { ...prev.loading, customer: false } }));
|
|
70
84
|
}
|
|
71
85
|
};
|
|
72
86
|
|
|
73
|
-
const onUpdateMetadata = createUpdater('metadata');
|
|
74
87
|
const onChange = (action: string) => {
|
|
75
88
|
if (action === 'remove') {
|
|
76
89
|
navigate('/admin/customers');
|
|
@@ -122,7 +135,8 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
122
135
|
variant="outlined"
|
|
123
136
|
color="inherit"
|
|
124
137
|
size="small"
|
|
125
|
-
|
|
138
|
+
disabled={state.editing.customer}
|
|
139
|
+
onClick={() => setState((prev) => ({ editing: { ...prev.editing, customer: true } }))}>
|
|
126
140
|
<Edit fontSize="small" sx={{ mr: 0.5 }} />
|
|
127
141
|
{t('common.edit')}
|
|
128
142
|
</Button>
|
|
@@ -157,6 +171,14 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
157
171
|
</Stack>
|
|
158
172
|
}
|
|
159
173
|
/>
|
|
174
|
+
{state.editing.customer && (
|
|
175
|
+
<EditCustomer
|
|
176
|
+
data={data}
|
|
177
|
+
loading={state.loading.customer}
|
|
178
|
+
onSave={onUpdateInfo}
|
|
179
|
+
onCancel={() => setState((prev) => ({ editing: { ...prev.editing, customer: false } }))}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
160
182
|
</Stack>
|
|
161
183
|
</Box>
|
|
162
184
|
<Box className="section">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
2
3
|
import type { TCustomerExpanded } from '@did-pay/types';
|
|
3
4
|
import { Edit } from '@mui/icons-material';
|
|
4
5
|
import { Alert, Box, Button, CircularProgress, Grid, Stack } from '@mui/material';
|
|
@@ -6,6 +7,7 @@ import { styled } from '@mui/system';
|
|
|
6
7
|
import { useRequest, useSetState } from 'ahooks';
|
|
7
8
|
import { FlagEmoji } from 'react-international-phone';
|
|
8
9
|
|
|
10
|
+
import EditCustomer from '../../components/customer/edit';
|
|
9
11
|
import InfoRow from '../../components/info-row';
|
|
10
12
|
import Layout from '../../components/layout';
|
|
11
13
|
import CustomerInvoiceList from '../../components/portal/invoice/list';
|
|
@@ -45,6 +47,20 @@ export default function CustomerHome() {
|
|
|
45
47
|
);
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
const onUpdateInfo = async (updates: TCustomerExpanded) => {
|
|
51
|
+
try {
|
|
52
|
+
setState({ loading: true });
|
|
53
|
+
await api.put(`/api/customers/${data.id}`, updates).then((res) => res.data);
|
|
54
|
+
Toast.success(t('common.saved'));
|
|
55
|
+
runAsync();
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(err);
|
|
58
|
+
Toast.error(formatError(err));
|
|
59
|
+
} finally {
|
|
60
|
+
setState({ loading: false });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
48
64
|
return (
|
|
49
65
|
<Layout>
|
|
50
66
|
<Grid container spacing={5}>
|
|
@@ -103,6 +119,14 @@ export default function CustomerHome() {
|
|
|
103
119
|
value={data.address?.postal_code}
|
|
104
120
|
/>
|
|
105
121
|
</Stack>
|
|
122
|
+
{state.editing && (
|
|
123
|
+
<EditCustomer
|
|
124
|
+
data={data}
|
|
125
|
+
loading={state.loading}
|
|
126
|
+
onSave={onUpdateInfo}
|
|
127
|
+
onCancel={() => setState({ editing: false })}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
106
130
|
</Box>
|
|
107
131
|
</Root>
|
|
108
132
|
</Grid>
|