Fluent Commerce Logo
Sign In

Create Customer via UI

How-to Guide
Extend

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.

Steps

Step arrow right iconUse 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.

Step arrow right iconSolution Approach

The feature can be implemented via the declaration of a custom mutation user action

`createCustomer`
in the manifest (see Page Component). For a better user experience, part of the default mutation field components have to be overridden. List of fields to override:

  • 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.

Step arrow right iconTechnical 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.

Step arrow right iconUI Component: SettingBaseStringSelector

`SettingBaseStringSelector`
component can be used for use cases when the user has to have a combo box with options filled from the setting. On the 'Create Customer' drawer the component will be used to present Country and Timezone selectors.

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

Step arrow right iconUI Component: BooleanSelector

`BooleanSelector`
component is used to customize the look and feel of default boolean field component (check box with label after instead of a switcher with label before).

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

Step arrow right iconUI Component: RetailerSelector

`RetailerSelector`
component adds some additional validation checks to retailer selection.

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

Step arrow right iconSetting: EDIT_LOCATION_COUNTRIES

`EDIT_LOCATION_COUNTRIES`
setting represents a list of countries available to select for new customers.

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]

Step arrow right iconSetting: EDIT_LOCATION_TIME_ZONES

`EDIT_LOCATION_TIME_ZONES`
setting represents a list of timezones available to select for new customers.

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]



Step arrow right iconManifest changes

`Create Customer`
action has to be declared as mutation user action
`createCustomer`
on page component (see Page Component).

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`
is used to configure the order of fields on the drawer 

Step arrow right iconUpdate 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

Step arrow right iconResult

After all steps,

`Create Customer`
action has to be available on the page. 

No alt providedNo alt provided

Step arrow right iconRelated Sources