/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "NECTLY_.*|hidePaymentConfirmation|sumbit_meeting_cancellation|renderRateSettingsSaveConfirmationElement|cancelRateSettingsSave|rateSettingsSave|getStarted|proResetPayout|setupCharity|renderConversationHistoryTable|requestStripeInformation|activateSeeker|proSelectSlot|proSelectSlots|activateProfessional|redirectToStripeConnectLink|startCreditCardAuthorization|logoutUser|sendEarlyAccessEmail|initStripePayment|joinMeeting|onLoad|deleteSlots|proCancelMeeting|seekerCancelMeeting|onProTimeSlotSelectChange|onTimeSlotSelectChange|deleteSlot|proDeleteSlot|proDeleteSlots|proSaveSession|seekerSaveSession|proAppointmentsTabClick|seekerAppointmentsTabClick|proSlotsTabClick|seekerSlotsTabClick|reserveSlot|.*UserRole$" }]*/ /*global google Stripe lottie setupAiChatbot disable_button stop_loader*/ let calendar = null; let mobileCalendar = null; let seekerCalendar = null; let mobileSeekerCalendar = null; let proCalendar = null; let mobileProCalendar = null; function callApi(url, method = 'GET', data = null) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(method, url); if (data) { xhr.setRequestHeader('Content-Type', 'application/json'); } xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { resolve({ status: xhr.status, response: xhr.responseText }); } else { reject({ status: xhr.status, error: xhr.responseText }); } }; xhr.onerror = function() { reject({ status: 0, error: 'Network error occurred' }); }; xhr.send(data ? JSON.stringify(data) : null); }); } function hideDiv(divId) { const el = document.getElementById(divId); if (el) { el.style.display = 'none'; Array.from(el.children).forEach(child => hideAllChildren(child)); } } function hideAllChildren(element) { element.style.display = 'none'; Array.from(element.children).forEach(child => hideAllChildren(child)); } function showDiv(divId) { const el = document.getElementById(divId); if (el) { el.style.removeProperty('display'); Array.from(el.children).forEach(child => showAllChildren(child)); } } function showAllChildren(element) { element.style.removeProperty('display'); Array.from(element.children).forEach(child => showAllChildren(child)); } function convertMonthStringToInt(monthString) { const lowerCaseMonth = monthString.toLowerCase(); switch (lowerCaseMonth) { case 'january': return 0; case 'february': return 1; case 'march': return 2; case 'april': return 3; case 'may': return 4; case 'june': return 5; case 'july': return 6; case 'august': return 7; case 'september': return 8; case 'october': return 9; case 'november': return 10; case 'december': return 11; default: console.error(`Error: "${monthString}" is not a valid month string.`); return -1; } } function convertIntMonthToString(monthInt) { const monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; if (monthInt >= 0 && monthInt <= 11) { return monthNames[monthInt]; } else { console.error(`Error: "${monthInt}" is not a valid month integer (0-11).`); return null; } } function getMeta(metaName) { const metas = document.getElementsByTagName('meta'); for (let i = 0; i < metas.length; i++) { if (metas[i].getAttribute('property') === metaName) { return metas[i].getAttribute('content'); } } return ''; } function logoutUser() { return callApi('/api/logout', 'POST') .then(data => { console.log('Logout successful:', data); window.location.href = '/'; return data; }) .catch(error => { console.error('Logout failed:', error); throw error; }); } function initStripePayment(user_id, user_email, bundle_name) { let url = new URL("https://buy.stripe.com/test_9B6fZiaPn5mQdoc0A57Zu03"); if (bundle_name && bundle_name.trim() === "Starter Pack") { url = new URL("https://buy.stripe.com/test_9B6fZiaPn5mQdoc0A57Zu03"); } if (bundle_name && bundle_name.trim() === "Momentum Pack") { url = new URL("https://buy.stripe.com/test_5kQ7sMg9H3eIfwkciN7Zu04"); } url.searchParams.append("customer_email", user_email); url.searchParams.append("client_reference_id", user_id); url.searchParams.append("prefilled_email", user_email); window.location.href = url.toString(); } let resolveLinkDateElements = []; let linkLastDayInMonth = null; let activeLinkDayElement = null; let resolveMobileLinkDateElements = []; let linkMobileLastDayInMonth = null; let activeMobileLinkDayElement = null; function loopLinkDaysInMonth(self, dateEl) { const leftArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); const rightArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); if (leftArrow.length > 0) leftArrow[0].disabled = true; if (rightArrow.length > 0) rightArrow[0].disabled = true; const btnFullDate = getDayMonthYearFromDateEl(dateEl); if (btnFullDate.btnMonth !== self.context.selectedMonth) { return; } const btnEl = dateEl.querySelector('[data-vc-date-btn]'); if (self.context.mainElement.id === linkCalendarElementId) { if (activeLinkDayElement === null) { const today = new Date(); if (today.getDate() === btnFullDate.btnDay && today.getMonth() === btnFullDate.btnMonth && today.getFullYear() === btnFullDate.btnYear) { activeLinkDayElement = btnEl; self.context.selectedDates = [dateEl.getAttribute('data-vc-date')]; } } } else { if (activeMobileLinkDayElement === null) { const today = new Date(); if (today.getDate() === btnFullDate.btnDay && today.getMonth() === btnFullDate.btnMonth && today.getFullYear() === btnFullDate.btnYear) { activeMobileLinkDayElement = btnEl; } } } const day = btnEl.innerText; if (parseInt(day) === 1) { if (self.context.mainElement.id === linkCalendarElementId) { resolveLinkDateElements = [dateEl]; linkLastDayInMonth = getLastDayOfMonth(btnFullDate.btnYear, convertIntMonthToString(btnFullDate.btnMonth)); } else { resolveMobileLinkDateElements = [dateEl]; linkMobileLastDayInMonth = getLastDayOfMonth(btnFullDate.btnYear, convertIntMonthToString(btnFullDate.btnMonth)); } } else { if (self.context.mainElement.id === linkCalendarElementId) { resolveLinkDateElements.push(dateEl); } else { resolveMobileLinkDateElements.push(dateEl); } } let endDayToCompare = null; if (self.context.mainElement.id === linkCalendarElementId) { endDayToCompare = linkLastDayInMonth; } else { endDayToCompare = linkMobileLastDayInMonth; } if (endDayToCompare.getDate() === btnFullDate.btnDay && endDayToCompare.getMonth() === btnFullDate.btnMonth && endDayToCompare.getFullYear() === btnFullDate.btnYear) { const today = new Date(); today.setHours(0, 0, 0, 0); getCurrentMonth(getLinkPageUserId(), endDayToCompare.toISOString(), function(currentMonth) { renderProPricing(currentMonth); let dateElements = null; if (self.context.mainElement.id === linkCalendarElementId) { dateElements = resolveLinkDateElements } else { dateElements = resolveMobileLinkDateElements } const busyDaysInMonth = currentMonth.busyDaysInMonth; for (const dateEl of dateElements) { const btnEl = dateEl.querySelector('[data-vc-date-btn]'); const btnDate = new Date(dateEl.getAttribute('data-vc-date')); btnDate.setHours(0, 0, 0, 0); const day = btnEl.innerText; if (btnDate < today) continue; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(btnEl); } } if (self.context.mainElement.id === linkCalendarElementId) { resolveLinkDateElements = []; } else { resolveMobileLinkDateElements = []; } if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; console.log("Days in month updated") }, function(error) { console.log("Error fetching slots:", error); if (self.context.mainElement.id === linkCalendarElementId) { resolveLinkDateElements = []; } else { resolveMobileLinkDateElements = []; } if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; }); } } function renderLinkCalendar(onChange, refreshSlots, elementId) { const { Calendar } = window.VanillaCalendarPro; const calendar = new Calendar( `#${elementId}`, { onCreateDateEls: loopLinkDaysInMonth, disableAllDates: true, enableDates:[getEnabledDates()], firstWeekday: 0, type: 'default', onClickArrow(self) { console.log("Month changed to", self.context.selectedYear, self.context.selectedMonth); onChange('month', convertIntMonthToString(self.context.selectedMonth)); self.selectedMonth = self.context.selectedMonth; self.selectedYear = self.context.selectedYear; }, onClickDate(self, pointerEvent) { const btnEl = pointerEvent.srcElement; console.log("Day clicked", self.context.selectedDates, btnEl); if (self.context.mainElement.id === linkCalendarElementId) { activeLinkDayElement = btnEl; } else { activeMobileLinkDayElement = btnEl; } onChange('value', self.context.selectedDates[0]); }, }); calendar.init(); setDayElementAsSelected(calendar, calendar.context.selectedDates[0]); calendar.selectedDates = []; if (refreshSlots) { const now = new Date(); refreshCalendarSlots("link-calendar-slots-list", now); refreshSlotsHeader(now); } return calendar; } const paymentSuccessfulPopupWrapperElementId = 'payment-successful-popup-wrapper'; function hidePaymentConfirmation() { const paymentConfirmationElement = document.getElementById(paymentSuccessfulPopupWrapperElementId); if (paymentConfirmationElement) { paymentConfirmationElement.style.visibility = "hidden"; paymentConfirmationElement.style.opacity = "0"; } } function showPaymentConfirmation() { const queryString = window.location.search; const params = new URLSearchParams(queryString); const paymentConfirmation = params.get("paymentConfirmation") || 'false'; if (paymentConfirmation === 'true') { const paymentConfirmationElement = document.getElementById(paymentSuccessfulPopupWrapperElementId); if (paymentConfirmationElement) { paymentConfirmationElement.innerHTML = ` `; paymentConfirmationElement.style.opacity = "1"; paymentConfirmationElement.style.visibility = "visible"; } } } let lastRateSettings = null; function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function onLoad() { renderTopMenu(); renderFooter(); setupTogglePairs(); loadFaqToggle(); initLinkButtonGlowEffect(); const params = new URLSearchParams(window.location.search); const wait = params.get("wait"); if (wait) { showLogoLoader() const start = Date.now(); while (Date.now() - start < 5000) { await sleep(1000); } hideLogoLoader(); window.location.href = window.location.origin + window.location.pathname; } if (typeof google !== 'undefined') { const params = new URLSearchParams(window.location.search); const role = params.get("role") || 'unknown'; google.accounts.id.initialize({ client_id: "1089541127844-jciirjufc6pli3d0af9o9s8mq4t0qk0d.apps.googleusercontent.com", callback: function (response) {console.info(`Google Auth Response for role ${role}: ${response}`);}, login_uri: "https://nectly.io/google-oauth-redirect", ux_mode: "redirect", context: "signup" }); const signUpButton = document.getElementById("nav-signup"); if (signUpButton) { google.accounts.id.renderButton( signUpButton, { type: "standard", shape: "rectangular", theme: "outline", text: "signin_with", size: "large", logo_alignment: "center", locale: "en", width: signUpButton.clientWidth, state: role, } ); } } if (document.getElementById(earlyAccessElementId)) renderEarlyAccess(); setupEarlyAccessToggle(); setupAiChatbot(); const calendarElement = document.getElementById(linkCalendarElementId); const mobileCalendarElement = document.getElementById(linkCalendarMobileElementId); if (calendarElement) { calendar = renderLinkCalendar(onCalendarChange, window.innerWidth >= 1024, linkCalendarElementId); } if (mobileCalendarElement) { mobileCalendar = renderLinkCalendar(onMobileCalendarChange, window.innerWidth < 1024, linkCalendarMobileElementId); } if (mobileCalendarElement || calendarElement) { renderTimeZoneString(); } const seekerCalendarElement = document.getElementById(seekerCalendarElementId); const mobileSeekerCalendarElement = document.getElementById(seekerCalendarMobileElementId); if (seekerCalendarElement) { seekerCalendar = renderSeekerCalendar(onSeekerCalendarChange, seekerCalendarElementId); seekerRefreshCalendarSlots(); } if (mobileSeekerCalendarElement) { mobileSeekerCalendar = renderSeekerCalendar(onMobileSeekerCalendarChange, seekerCalendarMobileElementId); seekerRefreshCalendarSlots(); } const proCalendarElement = document.getElementById(proCalendarElementId); const mobileProCalendarElement = document.getElementById(proCalendarMobileElementId); if (proCalendarElement) { proCalendar = renderProCalendar(onProCalendarChange, proCalendarElementId); proRefreshCalendarSlots(); } if (mobileProCalendarElement) { mobileProCalendar = renderProCalendar(onMobileProCalendarChange, proCalendarMobileElementId); proRefreshCalendarSlots(); } if (mobileSeekerCalendarElement || seekerCalendarElement) { renderTimeZoneString(); } setupResponsiveMenuToggle(); setupCancelMeetingToggle(); if (getMeta('meeting_cancellation') === 'True') { renderCancellationConfirmationElement(); } const dashboard = getMeta('dashboard'); if (dashboard === 'professional-dashboard.html') { getRate(); } showPaymentConfirmation(); } const NECTLY_USER_ROLE_SEEKER = "seeker"; const NECTLY_USER_ROLE_PROFESSIONAL = "professional"; const NECTLY_DAILY_SCHEDULE = "daily"; const NECTLY_WEEKLY_SCHEDULE = "weekly"; const NECTLY_MONTHLY_SCHEDULE = "monthly"; const NECTLY_EXACT_PERIOD_SCHEDULE = "exact_period" const postReserveSlotElementId = "link-post-reserve-slot"; const linkEmailInputElementId = 'link-calendar-professional-email'; const linkNameInputElementId = 'link-calendar-professional-name'; const linkPositionInputElementId = 'link-calendar-professional-position'; const linkMessageInputElementId = 'link-calendar-professional-message'; const linkDurationElementId = 'link-calendar-duration'; const linkPriceElementId = 'link-calendar-price'; const linkFeeElementId = 'link-calendar-fee'; const linkHeadDurationElementId = 'link-duration-element'; const linkDescriptionElementId = 'link-description-element'; const linkHeadPriceElementId = 'link-rate-element'; const linkHeadSubjectElementId = 'link-subject-description'; const earlyAccessElementId = 'early-access'; const seekerCalendarElementId = 'seeker-calendar'; const seekerCalendarMobileElementId = 'seeker-calendar-mobile'; const proCalendarElementId = 'pro-calendar'; const proCalendarMobileElementId = 'pro-calendar-mobile'; const linkCalendarElementId = 'link-calendar'; const linkCalendarMobileElementId = 'link-calendar-mobile'; function getUserRole(onSuccess, onError) { return callApi('/api/user/roles') .then(data => { console.log('User roles:', data); if (onSuccess) onSuccess(JSON.parse(data.response)); }) .catch(error => { console.error('Error fetching user role:', error); if (onError) onError(error); }); } function addUserRole(user_role, onSuccess, onError) { return callApi('/api/user/roles', 'POST', { role: user_role }) .then(data => { console.log('User role added:', data); if (onSuccess) onSuccess(data.response); }) .catch(error => { console.error('Error adding user role:', error); if (onError) onError(error); }); } function removeUserRole(user_role, onSuccess, onError) { return callApi('/api/user/roles', 'DELETE', { role: user_role }) .then(data => { console.log('User role removed:', data); if (onSuccess) onSuccess(data.response); }) .catch(error => { console.error('Error removing user role:', error); if (onError) onError(error); }); } function getCurrentMonth(seeker_id, selectedDate, onSuccess, onError) { return callApi(`/api/slots?userId=${seeker_id}&date=${selectedDate}`) .then(data => { console.log('Current month:', data); if (onSuccess) onSuccess(JSON.parse(data.response)); }) .catch((error) => { console.error('Error fetching current month:', error); if (onError) onError(error); /*onSuccess(JSON.parse( `{ "slots": [ { "startTime": "2023-10-01T09:00:00Z", "endTime": "2023-10-01T17:05:00Z", "title": "Meeting with John Doe", "description": "Discuss project updates and next steps." } ], "busyDaysInMonth": [ 4, 20, 25, 26 ], "seekerFullName": "John Doe" }` ));*/ }); } function refreshSlotsHeader(value) { const slotsHeader = document.getElementById("link-calendar-picked-date-header"); if (!slotsHeader) { console.error("Element with ID link-calendar-picked-date-header not found."); return; } const selectedDate = value; slotsHeader.innerHTML = selectedDate.toLocaleString('en-US', { weekday: 'long', month: 'short', day: 'numeric', hour12: true }); } function getLastDayOfMonth(year, month) { const intMonth = convertMonthStringToInt(month); const date = new Date(year, intMonth + 1, 0); return date; } function onCalendarChange(calendarParameter, value) { if (window.innerWidth < 1024) return; if (calendarParameter === "value") { console.log("Calendar value changed to", value); refreshCalendarSlots("link-calendar-slots-list", getCalendarSelectedDate(calendar)); refreshSlotsHeader(getCalendarSelectedDate(calendar)); calendar.selectedDates = calendar.context.selectedDates; } if (calendarParameter === "month") { console.log("Calendar month changed to", value); resetPostReserveSlotElement(); } } function onMobileCalendarChange(calendarParameter, value) { if (window.innerWidth >= 1024) return; if (calendarParameter === "value") { console.log("Mobile calendar value changed to", value); refreshCalendarSlots("link-calendar-slots-list", getCalendarSelectedDate(mobileCalendar)); refreshSlotsHeader(getCalendarSelectedDate(mobileCalendar)); mobileCalendar.selectedDates = mobileCalendar.context.selectedDates; } if (calendarParameter === "month" && mobileCalendar.month !== value) { console.log("Mobile calendar month changed to", value); resetPostReserveSlotElement(); } } function toggleConfirmationButton(disabled) { document.getElementById(linkEmailInputElementId).disabled = disabled; document.getElementById(linkNameInputElementId).disabled = disabled; document.getElementById("confirmMeetingButton").disabled = disabled; if (document.getElementById(linkPositionInputElementId)) document.getElementById(linkPositionInputElementId).disabled = disabled; if (document.getElementById(linkMessageInputElementId)) document.getElementById(linkMessageInputElementId).disabled = disabled; } function getVisibleCalendar() { if (window.innerWidth >= 1024) { return calendar; } else { return mobileCalendar; } } function updateCalendarDays(busyDaysInMonth) { console.log("Updating calendar days with busy days:", busyDaysInMonth); if (activeLinkDayElement) { const day = activeLinkDayElement.innerText; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(activeLinkDayElement); } } } function updateMobileCalendarDays(busyDaysInMonth) { console.log("Updating calendar days with busy days:", busyDaysInMonth); if (activeMobileLinkDayElement) { const day = activeMobileLinkDayElement.innerText; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(activeMobileLinkDayElement); } } } function renderProPricing(currentMonth) { if (currentMonth.proPricing && document.getElementById(linkDurationElementId) && document.getElementById(linkPriceElementId) && document.getElementById(linkFeeElementId)) { document.getElementById(linkDurationElementId).innerHTML = `${currentMonth.proPricing.length} minutes`; document.getElementById(linkPriceElementId).innerHTML = '$' + `${currentMonth.proPricing.wholePrice}
`; document.getElementById(linkFeeElementId).innerHTML = `($${currentMonth.proPricing.rate} expert + $${currentMonth.proPricing.fee} fee)`; } if (currentMonth.proPricing && document.getElementById(linkHeadDurationElementId)) { document.getElementById(linkHeadDurationElementId).innerHTML = `${currentMonth.proPricing.length}minutes`; } if (currentMonth.proPricing && document.getElementById(linkHeadPriceElementId)) { document.getElementById(linkHeadPriceElementId).innerHTML = `$${currentMonth.proPricing.rate}`; } if (currentMonth.proPricing && document.getElementById(linkHeadSubjectElementId)) { document.getElementById(linkHeadSubjectElementId).innerHTML = currentMonth.proPricing.description; } } function refreshCalendarSlots(targetElementId, selectedDate, calledFromPage) { function renderSlotFromTo(slot) { const startTime = new Date(slot.startTime); const endTime = new Date(slot.endTime); const fromDate = startTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toDate = endTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); return `${fromDate} - ${toDate}`; } function renderSlotRow(slot, message) { if (message) { const option = document.createElement('option'); option.value = message; option.textContent = message; return option; } const option = document.createElement('option'); option.value = `${slot.startTime}@${slot.endTime}`; option.textContent = renderSlotFromTo(slot); return option; } resetPostReserveSlotElement(); const slotsElement = document.getElementById(targetElementId); if (!slotsElement) { console.error(`Element with ID ${targetElementId} not found.`); return; } const seekerId = getLinkPageUserId(); if (!selectedDate) { selectedDate = getCalendarSelectedDate(getVisibleCalendar()); } slotsElement.innerHTML = ""; // Clear existing slots showLogoLoader(); getCurrentMonth(seekerId, selectedDate.toISOString(), function(currentMonth) { try { console.log("Fetched slots:", currentMonth.slots); renderProPricing(currentMonth); updateCalendarDays(currentMonth.busyDaysInMonth); updateMobileCalendarDays(currentMonth.busyDaysInMonth); document.getElementById("link-welcome-message").innerHTML = `Connect with ${currentMonth.seekerFullName}.`; if (currentMonth.slots?.length === 0) { const errorMessage = `
No Available Slots Icon

