payment-kit 1.13.19 → 1.13.21
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 +3 -3
- package/src/components/checkout/form/phone.tsx +14 -6
- package/src/components/customer/edit.tsx +74 -0
- package/src/components/customer/form.tsx +118 -0
- package/src/components/metadata/editor.tsx +6 -5
- package/src/components/payment-link/after-pay.tsx +1 -1
- package/src/components/payment-link/rename.tsx +7 -4
- package/src/components/product/add-price.tsx +6 -4
- package/src/components/product/create.tsx +6 -4
- package/src/components/product/edit-price.tsx +6 -4
- package/src/components/product/edit.tsx +6 -4
- 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.21",
|
|
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.21",
|
|
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": "9a767485dd2e712fdaf1e636d73556c90f20e2bd"
|
|
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>
|
|
@@ -215,9 +215,9 @@ export default function PaymentForm({
|
|
|
215
215
|
}
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
-
const onAction =
|
|
218
|
+
const onAction = () => {
|
|
219
219
|
if (session.user) {
|
|
220
|
-
|
|
220
|
+
handleSubmit(onSubmit)();
|
|
221
221
|
} else {
|
|
222
222
|
session.login({
|
|
223
223
|
onSuccess: onUserLoggedIn,
|
|
@@ -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,22 +1,30 @@
|
|
|
1
|
+
/* eslint-disable react/prop-types */
|
|
1
2
|
import { InputAdornment, MenuItem, Select, Typography } from '@mui/material';
|
|
3
|
+
import omit from 'lodash/omit';
|
|
2
4
|
import { useEffect } from 'react';
|
|
3
5
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
4
6
|
import { CountryIso2, FlagEmoji, defaultCountries, parseCountry, usePhoneInput } from 'react-international-phone';
|
|
5
7
|
|
|
6
8
|
import FormInput from '../../input';
|
|
7
9
|
|
|
10
|
+
const isValidCountry = (code: string) => defaultCountries.some((x) => x[1] === code);
|
|
11
|
+
|
|
8
12
|
export default function PhoneInput({ ...props }) {
|
|
13
|
+
const countryFieldName = props.countryFieldName || 'billing_address.country';
|
|
14
|
+
|
|
9
15
|
const { control, getValues, setValue } = useFormContext();
|
|
16
|
+
const values = getValues();
|
|
17
|
+
|
|
10
18
|
const { phone, handlePhoneValueChange, inputRef, country, setCountry } = usePhoneInput({
|
|
11
|
-
defaultCountry: 'us',
|
|
12
|
-
value:
|
|
19
|
+
defaultCountry: isValidCountry(values[countryFieldName]) ? values[countryFieldName] : 'us',
|
|
20
|
+
value: values[props.name] || '',
|
|
13
21
|
countries: defaultCountries,
|
|
14
22
|
onChange: (data) => {
|
|
15
|
-
setValue(
|
|
23
|
+
setValue(props.name, data.phone);
|
|
16
24
|
},
|
|
17
25
|
});
|
|
18
26
|
|
|
19
|
-
const userCountry = useWatch({ control, name:
|
|
27
|
+
const userCountry = useWatch({ control, name: countryFieldName });
|
|
20
28
|
|
|
21
29
|
useEffect(() => {
|
|
22
30
|
if (userCountry !== country) {
|
|
@@ -27,7 +35,7 @@ export default function PhoneInput({ ...props }) {
|
|
|
27
35
|
|
|
28
36
|
const onCountryChange = (e: any) => {
|
|
29
37
|
setCountry(e.target.value as CountryIso2);
|
|
30
|
-
setValue(
|
|
38
|
+
setValue(countryFieldName, e.target.value);
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
return (
|
|
@@ -90,7 +98,7 @@ export default function PhoneInput({ ...props }) {
|
|
|
90
98
|
</InputAdornment>
|
|
91
99
|
),
|
|
92
100
|
}}
|
|
93
|
-
{...props}
|
|
101
|
+
{...omit(props, ['countryFieldName'])}
|
|
94
102
|
/>
|
|
95
103
|
);
|
|
96
104
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 onSubmit = () => {
|
|
43
|
+
methods.handleSubmit(async (formData: any) => {
|
|
44
|
+
await onSave(formData);
|
|
45
|
+
methods.reset();
|
|
46
|
+
onCancel(null);
|
|
47
|
+
})();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Dialog
|
|
52
|
+
open
|
|
53
|
+
disableEscapeKeyDown
|
|
54
|
+
fullWidth
|
|
55
|
+
maxWidth="sm"
|
|
56
|
+
onClose={() => onCancel(null)}
|
|
57
|
+
showCloseButton={false}
|
|
58
|
+
title={t('customer.update')}
|
|
59
|
+
actions={
|
|
60
|
+
<Stack direction="row">
|
|
61
|
+
<Button size="small" sx={{ mr: 2 }} onClick={onCancel}>
|
|
62
|
+
{t('common.cancel')}
|
|
63
|
+
</Button>
|
|
64
|
+
<Button variant="contained" color="primary" size="small" disabled={loading} onClick={onSubmit}>
|
|
65
|
+
{loading && <CircularProgress size="small" />} {t('common.save')}
|
|
66
|
+
</Button>
|
|
67
|
+
</Stack>
|
|
68
|
+
}>
|
|
69
|
+
<FormProvider {...methods}>
|
|
70
|
+
<CustomerForm />
|
|
71
|
+
</FormProvider>
|
|
72
|
+
</Dialog>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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 { Controller, useFormContext } from 'react-hook-form';
|
|
7
|
+
import { CountrySelector } from 'react-international-phone';
|
|
8
|
+
import isEmail from 'validator/es/lib/isEmail';
|
|
9
|
+
|
|
10
|
+
import PhoneInput from '../checkout/form/phone';
|
|
11
|
+
import FormInput from '../input';
|
|
12
|
+
|
|
13
|
+
const phoneUtil = PhoneNumberUtil.getInstance();
|
|
14
|
+
|
|
15
|
+
export default function CustomerForm() {
|
|
16
|
+
const { t } = useLocaleContext();
|
|
17
|
+
const { control, setValue } = useFormContext();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Stack direction="column" spacing={1}>
|
|
21
|
+
<Typography component="h6" sx={{ mb: 1, color: 'text.primary', fontWeight: 600 }}>
|
|
22
|
+
{t('checkout.contact')}
|
|
23
|
+
</Typography>
|
|
24
|
+
<FormInput
|
|
25
|
+
name="name"
|
|
26
|
+
variant="outlined"
|
|
27
|
+
errorPosition="right"
|
|
28
|
+
rules={{
|
|
29
|
+
required: t('checkout.required'),
|
|
30
|
+
}}
|
|
31
|
+
InputProps={{
|
|
32
|
+
startAdornment: <InputAdornment position="start">{t('checkout.customer.name')}</InputAdornment>,
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<FormInput
|
|
36
|
+
name="email"
|
|
37
|
+
variant="outlined"
|
|
38
|
+
errorPosition="right"
|
|
39
|
+
rules={{
|
|
40
|
+
required: t('checkout.required'),
|
|
41
|
+
validate: (x) => (isEmail(x) ? true : t('checkout.invalid')),
|
|
42
|
+
}}
|
|
43
|
+
InputProps={{
|
|
44
|
+
startAdornment: <InputAdornment position="start">{t('checkout.customer.email')}</InputAdornment>,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
<PhoneInput
|
|
48
|
+
name="phone"
|
|
49
|
+
variant="outlined"
|
|
50
|
+
countryFieldName="address.country"
|
|
51
|
+
errorPosition="right"
|
|
52
|
+
placeholder={t('checkout.customer.phonePlaceholder')}
|
|
53
|
+
rules={{
|
|
54
|
+
required: t('checkout.required'),
|
|
55
|
+
validate: (x: string) => {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = phoneUtil.parseAndKeepRawInput(x);
|
|
58
|
+
return phoneUtil.isValidNumber(parsed) ? true : t('checkout.invalid');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(err, x);
|
|
61
|
+
return t('checkout.invalid');
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<Typography component="h6" sx={{ mb: 1, color: 'text.primary', fontWeight: 600 }}>
|
|
68
|
+
{t('checkout.billing.required')}
|
|
69
|
+
</Typography>
|
|
70
|
+
<Controller
|
|
71
|
+
name="address.country"
|
|
72
|
+
control={control}
|
|
73
|
+
render={({ field }) => (
|
|
74
|
+
<CountrySelector
|
|
75
|
+
selectedCountry={field.value}
|
|
76
|
+
onSelect={({ iso2 }) => setValue(field.name, iso2)}
|
|
77
|
+
buttonStyle={{}}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
<FormInput
|
|
82
|
+
name="address.state"
|
|
83
|
+
variant="outlined"
|
|
84
|
+
errorPosition="right"
|
|
85
|
+
label={t('checkout.billing.state')}
|
|
86
|
+
placeholder={t('checkout.billing.state')}
|
|
87
|
+
/>
|
|
88
|
+
<FormInput
|
|
89
|
+
name="address.city"
|
|
90
|
+
variant="outlined"
|
|
91
|
+
errorPosition="right"
|
|
92
|
+
label={t('checkout.billing.city')}
|
|
93
|
+
placeholder={t('checkout.billing.city')}
|
|
94
|
+
/>
|
|
95
|
+
<FormInput
|
|
96
|
+
name="address.line1"
|
|
97
|
+
variant="outlined"
|
|
98
|
+
errorPosition="right"
|
|
99
|
+
label={t('checkout.billing.line1')}
|
|
100
|
+
placeholder={t('checkout.billing.line1')}
|
|
101
|
+
/>
|
|
102
|
+
<FormInput
|
|
103
|
+
name="address.line2"
|
|
104
|
+
variant="outlined"
|
|
105
|
+
errorPosition="right"
|
|
106
|
+
label={t('checkout.billing.line2')}
|
|
107
|
+
placeholder={t('checkout.billing.line2')}
|
|
108
|
+
/>
|
|
109
|
+
<FormInput
|
|
110
|
+
name="address.postal_code"
|
|
111
|
+
variant="outlined"
|
|
112
|
+
errorPosition="right"
|
|
113
|
+
label={t('checkout.billing.postal_code')}
|
|
114
|
+
placeholder={t('checkout.billing.postal_code')}
|
|
115
|
+
/>
|
|
116
|
+
</Stack>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -31,11 +31,12 @@ export default function MetadataEditor({
|
|
|
31
31
|
reset();
|
|
32
32
|
onCancel(e);
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const onSubmit = () => {
|
|
35
|
+
handleSubmit(async (formData: any) => {
|
|
36
|
+
await onSave(formData);
|
|
37
|
+
reset();
|
|
38
|
+
onCancel(null);
|
|
39
|
+
})();
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
return (
|
|
@@ -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,10 +26,12 @@ export default function RenamePaymentLink({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const { handleSubmit, reset } = methods;
|
|
29
|
-
const onSubmit =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const onSubmit = () => {
|
|
30
|
+
handleSubmit(async (formData: any) => {
|
|
31
|
+
await onSave(formData);
|
|
32
|
+
reset();
|
|
33
|
+
onCancel(null);
|
|
34
|
+
})();
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
return (
|
|
@@ -54,6 +56,7 @@ export default function RenamePaymentLink({
|
|
|
54
56
|
<FormProvider {...methods}>
|
|
55
57
|
<TextInput
|
|
56
58
|
name="name"
|
|
59
|
+
rules={{ required: true }}
|
|
57
60
|
label={t('admin.paymentLink.name.label')}
|
|
58
61
|
placeholder={t('admin.paymentLink.name.placeholder')}
|
|
59
62
|
autoFocus
|
|
@@ -26,10 +26,12 @@ export default function AddPrice({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const { handleSubmit, reset } = methods;
|
|
29
|
-
const onSubmit =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const onSubmit = () => {
|
|
30
|
+
handleSubmit(async (formData: any) => {
|
|
31
|
+
await onSave(formData);
|
|
32
|
+
reset();
|
|
33
|
+
onCancel(null);
|
|
34
|
+
})();
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
return (
|
|
@@ -57,10 +57,12 @@ export default function CreateProduct({
|
|
|
57
57
|
});
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const onSubmit =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const onSubmit = () => {
|
|
61
|
+
handleSubmit(async (formData: any) => {
|
|
62
|
+
await onCreate(formData);
|
|
63
|
+
await reset();
|
|
64
|
+
await onSave(null);
|
|
65
|
+
})();
|
|
64
66
|
};
|
|
65
67
|
|
|
66
68
|
return (
|
|
@@ -46,10 +46,12 @@ export default function EditPrice({
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
const { handleSubmit, reset } = methods;
|
|
49
|
-
const onSubmit =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const onSubmit = () => {
|
|
50
|
+
handleSubmit(async (formData: any) => {
|
|
51
|
+
await onSave(formData);
|
|
52
|
+
reset();
|
|
53
|
+
onCancel(null);
|
|
54
|
+
})();
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
return (
|
|
@@ -34,10 +34,12 @@ export default function EditProduct({
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
const { handleSubmit, reset } = methods;
|
|
37
|
-
const onSubmit =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const onSubmit = () => {
|
|
38
|
+
handleSubmit(async (formData: any) => {
|
|
39
|
+
await onSave(formData);
|
|
40
|
+
reset();
|
|
41
|
+
onCancel(null);
|
|
42
|
+
})();
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
return (
|
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>
|