diff --git a/blocks/event-format-component/event-format-component.js b/blocks/event-format-component/event-format-component.js index 355981ef..3bfa20ce 100644 --- a/blocks/event-format-component/event-format-component.js +++ b/blocks/event-format-component/event-format-component.js @@ -136,20 +136,6 @@ function decorateCheckbox(column) { column.append(checkbox); } -// async function decorateNewSeriesBtnAndModal(column) { -// const pTag = column.querySelector(':scope > p'); -// const plusIcon = getIcon('add-circle'); -// const a = column.querySelector('a[href$="#new-series"]'); - -// if (a) { -// pTag.classList.add('add-series-btn-wrapper'); -// a.append(plusIcon); -// a.classList.add('add-series-modal-btn'); -// } - -// await decorateNewSeriesModal(column); -// } - export default function init(el) { el.classList.add('form-component'); diff --git a/blocks/form-handler/controllers/profile-component-controller.js b/blocks/form-handler/controllers/profile-component-controller.js index 97e69100..9c70409a 100644 --- a/blocks/form-handler/controllers/profile-component-controller.js +++ b/blocks/form-handler/controllers/profile-component-controller.js @@ -25,9 +25,9 @@ export async function onSubmit(component, props) { export default function init(component) { const eventData = getJoinedData(); - const { profiles } = eventData; + const { speakers } = eventData; const profileContainer = component.querySelector('profile-container'); - if (!profiles || !profileContainer) return; - profileContainer.profiles = profiles; + if (!speakers || !profileContainer) return; + profileContainer.profiles = speakers; profileContainer.requestUpdate(); } diff --git a/blocks/form-handler/form-handler.js b/blocks/form-handler/form-handler.js index 6b3feefa..d899ef55 100644 --- a/blocks/form-handler/form-handler.js +++ b/blocks/form-handler/form-handler.js @@ -53,6 +53,10 @@ const SPECTRUM_COMPONENTS = [ 'divider', 'button', 'progress-circle', + 'overlay', + 'dialog', + 'button-group', + 'tooltip', ]; function replaceAnchorWithButton(anchor) { diff --git a/blocks/profile-component/profile-component.js b/blocks/profile-component/profile-component.js index 815e648f..a9fc3ad9 100644 --- a/blocks/profile-component/profile-component.js +++ b/blocks/profile-component/profile-component.js @@ -9,6 +9,7 @@ function extractFieldLabels(element) { const payload = { heading: headingDiv.querySelector('h2, h3, h4, h5, h6')?.textContent?.trim(), + tooltipMessage: headingDiv.querySelector('p > em')?.textContent?.trim(), chooseType: rows[0].querySelector('div')?.textContent?.trim(), firstName: rows[1].querySelector('div')?.textContent?.trim(), firstNameSubText: rows[1].querySelector('div > div:nth-of-type(2)')?.textContent?.trim(), @@ -35,7 +36,7 @@ async function decorateProfile(element) { const profileContainer = createTag('profile-container', { class: 'profile-component' }); profileContainer.fieldlabels = fieldlabels; - profileContainer.profiles = [{ socialMedia: [{ url: '' }] }]; + profileContainer.profiles = [{ socialMedia: [{ link: '' }] }]; element.append(profileContainer); } diff --git a/components/image-dropzone/image-dropzone.css.js b/components/image-dropzone/image-dropzone.css.js index e0e11de6..4f12c9b1 100644 --- a/components/image-dropzone/image-dropzone.css.js +++ b/components/image-dropzone/image-dropzone.css.js @@ -40,6 +40,7 @@ export const style = css` height: 100%; width: 100%; object-fit: cover; + display: block; } .img-file-input-wrapper label { diff --git a/components/image-dropzone/image-dropzone.js b/components/image-dropzone/image-dropzone.js index cf79e9e0..d469ac3a 100644 --- a/components/image-dropzone/image-dropzone.js +++ b/components/image-dropzone/image-dropzone.js @@ -36,7 +36,7 @@ export class ImageDropzone extends LitElement { } async uploadImage(url = this.configs.targetUrl) { - if (!this.file) return; + if (!this.file || !(this.file instanceof File)) return; this.configs.targetUrl = url; const resp = await uploadBinaryFile(this.file, this.configs); diff --git a/components/profile-container/profile-container.css.js b/components/profile-container/profile-container.css.js index 645e3738..a68b1d5a 100644 --- a/components/profile-container/profile-container.css.js +++ b/components/profile-container/profile-container.css.js @@ -27,12 +27,6 @@ repeater-element { margin: 24px; } -.form-component { - display: flex; - flex-direction: column; - gap: 20px; -} - .form-component > div:first-of-type > div > h2, .form-component > div:first-of-type > div > h3 { display: flex; diff --git a/components/profile-container/profile-container.js b/components/profile-container/profile-container.js index c756d56a..b14bed28 100644 --- a/components/profile-container/profile-container.js +++ b/components/profile-container/profile-container.js @@ -6,7 +6,7 @@ import { style } from './profile-container.css.js'; const { LitElement, html, repeat, nothing } = await import(`${getLibs()}/deps/lit-all.min.js`); -const defaultProfile = { socialMedia: [{ url: '' }], isPlaceholder: true }; +const defaultProfile = { socialMedia: [{ link: '' }], isPlaceholder: true }; export class ProfileContainer extends LitElement { static properties = { diff --git a/components/profile/profile.css.js b/components/profile/profile.css.js index 5cb0d8a4..3c98df35 100644 --- a/components/profile/profile.css.js +++ b/components/profile/profile.css.js @@ -6,7 +6,8 @@ const { css } = await import(`${getLibs()}/deps/lit-all.min.js`); // eslint-disable-next-line import/prefer-default-export export const style = css` image-dropzone { - width: 40%; + width: 300px; + max-width: 100%; } .img-file-input-wrapper { @@ -18,14 +19,16 @@ image-dropzone { justify-content: center; text-align: center; align-items: center; + align-self: flex-start; + overflow: hidden; } sp-textfield { - width: 100%; + width: 100%; } p { - margin: 0px; + margin: 0px; } h2 { @@ -39,20 +42,31 @@ h5 { margin-bottom: 0px; } +.social-media { + display: flex; + flex-direction: column; + gap: 30px; +} + .social-media-row { display: flex; flex-direction: row; align-items: center; gap: 16px; - margin-bottom: 32px; +} + +.social-media-row svg { + margin-left: 16px; + color: var(--color-black); + height: 32px; + width: 32px; } .social-media-input { width: 100%; } -.save-profile-button { - width: fit-content; +.profile-action-button { align-self: end; } @@ -67,4 +81,86 @@ h5 { .icon-remove-circle:hover { opacity: 1; } + +.edit-profile { + margin-left: auto; +} + +modal{ + width: 1000px; +} + +.profile-view { + width: 100%; + display: flex; + flex-direction: column; + gap: 20px; +} + +.social-media h3 { + margin: 0; +} + +.speaker-image { + width: 300px; + max-width: 100%; + height: 100%; + display: block; + object-fit: cover; +} + +.feds-social-icon { + display: block; + width: 24px; + height: 24px; + color: #808080; +} + +.feds-footer-icons { + display: none; +} + +.last-updated { + width: 100%; +} + +.profile-footer { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; +} + +.save-profile-button { + width: fit-content; + align-self: end; +} + +.profile-save-footer { + display: flex; + flex-direction: row-reverse; +} + +.footer-button-group { + margin-left: auto; +} + +.profile-header { + display: flex; + flex-direction: row; + align-items: center; + gap: 16px; + margin-bottom: 16px; +} + +.profile-header h2 { + margin: 0; +} + +.profile-header overlay-trigger { + height: 18px; +} + +.edit-profile-title { + color: var(--color-red); +} `; diff --git a/components/profile/profile.js b/components/profile/profile.js index 6b55caf5..280b19c8 100644 --- a/components/profile/profile.js +++ b/components/profile/profile.js @@ -3,10 +3,12 @@ import { getLibs } from '../../scripts/utils.js'; import { style } from './profile.css.js'; import { createSpeaker } from '../../utils/esp-controller.js'; +import { getServiceName } from '../../utils/utils.js'; +import { icons } from '../../icons/icons.svg.js'; -const { LitElement, html, repeat, nothing, query } = await import(`${getLibs()}/deps/lit-all.min.js`); +const { LitElement, html, repeat, nothing } = await import(`${getLibs()}/deps/lit-all.min.js`); -const defaultFieldLabels = { +const DEFAULT_FIELD_LABELS = { heading: 'Profile', chooseType: 'Choose Type', name: 'Name', @@ -17,13 +19,15 @@ const defaultFieldLabels = { addSocialMediaRepeater: 'Add Social Media', }; -const speakerType = ['Presenter', 'Host', 'Speaker', 'Keynote']; +const SPEAKER_TYPE = ['Presenter', 'Host', 'Speaker', 'Keynote']; +const SUPPORTED_SOCIAL = ['facebook', 'instagram', 'twitter', 'linkedin', 'pinterest', 'discord', 'behance', 'youtube', 'weibo', 'social-media']; export class Profile extends LitElement { static properties = { seriesId: { type: String }, fieldlabels: { type: Object, reflect: true }, profile: { type: Object, reflect: true }, + profileCopy: { type: Object, reflect: true }, }; static styles = style; @@ -31,17 +35,14 @@ export class Profile extends LitElement { constructor() { super(); this.attachShadow({ mode: 'open' }); - this.fieldlabels = this.fieldlabels ?? defaultFieldLabels; + this.fieldlabels = this.fieldlabels ?? DEFAULT_FIELD_LABELS; - this.profile = this.profile ?? { socialMedia: [{ url: '' }] }; - } - - firstUpdated() { - this.imageDropzone = this.shadowRoot.querySelector('image-dropzone'); + this.profile = this.profile ?? { socialMedia: [{ link: '' }] }; + this.profileCopy = { ...this.profile }; } addSocialMedia() { - const socialMedia = { url: '' }; + const socialMedia = { link: '' }; if (this.profile?.socialMedia) { this.profile.socialMedia.push(socialMedia); } else { @@ -55,12 +56,49 @@ export class Profile extends LitElement { } updateSocialMedia(index, value) { - this.profile.socialMedia[index] = { url: value }; + this.profile.socialMedia[index] = { link: value, serviceName: getServiceName(value) }; + this.requestUpdate(); + } + + renderProfileTypePicker(fieldLabel) { + return html` +
+
${fieldLabel}
+ this.updateValue('type', event.target.value)}> + ${repeat(SPEAKER_TYPE, (type) => html` + ${type} + `)} + +
`; + } + + async saveProfile(e) { + const imageDropzone = this.shadowRoot.querySelector('image-dropzone'); + const saveButton = e.target; + + saveButton.pending = true; + + try { + this.profileCopy = { ...this.profile }; + const respJson = await createSpeaker(this.profile, this.seriesId); + + if (respJson.speakerId) { + this.profile.id = respJson.speakerId; + this.profile.socialMedia = this.profile.socialMedia.filter((sm) => sm.link !== ''); + this.profile.image = imageDropzone?.file ? { url: imageDropzone?.file?.url } : null; + await imageDropzone.uploadImage(`/v1/series/${this.seriesId}/speakers/${this.profile.id}/images`); + this.requestUpdate(); + } + } catch { + window.lana?.log('error occured while saving profile'); + } + + saveButton.pending = false; } renderProfileForm() { const fieldLabelsJSON = { - ...defaultFieldLabels, + ...DEFAULT_FIELD_LABELS, ...(this.fieldlabels ?? {}), }; @@ -101,33 +139,25 @@ export class Profile extends LitElement { const quietTextfieldConfig = { size: 'xl', quiet: true }; return html` -

