PWA manager is now visible to all users
This commit is contained in:
1
.env
1
.env
@ -10,6 +10,7 @@ NODE_ENV=development
|
|||||||
# mysql. ONLY THIS ENV is respected when generating/applying migrations in prisma
|
# mysql. ONLY THIS ENV is respected when generating/applying migrations in prisma
|
||||||
DATABASE=mysql://cart:cartpw@localhost:3306/cart
|
DATABASE=mysql://cart:cartpw@localhost:3306/cart
|
||||||
# DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev
|
# DATABASE=mysql://cart:cartpw@192.168.0.10:3306/cart_dev
|
||||||
|
NEXT_PUBLIC_PUBLIC_URL=https://localhost:3003
|
||||||
|
|
||||||
# // owner: dobromir.popov@gmail.com | Специално Свидетелстване София
|
# // owner: dobromir.popov@gmail.com | Специално Свидетелстване София
|
||||||
# // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716
|
# // https://console.cloud.google.com/apis/credentials/oauthclient/926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com?project=grand-forge-108716
|
||||||
|
@ -35,9 +35,11 @@ services:
|
|||||||
MYSQL_DATABASE: jwpwsofia_demo
|
MYSQL_DATABASE: jwpwsofia_demo
|
||||||
MYSQL_USER: jwpwsofia_demo
|
MYSQL_USER: jwpwsofia_demo
|
||||||
MYSQL_PASSWORD: dwxhns9p9vp248
|
MYSQL_PASSWORD: dwxhns9p9vp248
|
||||||
# networks:
|
adminer:
|
||||||
# - infrastructure_default
|
image: adminer
|
||||||
# - default
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 5002:8080
|
||||||
networks:
|
networks:
|
||||||
infrastructure_default:
|
infrastructure_default:
|
||||||
external: true
|
external: true
|
||||||
|
@ -3,6 +3,8 @@ import common from '../src/helpers/common'; // Ensure this path is correct
|
|||||||
//use session to get user role
|
//use session to get user role
|
||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
import e from 'express';
|
import e from 'express';
|
||||||
|
import ProtectedRoute from './protectedRoute';
|
||||||
|
import { UserRole } from '@prisma/client';
|
||||||
|
|
||||||
function PwaManager() {
|
function PwaManager() {
|
||||||
const [deferredPrompt, setDeferredPrompt] = useState(null);
|
const [deferredPrompt, setDeferredPrompt] = useState(null);
|
||||||
@ -12,8 +14,14 @@ function PwaManager() {
|
|||||||
const [subscription, setSubscription] = useState(null);
|
const [subscription, setSubscription] = useState(null);
|
||||||
const [registration, setRegistration] = useState(null);
|
const [registration, setRegistration] = useState(null);
|
||||||
const [notificationPermission, setNotificationPermission] = useState(Notification.permission);
|
const [notificationPermission, setNotificationPermission] = useState(Notification.permission);
|
||||||
|
const [subs, setSubs] = useState("")
|
||||||
|
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
// let isAdmin = ProtectedRoute.IsInRole(UserRole.ADMIN);
|
||||||
|
let isAdmin = false;
|
||||||
|
if (session) {
|
||||||
|
isAdmin = session.user.role === UserRole.ADMIN;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle PWA installation
|
// Handle PWA installation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -33,9 +41,13 @@ function PwaManager() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if the app is running in standalone mode
|
// Check if the app is running in standalone mode
|
||||||
|
// const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
|
||||||
|
// if (isStandalone) {
|
||||||
|
// console.log('Running in standalone mode');
|
||||||
|
// setIsPWAInstalled(true);
|
||||||
|
// }
|
||||||
|
|
||||||
if (window.matchMedia('(display-mode: standalone)').matches) {
|
if (window.matchMedia('(display-mode: standalone)').matches) {
|
||||||
setIsStandAlone(true);
|
setIsStandAlone(true);
|
||||||
}
|
}
|
||||||
@ -58,19 +70,28 @@ function PwaManager() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const installPWA = async (e) => {
|
const installPWA = async (e) => {
|
||||||
console.log('installing PWA');
|
console.log('Attempting to install PWA');
|
||||||
e.preventDefault();
|
e.preventDefault(); // Prevent default button action
|
||||||
if (deferredPrompt) {
|
if (deferredPrompt) {
|
||||||
|
console.log('Prompting install');
|
||||||
deferredPrompt.prompt();
|
deferredPrompt.prompt();
|
||||||
const { outcome } = await deferredPrompt.userChoice;
|
const { outcome } = await deferredPrompt.userChoice;
|
||||||
|
console.log('Installation outcome:', outcome);
|
||||||
if (outcome === 'accepted') {
|
if (outcome === 'accepted') {
|
||||||
|
console.log('User accepted the A2HS prompt');
|
||||||
setIsPWAInstalled(true);
|
setIsPWAInstalled(true);
|
||||||
|
} else {
|
||||||
|
console.log('User dismissed the A2HS prompt');
|
||||||
}
|
}
|
||||||
setDeferredPrompt(null);
|
setDeferredPrompt(null); // Clear the deferred prompt to manage its lifecycle
|
||||||
|
} else {
|
||||||
|
console.log('No deferred prompt available');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const subscribeToNotifications = async (e) => {
|
const subscribeToNotifications = async (e) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -90,7 +111,9 @@ function PwaManager() {
|
|||||||
if (!vapidPublicKey) {
|
if (!vapidPublicKey) {
|
||||||
// Fetch the public key from the server if not present in env variables
|
// Fetch the public key from the server if not present in env variables
|
||||||
const response = await fetch('/api/notify', { method: 'GET' });
|
const response = await fetch('/api/notify', { method: 'GET' });
|
||||||
vapidPublicKey = await response.text();
|
const responseData = await response.json();
|
||||||
|
vapidPublicKey = responseData.pk;
|
||||||
|
setSubs(responseData.subs);
|
||||||
if (!vapidPublicKey) {
|
if (!vapidPublicKey) {
|
||||||
throw new Error("Failed to fetch VAPID public key from server.");
|
throw new Error("Failed to fetch VAPID public key from server.");
|
||||||
}
|
}
|
||||||
@ -107,12 +130,14 @@ function PwaManager() {
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ subscription: sub, id: session.user.id })
|
body: JSON.stringify({ subscription: sub, id: session.user.id })
|
||||||
}).then(response => {
|
}).then(async response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to save subscription data on server.');
|
throw new Error('Failed to save subscription data on server.');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log('Subscription data saved on server.');
|
console.log('Subscription data saved on server.');
|
||||||
|
const s = await response.json();
|
||||||
|
setSubs(s.subs);
|
||||||
setSubscription(sub);
|
setSubscription(sub);
|
||||||
setIsSubscribed(true);
|
setIsSubscribed(true);
|
||||||
console.log('Web push subscribed!');
|
console.log('Web push subscribed!');
|
||||||
@ -144,12 +169,14 @@ function PwaManager() {
|
|||||||
//send the current subscription to be removed
|
//send the current subscription to be removed
|
||||||
body: JSON.stringify({ id: session.user.id, subscriptionId: subscription.endpoint })
|
body: JSON.stringify({ id: session.user.id, subscriptionId: subscription.endpoint })
|
||||||
}
|
}
|
||||||
).then(response => {
|
).then(async (response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to delete subscription data on server.');
|
throw new Error('Failed to delete subscription data on server.');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log('Subscription data deleted on server.');
|
console.log('Subscription data deleted on server.');
|
||||||
|
const s = await response.json();
|
||||||
|
setSubs(s.subs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -261,6 +288,7 @@ function PwaManager() {
|
|||||||
if (subscription) {
|
if (subscription) {
|
||||||
await subscription.unsubscribe();
|
await subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
setSubs("");
|
||||||
setSubscription(null);
|
setSubscription(null);
|
||||||
setIsSubscribed(false);
|
setIsSubscribed(false);
|
||||||
}
|
}
|
||||||
@ -270,30 +298,31 @@ function PwaManager() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<h1>PWA Manager</h1>
|
<h1>{isAdmin && " PWA (admin)"}</h1>
|
||||||
{!isStandAlone && !isPWAInstalled && (
|
{!isStandAlone && !isPWAInstalled && (
|
||||||
<button
|
<button
|
||||||
onClick={installPWA}
|
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"
|
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>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isPWAInstalled && <p>App is installed!</p>}
|
{isPWAInstalled && <p>Инсталирано!</p>}
|
||||||
{isStandAlone && <p>PWA App</p>}
|
{/* {isStandAlone && <p>PWA App</p>} */}
|
||||||
<button
|
<button
|
||||||
onClick={isSubscribed ? unsubscribeFromNotifications : subscribeToNotifications}
|
onClick={isSubscribed ? unsubscribeFromNotifications : subscribeToNotifications}
|
||||||
disabled={false} // Since the button itself acts as a toggle, the disabled attribute might not be needed
|
disabled={false} // Since the button itself acts as a toggle, the disabled attribute might not be needed
|
||||||
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${isSubscribed ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'}`} >
|
className={`text-xs py-1 px-2 rounded-full focus:outline-none transition duration-150 ease-in-out ${isSubscribed ? 'bg-red-500 hover:bg-red-700 text-white' : 'bg-green-500 hover:bg-green-700 text-white'}`} >
|
||||||
{isSubscribed ? 'Unsubscribe from Notifications' : 'Subscribe to Notifications'}
|
{isSubscribed ? 'Спри известията' : 'Показвай известия'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={deleteAllSubscriptions}
|
onClick={deleteAllSubscriptions}
|
||||||
className="text-xs py-1 px-2 rounded-full focus:outline-none bg-red-500 hover:bg-red-700 text-white"
|
className="text-xs py-1 px-2 rounded-full focus:outline-none bg-red-500 hover:bg-red-700 text-white"
|
||||||
>
|
>
|
||||||
Delete All Subscriptions
|
Спри известията на всички мои устройства {subs != "" ? `(${subs})` : ""}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{isAdmin &&
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
onClick={sendTestNotification}
|
onClick={sendTestNotification}
|
||||||
@ -319,7 +348,8 @@ function PwaManager() {
|
|||||||
>
|
>
|
||||||
Broadcast CoverMe
|
Broadcast CoverMe
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{notificationPermission !== "granted" && (
|
{notificationPermission !== "granted" && (
|
||||||
<button
|
<button
|
||||||
onClick={togglePushNotifications}
|
onClick={togglePushNotifications}
|
||||||
@ -329,18 +359,20 @@ function PwaManager() {
|
|||||||
{notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'}
|
{notificationPermission === "denied" ? 'Notifications Denied!' : 'Enable Notifications'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
{isAdmin && <div>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://t.me/mwhitnessing_bot" className="inline-flex items-center ml-4" target="_blank">
|
<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" />
|
<img src="/content/icons/telegram-svgrepo-com.svg" alt="Телеграм" width="32" height="32" className="align-middle" />
|
||||||
<span className="align-middle">Телеграм</span>
|
<span className="align-middle">Телеграм</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
<div>
|
<a href="/api/auth/apple-signin" className="inline-flex items-center ml-4 bg-gray-100 button" target="_blank">
|
||||||
<a href="/api/auth/apple-signin" className="inline-flex items-center ml-4" target="_blank">
|
|
||||||
<span className="align-middle">Apple sign-in</span>
|
<span className="align-middle">Apple sign-in</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -250,7 +250,11 @@ export default function PublisherForm({ item, me }) {
|
|||||||
{/* notifications */}
|
{/* notifications */}
|
||||||
<div className="mb-6 p-4 border border-gray-300 rounded-lg">
|
<div className="mb-6 p-4 border border-gray-300 rounded-lg">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend className="text-lg font-medium mb-2">Известия по имейл</legend>
|
<legend className="text-lg font-medium mb-2">Известия</legend>
|
||||||
|
|
||||||
|
{/* Email notifications group */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-md font-semibold mb-2">Известия по имейл</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input
|
<input
|
||||||
@ -293,6 +297,13 @@ export default function PublisherForm({ item, me }) {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* In-App notifications group */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-md font-semibold mb-2">Известия в приложението</h3>
|
||||||
|
<PwaManager />
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -307,7 +318,6 @@ export default function PublisherForm({ item, me }) {
|
|||||||
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" " className="">
|
<ProtectedRoute allowedRoles={[UserRole.ADMIN]} deniedMessage=" " className="">
|
||||||
<div className="border border-blue-500 border-solid p-2">
|
<div className="border border-blue-500 border-solid p-2">
|
||||||
{/* prompt to install PWA */}
|
{/* prompt to install PWA */}
|
||||||
<PwaManager />
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="label" htmlFor="type">Тип</label>
|
<label className="label" htmlFor="type">Тип</label>
|
||||||
<select id="type" 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 >
|
||||||
@ -366,7 +376,7 @@ export default function PublisherForm({ item, me }) {
|
|||||||
|
|
||||||
{/* save */}
|
{/* save */}
|
||||||
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="submit">
|
<button className="button bg-blue-500 hover:bg-blue-700 focus:outline-none focus:shadow-outline" type="submit">
|
||||||
{router.query?.id ? "Update" : "Create"}
|
{router.query?.id ? "Запази" : "Създай"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -25,9 +25,20 @@ const Notification = async (req, res) => {
|
|||||||
if (req.method == 'GET') {
|
if (req.method == 'GET') {
|
||||||
res.statusCode = 200
|
res.statusCode = 200
|
||||||
res.setHeader('Allow', 'POST')
|
res.setHeader('Allow', 'POST')
|
||||||
|
let subs = 0
|
||||||
|
if (req.query && req.query.id) {
|
||||||
|
const prisma = common.getPrismaClient();
|
||||||
|
const publisher = await prisma.publisher.findUnique({
|
||||||
|
where: { id: req.query.id },
|
||||||
|
select: { pushSubscription: true }
|
||||||
|
});
|
||||||
|
subs = Array.isArray(publisher.pushSubscription) ? publisher.pushSubscription.length : (publisher.pushSubscription ? 1 : 0);
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
// send the public key in the response headers
|
// send the public key in the response headers
|
||||||
//res.setHeader('Content-Type', 'text/plain')
|
//res.setHeader('Content-Type', 'text/plain')
|
||||||
res.end(process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY)
|
res.send({ pk: process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY, subs })
|
||||||
res.end()
|
res.end()
|
||||||
}
|
}
|
||||||
if (req.method == 'PUT') {
|
if (req.method == 'PUT') {
|
||||||
|
@ -64,13 +64,23 @@ export const getServerSideProps = async (context) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!item) {
|
if (!item) {
|
||||||
const user = context.req.session.user;
|
const user = context.req.session?.user;
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
// redirect to '/auth/signin'. assure it is not relative path
|
||||||
|
redirect: {
|
||||||
|
destination: process.env.NEXT_PUBLIC_PUBLIC_URL + "/auth/signin",
|
||||||
|
permanent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const message = encodeURIComponent(`Този имейл (${user?.email}) не е регистриран. Моля свържете се с администратора.`);
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
destination: '/message?message=Този имейл (' + user.email + ') не е регистриран. Моля свържете се с администратора.',
|
destination: `/message?message=${message}`,
|
||||||
permanent: false,
|
permanent: false,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
// item.allShifts = item.assignments.map((a: Assignment[]) => a.shift);
|
// item.allShifts = item.assignments.map((a: Assignment[]) => a.shift);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user