diff --git a/frontend/components/Domain/Cookbook/CookbookPage.vue b/frontend/components/Domain/Cookbook/CookbookPage.vue
index 44486e70d..5d771ad52 100644
--- a/frontend/components/Domain/Cookbook/CookbookPage.vue
+++ b/frontend/components/Domain/Cookbook/CookbookPage.vue
@@ -7,7 +7,7 @@
width="100%"
max-width="1100px"
:icon="$globals.icons.pages"
- :title="$t('general.edit')"
+ :title="$tc('general.edit')"
:submit-icon="$globals.icons.save"
:submit-text="$tc('general.save')"
:submit-disabled="!editTarget.queryFilterString"
@@ -25,7 +25,7 @@
{{ book.name }}
{
+ if (!($auth.user && book.value?.householdId)) {
+ return false;
+ }
+
+ return $auth.user.householdId === book.value.householdId;
+ })
+ const canEdit = computed(() => isOwnGroup.value && isOwnHousehold.value);
+
const dialogStates = reactive({
edit: false,
});
@@ -118,7 +127,7 @@
recipes,
removeRecipe,
replaceRecipes,
- isOwnGroup,
+ canEdit,
dialogStates,
editTarget,
handleEditCookbook,
diff --git a/frontend/components/Layout/DefaultLayout.vue b/frontend/components/Layout/DefaultLayout.vue
index 062a70ed3..5e1ea6eca 100644
--- a/frontend/components/Layout/DefaultLayout.vue
+++ b/frontend/components/Layout/DefaultLayout.vue
@@ -82,12 +82,17 @@ import { computed, defineComponent, onMounted, ref, useContext, useRoute } from
import { useLoggedInState } from "~/composables/use-logged-in-state";
import AppHeader from "@/components/Layout/LayoutParts/AppHeader.vue";
import AppSidebar from "@/components/Layout/LayoutParts/AppSidebar.vue";
-import { SidebarLinks } from "~/types/application-types";
+import { SideBarLink } from "~/types/application-types";
import LanguageDialog from "~/components/global/LanguageDialog.vue";
import TheSnackbar from "@/components/Layout/LayoutParts/TheSnackbar.vue";
import { useAppInfo } from "~/composables/api";
import { useCookbooks, usePublicCookbooks } from "~/composables/use-group-cookbooks";
+import { useCookbookPreferences } from "~/composables/use-users/preferences";
+import { useHouseholdStore, usePublicHouseholdStore } from "~/composables/store/use-household-store";
import { useToggleDarkMode } from "~/composables/use-utils";
+import { ReadCookBook } from "~/lib/api/types/cookbook";
+import { HouseholdSummary } from "~/lib/api/types/household";
+
export default defineComponent({
components: { AppHeader, AppSidebar, LanguageDialog, TheSnackbar },
@@ -99,6 +104,15 @@ export default defineComponent({
const route = useRoute();
const groupSlug = computed(() => route.value.params.groupSlug || $auth.user?.groupSlug || "");
const { cookbooks } = isOwnGroup.value ? useCookbooks() : usePublicCookbooks(groupSlug.value || "");
+ const cookbookPreferences = useCookbookPreferences();
+ const { store: households } = isOwnGroup.value ? useHouseholdStore() : usePublicHouseholdStore(groupSlug.value || "");
+
+ const householdsById = computed(() => {
+ return households.value.reduce((acc, household) => {
+ acc[household.id] = household;
+ return acc;
+ }, {} as { [key: string]: HouseholdSummary });
+ });
const appInfo = useAppInfo();
const showImageImport = computed(() => appInfo.value?.enableOpenaiImageServices);
@@ -113,29 +127,57 @@ export default defineComponent({
sidebar.value = !$vuetify.breakpoint.md;
});
- const cookbookLinks = computed(() => {
- if (!cookbooks.value) return [];
- return cookbooks.value.map((cookbook) => {
- return {
- key: cookbook.slug,
- icon: $globals.icons.pages,
- title: cookbook.name,
- to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug as string}`,
- };
- });
- });
-
- interface Link {
- insertDivider: boolean;
- icon: string;
- title: string;
- subtitle: string | null;
- to: string;
- restricted: boolean;
- hide: boolean;
+ function cookbookAsLink(cookbook: ReadCookBook): SideBarLink {
+ return {
+ key: cookbook.slug || "",
+ icon: $globals.icons.pages,
+ title: cookbook.name,
+ to: `/g/${groupSlug.value}/cookbooks/${cookbook.slug || ""}`,
+ restricted: false,
+ };
}
- const createLinks = computed(() => [
+ const currentUserHouseholdId = computed(() => $auth.user?.householdId);
+ const cookbookLinks = computed(() => {
+ if (!cookbooks.value) {
+ return [];
+ }
+ cookbooks.value.sort((a, b) => (a.position || 0) - (b.position || 0));
+
+ const ownLinks: SideBarLink[] = [];
+ const links: SideBarLink[] = [];
+ const cookbooksByHousehold = cookbooks.value.reduce((acc, cookbook) => {
+ const householdName = householdsById.value[cookbook.householdId]?.name || "";
+ if (!acc[householdName]) {
+ acc[householdName] = [];
+ }
+ acc[householdName].push(cookbook);
+ return acc;
+ }, {} as Record);
+
+ Object.entries(cookbooksByHousehold).forEach(([householdName, cookbooks]) => {
+ if (cookbooks[0].householdId === currentUserHouseholdId.value) {
+ ownLinks.push(...cookbooks.map(cookbookAsLink));
+ } else {
+ links.push({
+ key: householdName,
+ icon: $globals.icons.book,
+ title: householdName,
+ children: cookbooks.map(cookbookAsLink),
+ restricted: false,
+ });
+ }
+ });
+
+ links.sort((a, b) => a.title.localeCompare(b.title));
+ if ($auth.user && cookbookPreferences.value.hideOtherHouseholds) {
+ return ownLinks;
+ } else {
+ return [...ownLinks, ...links];
+ }
+ });
+
+ const createLinks = computed(() => [
{
insertDivider: false,
icon: $globals.icons.link,
@@ -165,7 +207,7 @@ export default defineComponent({
},
]);
- const bottomLinks = computed(() => [
+ const bottomLinks = computed(() => [
{
icon: $globals.icons.cog,
title: i18n.tc("general.settings"),
@@ -174,7 +216,7 @@ export default defineComponent({
},
]);
- const topLinks = computed(() => [
+ const topLinks = computed(() => [
{
icon: $globals.icons.silverwareForkKnife,
to: `/g/${groupSlug.value}`,
diff --git a/frontend/components/Layout/LayoutParts/AppSidebar.vue b/frontend/components/Layout/LayoutParts/AppSidebar.vue
index d6c5597f9..ab3e3fb83 100644
--- a/frontend/components/Layout/LayoutParts/AppSidebar.vue
+++ b/frontend/components/Layout/LayoutParts/AppSidebar.vue
@@ -135,7 +135,7 @@