From d2b0681dbb48a1e61e9cb21456b039a41ed39aae Mon Sep 17 00:00:00 2001 From: Michael Genson <71845777+michael-genson@users.noreply.github.com> Date: Sat, 11 Apr 2026 08:26:14 -0500 Subject: [PATCH] feat: Announcements (#7431) Co-authored-by: Kuchenpirat <24235032+Kuchenpirat@users.noreply.github.com> --- .../Announcement/AnnouncementDialog.vue | 139 ++++++++++++++++++ .../Announcements/2026-03-27_1_welcome.vue | 62 ++++++++ .../Announcements/announcements.test.ts | 37 +++++ .../Domain/Group/GroupPreferencesEditor.vue | 41 +++++- .../Household/HouseholdPreferencesEditor.vue | 14 ++ .../Layout/LayoutParts/AppSidebar.vue | 24 +++ frontend/app/components/global/BaseDialog.vue | 21 ++- frontend/app/composables/use-announcements.ts | 135 +++++++++++++++++ frontend/app/composables/use-groups.ts | 2 + frontend/app/lang/messages/en-US.json | 8 + frontend/app/lib/api/admin/admin-groups.ts | 5 - frontend/app/lib/api/base/base-clients.ts | 29 ++-- frontend/app/lib/api/types/group.ts | 3 + frontend/app/lib/api/types/household.ts | 4 + frontend/app/lib/api/types/user.ts | 9 ++ frontend/app/lib/icons/icons.ts | 7 + .../app/pages/admin/manage/groups/[id].vue | 2 +- .../pages/admin/manage/households/[id].vue | 1 - frontend/app/pages/group/index.vue | 60 ++++---- frontend/app/pages/user/profile/edit.vue | 9 ++ frontend/app/plugins/axios.ts | 8 +- frontend/eslint.config.mjs | 1 + frontend/vitest.config.js | 2 + ...20.19.07_4395a04f7784_add_announcements.py | 47 ++++++ mealie/db/models/group/preferences.py | 1 + mealie/db/models/household/preferences.py | 2 + mealie/db/models/users/users.py | 6 +- mealie/schema/group/group_preferences.py | 1 + .../schema/household/household_preferences.py | 2 + mealie/schema/user/user.py | 5 + .../admin_tests/test_admin_group_actions.py | 5 +- .../test_admin_household_actions.py | 1 + 32 files changed, 631 insertions(+), 62 deletions(-) create mode 100644 frontend/app/components/Domain/Announcement/AnnouncementDialog.vue create mode 100644 frontend/app/components/Domain/Announcement/Announcements/2026-03-27_1_welcome.vue create mode 100644 frontend/app/components/Domain/Announcement/Announcements/announcements.test.ts create mode 100644 frontend/app/composables/use-announcements.ts create mode 100644 mealie/alembic/versions/2026-03-27-20.19.07_4395a04f7784_add_announcements.py diff --git a/frontend/app/components/Domain/Announcement/AnnouncementDialog.vue b/frontend/app/components/Domain/Announcement/AnnouncementDialog.vue new file mode 100644 index 000000000..3f38c91c2 --- /dev/null +++ b/frontend/app/components/Domain/Announcement/AnnouncementDialog.vue @@ -0,0 +1,139 @@ + + + diff --git a/frontend/app/components/Domain/Announcement/Announcements/2026-03-27_1_welcome.vue b/frontend/app/components/Domain/Announcement/Announcements/2026-03-27_1_welcome.vue new file mode 100644 index 000000000..3134e1856 --- /dev/null +++ b/frontend/app/components/Domain/Announcement/Announcements/2026-03-27_1_welcome.vue @@ -0,0 +1,62 @@ + + + + + + + diff --git a/frontend/app/components/Domain/Announcement/Announcements/announcements.test.ts b/frontend/app/components/Domain/Announcement/Announcements/announcements.test.ts new file mode 100644 index 000000000..e1aa007dd --- /dev/null +++ b/frontend/app/components/Domain/Announcement/Announcements/announcements.test.ts @@ -0,0 +1,37 @@ +import { describe, test, expect } from "vitest"; + +const announcementFiles = import.meta.glob<{ default: unknown }>( + "~/components/Domain/Announcement/Announcements/*.vue", +); + +// Expected format: YYYY-MM-DD_N_slug e.g. 2026-03-27_1_welcome +const FILE_FORMAT = /^\d{4}-\d{2}-\d{2}_\d+_.+$/; + +describe("Announcement files", () => { + const filenames = Object.keys(announcementFiles).map(path => + path.split("/").at(-1)!.replace(".vue", ""), + ); + + test("directory is not empty", () => { + expect(filenames.length).toBeGreaterThan(0); + }); + + test("all filenames match YYYY-MM-DD_N_slug format", () => { + for (const name of filenames) { + expect(name, `"${name}" does not match the expected format`).toMatch(FILE_FORMAT); + } + }); + + test("all date prefixes are valid dates", () => { + for (const name of filenames) { + const datePart = name.split("_", 1)[0]!; + const date = new Date(datePart); + expect(isNaN(date.getTime()), `"${name}" has an invalid date prefix "${datePart}"`).toBe(false); + } + }); + + test("all filenames are unique", () => { + const unique = new Set(filenames); + expect(unique.size).toBe(filenames.length); + }); +}); diff --git a/frontend/app/components/Domain/Group/GroupPreferencesEditor.vue b/frontend/app/components/Domain/Group/GroupPreferencesEditor.vue index 1bfd0c935..e00c163a3 100644 --- a/frontend/app/components/Domain/Group/GroupPreferencesEditor.vue +++ b/frontend/app/components/Domain/Group/GroupPreferencesEditor.vue @@ -1,11 +1,38 @@ @@ -14,5 +41,3 @@ import type { ReadGroupPreferences } from "~/lib/api/types/user"; const preferences = defineModel({ required: true }); - - diff --git a/frontend/app/components/Domain/Household/HouseholdPreferencesEditor.vue b/frontend/app/components/Domain/Household/HouseholdPreferencesEditor.vue index bcbed74a8..6c24e033a 100644 --- a/frontend/app/components/Domain/Household/HouseholdPreferencesEditor.vue +++ b/frontend/app/components/Domain/Household/HouseholdPreferencesEditor.vue @@ -18,6 +18,20 @@

+
+ +
+

+ {{ $t("announcements.show-announcements-setting-description") }} +

+
+
+