${fieldLabelsJSON.heading}

-
-
${fieldLabelsJSON.chooseType}
- this.updateValue('type', event.target.value)}> - ${repeat(speakerType, (type) => html` - ${type} - `)} - -
+ ${this.renderProfileTypePicker(fieldLabelsJSON.chooseType)} this.updateValue('firstName', event.detail.value)}> this.updateValue('lastName', event.detail.value)}> + })} file=${JSON.stringify(this.profile.image)}> this.updateValue('title', event.detail.value)}> this.updateValue('bio', event.detail.value)}> -
+

${fieldLabelsJSON.socialMedia}

${this.profile?.socialMedia ? repeat( this.profile?.socialMedia, (socialMedia, index) => html` { this.addSocialMedia(); }}> - { - const respJson = await createSpeaker(this.profile, this.seriesId); - if (respJson.speakerId) { - this.profile.id = respJson.speakerId; - this.profile.socialMedia = this.profile.socialMedia.filter((sm) => sm.url !== ''); - this.imageDropzone.uploadImage(`/v1/series/${this.seriesId}/speakers/${this.profile.id}/images`); - - delete this.profile.isPlaceholder; - } - }}>Save Profile `; } + renderProfileCreateForm() { + return html` +
+

${this.fieldlabels.heading}

+ ${this.renderProfileForm()} + + `; + } + + revertProfile() { + this.profile = { ...this.profileCopy }; + this.requestUpdate(); + } + + renderProfileEditForm() { + return html` +
+

Edit Profile

+ ${this.renderProfileForm()} + + `; + } + + renderSocialMediaLink(socialMedia) { + const serviceName = SUPPORTED_SOCIAL.includes(socialMedia.serviceName) ? socialMedia.serviceName : 'social-media'; + return socialMedia.link ? html`` : nothing; + } + renderProfileView() { const fieldLabelsJSON = { - ...defaultFieldLabels, + ...DEFAULT_FIELD_LABELS, ...(this.fieldlabels ?? {}), }; + // FIXME: update last updated date to actual date. return html` +
+

${fieldLabelsJSON.heading}

-
-
${fieldLabelsJSON.chooseType}
- this.updateValue('type', event.target.value)}> - ${repeat(speakerType, (type) => html` - ${type} - `)} - -
+ + info icon + + ${fieldLabelsJSON.tooltipMessage} + + +
+ ${this.renderProfileTypePicker(fieldLabelsJSON.chooseType)}

${this.profile.firstName} ${this.profile.lastName}

-
${this.profile.image?.url ? html` -
-
- preview image +
+
+
+ preview image +
` : nothing} -
${this.profile.title}

${this.profile.bio}

-
-

${fieldLabelsJSON.socialMedia}

- ${this.profile?.socialMedia ? repeat(this.profile?.socialMedia, (socialMedia) => html`

${socialMedia.url}

`) : nothing} + ${this.profile?.socialMedia?.length ? html` + + ` : nothing} - Save Profile + +
`; } render() { if (!this.profile.id) { - return this.renderProfileForm(); + return this.renderProfileCreateForm(); } return this.renderProfileView(); } diff --git a/icons/icons.svg.js b/icons/icons.svg.js new file mode 100644 index 00000000..69124ea8 --- /dev/null +++ b/icons/icons.svg.js @@ -0,0 +1,55 @@ +import { getLibs } from '../scripts/utils.js'; + +const { svg } = await import(`${getLibs()}/deps/lit-all.min.js`); +// FIXME : use global footer icons instead. +export const icons = svg` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; \ No newline at end of file diff --git a/icons/user-add.svg b/icons/user-add.svg new file mode 100644 index 00000000..6827d586 --- /dev/null +++ b/icons/user-add.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/icons/user-edit.svg b/icons/user-edit.svg new file mode 100644 index 00000000..ec1b7bb8 --- /dev/null +++ b/icons/user-edit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/utils/utils.js b/utils/utils.js index e99f38c8..ccdf90a1 100644 --- a/utils/utils.js +++ b/utils/utils.js @@ -241,3 +241,9 @@ export async function getSecret(key) { const secret = secretCache.find((s) => s.key === key); return secret.value; } + +export function getServiceName(link) { + const url = new URL(link); + + return url.hostname.replace('.com', '').replace('www.', ''); +}