import { useHouseholdSelf } from "~/composables/use-households"; import { useGroupSelf } from "~/composables/use-groups"; import { useUserApi } from "~/composables/api"; export type AnnouncementMeta = { title: string | undefined; }; export type Announcement = { key: string; component: Component; date: Date | undefined; meta: AnnouncementMeta | undefined; }; const _announcementsUnsorted = import.meta.glob<{ default: Component; meta?: AnnouncementMeta }>( "~/components/Domain/Announcement/Announcements/*.vue", { eager: true }, ); const allAnnouncements: Announcement[] = Object.entries(_announcementsUnsorted) .sort(([a], [b]) => a.localeCompare(b)) .map(([path, mod]) => { const key = path.split("/").at(-1)!.replace(".vue", ""); const parsed = new Date(key.split("_", 1)[0]!); const date = isNaN(parsed.getTime()) ? undefined : parsed; return { key, component: mod.default, date, meta: mod.meta, }; }); const newAnnouncements = shallowRef([]); function isWelcomeAnnouncement(key: string) { return key === allAnnouncements.at(0)!.key; } export function useAnnouncements() { const auth = useMealieAuth(); const api = useUserApi(); const { household } = useHouseholdSelf(); const { group } = useGroupSelf(); const announcementsEnabled = computed( () => !!( auth.user.value?.showAnnouncements && household.value?.preferences?.showAnnouncements && group.value?.preferences?.showAnnouncements ), ); function updateUnreadAnnouncements(lastReadKey: string) { newAnnouncements.value = allAnnouncements.filter(a => a.key > lastReadKey); } async function setLastRead(key: string) { const user = auth.user.value!; if (!user.lastReadAnnouncement && isWelcomeAnnouncement(key)) { // The welcome announcement is a special case: it's shown to new users and // all other announcements are marked as read when they view it key = allAnnouncements.at(-1)!.key; updateUnreadAnnouncements(key); } else { // Only mark this specific announcement as read in the current session newAnnouncements.value = newAnnouncements.value.filter(a => a.key !== key); } if (user.lastReadAnnouncement && key <= user.lastReadAnnouncement) { // Don't update the last read announcement if it's older than the current one return; } user.lastReadAnnouncement = key; // update immediately so we don't have to wait for the db await api.users.updateOne( user.id, { ...user, lastReadAnnouncement: "2026-03-28_1_new-recipe-import-options", // TODO: switch back to key }, { suppressAlert: true }, ); } async function markAllAsRead() { setLastRead(allAnnouncements.at(-1)!.key); newAnnouncements.value = []; } function initUnreadAnnouncements() { const user = auth.user.value; // Only logged-in users can see announcements if (!user || !allAnnouncements.length) { newAnnouncements.value = []; return; } // If a user has never seen an announcement, show them only the welcome announcement if (!user.lastReadAnnouncement) { newAnnouncements.value = [allAnnouncements.at(0)!]; return; } // Return all announcements newer than the last read announcement updateUnreadAnnouncements(user.lastReadAnnouncement); } initUnreadAnnouncements(); // If the user changes, re-init let lastUserId = auth.user.value?.id; watch(auth.user, () => { if (auth.user.value?.id === lastUserId) { return; } lastUserId = auth.user.value?.id; initUnreadAnnouncements(); }); return { announcementsEnabled, newAnnouncements, allAnnouncements, setLastRead, markAllAsRead, }; }