UNPKG

@salla.sa/twilight-components

Version:
329 lines (328 loc) 17.4 kB
/*! * 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() { /** * The minimum allowed age for a user. Users with a birthdate indicating an age less than this value will be considered invalid. * Defaults to 10. * @type {number} */ this.minAge = 10; this.isEditable = true; this.disableAction = false; this.isLoading = true; // Translated Strings State 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 ''; } 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), this.renderCustomField(field))); }); } renderCustomField(field) { if (field.type !== FormFieldTypes.Photo) { return (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 (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)))); } // 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() { let customFields = null; // Load user-defined fields and initial user data if (this.customFields) { customFields = typeof this.customFields === 'string' ? JSON.parse(this.customFields) : this.customFields; } if (customFields && Array.isArray(customFields)) { this.userDefinedFields = customFields; } return salla.api.profile .info() .then(resp => (this.userData = resp.data)) .finally(() => { this.isLoading = false; this.isEditable = !Salla.config.get('store.features').includes('sso-login'); }); } 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 }))))); } componentWillLoad() { Salla.onReady().then(() => this.fetchData()); } 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", { disabled: !this.isEditable, 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", { disabled: !this.isEditable, 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", { disabled: !this.isEditable, 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", { disabled: !this.isEditable, 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", { disabled: !this.isEditable, 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", { disabled: !this.isEditable, name: "international-mobile", "country-code": this.userData.phone.country, phone: `${this.userData.phone.number}`, onPhoneEntered: data => this.phoneNumberFieldEventHandler(data) })), this.isEditable && this.renderCustomFields()), h("salla-button", { type: "submit", loading: this.disableAction, disabled: this.disableAction || !this.isEditable, "loader-position": "end", class: "s-user-profile-submit" }, this.save_btn_trans))); } 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", "attribute": "custom-fields", "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." }, "getter": false, "setter": false, "reflect": false }, "minAge": { "type": "number", "attribute": "min-age", "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." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "10" } }; } static get states() { return { "userData": {}, "isEditable": {}, "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": [{ "name": "fields", "type": "CustomField[]", "docs": "- 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