@salla.sa/twilight-components
Version:
Salla Web Component
306 lines (305 loc) • 14.9 kB
JavaScript
/*!
* Crafted with ❤ by Salla
*/
import { h } from "@stencil/core";
import { Genders, FormFieldTypes } from "./interfaces";
import CameraIcon from "../../assets/svg/camera.svg";
/**
* The SallaUserProfile is a versatile user profile form
* generator within the Salla platform, offering localization support,
* customizable fields, and seamless integration with the Salla API.
* It dynamically fetches translated strings for labels and messages from the
* Salla localization object, allowing for a multilingual user interface. Users
* can extend the default form with custom fields through the `customFields`
* property and the setCustomFields method. The component manages user data
* retrieval from the Salla configuration and includes fields for first name,
* last name, birthday, gender, email, mobile, and custom additions. Form
* validation and submission are handled through various event handlers, ensuring
* proper field handling and user input validation. The component supports file
* uploads for photo fields and includes a dedicated phone number input field.
* The componentWillLoad lifecycle method fetches additional user profile
* information from the API during initialization. Overall, this component
* provides an efficient and adaptable solution for creating user profiles with a
* rich set of features and customization options.
*/
export class SallaUserProfile {
// Constructor
constructor() {
this.customFields = undefined;
this.minAge = 10;
this.userData = undefined;
this.userDefinedFields = undefined;
this.disableAction = false;
this.isLoading = true;
this.first_name_trans = salla.lang.get('pages.profile.first_name');
this.last_name_trans = salla.lang.get('pages.profile.last_name');
this.birthday_trans = salla.lang.get('pages.profile.birthday');
this.birthday_placeholder_trans = salla.lang.get('pages.profile.birthday_placeholder');
this.gender_trans = salla.lang.get('pages.profile.gender');
this.gender_placeholder_trans = salla.lang.get('pages.profile.gender_placeholder');
this.male_trans = salla.lang.get('pages.profile.male');
this.female_trans = salla.lang.get('pages.profile.female');
this.email_trans = salla.lang.get('common.elements.email');
this.mobile_trans = salla.lang.get('common.elements.mobile');
this.save_btn_trans = salla.lang.get('common.elements.save');
this.drag_and_drop_trans = salla.lang.get('common.uploader.drag_and_drop');
this.browse_trans = salla.lang.get('common.uploader.browse');
this.email_required_trans = salla.lang.get('pages.checkout.email_required');
this.invalid_email_trans = salla.lang.get('pages.error.invalid_value', { attribute: "email" });
// Localization setup when the language is loaded
salla.lang.onLoaded(() => {
// Assigning translated strings to state properties
// These translations are fetched from the localization object
this.first_name_trans = salla.lang.get('pages.profile.first_name');
this.last_name_trans = salla.lang.get('pages.profile.last_name');
this.birthday_trans = salla.lang.get('pages.profile.birthday');
this.birthday_placeholder_trans = salla.lang.get('pages.profile.birthday_placeholder');
this.gender_trans = salla.lang.get('pages.profile.gender');
this.gender_placeholder_trans = salla.lang.get('pages.profile.gender_placeholder');
this.male_trans = salla.lang.get('pages.profile.male');
this.female_trans = salla.lang.get('pages.profile.female');
this.email_trans = salla.lang.get('common.elements.email');
this.mobile_trans = salla.lang.get('common.elements.mobile');
this.save_btn_trans = salla.lang.get('common.elements.save');
this.drag_and_drop_trans = salla.lang.get('common.uploader.drag_and_drop');
this.browse_trans = salla.lang.get('common.uploader.browse');
this.email_required_trans = salla.lang.get('pages.checkout.email_required');
this.invalid_email_trans = salla.lang.get('pages.error.invalid_value', { attribute: "email" });
});
}
/**
* Sets custom fields for the component. Can be handy for non HTML usage.
*
* @param fields - An array of custom fields.
*/
async setCustomFields(fields) {
this.userDefinedFields = fields;
}
// Event handler for phone number field changes
phoneNumberFieldEventHandler(data) {
if (!data.detail.number) {
return this.disableAction = true;
}
this.userData.phone.number = parseInt(data.detail.number);
this.userData.phone.country = data.detail.country_code;
this.disableAction = false;
}
// Event handler for generic field changes
handleFieldChange(key, event, required = false) {
if (event.target.value) {
this.userData[key] = event.target.value;
this.disableAction = false;
}
else {
if (required) {
this.disableAction = true;
}
}
}
// Event handler for file upload changes
handleOnFileAdded(key, data) {
// TODO: the key in here, shall be not have whitespace. or the key for
// the field shall be provided with one of the properties of the custom field
if (!data.detail.error) {
this.userData[key] = data.detail.file;
this.disableAction = false;
}
else {
this.disableAction = true;
}
}
// Event handler for email input changes
handleEmailInput(key, event) {
const emailErrorDisplaySection = document.getElementById('email-error');
const email = event.target.value;
if (!email) {
this.disableAction = true;
return emailErrorDisplaySection.textContent = this.email_required_trans;
}
const emailPattern = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
if (!emailPattern.test(email)) {
this.disableAction = true;
return emailErrorDisplaySection.textContent = this.invalid_email_trans;
}
emailErrorDisplaySection.textContent = '';
this.userData[key] = email;
this.disableAction = false;
}
// Helper method to trim language code from URL
trimLanguageCodeFromUrl(url, languageCode) {
const pattern = new RegExp(`^(https?://[^/]+)\\/${languageCode}(.*)`, 'i');
const match = url.replace(/\/+$/, '').match(pattern);
if (match && match.length >= 2) {
const [, baseUrl, restOfUrl] = match;
return baseUrl + restOfUrl;
}
return url;
}
// Helper method to get file upload URL
getFileUploadUrl() {
return `${this.trimLanguageCodeFromUrl(salla.config.get('store.url'), salla.config.get('user.language_code'))}/upload-image`;
}
// Render custom fields based on user-defined fields
renderCustomFields() {
if (!!this.userDefinedFields) {
return this.userDefinedFields.map((field) => {
return h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: `${field.id}`, class: "s-user-profile-field-label" }, field.label), field.type == FormFieldTypes.Photo ?
h("salla-file-upload", { "instant-upload": true, value: field.value, url: this.getFileUploadUrl(), name: `custom_fields[${field.id}]`, "payload-name": "image", height: "120px", onAdded: (data) => this.handleOnFileAdded(`custom_fields[${field.id}]`, data) }, h("div", { class: "s-user-profile-filepond-placeholder" }, h("span", { class: "s-user-profile-filepond-placeholder-icon" }, h("i", { innerHTML: CameraIcon })), h("p", { class: "s-user-profile-filepond-placeholder-text" }, this.drag_and_drop_trans), h("span", { class: "filepond--label-action" }, this.browse_trans)))
:
h("input", { type: field.type, id: `${field.id}`, value: field.value, onChange: (event) => this.handleFieldChange(`custom_fields[${field.id}]`, event), name: `custom_fields[${field.id}]`, class: "form-input", required: field.required }));
});
}
return "";
}
// Submit form method
submitForm(event) {
event.preventDefault();
this.disableAction = true;
let payload = Object.assign({}, this.userData);
delete payload.phone;
//@ts-ignore
payload["phone"] = this.userData.phone.number;
payload["country_code"] = this.userData.phone.country;
return salla.api.profile.update(payload).finally(() => this.disableAction = false);
}
getBirthDateRestriction() {
const now = new Date();
const pastYear = now.getFullYear() - this.minAge;
now.setFullYear(pastYear);
return now;
}
fetchData() {
// Load user-defined fields and initial user data
if (this.customFields)
this.userDefinedFields = JSON.parse(this.customFields);
return salla.api.profile.info().then(resp => {
this.userData = resp.data;
}).finally(() => {
this.isLoading = false;
});
}
renderLoadingSection() {
return h("div", { class: "s-user-profile-skeleton-wrapper" }, Array.from({ length: 6 }, (_, i) => h("salla-skeleton", { class: "skeleton-item", width: "100%", height: "50px", key: i })));
}
render() {
if (this.isLoading) {
return this.renderLoadingSection();
}
return h("form", { onSubmit: (event) => this.submitForm(event) }, h("div", { class: "s-user-profile-wrapper" }, h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "first-name", class: "s-user-profile-field-label" }, this.first_name_trans), h("input", { type: "text", name: "first_name", value: this.userData.first_name, id: "first-name", required: true, autocomplete: "first_name", class: "form-input", onChange: (event) => this.handleFieldChange("first_name", event) })), h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "last-name", class: "s-user-profile-field-label" }, this.last_name_trans), h("input", { type: "text", name: "last_name", value: this.userData.last_name, id: "last-name", required: true, autocomplete: "last_name", class: "form-input", onChange: (event) => this.handleFieldChange("last_name", event) })), h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "birthday", class: "s-user-profile-field-label" }, this.birthday_trans), h("salla-datetime-picker", { dateFormat: "Y-m-d", value: this.userData.birthday, placeholder: this.birthday_placeholder_trans, required: true, maxDate: this.getBirthDateRestriction(), name: "birthday", onPicked: (event) => this.handleFieldChange("birthday", event) })), h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "gender", class: "s-user-profile-field-label" }, this.gender_trans), h("select", { class: "form-input", name: "gender", required: true, onChange: (event) => this.handleFieldChange("gender", event) }, h("option", { value: "" }, this.gender_placeholder_trans), h("option", { value: Genders.Male, selected: this.userData.gender == Genders.Male }, this.male_trans), h("option", { value: Genders.Female, selected: this.userData.gender == Genders.Female }, this.female_trans))), h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "email", class: "s-user-profile-field-label" }, this.email_trans), h("input", { type: "email", name: "email", value: this.userData.email, id: "email", class: "form-input", required: true, onInput: (event) => this.handleEmailInput("email", event) }), h("p", { id: "email-error", class: "s-user-profile-field-error" })), h("div", { class: "s-user-profile-field" }, h("label", { htmlFor: "international-mobile", class: "s-user-profile-field-label" }, this.mobile_trans), h("salla-tel-input", { name: 'international-mobile', "country-code": this.userData.phone.country, phone: `${this.userData.phone.number}`, onPhoneEntered: (data) => this.phoneNumberFieldEventHandler(data) })), this.renderCustomFields()), h("salla-button", { type: "submit", loading: this.disableAction, disabled: this.disableAction, "loader-position": "end", class: "s-user-profile-submit" }, this.save_btn_trans));
}
componentWillLoad() {
this.fetchData();
}
static get is() { return "salla-user-profile"; }
static get originalStyleUrls() {
return {
"$": ["salla-user-profile.scss"]
};
}
static get styleUrls() {
return {
"$": ["salla-user-profile.css"]
};
}
static get properties() {
return {
"customFields": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Custome fields to be rendered in addition to the default ones."
},
"attribute": "custom-fields",
"reflect": false
},
"minAge": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "type",
"text": "{number}"
}],
"text": "The minimum allowed age for a user. Users with a birthdate indicating an age less than this value will be considered invalid.\nDefaults to 10."
},
"attribute": "min-age",
"reflect": false,
"defaultValue": "10"
}
};
}
static get states() {
return {
"userData": {},
"userDefinedFields": {},
"disableAction": {},
"isLoading": {},
"first_name_trans": {},
"last_name_trans": {},
"birthday_trans": {},
"birthday_placeholder_trans": {},
"gender_trans": {},
"gender_placeholder_trans": {},
"male_trans": {},
"female_trans": {},
"email_trans": {},
"mobile_trans": {},
"save_btn_trans": {},
"drag_and_drop_trans": {},
"browse_trans": {},
"email_required_trans": {},
"invalid_email_trans": {}
};
}
static get methods() {
return {
"setCustomFields": {
"complexType": {
"signature": "(fields: CustomField[]) => Promise<void>",
"parameters": [{
"tags": [{
"name": "param",
"text": "fields - An array of custom fields."
}],
"text": "- An array of custom fields."
}],
"references": {
"Promise": {
"location": "global",
"id": "global::Promise"
},
"CustomField": {
"location": "import",
"path": "./interfaces",
"id": "src/components/salla-user-profile/interfaces.ts::CustomField"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "Sets custom fields for the component. Can be handy for non HTML usage.",
"tags": [{
"name": "param",
"text": "fields - An array of custom fields."
}]
}
}
};
}
}
//# sourceMappingURL=salla-user-profile.js.map