Create Customer via UI
Author:
Siarhei Vaitsiakhovich
Changed on:
4 July 2024
Key Points
- Comprehensive Data Entry: Ensure that all necessary customer information is captured accurately to create a complete profile. This includes personal details, contact information, and any specific requirements or preferences.
- Validation and Verification: Implement validation checks to prevent errors during the data entry process. Verify that mandatory fields are not left blank.
- User Experience: Design the customer creation process to be user-friendly. Provide clear instructions and feedback during the process to enhance the user's experience and minimize the likelihood of errors.
Prerequisites
Steps
Use Case
The 'Create Customer' feature is designed to streamline the process of adding new customers to the system. A new customer can be added to the system by filling out a registration form with their personal and contact information.
Solution Approach
The feature can be implemented via the declaration of a custom mutation user action
`createCustomer`
- Country
- Timezone
- Promotion Opted In
- Retailer
Additionally, the components 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
- Username
- Primary Email
- Primary Phone
- Department
- Country
- Timezone
- Promotion Opted In
- Retailer
- Custom attributes
To save data consistency, options for Timezone and Country selectors have to be loaded from settings.
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`
`RetailerSelector`
- Declare settings to use it for Country and Timezone selectors
- Update manifest to add a new Create Customer action (for example on the Customers List 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 data-testId={`setting-${settingName}-selector`}
64 label={props.label}
65 onChange={handleChange}
66 onBlur={handleOnBlur}
67 value={currentItem}
68 >
69 {items.map((value, idx) => {
70 return (
71 <MenuItem data-testId={`setting-${settingName}-select-${idx}`} key={idx} value={value} selected={currentItem === value}>
72 {value}
73 </MenuItem>
74 );
75 })}
76 </Select>
77 {props.error && <FormHelperText>{props.error}</FormHelperText>}
78 </FormControl>
79 );
80};
81
82export default SettingBaseStringSelector;
Language: typescript
Name: SettingBaseStringSelector.tsx
Description:
SettingBaseStringSelector component implementation example
1import SettingBaseStringSelector from './fields/SettingBaseStringSelector';
2
3
4FieldRegistry.register(
5 ['settingBaseStringSelector'],
6 SettingBaseStringSelector,
7);
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;
47
Language: typescript
Name: BooleanSelector.tsx
Description:
BooleanSelector component implementation example
1import BooleanSelector from './fields/BooleanSelector';
2
3FieldRegistry.register(
4 ['booleanSelector'],
5 BooleanSelector
6);
Language: typescript
Name: index.tsx
Description:
BooleanSelector field component registration
UI Component: RetailerSelector
`RetailerSelector`
Create a new file:
`RetailerSelector.tsx`
1import { getQuery } from 'mystique/hooks/getQuery';
2import { FormFieldProps } from 'mystique/registry/FieldRegistry';
3import { Connection } from 'mystique/types/common';
4import { ChangeEvent, FC, useEffect, useState } from 'react';
5import {
6 FormControl,
7 FormHelperText,
8 InputLabel,
9 MenuItem,
10 Select,
11} from '@material-ui/core';
12import { Loading } from 'mystique/components/Loading';
13
14export interface RetailerNode {
15 id: number;
16 tradingName: string;
17}
18
19interface RetailersResult {
20 retailers: Connection<RetailerNode>;
21}
22
23const retailersQuery = `query {
24 retailers {
25 edges {
26 node {
27 id
28 tradingName
29 }
30 }
31 }
32}
33`;
34
35interface RetailerId {
36 id: number;
37}
38
39const RetailerSelector: FC<FormFieldProps<RetailerId | null>> = ({
40 error,
41 label,
42 onBlur,
43 onChange,
44}) => {
45 const [selectedOption, setSelectedOption] = useState<number | undefined>();
46 const [retailers, setRetailers] = useState<RetailerNode[]>([]);
47 const [loading, setLoading] = useState<boolean>(true);
48
49 useEffect(() => {
50 onChange(selectedOption ? { id: selectedOption } : null);
51 }, [selectedOption]);
52
53 useEffect(() => {
54 getQuery<RetailersResult>(retailersQuery)
55 .then((retailersData) => {
56 const newRetailers: RetailerNode[] = [];
57 retailersData.data?.retailers.edges.map((item) => {
58 newRetailers.push(item.node);
59 });
60 setRetailers(newRetailers);
61 setSelectedOption(newRetailers[0]?.id);
62 setLoading(false);
63 })
64 .catch(() => {
65 setLoading(false);
66 });
67 }, []);
68
69 const handleChange = (event: ChangeEvent<{ value: unknown }>) => {
70 setSelectedOption(event.target.value as number);
71 };
72
73 const handleOnBlur = () => {
74 onBlur && onBlur();
75 };
76
77 return (
78 <>
79 {!loading ? (
80 <FormControl fullWidth error={!!error}>
81 <InputLabel id="retailers-select-label">{label}</InputLabel>
82 <Select
83 labelId="retailers-select-label"
84 onChange={handleChange}
85 value={`${selectedOption}`}
86 onBlur={handleOnBlur}
87 >
88 {retailers.map((retailer: RetailerNode) => {
89 return (
90 <MenuItem key={retailer.id} value={`${retailer.id}`}>
91 {retailer.tradingName}
92 </MenuItem>
93 );
94 })}
95 </Select>
96 {!!error && <FormHelperText> {error}</FormHelperText>}
97 </FormControl>
98 ) : (
99 <Loading />
100 )}
101 </>
102 );
103};
104
105export default RetailerSelector;
Language: typescript
Name: RetailerSelector.tsx
Description:
RetailerSelector component implementation example
1import RetailerSelector from './fields/RetailerSelector';
2
3
4
5
6FieldRegistry.register(
7 ['retailerSelector'],
8 RetailerSelector
9);
Language: typescript
Name: index.tsx
Description:
RetailerSelector 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 | [ "Australia", "Canada", "France", "Germany", "United Kingdom", "United States" ] |
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 | [ "GMT", "UTC", "ECT", "EET", "ACT", "AET", "HST", "AST", "PST", "PNT", "MST", "CST", "EST", "IET", "PRT", "CNT" ] |
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
`Create Customer`
`createCustomer`
1{
2 "type": "mutation",
3 "label": "Create Customer",
4 "name": "createCustomer",
5 "overrides": {
6 "title": {
7 "sortPrefix": 11
8 },
9 "firstName": {
10 "sortPrefix": 12
11 },
12 "lastName": {
13 "sortPrefix": 13
14 },
15 "username": {
16 "sortPrefix": 14
17 },
18 "primaryEmail": {
19 "sortPrefix": 15
20 },
21 "primaryPhone": {
22 "sortPrefix": 16
23 },
24 "department": {
25 "sortPrefix": 17
26 },
27 "country": {
28 "sortPrefix": 18,
29 "component": "settingBaseStringSelector",
30 "extensions": {
31 "settingName": "EDIT_LOCATION_COUNTRIES"
32 }
33 },
34 "timezone": {
35 "sortPrefix": 19,
36 "component": "settingBaseStringSelector",
37 "extensions": {
38 "settingName": "EDIT_LOCATION_TIME_ZONES"
39 }
40 },
41 "promotionOptIn": {
42 "component": "booleanSelector",
43 "sortPrefix": 20
44 },
45 "retailer": {
46 "component": "retailerSelector",
47 "sortPrefix": 21
48 }
49 }
50}
51
Language: json
Name: Create Customer mutation user action
Description:
'Create Customer' mutation user action declaration example. Property
`sortPrefix`
Update Language Setting
Localization of fields is done by creating localization entries for mutation fields according to name convention (see Languages and Localisation, chapter 'Adding new Mutation Actions').
1{
2 "translation": {
3 "fc.gql.customer.title": "Title",
4 "fc.gql.customer.firstName": "First Name",
5 "fc.gql.customer.lastName": "Last Name",
6 "fc.gql.customer.username": "Username",
7 "fc.gql.customer.primaryEmail": "Primary Email",
8 "fc.gql.customer.primaryPhone": "Primary Phone",
9 "fc.gql.customer.department": "Department",
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,
`Create Customer`

