UNPKG

@salla.sa/twilight-components

Version:
318 lines (317 loc) 12.8 kB
/*! * Crafted with ❤ by Salla */ import { h, } from "@stencil/core"; import CameraIcon from "../../assets/svg/camera.svg"; import { FormFieldTypes, } from "../salla-user-profile/interfaces"; export class SallaCustomFields { constructor() { /** Whether the fields can be edited by the user. */ this.isEditable = true; this.fieldErrors = {}; this.isSubmitting = false; } get dragAndDropText() { return salla.lang.get("common.uploader.drag_and_drop"); } get browseText() { return salla.lang.get("common.uploader.browse"); } get saveButtonText() { return salla.lang.get("common.elements.save"); } /** Update the displayed fields programmatically. */ async setFields(fields) { this.parsedFields = fields; } /** Gets the current values of all fields, formatted for submission. */ async getFieldValues() { return this.parsedFields?.reduce((values, field) => { values[`custom_fields[${field.id}]`] = field.value; return values; }, {}) || {}; } /** Validates all required fields and updates the error state. Returns true if valid. */ async validateFields() { if (!this.parsedFields) return true; const errors = this.parsedFields.reduce((acc, field) => { if (field.required && (!field.value || field.value === "")) { acc[field.id.toString()] = `${field.label} is required`; } return acc; }, {}); this.fieldErrors = errors; return Object.keys(errors).length === 0; } handleFieldChange(field, event) { const value = event.target.value; field.value = value; this.clearFieldError(field.id); const isValid = !(field.required && !value); if (!isValid) { this.setFieldError(field.id, `${field.label} is required`); } this.fieldChanged.emit({ fieldId: field.id, value, isValid }); } handleFileUpload(field, event) { const { error, file } = event.detail; const isValid = !error; this.clearFieldError(field.id); if (isValid) { field.value = file; } else { this.setFieldError(field.id, "File upload failed"); } this.fileUploaded.emit({ fieldId: field.id, file, isValid }); } clearFieldError(fieldId) { this.fieldErrors = { ...this.fieldErrors }; delete this.fieldErrors[fieldId]; } setFieldError(fieldId, message) { this.fieldErrors = { ...this.fieldErrors, [fieldId]: message }; } renderTextField(field) { return (h("input", { disabled: !this.isEditable, type: field.type, id: `custom-field-${field.id}`, value: field.value?.toString() || "", onChange: (e) => this.handleFieldChange(field, e), name: `${field.id}`, class: "form-input", required: field.required })); } renderFileField(field) { return (h("salla-file-upload", { url: salla.url.api('upload'), id: `custom-field-${field.id}`, name: `${field.id}`, type: "registration", value: field.value, height: "120px", onAdded: (e) => this.handleFileUpload(field, e), required: field.required, disabled: !this.isEditable, onUploaded: e => { e.target.value = e.target.value?.url; }, allowRemove: true, onRemoved: (e) => { e.preventDefault(); e.stopPropagation(); field.value = ""; e.target.value = ""; }, payloadName: "files[]", payloadParams: { type: 'registration' }, "instant-upload": true }, h("div", { class: "s-custom-fields-filepond-placeholder" }, h("span", { class: "s-custom-fields-filepond-placeholder-icon" }, h("i", { innerHTML: CameraIcon })), h("p", { class: "s-custom-fields-filepond-placeholder-text" }, this.dragAndDropText), h("span", { class: "filepond--label-action" }, this.browseText)))); } renderField(field) { return (h("div", { class: "s-custom-fields-field", key: field.id }, h("label", { class: "s-custom-fields-field-label", htmlFor: `custom-field-${field.id}` }, field.label, field.required && h("span", { class: "s-custom-fields-required" }, "*")), h("div", null, field.type === FormFieldTypes.Photo ? this.renderFileField(field) : this.renderTextField(field), this.fieldErrors[field.id] && (h("p", { class: "s-custom-fields-error" }, this.fieldErrors[field.id]))))); } componentWillLoad() { this.parsedFields = this.parseFields(this.fields); } parseFields(fields) { if (!fields) return []; return typeof fields === "string" ? JSON.parse(fields) : Array.isArray(fields) ? fields : []; } async handleSubmit(e) { e.preventDefault(); this.isSubmitting = true; try { const payload = {}; const form = e.target; const elements = form.elements; for (let i = 0; i < elements.length; i++) { const element = elements[i]; if (element.name && !element.name.startsWith("hidden") && element.value) { payload[element.name] = element.value; } } await salla.api.profile.updateCustomFields({ id: salla.config.get("store.id"), fields: payload, }); } finally { this.isSubmitting = false; } } render() { if (!this.parsedFields?.length) return null; return (h("form", { class: "s-custom-fields-wrapper", onSubmit: this.handleSubmit }, this.parsedFields.map(field => this.renderField(field)), h("salla-button", { type: "submit", loading: this.isSubmitting, disabled: this.isSubmitting || !this.isEditable, "loader-position": "end", class: "s-custom-fields-submit-btn" }, this.saveButtonText))); } static get is() { return "salla-custom-fields"; } static get originalStyleUrls() { return { "$": ["salla-custom-fields.scss"] }; } static get styleUrls() { return { "$": ["salla-custom-fields.css"] }; } static get properties() { return { "fields": { "type": "string", "attribute": "fields", "mutable": false, "complexType": { "original": "string | CustomField[]", "resolved": "CustomField[] | string", "references": { "CustomField": { "location": "import", "path": "../salla-user-profile/interfaces", "id": "src/components/salla-user-profile/interfaces.ts::CustomField" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "The list of custom fields to render. Can be a JSON string or an array of objects." }, "getter": false, "setter": false, "reflect": false }, "isEditable": { "type": "boolean", "attribute": "is-editable", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Whether the fields can be edited by the user." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "fileUploadUrl": { "type": "string", "attribute": "file-upload-url", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The URL to send file uploads to." }, "getter": false, "setter": false, "reflect": false } }; } static get states() { return { "parsedFields": {}, "fieldErrors": {}, "isSubmitting": {} }; } static get events() { return [{ "method": "fieldChanged", "name": "fieldChanged", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when a field's value changes." }, "complexType": { "original": "{\n fieldId: string | number;\n value: unknown;\n isValid: boolean;\n }", "resolved": "{ fieldId: string | number; value: unknown; isValid: boolean; }", "references": {} } }, { "method": "fileUploaded", "name": "fileUploaded", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when a file is successfully uploaded or encounters an error." }, "complexType": { "original": "{\n fieldId: string | number;\n file: File;\n isValid: boolean;\n }", "resolved": "{ fieldId: string | number; file: File; isValid: boolean; }", "references": { "File": { "location": "global", "id": "global::File" } } } }]; } static get methods() { return { "setFields": { "complexType": { "signature": "(fields: CustomField[]) => Promise<void>", "parameters": [{ "name": "fields", "type": "CustomField[]", "docs": "" }], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "CustomField": { "location": "import", "path": "../salla-user-profile/interfaces", "id": "src/components/salla-user-profile/interfaces.ts::CustomField" } }, "return": "Promise<void>" }, "docs": { "text": "Update the displayed fields programmatically.", "tags": [] } }, "getFieldValues": { "complexType": { "signature": "() => Promise<{ [key: string]: unknown; }>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<{ [key: string]: unknown; }>" }, "docs": { "text": "Gets the current values of all fields, formatted for submission.", "tags": [] } }, "validateFields": { "complexType": { "signature": "() => Promise<boolean>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<boolean>" }, "docs": { "text": "Validates all required fields and updates the error state. Returns true if valid.", "tags": [] } } }; } }