diff --git a/.env b/.env index 614d1b9..c608057 100644 --- a/.env +++ b/.env @@ -14,9 +14,16 @@ NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003 # // owner: dobromir.popov@gmail.com | Специално Свидетелстване София # // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716 +# callback https://sofia.mwitnessing.com/api/auth/callback/google GOOGLE_ID=926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com GOOGLE_SECRET=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 + +# //https://sofia.mwitnessing.com/api/auth/callback/microsoft +# https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app +# owner: dobromirpopovgateway.onmicrosoft.com dobromir.popov@gateway.one (personal) Doby Popov P One +# callback https://sofia.mwhitnessing.com/api/auth/callback/azure-ad + AZURE_AD_CLIENT_ID=9e13bedd-1f9d-4c23-910e-a806aba308b6 # Application (client) ID AZURE_AD_CLIENT_SECRET=5ic8Q~GQmW-IUhuxzVGx3BE-i30GXDSpjfMHcb~z #client secret value AZURE_AD_TENANT_ID=f69d1a93-bfba-498a-9b60-e87c1bc26276 @@ -37,9 +44,6 @@ APPLE_PK=-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdw #APPLE_SECRET=eyJhbGciOiJFUzI1NiIsImtpZCI6IlRCM1YzNTVHNVkifQ.eyJhdWQiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiaXNzIjoiWEM1N1A5U1hESyIsImlhdCI6MTcxMjE3ODM0MiwiZXhwIjoxNzI3NzMwMzQzLCJzdWIiOiJjb20ubXdoaXRuZXNzaW5nLnNvZmlhIn0.XceA0qUQi0tXg0GM_LkJkpNU5AqXLiSB2JlEVbHCB_nINbQTWkjtoWxfqmvdOkIzwKtvdQ8FFb-crK9no9Bbbw # to generate - - - AUTH0_ID=Aa9f3HJowauUrmBVY4iQzQJ7fYsaZDbK AUTH0_SECRET=_c0O9GkyRXkoWMQW7jNExnl6UoXN6O4oD3mg7NZ_uHVeAinCUtcTAkeQmcKXpZ4x AUTH0_ISSUER=https://dev-wkzi658ckibr1amv.us.auth0.com diff --git a/_deploy/maintenance/default.conf b/_deploy/maintenance/default.conf new file mode 100644 index 0000000..3aa17e6 --- /dev/null +++ b/_deploy/maintenance/default.conf @@ -0,0 +1,11 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/_deploy/maintenance.html b/_deploy/maintenance/index.html similarity index 100% rename from _deploy/maintenance.html rename to _deploy/maintenance/index.html diff --git a/_deploy/maintenance.yml b/_deploy/maintenance/maintenance.yml similarity index 81% rename from _deploy/maintenance.yml rename to _deploy/maintenance/maintenance.yml index 1ec55e6..cd36069 100644 --- a/_deploy/maintenance.yml +++ b/_deploy/maintenance/maintenance.yml @@ -5,6 +5,7 @@ services: image: nginx:latest volumes: - /mnt/docker_volumes/maintenance:/usr/share/nginx/html + - /mnt/docker_volumes/maintenance/default.conf:/etc/nginx/conf.d/default.conf ports: - "3010:80" environment: diff --git a/components/PwaManager.tsx b/components/PwaManager.tsx index c7787c8..1e30d27 100644 --- a/components/PwaManager.tsx +++ b/components/PwaManager.tsx @@ -6,7 +6,7 @@ import e from 'express'; import ProtectedRoute from './protectedRoute'; import { UserRole } from '@prisma/client'; -function PwaManager() { +function PwaManager({ subs }) { //ToDo: for iOS, try to use apn? https://github.com/node-apn/node-apn/blob/master/doc/apn.markdown const isSupported = () => 'Notification' in window && @@ -21,7 +21,7 @@ function PwaManager() { const [subscription, setSubscription] = useState(null); const [registration, setRegistration] = useState(null); const [notificationPermission, setNotificationPermission] = useState(isSupported() && Notification.permission); - const [subs, setSubs] = useState("") + const [_subs, setSubs] = useState(subs) const { data: session } = useSession(); // let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN); @@ -39,14 +39,14 @@ function PwaManager() { // Handle Push Notification Subscription if ('serviceWorker' in navigator && 'PushManager' in window) { - navigator.serviceWorker.ready.then(reg => { - reg.pushManager.getSubscription().then(sub => { + navigator.serviceWorker.ready.then(swreg => { + swreg.pushManager.getSubscription().then(sub => { if (sub) { setSubscription(sub); setIsSubscribed(true); } }); - setRegistration(reg); + setRegistration(swreg); }); } @@ -341,7 +341,7 @@ function PwaManager() { onClick={deleteAllSubscriptions} className="text-xs py-1 px-2 rounded-full focus:outline-none bg-red-500 hover:bg-red-700 text-white" > - Спри известията на всички мои устройства {subs != "" ? `(${subs})` : ""} + Спри известията на всички мои устройства {_subs != "" ? `(${_subs})` : ""} {isAdmin && @@ -400,3 +400,13 @@ function PwaManager() { } } export default PwaManager; + +//get server side props - subs count +export const getServerSideProps = async (context) => { + //ToDo: get the number of subscriptions from the database + return { + props: { + subs: 0 + } + } +} diff --git a/components/PwaManagerNotifications.tsx b/components/PwaManagerNotifications.tsx new file mode 100644 index 0000000..fd5fc6f --- /dev/null +++ b/components/PwaManagerNotifications.tsx @@ -0,0 +1,133 @@ +import React, { useEffect, useState } from 'react'; +import { useSession } from "next-auth/react"; +import common from '../src/helpers/common'; // Ensure this path is correct +import { set } from 'date-fns'; + +function PwaManagerNotifications() { + const [isPermissionGranted, setIsPermissionGranted] = useState(false); + const [subscription, setSubscription] = useState(null); + const [registration, setRegistration] = useState(null); + const { data: session } = useSession(); + + // Check if all required APIs are supported + const isSupported = () => + 'Notification' in window && + 'serviceWorker' in navigator && + 'PushManager' in window; + + + useEffect(() => { + if (isSupported()) { + requestNotificationPermission(null); + } + + }, []); + + const requestNotificationPermission = async (e) => { + if (e) { + e.preventDefault(); + if (Notification.permission === 'denied') { + console.log('Notification permission denied.'); + alert('Известията са забранени. Моля, разрешете известията от браузъра.'); + } + } + setIsPermissionGranted(Notification.permission === 'granted'); + if (Notification.permission === 'default') { + const permission = await Notification.requestPermission(); + if (permission === 'granted') { + console.log('Notification permission granted.'); + getSubscription(); + } else { + console.log('Notification permission denied.'); + } + } + if (Notification.permission === 'granted') { + getSubscription(); + } + }; + const getSubscription = async () => { + // Handle Push Notification Subscription + if ('serviceWorker' in navigator && 'PushManager' in window) { + navigator.serviceWorker.ready.then(registration => { + registration.pushManager.getSubscription().then(existingSubscription => { + if (existingSubscription) { + console.log('Already subscribed.'); + setSubscription(existingSubscription); + } else if (Notification.permission === "granted") { + // Permission was already granted but no subscription exists, so subscribe now + subscribeToNotifications(registration); + } + }); + }); + } + } + + const subscribeToNotifications = async () => { + const registration = await navigator.serviceWorker.ready; + let vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY; // Ensure this is configured + if (!vapidPublicKey) { + // Fetch the public key from the server if not present in env variables + const response = await fetch('/api/notify', { method: 'GET' }); + const responseData = await response.json(); + vapidPublicKey = responseData.pk; + if (!vapidPublicKey) { + throw new Error("Failed to fetch VAPID public key from server."); + } + } + const convertedVapidKey = common.base64ToUint8Array(vapidPublicKey); + + try { + const subscription = await registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: convertedVapidKey + }); + console.log('Subscribed to push notifications:', subscription); + setSubscription(subscription); + sendSubscriptionToServer(subscription); + } catch (error) { + console.error('Failed to subscribe to push notifications:', error); + } + }; + + const sendSubscriptionToServer = async (sub) => { + if (session.user?.id != null) { + await fetch(`/api/notify`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ subscription: sub, id: session.user.id }) + }).then(async response => { + if (!response.ok) { + throw new Error('Failed to save subscription data on server.'); + } + else { + console.log('Subscription data saved on server.'); + const s = await response.json(); + setSubscription(sub); + console.log('Web push subscribed!'); + } + }); + } + }; + + + return ( +
+ +
+ ); +} + + +export default PwaManagerNotifications; diff --git a/components/sidebar.tsx b/components/sidebar.tsx index c34c5b9..73ff708 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -6,6 +6,7 @@ import sidemenu, { footerMenu } from './sidemenuData.js'; // Move sidemenu data import axiosInstance from "src/axiosSecure"; import common from "src/helpers/common"; import LanguageSwitcher from "./languageSwitcher"; +import PwaManagerNotifications from "./PwaManagerNotifications"; import { useTranslations } from 'next-intl'; import { getTranslations } from 'next-intl/server'; import ProtectedPage from "pages/examples/protected"; @@ -203,6 +204,8 @@ function UserDetails({ session }) {

