# User Interface

You can specify your own UI by specifying the `uiPath` property (and optionally `fileName`) or by using the React JSON schema forms.

Only the [Project Integration](/app-project-module/project-integration/) apps use the `crowdin-simple-integration` component provided by the [Crowdin UI Kit](https://crowdin-web-components.s3.amazonaws.com/index.html) by default.

## React JSON Schema Forms

For all modules that have UI (except `projectIntegration`), you can use [React JSON Schema forms](https://github.com/rjsf-team/react-jsonschema-form) as frontend.

To achieve this, you can specify the `formSchema` property in the module definition:

```js "formSchema" title="index.js"
configuration.projectMenu = [
  {
    formSchema: {
      "title": "A registration form",
      "description": "A simple form example.",
      "type": "object",
      "required": [
        "firstName",
        "lastName"
      ],
      "properties": {
        "firstName": {
          "type": "string",
          "title": "First name",
          "default": "Chuck"
        },
        "lastName": {
          "type": "string",
          "title": "Last name"
        }
      }
    },
    formUiSchema: {
      "ui:submitButtonOptions": {
        "submitText": "Confirm Details"
      },
      "lastName": {
        "ui:help": "Hint: Choose cool lastname!"
      }
    }
  }
];
```

:::note
It uses the latest version of the RJSF library - v6.x. For more information, see the [RJSF 6.x upgrade guide](https://rjsf-team.github.io/react-jsonschema-form/docs/migration-guides/v6.x%20upgrade%20guide/).
:::

By default, form data will be stored in app metadata. To retrieve data you can use this code:

```js
crowdinApp.getMetadata(`${organizationId}-${projectId}`);
```

To customize this behavior you can provide `formGetDataUrl` and `formPostDataUrl` properties that will contain URL to custom actions in your app. This actions should support GET request for fetching data (`formGetDataUrl`) and POST request to save new data (`formPostDataUrl`).

**Caution:** By default, all forms will use the same URLs to retrieve and store data. So if your application has more than 1 JSON schema form - be sure to modify the `formGetDataUrl` and `formPostDataUrl` options for them.

Response structure for GET request example:

```js
res.status(200).send({
  formData: {
    firstName: 'First Name'
  }
}).end();
```

Also, you can provide the following properties:

- `formSchema` - new schema to replace active schema;
- `formUiSchema` - new uiSchema to replace current;
- `message` - custom message that will be displayed in toasts;
- `redirect` - url for redirect (useful for files download).

### Custom HTML Field

You can add a piece of custom HTML to your form. To do this, you will need:

1.  Add field with type `string` to the form with additional `uiSchema` properties:
   ```js {4, 9}
      const exampleConfig = {
        formSchema: {
          bio: {
            type: 'string'
          }
        },
        formUiSchema: {
          bio: {
            'ui:widget': 'htmlWidget'
          }
        }
      };
      ```

2.  Provide form value using one of options - `ui:options` content or `formData` (`formData` value have higher priority):
   ```js {12-14}
      const exampleConfig = {
        formSchema: {
          bio: {
            type: 'string',
          },
        },
        formUiSchema: {
          bio: {
            'ui:widget': 'htmlWidget',
          },
        },
        formData: {
          bio: '<h2>HTML formatted description</h2>',
        },
      };
      ```
### Custom Widgets

You can extend the functionality of your form by using custom widgets. Below are two examples of custom widgets that can be used to enhance your forms: `crowdinFilesWidget` and `monacoEditorWidget`.

#### Crowdin Files Widget

:::caution
`crowdinFilesWidget` is deprecated and will be removed in an upcoming release. Do not use it in new apps.
:::

The `crowdinFilesWidget` widget, part of the [Crowdin UI Kit](https://crowdin-web-components.s3.amazonaws.com/index.html), allows users to select files. To use the `crowdinFilesWidget`, add a property to your `formSchema` with the type `string` and provide the custom widget in the `formUiSchema` as follows:

```js title="index.js"
formSchema: {
  properties: {
    files: {
      title: 'Crowdin Files',
      type: 'string',
    },
  },
},
formUiSchema: {
  files: {
    "ui:widget": 'crowdinFilesWidget',
  },
}
```
#### Monaco Editor Widget

The `monacoEditorWidget` allows you to embed a powerful code editor into your form. It is useful for scenarios where users need to provide or modify code snippets. The widget is based on the Monaco Editor, which powers Visual Studio Code, and supports syntax highlighting, auto-completion, and other advanced editor features.

```js title="index.js"
formSchema: {
  properties: {
    code: {
      type: 'string',
      language: 'javascript',
      height: 500,
      minimap: true,
      lineNumbers: 'on',
    },
  },
},
formUiSchema: {
  code: {
    "ui:widget": 'monacoEditorWidget',
    "ui:help": 'Provide your custom JavaScript code here',
  },
}
```

You can specify various editor options such as `language`, `height`, `lineNumbers`, and `minimap` visibility or provide custom `options` with different settings. You can find the full list of available options in the [Monaco Editor documentation](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IEditorConstructionOptions.html). For example:

```js title="index.js"
options: {
  minimap: {
    enabled: true,
    side: 'left',
  },
  lineNumbers: 'on',
}
```

### Manipulate Form from JavaScript

You may want to manipulate the form from JavaScript. For example, you may want to change the form schema or `uiSchema` from a custom code widget.

To do this, you can use the global `renderForm` function and the `formSchema`, `formUiSchema` and `formData` variables.

```js title="index.js"
const updatedSchema = {
  ...window.currentFormSchema,
  properties: {
    ...window.currentFormSchema.properties,
    lastName: {
      type: 'string',
      title: 'New title for last name field',
      default: 'Jon Doe'
    }
  }
};

window.renderForm(window.currentFormData, updatedSchema, window.currentFormUiSchema);
```

### Listening to Data Change Events

You can also listen for form data changes:

```js title="index.js"
function setInputValue (target, value) {
  const setter = Object.getOwnPropertyDescriptor(target, "value").set
  const prototype = Object.getPrototypeOf(target)
  const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set
  if (setter && setter !== prototypeValueSetter) {
    prototypeValueSetter.call(target, value)
  } else {
    setter.call(target, value)
  }
  const event = new Event("input", {bubbles: true})
  target.dispatchEvent(event);
}

document.addEventListener('formDataUpdated', (event) => {
  if (event?.detail?.type === '1') {
    setInputValue(document.getElementById('root_selector'), 'value');
  }
});
```

### Masking Credentials

The `maskKey` is designed to mask a given string, revealing only the last three characters while replacing the preceding characters with a masking symbol (by default, an asterisk *).

```js title="index.js"
import crowdinModule from '@crowdin/app-project-module';

// ...

crowdinModule.maskKey('ABCD-ABCD-ABCD'); // Output: "***********BCD"
```

You should mask input values if you use form to store credentials, api keys, passwords etc. To achieve this add middlewares before functions used to display and store form data. Also use password widget in form schema.

Example:

```js title="index.js"
const moduleConfig = {
  formSchema: {
    title: "Service Setup Form",
    type: "object",
    required: ["key"],
    properties: {
      key: {
        type: "string",
        title: "API Key"
      }
    }
  },
  formUiSchema: {
    key: {
      "ui:widget": "password"
    }
  },
  formPostDataUrl: '/form'
};
```

```js title="index.js"
import crowdinModule from '@crowdin/app-project-module';

// ...

app.post('/save-form-data', crowdinModule.postRequestCredentialsMasker(settingsForm), async (req, res) => {
  // Always specify moduleKey to restrict access to authorized modules
  const { client, context } = await crowdinApp.establishCrowdinConnection({
    jwtToken: req.query.jwtToken,
    moduleKey: ['my-settings-module'] // Only this module can access
  });

  const formData = req.body.data;
});

// if your application uses a non-default action to retrieve data, you should also mask it.
app.get('/get-form-data', crowdinModule.getRequestCredentialsMasker({ moduleConfig: settingsForm }), async (req, res) => {
  // Always specify moduleKey to restrict access to authorized modules
  const { client, context } = await crowdinApp.establishCrowdinConnection({
    jwtToken: req.query.jwtToken,
    moduleKey: ['my-settings-module'] // Only this module can access
  });

  const data = (await storage.getStorage().getMetadata(id)) || {};

  return res.send({
    formData: data
  });
});
```

With such setup API key will be masked for front-end. You can use it in your code as usual.

### Skipping Toast After Form Submit

To skip default toast you can return `message: ''`, or `message: null` in response.

```js
res.send({ message: null });
```

## Theming

For applications using the UI Kit, the theme will be automatically selected based on the theme chosen in Crowdin. Additionally, for those using React JSON Schema, the light or dark theme will be automatically set.

### Manual Theme Selection for Other Applications

For all other cases, you can use the following [App JS](https://support.crowdin.com/developer/crowdin-apps-js/) method:

```js
AP.getTheme(function (theme) {
  if (theme === 'dark') {
    // set dark styles
  } else {
    // set light styles
  }
});
```

You can also retrieve the list of CSS variables and use them for custom styling:

```js
AP.getCssVariables(style => {
  // apply custom styling
});
```

Alternatively, you can use one of the predefined CSS classes:

```yml title="Text colors"
crdn-text:primary
crdn-text:title
crdn-text:body
crdn-text:muted
crdn-text:disabled
crdn-text:info
crdn-text:success
crdn-text:warning
crdn-text:danger
```

```yml title="Backgrounds"
crdn-bg:lvl-0
crdn-bg:lvl-1
crdn-bg:lvl-2
crdn-bg:lvl-2:05
crdn-bg:lvl-3
crdn-bg:primary
crdn-bg:success
crdn-bg:warning
crdn-bg:danger
```

```yml title="Borders"
crdn-border:primary
crdn-border:danger
crdn-border:info
crdn-border:success
crdn-border:warning
```