Merge commit '5816838bbe9406f4c456257a8e9e071dbe33cc47'
This commit is contained in:
7
.env
7
.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
|
||||
|
11
_deploy/maintenance/default.conf
Normal file
11
_deploy/maintenance/default.conf
Normal file
@ -0,0 +1,11 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
@ -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:
|
@ -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})` : ""}
|
||||
</button>
|
||||
</div>
|
||||
{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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
133
components/PwaManagerNotifications.tsx
Normal file
133
components/PwaManagerNotifications.tsx
Normal file
@ -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 (
|
||||
<div>
|
||||
<button
|
||||
onClick={isPermissionGranted ? undefined : requestNotificationPermission}
|
||||
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out
|
||||
${isPermissionGranted ?
|
||||
'border border-blue-300 text-blue-300 bg-transparent hover:bg-blue-100'
|
||||
: 'bg-blue-400 text-white'
|
||||
}`}
|
||||
disabled={isPermissionGranted}
|
||||
>
|
||||
{!isSupported() ? "не поддъжа известия" : (isPermissionGranted && subscription ? 'Известията включени' : 'Включи известията')}
|
||||
</button>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default PwaManagerNotifications;
|
@ -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 }) {
|
||||
<div className="overflow-hidden">
|
||||
<p className="mx-1 mt-1 text-sm font-medium text-gray-800 dark:text-gray-200">{session.user.name}</p>
|
||||
<p className="mx-1 mt-1 text-sm font-medium text-gray-600 dark:text-gray-400">{session.user.role}</p>
|
||||
|
||||
<PwaManagerNotifications />
|
||||
<a href="/api/auth/signout" className={styles.button} onClick={(e) => { e.preventDefault(); signOut(); }}>
|
||||
{/* {t('logout')} */}
|
||||
изход
|
||||
|
@ -21,7 +21,7 @@ function ContactsPage({ publishers, allPublishers }) {
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER, UserRole.USER]}>
|
||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN, UserRole.POWERUSER]}>
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-xl font-semibold mb-4">Статистика </h1>
|
||||
<h5 className="text-lg font-semibold mb-4">{publishers.length} участника с предпочитания за месеца (от {allPublishers.length} )</h5>
|
||||
|
@ -42,7 +42,7 @@ export default function EventLogList() {
|
||||
}, []);
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN, UserRole.USER, UserRole.EXTERNAL]}>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}>
|
||||
|
||||
<div className="h-5/6 grid place-items-start px-4 pt-8">
|
||||
<div className="flex flex-col w-full px-4">
|
||||
|
@ -9,7 +9,7 @@ import ProtectedRoute from '../../../components/protectedRoute';
|
||||
function NewPage(loc: Location) {
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN, UserRole.USER, UserRole.EXTERNAL]}>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}>
|
||||
<div className="h-5/6 grid place-items-center">
|
||||
<ExperienceForm />
|
||||
</div></ProtectedRoute>
|
||||
|
@ -83,7 +83,7 @@ export default function Reports() {
|
||||
}, []);
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN, UserRole.USER, UserRole.EXTERNAL]}>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}>
|
||||
|
||||
<div className="h-5/6 grid place-items-start px-4 pt-8">
|
||||
<div className="flex flex-col w-full px-4">
|
||||
|
@ -9,7 +9,7 @@ import ProtectedRoute from '../../../components/protectedRoute';
|
||||
function NewPage(loc: Location) {
|
||||
return (
|
||||
<Layout>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN, UserRole.USER, UserRole.EXTERNAL]}>
|
||||
<ProtectedRoute allowedRoles={[UserRole.POWERUSER, UserRole.ADMIN]}>
|
||||
<div className="h-5/6 grid place-items-center">
|
||||
<ReportForm />
|
||||
</div></ProtectedRoute>
|
||||
|
@ -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,
|
||||
|
@ -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: {} };
|
||||
}
|
||||
|
Reference in New Issue
Block a user