Merge branch 'main' into production
This commit is contained in:
13
.env
13
.env
@@ -42,11 +42,20 @@ GITHUB_SECRET=
|
|||||||
TWITTER_ID=
|
TWITTER_ID=
|
||||||
TWITTER_SECRET=
|
TWITTER_SECRET=
|
||||||
|
|
||||||
EMAIL_SERVER=smtp://8ec69527ff2104:c7bc05f171c96c@smtp.mailtrap.io:2525
|
|
||||||
EMAIL_FROM=noreply@example.com
|
# EMAIL_SERVER=smtp://8ec69527ff2104:c7bc05f171c96c@smtp.mailtrap.io:2525
|
||||||
|
EMAIL_FROM=noreply@mwitnessing.com
|
||||||
|
|
||||||
|
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||||
|
MAILTRAP_HOST=sandbox.smtp.mailtrap.io
|
||||||
|
MAILTRAP_USER=8ec69527ff2104
|
||||||
|
MAILTRAP_PASS=c7bc05f171c96c
|
||||||
|
|
||||||
GMAIL_EMAIL_USERNAME=
|
GMAIL_EMAIL_USERNAME=
|
||||||
GMAIL_EMAIL_APP_PASS=
|
GMAIL_EMAIL_APP_PASS=
|
||||||
|
|
||||||
TELEGRAM_BOT=false
|
TELEGRAM_BOT=false
|
||||||
TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM
|
TELEGRAM_BOT_TOKEN=7050075088:AAH6VRpNCyQd9x9sW6CLm6q0q4ibUgYBfnM
|
||||||
|
|
||||||
|
NEXT_PUBLIC_VAPID_PUBLIC_KEY=BGxXJ0jdsQ4ihE7zp8mxrBO-QPSjeEtO9aCtPoMTuxc1VLW0OfRIt-DYinK9ekjTl2w-j0eQbeprIyBCpmmfciI
|
||||||
|
VAPID_PRIVATE_KEY=VXHu2NgcyM4J4w3O4grkS_0yLwWHCvVKDJexyBjqgx0
|
||||||
|
|||||||
@@ -6,4 +6,10 @@ NEXT_PUBLIC_PUBLIC_URL= https://sofia.mwitnessing.com
|
|||||||
# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
|
# Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
|
||||||
NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638
|
NEXTAUTH_SECRET=1dd8a5457970d1dda50600be28e935ecc4513ff27c49c431849e6746f158d638
|
||||||
# ? do we need to duplicate this? already defined in the deoployment yml file
|
# ? do we need to duplicate this? already defined in the deoployment yml file
|
||||||
DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia
|
DATABASE=mysql://jwpwsofia:dwxhns9p9vp248V39xJyRthUsZ2gR9@mariadb:3306/jwpwsofia
|
||||||
|
|
||||||
|
|
||||||
|
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||||
|
MAILTRAP_HOST=live.smtp.mailtrap.io
|
||||||
|
MAILTRAP_USER=api
|
||||||
|
MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d
|
||||||
@@ -30,5 +30,7 @@ GITHUB_SECRET=
|
|||||||
TWITTER_ID=
|
TWITTER_ID=
|
||||||
TWITTER_SECRET=
|
TWITTER_SECRET=
|
||||||
|
|
||||||
EMAIL_SERVER=smtp://8ec69527ff2104:c7bc05f171c96c@smtp.mailtrap.io:2525
|
MAILTRAP_HOST_BULK=bulk.smtp.mailtrap.io
|
||||||
EMAIL_FROM=noreply@example.com
|
MAILTRAP_HOST=live.smtp.mailtrap.io
|
||||||
|
MAILTRAP_USER=api
|
||||||
|
MAILTRAP_PASS=1cfe82e747b8dc3390ed08bb16e0f48d
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -10,8 +10,16 @@ lerna-debug.log*
|
|||||||
.yarn-integrity
|
.yarn-integrity
|
||||||
.npm
|
.npm
|
||||||
|
|
||||||
.eslintcache
|
# PWA files
|
||||||
|
**/public/sw.js
|
||||||
|
**/public/workbox-*.js
|
||||||
|
**/public/worker-*.js
|
||||||
|
**/public/sw.js.map
|
||||||
|
**/public/workbox-*.js.map
|
||||||
|
**/public/worker-*.js.map
|
||||||
|
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
nextjs-app: # https://sofia.mwhitnessing.com/
|
nextjs-app: # https://sofia.mwitnessing.com/
|
||||||
hostname: jwpw-app-staging # jwpw-nextjs-app-1
|
hostname: jwpw-app-staging # jwpw-nextjs-app-1
|
||||||
image: docker.d-popov.com/jwpw:latest
|
image: docker.d-popov.com/jwpw:latest
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -198,7 +198,13 @@ add assignment in calendar planner
|
|||||||
fix database
|
fix database
|
||||||
|
|
||||||
--
|
--
|
||||||
emails
|
emails: new shifts, replacements, announcements
|
||||||
mobile apps
|
mobile apps
|
||||||
apple login
|
apple login
|
||||||
разрешителни - upload
|
разрешителни - upload
|
||||||
|
wpa: android? apple? pc?
|
||||||
|
push notifications
|
||||||
|
store replacement
|
||||||
|
test email
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
227
components/PwaManager.tsx
Normal file
227
components/PwaManager.tsx
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import common from '../src/helpers/common'; // Ensure this path is correct
|
||||||
|
|
||||||
|
function PwaManager() {
|
||||||
|
const [deferredPrompt, setDeferredPrompt] = useState(null);
|
||||||
|
const [isPWAInstalled, setIsPWAInstalled] = useState(false);
|
||||||
|
const [isStandAlone, setIsStandAlone] = useState(false);
|
||||||
|
const [isSubscribed, setIsSubscribed] = useState(false);
|
||||||
|
const [subscription, setSubscription] = useState(null);
|
||||||
|
const [registration, setRegistration] = useState(null);
|
||||||
|
const [notificationPermission, setNotificationPermission] = useState(Notification.permission);
|
||||||
|
|
||||||
|
// Handle PWA installation
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
setNotificationPermission(Notification.permission);
|
||||||
|
|
||||||
|
// Handle Push Notification Subscription
|
||||||
|
if ('serviceWorker' in navigator && 'PushManager' in window) {
|
||||||
|
navigator.serviceWorker.ready.then(reg => {
|
||||||
|
reg.pushManager.getSubscription().then(sub => {
|
||||||
|
if (sub) {
|
||||||
|
setSubscription(sub);
|
||||||
|
setIsSubscribed(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setRegistration(reg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check if the app is running in standalone mode
|
||||||
|
if (window.matchMedia('(display-mode: standalone)').matches) {
|
||||||
|
setIsStandAlone(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBeforeInstallPrompt = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setDeferredPrompt(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAppInstalled = () => {
|
||||||
|
setIsPWAInstalled(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
||||||
|
window.addEventListener('appinstalled', handleAppInstalled);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
|
||||||
|
window.removeEventListener('appinstalled', handleAppInstalled);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const installPWA = async (e) => {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
if (deferredPrompt) {
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
const { outcome } = await deferredPrompt.userChoice;
|
||||||
|
if (outcome === 'accepted') {
|
||||||
|
setIsPWAInstalled(true);
|
||||||
|
}
|
||||||
|
setDeferredPrompt(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility function for converting base64 string to Uint8Array
|
||||||
|
const base64ToUint8Array = base64 => {
|
||||||
|
const padding = '='.repeat((4 - (base64.length % 4)) % 4);
|
||||||
|
const b64 = (base64 + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const rawData = window.atob(b64);
|
||||||
|
const outputArray = new Uint8Array(rawData.length);
|
||||||
|
for (let i = 0; i < rawData.length; ++i) {
|
||||||
|
outputArray[i] = rawData.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return outputArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscribeToNotifications = async (e) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!registration) {
|
||||||
|
console.error('Service worker registration not found.');
|
||||||
|
registration
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sub = await registration.pushManager.subscribe({
|
||||||
|
userVisibleOnly: true,
|
||||||
|
applicationServerKey: base64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY)
|
||||||
|
});
|
||||||
|
// Call your API to save subscription data on server
|
||||||
|
setSubscription(sub);
|
||||||
|
setIsSubscribed(true);
|
||||||
|
console.log('Web push subscribed!');
|
||||||
|
console.log(sub);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error subscribing to notifications:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribeFromNotifications = async (e) => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
e.preventDefault();
|
||||||
|
await subscription.unsubscribe();
|
||||||
|
// Call your API to delete or invalidate subscription data on server
|
||||||
|
setSubscription(null);
|
||||||
|
setIsSubscribed(false);
|
||||||
|
console.log('Web push unsubscribed!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error unsubscribing from notifications:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to request push notification permission
|
||||||
|
const requestNotificationPermission = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
setNotificationPermission(permission);
|
||||||
|
if (permission === "granted") {
|
||||||
|
// User granted permission
|
||||||
|
subscribeToNotifications(null); // Pass the required argument here
|
||||||
|
} else {
|
||||||
|
// User denied or dismissed permission
|
||||||
|
console.log("Push notifications permission denied.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to toggle push notifications
|
||||||
|
const togglePushNotifications = async (e) => {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
if (notificationPermission === "granted") {
|
||||||
|
// If already subscribed, unsubscribe
|
||||||
|
unsubscribeFromNotifications(null); // Pass null as the argument
|
||||||
|
} else if (notificationPermission === "default" || notificationPermission === "denied") {
|
||||||
|
// Request permission if not already granted
|
||||||
|
await requestNotificationPermission(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const sendTestNotification = async (e) => {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
if (!subscription) {
|
||||||
|
console.error('Web push not subscribed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch('/api/notification', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ subscription })
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<h1>PWA Manager</h1>
|
||||||
|
{!isStandAlone && !isPWAInstalled && (
|
||||||
|
<button
|
||||||
|
onClick={installPWA}
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white text-xs py-1 px-2 rounded-full focus:outline-none focus:shadow-outline transition duration-150 ease-in-out"
|
||||||
|
>
|
||||||
|
Install PWA
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{isPWAInstalled && <p>App is installed!</p>}
|
||||||
|
{isStandAlone && <p>PWA App</p>}
|
||||||
|
<button
|
||||||
|
onClick={subscribeToNotifications}
|
||||||
|
disabled={isSubscribed}
|
||||||
|
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-green-500 hover:bg-green-700 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Subscribe to Notifications
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={unsubscribeFromNotifications}
|
||||||
|
disabled={!isSubscribed}
|
||||||
|
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-red-500 hover:bg-red-700 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Unsubscribe from Notifications
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={sendTestNotification}
|
||||||
|
disabled={!isSubscribed}
|
||||||
|
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${!isSubscribed ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-yellow-500 hover:bg-yellow-600 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Send Test Notification
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{notificationPermission !== "granted" && (
|
||||||
|
<button
|
||||||
|
onClick={togglePushNotifications}
|
||||||
|
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${notificationPermission === "denied" ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="https://t.me/mwhitnessing_bot" className="inline-flex items-center ml-4" target="_blank">
|
||||||
|
<img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" />
|
||||||
|
<span className="align-middle">Телеграм</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PwaManager;
|
||||||
@@ -64,9 +64,14 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
|||||||
|
|
||||||
// Define the minimum and maximum times
|
// Define the minimum and maximum times
|
||||||
const minTime = new Date();
|
const minTime = new Date();
|
||||||
minTime.setHours(8, 0, 0, 0); // 8:00 AM
|
minTime.setHours(9, 0, 0, 0); // 8:00 AM
|
||||||
const maxTime = new Date();
|
const maxTime = new Date();
|
||||||
maxTime.setHours(20, 0, 0, 0); // 8:00 PM
|
maxTime.setHours(19, 30, 0, 0); // 8:00 PM
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeSlots(generateTimeSlots(minTime, maxTime, 90, availabilities));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const fetchItemFromDB = async () => {
|
const fetchItemFromDB = async () => {
|
||||||
const id = parseInt(router.query.id);
|
const id = parseInt(router.query.id);
|
||||||
@@ -330,12 +335,9 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
|||||||
|
|
||||||
const generateTimeSlots = (start, end, increment, items) => {
|
const generateTimeSlots = (start, end, increment, items) => {
|
||||||
const slots = [];
|
const slots = [];
|
||||||
// Initialize baseDate at the start of the day
|
let currentTime = start.getTime();
|
||||||
const baseDate = new Date(items?.startTime || day);
|
|
||||||
baseDate.setHours(start, 0, 0, 0);
|
|
||||||
let currentTime = baseDate.getTime();
|
|
||||||
|
|
||||||
const endTime = new Date(baseDate).setHours(end, 0, 0, 0);
|
const endTime = end.getTime();
|
||||||
|
|
||||||
while (currentTime < endTime) {
|
while (currentTime < endTime) {
|
||||||
let slotStart = new Date(currentTime);
|
let slotStart = new Date(currentTime);
|
||||||
@@ -343,11 +345,10 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
|||||||
|
|
||||||
const isChecked = items.some(item =>
|
const isChecked = items.some(item =>
|
||||||
item.startTime && item.endTime &&
|
item.startTime && item.endTime &&
|
||||||
(slotStart.getHours() * 60 + slotStart.getMinutes()) < (item.endTime.getHours() * 60 + item.endTime.getMinutes()) &&
|
(slotStart.getTime() < item.endTime.getTime()) &&
|
||||||
(slotEnd.getHours() * 60 + slotEnd.getMinutes()) > (item.startTime.getHours() * 60 + item.startTime.getMinutes())
|
(slotEnd.getTime() > item.startTime.getTime())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
slots.push({
|
slots.push({
|
||||||
startTime: slotStart,
|
startTime: slotStart,
|
||||||
endTime: slotEnd,
|
endTime: slotEnd,
|
||||||
@@ -358,17 +359,16 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Optional: Add isFirst, isLast, and isWithTransport properties
|
// Optional: Add isFirst, isLast, and isWithTransport properties
|
||||||
if (slots.length > 0) {
|
if (slots.length > 0 && items?.length > 0) {
|
||||||
slots[0].isFirst = true;
|
slots[0].isFirst = true;
|
||||||
slots[slots.length - 1].isLast = true;
|
slots[slots.length - 1].isLast = true;
|
||||||
slots[0].isWithTransport = items[0].isWithTransportIn;
|
slots[0].isWithTransport = items[0]?.isWithTransportIn;
|
||||||
slots[slots.length - 1].isWithTransport = items[items.length - 1].isWithTransportOut;
|
slots[slots.length - 1].isWithTransport = items[items.length - 1]?.isWithTransportOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => {
|
const TimeSlotCheckboxes = ({ slots, setSlots, items: [] }) => {
|
||||||
const [allDay, setAllDay] = useState(slots.every(slot => slot.isChecked));
|
const [allDay, setAllDay] = useState(slots.every(slot => slot.isChecked));
|
||||||
const handleAllDayChange = (e) => {
|
const handleAllDayChange = (e) => {
|
||||||
@@ -462,10 +462,6 @@ export default function AvailabilityForm({ publisherId, existingItems, inline, o
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTimeSlots(generateTimeSlots(9, 18, 90, availabilities));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<ToastContainer></ToastContainer>
|
<ToastContainer></ToastContainer>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Link from "next/link";
|
|||||||
import axiosInstance from '../../src/axiosSecure';
|
import axiosInstance from '../../src/axiosSecure';
|
||||||
//import { getDate } from "date-fns";
|
//import { getDate } from "date-fns";
|
||||||
|
|
||||||
|
import PwaManager from "../PwaManager";
|
||||||
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../src/helpers/const"
|
import { monthNamesBG, GetTimeFormat, GetDateFormat } from "../../src/helpers/const"
|
||||||
import PublisherSearchBox from './PublisherSearchBox';
|
import PublisherSearchBox from './PublisherSearchBox';
|
||||||
import AvailabilityList from "../availability/AvailabilityList";
|
import AvailabilityList from "../availability/AvailabilityList";
|
||||||
@@ -179,11 +180,11 @@ export default function PublisherForm({ item, me }) {
|
|||||||
onSubmit={handleSubmit}>
|
onSubmit={handleSubmit}>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="firstName">Име</label>
|
<label className="label" htmlFor="firstName">Име</label>
|
||||||
<input type="text" name="firstName" value={publisher.firstName} onChange={handleChange} className="textbox" placeholder="First Name" autoFocus />
|
<input type="text" id="firstName" name="firstName" value={publisher.firstName} onChange={handleChange} className="textbox" placeholder="First Name" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="lastName">Фамилия</label>
|
<label className="label" htmlFor="lastName">Фамилия</label>
|
||||||
<input type="text" name="lastName" value={publisher.lastName} onChange={handleChange} className="textbox" placeholder="Last Name" autoFocus />
|
<input type="text" id="lastName" name="lastName" value={publisher.lastName} onChange={handleChange} className="textbox" placeholder="Last Name" autoFocus />
|
||||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
|
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" ">
|
||||||
|
|
||||||
<div className="border border-blue-500 border-solid p-2">
|
<div className="border border-blue-500 border-solid p-2">
|
||||||
@@ -198,21 +199,21 @@ export default function PublisherForm({ item, me }) {
|
|||||||
{/* //desiredShiftsPerMonth */}
|
{/* //desiredShiftsPerMonth */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="desiredShiftsPerMonth">Желани смeни на месец</label>
|
<label className="label" htmlFor="desiredShiftsPerMonth">Желани смeни на месец</label>
|
||||||
<input type="number" name="desiredShiftsPerMonth" value={publisher.desiredShiftsPerMonth} onChange={handleChange} className="textbox" placeholder="desiredShiftsPerMonth" autoFocus />
|
<input type="number" id="desiredShiftsPerMonth" name="desiredShiftsPerMonth" value={publisher.desiredShiftsPerMonth} onChange={handleChange} className="textbox" placeholder="desiredShiftsPerMonth" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="email">Имейл</label>
|
<label className="label" htmlFor="email">Имейл</label>
|
||||||
<input type="text" name="email" value={publisher.email} onChange={handleChange} className="textbox" placeholder="Email" autoFocus />
|
<input type="text" id="email" name="email" value={publisher.email} onChange={handleChange} className="textbox" placeholder="Email" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="phone">Телефон</label>
|
<label className="label" htmlFor="phone">Телефон</label>
|
||||||
<input type="text" name="phone" value={publisher.phone} onChange={handleChange} className="textbox" placeholder="Phone" autoFocus />
|
<input type="text" id="phone" name="phone" value={publisher.phone} onChange={handleChange} className="textbox" placeholder="Phone" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="parentPublisher">
|
<label className="label" htmlFor="familyHeadId">
|
||||||
Семейство (избери главата на семейството)
|
Семейство (избери главата на семейството)
|
||||||
</label>
|
</label>
|
||||||
<PublisherSearchBox selectedId={publisher.familyHeadId} onChange={handleParentSelection} />
|
<PublisherSearchBox id="familyHeadId" selectedId={publisher.familyHeadId} onChange={handleParentSelection} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
@@ -238,23 +239,35 @@ export default function PublisherForm({ item, me }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="town">Град</label>
|
<label className="label" htmlFor="town">Град</label>
|
||||||
<input type="text" name="town" value={publisher.town} onChange={handleChange} className="textbox" placeholder="Град" autoFocus />
|
<input type="text" id="town" name="town" value={publisher.town} onChange={handleChange} className="textbox" placeholder="Град" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
|
{/* notifications */}
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input className="checkbox" type="checkbox" id="isSubscribedToCoverMe" name="isSubscribedToCoverMe" onChange={handleChange} checked={publisher.isSubscribedToCoverMe} autoComplete="off" />
|
<input className="checkbox" type="checkbox" id="isSubscribedToCoverMe" name="isSubscribedToCoverMe" onChange={handleChange} checked={publisher.isSubscribedToCoverMe} autoComplete="off" />
|
||||||
<label className="label" htmlFor="isSubscribedToCoverMe">Абониран за имейли за заместване</label>
|
<label className="label" htmlFor="isSubscribedToCoverMe">Абониран за имейли за заместване</label>
|
||||||
<input className="checkbox" type="checkbox" id="isSubscribedToReminders" name="isSubscribedToReminders" onChange={handleChange} checked={publisher.isSubscribedToReminders} autoComplete="off" />
|
<input className="checkbox" type="checkbox" id="isSubscribedToReminders" name="isSubscribedToReminders" onChange={handleChange} checked={publisher.isSubscribedToReminders} autoComplete="off" />
|
||||||
<label className="label" htmlFor="isSubscribedToReminders">Абониран за напомняния (имейл)</label>
|
<label className="label" htmlFor="isSubscribedToReminders">Абониран за напомняния (имейл)</label>
|
||||||
|
{/* prompt to install PWA */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* button to install PWA */}
|
||||||
|
{/* <div className="mb-4">
|
||||||
|
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="button" onClick={() => window.installPWA()}>
|
||||||
|
Инсталирай приложението
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
|
||||||
{/* ADMINISTRATORS ONLY */}
|
{/* ADMINISTRATORS ONLY */}
|
||||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" " className="">
|
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" " className="">
|
||||||
|
|
||||||
|
<PwaManager />
|
||||||
|
|
||||||
<div className="border border-blue-500 border-solid p-2">
|
<div className="border border-blue-500 border-solid p-2">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="type">Тип</label>
|
<label className="label" htmlFor="type">Тип</label>
|
||||||
<select name="type" value={publisher.type} onChange={handleChange} className="textbox" placeholder="Type" autoFocus >
|
<select id="type" name="type" value={publisher.type} onChange={handleChange} className="textbox" placeholder="Type" autoFocus >
|
||||||
<option value="Publisher">Вестител</option>
|
<option value="Publisher">Вестител</option>
|
||||||
<option value="Bethelite">Бетелит</option>
|
<option value="Bethelite">Бетелит</option>
|
||||||
<option value="RegularPioneer">Редовен Пионер</option>
|
<option value="RegularPioneer">Редовен Пионер</option>
|
||||||
@@ -265,11 +278,11 @@ export default function PublisherForm({ item, me }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="comments">Коментари</label>
|
<label className="label" htmlFor="comments">Коментари</label>
|
||||||
<input type="text" name="comments" value={publisher.comments} onChange={handleChange} className="textbox" placeholder="Коментари" autoFocus />
|
<input type="text" id="comments" name="comments" value={publisher.comments} onChange={handleChange} className="textbox" placeholder="Коментари" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="age">Възраст</label>
|
<label className="label" htmlFor="age">Възраст</label>
|
||||||
<input type="number" name="age" value={publisher.age} onChange={handleChange} className="textbox" placeholder="Age" autoFocus />
|
<input type="number" id="age" name="age" value={publisher.age} onChange={handleChange} className="textbox" placeholder="Age" autoFocus />
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
@@ -292,10 +305,7 @@ export default function PublisherForm({ item, me }) {
|
|||||||
{/* Add other roles as needed */}
|
{/* Add other roles as needed */}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<a href="https://t.me/mwHitnessing_bot" className="inline-flex items-center ml-4" target="_blank">
|
|
||||||
<img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" />
|
|
||||||
<span className="align-middle">Телеграм</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
{/* ---------------------------- Actions --------------------------------- */}
|
{/* ---------------------------- Actions --------------------------------- */}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import axiosInstance from '../../src/axiosSecure';
|
|||||||
import common from '../../src/helpers/common';
|
import common from '../../src/helpers/common';
|
||||||
//import { is } from 'date-fns/locale';
|
//import { is } from 'date-fns/locale';
|
||||||
|
|
||||||
function PublisherSearchBox({ selectedId, onChange, isFocused, filterDate, showSearch = true, showList = false, showAllAuto = false, infoText = " Семеен глава" }) {
|
function PublisherSearchBox({ id, selectedId, onChange, isFocused, filterDate, showSearch = true, showList = false, showAllAuto = false, infoText = " Семеен глава" }) {
|
||||||
const [selectedItem, setSelectedItem] = useState(null);
|
const [selectedItem, setSelectedItem] = useState(null);
|
||||||
|
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
@@ -95,6 +95,7 @@ function PublisherSearchBox({ selectedId, onChange, isFocused, filterDate, showS
|
|||||||
{showSearch ? (
|
{showSearch ? (
|
||||||
<>
|
<>
|
||||||
<input ref={inputRef}
|
<input ref={inputRef}
|
||||||
|
{...(id ? { id } : {})}
|
||||||
type="text"
|
type="text"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
|
|
||||||
module.exports = {
|
const withPWA = require('next-pwa')({
|
||||||
|
dest: 'public'
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = withPWA({
|
||||||
typescript: {
|
typescript: {
|
||||||
// !! WARN !!
|
// !! WARN !!
|
||||||
// Dangerously allow production builds to successfully complete even if
|
// Dangerously allow production builds to successfully complete even if
|
||||||
@@ -42,4 +46,4 @@ module.exports = {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
3712
package-lock.json
generated
3712
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -72,6 +72,7 @@
|
|||||||
"next": "^14.1.0",
|
"next": "^14.1.0",
|
||||||
"next-auth": "^4.24.6",
|
"next-auth": "^4.24.6",
|
||||||
"next-connect": "^1.0.0",
|
"next-connect": "^1.0.0",
|
||||||
|
"next-pwa": "^5.6.0",
|
||||||
"node-excel-export": "^1.4.4",
|
"node-excel-export": "^1.4.4",
|
||||||
"node-telegram-bot-api": "^0.64.0",
|
"node-telegram-bot-api": "^0.64.0",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
"tw-elements": "^1.1.0",
|
"tw-elements": "^1.1.0",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
|
"web-push": "^3.6.7",
|
||||||
"webpack-bundle-analyzer": "^4.10.1",
|
"webpack-bundle-analyzer": "^4.10.1",
|
||||||
"winston": "^3.11.0",
|
"winston": "^3.11.0",
|
||||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz",
|
||||||
@@ -110,4 +112,4 @@
|
|||||||
"depcheck": "^1.4.7",
|
"depcheck": "^1.4.7",
|
||||||
"prisma": "^5.11.0"
|
"prisma": "^5.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SessionProvider } from "next-auth/react"
|
import { SessionProvider } from "next-auth/react"
|
||||||
|
import type { Metadata } from "next"
|
||||||
import "../styles/styles.css"
|
import "../styles/styles.css"
|
||||||
import "../styles/global.css"
|
import "../styles/global.css"
|
||||||
import "tailwindcss/tailwind.css"
|
import "tailwindcss/tailwind.css"
|
||||||
@@ -13,6 +14,14 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
|
|||||||
// Use of the <SessionProvider> is mandatory to allow components that call
|
// Use of the <SessionProvider> is mandatory to allow components that call
|
||||||
// `useSession()` anywhere in your application to access the `session` object.
|
// `useSession()` anywhere in your application to access the `session` object.
|
||||||
|
|
||||||
|
// export const metadata: Metadata = {
|
||||||
|
// title: "Специално Свидетелстване София",
|
||||||
|
// description: "Специално Свидетелстване София",
|
||||||
|
// viewport: "width=device-width, initial-scale=1",
|
||||||
|
// appleWebApp: true,
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
export default function App({
|
export default function App({
|
||||||
Component,
|
Component,
|
||||||
pageProps: { session, ...pageProps },
|
pageProps: { session, ...pageProps },
|
||||||
@@ -26,15 +35,38 @@ export default function App({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
// PUSH NOTIFICATIONS
|
||||||
|
useEffect(() => {
|
||||||
|
// Function to ask for Notification permission
|
||||||
|
const askForNotificationPermission = async () => {
|
||||||
|
// Check if the browser supports service workers and push notifications
|
||||||
|
if ('serviceWorker' in navigator && 'PushManager' in window) {
|
||||||
|
try {
|
||||||
|
// Wait for service worker registration
|
||||||
|
const registration = await navigator.serviceWorker.ready;
|
||||||
|
// Ask for notification permission
|
||||||
|
const permission = await Notification.requestPermission();
|
||||||
|
if (permission === 'granted') {
|
||||||
|
console.log('Notification permission granted.');
|
||||||
|
// TODO: Subscribe the user to push notifications here
|
||||||
|
} else {
|
||||||
|
console.log('Notification permission not granted.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Service Worker or Push notifications not supported in this browser.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the function to ask for permission on first load
|
||||||
|
askForNotificationPermission();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
|
||||||
{/* Other tags */}
|
|
||||||
<link
|
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
</Head>
|
|
||||||
<SessionProvider session={session} >
|
<SessionProvider session={session} >
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
|||||||
29
pages/_document.tsx
Normal file
29
pages/_document.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||||
|
|
||||||
|
class MyDocument extends Document {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
<link
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content="#fff" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="Your PWA Name" />
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="/old-192x192.png"></link>
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDocument;
|
||||||
@@ -86,7 +86,7 @@ export default async function handler(req, res) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!assignment) {
|
if (!assignment) {
|
||||||
const messagePageUrl = `/message?message=${encodeURIComponent('Някой друг вече е отговорил на рази заявка за заместване')}&type=info&caption=${encodeURIComponent('Заявката е вече обработена')}`;
|
const messagePageUrl = `/message?message=${encodeURIComponent('Благодаря за желанието, но някой е отговорил на тази заявка за заместване и тя вече е неактивна')}&type=info&caption=${encodeURIComponent('Някой вече те изпревари. Заявката е вече обработена')}`;
|
||||||
res.redirect(messagePageUrl);
|
res.redirect(messagePageUrl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
46
pages/api/system.ts
Normal file
46
pages/api/system.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
|
import { join, resolve } from "node:path";
|
||||||
|
|
||||||
|
let findGitRoot = (path = __dirname): string | false => {
|
||||||
|
if (existsSync(join(path, ".git/HEAD"))) {
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
let parent = resolve(path, "..");
|
||||||
|
if (path === parent) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return findGitRoot(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let getGitVersion = async () => {
|
||||||
|
let root = findGitRoot();
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
throw new Error("Cannot call getGitVersion from non git project.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let rev = readFileSync(join(root, ".git/HEAD")).toString().trim();
|
||||||
|
|
||||||
|
if (rev.indexOf(":") === -1) {
|
||||||
|
return rev;
|
||||||
|
} else {
|
||||||
|
return readFileSync(join(root, ".git", rev.substring(5)))
|
||||||
|
.toString()
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
const token = await getToken({ req: req });
|
||||||
|
if (!token) {
|
||||||
|
// If no token or invalid token, return unauthorized status
|
||||||
|
return res.status(401).json({ message: "Unauthorized to call this API endpoint" });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If token is valid, log the user
|
||||||
|
//console.log("JWT | User: " + token.email);
|
||||||
|
}
|
||||||
|
res.status(200).json({ v: getGitVersion() })
|
||||||
|
}
|
||||||
26
public/manifest.json
Normal file
26
public/manifest.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#e36600",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"purpose": "maskable",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"src": "favicon.png",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"purpose": "any",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"src": "favicon.png",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orientation": "any",
|
||||||
|
"display": "standalone",
|
||||||
|
"dir": "auto",
|
||||||
|
"lang": "en-US",
|
||||||
|
"name": "Специално Свидетелстване София",
|
||||||
|
"short_name": "ССС",
|
||||||
|
"start_url": "/",
|
||||||
|
"scope": "/cart"
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
20
server.js
20
server.js
@@ -18,7 +18,7 @@ import('get-port').then(module => {
|
|||||||
|
|
||||||
process.env.TZ = 'Europe/Sofia';
|
process.env.TZ = 'Europe/Sofia';
|
||||||
// Global variable to store the base URL
|
// Global variable to store the base URL
|
||||||
let baseUrlGlobal;
|
// let baseUrlGlobal;
|
||||||
|
|
||||||
console.log("initial process.env.APP_ENV = ", process.env.APP_ENV);
|
console.log("initial process.env.APP_ENV = ", process.env.APP_ENV);
|
||||||
console.log("initial process.env.NODE_ENV = ", process.env.NODE_ENV); //NODE_ENV can be passed as docker param
|
console.log("initial process.env.NODE_ENV = ", process.env.NODE_ENV); //NODE_ENV can be passed as docker param
|
||||||
@@ -93,14 +93,14 @@ nextApp
|
|||||||
server.use((req, res, next) => {
|
server.use((req, res, next) => {
|
||||||
req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers.host;
|
req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers.host;
|
||||||
// ---------------
|
// ---------------
|
||||||
if (!baseUrlGlobal) {
|
// if (!baseUrlGlobal) {
|
||||||
const protocol = req.headers['x-forwarded-proto'] || 'http';
|
// const protocol = req.headers['x-forwarded-proto'] || 'http';
|
||||||
const host = req.headers.host;
|
// const host = req.headers.host;
|
||||||
const baseUrl = `${protocol}://${host}`;
|
// const baseUrl = `${protocol}://${host}`;
|
||||||
baseUrlGlobal = baseUrl;
|
// baseUrlGlobal = baseUrl;
|
||||||
fs.writeFileSync(path.join(__dirname, 'baseUrl.txt'), baseUrlGlobal, 'utf8');
|
// fs.writeFileSync(path.join(__dirname, 'baseUrl.txt'), baseUrlGlobal, 'utf8');
|
||||||
console.log("baseUrlGlobal set to: " + baseUrlGlobal);
|
// console.log("baseUrlGlobal set to: " + baseUrlGlobal);
|
||||||
}
|
// }
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
server.use("/favicon.ico", express.static("styles/favicon_io/favicon.ico"));
|
server.use("/favicon.ico", express.static("styles/favicon_io/favicon.ico"));
|
||||||
@@ -656,8 +656,6 @@ async function Stat() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Stat();
|
Stat();
|
||||||
|
|
||||||
exports.baseUrlGlobal = baseUrlGlobal;
|
|
||||||
exports.default = nextApp;
|
exports.default = nextApp;
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ const Handlebars = require('handlebars');
|
|||||||
// const OAuth2 = google.auth.OAuth2;
|
// const OAuth2 = google.auth.OAuth2;
|
||||||
|
|
||||||
const { Shift, Publisher, PrismaClient } = require("@prisma/client");
|
const { Shift, Publisher, PrismaClient } = require("@prisma/client");
|
||||||
|
const { env } = require("../../next.config");
|
||||||
|
|
||||||
|
// const TOKEN = process.env.TOKEN || "a7d7147a530235029d74a4c2f228e6ad";
|
||||||
|
// const SENDER_EMAIL = "sofia@mwitnessing.com";
|
||||||
|
// const sender = { name: "Специално Свидетелстване София", email: SENDER_EMAIL };
|
||||||
|
// const client = new MailtrapClient({ token: TOKEN });
|
||||||
|
|
||||||
const TOKEN = process.env.TOKEN || "a7d7147a530235029d74a4c2f228e6ad";
|
|
||||||
const SENDER_EMAIL = "sofia@mwitnessing.com";
|
|
||||||
const sender = { name: "Специално Свидетелстване София", email: SENDER_EMAIL };
|
|
||||||
const client = new MailtrapClient({ token: TOKEN });
|
|
||||||
let mailtrapTestClient = null;
|
let mailtrapTestClient = null;
|
||||||
// const mailtrapTestClient = new MailtrapClient({
|
// const mailtrapTestClient = new MailtrapClient({
|
||||||
// username: '8ec69527ff2104',//not working now
|
// username: '8ec69527ff2104',//not working now
|
||||||
@@ -25,11 +27,11 @@ let mailtrapTestClient = null;
|
|||||||
|
|
||||||
//test
|
//test
|
||||||
var transporter = nodemailer.createTransport({
|
var transporter = nodemailer.createTransport({
|
||||||
host: "sandbox.smtp.mailtrap.io",
|
host: process.env.MAILTRAP_HOST || "sandbox.smtp.mailtrap.io",
|
||||||
port: 2525,
|
port: 2525,
|
||||||
auth: {
|
auth: {
|
||||||
user: "8ec69527ff2104",
|
user: process.env.MAILTRAP_USER,
|
||||||
pass: "c7bc05f171c96c"
|
pass: process.env.MAILTRAP_PASS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// production
|
// production
|
||||||
@@ -196,7 +198,7 @@ exports.SendEmail_NewShifts = async function (publisher, shifts) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
//----------------------- OLD -----------------------------
|
//----------------------- OLD -----------------------------
|
||||||
|
|
||||||
// exports.SendEmail_NewShifts = async function (publisher, shifts) {
|
// exports.SendEmail_NewShifts = async function (publisher, shifts) {
|
||||||
// if (shifts.length == 0) return;
|
// if (shifts.length == 0) return;
|
||||||
@@ -245,51 +247,51 @@ exports.SendEmail_NewShifts = async function (publisher, shifts) {
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
|
|
||||||
// https://mailtrap.io/blog/sending-emails-with-nodemailer/
|
// // https://mailtrap.io/blog/sending-emails-with-nodemailer/
|
||||||
exports.SendEmail_Example = async function (to) {
|
// exports.SendEmail_Example = async function (to) {
|
||||||
const welcomeImage = fs.readFileSync(
|
// const welcomeImage = fs.readFileSync(
|
||||||
path.join(CON.contentPath, "welcome.png")
|
// path.join(CON.contentPath, "welcome.png")
|
||||||
);
|
// );
|
||||||
|
|
||||||
await client
|
// await client
|
||||||
.send({
|
// .send({
|
||||||
category: "test",
|
// category: "test",
|
||||||
custom_variables: {
|
// custom_variables: {
|
||||||
hello: "world",
|
// hello: "world",
|
||||||
year: 2022,
|
// year: 2022,
|
||||||
anticipated: true,
|
// anticipated: true,
|
||||||
},
|
// },
|
||||||
from: sender,
|
// from: sender,
|
||||||
to: [{ email: to }],
|
// to: [{ email: to }],
|
||||||
subject: "Hello from Mailtrap!",
|
// subject: "Hello from Mailtrap!",
|
||||||
html: `<!doctype html>
|
// html: `<!doctype html>
|
||||||
<html>
|
// <html>
|
||||||
<head>
|
// <head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
// <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
</head>
|
// </head>
|
||||||
<body style="font-family: sans-serif;">
|
// <body style="font-family: sans-serif;">
|
||||||
<div style="display: block; margin: auto; max-width: 600px;" class="main">
|
// <div style="display: block; margin: auto; max-width: 600px;" class="main">
|
||||||
<h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">Congrats for sending test email with Mailtrap!</h1>
|
// <h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">Congrats for sending test email with Mailtrap!</h1>
|
||||||
<p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
|
// <p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
|
||||||
<img alt="Inspect with Tabs" src="cid:welcome.png" style="width: 100%;">
|
// <img alt="Inspect with Tabs" src="cid:welcome.png" style="width: 100%;">
|
||||||
<p>Now send your email using our fake SMTP server and integration of your choice!</p>
|
// <p>Now send your email using our fake SMTP server and integration of your choice!</p>
|
||||||
<p>Good luck! Hope it works.</p>
|
// <p>Good luck! Hope it works.</p>
|
||||||
</div>
|
// </div>
|
||||||
<!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
|
// <!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
|
||||||
<style>
|
// <style>
|
||||||
.main { background-color: white; }
|
// .main { background-color: white; }
|
||||||
a:hover { border-left-width: 1em; min-height: 2em; }
|
// a:hover { border-left-width: 1em; min-height: 2em; }
|
||||||
</style>
|
// </style>
|
||||||
</body>
|
// </body>
|
||||||
</html>`,
|
// </html>`,
|
||||||
attachments: [
|
// attachments: [
|
||||||
{
|
// {
|
||||||
filename: "welcome.png",
|
// filename: "welcome.png",
|
||||||
content_id: "welcome.png",
|
// content_id: "welcome.png",
|
||||||
disposition: "inline",
|
// disposition: "inline",
|
||||||
content: welcomeImage,
|
// content: welcomeImage,
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
})
|
// })
|
||||||
.then(console.log, console.error, setResult);
|
// .then(console.log, console.error, setResult);
|
||||||
};
|
// };
|
||||||
|
|||||||
46
worker/index.js
Normal file
46
worker/index.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
console.log('Service Worker Loaded...')
|
||||||
|
|
||||||
|
self.addEventListener('push', function (event) {
|
||||||
|
console.log('Push message', event)
|
||||||
|
if (!(self.Notification && self.Notification.permission === 'granted')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = JSON.parse(event.data.text())
|
||||||
|
event.waitUntil(
|
||||||
|
registration.showNotification(data.title, {
|
||||||
|
body: data.message,
|
||||||
|
icon: '/icons/android-chrome-192x192.png'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
self.addEventListener('notificationclick', function (event) {
|
||||||
|
console.log('Notification click: tag', event.notification.tag)
|
||||||
|
event.notification.close()
|
||||||
|
event.waitUntil(
|
||||||
|
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) {
|
||||||
|
if (clientList.length > 0) {
|
||||||
|
let client = clientList[0]
|
||||||
|
for (let i = 0; i < clientList.length; i++) {
|
||||||
|
if (clientList[i].focused) {
|
||||||
|
client = clientList[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return client.focus()
|
||||||
|
}
|
||||||
|
return clients.openWindow('/')
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// self.addEventListener('pushsubscriptionchange', function(event) {
|
||||||
|
// event.waitUntil(
|
||||||
|
// Promise.all([
|
||||||
|
// Promise.resolve(event.oldSubscription ? deleteSubscription(event.oldSubscription) : true),
|
||||||
|
// Promise.resolve(event.newSubscription ? event.newSubscription : subscribePush(registration))
|
||||||
|
// .then(function(sub) { return saveSubscription(sub) })
|
||||||
|
// ])
|
||||||
|
// )
|
||||||
|
// })
|
||||||
Reference in New Issue
Block a user