No available slots for this day.

Try another date.

`; const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } toggleConfirmationButton(true); hideLogoLoader(); return; } currentMonth.slots.forEach(slot => { slotsElement.appendChild(renderSlotRow(slot)); toggleConfirmationButton(false); }); if (calledFromPage) { calendar.update(); mobileCalendar.update(); } } catch (error) { console.error("Error processing slots:", error); try { retrievingSlotsFailed(); toggleConfirmationButton(true); } catch (e) { console.error("Error handling fetch slots error:", e); } } hideLogoLoader(); }, function(error) { console.error("Error fetching slots:", error); try { if (error.status === 400 && error.error === "400: Selected date cannot be in the past") { selectedDateIsinThePast(); } else { retrievingSlotsFailed() } toggleConfirmationButton(true); } catch (e) { console.error("Error handling fetch slots error:", e); } hideLogoLoader(); }); } function getLinkPageUserId() { return getMeta('user-id'); } function selectedDateIsinThePast() { const errorMessage = `

Selected date cannot be in the past.

Don’t worry, just choose another slot to connect.

`; const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } } function retrievingSlotsFailed() { const errorMessage = `

Retrieving slots failed.

Try to refresh later.

`; const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } } function onReserveSlotError() { const errorMessage = `

That time’s no longer available-someone else grabbed it.

Don’t worry, just choose another slot to connect.

`; const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } } function onReserveSlotSuccess() { const successMessage = `
Email Sent Icon

You’re all set.

Check your inbox for the details-and enjoy the conversation.

`; const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = successMessage; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } } function resetPostReserveSlotElement() { const postReserveSlotElement = document.getElementById(postReserveSlotElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = ""; } else { console.error(`Element with ID ${postReserveSlotElementId} not found.`); } } function validateEmail(email) { return String(email).toLowerCase().match( /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ); } function reserveSlot(targetElementId, stripePublicKey) { function getLinkType() { if (window.location.href.indexOf('/link/') !== -1) { return NECTLY_USER_ROLE_PROFESSIONAL; } else if (window.location.href.indexOf('/connect/') !== -1) { return NECTLY_USER_ROLE_SEEKER; } } const attendee = document.getElementById(linkEmailInputElementId).value; const attendeeName = document.getElementById(linkNameInputElementId).value; const attendeePosition = document.getElementById(linkPositionInputElementId)?.value || ""; const attendeeMessage = document.getElementById(linkMessageInputElementId)?.value || ""; const validAttendee = validateEmail(attendee); if (!validAttendee) { console.log("Invalid email address:", attendee); document.getElementById(linkEmailInputElementId).focus(); return; } if (!attendeeName || attendeeName.trim() === "" || attendeeName.length > 60 || attendeeName.indexOf('"') !== -1) { console.log("Invalid name:", attendeeName); document.getElementById(linkNameInputElementId).focus(); return; } const slotsElement = document.getElementById(targetElementId); if (!slotsElement) { console.error(`Element with ID ${targetElementId} not found.`); return; } const selectedOption = slotsElement.options[slotsElement.selectedIndex]; if (!selectedOption) { console.error("No slot selected."); return; } toggleConfirmationButton(true); const startTime = selectedOption.value.split('@')[0]; const endTime = selectedOption.value.split('@')[1]; console.log("Reserving slot from", startTime, "to", endTime); const reserveSlotBody = { "startTime": startTime, "endTime": endTime, "attendee": attendee, "attendeeName": attendeeName, "attendeePosition": attendeePosition, "attendeeMessage": attendeeMessage, } if (getLinkType() === NECTLY_USER_ROLE_SEEKER) { console.log("Reserve slot body:", reserveSlotBody); const seeker_id = getLinkPageUserId(); console.log("Seeker ID:", seeker_id); callApi(`/api/slots?userId=${seeker_id}`, 'POST', reserveSlotBody) .then(data => { console.log('Current month:', data) onReserveSlotSuccess(); }) .catch((error) => { console.error('Error reserving slot:', error); onReserveSlotError(); }); } else if (getLinkType() === NECTLY_USER_ROLE_PROFESSIONAL) { const professional_id = getLinkPageUserId(); reserveSlotBody["professionalId"] = professional_id; console.log("Professional ID:", professional_id); console.log("Reserve professional slot body:", reserveSlotBody); /*callApi(`/api/pro-slots?userId=${professional_id}`, 'POST', reserveSlotBody) .then(data => { console.log('Current month:', data) const body = JSON.parse(data.response); var url = new URL("https://buy.stripe.com/test_9B6eVe5v3cPi5VKgz37Zu05"); url.searchParams.append("customer_email", attendee); url.searchParams.append("client_reference_id", `${professional_id}---${body.calendarId}---${body.appointmentId}`); url.searchParams.append("prefilled_email", attendee); window.location.href = url.toString(); }) .catch((error) => { console.error('Error reserving slot:', error); onReserveSlotError(); });*/ startStripePayment(stripePublicKey, reserveSlotBody, professional_id) } } function setupResponsiveMenuToggle() { const hamburger = document.getElementById('hamburger-btn'); const menuWrapper = document.getElementById('menu-items-wrapper'); if (!hamburger || !menuWrapper) return; let isManuallyOpened = false; function updateMenuDisplay() { if (window.innerWidth > 1024) { menuWrapper.classList.add('active'); hamburger.classList.remove('active'); isManuallyOpened = false; } else { if (!isManuallyOpened) { menuWrapper.classList.remove('active'); hamburger.classList.remove('active'); } } } window.addEventListener('resize', updateMenuDisplay); updateMenuDisplay(); hamburger.addEventListener('click', () => { if (window.innerWidth <= 1024) { const isActive = hamburger.classList.toggle('active'); menuWrapper.classList.toggle('active'); isManuallyOpened = isActive; } }); } function setupEarlyAccessToggle() { const toggle = document.getElementById('earlyAccessToggle'); const close = document.getElementById('closeEarlyAccessToggle'); const content = document.getElementById('earlyAccessContent'); if (!toggle || !close || !content) return; const container = toggle.closest('.early-access-toggle-container'); if (!container) return; toggle.addEventListener('click', () => { if (!container.classList.contains('open')) { content.style.height = content.scrollHeight + 'px'; container.classList.add('open'); } }); close.addEventListener('click', () => { content.style.height = '0px'; container.classList.remove('open'); }); } function renderTimeZoneString() { const timeZoneString = (new Date()).toLocaleDateString("en-US", {timeZoneName: "long"}).split(', ')[1]; const innerHTMLString = `

Time zone

${timeZoneString}

`; const timeZoneElement = document.getElementById("time-zone-container"); if (timeZoneElement) { timeZoneElement.innerHTML = innerHTMLString; } else { console.info("Element with ID time-zone-container not found."); } const timeZoneMobileElement = document.getElementById("time-zone-container-mobile"); if (timeZoneMobileElement) { timeZoneMobileElement.innerHTML = innerHTMLString; } else { console.info("Element with ID time-zone-container-mobile not found."); } } function renderFooter() { const footer = document.getElementById('footer'); if (footer) { footer.innerHTML = ` `; } else { console.error("Element with ID footer not found."); } } function renderTopMenu() { const topMenu = document.getElementById('menu'); if (topMenu) { const dashboard = getMeta('dashboard'); const isProfessionalDashboard = dashboard === 'professional-dashboard.html'; const isSeekerDashboard = dashboard === 'seekers-dashboard.html'; let loginIsHidden = getMeta('login-is-hidden'); const logoutIsHidden = getMeta('logout-is-hidden'); let hideAvatarMenu = 'style="display:none"'; if (logoutIsHidden.trim().length === 0) hideAvatarMenu = ''; let settingsLink = '/settings.html'; if (isProfessionalDashboard) settingsLink = '/rate-settings.html'; const settingsHtml = ` Settings `; const connectForOpportunities = ` Connect for Opportunities `; const connectAndEarn = ` Connect & Earn `; let topLinks = ''; if (isProfessionalDashboard) { topLinks = connectAndEarn; } else if (isSeekerDashboard) { topLinks = connectForOpportunities; } else { topLinks = connectAndEarn + connectForOpportunities; } let pictureHtml = ''; if (getMeta('picture') && getMeta('picture').trim().length > 0 && getMeta('picture') !== 'None') { pictureHtml = `User Avatar`; } if (loginIsHidden) loginIsHidden = 'style="display:none;"' topMenu.innerHTML = ` `; if (document.querySelector('.scroll-container')) { topMenu.innerHTML += `
`; safeInitSwipeSections(); } } } function renderEarlyAccess() { let loginIsHidden = getMeta('login-is-hidden'); if (loginIsHidden) { return; } const earlyAccess = document.getElementById(earlyAccessElementId); if (!earlyAccess) { console.error('Element with ID early-access not found.'); return; } earlyAccess.innerHTML = `
Close Icon

Try it first. Shape what’s next.

We’re launching soon-but a few curious minds get early access. Join our beta, test it out, and help us make Nectly even better.

Join the Beta Waitlist

Your role:

`; } const earlyAccessEmailElementId = "earlyAccessEmail"; const earlyAccessResultMessageElementId = "earlyAccessResultMessage"; const earlySeekerCheckBoxElementId = "earlySeekerCheckBox"; const earlyProfessionalCheckBoxElementId = "earlyProfessionalCheckBox"; const earlyAccessButtonElementId = "earlyAccessButton"; const cancellationConfirmationElementId = "cancellation-confirmation"; const rateSettingsSaveConfirmationElementId = "rate-settings-save-confirmation"; function sendEarlyAccessEmail(earlyAccessSecret) { const successMessage = `

You’re on the list-thank you!

We’ll be in touch soon with early access details. Can’t wait to see where your first Nectly conversation takes you.

`; const errorMessage = `

Joining the Beta Waitlist failed.

Try again later.