{session.user.name}

{session.user.role}

+ + { e.preventDefault(); signOut(); }}> {/* {t('logout')} */} изход diff --git a/pages/cart/publishers/stats.tsx b/pages/cart/publishers/stats.tsx index 2e8b558..ea3812e 100644 --- a/pages/cart/publishers/stats.tsx +++ b/pages/cart/publishers/stats.tsx @@ -21,7 +21,7 @@ function ContactsPage({ publishers, allPublishers }) { return ( - +

Статистика

{publishers.length} участника с предпочитания за месеца (от {allPublishers.length} )
diff --git a/pages/cart/reports/coverMe.tsx b/pages/cart/reports/coverMe.tsx index e076646..b90a0d5 100644 --- a/pages/cart/reports/coverMe.tsx +++ b/pages/cart/reports/coverMe.tsx @@ -42,7 +42,7 @@ export default function EventLogList() { }, []); return ( - +
diff --git a/pages/cart/reports/experience.tsx b/pages/cart/reports/experience.tsx index 9e4df08..b93cfd6 100644 --- a/pages/cart/reports/experience.tsx +++ b/pages/cart/reports/experience.tsx @@ -9,7 +9,7 @@ import ProtectedRoute from '../../../components/protectedRoute'; function NewPage(loc: Location) { return ( - +
diff --git a/pages/cart/reports/list.tsx b/pages/cart/reports/list.tsx index fc6d47c..0e451ea 100644 --- a/pages/cart/reports/list.tsx +++ b/pages/cart/reports/list.tsx @@ -83,7 +83,7 @@ export default function Reports() { }, []); return ( - +
diff --git a/pages/cart/reports/report.tsx b/pages/cart/reports/report.tsx index 8c7bcff..fc4e57a 100644 --- a/pages/cart/reports/report.tsx +++ b/pages/cart/reports/report.tsx @@ -9,7 +9,7 @@ import ProtectedRoute from '../../../components/protectedRoute'; function NewPage(loc: Location) { return ( - +
diff --git a/src/axiosSecure.js b/src/axiosSecure.js index 75bcabc..5c14060 100644 --- a/src/axiosSecure.js +++ b/src/axiosSecure.js @@ -24,7 +24,6 @@ applyAuthTokenInterceptor(axiosInstance, { requestRefresh }); // Notice that th // 4. Logging in const login = async (params) => { const response = await axiosInstance.post('/api/auth/signin', params) - // save tokens to storage setAuthTokens({ accessToken: response.data.access_token, diff --git a/src/axiosServer.js b/src/axiosServer.js index 77faaeb..742a8ec 100644 --- a/src/axiosServer.js +++ b/src/axiosServer.js @@ -31,7 +31,8 @@ const axiosServer = async (context) => { } else { //redirect to next-auth login page - context.res.writeHead(302, { Location: encodeURIComponent('/api/auth/signin') }); + //context.res.writeHead(302, { Location: encodeURIComponent('/api/auth/signin') }); + context.res.end(); return { props: {} }; }