feat: Implement push subscription event handling in AddToCartButton and ProductFilters components; enhance article and category unsubscribe functionality with optional identifiers
This commit is contained in:
@@ -15,6 +15,8 @@ import NotificationsActiveIcon from "@mui/icons-material/NotificationsActive";
|
|||||||
import CircularProgress from "@mui/material/CircularProgress";
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
import { withI18n } from "../i18n/withTranslation.js";
|
import { withI18n } from "../i18n/withTranslation.js";
|
||||||
import {
|
import {
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
emitPushSubscriptionsChanged,
|
||||||
isPushApiSupported,
|
isPushApiSupported,
|
||||||
fetchPushConfiguration,
|
fetchPushConfiguration,
|
||||||
registerPushServiceWorker,
|
registerPushServiceWorker,
|
||||||
@@ -109,9 +111,10 @@ class AddToCartButton extends Component {
|
|||||||
this.setState({ pushSubscribed: false, pushBusy: false });
|
this.setState({ pushSubscribed: false, pushBusy: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await articlePushUnsubscribe(subscription.endpoint);
|
const res = await articlePushUnsubscribe(subscription.endpoint, kArtikel);
|
||||||
if (parseSuccess(res)) {
|
if (parseSuccess(res)) {
|
||||||
this.setState({ pushSubscribed: false });
|
this.setState({ pushSubscribed: false });
|
||||||
|
emitPushSubscriptionsChanged();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
pushError:
|
pushError:
|
||||||
@@ -146,6 +149,7 @@ class AddToCartButton extends Component {
|
|||||||
const res = await articlePushSubscribe(kArtikel, subscription);
|
const res = await articlePushSubscribe(kArtikel, subscription);
|
||||||
if (parseSuccess(res)) {
|
if (parseSuccess(res)) {
|
||||||
this.setState({ pushSubscribed: true });
|
this.setState({ pushSubscribed: true });
|
||||||
|
emitPushSubscriptionsChanged();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
pushError:
|
pushError:
|
||||||
@@ -174,7 +178,14 @@ class AddToCartButton extends Component {
|
|||||||
if (this.state.quantity !== newQuantity)
|
if (this.state.quantity !== newQuantity)
|
||||||
this.setState({ quantity: newQuantity });
|
this.setState({ quantity: newQuantity });
|
||||||
};
|
};
|
||||||
|
this.onPushSubscriptionsChanged = () => {
|
||||||
|
this.refreshIncomingPushStatus();
|
||||||
|
};
|
||||||
window.addEventListener("cart", this.cart);
|
window.addEventListener("cart", this.cart);
|
||||||
|
window.addEventListener(
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
this.onPushSubscriptionsChanged
|
||||||
|
);
|
||||||
this.refreshIncomingPushStatus();
|
this.refreshIncomingPushStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +201,10 @@ class AddToCartButton extends Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener("cart", this.cart);
|
window.removeEventListener("cart", this.cart);
|
||||||
|
window.removeEventListener(
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
this.onPushSubscriptionsChanged
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIncrement = () => {
|
handleIncrement = () => {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { useParams, useSearchParams, useNavigate, useLocation } from 'react-rout
|
|||||||
import { setSessionSetting, removeSessionSetting, clearAllSessionSettings } from '../utils/sessionStorage.js';
|
import { setSessionSetting, removeSessionSetting, clearAllSessionSettings } from '../utils/sessionStorage.js';
|
||||||
import { withI18n } from '../i18n/withTranslation.js';
|
import { withI18n } from '../i18n/withTranslation.js';
|
||||||
import {
|
import {
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
emitPushSubscriptionsChanged,
|
||||||
isPushApiSupported,
|
isPushApiSupported,
|
||||||
fetchPushConfiguration,
|
fetchPushConfiguration,
|
||||||
registerPushServiceWorker,
|
registerPushServiceWorker,
|
||||||
@@ -68,14 +70,25 @@ class ProductFilters extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.onPushSubscriptionsChanged = () => {
|
||||||
|
this.refreshCategoryPushStatus();
|
||||||
|
};
|
||||||
this.adjustPaperHeight();
|
this.adjustPaperHeight();
|
||||||
window.addEventListener('resize', this.adjustPaperHeight);
|
window.addEventListener('resize', this.adjustPaperHeight);
|
||||||
|
window.addEventListener(
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
this.onPushSubscriptionsChanged
|
||||||
|
);
|
||||||
this._loadManufacturerImages();
|
this._loadManufacturerImages();
|
||||||
this.refreshCategoryPushStatus();
|
this.refreshCategoryPushStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener('resize', this.adjustPaperHeight);
|
window.removeEventListener('resize', this.adjustPaperHeight);
|
||||||
|
window.removeEventListener(
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
this.onPushSubscriptionsChanged
|
||||||
|
);
|
||||||
this._manufacturerImageUrls.forEach(url => URL.revokeObjectURL(url));
|
this._manufacturerImageUrls.forEach(url => URL.revokeObjectURL(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +217,10 @@ class ProductFilters extends Component {
|
|||||||
this.setState({ pushSubscribed: false, pushBusy: false });
|
this.setState({ pushSubscribed: false, pushBusy: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await categoryPushUnsubscribe(subscription.endpoint);
|
const res = await categoryPushUnsubscribe(subscription.endpoint, kKat);
|
||||||
if (parseSuccess(res)) {
|
if (parseSuccess(res)) {
|
||||||
this.setState({ pushSubscribed: false });
|
this.setState({ pushSubscribed: false });
|
||||||
|
emitPushSubscriptionsChanged();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
pushError:
|
pushError:
|
||||||
@@ -237,6 +251,7 @@ class ProductFilters extends Component {
|
|||||||
const res = await categoryPushSubscribe(kKat, subscription);
|
const res = await categoryPushSubscribe(kKat, subscription);
|
||||||
if (parseSuccess(res)) {
|
if (parseSuccess(res)) {
|
||||||
this.setState({ pushSubscribed: true });
|
this.setState({ pushSubscribed: true });
|
||||||
|
emitPushSubscriptionsChanged();
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
pushError:
|
pushError:
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
* Web Push helpers for article notifications (VAPID + backend API).
|
* Web Push helpers for article notifications (VAPID + backend API).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** Fired after any article/category push subscribe or unsubscribe so all UIs re-query status. */
|
||||||
|
export const PUSH_SUBSCRIPTIONS_CHANGED_EVENT = "growheads-push-subscriptions-changed";
|
||||||
|
|
||||||
|
export function emitPushSubscriptionsChanged() {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
window.dispatchEvent(new CustomEvent(PUSH_SUBSCRIPTIONS_CHANGED_EVENT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ARTICLE_PUSH_SW_PATH = "/api/check/push/service-worker";
|
export const ARTICLE_PUSH_SW_PATH = "/api/check/push/service-worker";
|
||||||
export const ARTICLE_PUSH_VAPID_URL = "/api/check/push/vapid-public-key";
|
export const ARTICLE_PUSH_VAPID_URL = "/api/check/push/vapid-public-key";
|
||||||
|
|
||||||
@@ -89,13 +98,28 @@ export async function articlePushSubscribe(kArtikel, subscription) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* POST /api/article/push/unsubscribe — body: `{ endpoint }` or `{ subscription: { endpoint } }` (pickup-style).
|
||||||
|
* Optional `kArtikel` (number or numeric string): if it parses to a finite id > 0, only that row is removed.
|
||||||
|
* Otherwise legacy: server clears all push rows for that endpoint (article + category + pickup).
|
||||||
|
*
|
||||||
* @param {string} endpoint
|
* @param {string} endpoint
|
||||||
|
* @param {number|string} [kArtikel]
|
||||||
*/
|
*/
|
||||||
export async function articlePushUnsubscribe(endpoint) {
|
export async function articlePushUnsubscribe(endpoint, kArtikel) {
|
||||||
|
const parsed =
|
||||||
|
kArtikel != null && kArtikel !== ""
|
||||||
|
? typeof kArtikel === "number"
|
||||||
|
? kArtikel
|
||||||
|
: parseInt(String(kArtikel), 10)
|
||||||
|
: NaN;
|
||||||
|
const body =
|
||||||
|
Number.isFinite(parsed) && parsed > 0
|
||||||
|
? { endpoint, kArtikel: parsed }
|
||||||
|
: { endpoint };
|
||||||
const res = await fetch("/api/article/push/unsubscribe", {
|
const res = await fetch("/api/article/push/unsubscribe", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ endpoint }),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
return { ...data, httpOk: res.ok };
|
return { ...data, httpOk: res.ok };
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
PUSH_SUBSCRIPTIONS_CHANGED_EVENT,
|
||||||
|
emitPushSubscriptionsChanged,
|
||||||
fetchPushConfiguration,
|
fetchPushConfiguration,
|
||||||
registerPushServiceWorker,
|
registerPushServiceWorker,
|
||||||
ensurePushSubscription,
|
ensurePushSubscription,
|
||||||
@@ -44,13 +46,26 @@ export async function categoryPushSubscribe(kKategorie, subscription) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* POST /api/category/push/unsubscribe — same contract as article: optional `kKategorie` for scoped delete.
|
||||||
|
*
|
||||||
* @param {string} endpoint
|
* @param {string} endpoint
|
||||||
|
* @param {number|string} [kKategorie]
|
||||||
*/
|
*/
|
||||||
export async function categoryPushUnsubscribe(endpoint) {
|
export async function categoryPushUnsubscribe(endpoint, kKategorie) {
|
||||||
|
const parsed =
|
||||||
|
kKategorie != null && kKategorie !== ""
|
||||||
|
? typeof kKategorie === "number"
|
||||||
|
? kKategorie
|
||||||
|
: parseInt(String(kKategorie), 10)
|
||||||
|
: NaN;
|
||||||
|
const body =
|
||||||
|
Number.isFinite(parsed) && parsed > 0
|
||||||
|
? { endpoint, kKategorie: parsed }
|
||||||
|
: { endpoint };
|
||||||
const res = await fetch("/api/category/push/unsubscribe", {
|
const res = await fetch("/api/category/push/unsubscribe", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ endpoint }),
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
const data = await res.json().catch(() => ({}));
|
const data = await res.json().catch(() => ({}));
|
||||||
return { ...data, httpOk: res.ok };
|
return { ...data, httpOk: res.ok };
|
||||||
|
|||||||
Reference in New Issue
Block a user