`; const earlyAccessResultMessageElement = document.getElementById(earlyAccessResultMessageElementId); if (!earlyAccessResultMessageElement) { console.error(`Element with ID ${earlyAccessResultMessageElementId} not found.`); return; } const attendee = document.getElementById(earlyAccessEmailElementId).value; const validAttendee = validateEmail(attendee); if (!validAttendee) { console.log("Invalid email address:", attendee); document.getElementById(earlyAccessEmailElementId).focus(); return; } const earlyAccessRoleSeeker = document.getElementById(earlySeekerCheckBoxElementId).checked; const earlyAccessRoleProfessional = document.getElementById(earlyProfessionalCheckBoxElementId).checked; if (!earlyAccessRoleSeeker && !earlyAccessRoleProfessional) { console.log("No role selected."); earlyAccessRoleSeeker.focus(); return; } const earlyAccessButtonElement = document.getElementById(earlyAccessButtonElementId); if (!earlyAccessButtonElement) { console.error(`Element with ID ${earlyAccessButtonElementId} not found.`); return; } earlyAccessButtonElement.disabled = true; callApi(`/api/early-access`, 'POST', { "email": attendee, "role": earlyAccessRoleSeeker ? "seeker" : "professional", "secret": earlyAccessSecret }).then(data => { earlyAccessResultMessageElement.innerHTML = successMessage; console.error('Early access email sent successfully:', data); }) .catch((error) => { earlyAccessResultMessageElement.innerHTML = errorMessage; console.error('Error sending early access email:', error); }) } function getProCurrentDate(selectedDate, timezone, onSuccess, onError) { return callApi(`/api/professional-slots?date=${selectedDate}&tzOffsetInMinutes=${timezone}`) .then(data => { console.log('Current month:', data); if (onSuccess) onSuccess(JSON.parse(data.response)); }) .catch((error) => { console.error('Error fetching current month:', error); if (onError) onError(error); }); } function getSeekerCurrentDate(selectedDate, timezone, onSuccess, onError) { return callApi(`/api/seeker-slots?date=${selectedDate}&tzOffsetInMinutes=${timezone}`) .then(data => { console.log('Current month:', data); if (onSuccess) onSuccess(JSON.parse(data.response)); }) .catch((error) => { console.error('Error fetching current month:', error); if (onError) onError(error); /*onSuccess(JSON.parse( `{ "freeSlots": [ { "slotType": "exact_period", "startTime": "2025-05-29T08:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T08:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T08:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T08:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T09:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T09:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T09:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T09:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T10:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T10:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T10:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T10:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T11:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T11:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T11:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T11:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T12:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T12:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T12:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T12:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T13:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T13:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T13:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T13:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T14:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T14:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T14:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T14:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T15:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T15:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T15:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T15:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T16:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T16:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T16:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T16:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T17:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T17:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T18:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T18:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T18:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T18:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T19:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T19:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-29T19:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-29T19:50:00+02:00" } ], "createdSlots": [], "appointments": [ { "createdAt": "2025-05-23T10:56:48.423125+00:00", "attendees": [ "zeman.silvie@gmail.com" ], "appointmentId": "826ac3a9-61cd-43c8-a45f-6dbd35e2d83b", "description": null, "startTime": "2025-05-29T17:00:00+02:00", "location": "", "endTime": "2025-05-29T17:20:00+02:00", "title": "" }, { "createdAt": "2025-05-27T16:15:46.941689+00:00", "attendees": [ "tvavra1@gmail.com" ], "appointmentId": "b2757583-55ca-44bf-8411-9fe7ebd3603a", "description": null, "startTime": "2025-05-30T15:30:00+02:00", "location": "https://meet.google.com/xww-krkr-mso", "endTime": "2025-05-30T15:50:00+02:00", "title": "" }, { "createdAt": "2025-05-23T17:57:37.550312+00:00", "attendees": [ "jakub@zeman.wtf" ], "appointmentId": "57d3199f-320a-44a3-9969-a664e4b20ea3", "description": null, "startTime": "2025-05-30T17:00:00+02:00", "location": "https://meet.google.com/uok-toqp-iqm", "endTime": "2025-05-30T17:20:00+02:00", "title": "" }, { "createdAt": "2025-05-23T16:40:39.399717+00:00", "attendees": [ "jakub@zeman.wtf" ], "appointmentId": "819860f3-8afb-46c7-9543-2aaad2723027", "description": null, "startTime": "2025-05-30T17:30:00+02:00", "location": "https://meet.google.com/vvj-avvn-oop", "endTime": "2025-05-30T17:50:00+02:00", "title": "" } ], "createdSlotsToDate": [], "createdSlotsToday": [], "allCreatedSlots": [ { "slotType": "exact_period", "startTime": "2025-05-30T16:00:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-30T16:20:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-30T16:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-30T16:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-31T12:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-31T12:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-05-31T15:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-05-31T15:50:00+02:00" }, { "slotType": "exact_period", "startTime": "2025-06-18T13:30:00+02:00", "title": "", "repeatCount": "-1", "endTime": "2025-06-18T13:50:00+02:00" } ], "appointmentsToDate": [ { "createdAt": "2025-05-27T16:15:46.941689+00:00", "attendees": [ "tvavra1@gmail.com" ], "appointmentId": "b2757583-55ca-44bf-8411-9fe7ebd3603a", "description": null, "startTime": "2025-05-30T15:30:00+02:00", "location": "https://meet.google.com/xww-krkr-mso", "endTime": "2025-05-30T15:50:00+02:00", "title": "" }, { "createdAt": "2025-05-23T17:57:37.550312+00:00", "attendees": [ "jakub@zeman.wtf" ], "appointmentId": "57d3199f-320a-44a3-9969-a664e4b20ea3", "description": null, "startTime": "2025-05-30T17:00:00+02:00", "location": "https://meet.google.com/uok-toqp-iqm", "endTime": "2025-05-30T17:20:00+02:00", "title": "" }, { "createdAt": "2025-05-23T16:40:39.399717+00:00", "attendees": [ "jakub@zeman.wtf" ], "appointmentId": "819860f3-8afb-46c7-9543-2aaad2723027", "description": null, "startTime": "2025-05-30T17:30:00+02:00", "location": "https://meet.google.com/vvj-avvn-oop", "endTime": "2025-05-30T17:50:00+02:00", "title": "" } ], "appointmentsToday": [], "appointmentsInMonth": [ 29, 30 ], "slotsInMonth": [ 30, 31 ], "countOfPaidSessions": 86, "countOfUsedSession": 0, "paymentMethodSet": true }` ));*/ }); } const postReserveSlotSeekerElementId = 'seeker-post-reserve-slot'; const seekerNewSessionDateSelect = 'seeker-date-slots-list'; const seekerNewSessionTimeSelect = 'seeker-calendar-slots-list'; const confirmMeetingButtonElementId = 'confirmMeetingButton'; const seekerAppointmentListElementId = 'seeker-appointment-list-container'; const seekerSlotsListElementId = 'seeker-slots-list-container'; const sessionBalanceSummaryElementId = 'session-balance-summary'; const seekerSessionBalanceSummaryElementId = 'seeker-session-balance-element'; const seekerNoAppointmentsElementId = 'seeker-no-appointments'; const seekerNoSlotsElementId = 'seeker-no-slots'; const seekerTodayAppointmentsElementId = "seeker-today-appointments"; const seekerTodaySlotsElementId = "seeker-today-slots"; const seekerAppointmentsElementId = "seeker-appointments"; const seekerSlotsElementId = "seeker-slots"; const seekerAppointmentsDateTabElementId = "seeker-appointments-date-tab"; const seekerAppointmentsAllTabElementId = "seeker-appointments-all-tab"; const seekerSlotsDateTabElementId = "seeker-slots-date-tab"; const seekerSlotsAllTabElementId = "seeker-slots-all-tab"; const seekerRecurringSelectionElementId = "seeker-reccuring-selection"; const seekerBalanceProgressBarElementId = "session-balance-progress-bar"; const seekerSetupYourAccountMessageElementId = "seeker-setup-your-account-message"; const seekerSetPaymentMethodElementId = "seeker-set-payment-method"; const seekerAddYourAvailabilityElementId = "seeker-add-your-availability"; const seekerShareLinkElementId = "seeker-share-link"; const postReserveSlotProElementId = 'pro-post-reserve-slot'; const proNewSessionDateSelect = 'pro-date-slots-list'; const proNewSessionTimeSelect = 'pro-calendar-slots-list'; const proAppointmentListElementId = 'pro-appointment-list-container'; const proSlotsListElementId = 'pro-slots-list-container'; const proNoAppointmentsElementId = 'pro-no-appointments'; const proNoSlotsElementId = 'pro-no-slots'; const proTodayAppointmentsElementId = "pro-today-appointments"; const proTodaySlotsElementId = "pro-today-slots"; const proAppointmentsElementId = "pro-appointments"; const proSlotsElementId = "pro-slots"; const proAppointmentsDateTabElementId = "pro-appointments-date-tab"; const proAppointmentsAllTabElementId = "pro-appointments-all-tab"; const proSlotsDateTabElementId = "pro-slots-date-tab"; const proSlotsAllTabElementId = "pro-slots-all-tab"; const proRecurringSelectionElementId = "pro-reccuring-selection"; const proSetPaymentMethodStripeElementId = "pro-set-payment-method-stripe"; const proSetPaymentMethodStripeOrElementId = "pro-set-payment-method-or"; const proSetPaymentMethodCharityElementId = "pro-set-payment-method-charity"; const proSetPaymentMethodCharityToolTipElementId = "pro-set-payment-method-charity-tooltip"; const proSetPaymentMethodCharityConfirmationElementId = "pro-set-payment-method-charity-confirmation"; const proLinkElementId = "pro-link"; const proShareLinkElementId = "pro-share-link"; const proSetupYourAccountMessageElementId = "pro-setup-your-account-message"; const proEarningsElementId = "pro-earnings"; const proAddYourAvailabilityElementId = "pro-add-your-availability"; function resetSeekerSetup() { const seekerSetupYourAccountMessageElement = document.getElementById(seekerSetupYourAccountMessageElementId); if (seekerSetupYourAccountMessageElement) { seekerSetupYourAccountMessageElement.innerHTML = ""; seekerSetupYourAccountMessageElement.className = ""; } const seekerSetPaymentMethodElement = document.getElementById(seekerSetPaymentMethodElementId); if (seekerSetPaymentMethodElement) { seekerSetPaymentMethodElement.innerHTML = ""; seekerSetPaymentMethodElement.className = ""; } const seekerAddYourAvailabilityElement = document.getElementById(seekerAddYourAvailabilityElementId); if (seekerAddYourAvailabilityElement) { seekerAddYourAvailabilityElement.innerHTML = ""; seekerAddYourAvailabilityElement.className = ""; } const seekerShareLinkElement = document.getElementById(seekerShareLinkElementId); if (seekerShareLinkElement) { seekerShareLinkElement.innerHTML = ""; seekerShareLinkElement.className = ""; } } function resetProSetup() { const proSetPaymentMethodStripeElement = document.getElementById(proSetPaymentMethodStripeElementId); if (proSetPaymentMethodStripeElement) { proSetPaymentMethodStripeElement.innerHTML = ""; proSetPaymentMethodStripeElement.className = ""; } const proSetPaymentMethodStripeOrElement = document.getElementById(proSetPaymentMethodStripeOrElementId); if (proSetPaymentMethodStripeOrElement) { proSetPaymentMethodStripeOrElement.innerHTML = ""; proSetPaymentMethodStripeOrElement.className = ""; } const proSetPaymentMethodCharityElement = document.getElementById(proSetPaymentMethodCharityElementId); if (proSetPaymentMethodCharityElement) { proSetPaymentMethodCharityElement.innerHTML = ""; proSetPaymentMethodCharityElement.className = ""; } const proSetPaymentMethodCharityToolTipElement = document.getElementById(proSetPaymentMethodCharityToolTipElementId); if (proSetPaymentMethodCharityToolTipElement) { proSetPaymentMethodCharityToolTipElement.innerHTML = ""; proSetPaymentMethodCharityToolTipElement.className = ""; } const proSetupYourAccountMessageElement = document.getElementById(proSetupYourAccountMessageElementId); if (proSetupYourAccountMessageElement) { proSetupYourAccountMessageElement.innerHTML = ""; proSetupYourAccountMessageElement.className = ""; } const proAddYourAvailabilityElement = document.getElementById(proAddYourAvailabilityElementId); if (proAddYourAvailabilityElement) { proAddYourAvailabilityElement.innerHTML = ""; proAddYourAvailabilityElement.className = ""; } const proLinkElement = document.getElementById(proLinkElementId); if (proLinkElement) { proLinkElement.innerHTML = ""; proLinkElement.className = ""; } const proShareLinkElement = document.getElementById(proShareLinkElementId); if (proShareLinkElement) { proShareLinkElement.innerHTML = ""; proShareLinkElement.className = ""; } } function renderProCharityConfirmationElement(elementId=proSetPaymentMethodCharityToolTipElementId) { const element = document.getElementById(elementId); if (element) { const icon = 'Information Icon'; const toolTipClass = "tooltip-content"; const mainClassName = "notification-message-container"; const text = `

All your session payouts will go to Stand Up To Cancer. You’re turning conversations into real contribution-with no extra steps.

You can update this anytime under Account → Payout methods.

Thank you for using your time to do something extraordinary.

`; let html = `
${icon}
${text}
`; let elementClassName = "tolltip-container flex flex-column halfgap"; if (elementId !== proSetPaymentMethodCharityToolTipElementId) { elementClassName = 'notification-message notification-position'; html = 'Donation set – thank you.'; } element.className = elementClassName; element.innerHTML = html; if (elementId !== proSetPaymentMethodCharityToolTipElementId) { element.classList.add('show'); } } else { console.error(`Element with ID ${elementId} not found.`); } } function renderCancellationConfirmationElement() { const cancellationConfirmationElement = document.getElementById(cancellationConfirmationElementId); if (cancellationConfirmationElement) { cancellationConfirmationElement.className = "notification-message notification-position"; cancellationConfirmationElement.innerHTML = `

Your Nectly conversation was canceled.

We’ve let the other side know. Check your email for details and next steps.

`; cancellationConfirmationElement.classList.add('show'); cancellationConfirmationElement.classList.remove('slide-down'); cancellationConfirmationElement.classList.add('slide-up'); setTimeout(function() { cancellationConfirmationElement.classList.remove('slide-up'); cancellationConfirmationElement.classList.add('slide-down'); }, 5000); } else { console.error(`Element with ID ${cancellationConfirmationElementId} not found.`); } } function cancelRateSettingsSave() { const rateSettingsSaveConfirmationElement = document.getElementById(rateSettingsSaveConfirmationElementId); if (rateSettingsSaveConfirmationElement) { rateSettingsSaveConfirmationElement.classList.remove('slide-up'); rateSettingsSaveConfirmationElement.classList.add('slide-down'); rateSettingsSaveConfirmationElement.className = ""; rateSettingsSaveConfirmationElement.innerHTML = ''; } else { console.error(`Element with ID ${rateSettingsSaveConfirmationElementId} not found.`); } } function rateSettingsSave() { const rateSettingsSaveConfirmationElement = document.getElementById(rateSettingsSaveConfirmationElementId); if (rateSettingsSaveConfirmationElement) { rateSettingsSaveConfirmationElement.classList.remove('slide-up'); rateSettingsSaveConfirmationElement.classList.add('slide-down'); rateSettingsSaveConfirmationElement.className = ""; rateSettingsSaveConfirmationElement.innerHTML = ''; } else { console.error(`Element with ID ${rateSettingsSaveConfirmationElementId} not found.`); } setRate(); } function renderRateSettingsSaveConfirmationElement() { if (lastRateSettings === null) { setRate(); return; } if (parseInt(document.getElementById('length-input').value) === lastRateSettings?.length && parseInt(document.getElementById('buffer-input').value) === lastRateSettings?.buffer) { setRate(); return; } const rateSettingsSaveConfirmationElement = document.getElementById(rateSettingsSaveConfirmationElementId); if (rateSettingsSaveConfirmationElement) { rateSettingsSaveConfirmationElement.className = "notification-message notification-position"; rateSettingsSaveConfirmationElement.innerHTML = `

You changed meeting length or buffer!

If you change meeting length or buffer all your configured slots will be deleted!

`; rateSettingsSaveConfirmationElement.classList.add('show'); rateSettingsSaveConfirmationElement.classList.remove('slide-down'); rateSettingsSaveConfirmationElement.classList.add('slide-up'); } else { console.error(`Element with ID ${rateSettingsSaveConfirmationElementId} not found.`); } } function renderProPaymentMethodElement(index) { const proSetPaymentMethodStripeElement = document.getElementById(proSetPaymentMethodStripeElementId); if (proSetPaymentMethodStripeElement) { let indexHeader = ""; if (index > 0) { indexHeader = `

${index}A

`; } proSetPaymentMethodStripeElement.className = "setup-account-container flex flex-column halfgap"; proSetPaymentMethodStripeElement.innerHTML = `
${indexHeader}

Get Paid to Your Account

`; } else { console.error(`Element with ID ${proSetPaymentMethodStripeElementId} not found.`); } const proSetPaymentMethodStripeOrElement = document.getElementById(proSetPaymentMethodStripeOrElementId); if (proSetPaymentMethodStripeOrElement) { proSetPaymentMethodStripeOrElement.className = "text-center"; proSetPaymentMethodStripeOrElement.innerHTML = `OR`; } else { console.error(`Element with ID ${proSetPaymentMethodStripeOrElementId} not found.`); } const proSetPaymentMethodCharityElement = document.getElementById(proSetPaymentMethodCharityElementId); if (proSetPaymentMethodCharityElement) { renderProCharityConfirmationElement(); let indexHeader = ""; if (index > 0) { indexHeader = `

