Edit Customer via UI
Author:
Siarhei Vaitsiakhovich
Changed on:
4 July 2024
Key Points
- Data Modification: Ensure that users can modify customer data, including the ability to correct typos in existing information.
- Error Handling: Implement error handling procedures in case the edit fails or invalid data is submitted.
- User Experience: Aim for a seamless and intuitive editing process to minimize the risk of user errors.
Prerequisites
Steps
Use Case
To effectively manage customer information within our system, the Edit Customer feature is used. This functionality allows users to update customer details such as contact information, ensuring that the customer database remains accurate and up-to-date.
Solution Approach
The feature can be implemented via the declaration of a custom mutation user action
`updateCustomer`
- Country
- Timezone
- Promotion Opted In
Additionally, the fields have to be reordered to enhance the logical flow of the user interface, making it more intuitive for users to navigate and input their information efficiently. New fields order:
- Title
- First Name
- Last Name
- Primary Email
- Primary Phone
- Country
- Timezone
- Promotion Opted In
- Customer attributes
To save data consistency, options for Timezone and Country selectors can be loaded from settings. All fields have to be prefilled with current user information. Additionally, the field username has to be loaded as a customer reference but hidden to avoid reference change.
Technical Design Overview
To support the business solution design, the following technical areas need to be enhanced:
- New custom UI components (by using Component SDK) - ,
`SettingBaseStringSelector`
`BooleanSelector`
- Declare settings to use it for Country and Timezone selectors
- Update manifest to add a new Edit Customer action (for example, on the Customer Details page)
- Update localization files to modify field labels
Each of these tasks will be described below in steps.
UI Component: SettingBaseStringSelector
`SettingBaseStringSelector`
Create a new file:
`SettingBaseStringSelector.tsx`
1import { FC, useEffect, useState } from 'react';
2import { FormFieldProps } from 'mystique/registry/FieldRegistry';
3import { getSettings } from 'mystique/hooks/getSettings';
4import {
5 FormControl,
6 FormHelperText,
7 InputLabel,
8 MenuItem,
9 Select,
10} from '@material-ui/core';
11import { useAuth } from 'mystique/hooks/useAuth';
12
13const SettingBaseStringSelector: FC<FormFieldProps<any>> = (props) => {
14 const auth = useAuth();
15
16 const settingName = props.extensions?.settingName;
17 const [items, setItems] = useState<string[]>([]);
18 const [currentItem, setCurrentItem] = useState<string | undefined>(
19 props.value,
20 );
21
22 useEffect(() => {
23 if (settingName) {
24 getSettings(
25 { setting: settingName },
26 parseInt(auth.context.current.contextId),
27 ).then((value) => {
28 if (value.setting.status == 'ok') {
29 const list: string[] = [...value.setting.result.value];
30 if (props.value && !list.includes(props.value)) {
31 list.push(props.value);
32 }
33 setItems(list);
34 }
35 });
36 } else {
37 setItems([]);
38 }
39 }, [settingName]);
40
41 useEffect(() => {
42 if (currentItem) {
43 props.onChange(currentItem);
44 } else {
45 props.onChange(undefined);
46 }
47 }, [currentItem]);
48
49 const handleChange = (event: any) => {
50 setCurrentItem(event.target.value as string);
51 };
52
53 const handleOnBlur = () => {
54 props && props.onBlur && props.onBlur();
55 };
56
57 return (
58 <FormControl fullWidth error={!!props.error}>
59 <InputLabel id="string-select-label">{props.label}</InputLabel>
60 <Select
61 labelId="string-select-label"
62 id="string-select"
63 label={props.label}
64 onChange={handleChange}
65 onBlur={handleOnBlur}
66 value={currentItem}
67 >
68 {items.map((value, idx) => {
69 return (
70 <MenuItem key={idx} value={value} selected={currentItem === value}>
71 {value}
72 </MenuItem>
73 );
74 })}
75 </Select>
76 {props.error && <FormHelperText>{props.error}</FormHelperText>}
77 </FormControl>
78 );
79};
80
81export default SettingBaseStringSelector;
Language: typescript
Name: SettingBaseStringSelector.tsx
Description:
SettingBaseStringSelector component implementation example
1import SettingBaseStringSelector from './fields/SettingBaseStringSelector';
2
3
4
5
6
7FieldRegistry.register(
8 ['settingBaseStringSelector'],
9 SettingBaseStringSelector,
10);
Language: typescript
Name: index.tsx
Description:
SettingBaseStringSelector field component registration
UI Component: BooleanSelector
`BooleanSelector`
Create a new file:
`BooleanSelector.tsx`
1import { FC, useEffect, useState } from 'react';
2import { FormFieldProps } from 'mystique/registry/FieldRegistry';
3import {
4 Checkbox,
5 FormControl,
6 FormControlLabel,
7 FormHelperText,
8} from '@material-ui/core';
9
10
11const BooleanSelector: FC<FormFieldProps<any>> = (props) => {
12 const [checked, setChecked] = useState<boolean>(
13 props.extensions?.checked || props.value || false,
14 );
15
16 useEffect(() => {
17 props.onChange(`${checked}`);
18 }, [checked]);
19
20 const handleChange = (event: any) => {
21 setChecked(event.target.checked as boolean);
22 };
23
24 const handleOnBlur = () => {
25 props && props.onBlur && props.onBlur();
26 };
27
28 return (
29 <FormControl fullWidth error={!!props.error}>
30 <FormControlLabel
31 control={
32 <Checkbox
33 id="checkbox-selector"
34 onChange={handleChange}
35 onBlur={handleOnBlur}
36 checked={checked}
37 />
38 }
39 label={props.label}
40 />
41 {props.error && <FormHelperText>{props.error}</FormHelperText>}
42 </FormControl>
43 );
44};
45
46export default BooleanSelector;
Language: typescript
Name: BooleanSelector.tsx
Description:
BooleanSelector component implementation example
1import BooleanSelector from './fields/BooleanSelector';
2
3
4
5FieldRegistry.register(['booleanSelector'], BooleanSelector);
Language: typescript
Name: index.tsx
Description:
BooleanSelector field component registration
Setting: EDIT_LOCATION_COUNTRIES
`EDIT_LOCATION_COUNTRIES`
Name | EDIT_LOCATION_COUNTRIES |
Value Type | JSON |
Context | ACCOUNT or RETAILER |
Context ID | 0 or Retailer ID |
JSON Value |
|
Create Setting
1POST: {{fluentApiHost}}/graphql
2
3## create a postman environment variable:
4## Variable: json_value
5## initial val + current value:
6[
7 "Australia",
8 "Canada",
9 "France",
10 "Germany",
11 "United Kingdom",
12 "United States"
13]
14
15
16# GraphQL variables:
17{
18 "retailerId": {{retailer_id}},
19 "lobValue" : {{json_value}}
20}
21
22
23#GraphQL Query:
24mutation CreateSetting($retailerId:Int! , $lobValue:Json) {
25createSetting(input: {
26 name: "EDIT_LOCATION_COUNTRIES",
27 valueType: "JSON",
28 lobValue:$lobValue ,
29 context: "RETAILER",
30 contextId:$retailerId}) {
31 id
32 name
33 }
34}
Language: graphqlschema
Name: create setting EDIT_LOCATION_COUNTRIES
Description:
[Warning: empty required content area]Update Setting
1POST: {{fluentApiHost}}/graphql
2
3## create a postman environment variable:
4## Variable: json_value
5## initial val + current value:
6[
7 "Australia",
8 "Canada",
9 "France",
10 "Germany",
11 "United Kingdom",
12 "United States"
13]
14
15
16# GraphQL variables:
17{
18 "retailerId": {{retailer_id}},
19 "lobValue" : {{json_value}}
20}
21
22
23#GraphQL Query:
24mutation updateSetting($retailerId:Int! , $lobValue:Json) {
25updateSetting(input: {
26 id: 5001471,
27 name: "EDIT_LOCATION_COUNTRIES",
28 valueType: "JSON",
29 lobValue: $lobValue,
30 context: "RETAILER",
31 contextId: $retailerId}) {
32 id
33 name
34 }
35}
36
Language: graphqlschema
Name: update setting EDIT_LOCATION_COUNTRIES
Description:
[Warning: empty required content area]
Setting: EDIT_LOCATION_TIME_ZONES
`EDIT_LOCATION_TIME_ZONES`
Name | EDIT_LOCATION_TIME_ZONES |
Value Type | JSON |
Context | ACCOUNT or RETAILER |
Context ID | 0 or Retailer ID |
JSON Value |
|
Create Setting
1POST: {{fluentApiHost}}/graphql
2
3## create a postman environment variable:
4## Variable: json_value
5## initial val + current value:
6[
7 "GMT",
8 "UTC",
9 "ECT",
10 "EET",
11 "ACT",
12 "AET",
13 "HST",
14 "AST",
15 "PST",
16 "PNT",
17 "MST",
18 "CST",
19 "EST",
20 "IET",
21 "PRT",
22 "CNT"
23]
24
25
26# GraphQL variables:
27{
28 "retailerId": {{retailer_id}},
29 "lobValue" : {{json_value}}
30}
31
32
33#GraphQL Query:
34mutation CreateSetting($retailerId:Int! , $lobValue:Json) {
35createSetting(input: {
36 name: "EDIT_LOCATION_TIME_ZONES",
37 valueType: "JSON",
38 lobValue:$lobValue ,
39 context: "RETAILER",
40 contextId:$retailerId}) {
41 id
42 name
43 }
44}
Language: graphqlschema
Name: create setting EDIT_LOCATION_TIME_ZONES
Description:
[Warning: empty required content area]Update Setting
1POST: {{fluentApiHost}}/graphql
2
3## create a postman environment variable:
4## Variable: json_value
5## initial val + current value:
6[
7 "GMT",
8 "UTC",
9 "ECT",
10 "EET",
11 "ACT",
12 "AET",
13 "HST",
14 "AST",
15 "PST",
16 "PNT",
17 "MST",
18 "CST",
19 "EST",
20 "IET",
21 "PRT",
22 "CNT"
23]
24
25
26# GraphQL variables:
27{
28 "retailerId": {{retailer_id}},
29 "lobValue" : {{json_value}}
30}
31
32
33#GraphQL Query:
34mutation updateSetting($retailerId:Int! , $lobValue:Json) {
35updateSetting(input: {
36 id: 5001471,
37 name: "EDIT_LOCATION_TIME_ZONES",
38 valueType: "JSON",
39 lobValue: $lobValue,
40 context: "RETAILER",
41 contextId: $retailerId}) {
42 id
43 name
44 }
45}
46
Language: graphqlschema
Name: update setting EDIT_LOCATION_TIME_ZONES
Description:
[Warning: empty required content area]
Manifest changes
`Edit Customer`
`updateCustomer`
1{
2 "type": "mutation",
3 "label": "i18n:fc.om.customers.updateCustomer.button.Label",
4 "name": "updateCustomer",
5 "args": {
6 "ref": "{{customerById.username}}"
7 },
8 "overrides": {
9 "title": {
10 "sortPrefix": 11
11 },
12 "firstName": {
13 "sortPrefix": 12
14 },
15 "lastName": {
16 "sortPrefix": 13
17 },
18 "primaryEmail": {
19 "sortPrefix": 14
20 },
21 "primaryPhone": {
22 "sortPrefix": 15
23 },
24 "country": {
25 "sortPrefix": 17,
26 "component": "settingBaseStringSelector",
27 "extensions": {
28 "settingName": "EDIT_LOCATION_COUNTRIES"
29 }
30 },
31 "timezone": {
32 "sortPrefix": 18,
33 "component": "settingBaseStringSelector",
34 "extensions": {
35 "settingName": "EDIT_LOCATION_TIME_ZONES"
36 }
37 },
38 "promotionOptIn": {
39 "component": "booleanSelector",
40 "sortPrefix": 19
41 },
42 "attributes": {
43 "sortPrefix": 21
44 },
45 "username": {
46 "sortPrefix": 22,
47 "component": "input",
48 "options": [
49 {
50 "label": "current Username",
51 "value": "{{customerById.username}}"
52 }
53 ]
54 }
55 }
56}
Language: json
Name: Edit Customer mutation user action
Description:
Edit Customer mutation user action declaration example. Property
`sortPrefix`
Update Language Setting
Localization of fields is done by creating localization entries to mutation fields according to name convention (see Languages and Localisation, chapter 'Adding new Mutation Actions').
1{
2 "translation": {
3 "fc.om.customers.updateCustomer.button.Label": "Update Customer",
4 "fc.gql.customer.title": "Title",
5 "fc.gql.customer.firstName": "First Name",
6 "fc.gql.customer.lastName": "Last Name",
7 "fc.gql.customer.username": "Username",
8 "fc.gql.customer.primaryEmail": "Primary Email",
9 "fc.gql.customer.primaryPhone": "Primary Phone",
10 "fc.gql.customer.country": "Country",
11 "fc.gql.customer.timezone": "Timezone",
12 "fc.gql.customer.promotionOptIn": "Customer has opted to receive promotions",
13 "fc.gql.customer.retailer.label": "Retailer"
14 }
15}
Language: json
Name: New localization entries
Description:
Example of mutation field labels localisation with using name convention
Result
After all steps,
`Edit Customer`

