/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "NECTLY_.*|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*/ 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 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) { var url = new URL("https://buy.stripe.com/test_28E4gA0aJ4iM0BqaaF7Zu01"); 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 btnEl = dateEl.querySelector('[data-vc-date-btn]'); const btnDate = new Date(dateEl.getAttribute('data-vc-date')); if (btnDate.getMonth() !== self.context.selectedMonth) { return; } if (self.context.mainElement.id === linkCalendarElementId) { if (activeLinkDayElement === null) { const today = new Date(); if (today.getDate() === btnDate.getDate()) { activeLinkDayElement = btnEl; } } } else { if (activeMobileLinkDayElement === null) { const today = new Date(); if (today.getDate() === btnDate.getDate()) { activeMobileLinkDayElement = btnEl; } } } const day = btnEl.innerText; if (parseInt(day) === 1) { if (self.context.mainElement.id === linkCalendarElementId) { resolveLinkDateElements = [dateEl]; linkLastDayInMonth = getLastDayOfMonth(btnDate.getFullYear(), convertIntMonthToString(btnDate.getMonth())); } else { resolveMobileLinkDateElements = [dateEl]; linkMobileLastDayInMonth = getLastDayOfMonth(btnDate.getFullYear(), convertIntMonthToString(btnDate.getMonth())); } } 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() === btnDate.getDate()) { getCurrentMonth(getLinkPageUserId(), endDayToCompare.toISOString(), function(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')); const today = new Date(); today.setHours(0, 0, 0, 0); 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)); }, 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(); if (refreshSlots) { const now = new Date(); refreshCalendarSlots("link-calendar-slots-list", now); refreshSlotsHeader(now); } return calendar; } function onLoad() { renderTopMenu(); renderFooter(); setupTogglePairs(); loadFaqToggle(); initLinkButtonGlowEffect(); if (typeof google !== 'undefined') { google.accounts.id.initialize({ client_id: "1089541127844-jciirjufc6pli3d0af9o9s8mq4t0qk0d.apps.googleusercontent.com", callback: function (response) {console.info('Google Auth Response: ', response);}, login_uri: "https://nectly.io/google-oauth-redirect", ux_mode: "redirect", context: "signup" }); google.accounts.id.prompt(); 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: "left", locale: "en", } ); } } if (document.getElementById(earlyAccessElementId)) renderEarlyAccess(); setupEarlyAccessToggle(); 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(); } 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 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)); } 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)); } 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; } 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 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 getCurrentMonth(seekerId, selectedDate.toISOString(), function(currentMonth) { console.log("Fetched slots:", currentMonth.slots); updateCalendarDays(currentMonth.busyDaysInMonth); updateMobileCalendarDays(currentMonth.busyDaysInMonth); document.getElementById("link-welcome-message").innerHTML = `${currentMonth.seekerFullName} has invited you to 20-minute meeting`; if (currentMonth.slots?.length === 0) { const errorMessage = `
Try another date.
Don’t worry, just choose another slot to connect.
Try to refresh later.
Don’t worry, just choose another slot to connect.
Check your inbox for the details—and enjoy the conversation.
Time zone
${timeZoneString}
`; const timeZoneElement = document.getElementById("time-zone-container"); if (timeZoneElement) { timeZoneElement.innerHTML = innerHTMLString; } else { console.error("Element with ID time-zone-container not found."); } const timeZoneMobileElement = document.getElementById("time-zone-container-mobile"); if (timeZoneMobileElement) { timeZoneMobileElement.innerHTML = innerHTMLString; } else { console.error("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 loginIsHidden = getMeta('login-is-hidden'); const logoutIsHidden = getMeta('logout-is-hidden'); let hideAvatarMenu = 'style="display:none"'; if (logoutIsHidden.trim().length === 0) hideAvatarMenu = ''; let settingsHtml = ` Settings `; if (getMeta('dashboard') === 'professional-dashboard.html') settingsHtml = ''; topMenu.innerHTML = ` `; if (document.querySelector('.scroll-container')) { topMenu.innerHTML += ` `; safeInitSwipeSections(); } } else { console.error("Element with ID top-menu not found."); } } function renderEarlyAccess() { const earlyAccess = document.getElementById(earlyAccessElementId); if (!earlyAccess) { console.error('Element with ID early-access not found.'); return; } earlyAccess.innerHTML = `