${index}B

`; } proSetPaymentMethodCharityElement.className = "setup-account-container flex flex-column halfgap"; proSetPaymentMethodCharityElement.innerHTML = `
${indexHeader}

Donate to Stand Up To Cancer

`; } else { console.error(`Element with ID ${proSetPaymentMethodCharityElementId} not found.`); } } function renderProLinkElement(index, paymentAccountEligibleForPayout) { const proLinkElement = document.getElementById(proLinkElementId); if (proLinkElement) { proLinkElement.className = "setup-account-container flex flex-column halfgap"; if (index > 0) { proLinkElement.innerHTML = `

${index}

Your link becomes active!

Once payment method is set, you’ll see your personal Nectly link activated here-ready to share.

`; } else { if (paymentAccountEligibleForPayout) { proLinkElement.className = "setup-account-container flex flex-column halfgap"; proLinkElement.innerHTML = `

Add it to LinkedIn. Share it in DMs. Link it on your site. Wherever you connect, your Nectly link helps you say yes - on your terms, with zero friction.

`; setupNectlyLinkButtonAnimation(); } else { proLinkElement.innerHTML = `

Your payment method is set but not eligible for payouts. Please login to your Stripe account and finish the onboarding process.

`; } } } else { console.error(`Element with ID ${proLinkElementId} not found.`); } } function renderProShareLinkElement(index) { const proShareLinkElement = document.getElementById(proShareLinkElementId); if (proShareLinkElement) { let indexHeader = ""; if (index > 0) { indexHeader = `

${index}

`; } proShareLinkElement.className = "setup-account-container flex flex-column halfgap"; proShareLinkElement.innerHTML = `
${indexHeader}

Share Your Link Everywhere You Connect

`; } else { console.error(`Element with ID ${proShareLinkElementId} not found.`); } } function getVisibleProCalendar() { if (window.innerWidth >= 1024) { return proCalendar || mobileProCalendar; } else { return mobileProCalendar || proCalendar; } } function proSlotsFailed() { const errorMessage = `

Retrieving slots failed.

Try to refresh later.

`; const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } } function getVisibleSeekerCalendar() { if (window.innerWidth >= 1024) { return seekerCalendar || mobileSeekerCalendar; } else { return mobileSeekerCalendar || seekerCalendar; } } let resolveProDateElements = []; let proLastDayInMonth = null; let activeProDayElement = null; function loopProDaysInMonth(self, dateEl) { const leftArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); const rightArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); if (leftArrow.length > 0) leftArrow[0].disabled = true; if (rightArrow.length > 0) rightArrow[0].disabled = true; const btnFullDate = getDayMonthYearFromDateEl(dateEl); const btnEl = dateEl.querySelector('[data-vc-date-btn]'); if (dateEl.getAttribute('data-vc-date-week-day') === '6' || dateEl.getAttribute('data-vc-date-week-day') === '0') { btnEl.classList.add('disabled'); btnEl.disabled = true; } if (btnFullDate.btnMonth !== self.context.selectedMonth) { return; } if (activeProDayElement === null) { const today = new Date(); today.setHours(0, 0, 0, 0); if (today.getDay() === 0) today.setDate(today.getDate() + 1); if (today.getDay() === 6) today.setDate(today.getDate() + 2); if (today.getDate() === btnFullDate.btnDay && today.getMonth() === btnFullDate.btnMonth && today.getFullYear() === btnFullDate.btnYear) { activeProDayElement = btnEl; self.context.selectedDates = [dateEl.getAttribute('data-vc-date')]; } } const day = btnEl.innerText; if (parseInt(day) === 1) { resolveProDateElements = [dateEl]; proLastDayInMonth = getLastDayOfMonth(btnFullDate.btnYear, convertIntMonthToString(btnFullDate.btnMonth)); } else { resolveProDateElements.push(dateEl); } if (proLastDayInMonth.getDate() === btnFullDate.btnDay && proLastDayInMonth.getMonth() === btnFullDate.btnMonth && proLastDayInMonth.getFullYear() === btnFullDate.btnYear) { const today = new Date(); today.setHours(0, 0, 0, 0); getProCurrentDate(proLastDayInMonth.toISOString(), proLastDayInMonth.getTimezoneOffset(), function(currentDay) { const busyDaysInMonth = mergeAppointmentsAndSlots(currentDay); for (const dateEl of resolveProDateElements) { const btnEl = dateEl.querySelector('[data-vc-date-btn]'); const btnDate = new Date(dateEl.getAttribute('data-vc-date')); btnDate.setHours(0, 0, 0, 0); const day = btnEl.innerText; if (btnDate < today) continue; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(btnEl); } } resolveProDateElements = []; if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; console.log("Days in month updated") }, function(error) { console.log("Error fetching slots:", error); resolveProDateElements = []; if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; }); } } function renderProCalendar(onChange, elementId) { const { Calendar } = window.VanillaCalendarPro; const calendar = new Calendar( `#${elementId}`, { onCreateDateEls: loopProDaysInMonth, disableAllDates: true, enableDates:[getEnabledDates()], firstWeekday: 0, type: 'default', onClickArrow(self) { console.log("Month changed to", self.context.selectedYear, self.context.selectedMonth); onChange('month', convertIntMonthToString(self.context.selectedMonth)); self.selectedMonth = self.context.selectedMonth; self.selectedYear = self.context.selectedYear; }, onClickDate(self, pointerEvent) { const btnEl = pointerEvent.srcElement; console.log("Day clicked", self.context.selectedDates, btnEl); activeProDayElement = btnEl; onChange('value', self.context.selectedDates[0]); }, }); calendar.init(); setDayElementAsSelected(calendar, calendar.context.selectedDates[0]); calendar.selectedDates = []; return calendar; } function updateProCalendarDays(busyDaysInMonth) { console.log("Updating calendar days with busy days:", busyDaysInMonth); if (activeProDayElement) { const day = activeProDayElement.innerText; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(activeProDayElement); } } } function onProCalendarChange(calendarParameter, value) { if (calendarParameter === "value") { console.log("Calendar value changed to", value); resetProAppointmentListContainer(); proRefreshCalendarSlots(); } if (calendarParameter === "month") { console.log("Calendar month changed to", value); } } function onMobileProCalendarChange(calendarParameter, value) { if (calendarParameter === "value") { console.log("Professional Mobile calendar value changed to", value); resetProAppointmentListContainer(); proRefreshCalendarSlots(); } if (calendarParameter === "month") { console.log("Professional Mobile calendar month changed to", value); } } function renderProSetupAvailabilityElement(index) { const proAddYourAvailabilityElement = document.getElementById(proAddYourAvailabilityElementId); if (proAddYourAvailabilityElement) { proAddYourAvailabilityElement.className = "setup-account-container flex flex-column halfgap"; proAddYourAvailabilityElement.innerHTML = `

${index}

Add your availability

`; } else { console.error(`Element with ID ${proAddYourAvailabilityElementId} not found.`); } } function renderProSetup(currentDay) { resetProSetup(); let countOfSetupSteps = 0; if (currentDay.allCreatedSlots.length === 0) countOfSetupSteps++; if (!currentDay.paymentAccount && !currentDay.charityPayout) countOfSetupSteps++; if (countOfSetupSteps === 0) { renderProLinkElement(0, currentDay.paymentAccountEligibleForPayout); } else { const proSetupYourAccountMessageElement = document.getElementById(proSetupYourAccountMessageElementId); if (proSetupYourAccountMessageElement) { proSetupYourAccountMessageElement.innerHTML = `Setup your Account`; } else { console.error(`Element with ID ${proSetupYourAccountMessageElementId} not found.`); } let index = 1; if (!currentDay.paymentAccount && !currentDay.charityPayout) renderProPaymentMethodElement(index++); if (currentDay.allCreatedSlots.length === 0) renderProSetupAvailabilityElement(index++); renderProLinkElement(index++, currentDay.paymentAccountEligibleForPayout); renderProShareLinkElement(index++); } } function proRenderEarnings(payouts) { const proEarningsElement = document.getElementById(proEarningsElementId); if (proEarningsElement) { const inReview = `

In Review

$${payouts.inReview}

This session wrapped recently-your payout will be released within the hour.

`; const availableToWithdraw = `

Available to Withdraw

$${payouts.availableToWithdraw}

These funds are all yours.

`; let charityPayout = ``; if (payouts.donated && payouts.donated > 0) { charityPayout = `
Funder
Payment Card Icon

${payouts.donated}

paid out to Funds
`; } const totalEarnings = `

Total Earned with Nectly

$${payouts.earned}

You’ve turned time into impact.

${charityPayout}
`; let earnings = ""; earnings += totalEarnings; earnings += inReview; /*if (parseFloat(payouts.inReview).toFixed(2) > 0) { earnings += inReview; }*/ if (parseFloat(payouts.availableToWithdraw).toFixed(2) > 0) { earnings += availableToWithdraw; } /*if (parseFloat(payouts.earned).toFixed(2) > 0) { earnings += totalEarnings; }*/ if (earnings.length > 0) { proEarningsElement.innerHTML = `

Your Nectly Earnings

