@wasto/cms-components 1.5.16 → 1.5.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +11 -4
- package/dist/index.d.ts +11 -4
- package/dist/index.js +1766 -1269
- package/dist/index.mjs +1497 -999
- package/package.json +2 -2
- package/src/components/announcement-modal/AnnouncementModal.tsx +46 -7
- package/src/components/collection-calendar/CollectionCalendar.tsx +32 -4
- package/src/components/collection-calendar/CollectionDayModal.tsx +286 -0
- package/src/components/header/Header.tsx +52 -12
- package/src/components/locations-map/LocationsMapShell.tsx +1 -1
- package/src/components/push/PushNotificationNavigator.tsx +133 -0
- package/src/components/push/PushTokenRegistrar.tsx +5 -3
- package/src/contexts/AnnouncementsContext.tsx +72 -8
- package/src/contexts/LocalContext.tsx +9 -5
- package/src/contexts/WastoContext.tsx +4 -2
- package/src/hooks/useCityAnnouncements.ts +4 -4
- package/src/hooks/useCollectionCalendar.ts +2 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useEffect, useRef, type ReactNode } from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import { useAnnouncements } from "../../contexts/AnnouncementsContext";
|
|
4
|
+
import { useWasto } from "../../contexts/WastoContext";
|
|
5
|
+
import { useOptionalAppNavigation } from "../../navigation/NavigationContext";
|
|
6
|
+
|
|
7
|
+
type NotificationsModule = typeof import("expo-notifications");
|
|
8
|
+
|
|
9
|
+
let notificationsModule: NotificationsModule | null | undefined;
|
|
10
|
+
|
|
11
|
+
function loadNotifications(): NotificationsModule | null {
|
|
12
|
+
if (notificationsModule !== undefined) return notificationsModule;
|
|
13
|
+
try {
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
+
notificationsModule = require("expo-notifications") as NotificationsModule;
|
|
16
|
+
} catch {
|
|
17
|
+
notificationsModule = null;
|
|
18
|
+
}
|
|
19
|
+
return notificationsModule;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type PushData = {
|
|
23
|
+
announcementId?: unknown;
|
|
24
|
+
showAsModal?: unknown;
|
|
25
|
+
href?: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function asPushData(data: unknown): PushData {
|
|
29
|
+
if (!data || typeof data !== "object") return {};
|
|
30
|
+
return data as PushData;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractHref(data: PushData): string | null {
|
|
34
|
+
if (typeof data.href !== "string") return null;
|
|
35
|
+
const trimmed = data.href.trim();
|
|
36
|
+
return trimmed || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isShowAsModal(data: PushData): boolean {
|
|
40
|
+
return data.showAsModal === true || data.showAsModal === "true";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function extractAnnouncementId(data: PushData): string | null {
|
|
44
|
+
if (typeof data.announcementId !== "string") return null;
|
|
45
|
+
const trimmed = data.announcementId.trim();
|
|
46
|
+
return trimmed || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Handles Expo notification taps (warm + cold start):
|
|
51
|
+
* - showAsModal + announcementId → open AnnouncementModal
|
|
52
|
+
* - otherwise href → NavigationApi.push
|
|
53
|
+
* Mount inside AnnouncementsProvider (under NavigationProvider).
|
|
54
|
+
*/
|
|
55
|
+
export function PushNotificationNavigator(): ReactNode {
|
|
56
|
+
const { previewMode, reportError } = useWasto();
|
|
57
|
+
const navigation = useOptionalAppNavigation();
|
|
58
|
+
const { openFromPushPayload } = useAnnouncements();
|
|
59
|
+
const handledResponseId = useRef<string | null>(null);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (previewMode || Platform.OS === "web") return;
|
|
63
|
+
|
|
64
|
+
const Notifications = loadNotifications();
|
|
65
|
+
if (!Notifications) return;
|
|
66
|
+
|
|
67
|
+
const openFromResponse = (response: {
|
|
68
|
+
notification: {
|
|
69
|
+
request: {
|
|
70
|
+
identifier?: string;
|
|
71
|
+
content: {
|
|
72
|
+
title?: string | null;
|
|
73
|
+
body?: string | null;
|
|
74
|
+
data?: unknown;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}) => {
|
|
79
|
+
const responseId = response.notification.request.identifier ?? "";
|
|
80
|
+
if (responseId && handledResponseId.current === responseId) return;
|
|
81
|
+
|
|
82
|
+
const content = response.notification.request.content;
|
|
83
|
+
const data = asPushData(content.data);
|
|
84
|
+
const announcementId = extractAnnouncementId(data);
|
|
85
|
+
const href = extractHref(data);
|
|
86
|
+
|
|
87
|
+
const markHandled = () => {
|
|
88
|
+
if (responseId) handledResponseId.current = responseId;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
void (async () => {
|
|
92
|
+
try {
|
|
93
|
+
if (isShowAsModal(data) && announcementId) {
|
|
94
|
+
markHandled();
|
|
95
|
+
await openFromPushPayload({
|
|
96
|
+
announcementId,
|
|
97
|
+
title: content.title,
|
|
98
|
+
body: content.body,
|
|
99
|
+
redirectHref: href,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (href && navigation) {
|
|
105
|
+
markHandled();
|
|
106
|
+
navigation.push(href);
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
reportError(error, {
|
|
110
|
+
source: "push",
|
|
111
|
+
path: href ?? announcementId ?? undefined,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const subscription = Notifications.addNotificationResponseReceivedListener(openFromResponse);
|
|
118
|
+
|
|
119
|
+
void Notifications.getLastNotificationResponseAsync()
|
|
120
|
+
.then((response) => {
|
|
121
|
+
if (response) openFromResponse(response);
|
|
122
|
+
})
|
|
123
|
+
.catch((error) => {
|
|
124
|
+
reportError(error, { source: "push" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return () => {
|
|
128
|
+
subscription.remove();
|
|
129
|
+
};
|
|
130
|
+
}, [navigation, openFromPushPayload, previewMode, reportError]);
|
|
131
|
+
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
@@ -8,12 +8,12 @@ import { registerExpoPushToken } from "../../push/registerExpoPushToken";
|
|
|
8
8
|
/**
|
|
9
9
|
* Registers the Expo push token once a deviceId exists.
|
|
10
10
|
* Retries when the app comes back to the foreground (permission granted in OS settings).
|
|
11
|
-
* Associates the token with
|
|
11
|
+
* Associates the token with cmsAppId + selected collection place (→ city → city group).
|
|
12
12
|
* Renders nothing — mount inside LocalProvider.
|
|
13
13
|
*/
|
|
14
14
|
export function PushTokenRegistrar(): ReactNode {
|
|
15
15
|
const { deviceId, selectedCityId, selectedPlaceId } = useLocal();
|
|
16
|
-
const { previewMode, reportError } = useWasto();
|
|
16
|
+
const { previewMode, reportError, appId } = useWasto();
|
|
17
17
|
const client = useWastoClient();
|
|
18
18
|
const lastRegistered = useRef<string | null>(null);
|
|
19
19
|
const inFlight = useRef(false);
|
|
@@ -25,7 +25,7 @@ export function PushTokenRegistrar(): ReactNode {
|
|
|
25
25
|
|
|
26
26
|
const tryRegister = async () => {
|
|
27
27
|
if (cancelled || inFlight.current) return;
|
|
28
|
-
const registrationKey = `${deviceId}:${selectedPlaceId ?? ""}:${selectedCityId ?? ""}`;
|
|
28
|
+
const registrationKey = `${deviceId}:${appId ?? ""}:${selectedPlaceId ?? ""}:${selectedCityId ?? ""}`;
|
|
29
29
|
if (lastRegistered.current === registrationKey) return;
|
|
30
30
|
|
|
31
31
|
inFlight.current = true;
|
|
@@ -35,6 +35,7 @@ export function PushTokenRegistrar(): ReactNode {
|
|
|
35
35
|
registerPushToken: (id: string, pushToken: string) =>
|
|
36
36
|
client.public.devices.registerPushToken(id, {
|
|
37
37
|
token: pushToken,
|
|
38
|
+
cmsAppId: appId || null,
|
|
38
39
|
cityId: selectedCityId,
|
|
39
40
|
wasteCollectionPlaceId: selectedPlaceId,
|
|
40
41
|
}),
|
|
@@ -73,6 +74,7 @@ export function PushTokenRegistrar(): ReactNode {
|
|
|
73
74
|
sub.remove();
|
|
74
75
|
};
|
|
75
76
|
}, [
|
|
77
|
+
appId,
|
|
76
78
|
client.public.devices,
|
|
77
79
|
deviceId,
|
|
78
80
|
previewMode,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
8
|
useMemo,
|
|
9
|
+
useRef,
|
|
9
10
|
useState,
|
|
10
11
|
type ReactNode,
|
|
11
12
|
} from "react";
|
|
@@ -18,6 +19,15 @@ export type PublicAnnouncement = {
|
|
|
18
19
|
body: string;
|
|
19
20
|
imageUrl?: string | null;
|
|
20
21
|
sentAt?: string | null;
|
|
22
|
+
redirectHref?: string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type OpenFromPushPayload = {
|
|
26
|
+
announcementId: string;
|
|
27
|
+
title?: string | null;
|
|
28
|
+
body?: string | null;
|
|
29
|
+
redirectHref?: string | null;
|
|
30
|
+
imageUrl?: string | null;
|
|
21
31
|
};
|
|
22
32
|
|
|
23
33
|
type AnnouncementsContextType = {
|
|
@@ -28,6 +38,8 @@ type AnnouncementsContextType = {
|
|
|
28
38
|
markAsRead: (id: string) => Promise<void>;
|
|
29
39
|
/** Open the shared AnnouncementModal for a specific item (inbox). */
|
|
30
40
|
openAnnouncement: (item: PublicAnnouncement) => void;
|
|
41
|
+
/** Open modal from a push notification tap (skips if already dismissed). */
|
|
42
|
+
openFromPushPayload: (payload: OpenFromPushPayload) => Promise<boolean>;
|
|
31
43
|
openFromBell: () => Promise<void>;
|
|
32
44
|
forceVisible: boolean;
|
|
33
45
|
};
|
|
@@ -63,25 +75,33 @@ function resolveImageUrl(apiUrl: string, url?: string | null): string | null {
|
|
|
63
75
|
|
|
64
76
|
export type AnnouncementsProviderProps = PropsWithChildren & {
|
|
65
77
|
apiUrl: string;
|
|
78
|
+
appId: string;
|
|
66
79
|
};
|
|
67
80
|
|
|
68
|
-
export function AnnouncementsProvider({ children, apiUrl }: AnnouncementsProviderProps): ReactNode {
|
|
81
|
+
export function AnnouncementsProvider({ children, apiUrl, appId }: AnnouncementsProviderProps): ReactNode {
|
|
69
82
|
const { selectedCityId, hasPlaceSelected } = useLocal();
|
|
70
83
|
const client = useWastoClient();
|
|
71
84
|
const [announcement, setAnnouncement] = useState<PublicAnnouncement | null>(null);
|
|
72
85
|
const [hasUnread, setHasUnread] = useState(false);
|
|
73
86
|
const [forceVisible, setForceVisible] = useState(false);
|
|
74
87
|
const [queue, setQueue] = useState<PublicAnnouncement[]>([]);
|
|
88
|
+
/** When true, load() must not replace the currently shown announcement. */
|
|
89
|
+
const holdingPushModalRef = useRef(false);
|
|
90
|
+
const queueRef = useRef<PublicAnnouncement[]>([]);
|
|
91
|
+
queueRef.current = queue;
|
|
75
92
|
|
|
76
93
|
const load = useCallback(async (): Promise<PublicAnnouncement[]> => {
|
|
77
|
-
if (!selectedCityId || !hasPlaceSelected) {
|
|
78
|
-
|
|
94
|
+
if (!selectedCityId || !hasPlaceSelected || !appId) {
|
|
95
|
+
if (!holdingPushModalRef.current) {
|
|
96
|
+
setAnnouncement(null);
|
|
97
|
+
setForceVisible(false);
|
|
98
|
+
}
|
|
79
99
|
setHasUnread(false);
|
|
80
100
|
setQueue([]);
|
|
81
101
|
return [];
|
|
82
102
|
}
|
|
83
103
|
try {
|
|
84
|
-
const list = await client.public.announcements.listForCity(selectedCityId);
|
|
104
|
+
const list = await client.public.announcements.listForCity(selectedCityId, appId);
|
|
85
105
|
const dismissed = await getDismissedIds();
|
|
86
106
|
const pending = (list ?? [])
|
|
87
107
|
.filter((a) => a?.id && !dismissed.has(a.id))
|
|
@@ -91,18 +111,24 @@ export function AnnouncementsProvider({ children, apiUrl }: AnnouncementsProvide
|
|
|
91
111
|
}));
|
|
92
112
|
setQueue(pending);
|
|
93
113
|
setHasUnread(pending.length > 0);
|
|
94
|
-
|
|
114
|
+
if (!holdingPushModalRef.current) {
|
|
115
|
+
setAnnouncement(pending[0] ?? null);
|
|
116
|
+
}
|
|
95
117
|
return pending;
|
|
96
118
|
} catch {
|
|
97
|
-
|
|
119
|
+
if (!holdingPushModalRef.current) {
|
|
120
|
+
setAnnouncement(null);
|
|
121
|
+
setForceVisible(false);
|
|
122
|
+
}
|
|
98
123
|
setHasUnread(false);
|
|
99
124
|
setQueue([]);
|
|
100
125
|
return [];
|
|
101
126
|
}
|
|
102
|
-
}, [apiUrl, client.public.announcements, hasPlaceSelected, selectedCityId]);
|
|
127
|
+
}, [apiUrl, appId, client.public.announcements, hasPlaceSelected, selectedCityId]);
|
|
103
128
|
|
|
104
129
|
useEffect(() => {
|
|
105
130
|
void load().then((pending) => {
|
|
131
|
+
if (holdingPushModalRef.current) return;
|
|
106
132
|
if (pending.length > 0) setForceVisible(true);
|
|
107
133
|
});
|
|
108
134
|
}, [load]);
|
|
@@ -118,11 +144,13 @@ export function AnnouncementsProvider({ children, apiUrl }: AnnouncementsProvide
|
|
|
118
144
|
|
|
119
145
|
const dismiss = useCallback(async () => {
|
|
120
146
|
if (!announcement) {
|
|
147
|
+
holdingPushModalRef.current = false;
|
|
121
148
|
setForceVisible(false);
|
|
122
149
|
return;
|
|
123
150
|
}
|
|
124
151
|
const id = announcement.id;
|
|
125
152
|
await markDismissed(id);
|
|
153
|
+
holdingPushModalRef.current = false;
|
|
126
154
|
const nextQueue = queue.filter((a) => a.id !== id);
|
|
127
155
|
setQueue(nextQueue);
|
|
128
156
|
setHasUnread(nextQueue.length > 0);
|
|
@@ -135,6 +163,32 @@ export function AnnouncementsProvider({ children, apiUrl }: AnnouncementsProvide
|
|
|
135
163
|
setForceVisible(true);
|
|
136
164
|
}, []);
|
|
137
165
|
|
|
166
|
+
const openFromPushPayload = useCallback(async (payload: OpenFromPushPayload) => {
|
|
167
|
+
const id = payload.announcementId?.trim();
|
|
168
|
+
if (!id) return false;
|
|
169
|
+
const dismissed = await getDismissedIds();
|
|
170
|
+
if (dismissed.has(id)) return false;
|
|
171
|
+
|
|
172
|
+
const fromQueue = queueRef.current.find((a) => a.id === id);
|
|
173
|
+
const item: PublicAnnouncement = {
|
|
174
|
+
id,
|
|
175
|
+
title: (payload.title?.trim() || fromQueue?.title || "").trim() || "Annonce",
|
|
176
|
+
body: (payload.body?.trim() || fromQueue?.body || "").trim(),
|
|
177
|
+
imageUrl: payload.imageUrl
|
|
178
|
+
? resolveImageUrl(apiUrl, payload.imageUrl)
|
|
179
|
+
: fromQueue?.imageUrl ?? null,
|
|
180
|
+
sentAt: fromQueue?.sentAt ?? null,
|
|
181
|
+
redirectHref: payload.redirectHref?.trim() || fromQueue?.redirectHref || null,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
holdingPushModalRef.current = true;
|
|
185
|
+
setQueue((prev) => prev.filter((a) => a.id !== id));
|
|
186
|
+
setAnnouncement(item);
|
|
187
|
+
setForceVisible(true);
|
|
188
|
+
setHasUnread(true);
|
|
189
|
+
return true;
|
|
190
|
+
}, [apiUrl]);
|
|
191
|
+
|
|
138
192
|
const openFromBell = useCallback(async () => {
|
|
139
193
|
const pending = queue.length > 0 ? queue : await load();
|
|
140
194
|
if (pending.length === 0) return;
|
|
@@ -149,10 +203,20 @@ export function AnnouncementsProvider({ children, apiUrl }: AnnouncementsProvide
|
|
|
149
203
|
dismiss,
|
|
150
204
|
markAsRead,
|
|
151
205
|
openAnnouncement,
|
|
206
|
+
openFromPushPayload,
|
|
152
207
|
openFromBell,
|
|
153
208
|
forceVisible,
|
|
154
209
|
}),
|
|
155
|
-
[
|
|
210
|
+
[
|
|
211
|
+
announcement,
|
|
212
|
+
dismiss,
|
|
213
|
+
forceVisible,
|
|
214
|
+
hasUnread,
|
|
215
|
+
markAsRead,
|
|
216
|
+
openAnnouncement,
|
|
217
|
+
openFromBell,
|
|
218
|
+
openFromPushPayload,
|
|
219
|
+
],
|
|
156
220
|
);
|
|
157
221
|
|
|
158
222
|
return (
|
|
@@ -50,17 +50,20 @@ export type LocalContextType = {
|
|
|
50
50
|
|
|
51
51
|
export const LocalContext = createContext<LocalContextType | undefined>(undefined);
|
|
52
52
|
|
|
53
|
-
export type LocalContextProps = PropsWithChildren & {
|
|
54
|
-
previewMode?: boolean;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
53
|
function storageKey(base: string, previewMode: boolean) {
|
|
58
54
|
return previewMode ? `${PREVIEW_PREFIX}${base}` : base;
|
|
59
55
|
}
|
|
60
56
|
|
|
57
|
+
export type LocalContextProps = PropsWithChildren & {
|
|
58
|
+
previewMode?: boolean;
|
|
59
|
+
/** CmsApp id — stored on anonymous devices for push/announcement scoping */
|
|
60
|
+
appId?: string | null;
|
|
61
|
+
};
|
|
62
|
+
|
|
61
63
|
export function LocalProvider({
|
|
62
64
|
children,
|
|
63
65
|
previewMode = false,
|
|
66
|
+
appId = null,
|
|
64
67
|
}: LocalContextProps): ReactNode {
|
|
65
68
|
const keys = useMemo(
|
|
66
69
|
() => ({
|
|
@@ -103,6 +106,7 @@ export function LocalProvider({
|
|
|
103
106
|
osVersion: null,
|
|
104
107
|
appVersion: null,
|
|
105
108
|
deviceModel: null,
|
|
109
|
+
cmsAppId: appId || null,
|
|
106
110
|
});
|
|
107
111
|
if (!existingDeviceId) {
|
|
108
112
|
await AsyncStorage.setItem(keys.device, nextDeviceId);
|
|
@@ -125,7 +129,7 @@ export function LocalProvider({
|
|
|
125
129
|
setIsLoading(false);
|
|
126
130
|
return resolvedDeviceId;
|
|
127
131
|
},
|
|
128
|
-
[client.public.analytics, keys, previewMode],
|
|
132
|
+
[appId, client.public.analytics, keys, previewMode],
|
|
129
133
|
);
|
|
130
134
|
|
|
131
135
|
const clearPlaceSelection = useCallback(async () => {
|
|
@@ -16,6 +16,7 @@ import { AnnouncementsProvider } from "./AnnouncementsContext";
|
|
|
16
16
|
import SelectStreetModal from "../components/select-street-modal/SelectStreetModal";
|
|
17
17
|
import { AnnouncementModal } from "../components/announcement-modal/AnnouncementModal";
|
|
18
18
|
import { PushTokenRegistrar } from "../components/push/PushTokenRegistrar";
|
|
19
|
+
import { PushNotificationNavigator } from "../components/push/PushNotificationNavigator";
|
|
19
20
|
import { OptionalSafeAreaProvider } from "../components/safe-area/OptionalSafeAreaProvider";
|
|
20
21
|
import {
|
|
21
22
|
initWastoSentry,
|
|
@@ -104,10 +105,11 @@ export function WastoProvider(props: WastoProviderProps): ReactNode {
|
|
|
104
105
|
return (
|
|
105
106
|
<WastoContext value={value}>
|
|
106
107
|
<OptionalSafeAreaProvider>
|
|
107
|
-
<LocalProvider previewMode={previewMode}>
|
|
108
|
+
<LocalProvider previewMode={previewMode} appId={appId}>
|
|
108
109
|
<PushTokenRegistrar />
|
|
109
110
|
<ChallengesProvider>
|
|
110
|
-
<AnnouncementsProvider apiUrl={apiUrl}>
|
|
111
|
+
<AnnouncementsProvider apiUrl={apiUrl} appId={appId}>
|
|
112
|
+
<PushNotificationNavigator />
|
|
111
113
|
<ThemeProvider theme={props.theme}>
|
|
112
114
|
<AppDataProvider apiUrl={apiUrl}>
|
|
113
115
|
{props.children}
|
|
@@ -33,7 +33,7 @@ function resolveImageUrl(apiUrl: string, url?: string | null): string | null {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export function useCityAnnouncements() {
|
|
36
|
-
const { apiUrl } = useWasto();
|
|
36
|
+
const { apiUrl, appId } = useWasto();
|
|
37
37
|
const { selectedCityId, hasPlaceSelected } = useLocal();
|
|
38
38
|
const client = useWastoClient();
|
|
39
39
|
const { markAsRead: contextMarkAsRead, openAnnouncement } = useAnnouncements();
|
|
@@ -44,7 +44,7 @@ export function useCityAnnouncements() {
|
|
|
44
44
|
const [error, setError] = useState<string | null>(null);
|
|
45
45
|
|
|
46
46
|
const reload = useCallback(async () => {
|
|
47
|
-
if (!selectedCityId || !hasPlaceSelected) {
|
|
47
|
+
if (!selectedCityId || !hasPlaceSelected || !appId) {
|
|
48
48
|
setAll([]);
|
|
49
49
|
setLoading(false);
|
|
50
50
|
setError(null);
|
|
@@ -54,7 +54,7 @@ export function useCityAnnouncements() {
|
|
|
54
54
|
setError(null);
|
|
55
55
|
try {
|
|
56
56
|
const [list, dismissed] = await Promise.all([
|
|
57
|
-
client.public.announcements.listForCity(selectedCityId),
|
|
57
|
+
client.public.announcements.listForCity(selectedCityId, appId),
|
|
58
58
|
readDismissedIds(),
|
|
59
59
|
]);
|
|
60
60
|
const items: CityAnnouncementItem[] = (list ?? [])
|
|
@@ -77,7 +77,7 @@ export function useCityAnnouncements() {
|
|
|
77
77
|
} finally {
|
|
78
78
|
setLoading(false);
|
|
79
79
|
}
|
|
80
|
-
}, [apiUrl, client.public.announcements, hasPlaceSelected, selectedCityId]);
|
|
80
|
+
}, [apiUrl, appId, client.public.announcements, hasPlaceSelected, selectedCityId]);
|
|
81
81
|
|
|
82
82
|
useEffect(() => {
|
|
83
83
|
void reload();
|
|
@@ -94,6 +94,7 @@ export type UseCollectionCalendarResult = {
|
|
|
94
94
|
weeks: CalendarDayCell[][];
|
|
95
95
|
|
|
96
96
|
selectedPlaceId: string | null;
|
|
97
|
+
wasteTypes: WasteType[];
|
|
97
98
|
|
|
98
99
|
/** Collectes pour une date précise (utile pour les cases en débordement). */
|
|
99
100
|
getWasteTypesForDate: (year: number, month: number, day: number) => WasteTypeInfoForDay[];
|
|
@@ -413,6 +414,7 @@ export function useCollectionCalendar(options: UseCollectionCalendarOptions = {}
|
|
|
413
414
|
weeks,
|
|
414
415
|
|
|
415
416
|
selectedPlaceId: resolvedPlaceId,
|
|
417
|
+
wasteTypes,
|
|
416
418
|
|
|
417
419
|
getWasteTypesForDate,
|
|
418
420
|
getWasteTypesForDay,
|