diff --git a/.env b/.env index decd9d0..4190848 100644 --- a/.env +++ b/.env @@ -46,7 +46,8 @@ GITHUB_SECRET= TWITTER_ID= TWITTER_SECRET= - +EMAIL_BYPASS_TO=mwitnessing@gmail.com +EMAIL_SENDER='"Специално Свидетелстване София " ' # EMAIL_SERVER=smtp://8ec69527ff2104:c7bc05f171c96c@smtp.mailtrap.io:2525 EMAIL_FROM=noreply@mwitnessing.com @@ -61,8 +62,8 @@ MAILERSEND_PORT=587 MAILERSEND_USER=MS_bL93ka@mwitnessing.com MAILERSEND_PASS=v23Z2XrDSNjHJxgo -GMAIL_EMAIL_USERNAME= -GMAIL_EMAIL_APP_PASS= +EMAIL_GMAIL_USERNAME=mwitnessing +EMAIL_GMAIL_APP_PASS="acys uzsp eere qzyh" TELEGRAM_BOT=false TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM diff --git a/.env.production b/.env.production index 82f44eb..112901f 100644 --- a/.env.production +++ b/.env.production @@ -9,6 +9,7 @@ NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638 DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia +EMAIL_BYPASS_TO= MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io MAILTRAP_HOST=live.smtp.mailtrap.io MAILTRAP_USER=api diff --git a/.env.test b/.env.test index 6a513e6..52a165f 100644 --- a/.env.test +++ b/.env.test @@ -10,26 +10,13 @@ NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638 # ? do we need to duplicate this? already defined in the deoployment yml file DATABASE=mysql://jwpwsofia_demo:dwxhns9p9vp248@mariadb:3306/jwpwsofia_demo -APPLE_ID= -APPLE_TEAM_ID= -APPLE_PRIVATE_KEY= -APPLE_KEY_ID= - AUTH0_ID=Aa9f3HJowauUrmBVY4iQzQJ7fYsaZDbK AUTH0_SECRET=_c0O9GkyRXkoWMQW7jNExnl6UoXN6O4oD3mg7NZ_uHVeAinCUtcTAkeQmcKXpZ4x AUTH0_ISSUER=https://dev-wkzi658ckibr1amv.us.auth0.com -FACEBOOK_ID= -FACEBOOK_SECRET= - -GITHUB_ID= -GITHUB_SECRET= # GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com # GOOGLE_SECRET=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 -TWITTER_ID= -TWITTER_SECRET= - MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io MAILTRAP_HOST=live.smtp.mailtrap.io MAILTRAP_USER=api diff --git a/_deploy/deoloy.azure.production.yml b/_deploy/deoloy.azure.production.yml index 7f8bcdb..eb32023 100644 --- a/_deploy/deoloy.azure.production.yml +++ b/_deploy/deoloy.azure.production.yml @@ -19,7 +19,7 @@ services: - GIT_USERNAME=deploy - GIT_PASSWORD=L3Kr2R438u4F7 command: sh -c " cd /app && npm install && npm run prod; tail -f /dev/null" - #command: sh -c " cd /app && n + #command: sh -c " cd /app && tail -f /dev/null" tty: true stdin_open: true restart: always diff --git a/components/availability/AvailabilityForm.js b/components/availability/AvailabilityForm.js index 721df18..3da3063 100644 --- a/components/availability/AvailabilityForm.js +++ b/components/availability/AvailabilityForm.js @@ -10,6 +10,8 @@ import { bgBG } from '../x-date-pickers/locales/bgBG'; import { ToastContainer } from 'react-toastify'; const common = require('src/helpers/common'); //todo import Availability type from prisma schema +import { isBefore, addMinutes, isAfter, isEqual, set, getHours, getMinutes, getSeconds } from 'date-fns'; + const fetchConfig = async () => { @@ -183,59 +185,6 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o return groupedIntervals; } - // // const firstSlotWithTransport = timeSlots[0].checked && timeSlots[0]?.isWithTransport; - // // const lastSlotWithTransport = timeSlots[timeSlots.length - 1].checked && timeSlots[timeSlots.length - 1]?.isWithTransport; - // function createAvailabilityFromGroup(group) { - // let startTime = new Date(day); - // startTime.setHours(group[0].startTime.getHours(), group[0].startTime.getMinutes(), group[0].startTime.getSeconds(), 0); - - // let endTime = new Date(day); - // endTime.setHours(group[group.length - 1].endTime.getHours(), group[group.length - 1].endTime.getMinutes(), group[group.length - 1].endTime.getSeconds(), 0); - - - // return { - // name: common.getTimeFomatted(startTime) + "-" + common.getTimeFomatted(endTime), - // publisherId: publisher.id, - // startTime: startTime, - // endTime: endTime, - // isWithTransportIn: group[0].isFirst && timeSlots[0].isWithTransport, - // isWithTransportOut: group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport, - // dayofweek: common.getDayOfWeekNameEnEnumForDate(day.getDay()), - // repeatWeekly: doRepeat, - // dayOfMonth: doRepeat ? null : startTime.getDate(), - // endDate: doRepeat ? repeatUntil : null, - // dateOfEntry: new Date(), - // }; - // } - - // function updateAvailabilityFromGroup(availability, group) { - // availability.startTime.setTime(group[0].startTime); - // availability.endTime.setTime(group[group.length - 1].endTime); - // availability.name = common.getTimeFomatted(availability.startTime) + "-" + common.getTimeFomatted(availability.endTime); - - // availability.isWithTransportIn = group[0].isFirst && timeSlots[0].isWithTransport; - // availability.isWithTransportOut = group[group.length - 1].isLast && timeSlots[timeSlots.length - 1].isWithTransport; - - // delete availability.weekOfMonth; - // if (doRepeat) { - // availability.repeatWeekly = true; - // availability.dayOfMonth = null; - // availability.weekOfMonth = 0; - // availability.endDate = repeatUntil; - // } else { - // availability.repeatWeekly = false; - // availability.dayOfMonth = availability.startTime.getDate(); - // availability.endDate = null; - // } - - // availability.dateOfEntry = new Date(); - // if (availability.parentAvailabilityId) { - // availability.parentAvailability = { connect: { id: parentAvailabilityId } }; - // } - // delete availability.parentAvailabilityId; - - // return availability; - // } // Common function to set shared properties function setSharedAvailabilityProperties(availability, group, timeSlots) { let startTime = new Date(availability.startTime || day); @@ -332,22 +281,25 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o } // console.log("AvailabilityForm: publisherId: " + publisher.id + ", id: " + availabilit .id, ", inline: " + isInline); - + //ToDo: this is examplary function to be used in the future. replace all date/time related functions with this one const generateTimeSlots = (start, end, increment, items) => { const slots = []; - let currentTime = start.getTime(); + let currentTime = start; - const endTime = end.getTime(); + const baseDate = new Date(2000, 0, 1); // Use a constant date for all time comparisons - while (currentTime < endTime) { - let slotStart = new Date(currentTime); - let slotEnd = new Date(currentTime + increment * 60000); // increment is in minutes + while (isBefore(currentTime, end)) { + let slotStart = normalizeTime(currentTime, baseDate); + let slotEnd = normalizeTime(addMinutes(currentTime, increment), baseDate); - const isChecked = items.some(item => - item.startTime && item.endTime && - (slotStart.getTime() < item.endTime.getTime()) && - (slotEnd.getTime() > item.startTime.getTime()) - ); + const isChecked = items.some(item => { + let itemStart = item.startTime ? normalizeTime(new Date(item.startTime), baseDate) : null; + let itemEnd = item.endTime ? normalizeTime(new Date(item.endTime), baseDate) : null; + + return itemStart && itemEnd && + (slotStart.getTime() < itemEnd.getTime()) && + (slotEnd.getTime() > itemStart.getTime()); + }); slots.push({ startTime: slotStart, @@ -355,10 +307,9 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o isChecked: isChecked, }); - currentTime += increment * 60000; // Increment in milliseconds (minutes to milliseconds) + currentTime = addMinutes(currentTime, increment); } - // Optional: Add isFirst, isLast, and isWithTransport properties if (slots.length > 0 && items?.length > 0) { slots[0].isFirst = true; slots[slots.length - 1].isLast = true; @@ -369,6 +320,16 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o return slots; }; + // Normalize the time part of a date by using a base date + function normalizeTime(date, baseDate) { + return set(baseDate, { + hours: getHours(date), + minutes: getMinutes(date), + seconds: getSeconds(date), + milliseconds: 0 + }); + } + const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => { const [allDay, setAllDay] = useState(slots.every(slot => slot.isChecked)); const handleAllDayChange = (e) => { diff --git a/components/calendar/avcalendar.tsx b/components/calendar/avcalendar.tsx index a83d257..4fb3e2a 100644 --- a/components/calendar/avcalendar.tsx +++ b/components/calendar/avcalendar.tsx @@ -16,7 +16,13 @@ import { MdToday } from 'react-icons/md'; import { useSwipeable } from 'react-swipeable'; import axiosInstance from '../../src/axiosSecure'; -import { set } from 'date-fns'; +// import { set, format, addDays } from 'date-fns'; +import { isEqual, isSameDay, getHours, getMinutes } from 'date-fns'; +import { filter } from 'jszip'; +import e from 'express'; + + + // Set moment to use the Bulgarian locale moment.locale('bg'); @@ -162,6 +168,31 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { occurrences.push(occurrence); } }; + const filterEvents = (evts, publisherId, startdate) => { + setDate(startdate); // Assuming setDate is a function that sets some state or context + + // Filter events based on the publisher ID and the start date/time + const existingEvents = evts?.filter(event => { + // Ensure the event belongs to the specified publisher + const isPublisherMatch = (event.publisher?.id || event.publisherId) === publisherId; + + let isDateMatch; + if (event.repeatWeekly && event.date) { + // Compare only the time part + const eventDate = new Date(event.startTime); + const isSameHour = getHours(eventDate) === getHours(startdate); + const isSameMinute = getMinutes(eventDate) === getMinutes(startdate); + isDateMatch = isSameHour && isSameMinute; + } else if (event.date) { + // Compare the full date + isDateMatch = isSameDay(new Date(event.date), startdate); + } + + return isPublisherMatch && isDateMatch; + }); + + return existingEvents; + }; // Define min and max times const minHour = 8; // 8:00 AM @@ -177,7 +208,8 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { const enddate = typeof end === 'string' ? new Date(end) : end; if (!start || !end) return; - if (startdate < new Date() || end < new Date() || startdate > end) return; + //readonly for past dates (ToDo: if not admin) + //if (startdate < new Date() || end < new Date() || startdate > end) return; // Check if start and end are on the same day if (startdate.toDateString() !== enddate.toDateString()) { @@ -198,52 +230,19 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { setDate(start); // get exising events for the selected date - const existingEvents = evts?.filter(event => (event.publisher?.id || event.publisherId) === publisherId && new Date(event.date).toDateString() === startdate.toDateString()); - // const existingEvents = evts?.filter(event => { - // return event.publisherId === publisherId && - // new Date(event.startTime).getFullYear() === start.getFullYear() && - // new Date(event.startTime).getMonth() === start.getMonth() && - // new Date(event.startTime).getDate() === start.getDate(); - // }); + let existingEvents = filterEvents(evts, publisherId, startdate); + // if existingEvents is empty - create new with the selected range + if (existingEvents.length === 0) { + existingEvents = [{ startTime: start, endTime: end }]; + } console.log("handleSelect: " + existingEvents); setSelectedEvents(existingEvents); - - // setSelectedEvent({ - // date: start, - // startTime: start, - // endTime: end, - // dayOfMonth: start.getDate(), - // isActive: true, - // publisherId: publisherId, - // // Add any other initial values needed - // //set dayOfMonth to null, so that we repeat the availability every week - // dayOfMonth: null, - - // }); setIsModalOpen(true); }; const handleEventClick = (event) => { if (event.type === "assignment") return; handleSelect({ start: event.startTime, end: event.endTime }); - // Handle event click - // const eventForEditing = { - // ...event, - // startTime: new Date(event.startTime), - // endTime: new Date(event.endTime), - // publisherId: event.publisherId || event.publisher?.connect?.id, - // repeatWeekly: event.repeatWeekly || false, - // }; - // //strip title, start, end and allDay properties - // delete eventForEditing.title; - // delete eventForEditing.start; - // delete eventForEditing.end; - // delete eventForEditing.type; - // delete eventForEditing.publisher - // console.log("handleEventClick: " + eventForEditing); - // setSelectedEvents([eventForEditing]); - // setIsModalOpen(true); - }; const handleDialogClose = async (dialogEvent) => { @@ -256,10 +255,8 @@ const AvCalendar = ({ publisherId, events, selectedDate }) => { newEvents.forEach(event => { event.startTime = new Date(event.startTime); event.endTime = new Date(event.endTime); - }); setEvents(newEvents); - } console.log("handleSave: ", dialogEvent); diff --git a/components/location/LocationForm.js b/components/location/LocationForm.js index 4f58fdb..be36371 100644 --- a/components/location/LocationForm.js +++ b/components/location/LocationForm.js @@ -7,7 +7,7 @@ import DayOfWeek from "../DayOfWeek"; import TextEditor from "../TextEditor"; import FileUploadWithPreview from 'components/FileUploadWithPreview '; -import ProtectedRoute, { serverSideAuth } from "../..//components/protectedRoute"; +import ProtectedRoute, { serverSideAuth } from "../../components/protectedRoute"; import { UserRole } from "@prisma/client"; const common = require('src/helpers/common'); diff --git a/components/publisher/PublisherCard.js b/components/publisher/PublisherCard.js index 248f067..c43025d 100644 --- a/components/publisher/PublisherCard.js +++ b/components/publisher/PublisherCard.js @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react' import toast from "react-hot-toast"; import axiosInstance from '../../src/axiosSecure'; +import ProtectedRoute, { serverSideAuth } from "../../components/protectedRoute"; //add months to date. works with negative numbers and numbers > 12 export function addMonths(numOfMonths, date) { @@ -53,6 +54,23 @@ export default function PublisherCard({ publisher }) { console.log(JSON.stringify(error)); } }; + const handleLoginAs = async (userId) => { + const response = await fetch('/api/auth/login-as', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ userId }), + }); + + if (response.ok) { + const data = await response.json(); + // Assuming you have some context or state management to update the session + updateSession(data.session); + } else { + alert("Failed to impersonate user."); + } + }; return isCardVisible ? ( // className="block p-6 max-w-sm bg-white rounded-lg border border-gray-200 shadow-md hover:bg-gray-100 dark:bg-gray-800 dark:border-gray-700 dark:hover:bg-gray-700 mb-3" @@ -89,6 +107,10 @@ export default function PublisherCard({ publisher }) { */} + + + +