${earnings}
`; } } else { console.error(`Element with ID ${proEarningsElementId} not found.`); } } function renderProAvailableSessions(currentDay) { const timeSelect = document.getElementById(proNewSessionTimeSelect); timeSelect.innerHTML = ""; currentDay.freeSlots.forEach( slot => { timeSelect.appendChild( new Option(renderSlotFromTo(slot), `${slot.startTime}@${slot.endTime}`) ); } ); } function proRefreshCalendarSlots(calledFromPage) { function resetNewSessionElement() { document.getElementById(proNewSessionDateSelect).innerHTML = ``; document.getElementById(proNewSessionTimeSelect).innerHTML = ``; } function renderNewSessionDate() { const selectedDate = getCalendarSelectedDate(getVisibleProCalendar()); const stringDate = selectedDate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); document.getElementById(proNewSessionDateSelect).innerHTML = ``; } function renderToday() { const selectedDate = getCalendarSelectedDate(getVisibleProCalendar()); const stringToday = selectedDate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); const appointmentsTodayElemnet = document.getElementById(proTodayAppointmentsElementId); if (appointmentsTodayElemnet) { appointmentsTodayElemnet.innerHTML = stringToday; } else { console.error(`Element with ID ${proTodayAppointmentsElementId} not found.`); } const slotsTodayElemnet = document.getElementById(proTodaySlotsElementId); if (slotsTodayElemnet) { slotsTodayElemnet.innerHTML = stringToday; } else { console.error(`Element with ID ${proTodaySlotsElementId} not found.`); } } renderToday(); proPostCreateSlot(); resetNewSessionElement(); const selectedDate = getCalendarSelectedDate(getVisibleProCalendar()); showLogoLoader(); getProCurrentDate(selectedDate.toISOString(), selectedDate.getTimezoneOffset(), function(currentDay) { console.log("Current day status:", currentDay); try { proRenderEarnings(currentDay.payouts); renderProSetup(currentDay); renderProAppointmentList(currentDay); renderProSlotsList(currentDay); renderNewSessionDate(); renderProAvailableSessions(currentDay); updateProCalendarDays(mergeAppointmentsAndSlots(currentDay)); if (currentDay.freeSlots.length > 0) { toggleSeekerConfirmationButton(false); } else { toggleSeekerConfirmationButton(true); } if (calledFromPage) { getVisibleProCalendar().update(); } } catch (error) { console.error("Error rendering current day data:", error); } hideLogoLoader(); }, function(error) { console.error("Error fetching slots:", error); try { proSlotsFailed(); toggleSeekerConfirmationButton(true); } catch (error) { console.error("Error handling slot fetch failure:", error); } hideLogoLoader(); }); } function proAppointmentsTabClick(clickedElement) { const activeAppointmentList = getActiveProAppointmentList(); if (activeAppointmentList) { activeAppointmentList.classList.remove('active'); } clickedElement.classList.add('active'); proRefreshCalendarSlots(); } function proSlotsTabClick(clickedElement) { const activeSlotList = getActiveProSlotList(); if (activeSlotList) { activeSlotList.classList.remove('active'); } clickedElement.classList.add('active'); proRefreshCalendarSlots(); } function proCancelMeeting(appointmentId) { console.log("Cancelling meeting:", appointmentId); window.location.href = `https://nectly.io/meeting-cancellation.html?appointmentId=${appointmentId}`; } function proSelectSlot(startTime, endTime) { console.log("Deleting slot from", startTime, "to", endTime) return callApi(`/api/professional-slots?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`, 'DELETE') .then(data => { console.log(`Slot deleted ${startTime}-${endTime}:`, data); proRefreshCalendarSlots(true); }) .catch((error) => { console.log(`Slot delete failed ${startTime}-${endTime}:`, error); proRefreshCalendarSlots(true); }); } function proSelectSlots(params) { let promises = []; let idx = 0; function deleteOneSlot(slot) { return callApi(`/api/professional-slots?startTime=${encodeURIComponent(slot.startTime)}&endTime=${encodeURIComponent(slot.endTime)}`, 'DELETE') .then(data => { console.log(`Slot deleted`, data); idx += 1; if (params.length > idx) { promises.push(deleteOneSlot(params[idx])); } else { Promise.all(promises).then((values) => { console.log('All slots deleted:', values); proRefreshCalendarSlots(true); }); } }) .catch((error) => { console.log(`Slot delete failed`, error); idx += 1; if (params.length > idx) { promises.push(deleteOneSlot(params[idx])); } else { Promise.all(promises).then((values) => { console.log('All slots deleted:', values); proRefreshCalendarSlots(true); }); } }); } console.log("Deleting slots:", params); if (params.length > idx) { promises.push(deleteOneSlot(params[0])); } } function renderProSlotsList(currentDay) { function agregateSlotsByDate(slots) { const slotsByDate = new Map(); slots.forEach(slot => { const slotDate = new Date(slot.startTime); const dateKey = `${slotDate.getFullYear()}-${String(slotDate.getMonth() + 1).padStart(2, '0')}-${String(slotDate.getDate()).padStart(2, '0')}`; if (!slotsByDate.has(dateKey)) { slotsByDate.set(dateKey, []); } slotsByDate.get(dateKey).push(slot); }); return slotsByDate; } function displayMaxThreeTimeSlots(slots) { let ret = []; const loopEnd = Math.min(...[3, slots.length]); for (let i = 0; i < loopEnd; i++) { const fromTime = (new Date(slots[i].startTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toTime = (new Date(slots[i].endTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); ret.push(`${fromTime} – ${toTime}`); } return ret.join('; ') } function renderSlotsDetail(slots) { let ret = ""; slots.forEach(slot => { const fromTime = (new Date(slot.startTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toTime = (new Date(slot.endTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); ret += `
  • ${fromTime} – ${toTime} Delete Icon
  • `; }); return ret; } function composeOnclickParameters(slots) { return JSON.stringify(slots); } const slotsListElement = document.getElementById(proSlotsListElementId); if (!slotsListElement) { console.error(`Element with ID ${proSlotsListElementId} not found.`); } const slotsTabElement = getActiveProSlotList(); if (!slotsTabElement) { console.error('No active slots tab found.'); return; } let workWithSlots; let todayTab = false; if (slotsTabElement.id === proSlotsDateTabElementId) { workWithSlots = currentDay.createdSlotsToDate; } else if (slotsTabElement.id === proSlotsAllTabElementId) { workWithSlots = currentDay.allCreatedSlots; } else { workWithSlots = currentDay.createdSlotsToday; todayTab = true; } if (workWithSlots.length === 0) { slotsListElement.innerHTML = ""; proRenderNoSlots(todayTab); return; } const slotsByDate = agregateSlotsByDate(workWithSlots); let innerHTML = ""; slotsByDate.forEach((slots, date) => { innerHTML += '
    '; innerHTML += `

    ${date.toLocaleString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}

    ${displayMaxThreeTimeSlots(slots)}...

    `; innerHTML += `
      ${renderSlotsDetail(slots)}
    `; innerHTML += '
    '; }); slotsListElement.innerHTML = innerHTML; } function onProTimeSlotSelectChange() { const selectedDate = getCalendarSelectedDate(getVisibleProCalendar()); const selectedDates = getVisibleProCalendar().context.selectedDates; getVisibleProCalendar().selectedDates = selectedDates; getVisibleProCalendar().selectedMonth = getVisibleProCalendar().context.selectedMonth; getVisibleProCalendar().selectedYear = getVisibleProCalendar().context.selectedYear; proPostCreateSlot(); getProCurrentDate(selectedDate.toISOString(), selectedDate.getTimezoneOffset(), function(currentDay) { console.log("Current day status:", currentDay); renderProSetup(currentDay); updateProCalendarDays(mergeAppointmentsAndSlots(currentDay)); getVisibleProCalendar().update(); getVisibleProCalendar().context.selectedDates = selectedDates; renderProAppointmentList(currentDay); renderProSlotsList(currentDay); if (currentDay.freeSlots.length > 0) { const timeSelect = document.getElementById(proNewSessionTimeSelect); if (!timeSelect) { console.error(`Element with ID ${proNewSessionTimeSelect} not found.`); return; } if (currentDay.freeSlots.length !== timeSelect.options.length) { renderProAvailableSessions(currentDay); } toggleSeekerConfirmationButton(false); } else { renderProAvailableSessions(currentDay); toggleSeekerConfirmationButton(true); } }, function(error) { console.error("Error fetching professional's slots:", error); if (error.status === 400 && error.error === "400: Selected date cannot be in the past") { proSelectedDateIsinThePast(); } else { proSlotsFailed(); } toggleSeekerConfirmationButton(true); }); } function proSaveSession(slotType, repeatCount) { const subject = ''; const timeSelect = document.getElementById(proNewSessionTimeSelect); if (!timeSelect) { console.error(`Element with ID ${proNewSessionTimeSelect} not found.`); return; } const selectedOption = timeSelect.options[timeSelect.selectedIndex]; if (!selectedOption) { console.error("No slot selected."); return; } toggleSeekerConfirmationButton(true); const startTime = selectedOption.value.split('@')[0]; const endTime = selectedOption.value.split('@')[1]; console.log("Reserving session from", startTime, "to", endTime); const reserveSlotBody = { "startTime": startTime, "endTime": endTime, "title": subject, "slotType": slotType || getProRecurringValue(), "repeatCount": repeatCount || -1, // TODO: implement in UI } const pro_id = getLinkPageUserId(); console.log("Professional ID:", pro_id); console.log("Reserve slot body:", reserveSlotBody); callApi(`/api/professional-slots`, 'POST', reserveSlotBody) .then(data => { console.log('Current month:', data) onProTimeSlotSelectChange(); onReserveProSlotSuccess(); }) .catch((error) => { console.error('Error saving professional session:', error); if (error.status === 400) { if (error.error === "400: Selected date cannot be in the past") { proSelectedDateIsinThePast(); } else if (error.error === "400: Slot is too soon for booking") { proSlotIsTooSoon(); } else { onReserveProSlotError(); } } else { onReserveProSlotError(); } }); } function getProRecurringValue() { const recurringSelect = document.getElementById(proRecurringSelectionElementId); if (!recurringSelect) { console.error(`Element with ID ${proRecurringSelectionElementId} not found.`); return null; } let selectedOption = null; for (const child of recurringSelect.children) { if (child.children[0].checked === true) { selectedOption = child.children[0]; break; } } if (!selectedOption) { console.error("No recurring selected."); return null; } return selectedOption.value; } function renderProAppointmentList(currentDay) { const appointmentListElement = document.getElementById(proAppointmentListElementId); if (!appointmentListElement) { console.error(`Element with ID ${proAppointmentListElementId} not found.`); } const appointmentsTabElement = getActiveProAppointmentList(); if (!appointmentsTabElement) { console.error('No active appointments tab found.'); return; } let workWithAppointments; let todayTab = false if (appointmentsTabElement.id === proAppointmentsDateTabElementId) { workWithAppointments = currentDay.appointmentsToDate; } else if (appointmentsTabElement.id === proAppointmentsAllTabElementId) { workWithAppointments = currentDay.appointments; } else { workWithAppointments = currentDay.appointmentsToday; todayTab = true; } if (workWithAppointments.length === 0) { appointmentListElement.innerHTML = ""; proRenderNoAppointments(todayTab); return; } let innerHTML = ""; workWithAppointments.forEach( (appointment) => { let subject = ""; if (appointment.title) { subject = `

    ${appointment.title}

    `; } innerHTML += `

    Conversation with ${appointment.attendees.join(', ')}

    ${subject}

    ${renderAppointmentDate(appointment)}

    `; }); appointmentListElement.innerHTML = innerHTML; setupCancelMeetingToggle(); } function resetProAppointmentListContainer() { const appointmentListElement = document.getElementById(proAppointmentListElementId); if (appointmentListElement) { appointmentListElement.innerHTML = ""; } else { console.error(`Element with ID ${proAppointmentListElementId} not found.`); } } function getActiveProSlotList() { const slotsElement = document.getElementById(proSlotsElementId); if (slotsElement) { for (const child of slotsElement.children) { if (child.classList.contains('active')) { return child; } } } else { console.error(`Element with ID ${proSlotsElementId} not found.`); return null; } } function getActiveProAppointmentList() { const appointmentsElement = document.getElementById(proAppointmentsElementId); if (appointmentsElement) { for (const child of appointmentsElement.children) { if (child.classList.contains('active')) { return child; } } } else { console.error(`Element with ID ${proAppointmentsElementId} not found.`); return null; } } function proPostCreateSlot() { const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = ""; } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } const noAppointmentsElement = document.getElementById(proNoAppointmentsElementId); if (noAppointmentsElement) { noAppointmentsElement.innerHTML = ""; } else { console.error(`Element with ID ${proNoAppointmentsElementId} not found.`) } const noSlotsElement = document.getElementById(proNoSlotsElementId); if (noSlotsElement) { noSlotsElement.innerHTML = ""; } else { console.error(`Element with ID ${proNoSlotsElementId} not found.`) } } function proRenderNoSlots(todayTab) { const noSlotsElement = document.getElementById(proNoSlotsElementId); if (noSlotsElement) { let h4Text = `

    No time slots added yet

    `; if (todayTab) h4Text = `

    No time slots added for today. Add a few, and they’ll show up here-ready to book.

    `; noSlotsElement.innerHTML = ""; noSlotsElement.innerHTML = ` Calendar Icon ${h4Text}

    Choose the times you’re available, and they’ll appear here ready to book.

    `; } else { console.error(`Element with ID ${proNoSlotsElementId} not found.`); } } function proRenderNoAppointments(todayTab) { const noAppointmentsElement = document.getElementById(proNoAppointmentsElementId); if (noAppointmentsElement) { let h4Text = `

    No conversations booked yet

    `; if (todayTab) h4Text = `

    No conversations booked for today. Keep sharing your link to start the right ones.

    `; noAppointmentsElement.innerHTML = ""; noAppointmentsElement.innerHTML = ` No Booked Time Slots Icon ${h4Text}

    Once someone books time with you, it’ll appear here. Until then, share your link to start the right conversations - on your terms.

    `; } else { console.error(`Element with ID ${proNoAppointmentsElementId} not found.`); } } function onReserveProSlotError() { const errorMessage = `

    That time’s no longer available.

    Don’t worry, just choose another time for the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } } function onReserveProSlotSuccess() { const successMessage = `
    Email Sent Icon

    You’re all set.

    Your session slot is reserved.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = successMessage; setTimeout(() => { postReserveSlotElement.classList.add("hidden"); setTimeout(() => {postReserveSlotElement.innerHTML = ""; postReserveSlotElement.classList.remove("hidden"); }, 2000); }, 3000); } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } } function proSelectedDateIsinThePast() { const errorMessage = `

    Selected date cannot be in the past.

    Don’t worry, just choose another slot to create the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } } function proSlotIsTooSoon() { const errorMessage = `

    Selected slot is to soon in the future.

    Don’t worry, just choose another slot to create the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotProElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotProElementId} not found.`); } } function refreshSessionBalanceSummary(countOfPaidSessions, countOfUsedSession) { const summaryElement = document.getElementById(sessionBalanceSummaryElementId); if (summaryElement) { summaryElement.innerHTML = `Remaining: ${countOfPaidSessions - countOfUsedSession} of ${countOfPaidSessions} sessions`; } else { console.error(`Element with ID ${sessionBalanceSummaryElementId} not found.`); } const progressBar = document.getElementById(seekerBalanceProgressBarElementId); if (progressBar) { if (countOfPaidSessions === 0) { progressBar.style.width = '0%'; } else { progressBar.style.width = `${((countOfPaidSessions - countOfUsedSession) / countOfPaidSessions) * 100}%`; } } else { console.error(`Element with ID ${seekerBalanceProgressBarElementId} not found.`); } if (countOfPaidSessions === 0) { hideDiv(seekerSessionBalanceSummaryElementId); } else { showDiv(seekerSessionBalanceSummaryElementId); } } function seekerSlotsFailed() { const errorMessage = `

    Retrieving slots failed.

    Try to refresh later.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function seekerNoMoreAvailableSlots(countOfPaidSessions) { let h4Text = ''; if (countOfPaidSessions > 0) h4Text = `

    All pre-paid sessions are booked already.

    `; const errorMessage = `
    No Available Slots Icon ${h4Text}

    Add more sessions to continue.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; document.getElementById(seekerNewSessionTimeSelect).innerHTML = ``; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function getCalendarSelectedDate(cal) { if (cal.context.selectedDates.length > 0) { return new Date(cal.context.selectedDates[0]); } return new Date(); } function getEnabledDates() { const today = new Date(); today.setHours(0, 0, 0, 0); const yyyy = today.getFullYear(); const mm = String(today.getMonth() + 1).padStart(2, '0'); const dd = String(today.getDate()).padStart(2, '0'); return `${yyyy}-${mm}-${dd}:2099-12-31` } let resolveSeekerDateElements = []; let seekerLastDayInMonth = null; let activeSeekerDayElement = null; function setDayElementAsBusy(btnEl) { btnEl.style.backgroundColor = "#EBFFD6"; } function getDayMonthYearFromDateEl(dateEl) { const stringDate = dateEl.getAttribute('data-vc-date'); const dateParts = stringDate.split('-'); const btnDay = parseInt(dateParts[2], 10); const btnMonth = parseInt(dateParts[1], 10) - 1; const btnYear = parseInt(dateParts[0], 10); return { btnDay, btnMonth, btnYear }; } function setDayElementAsSelected(calendar, selectedDate) { calendar.set({'selectedDates': [selectedDate]}, {dates: true}); } function loopSeekerDaysInMonth(self, dateEl) { const leftArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); const rightArrow = self.context.mainElement.querySelectorAll('[data-vc-arrow="prev"]'); if (leftArrow.length > 0) leftArrow[0].disabled = true; if (rightArrow.length > 0) rightArrow[0].disabled = true; const btnFullDate = getDayMonthYearFromDateEl(dateEl); const btnEl = dateEl.querySelector('[data-vc-date-btn]'); if (dateEl.getAttribute('data-vc-date-week-day') === '6' || dateEl.getAttribute('data-vc-date-week-day') === '0') { btnEl.classList.add('disabled'); btnEl.disabled = true; } if (btnFullDate.btnMonth !== self.context.selectedMonth) { return; } if (activeSeekerDayElement === null) { const today = new Date(); today.setHours(0, 0, 0, 0); if (today.getDay() === 0) today.setDate(today.getDate() + 1); if (today.getDay() === 6) today.setDate(today.getDate() + 2); if (today.getDate() === btnFullDate.btnDay && today.getMonth() === btnFullDate.btnMonth && today.getFullYear() === btnFullDate.btnYear) { activeSeekerDayElement = btnEl; self.context.selectedDates = [dateEl.getAttribute('data-vc-date')]; } } const day = btnEl.innerText; if (parseInt(day) === 1) { resolveSeekerDateElements = [dateEl]; seekerLastDayInMonth = getLastDayOfMonth(btnFullDate.btnYear, convertIntMonthToString(btnFullDate.btnMonth)); } else { resolveSeekerDateElements.push(dateEl); } if (dateEl.getAttribute('data-vc-date-week-day') === '6' || dateEl.getAttribute('data-vc-date-week-day') === '0') { btnEl.classList.add('disabled'); btnEl.disabled = true; } if (seekerLastDayInMonth.getDate() === btnFullDate.btnDay && seekerLastDayInMonth.getMonth() === btnFullDate.btnMonth && seekerLastDayInMonth.getFullYear() === btnFullDate.btnYear) { const today = new Date(); today.setHours(0, 0, 0, 0); getSeekerCurrentDate(seekerLastDayInMonth.toISOString(), seekerLastDayInMonth.getTimezoneOffset(), function(currentDay) { const busyDaysInMonth = mergeAppointmentsAndSlots(currentDay); for (const dateEl of resolveSeekerDateElements) { const btnEl = dateEl.querySelector('[data-vc-date-btn]'); const btnDate = new Date(dateEl.getAttribute('data-vc-date')); btnDate.setHours(0, 0, 0, 0); const day = btnEl.innerText; if (btnDate < today) continue; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(btnEl); } } resolveSeekerDateElements = []; if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; console.log("Days in month updated") }, function(error) { console.log("Error fetching slots:", error); resolveSeekerDateElements = []; if (leftArrow.length > 0) leftArrow[0].disabled = false; if (rightArrow.length > 0) rightArrow[0].disabled = false; }); } } function renderSeekerChooseHowYouPayElement(index) { const seekerSetPaymentMethodElement = document.getElementById(seekerSetPaymentMethodElementId); if (seekerSetPaymentMethodElement) { seekerSetPaymentMethodElement.className = "setup-account-container flex flex-column halfgap"; seekerSetPaymentMethodElement.innerHTML = `

    ${index}

    Choose how you’ll pay

    `; } else { console.error(`Element with ID ${seekerSetPaymentMethodElementId} not found.`); } } function renderSeekerSetupAvailabilityElement(index) { const seekerAddYourAvailabilityElement = document.getElementById(seekerAddYourAvailabilityElementId); if (seekerAddYourAvailabilityElement) { seekerAddYourAvailabilityElement.className = "setup-account-container flex flex-column halfgap"; seekerAddYourAvailabilityElement.innerHTML = `

    ${index}

    Add your availability

    `; } else { console.error(`Element with ID ${seekerAddYourAvailabilityElementId} not found.`); } } function renderSeekerLinkElement(index) { const seekerShareLinkElement = document.getElementById(seekerShareLinkElementId); if (seekerShareLinkElement) { if (index > 0) { seekerShareLinkElement.className = "setup-account-container flex flex-column halfgap"; seekerShareLinkElement.innerHTML = `

    ${index}

    Your link becomes active!

    Once payment and availability are set, you’ll see your personal Nectly link activated here-ready to share.

    `; } else { seekerShareLinkElement.className = "setup-account-container flex flex-column halfgap"; seekerShareLinkElement.innerHTML = `

    Share it in outreach messages, LinkedIn, or email. It shows your calendar and handles payment automatically.

    `; setupNectlyLinkButtonAnimation(); } } } function renderSeekerSetup(currentDay) { resetSeekerSetup(); let countOfSetupSteps = 0; if (currentDay.allCreatedSlots.length === 0) countOfSetupSteps++; if (!currentDay.paymentMethodSet) countOfSetupSteps++; if (countOfSetupSteps === 0) { renderSeekerLinkElement(0); } else { const seekerSetupYourAccountMessageElement = document.getElementById(seekerSetupYourAccountMessageElementId); if (seekerSetupYourAccountMessageElement) { seekerSetupYourAccountMessageElement.innerHTML = `Setup your Account`; } else { console.error(`Element with ID ${seekerSetupYourAccountMessageElementId} not found.`); } let index = 1; if (!currentDay.paymentMethodSet) renderSeekerChooseHowYouPayElement(index++); if (currentDay.allCreatedSlots.length === 0) renderSeekerSetupAvailabilityElement(index++); renderSeekerLinkElement(index++); } } function renderSeekerCalendar(onChange, elementId) { const { Calendar } = window.VanillaCalendarPro; const calendar = new Calendar( `#${elementId}`, { onCreateDateEls: loopSeekerDaysInMonth, disableAllDates: true, enableDates:[getEnabledDates()], firstWeekday: 0, type: 'default', onClickArrow(self) { console.log("Month changed to", self.context.selectedYear, self.context.selectedMonth); onChange('month', convertIntMonthToString(self.context.selectedMonth)); self.selectedMonth = self.context.selectedMonth; self.selectedYear = self.context.selectedYear; }, onClickDate(self, pointerEvent) { const btnEl = pointerEvent.srcElement; console.log("Day clicked", self.context.selectedDates, btnEl); activeSeekerDayElement = btnEl; onChange('value', self.context.selectedDates[0]); }, }); calendar.init(); setDayElementAsSelected(calendar, calendar.context.selectedDates[0]); calendar.selectedDates = []; return calendar; } function mergeAppointmentsAndSlots(currentDay) { const appointments = new Set(currentDay.appointmentsInMonth); const slots = new Set(currentDay.slotsInMonth); const busyDaysInMonth = new Set([...appointments, ...slots]); return Array.from(busyDaysInMonth); } function updateSeekerCalendarDays(busyDaysInMonth) { console.log("Updating calendar days with busy days:", busyDaysInMonth); if (activeSeekerDayElement) { const day = activeSeekerDayElement.innerText; if (busyDaysInMonth.indexOf(parseInt(day)) !== -1) { setDayElementAsBusy(activeSeekerDayElement); } } } function onSeekerCalendarChange(calendarParameter, value) { if (calendarParameter === "value") { console.log("Calendar value changed to", value); resetAppointmentListContainer(); seekerRefreshCalendarSlots(); } if (calendarParameter === "month") { getVisibleSeekerCalendar().selectedMonth = getVisibleSeekerCalendar().context.selectedMonth; getVisibleSeekerCalendar().selectedYear = getVisibleSeekerCalendar().context.selectedYear; console.log("Calendar month changed to", value); } } function onMobileSeekerCalendarChange(calendarParameter, value) { if (calendarParameter === "value") { console.log("Seeker Mobile calendar value changed to", value); resetAppointmentListContainer(); seekerRefreshCalendarSlots(); } if (calendarParameter === "month") { console.log("Seeker Mobile calendar month changed to", value); } } function renderSlotFromTo(slot) { const startTime = new Date(slot.startTime); const endTime = new Date(slot.endTime); const fromDate = startTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toDate = endTime.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); return `${fromDate} - ${toDate}`; } function renderAvailableSessions(currentDay) { const timeSelect = document.getElementById(seekerNewSessionTimeSelect); timeSelect.innerHTML = ""; currentDay.freeSlots.forEach( slot => { timeSelect.appendChild( new Option(renderSlotFromTo(slot), `${slot.startTime}@${slot.endTime}`) ); } ); } function getActiveSeekerAppointmentList() { const appointmentsElement = document.getElementById(seekerAppointmentsElementId); if (appointmentsElement) { for (const child of appointmentsElement.children) { if (child.classList.contains('active')) { return child; } } } else { console.error(`Element with ID ${seekerAppointmentsElementId} not found.`); return null; } } function getActiveSeekerSlotList() { const slotsElement = document.getElementById(seekerSlotsElementId); if (slotsElement) { for (const child of slotsElement.children) { if (child.classList.contains('active')) { return child; } } } else { console.error(`Element with ID ${seekerSlotsElementId} not found.`); return null; } } function seekerAppointmentsTabClick(clickedElement) { const activeAppointmentList = getActiveSeekerAppointmentList(); if (activeAppointmentList) { activeAppointmentList.classList.remove('active'); } clickedElement.classList.add('active'); seekerRefreshCalendarSlots(); } function seekerSlotsTabClick(clickedElement) { const activeSlotList = getActiveSeekerSlotList(); if (activeSlotList) { activeSlotList.classList.remove('active'); } clickedElement.classList.add('active'); seekerRefreshCalendarSlots(); } function seekerRefreshCalendarSlots(calledFromPage) { function resetNewSessionElement() { document.getElementById(seekerNewSessionDateSelect).innerHTML = ``; document.getElementById(seekerNewSessionTimeSelect).innerHTML = ``; } function renderNewSessionDate() { const selectedDate = getCalendarSelectedDate(getVisibleSeekerCalendar()); const stringDate = selectedDate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); document.getElementById(seekerNewSessionDateSelect).innerHTML = ``; } function renderToday() { const selectedDate = getCalendarSelectedDate(getVisibleSeekerCalendar()); const stringToday = selectedDate.toLocaleString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); const appointmentsTodayElemnet = document.getElementById(seekerTodayAppointmentsElementId); if (appointmentsTodayElemnet) { appointmentsTodayElemnet.innerHTML = stringToday; } else { console.error(`Element with ID ${seekerTodayAppointmentsElementId} not found.`); } const slotsTodayElemnet = document.getElementById(seekerTodaySlotsElementId); if (slotsTodayElemnet) { slotsTodayElemnet.innerHTML = stringToday; } else { console.error(`Element with ID ${seekerTodaySlotsElementId} not found.`); } } renderToday(); resetSeekerPostCreateSlot(); resetNewSessionElement(); const selectedDate = getCalendarSelectedDate(getVisibleSeekerCalendar()); showLogoLoader(); getSeekerCurrentDate(selectedDate.toISOString(), selectedDate.getTimezoneOffset(), function(currentDay) { console.log("Current day status:", currentDay); try { renderSeekerSetup(currentDay); renderSeekerAppointmentList(currentDay); renderSeekerSlotsList(currentDay); renderNewSessionDate(); if (!currentDay.paymentMethodSet && currentDay.countOfPaidSessions <= currentDay.countOfUsedSession) { seekerNoMoreAvailableSlots(currentDay.countOfPaidSessions); toggleSeekerConfirmationButton(true); refreshSessionBalanceSummary(currentDay.countOfPaidSessions, currentDay.countOfUsedSession); hideLogoLoader(); return; } updateSeekerCalendarDays(mergeAppointmentsAndSlots(currentDay)); renderAvailableSessions(currentDay); if (currentDay.freeSlots.length > 0) { toggleSeekerConfirmationButton(false); } else { toggleSeekerConfirmationButton(true); } refreshSessionBalanceSummary(currentDay.countOfPaidSessions, currentDay.countOfUsedSession); if (calledFromPage) { getVisibleSeekerCalendar().update(); } } catch (error) { console.error("Error rendering seeker data:", error); } hideLogoLoader(); }, function(error) { console.error("Error fetching slots:", error); try { seekerSlotsFailed(); toggleSeekerConfirmationButton(true); } catch (renderError) { console.error("Error rendering seeker error state:", renderError); } hideLogoLoader(); }); } function toggleSeekerConfirmationButton(disabled) { document.getElementById(confirmMeetingButtonElementId).disabled = disabled; } function resetSeekerPostCreateSlot() { const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = ""; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } const noAppointmentsElement = document.getElementById(seekerNoAppointmentsElementId); if (noAppointmentsElement) { noAppointmentsElement.innerHTML = ""; } else { console.error(`Element with ID ${seekerNoAppointmentsElementId} not found.`) } const noSlotsElement = document.getElementById(seekerNoSlotsElementId); if (noSlotsElement) { noSlotsElement.innerHTML = ""; } else { console.error(`Element with ID ${seekerNoSlotsElementId} not found.`) } } function getRecurringValue() { const recurringSelect = document.getElementById(seekerRecurringSelectionElementId); if (!recurringSelect) { console.error(`Element with ID ${seekerRecurringSelectionElementId} not found.`); return null; } let selectedOption = null; for (const child of recurringSelect.children) { if (child.children[0].checked === true) { selectedOption = child.children[0]; break; } } if (!selectedOption) { console.error("No recurring selected."); return null; } return selectedOption.value; } function seekerSaveSession(slotType, repeatCount) { const subject = ''; const timeSelect = document.getElementById(seekerNewSessionTimeSelect); if (!timeSelect) { console.error(`Element with ID ${seekerNewSessionTimeSelect} not found.`); return; } const selectedOption = timeSelect.options[timeSelect.selectedIndex]; if (!selectedOption) { console.error("No slot selected."); return; } toggleSeekerConfirmationButton(true); const startTime = selectedOption.value.split('@')[0]; const endTime = selectedOption.value.split('@')[1]; console.log("Reserving session from", startTime, "to", endTime); const reserveSlotBody = { "startTime": startTime, "endTime": endTime, "title": subject, "slotType": slotType || getRecurringValue(), "repeatCount": repeatCount || -1, // TODO: implement in UI } const seeker_id = getLinkPageUserId(); console.log("Seeker ID:", seeker_id); console.log("Reserve slot body:", reserveSlotBody); callApi(`/api/seeker-slots`, 'POST', reserveSlotBody) .then(data => { console.log('Current month:', data) onTimeSlotSelectChange(); onReserveSeekerSlotSuccess(); }) .catch((error) => { console.error('Error saving seeker session:', error); if (error.status === 400) { if (error.error === "400: Selected date cannot be in the past") { seekerSelectedDateIsinThePast(); } else if (error.error === "400: Slot is too soon for booking") { seekerSlotIsTooSoon(); } else { onReserveSeekerSlotError(); } } else { onReserveSeekerSlotError(); } }); } function onReserveSeekerSlotError() { const errorMessage = `

    That time’s no longer available.

    Don’t worry, just choose another time for the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function onReserveSeekerSlotSuccess() { const successMessage = `
    Email Sent Icon

    You’re all set.

    Your session slot is reserved.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = successMessage; setTimeout(() => { postReserveSlotElement.classList.add("hidden"); setTimeout(() => {postReserveSlotElement.innerHTML = ""; postReserveSlotElement.classList.remove("hidden"); }, 2000); }, 3000); } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function seekerSelectedDateIsinThePast() { const errorMessage = `

    Selected date cannot be in the past.

    Don’t worry, just choose another slot to create the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function seekerSlotIsTooSoon() { const errorMessage = `

    Selected slot is to soon in the future.

    Don’t worry, just choose another slot to create the session.

    `; const postReserveSlotElement = document.getElementById(postReserveSlotSeekerElementId); if (postReserveSlotElement) { postReserveSlotElement.innerHTML = errorMessage; } else { console.error(`Element with ID ${postReserveSlotSeekerElementId} not found.`); } } function onTimeSlotSelectChange() { const selectedDate = getCalendarSelectedDate(getVisibleSeekerCalendar()); const selectedDates = getVisibleSeekerCalendar().context.selectedDates; getVisibleSeekerCalendar().selectedDates = selectedDates; getVisibleSeekerCalendar().selectedMonth = getVisibleSeekerCalendar().context.selectedMonth; getVisibleSeekerCalendar().selectedYear = getVisibleSeekerCalendar().context.selectedYear; resetSeekerPostCreateSlot(); getSeekerCurrentDate(selectedDate.toISOString(), selectedDate.getTimezoneOffset(), function(currentDay) { renderSeekerSetup(currentDay); updateSeekerCalendarDays(mergeAppointmentsAndSlots(currentDay)); getVisibleSeekerCalendar().update(); getVisibleSeekerCalendar().context.selectedDates = selectedDates; console.log("Current day status:", currentDay); if (!currentDay.paymentMethodSet && currentDay.countOfPaidSessions <= currentDay.countOfUsedSession) { seekerNoMoreAvailableSlots(currentDay.countOfPaidSessions); toggleSeekerConfirmationButton(true); return; } renderSeekerAppointmentList(currentDay); renderSeekerSlotsList(currentDay); if (currentDay.freeSlots.length > 0) { const timeSelect = document.getElementById(seekerNewSessionTimeSelect); if (!timeSelect) { console.error(`Element with ID ${seekerNewSessionTimeSelect} not found.`); return; } if (currentDay.freeSlots.length !== timeSelect.options.length) { renderAvailableSessions(currentDay); } toggleSeekerConfirmationButton(false); } else { renderAvailableSessions(currentDay); toggleSeekerConfirmationButton(true); } }, function(error) { console.error("Error fetching seeker's slots:", error); if (error.status === 400 && error.error === "400: Selected date cannot be in the past") { seekerSelectedDateIsinThePast(); } else { seekerSlotsFailed(); } toggleSeekerConfirmationButton(true); }); } function resetAppointmentListContainer() { const appointmentListElement = document.getElementById(seekerAppointmentListElementId); if (appointmentListElement) { appointmentListElement.innerHTML = ""; } else { console.error(`Element with ID ${seekerAppointmentListElementId} not found.`); } } function renderAppointmentDate(appointment) { const appointmentStart = new Date(appointment.startTime); const appointmentEnd = new Date(appointment.endTime); const fromTime = appointmentStart.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toTime = appointmentEnd.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const day = appointmentStart.toLocaleString('en-US', { weekday: 'long', month: 'short', day: 'numeric', hour12: true }); return `${fromTime} – ${toTime} | ${day}`; } function renderNoAppointments(todayTab) { const noAppointmentsElement = document.getElementById(seekerNoAppointmentsElementId); if (noAppointmentsElement) { let h4Text = `

    No conversations booked yet

    `; if (todayTab) h4Text = `

    No conversations booked for today. Keep sharing your link to start the right ones.

    `; noAppointmentsElement.innerHTML = ""; noAppointmentsElement.innerHTML = ` No Booked Time Slots Icon ${h4Text}

    Once an expert books time with you, it’ll appear here. Until then, keep sharing your link to start the right conversations.

    `; } else { console.error(`Element with ID ${seekerNoAppointmentsElementId} not found.`); } } function renderSeekerAppointmentList(currentDay) { const appointmentListElement = document.getElementById(seekerAppointmentListElementId); if (!appointmentListElement) { console.error(`Element with ID ${seekerAppointmentListElementId} not found.`); } const appointmentsTabElement = getActiveSeekerAppointmentList(); if (!appointmentsTabElement) { console.error('No active appointments tab found.'); return; } let workWithAppointments; let todayTab = false; if (appointmentsTabElement.id === seekerAppointmentsDateTabElementId) { workWithAppointments = currentDay.appointmentsToDate; } else if (appointmentsTabElement.id === seekerAppointmentsAllTabElementId) { workWithAppointments = currentDay.appointments; } else { workWithAppointments = currentDay.appointmentsToday; todayTab = true; } if (workWithAppointments.length === 0) { appointmentListElement.innerHTML = ""; renderNoAppointments(todayTab); return; } let innerHTML = ""; workWithAppointments.forEach( (appointment) => { let subject = ""; if (appointment.title) { subject = `

    ${appointment.title}

    `; } innerHTML += `

    Conversation with ${appointment.attendees.join(', ')}

    ${subject}

    ${renderAppointmentDate(appointment)}

    `; }); appointmentListElement.innerHTML = innerHTML; setupCancelMeetingToggle(); } function renderNoSlots(todayTab) { const noSlotsElement = document.getElementById(seekerNoSlotsElementId); if (noSlotsElement) { let textBelow = 'Choose the times you’re available, and they’ll appear here-ready for experts to book.'; if (window.location.href.indexOf("professional-dashboard.html") !== -1) { textBelow = 'Choose the times you’re available, and they’ll appear here-ready to book.'; } let h4Text = `

    No time slots added yet

    `; if (todayTab) h4Text = `

    No time slots added for today. Add a few, and they’ll show up here-ready to book.

    `; noSlotsElement.innerHTML = ""; noSlotsElement.innerHTML = ` Calendar Icon ${h4Text}

    ${textBelow}

    `; } else { console.error(`Element with ID ${seekerNoSlotsElementId} not found.`); } } function renderSeekerSlotsList(currentDay) { function agregateSlotsByDate(slots) { const slotsByDate = new Map(); slots.forEach(slot => { const slotDate = new Date(slot.startTime); const dateKey = `${slotDate.getFullYear()}-${String(slotDate.getMonth() + 1).padStart(2, '0')}-${String(slotDate.getDate()).padStart(2, '0')}`; if (!slotsByDate.has(dateKey)) { slotsByDate.set(dateKey, []); } slotsByDate.get(dateKey).push(slot); }); return slotsByDate; } function displayMaxThreeTimeSlots(slots) { let ret = []; const loopEnd = Math.min(...[3, slots.length]); for (let i = 0; i < loopEnd; i++) { const fromTime = (new Date(slots[i].startTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toTime = (new Date(slots[i].endTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); ret.push(`${fromTime} – ${toTime}`); } return ret.join('; ') } function renderSlotsDetail(slots) { let ret = ""; slots.forEach(slot => { const fromTime = (new Date(slot.startTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); const toTime = (new Date(slot.endTime)).toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }); ret += `
  • ${fromTime} – ${toTime} Delete Icon
  • `; }); return ret; } function composeOnclickParameters(slots) { return JSON.stringify(slots); } const slotsListElement = document.getElementById(seekerSlotsListElementId); if (!slotsListElement) { console.error(`Element with ID ${seekerSlotsListElementId} not found.`); } const slotsTabElement = getActiveSeekerSlotList(); if (!slotsTabElement) { console.error('No active slots tab found.'); return; } let workWithSlots; let todayTab = false; if (slotsTabElement.id === seekerSlotsDateTabElementId) { workWithSlots = currentDay.createdSlotsToDate; } else if (slotsTabElement.id === seekerSlotsAllTabElementId) { workWithSlots = currentDay.allCreatedSlots; } else { workWithSlots = currentDay.createdSlotsToday; todayTab = true; } if (workWithSlots.length === 0) { slotsListElement.innerHTML = ""; renderNoSlots(todayTab); return; } const slotsByDate = agregateSlotsByDate(workWithSlots); let innerHTML = ''; slotsByDate.forEach((slots, date) => { innerHTML += '
    '; innerHTML += `

    ${date.toLocaleString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}

    ${displayMaxThreeTimeSlots(slots)}...

    `; innerHTML += `
      ${renderSlotsDetail(slots)}
    `; innerHTML += '
    '; }); slotsListElement.innerHTML = innerHTML; } function joinMeeting(meetingUrl) { console.log("Joining meeting:", meetingUrl); window.open(meetingUrl, '_blank'); } function seekerCancelMeeting(appointmentId) { console.log("Cancelling meeting:", appointmentId); window.location.href = `https://nectly.io/meeting-cancellation.html?appointmentId=${appointmentId}`; } function deleteSlot(startTime, endTime) { console.log("Deleting slot from", startTime, "to", endTime) return callApi(`/api/seeker-slots?startTime=${encodeURIComponent(startTime)}&endTime=${encodeURIComponent(endTime)}`, 'DELETE') .then(data => { console.log(`Slot deleted ${startTime}-${endTime}:`, data); seekerRefreshCalendarSlots(true); }) .catch((error) => { console.log(`Slot delete failed ${startTime}-${endTime}:`, error); seekerRefreshCalendarSlots(true); }); } function deleteSlots(params) { let promises = []; let idx = 0; function deleteOneSlot(slot) { return callApi(`/api/seeker-slots?startTime=${encodeURIComponent(slot.startTime)}&endTime=${encodeURIComponent(slot.endTime)}`, 'DELETE') .then(data => { console.log(`Slot deleted`, data); idx += 1; if (params.length > idx) { promises.push(deleteOneSlot(params[idx])); } else { Promise.all(promises).then((values) => { console.log('All slots deleted:', values); seekerRefreshCalendarSlots(true); }); } }) .catch((error) => { console.log(`Slot delete failed`, error); idx += 1; if (params.length > idx) { promises.push(deleteOneSlot(params[idx])); } else { Promise.all(promises).then((values) => { console.log('All slots deleted:', values); seekerRefreshCalendarSlots(true); }); } }); } console.log("Deleting slots:", params); if (params.length > idx) { promises.push(deleteOneSlot(params[0])); } } const paymentElementId = 'stripe-payment-element'; const paymentSubmitElementId = 'stripe-payment-submit'; const paymentFormElementId = 'stripe-payment-form'; const paymentErrorElementId = 'stripe-error-message'; const popupWrapperElementId = 'payment-popup-wrapper'; const buttonTextElementId = 'stripe-payment-button-text'; const paymentPriceDisplayElementId = 'stripe-payment-price-display'; const closePaymentPopupElementId = 'close-payment-popup'; function startStripePayment(stripePublicKey, reserveSlotBody, professionalId) { function onPaymentIntentSuccess(data) { console.log('Stripe payment intent creation successful:', data); const response = JSON.parse(data.response); const closePaymentPopupElement = document.getElementById(closePaymentPopupElementId); if (closePaymentPopupElement) { function clickClosePaymentPopup(event) { event.preventDefault(); closePaymentPopupElement.disabled = true; callApi('/api/stripe-create-intent-cleanup', 'POST', response) .then(data => { console.info('Stripe payment close button cleanup successful:', data); closePaymentPopupElement.disabled = false; closePaymentPopupElement.removeEventListener("click", clickClosePaymentPopup); closePaymentPopup(); toggleConfirmationButton(false); }) .catch(error => { console.error('Stripe payment close button failed:', error); closePaymentPopupElement.disabled = false; closePaymentPopupElement.removeEventListener("click", clickClosePaymentPopup); closePaymentPopup(); toggleConfirmationButton(false); }); } closePaymentPopupElement.addEventListener("click", clickClosePaymentPopup); } const stripe = new Stripe(stripePublicKey); const elements = stripe.elements({ clientSecret: response.clientSecret }); const paymentElement = elements.create('payment'); const popupWrapper = document.getElementById(popupWrapperElementId); // Get the price and currency from the API response const priceInCents = response.amount; const currency = response.currency; // Convert cents to dollars/euros and format it const priceInDollars = priceInCents / 100; const formattedPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: currency, }).format(priceInDollars); document.getElementById(paymentPriceDisplayElementId).textContent = `Total: ${formattedPrice}`; popupWrapper.style.visibility = 'visible'; popupWrapper.style.opacity = '1'; paymentElement.mount(`#${paymentElementId}`); const form = document.getElementById(paymentFormElementId); form.addEventListener('submit', async (e) => { // Prevent the form from submitting normally e.preventDefault(); // Disable the button to prevent multiple submissions const submitButton = document.getElementById(paymentSubmitElementId); const buttonText = document.getElementById(buttonTextElementId); submitButton.style.visibility = 'visible'; submitButton.disabled = true; buttonText.textContent = 'Processing...'; // --- STEP 5: Confirm the payment --- const { error } = await stripe.confirmPayment({ elements, confirmParams: { // Return URL after payment is complete (e.g., your success page) return_url: `https://nectly.io/api/stripe-payment-success/${response.professionalId}/${response.seekerId}/${response.calendarId}/${response.appointmentId}/${response.paymentId}`, } }); if (error) { // This case occurs if there was an error with card details (e.g., declined) // or other issues before the final confirmation. const messageBox = document.getElementById(paymentErrorElementId); messageBox.textContent = error.message; messageBox.style.display = 'block'; // Re-enable the button for the user to try again submitButton.disabled = false; buttonText.textContent = 'Pay now'; } }); } const body = { "seekerEmail": reserveSlotBody.attendee, "seekerName": reserveSlotBody.attendeeName, "slot": reserveSlotBody, "professionalId": professionalId, "attendeeMessage": reserveSlotBody.attendeeMessage || "" }; showLogoLoader(); callApi('/api/stripe-create-intent', 'POST', body) .then(data => { hideLogoLoader(); onPaymentIntentSuccess(data); }) .catch(error => { console.error('Stripe subscription failed:', error); hideLogoLoader(); toggleConfirmationButton(false); }); } function startCreditCardAuthorization(stripePublicKey) { const stripe = new Stripe(stripePublicKey); const elements = stripe.elements({ mode: 'setup', currency: 'eur', }); const paymentElement = elements.create('payment', { layout: 'accordion'}); paymentElement.mount(`#${paymentElementId}`); const submitBtn = document.getElementById(paymentSubmitElementId); submitBtn.style.visibility = 'visible'; const handleError = (error) => { const paymentErrorElement = document.getElementById(paymentErrorElementId); paymentErrorElement.innerHTML = error.message; submitBtn.disabled = false; } const popupWrapper = document.getElementById(popupWrapperElementId); popupWrapper.style.visibility = 'visible'; popupWrapper.style.opacity = '1'; const form = document.getElementById(paymentFormElementId); form.addEventListener('submit', async (event) => { event.preventDefault(); if (submitBtn.disabled) { return; } submitBtn.disabled = true; const {error: submitError} = await elements.submit(); if (submitError) { handleError(submitError); return; } showLogoLoader(); callApi('/api/stripe-subscription', 'POST') .then(data => { console.log('Stripe subscription successful:', data); hideLogoLoader(); const response = JSON.parse(data.response); stripe.confirmSetup({ elements: elements, clientSecret: response.clientSecretForFirstPayment, confirmParams: { return_url: 'https://nectly.io/seekers-dashboard.html', }, redirect: 'always' }).then((result) => { hideLogoLoader(); if(result.error) { handleError(result.error); console.error('Payment failed:', result.error); } else { console.log('Payment subscription successful:', result); } }); }) .catch(error => { console.error('Stripe subscription failed:', error); hideLogoLoader(); }); }); } function closePaymentPopup() { const popupWrapper = document.getElementById(popupWrapperElementId); popupWrapper.style.visibility = 'hidden'; popupWrapper.style.opacity = '0'; } const closePaymentPopupElement = document.getElementById(closePaymentPopupElementId); if (closePaymentPopupElement) closePaymentPopupElement.addEventListener("click", function(event) { event.preventDefault(); closePaymentPopup(); }); document.addEventListener("keydown", function(event) { if (event.key === "Escape") { closePaymentPopup(); } }); const paymentPopupWrapperElement = document.getElementById(popupWrapperElementId); if (paymentPopupWrapperElement) paymentPopupWrapperElement.addEventListener("click", function(event) { const form = document.querySelector(".popup-form"); if (!form.contains(event.target)) { closePaymentPopup(); } }); function redirectToStripeConnectLink() { showLogoLoader(); callApi('/api/stripe-connect/get-account-link', 'GET') .then(data => { hideLogoLoader(); console.log('Get Stripe Connect Link successful:', data); const response = JSON.parse(data.response); window.location.href = response.link; }) .catch(error => { hideLogoLoader(); console.error('Stripe subscription failed:', error); }); } function renderCharityPayoutConfirmationWithTimeout() { renderProCharityConfirmationElement(proSetPaymentMethodCharityConfirmationElementId); const proSetPaymentMethodCharityConfirmationElement = document.getElementById(proSetPaymentMethodCharityConfirmationElementId); if (!proSetPaymentMethodCharityConfirmationElement) return; proSetPaymentMethodCharityConfirmationElement.classList.remove('slide-down'); proSetPaymentMethodCharityConfirmationElement.classList.add('slide-up'); setTimeout(function() { proSetPaymentMethodCharityConfirmationElement.classList.remove('slide-up'); proSetPaymentMethodCharityConfirmationElement.classList.add('slide-down'); }, 5000); } function setupCharity(charityPayoutType) { const body = { "charityPayoutType": charityPayoutType }; callApi('/api/professional-setup-charity', 'POST', body) .then(data => { console.log('Setup charity successful:', data); renderCharityPayoutConfirmationWithTimeout() proRefreshCalendarSlots(); }) .catch(error => { console.error('Setup charity failed:', error); }); } function proResetPayout() { const postReserveSlotElement = document.getElementById('account-settings-save-result'); const successMessage = `

    Account reset successfully.

    `; const failedMessage = `

    Failed to reset your account.

    `; callApi('/api/professional-reset-payouts', 'DELETE') .then(data => { console.log('Professional payout reset successful:', data); postReserveSlotElement.innerHTML = successMessage; }) .catch(error => { console.error('Professional payout reset failed:', error); postReserveSlotElement.innerHTML = failedMessage; }); } function requestStripeInformation() { showLogoLoader(); callApi('/api/stripe-connect/request-information', 'GET') .then(data => { hideLogoLoader(); console.log('Get Stripe Connect Link successful:', data); const response = JSON.parse(data.response); window.location = response.requestInformationLink; }) .catch(error => { hideLogoLoader(); console.error('Stripe subscription failed:', error); }); return false; } function activateProfessional() { function onAddUserRoleSuccess() { console.log("Professional role added successfully."); window.location = '/professional-dashboard.html'; } const dashboard = getMeta('dashboard'); if (dashboard) { addUserRole(NECTLY_USER_ROLE_PROFESSIONAL, onAddUserRoleSuccess, (data) => { if (data.status === 400) { // 400 means the user already has a role window.location = '/seekers-dashboard.html'; } else { window.location = '/professional-dashboard.html'; } }); } else { window.location = '/register.html?role=professional'; } } function activateSeeker() { function onAddUserRoleSuccess() { console.log("Professional role added successfully."); window.location = '/seekers-dashboard.html'; } const dashboard = getMeta('dashboard'); if (dashboard) { addUserRole(NECTLY_USER_ROLE_SEEKER, '/seekers-dashboard.html', onAddUserRoleSuccess, (data) => { if (data.status === 400) { // 400 means the user already has a role window.location = '/professional-dashboard.html'; } else { window.location = '/seekers-dashboard.html'; } }); } else { window.location = '/register.html?role=seeker'; } } const conversationHistoryTableElementId = 'conversation-history-table'; let conversationHistoryPages = []; let conversationHistoryIndex = -1; function renderConversationHistoryTable(action) { /* { "items": [ {"startTime": "2025-05-29T19:30:00+02:00", "seeker": true, "professional": true, "type": "paid", "amount": "€30.00"}, {"startTime": "2025-05-29T20:30:00+02:00", "seeker": true, "professional": true, "type": "paid", "amount": "€30.00"} ], "nextToken": "next-token-id" } */ function clearConversationHistoryTable(conversationHistoryTableElement) { conversationHistoryTableElement.replaceChildren(); conversationHistoryTableElement.innerHTML = ""; } function renderTableHeader(conversationHistoryTableElement, role) { const headerElement = document.createElement('div'); if (role === NECTLY_USER_ROLE_PROFESSIONAL) { headerElement.innerHTML = `

    Start time and date

    Seeker

    Professional

    Payment status

    Amount

    `; } else { headerElement.innerHTML = `

    Start time and date

    Seeker

    Professional

    Payment status

    Receipt

    `; } headerElement.className = "conversation-history-table-header"; conversationHistoryTableElement.appendChild(headerElement); } function renderNoConversation(conversationHistoryTableElement) { const emptyMessageElement = document.createElement('div'); emptyMessageElement.className = "conversation-history-status-container flex flex-column gap-1"; emptyMessageElement.innerHTML = ` No Conversations Illustration

    No past conversations yet.

    Once you’ve had a few, they’ll appear here—your personal history of meaningful, 20-minute connections.

    `; conversationHistoryTableElement.appendChild(emptyMessageElement); } function renderTableItem(conversationHistoryTableElement, item) { const startTime = new Date(item.startTime); let seeker = 'No'; let seekerClass = 'system-status no'; if (item.seeker) { seeker = 'Yes' seekerClass = 'system-status yes'; } const rowElement = document.createElement('div'); let professional = 'No'; let professionalClass = 'system-status no'; if (item.professional) { professional = 'Yes'; professionalClass = 'system-status yes'; } let paymentStatus = 'Unpaid'; let paymentStatusClass = 'system-status unpaid'; if (item.type === 'paid') { paymentStatus = 'Paid ' + item.amount; paymentStatusClass = 'system-status paid'; } if (item.type === 'charity') { paymentStatus = 'For charity'; paymentStatusClass = 'system-status charity'; } let receipt = ''; if (item.receiptUrl) { receipt = ` View receipt`; } else { receipt = ` (No receipt available)`; } if (item.role === NECTLY_USER_ROLE_PROFESSIONAL) { rowElement.innerHTML = `

    ${startTime.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: true })}

    ${seeker}

    ${professional}

    ${paymentStatus}

    ${item.amount}

    `; } else { rowElement.innerHTML = `

    ${startTime.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: true })}

    ${seeker}

    ${professional}

    ${paymentStatus}

    ${receipt}

    `; } rowElement.className = "conversation-history-table-row"; conversationHistoryTableElement.appendChild(rowElement); } function renderPagination(conversationHistoryTableElement, historyPage) { if (historyPage.nextToken) { const paginationElement = document.createElement('div'); paginationElement.className = "pagination"; if (conversationHistoryIndex <= 0) { paginationElement.innerHTML = ` `; } else { paginationElement.innerHTML = ` `; } conversationHistoryTableElement.appendChild(paginationElement); } else if (conversationHistoryIndex > 0) { const paginationElement = document.createElement('div'); paginationElement.className = "pagination"; paginationElement.innerHTML = ` `; conversationHistoryTableElement.appendChild(paginationElement); } else { const paginationElement = document.createElement('div'); paginationElement.className = "pagination"; paginationElement.innerHTML = ` `; conversationHistoryTableElement.appendChild(paginationElement); } } function renderTable(conversationHistoryTableElement, historyPage) { clearConversationHistoryTable(conversationHistoryTableElement); if (historyPage.items.length === 0) { renderNoConversation(conversationHistoryTableElement); return; } renderTableHeader(conversationHistoryTableElement, historyPage.items[0].role); historyPage.items.forEach(item => { renderTableItem(conversationHistoryTableElement, item); }); renderPagination(conversationHistoryTableElement, historyPage); } const conversationHistoryTableElement = document.getElementById(conversationHistoryTableElementId); if (!conversationHistoryTableElement) { console.error(`Element with ID ${conversationHistoryTableElementId} not found.`); return; } if (action === 'init') { showLogoLoader(); callApi('/api/conversation/history', 'GET') .then(data => { try { console.log('Get conversation history successful:', data); const historyPage = JSON.parse(data.response); conversationHistoryPages = [historyPage]; conversationHistoryIndex = 0; renderTable(conversationHistoryTableElement, historyPage); hideLogoLoader(); } catch (error) { console.error('Error processing conversation history:', error); hideLogoLoader(); } }) .catch(error => { console.error('Get conversation history failed:', error); hideLogoLoader(); }); } if (action === 'next' && conversationHistoryIndex === conversationHistoryPages.length - 1) { showLogoLoader(); callApi(`/api/conversation/history&nextToken=${conversationHistoryPages[conversationHistoryIndex].nextToken}`, 'GET') .then(data => { try { console.log('Get conversation history successful:', data); const historyPage = JSON.parse(data.response); conversationHistoryPages.push(historyPage); conversationHistoryIndex += 1; renderTable(conversationHistoryTableElement, historyPage); hideLogoLoader(); } catch (error) { console.error('Error processing conversation history:', error); hideLogoLoader(); } }) .catch(error => { console.error('Get conversation history failed:', error); hideLogoLoader(); }); } if (action === 'next' && conversationHistoryIndex < conversationHistoryPages.length - 1) { conversationHistoryIndex += 1; const historyPage = conversationHistoryPages[conversationHistoryIndex]; renderTable(conversationHistoryTableElement, historyPage); } if (action === 'prev') { if (conversationHistoryIndex > 0) conversationHistoryIndex -= 1; const historyPage = conversationHistoryPages[conversationHistoryIndex]; renderTable(conversationHistoryTableElement, historyPage); } } function initLinkButtonGlowEffect() { const wrapper = document.querySelector('.glow-button-wrapper'); if (wrapper) { wrapper.addEventListener('mousemove', (e) => { const rect = wrapper.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const angle = Math.atan2(y - rect.height / 2, x - rect.width / 2) * (180 / Math.PI); const deg = (angle + 360) % 360; wrapper.style.setProperty('--start', deg); wrapper.style.setProperty('--active', 1); }); wrapper.addEventListener('mouseleave', () => { wrapper.style.setProperty('--active', 0); }); } } const tabs = document.querySelectorAll('.tab'); const contents = document.querySelectorAll('.tab-content'); tabs.forEach(tab => { tab.addEventListener('click', () => { const tabId = tab.getAttribute('data-tab'); tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); contents.forEach(c => { c.classList.toggle('active', c.getAttribute('data-content') === tabId); }); }); }); function setupCancelMeetingToggle() { document.querySelectorAll('.slot-list-item').forEach(item => { const cancelBtn = item.querySelector('#cancelMeetingButton'); const confirmBtn = item.querySelector('#cancelMeetingConfirmationButton'); const declineBtn = item.querySelector('#cancelMeetingDeclineButton'); const cancelWindow = item.querySelector('#cancel-conversation-window'); const meetingContainer = item.querySelector('.meeting-button-container'); if (!cancelBtn || !confirmBtn || !declineBtn || !cancelWindow || !meetingContainer) return; // Init styles cancelWindow.style.height = "0px"; cancelWindow.style.opacity = "0"; cancelWindow.style.padding = "0"; cancelWindow.style.overflow = "hidden"; cancelWindow.style.transition = "height 0.3s ease, opacity 0.3s ease, padding 0.3s ease"; meetingContainer.style.transition = "height 0.3s ease, padding 0.3s ease"; // OPEN: Cancel Conversation cancelBtn.addEventListener("click", () => { item.classList.add("cancel-active"); const contentHeight = cancelWindow.scrollHeight; cancelWindow.style.height = contentHeight + "px"; cancelWindow.style.opacity = "1"; cancelWindow.style.padding = "1rem"; meetingContainer.style.height = "0px"; }); // CLOSE: Confirm or Decline const resetView = () => { item.classList.remove("cancel-active"); cancelWindow.style.height = "0px"; cancelWindow.style.opacity = "0"; cancelWindow.style.padding = "0"; meetingContainer.style.height = ""; meetingContainer.style.padding = ""; }; confirmBtn.addEventListener("click", resetView); declineBtn.addEventListener("click", resetView); }); } function setupTogglePairs() { document.querySelectorAll('.toggle-pair').forEach(pair => { const a = pair.querySelector('.toggle-a'); const b = pair.querySelector('.toggle-b'); if (!a || !b) return; a.style.height = a.scrollHeight + 'px'; pair.addEventListener('mouseenter', () => { a.style.height = '0px'; a.style.opacity = '0'; b.style.height = b.scrollHeight + 'px'; b.style.opacity = '1'; }); pair.addEventListener('mouseleave', () => { a.style.height = a.scrollHeight + 'px'; a.style.opacity = '1'; b.style.height = '0px'; b.style.opacity = '0'; }); }); } new ResizeObserver(entries => { for (let entry of entries) { const container = entry.target; const { width, height } = entry.contentRect; if (width > height) { container.classList.add('row-layout'); container.classList.remove('column-layout'); } else { container.classList.add('column-layout'); container.classList.remove('row-layout'); } } }); function loadFaqToggle() { const toggles = document.querySelectorAll('.faq-toggle'); if (!toggles) return; toggles.forEach(btn => { btn.addEventListener('click', () => { const expanded = btn.getAttribute('aria-expanded') === 'true'; const content = document.getElementById(btn.getAttribute('aria-controls')); const icon = btn.querySelector('.faq-icon'); btn.setAttribute('aria-expanded', String(!expanded)); icon.textContent = expanded ? '+' : '–'; content.classList.toggle('open', !expanded); }); }); // delayed auto-toggle (to be safe after scroll & layout) setTimeout(() => { const hash = location.hash.slice(1); if (!hash) return; const section = document.getElementById(hash); if (!section) return; const autoBtn = section.querySelector('.faq-toggle'); if (autoBtn && autoBtn.getAttribute('aria-expanded') === 'false') { autoBtn.click(); } }, 200); } function initSwipeSections() { const menu = document.querySelector('.app-menu'); const container = document.querySelector('.scroll-container'); if (!menu || !container) return; const buttons = menu.querySelectorAll('button'); function scrollToIndex(index) { const containerWidth = container.clientWidth; container.scrollTo({ left: index * containerWidth, behavior: 'smooth', }); } function updateActiveButton() { const index = Math.round(container.scrollLeft / container.clientWidth); buttons.forEach((btn, i) => { btn.classList.toggle('active', i === index); }); } buttons.forEach((button, index) => { button.addEventListener('click', () => { scrollToIndex(index); }); }); container.addEventListener('scroll', () => { updateActiveButton(); }); // Nastaviť správny button na začiatku updateActiveButton(); } function safeInitSwipeSections(attempt = 0) { const menus = document.querySelectorAll('.app-menu'); const containers = document.querySelectorAll('.scroll-container'); if (menus.length > 0 && containers.length > 0) { // overíme, či už majú kontajnery nenulovú šírku const allReady = Array.from(containers).every((c) => c.clientWidth > 0); if (allReady) { initSwipeSections(); } else if (attempt < 20) { setTimeout(() => safeInitSwipeSections(attempt + 1), 100); // opakuj max. 20x } } else if (attempt < 20) { setTimeout(() => safeInitSwipeSections(attempt + 1), 100); } } let isAnimating = false; function setupNectlyLinkButtonAnimation() { const button = document.getElementById('nectly-link-button'); if (!button) return; if (button.dataset.animationInitialized === 'true') return; button.dataset.animationInitialized = 'true'; button.addEventListener('click', () => { if (isAnimating) return; isAnimating = true; button.classList.add('clicked'); setTimeout(() => { button.classList.remove('clicked'); button.classList.add('returning'); setTimeout(() => { button.classList.remove('returning'); isAnimating = false; }, 300); }, 3000); }); } function showPopup() { const overlay = document.getElementById('popup-overlay'); if (overlay) overlay.classList.add('active'); } function hidePopup() { const overlay = document.getElementById('popup-overlay'); if (overlay) overlay.classList.remove('active'); } document.addEventListener('click', (e) => { const openTrigger = e.target.closest('[data-popup="open"]'); const closeTrigger = e.target.closest('[data-popup="close"]'); const overlay = document.getElementById('popup-overlay'); const popupWindow = document.getElementById('popup-window'); if (openTrigger) { e.preventDefault(); showPopup(); } if (closeTrigger) { e.preventDefault(); hidePopup(); } if ( overlay && overlay.classList.contains('active') && !popupWindow.contains(e.target) && !openTrigger && !closeTrigger ) { hidePopup(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hidePopup(); }); function getStarted() { function onGetUserRoleSuccess(response) { console.info('User get role called successfully:' + response); if (response.roles.includes(NECTLY_USER_ROLE_PROFESSIONAL)) { window.location.href = '/professional-dashboard.html'; } else if (response.roles.includes(NECTLY_USER_ROLE_SEEKER)) { window.location.href = '/seekers-dashboard.html'; } else { window.location.href = '/choose-role.html'; } } function onError(error) { console.error('Error getting user role:', error); window.location.href = '/choose-role.html'; } getUserRole(onGetUserRoleSuccess, onError); } function setRate() { function validateInputs(body) { if (!body) return false; const { rate, currency, length, buffer, description } = body; if (!Number.isInteger(rate) || rate <= 0 || rate > 10000) { if (rate > 10000) alert('Please set a rate below €10,000.'); document.getElementById('rate-input').focus(); return false; } if (typeof currency !== 'string' || currency.length === 0) { if (currency.length !== 'eur') alert('Please select a valid currency.'); document.getElementById('currency-select').focus(); return false; } if (!Number.isInteger(length) || length < 10 || length > 240) { if (length > 240) alert('Please set a meeting length below 240 minutes.'); if (length < 10) alert('Please set a meeting length at least 10 minutes.'); document.getElementById('length-input').focus(); return false; } if (!Number.isInteger(buffer) || buffer < 0 || buffer > 240) { if (buffer > 240) alert('Please set a buffer time below 240 minutes.'); document.getElementById('buffer-input').focus(); return false; } if (typeof description !== 'string') { document.getElementById('description-input').focus(); return false; } return true; } const dashboard = getMeta('dashboard'); const body = { "rate": parseInt(document.getElementById('rate-input').value), "currency": document.getElementById('currency-select').value.toLowerCase(), "length": parseInt(document.getElementById('length-input').value), "buffer": parseInt(document.getElementById('buffer-input').value), "description": document.getElementById('description-input').value, }; if (!validateInputs(body)) { console.info('Invalid input values:', body); return; } console.log("Setting rate:", body); return callApi('/api/set-rate', 'POST', body) .then(data => { console.log('Set rate successful:', data); window.location.href = `/${dashboard}`; return; }) .catch(error => { console.error('Set rate failed:', error); }); } function getRate(professionalId) { function onGetRateSuccess(data) { console.log('Get rate successful:', data); const response = JSON.parse(data.response); if (response?.rate && document.getElementById('rate-input')) document.getElementById('rate-input').value = response.rate; if (response?.currency && document.getElementById('currency-select')) document.getElementById('currency-select').value = response.currency.toUpperCase(); if (response?.length && document.getElementById('length-input')) document.getElementById('length-input').value = response.length; if (response?.buffer && document.getElementById('buffer-input')) document.getElementById('buffer-input').value = response.buffer; if (response?.description && document.getElementById('description-input')) document.getElementById('description-input').value = response.description; if (response?.length && document.getElementById(linkHeadDurationElementId)) { document.getElementById(linkHeadDurationElementId).innerHTML = `${response.length}`; } if (response?.rate && document.getElementById(linkHeadPriceElementId)) { document.getElementById(linkHeadPriceElementId).innerHTML = `$${response.rate}`; } if (response?.description && document.getElementById(linkDescriptionElementId)) { document.getElementById(linkDescriptionElementId).innerHTML = response.description; } lastRateSettings = response; } function onGetRateError(error) { console.error('Get rate failed:', error); } const uri = professionalId ? `/api/get-rate/${professionalId}` : '/api/get-rate'; callApi(uri, 'GET') .then(onGetRateSuccess) .catch(onGetRateError); } function showLogoLoader() { const overlay = document.getElementById("logo-loader-overlay"); const box = document.getElementById("logo-loader"); if (!overlay || !box || typeof lottie === "undefined") return; if (!box.dataset.inited) { box._anim = lottie.loadAnimation({ container: box, renderer: "svg", loop: true, autoplay: false, path: "json/loading-logo.json" }); box.dataset.inited = "true"; } overlay.style.display = "grid"; box._anim.play(); } function hideLogoLoader() { const overlay = document.getElementById("logo-loader-overlay"); const box = document.getElementById("logo-loader"); if (!overlay || !box || !box._anim) return; box._anim.stop(); overlay.style.display = "none"; } function sumbit_meeting_cancellation() { const button = document.getElementById('meeting_cancellation_button'); const button_text = document.getElementById('meeting_cancelation_button_text'); disable_button(button, button_text); const reason = document.getElementById('reason').value; const queryString = window.location.search; const params = new URLSearchParams(queryString); const user_id = params.get('UserId'); const appointment_id = params.get('appointmentId'); const cancellation_token = params.get('cancellationToken'); let url = ''; let method = ''; if (user_id) { method = 'POST' url = `/api/appointment/${user_id}/${appointment_id}/cancel` if (cancellation_token) { url += `?cancellationToken=${cancellation_token}`; } } else { method = 'DELETE'; url = `/api/appointment/${appointment_id}`; } callApi(url, method, { "reason": reason }).then(data => { console.log('Appointment cancellation successful:', data); cancellationPopupRender(`

    Your Nectly conversation was canceled.

    We’ve let the other side know. Check your email for details and next steps.

    `); stop_loader(button, button_text); if (getMeta('dashboard')) { setTimeout(function() { const dashboard = getMeta('dashboard'); window.location.href = `https://nectly.io/${dashboard}`; }, 2500); } }).catch(error => { if (error.status === 404) { console.info('Conversation has already been canceled:', error); cancellationPopupRender(`

    This conversation has already been canceled.

    `); } else { console.error('Appointment cancellation failed:', error); cancellationPopupRender(`

    Failed to cancel the conversation.

    `) } stop_loader(button, button_text); }); } function cancellationPopupRender(message) { const cancellationConfirmationElement = document.getElementById(cancellationConfirmationElementId); if (cancellationConfirmationElement) { cancellationConfirmationElement.className = "notification-message notification-position"; cancellationConfirmationElement.innerHTML = message; cancellationConfirmationElement.classList.add('show'); cancellationConfirmationElement.classList.remove('slide-down'); cancellationConfirmationElement.classList.add('slide-up'); setTimeout(function() { cancellationConfirmationElement.classList.remove('slide-up'); cancellationConfirmationElement.classList.add('slide-down'); }, 5000); } else { console.error(`Element with ID ${cancellationConfirmationElementId} not found.`); } }