-
-
+
+
+
@@ -15,20 +26,19 @@
{{ $t('meal-plan.this-rule-will-apply', {
- dayCriteria: inputDay === "unset" ? $t('meal-plan.to-all-days') : $t('meal-plan.on-days', [inputDay]),
- mealTypeCriteria: inputEntryType === "unset" ? $t('meal-plan.for-all-meal-types') : $t('meal-plan.for-type-meal-types', [inputEntryType])
- }) }}
+ dayCriteria: inputDay === "unset" ? $t('meal-plan.to-all-days') : $t('meal-plan.on-days', [inputDay]),
+ mealTypeCriteria: inputEntryType === "unset" ? $t('meal-plan.for-all-meal-types') : $t('meal-plan.for-type-meal-types', [inputEntryType]),
+ }) }}
diff --git a/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue b/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
index e5c889938..ea1df3670 100644
--- a/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
+++ b/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
@@ -1,157 +1,144 @@
-
-
-
-
-
-
- {{ $t("household.private-household-description") }}
-
-
-
-
-
-
-
-
- {{ $t("household.lock-recipe-edits-from-other-households-description") }}
-
-
-
-
+
+
+
+
+
+
+ {{ $t("household.private-household-description") }}
+
+
+
+
+
+
+
+
+ {{ $t("household.lock-recipe-edits-from-other-households-description") }}
+
+
+
+
-
-
-
-
-
- {{ p.description }}
-
-
-
-
+
+
+
+
+
+ {{ p.description }}
+
+
+
+
diff --git a/frontend/components/Domain/QueryFilterBuilder.vue b/frontend/components/Domain/QueryFilterBuilder.vue
index b87fb2461..85a1672ba 100644
--- a/frontend/components/Domain/QueryFilterBuilder.vue
+++ b/frontend/components/Domain/QueryFilterBuilder.vue
@@ -2,10 +2,10 @@
-
+
{{ $globals.icons.arrowUpDown }}
+
-
-
- {{ item.label }}
+
+
+ {{ item.raw.label }}
+
-
-
- {{ item }}
+
+
+ {{ item.raw }}
+
-
-
- {{ item.label }}
+
+
+ {{ item.raw.label }}
+
-
-
- {{ item.label }}
+
+
+ {{ item.raw.label }}
+
-
+
setFieldValue(field, index, val ? val.toISOString().slice(0, 10) : '')"
/>
+
-
-
- {{ item }}
+
+
+ {{ item.raw }}
+
-
+
-
+
+
-
-
+
+
diff --git a/frontend/components/Domain/Recipe/RecipeActionMenu.vue b/frontend/components/Domain/Recipe/RecipeActionMenu.vue
index 6de10c9ee..22a633e8a 100644
--- a/frontend/components/Domain/Recipe/RecipeActionMenu.vue
+++ b/frontend/components/Domain/Recipe/RecipeActionMenu.vue
@@ -1,33 +1,37 @@
-
+
{{ $t("recipe.delete-confirmation") }}
-
+
-
-
+
+
-
-
- {{ $globals.icons.edit }}
+
+
+
+ {{ $globals.icons.edit }}
+
{{ $t("general.edit") }}
@@ -37,14 +41,14 @@
- {{ btn.icon }}
- {{ $vuetify.breakpoint.xs ? "" : btn.text }}
+
+ {{ btn.icon }}
+
+ {{ $vuetify.display.xs ? "" : btn.text }}
-
diff --git a/frontend/components/Domain/Recipe/RecipeCardSection.vue b/frontend/components/Domain/Recipe/RecipeCardSection.vue
index 64fb9c0b3..10a29ec4f 100644
--- a/frontend/components/Domain/Recipe/RecipeCardSection.vue
+++ b/frontend/components/Domain/Recipe/RecipeCardSection.vue
@@ -1,67 +1,102 @@
-
+
-
+
{{ displayTitleIcon }}
- {{ title }}
+
+ {{ title }}
+
-
-
-
+
+
+
{{ $globals.icons.diceMultiple }}
- {{ $vuetify.breakpoint.xsOnly ? null : $t("general.random") }}
+ {{ $vuetify.display.xs ? null : $t("general.random") }}
-
-
-
-
-
+
+
+
+
{{ preferences.sortIcon }}
- {{ $vuetify.breakpoint.xsOnly ? null : $t("general.sort") }}
+ {{ $vuetify.display.xs ? null : $t("general.sort") }}
-
- {{ $globals.icons.orderAlphabeticalAscending }}
-
- {{ $t("general.sort-alphabetically") }}
+
+
+ {{ $globals.icons.orderAlphabeticalAscending }}
+
+ {{ $t("general.sort-alphabetically") }}
+
-
- {{ $globals.icons.star }}
-
- {{ $t("general.rating") }}
+
+
+ {{ $globals.icons.star }}
+
+ {{ $t("general.rating") }}
+
-
- {{ $globals.icons.newBox }}
-
- {{ $t("general.created") }}
+
+
+ {{ $globals.icons.newBox }}
+
+ {{ $t("general.created") }}
+
-
- {{ $globals.icons.update }}
-
- {{ $t("general.updated") }}
+
+
+ {{ $globals.icons.update }}
+
+ {{ $t("general.updated") }}
+
-
- {{ $globals.icons.chefHat }}
-
- {{ $t("general.last-made") }}
+
+
+ {{ $globals.icons.chefHat }}
+
+ {{ $t("general.last-made") }}
+
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
+
-
+
-
+
diff --git a/frontend/components/Domain/Recipe/RecipeDataTable.vue b/frontend/components/Domain/Recipe/RecipeDataTable.vue
index 620cdffd0..ac4c28715 100644
--- a/frontend/components/Domain/Recipe/RecipeDataTable.vue
+++ b/frontend/components/Domain/Recipe/RecipeDataTable.vue
@@ -3,60 +3,73 @@
v-model="selected"
item-key="id"
show-select
- sort-by="dateAdded"
- sort-desc
+ :sort-by="[{ key: 'dateAdded', order: 'desc' }]"
:headers="headers"
:items="recipes"
:items-per-page="15"
class="elevation-0"
:loading="loading"
- @input="setValue(selected)"
>
-
-
- |
- Hello |
- |
-
+
+ {{ item.name }}
-
- {{ item.name }}
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+ {{ getMember(item.userId!) }}
+
+
+
-
-
-
-
-
- {{ getMember(item.userId) }}
-
-
-
-
-
- {{ formatDate(item.dateAdded) }}
+
+ {{ formatDate(item.dateAdded!) }}
diff --git a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
index 0aae51731..c7ed432b3 100644
--- a/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
+++ b/frontend/components/Domain/Recipe/RecipeDialogAddToShoppingList.vue
@@ -1,11 +1,18 @@
-
-
-
- {{ $t('shopping-list.no-shopping-lists-found') }}
-
-
+
+
+
+
+ {{ $t('shopping-list.no-shopping-lists-found') }}
+
+
+
{{ $t("general.cancel") }}
-
-
+
+
@@ -38,32 +54,52 @@
:title="selectedShoppingList ? selectedShoppingList.name : $t('recipe.add-to-list')"
:icon="$globals.icons.cartCheck"
width="70%"
- :submit-text="$tc('recipe.add-to-list')"
+ :submit-text="$t('recipe.add-to-list')"
+ can-submit
@submit="addRecipesToList()"
>
-
+
-
-
+
+
{{ recipeSection.recipeName }}
-
+
-
- ({{ $tc("recipe.quantity") }}: {{ recipeSection.recipeScale }})
+
+ ({{ $t("recipe.quantity") }}: {{ recipeSection.recipeScale }})
@@ -73,36 +109,41 @@
v-for="(ingredientSection, ingredientSectionIndex) in recipeSection.ingredientSections"
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex"
>
-
+
{{ ingredientSection.sectionName }}
-
+
-
+ :scale="recipeSection.recipeScale"
+ />
+
@@ -114,12 +155,12 @@
:buttons="[
{
icon: $globals.icons.checkboxBlankOutline,
- text: $tc('shopping-list.uncheck-all-items'),
+ text: $t('shopping-list.uncheck-all-items'),
event: 'uncheck',
},
{
icon: $globals.icons.checkboxOutline,
- text: $tc('shopping-list.check-all-items'),
+ text: $t('shopping-list.check-all-items'),
event: 'check',
},
]"
@@ -132,14 +173,13 @@
diff --git a/frontend/components/Domain/Recipe/RecipeIngredients.vue b/frontend/components/Domain/Recipe/RecipeIngredients.vue
index 63f793b32..d8025be6f 100644
--- a/frontend/components/Domain/Recipe/RecipeIngredients.vue
+++ b/frontend/components/Domain/Recipe/RecipeIngredients.vue
@@ -1,20 +1,51 @@
-
-
{{ $t("recipe.ingredients") }}
-
+
+
+ {{ $t("recipe.ingredients") }}
+
+
-
+
- {{ ingredient.title }}
-
+
+ {{ ingredient.title }}
+
+
-
-
-
-
-
+
+
+
+
+
+
+
@@ -22,12 +53,11 @@
-
+function addNote() {
+ model.value = [...model.value, { title: "", text: "" }];
+}
+
+function removeByIndex(index: number) {
+ const newNotes = [...model.value];
+ newNotes.splice(index, 1);
+ model.value = newNotes;
+}
+
diff --git a/frontend/components/Domain/Recipe/RecipeNutrition.vue b/frontend/components/Domain/Recipe/RecipeNutrition.vue
index aefc6cde7..f9d1bbd3d 100644
--- a/frontend/components/Domain/Recipe/RecipeNutrition.vue
+++ b/frontend/components/Domain/Recipe/RecipeNutrition.vue
@@ -4,23 +4,42 @@
{{ $t("recipe.nutrition") }}
-
+
-
+
+ density="compact"
+ :model-value="modelValue[key]"
+ :label="labels[key].label"
+ :suffix="labels[key].suffix"
+ type="number"
+ autocomplete="off"
+ @update:model-value="updateValue(key, $event)"
+ />
-
-
-
+
+
+
{{ item.label }}
- {{ item.value }}
+
+ {{ item.value }}
+
{{ item.suffix }}
-
+
@@ -28,13 +47,13 @@ dense :value="value[key]" :label="labels[key].label" :suffix="labels[key].suffix
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
index 26dd1a84d..7d62ebd53 100644
--- a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
@@ -1,62 +1,61 @@
-
+
- {{ data.item.name || data.item }}
+ {{ item.value }}
-
-
-
- {{ $globals.icons.create }}
-
-
-
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
index 53d262ea6..c5f456bce 100644
--- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageOrganizers.vue
@@ -1,7 +1,10 @@
-
+
{{ $t("recipe.categories") }}
@@ -14,12 +17,19 @@
:show-add="true"
selector-type="categories"
/>
-
+
-
+
{{ $t("tag.tags") }}
@@ -32,20 +42,39 @@
:show-add="true"
selector-type="tags"
/>
-
+
-
- {{ $t('tool.required-tools') }}
+
+
+ {{ $t('tool.required-tools') }}
+
-
+
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
index c37b04b2e..361f1e7d3 100644
--- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
@@ -1,28 +1,21 @@
-
-
-
-
- {{ $t("recipe.edit-scale") }}
-
+
diff --git a/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue b/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
index a0e0e8cb8..8bfd49852 100644
--- a/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
+++ b/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
@@ -1,51 +1,39 @@
+ />
-
diff --git a/frontend/components/Domain/Recipe/RecipeSuggestion.vue b/frontend/components/Domain/Recipe/RecipeSuggestion.vue
index b6b100877..abf4f6b82 100644
--- a/frontend/components/Domain/Recipe/RecipeSuggestion.vue
+++ b/frontend/components/Domain/Recipe/RecipeSuggestion.vue
@@ -12,23 +12,23 @@
/>
-
+
-
{{ organizer.icon }}
-
- {{ $tc("recipe-finder.missing") }}:
+
+ {{ organizer.icon }}
+
+
+ {{ $t("recipe-finder.missing") }}:
-
+
{{ organizer.getLabel(item.item) }}
@@ -42,9 +42,8 @@
diff --git a/frontend/components/Domain/Recipe/RecipeTimeCard.vue b/frontend/components/Domain/Recipe/RecipeTimeCard.vue
index 4aa555ce5..04191fe6f 100644
--- a/frontend/components/Domain/Recipe/RecipeTimeCard.vue
+++ b/frontend/components/Domain/Recipe/RecipeTimeCard.vue
@@ -1,34 +1,77 @@
-
-
-
- {{ $globals.icons.clockOutline }}
-
- {{ validateTotalTime.name }}
{{ validateTotalTime.value }}
-
-
-
-
-
+
-
-
+
+ {{ $globals.icons.clockOutline }}
+
+
+ {{ validateTotalTime.name }}
{{ validateTotalTime.value }}
+
+
+
+
+
+
+
+
+
{{ $globals.icons.knfife }}
-
{{ validatePrepTime.name }}
{{ validatePrepTime.value }}
+
+ {{ validatePrepTime.name }}
{{ validatePrepTime.value }}
+
-
-
-
+
+
+
{{ $globals.icons.potSteam }}
-
{{ validatePerformTime.name }}
{{ validatePerformTime.value }}
+
+ {{ validatePerformTime.name }}
{{ validatePerformTime.value }}
+
@@ -36,9 +79,7 @@
diff --git a/frontend/components/Domain/Recipe/RecipeTimelineItem.vue b/frontend/components/Domain/Recipe/RecipeTimelineItem.vue
index 180859d47..1913ae36d 100644
--- a/frontend/components/Domain/Recipe/RecipeTimelineItem.vue
+++ b/frontend/components/Domain/Recipe/RecipeTimelineItem.vue
@@ -1,61 +1,57 @@
-
+
- {{ $globals.icons.calendar }}
+
+ {{ $globals.icons.calendar }}
+
{{ new Date(event.timestamp).toLocaleDateString($i18n.locale) }}
-
+
-
+
{{ $globals.icons.calendar }}
{{ new Date(event.timestamp || "").toLocaleDateString($i18n.locale) }}
-
+
-
- {{ event.subject }}
+
+ {{ event.subject }}
-
+
-
-
+
+
-
+
- {{ event.subject }}
-
-
-
-
+ {{ event.subject }}
+
+
+
+
@@ -95,16 +91,15 @@
diff --git a/frontend/components/Domain/User/UserAvatar.vue b/frontend/components/Domain/User/UserAvatar.vue
index b932e0fe3..e287deaac 100644
--- a/frontend/components/Domain/User/UserAvatar.vue
+++ b/frontend/components/Domain/User/UserAvatar.vue
@@ -4,12 +4,29 @@
:disabled="!user || !tooltip"
right
>
-
-
-
-
-
-
+
+
+
+
+
+
@@ -19,11 +36,9 @@
-
-
diff --git a/frontend/components/Domain/User/UserProfileLinkCard.vue b/frontend/components/Domain/User/UserProfileLinkCard.vue
index 5946e1454..f19df7c01 100644
--- a/frontend/components/Domain/User/UserProfileLinkCard.vue
+++ b/frontend/components/Domain/User/UserProfileLinkCard.vue
@@ -1,55 +1,75 @@
-
-
-
+
+
+
-
-
+
+
-
+
{{ link.text }}
-
diff --git a/frontend/components/Domain/User/UserRegistrationForm.vue b/frontend/components/Domain/User/UserRegistrationForm.vue
index 8b1b945f9..a216f8f9d 100644
--- a/frontend/components/Domain/User/UserRegistrationForm.vue
+++ b/frontend/components/Domain/User/UserRegistrationForm.vue
@@ -1,17 +1,25 @@
- {{ $globals.icons.user }}
+
+ {{ $globals.icons.user }}
+
{{ $t("user-registration.account-details") }}
-
+
@@ -28,7 +36,7 @@
v-model="accountDetails.email.value"
v-bind="inputAttrs"
:prepend-icon="$globals.icons.email"
- :label="$tc('user.email')"
+ :label="$t('user.email')"
:rules="[validators.required, validators.email]"
:error-messages="emailErrorMessages"
@blur="validateEmail"
@@ -37,32 +45,32 @@
v-model="credentials.password1.value"
v-bind="inputAttrs"
:type="pwFields.inputType.value"
- :append-icon="pwFields.passwordIcon.value"
+ :append-inner-icon="pwFields.passwordIcon.value"
:prepend-icon="$globals.icons.lock"
- :label="$tc('user.password')"
+ :label="$t('user.password')"
:rules="[validators.required, validators.minLength(8), validators.maxLength(258)]"
- @click:append="pwFields.togglePasswordShow"
+ @click:append-inner="pwFields.togglePasswordShow"
/>
-
+
- {{ $tc("user.enable-advanced-content-description") }}
+ {{ $t("user.enable-advanced-content-description") }}
@@ -71,7 +79,6 @@
+
+
diff --git a/frontend/components/Layout/LayoutParts/AppSidebar.vue b/frontend/components/Layout/LayoutParts/AppSidebar.vue
index ab3e3fb83..49b028f88 100644
--- a/frontend/components/Layout/LayoutParts/AppSidebar.vue
+++ b/frontend/components/Layout/LayoutParts/AppSidebar.vue
@@ -1,66 +1,52 @@
-
+
-
-
+
+
+
-
- {{ $auth.user.fullName }}
-
-
-
- {{ $globals.icons.heart }}
-
- {{ $t("user.favorite-recipes") }}
-
-
-
+
+
+ {{ sessionUser.fullName }}
+
+
+
+
+ {{ $globals.icons.heart }}
+
+ {{ $t("user.favorite-recipes") }}
+
+
+
+
-
+
-
+
-
+
-
-
- {{ nav.title }}
+
+
+
-
-
- {{ child.icon }}
-
- {{ child.title }}
-
+
-
-
-
- {{ nav.icon }}
-
- {{ nav.title }}
-
-
+
+
+
@@ -68,39 +54,28 @@
-
-
+
+
-
-
- {{ nav.title }}
+
+
+
-
-
- {{ child.icon }}
-
- {{ child.title }}
-
+
-
-
-
- {{ nav.icon }}
-
- {{ nav.title }}
-
-
+
+
+ {{ nav.icon }}
+
+ {{ nav.title }}
+
@@ -108,46 +83,39 @@
-
-
-
-
-
-
- {{ nav.icon }}
-
- {{ nav.title }}
-
-
-
-
-
+
+
+
+
+
+ {{ nav.icon }}
+
+ {{ nav.title }}
+
+
+
+
-
diff --git a/frontend/components/global/BannerExperimental.vue b/frontend/components/global/BannerExperimental.vue
index 35254833f..3b6a71bfe 100644
--- a/frontend/components/global/BannerExperimental.vue
+++ b/frontend/components/global/BannerExperimental.vue
@@ -1,10 +1,16 @@
-
- {{ $t("banner-experimental.issue-link-text") }}
+
+ {{ $t("banner-experimental.issue-link-text") }}
diff --git a/frontend/components/global/BannerWarning.vue b/frontend/components/global/BannerWarning.vue
index 9ffb40880..bf2289bbc 100644
--- a/frontend/components/global/BannerWarning.vue
+++ b/frontend/components/global/BannerWarning.vue
@@ -1,9 +1,21 @@
-
+
{{ title }}
- {{ description }}
-
-
+
+ {{ description }}
+
+
+
diff --git a/frontend/components/global/BaseButton.vue b/frontend/components/global/BaseButton.vue
index 7eebc912c..70630d23f 100644
--- a/frontend/components/global/BaseButton.vue
+++ b/frontend/components/global/BaseButton.vue
@@ -1,18 +1,19 @@
-
+
{{ icon || btnAttrs.icon }}
@@ -20,7 +21,10 @@
{{ text || btnAttrs.text }}
-
+
{{ icon || btnAttrs.icon }}
@@ -29,10 +33,9 @@
diff --git a/frontend/components/global/BaseCardSectionTitle.vue b/frontend/components/global/BaseCardSectionTitle.vue
index 71fe97ad6..031634819 100644
--- a/frontend/components/global/BaseCardSectionTitle.vue
+++ b/frontend/components/global/BaseCardSectionTitle.vue
@@ -7,25 +7,29 @@
'mt-8': section,
}"
>
-
-
+
+
{{ icon }}
{{ title }}
-
+
-
+
diff --git a/frontend/components/global/BaseStatCard.vue b/frontend/components/global/BaseStatCard.vue
index 7e3877e05..582cbb3f9 100644
--- a/frontend/components/global/BaseStatCard.vue
+++ b/frontend/components/global/BaseStatCard.vue
@@ -1,5 +1,9 @@
-
+
- {{ icon }}
-
+
+ {{ icon }}
+
+
-
@@ -31,7 +47,10 @@
-
+
@@ -41,9 +60,7 @@
diff --git a/frontend/components/global/ButtonLink.vue b/frontend/components/global/ButtonLink.vue
index 7580a4dda..98b6b053b 100644
--- a/frontend/components/global/ButtonLink.vue
+++ b/frontend/components/global/ButtonLink.vue
@@ -1,7 +1,14 @@
-
-
+
+
{{ icon }}
{{ text }}
@@ -10,9 +17,7 @@
diff --git a/frontend/components/global/InputColor.vue b/frontend/components/global/InputColor.vue
index 8114c69ea..b9356c3b9 100644
--- a/frontend/components/global/InputColor.vue
+++ b/frontend/components/global/InputColor.vue
@@ -1,22 +1,44 @@
-
+
-
+
{{ $globals.icons.refreshCircle }}
-
-
-
+
+
+
{{ $globals.icons.formatColorFill }}
-
+
@@ -25,24 +47,23 @@
-
-
diff --git a/frontend/components/global/MarkdownEditor.vue b/frontend/components/global/MarkdownEditor.vue
index 0816393d0..e5cb3338e 100644
--- a/frontend/components/global/MarkdownEditor.vue
+++ b/frontend/components/global/MarkdownEditor.vue
@@ -1,11 +1,14 @@
-
+
+
-
diff --git a/frontend/components/global/ReportTable.vue b/frontend/components/global/ReportTable.vue
index aede7b1c4..a57dd0050 100644
--- a/frontend/components/global/ReportTable.vue
+++ b/frontend/components/global/ReportTable.vue
@@ -5,19 +5,22 @@
item-key="id"
class="elevation-0"
:items-per-page="50"
- @click:row="handleRowClick"
+ @click:row="($event, { item }) => handleRowClick(item)"
>
-
+
{{ capitalize(item.category) }}
-
- {{ $d(Date.parse(item.timestamp), "long") }}
+
+ {{ $d(Date.parse(item.timestamp!), "long") }}
-
- {{ capitalize(item.status) }}
+
+ {{ capitalize(item.status!) }}
-
-
+
+
{{ $globals.icons.delete }}
@@ -25,27 +28,27 @@
diff --git a/frontend/components/global/StatsCards.vue b/frontend/components/global/StatsCards.vue
index ca3d8589c..d4bff12eb 100644
--- a/frontend/components/global/StatsCards.vue
+++ b/frontend/components/global/StatsCards.vue
@@ -1,17 +1,29 @@
-
+
-
-
+
+
{{ activeIcon }}
-
+
-
+
@@ -19,9 +31,7 @@
diff --git a/frontend/composables/api/api-client.ts b/frontend/composables/api/api-client.ts
index eae0b0983..b8739915a 100644
--- a/frontend/composables/api/api-client.ts
+++ b/frontend/composables/api/api-client.ts
@@ -1,7 +1,5 @@
-import { AxiosResponse } from "axios";
-import { useContext } from "@nuxtjs/composition-api";
-import type { NuxtAxiosInstance } from "@nuxtjs/axios";
-import { ApiRequestInstance, RequestResponse } from "~/lib/api/types/non-generated";
+import type { AxiosInstance, AxiosResponse } from "axios";
+import type { ApiRequestInstance, RequestResponse } from "~/lib/api/types/non-generated";
import { AdminAPI, PublicApi, UserApi } from "~/lib/api";
import { PublicExploreApi } from "~/lib/api/client-public";
@@ -9,7 +7,7 @@ const request = {
async safe(
funcCall: (url: string, data: U) => Promise>,
url: string,
- data: U
+ data: U,
): Promise> {
let error = null;
const response = await funcCall(url, data).catch(function (e) {
@@ -22,7 +20,7 @@ const request = {
},
};
-function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
+function getRequests(axiosInstance: AxiosInstance): ApiRequestInstance {
return {
async get(url: string, params = {}): Promise> {
let error = null;
@@ -36,31 +34,28 @@ function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
},
async post(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
return await request.safe(axiosInstance.post, url, data);
},
async put(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
return await request.safe(axiosInstance.put, url, data);
},
async patch>(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
return await request.safe(axiosInstance.patch, url, data);
},
async delete(url: string) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
return await request.safe(axiosInstance.delete, url, undefined);
},
};
}
export const useRequests = function (): ApiRequestInstance {
- const { $axios, i18n } = useContext();
+ const i18n = useI18n();
+ const { $axios } = useNuxtApp();
- $axios.setHeader("Accept-Language", i18n.locale);
+ $axios.defaults.headers.common["Accept-Language"] = i18n.locale.value;
return getRequests($axios);
};
@@ -83,4 +78,4 @@ export const usePublicApi = function (): PublicApi {
export const usePublicExploreApi = function (groupSlug: string): PublicExploreApi {
const requests = useRequests();
return new PublicExploreApi(requests, groupSlug);
-}
+};
diff --git a/frontend/composables/api/static-routes.ts b/frontend/composables/api/static-routes.ts
index 749a50bb4..3b65d183d 100644
--- a/frontend/composables/api/static-routes.ts
+++ b/frontend/composables/api/static-routes.ts
@@ -1,15 +1,12 @@
-import { useContext } from "@nuxtjs/composition-api";
-import { detectServerBaseUrl } from "../use-utils";
-
function UnknownToString(ukn: string | unknown) {
return typeof ukn === "string" ? ukn : "";
}
export const useStaticRoutes = () => {
- const { $config, req } = useContext();
- const serverBase = detectServerBaseUrl(req);
+ const { $config } = useNuxtApp();
+ const serverBase = useRequestURL().origin;
- const prefix = `${$config.SUB_PATH as string}/api`.replace("//", "/");
+ const prefix = `${$config.public.SUB_PATH}/api`.replace("//", "/");
const fullBase = serverBase + prefix;
@@ -20,13 +17,13 @@ export const useStaticRoutes = () => {
function recipeSmallImage(recipeId: string, version: string | unknown = "", key: string | number = 1) {
return `${fullBase}/media/recipes/${recipeId}/images/min-original.webp?rnd=${key}&version=${UnknownToString(
- version
+ version,
)}`;
}
function recipeTinyImage(recipeId: string, version: string | unknown = "", key: string | number = 1) {
return `${fullBase}/media/recipes/${recipeId}/images/tiny-original.webp?rnd=${key}&version=${UnknownToString(
- version
+ version,
)}`;
}
diff --git a/frontend/composables/api/use-app-info.ts b/frontend/composables/api/use-app-info.ts
index 3e73fce4d..13bd5cf10 100644
--- a/frontend/composables/api/use-app-info.ts
+++ b/frontend/composables/api/use-app-info.ts
@@ -1,17 +1,17 @@
-import { ref, Ref, useAsync, useContext } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
-import { AppInfo } from "~/lib/api/types/admin";
+import type { AppInfo } from "~/lib/api/types/admin";
export function useAppInfo(): Ref {
const appInfo = ref(null);
- const { $axios, i18n } = useContext();
- $axios.setHeader("Accept-Language", i18n.locale);
+ const i18n = useI18n();
+ const { $axios } = useNuxtApp();
+ $axios.defaults.headers.common["Accept-Language"] = i18n.locale.value;
- useAsync(async () => {
+ useAsyncData(useAsyncKey(), async () => {
const data = await $axios.get("/api/app/about");
appInfo.value = data.data;
- }, useAsyncKey());
+ });
return appInfo;
}
diff --git a/frontend/composables/api/use-axios-download.ts b/frontend/composables/api/use-axios-download.ts
deleted file mode 100644
index 1dcb0ae01..000000000
--- a/frontend/composables/api/use-axios-download.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useContext } from "@nuxtjs/composition-api";
-
-export function useAxiosDownloader() {
- const { $axios } = useContext();
-
- function download(url: string, filename: string) {
- $axios({
- url,
- method: "GET",
- responseType: "blob",
- }).then((response) => {
- const url = window.URL.createObjectURL(new Blob([response.data]));
- const link = document.createElement("a");
- link.href = url;
- link.setAttribute("download", filename);
- document.body.appendChild(link);
- link.click();
- });
- }
-
- return download;
-}
diff --git a/frontend/composables/api/use-downloader.ts b/frontend/composables/api/use-downloader.ts
new file mode 100644
index 000000000..4cecca9d0
--- /dev/null
+++ b/frontend/composables/api/use-downloader.ts
@@ -0,0 +1,18 @@
+export function useDownloader() {
+ function download(url: string, filename: string) {
+ useFetch(url, {
+ method: "GET",
+ responseType: "blob",
+ onResponse({ response }) {
+ const url = window.URL.createObjectURL(new Blob([response._data]));
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", filename);
+ document.body.appendChild(link);
+ link.click();
+ },
+ });
+ }
+
+ return download;
+}
diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts
index 3bcb5983f..8a9cbaa38 100644
--- a/frontend/composables/partials/use-actions-factory.ts
+++ b/frontend/composables/partials/use-actions-factory.ts
@@ -1,8 +1,7 @@
-import { Ref, useAsync } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
-import { BoundT } from "./types";
-import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
-import { QueryValue } from "~/lib/api/base/route";
+import type { BoundT } from "./types";
+import type { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
+import type { QueryValue } from "~/lib/api/base/route";
interface ReadOnlyStoreActions {
getAll(page?: number, perPage?: number, params?: any): Ref;
@@ -15,7 +14,6 @@ interface StoreActions extends ReadOnlyStoreActions {
deleteOne(id: string | number): Promise;
}
-
/**
* useReadOnlyActions is a factory function that returns a set of methods
* that can be reused to manage the state of a data store without using
@@ -25,14 +23,14 @@ interface StoreActions extends ReadOnlyStoreActions {
export function useReadOnlyActions(
api: BaseCRUDAPIReadOnly,
allRef: Ref | null,
- loading: Ref
+ loading: Ref,
): ReadOnlyStoreActions {
function getAll(page = 1, perPage = -1, params = {} as Record) {
params.orderBy ??= "name";
params.orderDirection ??= "asc";
loading.value = true;
- const allItems = useAsync(async () => {
+ const allItems = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.getAll(page, perPage, params);
loading.value = false;
@@ -42,10 +40,11 @@ export function useReadOnlyActions(
if (data) {
return data.items ?? [];
- } else {
+ }
+ else {
return [];
}
- }, useAsyncKey());
+ });
return allItems;
}
@@ -79,14 +78,14 @@ export function useReadOnlyActions(
export function useStoreActions(
api: BaseCRUDAPI,
allRef: Ref | null,
- loading: Ref
+ loading: Ref,
): StoreActions {
function getAll(page = 1, perPage = -1, params = {} as Record) {
params.orderBy ??= "name";
params.orderDirection ??= "asc";
loading.value = true;
- const allItems = useAsync(async () => {
+ const allItems = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.getAll(page, perPage, params);
loading.value = false;
@@ -96,10 +95,11 @@ export function useStoreActions(
if (data) {
return data.items ?? [];
- } else {
+ }
+ else {
return [];
}
- }, useAsyncKey());
+ });
return allItems;
}
@@ -123,7 +123,8 @@ export function useStoreActions(
const { data } = await api.createOne(createData);
if (data && allRef?.value) {
allRef.value.push(data);
- } else {
+ }
+ else {
await refresh();
}
loading.value = false;
diff --git a/frontend/composables/partials/use-store-factory.ts b/frontend/composables/partials/use-store-factory.ts
index 72a5f5b3a..0d6701bf1 100644
--- a/frontend/composables/partials/use-store-factory.ts
+++ b/frontend/composables/partials/use-store-factory.ts
@@ -1,19 +1,18 @@
-import { ref, reactive, Ref } from "@nuxtjs/composition-api";
import { useReadOnlyActions, useStoreActions } from "./use-actions-factory";
-import { BoundT } from "./types";
-import { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
-import { QueryValue } from "~/lib/api/base/route";
+import type { BoundT } from "./types";
+import type { BaseCRUDAPI, BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
+import type { QueryValue } from "~/lib/api/base/route";
-export const useData = function(defaultObject: T) {
+export const useData = function (defaultObject: T) {
const data = reactive({ ...defaultObject });
function reset() {
Object.assign(data, defaultObject);
};
return { data, reset };
-}
+};
-export const useReadOnlyStore = function(
+export const useReadOnlyStore = function (
store: Ref,
loading: Ref,
api: BaseCRUDAPIReadOnly,
@@ -36,9 +35,9 @@ export const useReadOnlyStore = function(
}
return { store, actions };
-}
+};
-export const useStore = function(
+export const useStore = function (
store: Ref,
loading: Ref,
api: BaseCRUDAPI,
@@ -61,4 +60,4 @@ export const useStore = function(
}
return { store, actions };
-}
+};
diff --git a/frontend/composables/recipe-page/shared-state.ts b/frontend/composables/recipe-page/shared-state.ts
index a434638e2..0649f17a8 100644
--- a/frontend/composables/recipe-page/shared-state.ts
+++ b/frontend/composables/recipe-page/shared-state.ts
@@ -1,5 +1,4 @@
-import { computed, ComputedRef, ref, Ref, useContext } from "@nuxtjs/composition-api";
-import { UserOut } from "~/lib/api/types/user";
+import type { UserOut } from "~/lib/api/types/user";
import { useNavigationWarning } from "~/composables/use-navigation-warning";
export enum PageMode {
@@ -30,20 +29,20 @@ interface PageState {
editMode: ComputedRef;
/**
- * true is the page is in edit mode and the edit mode is in form mode.
- */
+ * true is the page is in edit mode and the edit mode is in form mode.
+ */
isEditForm: ComputedRef;
/**
- * true is the page is in edit mode and the edit mode is in json mode.
- */
+ * true is the page is in edit mode and the edit mode is in json mode.
+ */
isEditJSON: ComputedRef;
/**
- * true is the page is in view mode.
- */
+ * true is the page is in view mode.
+ */
isEditMode: ComputedRef;
/**
- * true is the page is in cook mode.
- */
+ * true is the page is in cook mode.
+ */
isCookMode: ComputedRef;
setMode: (v: PageMode) => void;
@@ -96,7 +95,8 @@ function pageState({ slugRef, pageModeRef, editModeRef, imageKey }: PageRefs): P
setEditMode(EditorMode.FORM);
}
deactivateNavigationWarning();
- } else if (toMode === PageMode.EDIT) {
+ }
+ else if (toMode === PageMode.EDIT) {
activateNavigationWarning();
}
@@ -142,6 +142,7 @@ export function usePageState(slug: string): PageState {
}
export function clearPageState(slug: string) {
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete memo[slug];
}
@@ -151,9 +152,9 @@ export function clearPageState(slug: string) {
* object with all properties set to their zero value is returned.
*/
export function usePageUser(): { user: UserOut } {
- const { $auth } = useContext();
+ const $auth = useMealieAuth();
- if (!$auth.user) {
+ if (!$auth.user.value) {
return {
user: {
id: "",
@@ -169,5 +170,5 @@ export function usePageUser(): { user: UserOut } {
};
}
- return { user: $auth.user };
+ return { user: $auth.user.value };
}
diff --git a/frontend/composables/recipe-page/use-extract-ingredient-references.test.ts b/frontend/composables/recipe-page/use-extract-ingredient-references.test.ts
index c9b05c095..9721348ce 100644
--- a/frontend/composables/recipe-page/use-extract-ingredient-references.test.ts
+++ b/frontend/composables/recipe-page/use-extract-ingredient-references.test.ts
@@ -3,66 +3,59 @@ import { useExtractIngredientReferences } from "./use-extract-ingredient-referen
const punctuationMarks = ["*", "?", "/", "!", "**", "&", "."];
-
describe("test use extract ingredient references", () => {
- test("when text empty return empty", () => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "", true)
- expect(result).toStrictEqual(new Set());
- });
+ test("when text empty return empty", () => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "", true);
+ expect(result).toStrictEqual(new Set());
+ });
- test("when and ingredient matches exactly and has a reference id, return the referenceId", () => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion", true);
+ test("when and ingredient matches exactly and has a reference id, return the referenceId", () => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion", true);
- expect(result).toEqual(new Set(["123"]));
- });
+ expect(result).toEqual(new Set(["123"]));
+ });
+ test.each(punctuationMarks)("when ingredient is suffixed by punctuation, return the referenceId", (suffix) => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion" + suffix, true);
- test.each(punctuationMarks)("when ingredient is suffixed by punctuation, return the referenceId", (suffix) => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onion" + suffix, true);
+ expect(result).toEqual(new Set(["123"]));
+ });
- expect(result).toEqual(new Set(["123"]));
- });
+ test.each(punctuationMarks)("when ingredient is prefixed by punctuation, return the referenceId", (prefix) => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing " + prefix + "Onion", true);
+ expect(result).toEqual(new Set(["123"]));
+ });
- test.each(punctuationMarks)("when ingredient is prefixed by punctuation, return the referenceId", (prefix) => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing " + prefix + "Onion", true);
- expect(result).toEqual(new Set(["123"]));
- });
+ test("when ingredient is first on a multiline, return the referenceId", () => {
+ const multilineSting = "lksjdlk\nOnion";
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], multilineSting, true);
+ expect(result).toEqual(new Set(["123"]));
+ });
- test("when ingredient is first on a multiline, return the referenceId", () => {
- const multilineSting = "lksjdlk\nOnion"
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], multilineSting, true);
- expect(result).toEqual(new Set(["123"]));
- });
+ test("when the ingredient matches partially exactly and has a reference id, return the referenceId", () => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onions", true);
+ expect(result).toEqual(new Set(["123"]));
+ });
- test("when the ingredient matches partially exactly and has a reference id, return the referenceId", () => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing Onions", true);
- expect(result).toEqual(new Set(["123"]));
- });
+ test("when the ingredient matches with different casing and has a reference id, return the referenceId", () => {
+ const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing oNions", true);
+ expect(result).toEqual(new Set(["123"]));
+ });
+ test("when no ingredients, return empty", () => {
+ const result = useExtractIngredientReferences([], [], "A sentence containing oNions", true);
+ expect(result).toEqual(new Set());
+ });
- test("when the ingredient matches with different casing and has a reference id, return the referenceId", () => {
- const result = useExtractIngredientReferences([{ note: "Onions", referenceId: "123" }], [], "A sentence containing oNions", true);
- expect(result).toEqual(new Set(["123"]));
- });
-
- test("when no ingredients, return empty", () => {
- const result = useExtractIngredientReferences([], [], "A sentence containing oNions", true);
- expect(result).toEqual(new Set());
- });
-
- test("when and ingredient matches but in the existing referenceIds, do not return the referenceId", () => {
- const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], ["123"], "A sentence containing Onion", true);
-
- expect(result).toEqual(new Set());
- });
-
- test("when an word is 2 letter of shorter, it is ignored", () => {
- const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], [], "A sentence containing On", true);
-
- expect(result).toEqual(new Set());
-
- })
+ test("when and ingredient matches but in the existing referenceIds, do not return the referenceId", () => {
+ const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], ["123"], "A sentence containing Onion", true);
+ expect(result).toEqual(new Set());
+ });
+ test("when an word is 2 letter of shorter, it is ignored", () => {
+ const result = useExtractIngredientReferences([{ note: "Onion", referenceId: "123" }], [], "A sentence containing On", true);
+ expect(result).toEqual(new Set());
+ });
});
diff --git a/frontend/composables/recipe-page/use-extract-ingredient-references.ts b/frontend/composables/recipe-page/use-extract-ingredient-references.ts
index bf34074c4..4504bf612 100644
--- a/frontend/composables/recipe-page/use-extract-ingredient-references.ts
+++ b/frontend/composables/recipe-page/use-extract-ingredient-references.ts
@@ -1,60 +1,58 @@
-import { RecipeIngredient } from "~/lib/api/types/recipe";
+import type { RecipeIngredient } from "~/lib/api/types/recipe";
import { parseIngredientText } from "~/composables/recipes";
-
function normalize(word: string): string {
- let normalizing = word;
- normalizing = removeTrailingPunctuation(normalizing);
- normalizing = removeStartingPunctuation(normalizing);
- return normalizing;
+ let normalizing = word;
+ normalizing = removeTrailingPunctuation(normalizing);
+ normalizing = removeStartingPunctuation(normalizing);
+ return normalizing;
}
function removeTrailingPunctuation(word: string): string {
- const punctuationAtEnding = /\p{P}+$/u;
- return word.replace(punctuationAtEnding, "");
+ const punctuationAtEnding = /\p{P}+$/u;
+ return word.replace(punctuationAtEnding, "");
}
function removeStartingPunctuation(word: string): string {
- const punctuationAtBeginning = /^\p{P}+/u;
- return word.replace(punctuationAtBeginning, "");
+ const punctuationAtBeginning = /^\p{P}+/u;
+ return word.replace(punctuationAtBeginning, "");
}
function ingredientMatchesWord(ingredient: RecipeIngredient, word: string, recipeIngredientAmountsDisabled: boolean) {
- const searchText = parseIngredientText(ingredient, recipeIngredientAmountsDisabled);
- return searchText.toLowerCase().includes(word.toLowerCase());
+ const searchText = parseIngredientText(ingredient, recipeIngredientAmountsDisabled);
+ return searchText.toLowerCase().includes(word.toLowerCase());
}
function isBlackListedWord(word: string) {
- // Ignore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
- // other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
- // at the food variable and seeing if the food is in the instructions, but I still need to support those who don't want to provide the value
- // and only use the "notes" feature.
- const blackListedText: string[] = [
- "and",
- "the",
- "for",
- "with",
- "without"
- ];
- const blackListedRegexMatch = /\d/gm; // Match Any Number
- return blackListedText.includes(word) || word.match(blackListedRegexMatch);
+ // Ignore matching blacklisted words when auto-linking - This is kind of a cludgey implementation. We're blacklisting common words but
+ // other common phrases trigger false positives and I'm not sure how else to approach this. In the future I maybe look at looking directly
+ // at the food variable and seeing if the food is in the instructions, but I still need to support those who don't want to provide the value
+ // and only use the "notes" feature.
+ const blackListedText: string[] = [
+ "and",
+ "the",
+ "for",
+ "with",
+ "without",
+ ];
+ const blackListedRegexMatch = /\d/gm; // Match Any Number
+ return blackListedText.includes(word) || word.match(blackListedRegexMatch);
}
export function useExtractIngredientReferences(recipeIngredients: RecipeIngredient[], activeRefs: string[], text: string, recipeIngredientAmountsDisabled: boolean): Set {
- const availableIngredients = recipeIngredients
- .filter((ingredient) => ingredient.referenceId !== undefined)
- .filter((ingredient) => !activeRefs.includes(ingredient.referenceId as string));
+ const availableIngredients = recipeIngredients
+ .filter(ingredient => ingredient.referenceId !== undefined)
+ .filter(ingredient => !activeRefs.includes(ingredient.referenceId as string));
- const allMatchedIngredientIds: string[] = text
- .toLowerCase()
- .split(/\s/)
- .map(normalize)
- .filter((word) => word.length > 2)
- .filter((word) => !isBlackListedWord(word))
- .flatMap((word) => availableIngredients.filter((ingredient) => ingredientMatchesWord(ingredient, word, recipeIngredientAmountsDisabled)))
- .map((ingredient) => ingredient.referenceId as string);
- // deduplicate
-
- return new Set(allMatchedIngredientIds)
+ const allMatchedIngredientIds: string[] = text
+ .toLowerCase()
+ .split(/\s/)
+ .map(normalize)
+ .filter(word => word.length > 2)
+ .filter(word => !isBlackListedWord(word))
+ .flatMap(word => availableIngredients.filter(ingredient => ingredientMatchesWord(ingredient, word, recipeIngredientAmountsDisabled)))
+ .map(ingredient => ingredient.referenceId as string);
+ // deduplicate
+ return new Set(allMatchedIngredientIds);
}
diff --git a/frontend/composables/recipes/use-fraction.ts b/frontend/composables/recipes/use-fraction.ts
index bbb7eac71..07416bfa4 100644
--- a/frontend/composables/recipes/use-fraction.ts
+++ b/frontend/composables/recipes/use-fraction.ts
@@ -14,13 +14,16 @@ function frac(x: number, D: number, mixed: boolean) {
d1 += d2;
n1 += n2;
d2 = D + 1;
- } else if (d1 > d2) d2 = D + 1;
+ }
+ else if (d1 > d2) d2 = D + 1;
else d1 = D + 1;
break;
- } else if (x < m) {
+ }
+ else if (x < m) {
n2 = n1 + n2;
d2 = d1 + d2;
- } else {
+ }
+ else {
n1 = n1 + n2;
d1 = d1 + d2;
}
@@ -58,7 +61,8 @@ function cont(x: number, D: number, mixed: boolean) {
if (Q_1 > D) {
Q = Q_2;
P = P_2;
- } else {
+ }
+ else {
Q = Q_1;
P = P_1;
}
diff --git a/frontend/composables/recipes/use-recipe-ingredients.test.ts b/frontend/composables/recipes/use-recipe-ingredients.test.ts
index 4b6a93869..52eaa3904 100644
--- a/frontend/composables/recipes/use-recipe-ingredients.test.ts
+++ b/frontend/composables/recipes/use-recipe-ingredients.test.ts
@@ -1,6 +1,6 @@
import { describe, test, expect } from "vitest";
import { parseIngredientText } from "./use-recipe-ingredients";
-import { RecipeIngredient } from "~/lib/api/types/recipe";
+import type { RecipeIngredient } from "~/lib/api/types/recipe";
describe(parseIngredientText.name, () => {
const createRecipeIngredient = (overrides: Partial): RecipeIngredient => ({
@@ -59,7 +59,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 2,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("2 tbsps diced onions");
@@ -69,7 +69,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 2,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("2 tablespoons diced onions");
@@ -79,7 +79,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("1 tbsp diced onion");
@@ -89,7 +89,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("1 tablespoon diced onion");
@@ -99,7 +99,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0.5,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: true },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tbsp diced onion");
@@ -109,7 +109,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0.5,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("0.5 tablespoon diced onion");
@@ -119,7 +119,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 0,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false)).toEqual("diced onions");
@@ -129,7 +129,7 @@ describe(parseIngredientText.name, () => {
const ingredient = createRecipeIngredient({
quantity: 1,
unit: { id: "1", name: "tablespoon", pluralName: "tablespoons", abbreviation: "tbsp", pluralAbbreviation: "tbsps", useAbbreviation: false },
- food: { id: "1", name: "diced onion", pluralName: "diced onions" }
+ food: { id: "1", name: "diced onion", pluralName: "diced onions" },
});
expect(parseIngredientText(ingredient, false, 2)).toEqual("2 tablespoons diced onions");
diff --git a/frontend/composables/recipes/use-recipe-ingredients.ts b/frontend/composables/recipes/use-recipe-ingredients.ts
index f22508e8e..362cda3d5 100644
--- a/frontend/composables/recipes/use-recipe-ingredients.ts
+++ b/frontend/composables/recipes/use-recipe-ingredients.ts
@@ -1,6 +1,7 @@
import DOMPurify from "isomorphic-dompurify";
import { useFraction } from "./use-fraction";
-import { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, RecipeIngredient } from "~/lib/api/types/recipe";
+import type { CreateIngredientFood, CreateIngredientUnit, IngredientFood, IngredientUnit, RecipeIngredient } from "~/lib/api/types/recipe";
+
const { frac } = useFraction();
export function sanitizeIngredientHTML(rawHtml: string) {
@@ -47,7 +48,7 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
const { quantity, food, unit, note } = ingredient;
const usePluralUnit = quantity !== undefined && ((quantity || 0) * scale > 1 || (quantity || 0) * scale === 0);
- const usePluralFood = (!quantity) || quantity * scale > 1
+ const usePluralFood = (!quantity) || quantity * scale > 1;
let returnQty = "";
@@ -55,16 +56,17 @@ export function useParsedIngredientText(ingredient: RecipeIngredient, disableAmo
if (quantity && Number(quantity) !== 0) {
if (unit && !unit.fraction) {
returnQty = Number((quantity * scale).toPrecision(3)).toString();
- } else {
+ }
+ else {
const fraction = frac(quantity * scale, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
returnQty += fraction[0];
}
if (fraction[1] > 0) {
- returnQty += includeFormating ?
- `${fraction[1]}⁄${fraction[2]}` :
- ` ${fraction[1]}/${fraction[2]}`;
+ returnQty += includeFormating
+ ? `${fraction[1]}⁄${fraction[2]}`
+ : ` ${fraction[1]}/${fraction[2]}`;
}
}
}
diff --git a/frontend/composables/recipes/use-recipe-nutrition.ts b/frontend/composables/recipes/use-recipe-nutrition.ts
index e249d5d61..c61f03263 100644
--- a/frontend/composables/recipes/use-recipe-nutrition.ts
+++ b/frontend/composables/recipes/use-recipe-nutrition.ts
@@ -1,6 +1,3 @@
-import { useContext } from "@nuxtjs/composition-api";
-
-
export interface NutritionLabelType {
[key: string]: {
label: string;
@@ -9,55 +6,54 @@ export interface NutritionLabelType {
};
};
-
export function useNutritionLabels() {
- const { i18n } = useContext();
+ const i18n = useI18n();
const labels = {
calories: {
- label: i18n.tc("recipe.calories"),
- suffix: i18n.tc("recipe.calories-suffix"),
+ label: i18n.t("recipe.calories"),
+ suffix: i18n.t("recipe.calories-suffix"),
},
carbohydrateContent: {
- label: i18n.tc("recipe.carbohydrate-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.carbohydrate-content"),
+ suffix: i18n.t("recipe.grams"),
},
cholesterolContent: {
- label: i18n.tc("recipe.cholesterol-content"),
- suffix: i18n.tc("recipe.milligrams"),
+ label: i18n.t("recipe.cholesterol-content"),
+ suffix: i18n.t("recipe.milligrams"),
},
fatContent: {
- label: i18n.tc("recipe.fat-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.fat-content"),
+ suffix: i18n.t("recipe.grams"),
},
fiberContent: {
- label: i18n.tc("recipe.fiber-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.fiber-content"),
+ suffix: i18n.t("recipe.grams"),
},
proteinContent: {
- label: i18n.tc("recipe.protein-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.protein-content"),
+ suffix: i18n.t("recipe.grams"),
},
saturatedFatContent: {
- label: i18n.tc("recipe.saturated-fat-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.saturated-fat-content"),
+ suffix: i18n.t("recipe.grams"),
},
sodiumContent: {
- label: i18n.tc("recipe.sodium-content"),
- suffix: i18n.tc("recipe.milligrams"),
+ label: i18n.t("recipe.sodium-content"),
+ suffix: i18n.t("recipe.milligrams"),
},
sugarContent: {
- label: i18n.tc("recipe.sugar-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.sugar-content"),
+ suffix: i18n.t("recipe.grams"),
},
transFatContent: {
- label: i18n.tc("recipe.trans-fat-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.trans-fat-content"),
+ suffix: i18n.t("recipe.grams"),
},
unsaturatedFatContent: {
- label: i18n.tc("recipe.unsaturated-fat-content"),
- suffix: i18n.tc("recipe.grams"),
+ label: i18n.t("recipe.unsaturated-fat-content"),
+ suffix: i18n.t("recipe.grams"),
},
};
- return { labels }
+ return { labels };
}
diff --git a/frontend/composables/recipes/use-recipe-permissions.test.ts b/frontend/composables/recipes/use-recipe-permissions.test.ts
index a962d7af3..e2670d51b 100644
--- a/frontend/composables/recipes/use-recipe-permissions.test.ts
+++ b/frontend/composables/recipes/use-recipe-permissions.test.ts
@@ -1,9 +1,9 @@
import { describe, test, expect } from "vitest";
-import { ref, Ref } from "@nuxtjs/composition-api";
+import { ref } from "vue";
import { useRecipePermissions } from "./use-recipe-permissions";
-import { HouseholdSummary } from "~/lib/api/types/household";
-import { Recipe } from "~/lib/api/types/recipe";
-import { UserOut } from "~/lib/api/types/user";
+import type { HouseholdSummary } from "~/lib/api/types/household";
+import type { Recipe } from "~/lib/api/types/recipe";
+import type { UserOut } from "~/lib/api/types/user";
describe("test use recipe permissions", () => {
const commonUserId = "my-user-id";
@@ -67,7 +67,7 @@ describe("test use recipe permissions", () => {
createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(true);
- }
+ },
);
test(
@@ -79,14 +79,14 @@ describe("test use recipe permissions", () => {
createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(true);
- }
+ },
);
test("when user is not recipe owner, and user is other group, cannot edit", () => {
const result = useRecipePermissions(
createRecipe({}),
createRecipeHousehold({}),
- createUser({ id: "other-user-id", groupId: "other-group-id"}),
+ createUser({ id: "other-user-id", groupId: "other-group-id" }),
);
expect(result.canEditRecipe.value).toBe(false);
});
@@ -113,7 +113,7 @@ describe("test use recipe permissions", () => {
const result = useRecipePermissions(
createRecipe({}, true),
createRecipeHousehold({}),
- createUser({ id: "other-user-id"}),
+ createUser({ id: "other-user-id" }),
);
expect(result.canEditRecipe.value).toBe(false);
});
diff --git a/frontend/composables/recipes/use-recipe-permissions.ts b/frontend/composables/recipes/use-recipe-permissions.ts
index d4efbfd05..ecb4f75c3 100644
--- a/frontend/composables/recipes/use-recipe-permissions.ts
+++ b/frontend/composables/recipes/use-recipe-permissions.ts
@@ -1,7 +1,7 @@
-import { computed, Ref } from "@nuxtjs/composition-api";
-import { Recipe } from "~/lib/api/types/recipe";
-import { HouseholdSummary } from "~/lib/api/types/household";
-import { UserOut } from "~/lib/api/types/user";
+import { computed } from "vue";
+import type { Recipe } from "~/lib/api/types/recipe";
+import type { HouseholdSummary } from "~/lib/api/types/household";
+import type { UserOut } from "~/lib/api/types/user";
export function useRecipePermissions(
recipe: Recipe,
@@ -40,5 +40,5 @@ export function useRecipePermissions(
return {
canEditRecipe,
- }
+ };
}
diff --git a/frontend/composables/recipes/use-recipe-search.ts b/frontend/composables/recipes/use-recipe-search.ts
index e4e0eecd4..e0a9dd711 100644
--- a/frontend/composables/recipes/use-recipe-search.ts
+++ b/frontend/composables/recipes/use-recipe-search.ts
@@ -1,8 +1,7 @@
-import { Ref, ref } from "@nuxtjs/composition-api";
import { watchDebounced } from "@vueuse/core";
-import { UserApi } from "~/lib/api";
-import { ExploreApi } from "~/lib/api/public/explore";
-import { Recipe } from "~/lib/api/types/recipe";
+import type { UserApi } from "~/lib/api";
+import type { ExploreApi } from "~/lib/api/public/explore";
+import type { Recipe } from "~/lib/api/types/recipe";
export interface UseRecipeSearchReturn {
query: Ref;
@@ -54,7 +53,7 @@ export function useRecipeSearch(api: UserApi | ExploreApi): UseRecipeSearchRetur
async (term: string) => {
await searchRecipes(term);
},
- { debounce: 500 }
+ { debounce: 500 },
);
async function trigger() {
diff --git a/frontend/composables/recipes/use-recipe-timeline-events.ts b/frontend/composables/recipes/use-recipe-timeline-events.ts
index 3bef821fb..b923a7bb7 100644
--- a/frontend/composables/recipes/use-recipe-timeline-events.ts
+++ b/frontend/composables/recipes/use-recipe-timeline-events.ts
@@ -1,5 +1,4 @@
-import { computed, useContext } from "@nuxtjs/composition-api";
-import { TimelineEventType } from "~/lib/api/types/recipe";
+import type { TimelineEventType } from "~/lib/api/types/recipe";
export interface TimelineEventTypeData {
value: TimelineEventType;
@@ -8,22 +7,23 @@ export interface TimelineEventTypeData {
}
export const useTimelineEventTypes = () => {
- const { $globals, i18n } = useContext();
+ const i18n = useI18n();
+ const { $globals } = useNuxtApp();
const eventTypeOptions = computed(() => {
return [
{
value: "comment",
- label: i18n.tc("recipe.comment"),
+ label: i18n.t("recipe.comment"),
icon: $globals.icons.commentTextMultiple,
},
{
value: "info",
- label: i18n.tc("settings.theme.info"),
+ label: i18n.t("settings.theme.info"),
icon: $globals.icons.informationVariant,
},
{
value: "system",
- label: i18n.tc("general.system"),
+ label: i18n.t("general.system"),
icon: $globals.icons.cog,
},
];
@@ -31,5 +31,5 @@ export const useTimelineEventTypes = () => {
return {
eventTypeOptions,
- }
-}
+ };
+};
diff --git a/frontend/composables/recipes/use-recipe-tools.ts b/frontend/composables/recipes/use-recipe-tools.ts
index 0bddacb3c..486eba18c 100644
--- a/frontend/composables/recipes/use-recipe-tools.ts
+++ b/frontend/composables/recipes/use-recipe-tools.ts
@@ -1,8 +1,7 @@
-import { reactive, ref, useAsync } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
import { useUserApi } from "~/composables/api";
-import { VForm } from "~/types/vuetify";
-import { RecipeTool } from "~/lib/api/types/recipe";
+import type { VForm } from "~/types/vuetify";
+import type { RecipeTool } from "~/lib/api/types/recipe";
export const useTools = function (eager = true) {
const workingToolData = reactive({
@@ -18,15 +17,16 @@ export const useTools = function (eager = true) {
const actions = {
getAll() {
loading.value = true;
- const units = useAsync(async () => {
+ const units = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.tools.getAll();
if (data) {
return data.items;
- } else {
+ }
+ else {
return null;
}
- }, useAsyncKey());
+ });
loading.value = false;
return units;
@@ -86,7 +86,8 @@ export const useTools = function (eager = true) {
const tools = (() => {
if (eager) {
return actions.getAll();
- } else {
+ }
+ else {
return ref([]);
}
})();
diff --git a/frontend/composables/recipes/use-recipe.ts b/frontend/composables/recipes/use-recipe.ts
index 764bebe1b..f74740126 100644
--- a/frontend/composables/recipes/use-recipe.ts
+++ b/frontend/composables/recipes/use-recipe.ts
@@ -1,6 +1,5 @@
-import { ref, onMounted } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
-import { Recipe } from "~/lib/api/types/recipe";
+import type { Recipe } from "~/lib/api/types/recipe";
export const useRecipe = function (slug: string, eager = true) {
const api = useUserApi();
diff --git a/frontend/composables/recipes/use-recipes.ts b/frontend/composables/recipes/use-recipes.ts
index 9286968f3..104e7403d 100644
--- a/frontend/composables/recipes/use-recipes.ts
+++ b/frontend/composables/recipes/use-recipes.ts
@@ -1,9 +1,9 @@
-import { useAsync, useRouter, ref } from "@nuxtjs/composition-api";
+import { ref } from "vue";
import { useAsyncKey } from "../use-utils";
import { usePublicExploreApi } from "~/composables/api/api-client";
import { useUserApi } from "~/composables/api";
-import { OrderByNullPosition, Recipe } from "~/lib/api/types/recipe";
-import { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
+import type { OrderByNullPosition, Recipe } from "~/lib/api/types/recipe";
+import type { RecipeSearchQuery } from "~/lib/api/user/recipes/recipe";
export const allRecipes = ref([]);
export const recentRecipes = ref([]);
@@ -13,7 +13,7 @@ function getParams(
orderDirection = "desc",
orderByNullPosition: OrderByNullPosition | null = null,
query: RecipeSearchQuery | null = null,
- queryFilter: string | null = null
+ queryFilter: string | null = null,
) {
return {
orderBy,
@@ -53,7 +53,6 @@ export const useLazyRecipes = function (publicGroupSlug: string | null = null) {
query: RecipeSearchQuery | null = null,
queryFilter: string | null = null,
) {
-
const { data, error } = await api.recipes.getAll(
page,
perPage,
@@ -113,7 +112,7 @@ export const useRecipes = (
fetchRecipes = true,
loadFood = false,
queryFilter: string | null = null,
- publicGroupSlug: string | null = null
+ publicGroupSlug: string | null = null,
) => {
const api = publicGroupSlug ? usePublicExploreApi(publicGroupSlug).explore : useUserApi();
@@ -125,7 +124,8 @@ export const useRecipes = (
page: 1,
perPage: -1,
};
- } else {
+ }
+ else {
return {
recipes: recentRecipes,
page: 1,
@@ -142,9 +142,9 @@ export const useRecipes = (
}
function getAllRecipes() {
- useAsync(async () => {
+ useAsyncData(useAsyncKey(), async () => {
await refreshRecipes();
- }, useAsyncKey());
+ });
}
function assignSorted(val: Array) {
diff --git a/frontend/composables/recipes/use-scaled-amount.ts b/frontend/composables/recipes/use-scaled-amount.ts
index 1f642e91d..06d37db89 100644
--- a/frontend/composables/recipes/use-scaled-amount.ts
+++ b/frontend/composables/recipes/use-scaled-amount.ts
@@ -11,11 +11,11 @@ function formatQuantity(val: number): string {
const fraction = frac(val, 10, true);
if (fraction[0] !== undefined && fraction[0] > 0) {
- valString += fraction[0];
+ valString += fraction[0];
}
if (fraction[1] > 0) {
- valString += `${fraction[1]}⁄${fraction[2]}`;
+ valString += `${fraction[1]}⁄${fraction[2]}`;
}
return valString.trim();
diff --git a/frontend/composables/store/use-category-store.ts b/frontend/composables/store/use-category-store.ts
index e64cd060a..0509fa7c7 100644
--- a/frontend/composables/store/use-category-store.ts
+++ b/frontend/composables/store/use-category-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
-import { RecipeCategory } from "~/lib/api/types/recipe";
+import type { RecipeCategory } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -13,14 +12,14 @@ export const useCategoryData = function () {
name: "",
slug: "",
});
-}
+};
export const useCategoryStore = function () {
const api = useUserApi();
return useStore(store, loading, api.categories);
-}
+};
export const usePublicCategoryStore = function (groupSlug: string) {
const api = usePublicExploreApi(groupSlug).explore;
return useReadOnlyStore(store, publicLoading, api.categories);
-}
+};
diff --git a/frontend/composables/store/use-cookbook-store.ts b/frontend/composables/store/use-cookbook-store.ts
new file mode 100644
index 000000000..34e7522a3
--- /dev/null
+++ b/frontend/composables/store/use-cookbook-store.ts
@@ -0,0 +1,17 @@
+import { useReadOnlyStore, useStore } from "../partials/use-store-factory";
+import type { RecipeCookBook } from "~/lib/api/types/cookbook";
+import { usePublicExploreApi, useUserApi } from "~/composables/api";
+
+const store: Ref = ref([]);
+const loading = ref(false);
+const publicLoading = ref(false);
+
+export const useCookbookStore = function () {
+ const api = useUserApi();
+ return useStore(store, loading, api.cookbooks);
+};
+
+export const usePublicCookbookStore = function (groupSlug: string) {
+ const api = usePublicExploreApi(groupSlug).explore;
+ return useReadOnlyStore(store, publicLoading, api.cookbooks);
+};
diff --git a/frontend/composables/store/use-food-store.ts b/frontend/composables/store/use-food-store.ts
index 6599f2dac..80ef1c7c3 100644
--- a/frontend/composables/store/use-food-store.ts
+++ b/frontend/composables/store/use-food-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
-import { IngredientFood } from "~/lib/api/types/recipe";
+import type { IngredientFood } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -14,14 +13,14 @@ export const useFoodData = function () {
description: "",
labelId: undefined,
});
-}
+};
export const useFoodStore = function () {
const api = useUserApi();
return useStore(store, loading, api.foods);
-}
+};
export const usePublicFoodStore = function (groupSlug: string) {
const api = usePublicExploreApi(groupSlug).explore;
return useReadOnlyStore(store, publicLoading, api.foods);
-}
+};
diff --git a/frontend/composables/store/use-household-store.ts b/frontend/composables/store/use-household-store.ts
index 0b7c8eef1..0622be677 100644
--- a/frontend/composables/store/use-household-store.ts
+++ b/frontend/composables/store/use-household-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useReadOnlyStore } from "../partials/use-store-factory";
-import { HouseholdSummary } from "~/lib/api/types/household";
+import type { HouseholdSummary } from "~/lib/api/types/household";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -10,9 +9,9 @@ const publicLoading = ref(false);
export const useHouseholdStore = function () {
const api = useUserApi();
return useReadOnlyStore(store, loading, api.households);
-}
+};
export const usePublicHouseholdStore = function (groupSlug: string) {
const api = usePublicExploreApi(groupSlug).explore;
return useReadOnlyStore(store, publicLoading, api.households);
-}
+};
diff --git a/frontend/composables/store/use-label-store.ts b/frontend/composables/store/use-label-store.ts
index 0cd3bb58d..96219aa15 100644
--- a/frontend/composables/store/use-label-store.ts
+++ b/frontend/composables/store/use-label-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useStore } from "../partials/use-store-factory";
-import { MultiPurposeLabelOut } from "~/lib/api/types/labels";
+import type { MultiPurposeLabelOut } from "~/lib/api/types/labels";
import { useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -13,9 +12,9 @@ export const useLabelData = function () {
name: "",
color: "",
});
-}
+};
export const useLabelStore = function () {
const api = useUserApi();
return useStore(store, loading, api.multiPurposeLabels);
-}
+};
diff --git a/frontend/composables/store/use-tag-store.ts b/frontend/composables/store/use-tag-store.ts
index b5a30822a..939a14232 100644
--- a/frontend/composables/store/use-tag-store.ts
+++ b/frontend/composables/store/use-tag-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
-import { RecipeTag } from "~/lib/api/types/recipe";
+import type { RecipeTag } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -13,14 +12,14 @@ export const useTagData = function () {
name: "",
slug: "",
});
-}
+};
export const useTagStore = function () {
const api = useUserApi();
return useStore(store, loading, api.tags);
-}
+};
export const usePublicTagStore = function (groupSlug: string) {
const api = usePublicExploreApi(groupSlug).explore;
return useReadOnlyStore(store, publicLoading, api.tags);
-}
+};
diff --git a/frontend/composables/store/use-tool-store.ts b/frontend/composables/store/use-tool-store.ts
index e6c38d307..4886d0b13 100644
--- a/frontend/composables/store/use-tool-store.ts
+++ b/frontend/composables/store/use-tool-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useReadOnlyStore, useStore } from "../partials/use-store-factory";
-import { RecipeTool } from "~/lib/api/types/recipe";
+import type { RecipeTool } from "~/lib/api/types/recipe";
import { usePublicExploreApi, useUserApi } from "~/composables/api";
interface RecipeToolWithOnHand extends RecipeTool {
@@ -19,14 +18,14 @@ export const useToolData = function () {
onHand: false,
householdsWithTool: [],
});
-}
+};
export const useToolStore = function () {
const api = useUserApi();
return useStore(store, loading, api.tools);
-}
+};
export const usePublicToolStore = function (groupSlug: string) {
const api = usePublicExploreApi(groupSlug).explore;
return useReadOnlyStore(store, publicLoading, api.tools);
-}
+};
diff --git a/frontend/composables/store/use-unit-store.ts b/frontend/composables/store/use-unit-store.ts
index 3bf0926a6..2ba3592d4 100644
--- a/frontend/composables/store/use-unit-store.ts
+++ b/frontend/composables/store/use-unit-store.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useData, useStore } from "../partials/use-store-factory";
-import { IngredientUnit } from "~/lib/api/types/recipe";
+import type { IngredientUnit } from "~/lib/api/types/recipe";
import { useUserApi } from "~/composables/api";
const store: Ref = ref([]);
@@ -14,9 +13,9 @@ export const useUnitData = function () {
abbreviation: "",
description: "",
});
-}
+};
export const useUnitStore = function () {
const api = useUserApi();
return useStore(store, loading, api.units);
-}
+};
diff --git a/frontend/composables/store/use-user-store.ts b/frontend/composables/store/use-user-store.ts
index 2bd796e54..b5ce6e385 100644
--- a/frontend/composables/store/use-user-store.ts
+++ b/frontend/composables/store/use-user-store.ts
@@ -1,7 +1,6 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
import { useReadOnlyStore } from "../partials/use-store-factory";
import { useRequests } from "../api/api-client";
-import { UserSummary } from "~/lib/api/types/user";
+import type { UserSummary } from "~/lib/api/types/user";
import { BaseCRUDAPIReadOnly } from "~/lib/api/base/base-clients";
const store: Ref = ref([]);
@@ -16,5 +15,5 @@ export const useUserStore = function () {
const requests = useRequests();
const api = new GroupUserAPIReadOnly(requests);
- return useReadOnlyStore(store, loading, api, {orderBy: "full_name"});
-}
+ return useReadOnlyStore(store, loading, api, { orderBy: "full_name" });
+};
diff --git a/frontend/composables/use-backups.ts b/frontend/composables/use-backups.ts
index 481993a4b..fbcbed6c4 100644
--- a/frontend/composables/use-backups.ts
+++ b/frontend/composables/use-backups.ts
@@ -1,6 +1,5 @@
-import { useAsync, ref, reactive } from "@nuxtjs/composition-api";
import { toastLoading, loader } from "./use-toast";
-import { AllBackups, BackupOptions } from "~/lib/api/types/admin";
+import type { AllBackups, BackupOptions } from "~/lib/api/types/admin";
import { useUserApi } from "~/composables/api";
interface ImportBackup {
@@ -54,7 +53,7 @@ export const useBackups = function (fetch = true) {
});
function getBackups() {
- const backups = useAsync(async () => {
+ const backups = useAsyncData(async () => {
const { data } = await api.backups.getAll();
return data;
});
diff --git a/frontend/composables/use-context-presents.ts b/frontend/composables/use-context-presents.ts
index 9af06eb88..7a9f6e9bf 100644
--- a/frontend/composables/use-context-presents.ts
+++ b/frontend/composables/use-context-presents.ts
@@ -1,5 +1,3 @@
-import { useContext } from "@nuxtjs/composition-api";
-
export interface ContextMenuItem {
title: string;
icon: string;
@@ -14,21 +12,22 @@ export interface ContextMenuPresets {
}
export function useContextPresets(): ContextMenuPresets {
- const { $globals, i18n } = useContext();
+ const i18n = useI18n();
+ const { $globals } = useNuxtApp();
return {
delete: {
- title: i18n.tc("general.delete"),
+ title: i18n.t("general.delete"),
icon: $globals.icons.delete,
event: "delete",
},
edit: {
- title: i18n.tc("general.edit"),
+ title: i18n.t("general.edit"),
icon: $globals.icons.edit,
event: "edit",
},
save: {
- title: i18n.tc("general.save"),
+ title: i18n.t("general.save"),
icon: $globals.icons.save,
event: "save",
},
diff --git a/frontend/composables/use-copy.ts b/frontend/composables/use-copy.ts
index 4bd32fff3..61bcf87c3 100644
--- a/frontend/composables/use-copy.ts
+++ b/frontend/composables/use-copy.ts
@@ -1,23 +1,22 @@
-import { useContext } from "@nuxtjs/composition-api";
import { useClipboard } from "@vueuse/core";
import { alert } from "./use-toast";
export function useCopy() {
const { copy, copied, isSupported } = useClipboard();
- const { i18n } = useContext();
+ const i18n = useI18n();
function copyText(text: string) {
if (!isSupported.value) {
- alert.error(i18n.tc("general.clipboard-not-supported"));
+ alert.error(i18n.t("general.clipboard-not-supported"));
return;
}
copy(text).then(() => {
// Verify copy success as no error is thrown on failure.
if (copied.value) {
- alert.success(i18n.tc("general.copied-to-clipboard"));
+ alert.success(i18n.t("general.copied-to-clipboard"));
}
else {
- alert.error(i18n.tc("general.clipboard-copy-failure"));
+ alert.error(i18n.t("general.clipboard-copy-failure"));
}
});
}
@@ -27,11 +26,11 @@ export function useCopy() {
export function useCopyList() {
const { copy, isSupported, copied } = useClipboard();
- const { i18n } = useContext();
+ const i18n = useI18n();
function checkClipboard() {
if (!isSupported.value) {
- alert.error(i18n.tc("general.your-browser-does-not-support-clipboard"));
+ alert.error(i18n.t("general.your-browser-does-not-support-clipboard"));
return false;
}
@@ -48,14 +47,14 @@ export function useCopyList() {
function copyMarkdown(list: string[]) {
if (!checkClipboard()) return;
- const text = list.map((item) => `- ${item}`).join("\n");
+ const text = list.map(item => `- ${item}`).join("\n");
copyText(text, list.length);
}
function copyMarkdownCheckList(list: string[]) {
if (!checkClipboard()) return;
- const text = list.map((item) => `- [ ] ${item}`).join("\n");
+ const text = list.map(item => `- [ ] ${item}`).join("\n");
copyText(text, list.length);
}
@@ -63,10 +62,10 @@ export function useCopyList() {
copy(text).then(() => {
// Verify copy success as no error is thrown on failure.
if (copied.value) {
- alert.success(i18n.tc("general.copied-items-to-clipboard", len));
+ alert.success(i18n.t("general.copied-items-to-clipboard", len));
}
else {
- alert.error(i18n.tc("general.clipboard-copy-failure"));
+ alert.error(i18n.t("general.clipboard-copy-failure"));
}
});
}
diff --git a/frontend/composables/use-group-cookbooks.ts b/frontend/composables/use-group-cookbooks.ts
index 52e989a24..0cdeee82a 100644
--- a/frontend/composables/use-group-cookbooks.ts
+++ b/frontend/composables/use-group-cookbooks.ts
@@ -1,168 +1,20 @@
-import { useAsync, ref, Ref, useContext } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { usePublicExploreApi } from "./api/api-client";
-import { useHouseholdSelf } from "./use-households";
import { useUserApi } from "~/composables/api";
-import { ReadCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
-
-let cookbookStore: Ref | null = null;
export const useCookbook = function (publicGroupSlug: string | null = null) {
function getOne(id: string | number) {
// passing the group slug switches to using the public API
const api = publicGroupSlug ? usePublicExploreApi(publicGroupSlug).explore : useUserApi();
- const units = useAsync(async () => {
+ const { data: units } = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.cookbooks.getOne(id);
return data;
- }, useAsyncKey());
+ });
return units;
}
return { getOne };
};
-
-export const usePublicCookbooks = function (groupSlug: string) {
- const api = usePublicExploreApi(groupSlug).explore;
- const loading = ref(false);
-
- const actions = {
- getAll() {
- loading.value = true;
- const units = useAsync(async () => {
- const { data } = await api.cookbooks.getAll(1, -1, { orderBy: "position", orderDirection: "asc" });
-
- if (data) {
- return data.items;
- } else {
- return null;
- }
- }, useAsyncKey());
-
- loading.value = false;
- return units;
- },
- async refreshAll() {
- loading.value = true;
- const { data } = await api.cookbooks.getAll(1, -1, { orderBy: "position", orderDirection: "asc" });
-
- if (data && data.items && cookbookStore) {
- cookbookStore.value = data.items;
- }
-
- loading.value = false;
- },
- flushStore() {
- cookbookStore = null;
- },
- };
-
- if (!cookbookStore) {
- cookbookStore = actions.getAll();
- }
-
- return { cookbooks: cookbookStore, actions };
-}
-
-export const useCookbooks = function () {
- const api = useUserApi();
- const { household } = useHouseholdSelf();
- const loading = ref(false);
-
- const { i18n } = useContext();
-
- const actions = {
- getAll() {
- loading.value = true;
- const units = useAsync(async () => {
- const { data } = await api.cookbooks.getAll(1, -1, { orderBy: "position", orderDirection: "asc" });
-
- if (data) {
- return data.items;
- } else {
- return null;
- }
- }, useAsyncKey());
-
- loading.value = false;
- return units;
- },
- async refreshAll() {
- loading.value = true;
- const { data } = await api.cookbooks.getAll(1, -1, { orderBy: "position", orderDirection: "asc" });
-
- if (data && data.items && cookbookStore) {
- cookbookStore.value = data.items;
- }
-
- loading.value = false;
- },
- async createOne(name: string | null = null) {
- loading.value = true;
- const { data } = await api.cookbooks.createOne({
- name: name || i18n.t("cookbook.household-cookbook-name", [household.value?.name || "", String((cookbookStore?.value?.length ?? 0) + 1)]) as string,
- position: (cookbookStore?.value?.length ?? 0) + 1,
- queryFilterString: "",
- });
- if (data && cookbookStore?.value) {
- cookbookStore.value.push(data);
- } else {
- this.refreshAll();
- }
-
- loading.value = false;
- return data;
- },
- async updateOne(updateData: UpdateCookBook) {
- if (!updateData.id) {
- return;
- }
-
- loading.value = true;
- const { data } = await api.cookbooks.updateOne(updateData.id, updateData);
- if (data && cookbookStore?.value) {
- this.refreshAll();
- }
- loading.value = false;
- return data;
- },
-
- async updateOrder(cookbooks: ReadCookBook[]) {
- if (!cookbooks?.length) {
- return;
- }
-
- loading.value = true;
-
- cookbooks.forEach((element, index) => {
- element.position = index + 1;
- });
-
- const { data } = await api.cookbooks.updateAll(cookbooks);
-
- if (data && cookbookStore?.value) {
- this.refreshAll();
- }
-
- loading.value = true;
- },
- async deleteOne(id: string | number) {
- loading.value = true;
- const { data } = await api.cookbooks.deleteOne(id);
- if (data && cookbookStore?.value) {
- this.refreshAll();
- }
- },
- flushStore() {
- cookbookStore = null;
- },
- };
-
- if (!cookbookStore) {
- cookbookStore = actions.getAll();
- }
-
- return { cookbooks: cookbookStore, actions };
-};
diff --git a/frontend/composables/use-group-mealplan.ts b/frontend/composables/use-group-mealplan.ts
index 171450395..f8dcb402f 100644
--- a/frontend/composables/use-group-mealplan.ts
+++ b/frontend/composables/use-group-mealplan.ts
@@ -1,27 +1,26 @@
-import { useAsync, ref, Ref, watch, useContext } from "@nuxtjs/composition-api";
import { format } from "date-fns";
import { useAsyncKey } from "./use-utils";
import { useUserApi } from "~/composables/api";
-import { CreatePlanEntry, PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
+import type { CreatePlanEntry, PlanEntryType, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
type PlanOption = {
text: string;
value: PlanEntryType;
};
export function usePlanTypeOptions() {
- const { i18n } = useContext();
+ const i18n = useI18n();
return [
- { text: i18n.tc("meal-plan.breakfast"), value: "breakfast" },
- { text: i18n.tc("meal-plan.lunch"), value: "lunch" },
- { text: i18n.tc("meal-plan.dinner"), value: "dinner" },
- { text: i18n.tc("meal-plan.side"), value: "side" },
+ { text: i18n.t("meal-plan.breakfast"), value: "breakfast" },
+ { text: i18n.t("meal-plan.lunch"), value: "lunch" },
+ { text: i18n.t("meal-plan.dinner"), value: "dinner" },
+ { text: i18n.t("meal-plan.side"), value: "side" },
] as PlanOption[];
}
export function getEntryTypeText(value: PlanEntryType) {
- const { i18n } = useContext();
- return i18n.tc("meal-plan." + value);
+ const i18n = useI18n();
+ return i18n.t("meal-plan." + value);
}
export interface DateRange {
start: Date;
@@ -36,7 +35,7 @@ export const useMealplans = function (range: Ref) {
const actions = {
getAll() {
loading.value = true;
- const units = useAsync(async () => {
+ const { data: units } = useAsyncData(useAsyncKey(), async () => {
const query = {
start_date: format(range.value.start, "yyyy-MM-dd"),
end_date: format(range.value.end, "yyyy-MM-dd"),
@@ -45,15 +44,16 @@ export const useMealplans = function (range: Ref) {
if (data) {
return data.items;
- } else {
+ }
+ else {
return null;
}
- }, useAsyncKey());
+ });
loading.value = false;
return units;
},
- async refreshAll(this: void) {
+ async refreshAll() {
loading.value = true;
const query = {
start_date: format(range.value.start, "yyyy-MM-dd"),
diff --git a/frontend/composables/use-group-recipe-actions.ts b/frontend/composables/use-group-recipe-actions.ts
index d1c5171e7..8e3aa5a09 100644
--- a/frontend/composables/use-group-recipe-actions.ts
+++ b/frontend/composables/use-group-recipe-actions.ts
@@ -1,9 +1,9 @@
-import { computed, reactive, ref } from "@nuxtjs/composition-api";
import { useStoreActions } from "./partials/use-actions-factory";
import { useUserApi } from "~/composables/api";
-import { GroupRecipeActionOut, GroupRecipeActionType } from "~/lib/api/types/household";
-import { RequestResponse } from "~/lib/api/types/non-generated";
-import { Recipe } from "~/lib/api/types/recipe";
+import { useScaledAmount } from "~/composables/recipes/use-scaled-amount";
+import type { GroupRecipeActionOut, GroupRecipeActionType } from "~/lib/api/types/household";
+import type { RequestResponse } from "~/lib/api/types/non-generated";
+import type { Recipe } from "~/lib/api/types/recipe";
const groupRecipeActions = ref(null);
const loading = ref(false);
@@ -30,8 +30,8 @@ export function useGroupRecipeActionData() {
}
export const useGroupRecipeActions = function (
- orderBy: string | null = "title",
- orderDirection: string | null = "asc",
+ orderBy: string | null = "title",
+ orderDirection: string | null = "asc",
) {
const api = useUserApi();
@@ -50,17 +50,16 @@ export const useGroupRecipeActions = function (
const recipeServings = (recipe.recipeServings || 1) * recipeScale;
const recipeYieldQuantity = (recipe.recipeYieldQuantity || 1) * recipeScale;
- /* eslint-disable no-template-curly-in-string */
return url
.replace("${url}", window.location.href)
.replace("${id}", recipe.id || "")
.replace("${slug}", recipe.slug || "")
.replace("${servings}", recipeServings.toString())
.replace("${yieldQuantity}", recipeYieldQuantity.toString())
- .replace("${yieldText}", recipe.recipeYield || "")
- /* eslint-enable no-template-curly-in-string */
+ .replace("${yieldText}", recipe.recipeYield || "");
};
+ // eslint-disable-next-line @typescript-eslint/no-invalid-void-type
async function execute(action: GroupRecipeActionOut, recipe: Recipe, recipeScale: number): Promise> {
const url = parseRecipeActionUrl(action.url, recipe, recipeScale);
@@ -69,7 +68,7 @@ export const useGroupRecipeActions = function (
window.open(url, "_blank")?.focus();
return;
case "post":
- return await api.groupRecipeActions.triggerAction(action.id, recipe.slug || "");
+ return await api.groupRecipeActions.triggerAction(action.id, recipe.slug || "", useScaledAmount(recipe.recipeServings || 1, recipeScale).scaledAmount);
default:
break;
}
@@ -83,8 +82,8 @@ export const useGroupRecipeActions = function (
...useStoreActions(api.groupRecipeActions, groupRecipeActions, loading),
flushStore() {
groupRecipeActions.value = [];
- }
- }
+ },
+ };
return {
actions,
diff --git a/frontend/composables/use-group-webhooks.ts b/frontend/composables/use-group-webhooks.ts
index 7c5cf376a..60fdafd24 100644
--- a/frontend/composables/use-group-webhooks.ts
+++ b/frontend/composables/use-group-webhooks.ts
@@ -1,7 +1,6 @@
-import { useAsync, ref } from "@nuxtjs/composition-api";
import { useAsyncKey } from "./use-utils";
import { useUserApi } from "~/composables/api";
-import { ReadWebhook } from "~/lib/api/types/household";
+import type { ReadWebhook } from "~/lib/api/types/household";
export const useGroupWebhooks = function () {
const api = useUserApi();
@@ -11,15 +10,16 @@ export const useGroupWebhooks = function () {
const actions = {
getAll() {
loading.value = true;
- const units = useAsync(async () => {
+ const { data: units } = useAsyncData(useAsyncKey(), async () => {
const { data } = await api.groupWebhooks.getAll();
if (data) {
return data.items;
- } else {
+ }
+ else {
return null;
}
- }, useAsyncKey());
+ });
loading.value = false;
return units;
@@ -91,7 +91,7 @@ export const useGroupWebhooks = function () {
loading.value = true;
await api.groupWebhooks.testOne(id);
loading.value = false;
- }
+ },
};
const webhooks = actions.getAll();
diff --git a/frontend/composables/use-groups.ts b/frontend/composables/use-groups.ts
index c6fed25e3..7a84d2305 100644
--- a/frontend/composables/use-groups.ts
+++ b/frontend/composables/use-groups.ts
@@ -1,6 +1,5 @@
-import { useAsync, ref } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
-import { GroupBase, GroupSummary } from "~/lib/api/types/user";
+import type { GroupBase, GroupSummary } from "~/lib/api/types/user";
const groupSelfRef = ref(null);
const loading = ref(false);
@@ -50,15 +49,16 @@ export const useGroups = function () {
function getAllGroups() {
loading.value = true;
const asyncKey = String(Date.now());
- const groups = useAsync(async () => {
- const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});;
+ const { data: groups } = useAsyncData(asyncKey, async () => {
+ const { data } = await api.groups.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); ;
if (data) {
return data.items;
- } else {
+ }
+ else {
return null;
}
- }, asyncKey);
+ });
loading.value = false;
return groups;
@@ -66,11 +66,12 @@ export const useGroups = function () {
async function refreshAllGroups() {
loading.value = true;
- const { data } = await api.groups.getAll(1, -1, {orderBy: "name", orderDirection: "asc"});;
+ const { data } = await api.groups.getAll(1, -1, { orderBy: "name", orderDirection: "asc" }); ;
if (data) {
groups.value = data.items;
- } else {
+ }
+ else {
groups.value = null;
}
diff --git a/frontend/composables/use-households.ts b/frontend/composables/use-households.ts
index c22d04422..2e7d53a5b 100644
--- a/frontend/composables/use-households.ts
+++ b/frontend/composables/use-households.ts
@@ -1,6 +1,5 @@
-import { computed, ref, Ref, useAsync } from "@nuxtjs/composition-api";
import { useAdminApi, useUserApi } from "~/composables/api";
-import { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household";
+import type { HouseholdCreate, HouseholdInDB } from "~/lib/api/types/household";
const householdSelfRef = ref(null);
const loading = ref(false);
@@ -53,15 +52,16 @@ export const useAdminHouseholds = function () {
function getAllHouseholds() {
loading.value = true;
const asyncKey = String(Date.now());
- const households = useAsync(async () => {
- const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"});
+ const { data: households } = useAsyncData(asyncKey, async () => {
+ const { data } = await api.households.getAll(1, -1, { orderBy: "name, group.name", orderDirection: "asc" });
if (data) {
return data.items;
- } else {
+ }
+ else {
return null;
}
- }, asyncKey);
+ });
loading.value = false;
return households;
@@ -69,12 +69,13 @@ export const useAdminHouseholds = function () {
async function refreshAllHouseholds() {
loading.value = true;
- const { data } = await api.households.getAll(1, -1, {orderBy: "name, group.name", orderDirection: "asc"});;
+ const { data } = await api.households.getAll(1, -1, { orderBy: "name, group.name", orderDirection: "asc" }); ;
if (data) {
households.value = data.items;
- } else {
- households.value = null;
+ }
+ else {
+ households.value = null;
}
loading.value = false;
@@ -93,7 +94,7 @@ export const useAdminHouseholds = function () {
const { data } = await api.households.createOne(payload);
if (data && households.value) {
- households.value.push(data);
+ households.value.push(data);
}
}
@@ -102,8 +103,8 @@ export const useAdminHouseholds = function () {
return computed(
() => {
return (households.value && groupIdRef.value)
- ? households.value.filter((h) => h.groupId === groupIdRef.value)
- : [];
+ ? households.value.filter(h => h.groupId === groupIdRef.value)
+ : [];
},
);
}
diff --git a/frontend/composables/use-locales/available-locales.ts b/frontend/composables/use-locales/available-locales.ts
index cf04d7fae..a5ff2794f 100644
--- a/frontend/composables/use-locales/available-locales.ts
+++ b/frontend/composables/use-locales/available-locales.ts
@@ -246,4 +246,4 @@ export const LOCALES = [
progress: 90,
dir: "ltr",
},
-]
+];
diff --git a/frontend/composables/use-locales/use-locales.ts b/frontend/composables/use-locales/use-locales.ts
index f710260ff..6dcf27851 100644
--- a/frontend/composables/use-locales/use-locales.ts
+++ b/frontend/composables/use-locales/use-locales.ts
@@ -1,39 +1,29 @@
-import { computed, useContext } from "@nuxtjs/composition-api";
+import type { LocaleObject } from "@nuxtjs/i18n";
import { LOCALES } from "./available-locales";
export const useLocales = () => {
- const { i18n, $vuetify } = useContext();
+ const i18n = useI18n();
- function getLocale(value: string) {
- const currentLocale = LOCALES.filter((locale) => locale.value === value);
- return currentLocale.length ? currentLocale[0] : null;
- }
+ const { isRtl } = useRtl();
+ const { current: vuetifyLocale } = useLocale();
- const locale = computed({
- get() {
- // dirty hack
- $vuetify.lang.current = i18n.locale;
- const currentLocale = getLocale(i18n.locale);
- if (currentLocale) {
- $vuetify.rtl = currentLocale.dir === "rtl";
- }
-
- return i18n.locale;
- },
+ const locale = computed({
+ get: () => i18n.locale.value,
set(value) {
i18n.setLocale(value);
-
- // this does not persist after window reload :-(
- $vuetify.lang.current = value;
- const currentLocale = getLocale(value);
- if (currentLocale) {
- $vuetify.rtl = currentLocale.dir === "rtl";
- }
-
- // Reload the page to update the language - not all strings are reactive
- window.location.reload();
},
});
+ // auto update vuetify locale
+ watch(locale, (lc) => {
+ vuetifyLocale.value = lc;
+ });
+ // auto update rtl
+ watch(vuetifyLocale, (vl) => {
+ const currentLocale = LOCALES.find(lc => lc.value === vl);
+ if (currentLocale) {
+ isRtl.value = currentLocale.dir === "rtl";
+ }
+ });
return {
locale,
diff --git a/frontend/composables/use-logged-in-state.ts b/frontend/composables/use-logged-in-state.ts
index 878fe6925..c4d6078b0 100644
--- a/frontend/composables/use-logged-in-state.ts
+++ b/frontend/composables/use-logged-in-state.ts
@@ -1,17 +1,16 @@
-import { computed, useContext, useRoute } from "@nuxtjs/composition-api";
-
export const useLoggedInState = function () {
- const { $auth } = useContext();
+ const $auth = useMealieAuth();
const route = useRoute();
- const loggedIn = computed(() => $auth.loggedIn);
+ const loggedIn = computed(() => $auth.loggedIn.value);
const isOwnGroup = computed(() => {
- if (!route.value.params.groupSlug) {
+ if (!route.params.groupSlug) {
return loggedIn.value;
- } else {
- return loggedIn.value && $auth.user?.groupSlug === route.value.params.groupSlug;
+ }
+ else {
+ return loggedIn.value && $auth.user.value?.groupSlug === route.params.groupSlug;
}
});
return { loggedIn, isOwnGroup };
-}
+};
diff --git a/frontend/composables/use-navigation-warning.ts b/frontend/composables/use-navigation-warning.ts
index 85cf0b85e..3f093bb91 100644
--- a/frontend/composables/use-navigation-warning.ts
+++ b/frontend/composables/use-navigation-warning.ts
@@ -1,5 +1,5 @@
export function useNavigationWarning() {
- return { activateNavigationWarning, deactivateNavigationWarning };
+ return { activateNavigationWarning, deactivateNavigationWarning };
}
/**
@@ -9,12 +9,12 @@ export function useNavigationWarning() {
* or closing the tab.
*/
const activateNavigationWarning = () => {
- window.onbeforeunload = () => true;
-}
+ window.onbeforeunload = () => true;
+};
/**
* Disables the warning when navigating to a page
*/
const deactivateNavigationWarning = () => {
- window.onbeforeunload = null;
-}
+ window.onbeforeunload = null;
+};
diff --git a/frontend/composables/use-passwords.test.ts b/frontend/composables/use-passwords.test.ts
index 0b1c7329a..9060a60f0 100644
--- a/frontend/composables/use-passwords.test.ts
+++ b/frontend/composables/use-passwords.test.ts
@@ -1,15 +1,14 @@
-import { ref } from "@nuxtjs/composition-api";
+import { ref } from "vue";
import { describe, expect, test } from "vitest";
import { usePasswordStrength } from "./use-passwords";
import { stubI18n } from "~/tests/utils";
-
describe("test usePasswordStrength", () => {
test("weak password", () => {
const pw = ref("123456");
const result = usePasswordStrength(pw, stubI18n());
- const { score, strength, color } = result
+ const { score, strength, color } = result;
expect(score.value).toBeGreaterThan(0);
expect(score.value).toBeLessThan(40);
diff --git a/frontend/composables/use-passwords.ts b/frontend/composables/use-passwords.ts
index e0bc94edc..efc3f8b06 100644
--- a/frontend/composables/use-passwords.ts
+++ b/frontend/composables/use-passwords.ts
@@ -1,10 +1,10 @@
-import { computed, Ref, ref, useContext } from "@nuxtjs/composition-api";
-import VueI18n from "vue-i18n";
+import { computed } from "vue";
+import type { VueI18n } from "vue-i18n";
import { scorePassword } from "~/lib/validators";
export function usePasswordField() {
const show = ref(false);
- const { $globals } = useContext();
+ const { $globals } = useNuxtApp();
const passwordIcon = computed(() => {
return show.value ? $globals.icons.eyeOff : $globals.icons.eye;
@@ -26,24 +26,30 @@ export const usePasswordStrength = (password: Ref, i18n: VueI18n) => {
const score = computed(() => scorePassword(password.value));
const strength = computed(() => {
if (score.value < 50) {
- return i18n.tc("user.password-strength-values.weak");
- } else if (score.value < 80) {
- return i18n.tc("user.password-strength-values.good");
- } else if (score.value < 100) {
- return i18n.tc("user.password-strength-values.strong");
- } else {
- return i18n.tc("user.password-strength-values.very-strong");
+ return i18n.t("user.password-strength-values.weak");
+ }
+ else if (score.value < 80) {
+ return i18n.t("user.password-strength-values.good");
+ }
+ else if (score.value < 100) {
+ return i18n.t("user.password-strength-values.strong");
+ }
+ else {
+ return i18n.t("user.password-strength-values.very-strong");
}
});
const color = computed(() => {
if (score.value < 50) {
return "error";
- } else if (score.value < 80) {
+ }
+ else if (score.value < 80) {
return "warning";
- } else if (score.value < 100) {
+ }
+ else if (score.value < 100) {
return "info";
- } else {
+ }
+ else {
return "success";
}
});
diff --git a/frontend/composables/use-query-filter-builder.ts b/frontend/composables/use-query-filter-builder.ts
index 30c40cdd0..88dcc1067 100644
--- a/frontend/composables/use-query-filter-builder.ts
+++ b/frontend/composables/use-query-filter-builder.ts
@@ -1,6 +1,5 @@
-import { computed, useContext } from "@nuxtjs/composition-api";
-import { Organizer, RecipeOrganizer } from "~/lib/api/types/non-generated";
-import { LogicalOperator, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
+import { Organizer, type RecipeOrganizer } from "~/lib/api/types/non-generated";
+import type { LogicalOperator, RelationalKeyword, RelationalOperator } from "~/lib/api/types/response";
export interface FieldLogicalOperator {
label: string;
@@ -60,16 +59,16 @@ export interface Field extends FieldDefinition {
}
export function useQueryFilterBuilder() {
- const { i18n } = useContext();
+ const i18n = useI18n();
const logOps = computed>(() => {
const AND = {
- label: i18n.tc("query-filter.logical-operators.and"),
+ label: i18n.t("query-filter.logical-operators.and"),
value: "AND",
} as FieldLogicalOperator;
const OR = {
- label: i18n.tc("query-filter.logical-operators.or"),
+ label: i18n.t("query-filter.logical-operators.or"),
value: "OR",
} as FieldLogicalOperator;
@@ -81,71 +80,70 @@ export function useQueryFilterBuilder() {
const relOps = computed>(() => {
const EQ = {
- label: i18n.tc("query-filter.relational-operators.equals"),
+ label: i18n.t("query-filter.relational-operators.equals"),
value: "=",
} as FieldRelationalOperator;
const NOT_EQ = {
- label: i18n.tc("query-filter.relational-operators.does-not-equal"),
+ label: i18n.t("query-filter.relational-operators.does-not-equal"),
value: "<>",
} as FieldRelationalOperator;
const GT = {
- label: i18n.tc("query-filter.relational-operators.is-greater-than"),
+ label: i18n.t("query-filter.relational-operators.is-greater-than"),
value: ">",
} as FieldRelationalOperator;
const GTE = {
- label: i18n.tc("query-filter.relational-operators.is-greater-than-or-equal-to"),
+ label: i18n.t("query-filter.relational-operators.is-greater-than-or-equal-to"),
value: ">=",
} as FieldRelationalOperator;
const LT = {
- label: i18n.tc("query-filter.relational-operators.is-less-than"),
+ label: i18n.t("query-filter.relational-operators.is-less-than"),
value: "<",
} as FieldRelationalOperator;
const LTE = {
- label: i18n.tc("query-filter.relational-operators.is-less-than-or-equal-to"),
+ label: i18n.t("query-filter.relational-operators.is-less-than-or-equal-to"),
value: "<=",
} as FieldRelationalOperator;
const IS = {
- label: i18n.tc("query-filter.relational-keywords.is"),
+ label: i18n.t("query-filter.relational-keywords.is"),
value: "IS",
} as FieldRelationalOperator;
const IS_NOT = {
- label: i18n.tc("query-filter.relational-keywords.is-not"),
+ label: i18n.t("query-filter.relational-keywords.is-not"),
value: "IS NOT",
} as FieldRelationalOperator;
const IN = {
- label: i18n.tc("query-filter.relational-keywords.is-one-of"),
+ label: i18n.t("query-filter.relational-keywords.is-one-of"),
value: "IN",
} as FieldRelationalOperator;
const NOT_IN = {
- label: i18n.tc("query-filter.relational-keywords.is-not-one-of"),
+ label: i18n.t("query-filter.relational-keywords.is-not-one-of"),
value: "NOT IN",
} as FieldRelationalOperator;
const CONTAINS_ALL = {
- label: i18n.tc("query-filter.relational-keywords.contains-all-of"),
+ label: i18n.t("query-filter.relational-keywords.contains-all-of"),
value: "CONTAINS ALL",
} as FieldRelationalOperator;
const LIKE = {
- label: i18n.tc("query-filter.relational-keywords.is-like"),
+ label: i18n.t("query-filter.relational-keywords.is-like"),
value: "LIKE",
} as FieldRelationalOperator;
const NOT_LIKE = {
- label: i18n.tc("query-filter.relational-keywords.is-not-like"),
+ label: i18n.t("query-filter.relational-keywords.is-not-like"),
value: "NOT LIKE",
} as FieldRelationalOperator;
- /* eslint-disable object-shorthand */
return {
"=": EQ,
"<>": NOT_EQ,
@@ -161,22 +159,20 @@ export function useQueryFilterBuilder() {
"LIKE": LIKE,
"NOT LIKE": NOT_LIKE,
};
- /* eslint-enable object-shorthand */
});
function isOrganizerType(type: FieldType): type is Organizer {
return (
- type === Organizer.Category ||
- type === Organizer.Tag ||
- type === Organizer.Tool ||
- type === Organizer.Food ||
- type === Organizer.Household
+ type === Organizer.Category
+ || type === Organizer.Tag
+ || type === Organizer.Tool
+ || type === Organizer.Food
+ || type === Organizer.Household
);
};
function getFieldFromFieldDef(field: Field | FieldDefinition, resetValue = false): Field {
- /* eslint-disable dot-notation */
- const updatedField = {logicalOperator: logOps.value.AND, ...field} as Field;
+ const updatedField = { logicalOperator: logOps.value.AND, ...field } as Field;
let operatorOptions: FieldRelationalOperator[];
if (updatedField.fieldOptions?.length || isOrganizerType(updatedField.type)) {
operatorOptions = [
@@ -184,7 +180,8 @@ export function useQueryFilterBuilder() {
relOps.value["NOT IN"],
relOps.value["CONTAINS ALL"],
];
- } else {
+ }
+ else {
switch (updatedField.type) {
case "string":
operatorOptions = [
@@ -209,7 +206,7 @@ export function useQueryFilterBuilder() {
break;
case "date":
operatorOptions = [
- relOps.value["="],
+ relOps.value["="],
relOps.value["<>"],
relOps.value[">"],
relOps.value[">="],
@@ -230,14 +227,14 @@ export function useQueryFilterBuilder() {
updatedField.value = "";
updatedField.values = [];
updatedField.organizers = [];
- } else {
+ }
+ else {
updatedField.value = updatedField.value || "";
updatedField.values = updatedField.values || [];
updatedField.organizers = updatedField.organizers || [];
}
return updatedField;
- /* eslint-enable dot-notation */
};
function buildQueryFilterString(fields: Field[], useParenthesis: boolean): string {
@@ -261,13 +258,15 @@ export function useQueryFilterBuilder() {
if (field.label) {
parts.push(field.name);
- } else {
+ }
+ else {
isValid = false;
}
if (field.relationalOperatorValue) {
parts.push(field.relationalOperatorValue.value);
- } else if (field.type !== "boolean") {
+ }
+ else if (field.type !== "boolean") {
isValid = false;
}
@@ -275,23 +274,29 @@ export function useQueryFilterBuilder() {
if (field.values?.length) {
let val: string;
if (field.type === "string" || field.type === "date" || isOrganizerType(field.type)) {
- val = field.values.map((value) => `"${value.toString()}"`).join(",");
- } else {
+ val = field.values.map(value => `"${value.toString()}"`).join(",");
+ }
+ else {
val = field.values.join(",");
}
parts.push(`[${val}]`);
- } else {
+ }
+ else {
isValid = false;
}
- } else if (field.value) {
+ }
+ else if (field.value) {
if (field.type === "string" || field.type === "date") {
parts.push(`"${field.value.toString()}"`);
- } else {
+ }
+ else {
parts.push(field.value.toString());
}
- } else if (field.type === "boolean") {
+ }
+ else if (field.type === "boolean") {
parts.push("false");
- } else {
+ }
+ else {
isValid = false;
}
diff --git a/frontend/composables/use-router.ts b/frontend/composables/use-router.ts
index 39ad07490..dfbd0fe84 100644
--- a/frontend/composables/use-router.ts
+++ b/frontend/composables/use-router.ts
@@ -1,5 +1,3 @@
-import { useRoute, WritableComputedRef, computed, nextTick, useRouter } from "@nuxtjs/composition-api";
-
export function useRouterQuery(query: string) {
const router = useRoute();
// TODO FUTURE: Remove when migrating to Vue 3
@@ -7,11 +5,10 @@ export function useRouterQuery(query: string) {
const param: WritableComputedRef = computed({
get(): string {
console.log("Get Query Change");
- // @ts-ignore For some reason, this could also return an array
- return router.value?.query[query] || "";
+ return router?.query[query] as string || "";
},
set(v: string): void {
- router.value.query[query] = v;
+ router.query[query] = v;
},
});
@@ -24,13 +21,13 @@ export function useRouteQuery(name: string, default
return computed({
get() {
- const data = route.value.query[name];
+ const data = route.query[name];
if (data == null) return defaultValue ?? null;
return data;
},
set(v) {
nextTick(() => {
- router.replace({ query: { ...route.value.query, [name]: v } });
+ router.replace({ query: { ...route.query, [name]: v } });
});
},
});
diff --git a/frontend/composables/use-setup/common-settings-form.ts b/frontend/composables/use-setup/common-settings-form.ts
index 12b3a7496..15c344e09 100644
--- a/frontend/composables/use-setup/common-settings-form.ts
+++ b/frontend/composables/use-setup/common-settings-form.ts
@@ -1,30 +1,29 @@
-import { useContext } from "@nuxtjs/composition-api";
import { fieldTypes } from "../forms";
-import { AutoFormItems } from "~/types/auto-forms";
+import type { AutoFormItems } from "~/types/auto-forms";
export const useCommonSettingsForm = () => {
- const { i18n } = useContext();
+ const i18n = useI18n();
- const commonSettingsForm: AutoFormItems = [
- {
- section: i18n.tc("profile.group-settings"),
- label: i18n.tc("group.enable-public-access"),
- hint: i18n.tc("group.enable-public-access-description"),
- varName: "makeGroupRecipesPublic",
- type: fieldTypes.BOOLEAN,
- rules: ["required"],
- },
- {
- section: i18n.tc("data-pages.data-management"),
- label: i18n.tc("user-registration.use-seed-data"),
- hint: i18n.tc("user-registration.use-seed-data-description"),
- varName: "useSeedData",
- type: fieldTypes.BOOLEAN,
- rules: ["required"],
- },
- ];
+ const commonSettingsForm: AutoFormItems = [
+ {
+ section: i18n.t("profile.group-settings"),
+ label: i18n.t("group.enable-public-access"),
+ hint: i18n.t("group.enable-public-access-description"),
+ varName: "makeGroupRecipesPublic",
+ type: fieldTypes.BOOLEAN,
+ rules: ["required"],
+ },
+ {
+ section: i18n.t("data-pages.data-management"),
+ label: i18n.t("user-registration.use-seed-data"),
+ hint: i18n.t("user-registration.use-seed-data-description"),
+ varName: "useSeedData",
+ type: fieldTypes.BOOLEAN,
+ rules: ["required"],
+ },
+ ];
- return {
- commonSettingsForm,
- }
-}
+ return {
+ commonSettingsForm,
+ };
+};
diff --git a/frontend/composables/use-shopping-list-item-actions.ts b/frontend/composables/use-shopping-list-item-actions.ts
index ccc683fe7..b3fc207a0 100644
--- a/frontend/composables/use-shopping-list-item-actions.ts
+++ b/frontend/composables/use-shopping-list-item-actions.ts
@@ -1,11 +1,10 @@
-import { computed, reactive, watch } from "@nuxtjs/composition-api";
-import { useLocalStorage } from "@vueuse/core";
+import { useLocalStorage, useOnline } from "@vueuse/core";
import { useUserApi } from "~/composables/api";
-import { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/household";
-import { RequestResponse } from "~/lib/api/types/non-generated";
+import type { ShoppingListItemOut, ShoppingListOut } from "~/lib/api/types/household";
+import type { RequestResponse } from "~/lib/api/types/non-generated";
const localStorageKey = "shopping-list-queue";
-const queueTimeout = 5 * 60 * 1000; // 5 minutes
+const queueTimeout = 5 * 60 * 1000; // 5 minutes
type ItemQueueType = "create" | "update" | "delete";
@@ -22,6 +21,7 @@ interface Storage {
}
export function useShoppingListItemActions(shoppingListId: string) {
+ const isOnline = useOnline();
const api = useUserApi();
const storage = useLocalStorage(localStorageKey, {} as Storage, { deep: true });
const queue = reactive(getQueue());
@@ -30,17 +30,17 @@ export function useShoppingListItemActions(shoppingListId: string) {
queue.lastUpdate = Date.now();
}
- storage.value[shoppingListId] = { ...queue }
+ storage.value[shoppingListId] = { ...queue };
watch(
() => queue,
(value) => {
- storage.value[shoppingListId] = { ...value }
+ storage.value[shoppingListId] = { ...value };
},
{
deep: true,
immediate: true,
},
- )
+ );
function isValidQueueObject(obj: any): obj is ShoppingListQueue {
if (typeof obj !== "object" || obj === null) {
@@ -53,7 +53,7 @@ export function useShoppingListItemActions(shoppingListId: string) {
}
const arraysValid = Array.isArray(obj.create) && Array.isArray(obj.update) && Array.isArray(obj.delete);
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+
const lastUpdateValid = typeof obj.lastUpdate === "number" && !isNaN(new Date(obj.lastUpdate).getTime());
return arraysValid && lastUpdateValid;
@@ -70,10 +70,12 @@ export function useShoppingListItemActions(shoppingListId: string) {
if (!isValidQueueObject(fetchedQueue)) {
console.log("Invalid queue object in local storage; resetting queue.");
return createEmptyQueue();
- } else {
+ }
+ else {
return fetchedQueue;
}
- } catch (error) {
+ }
+ catch (error) {
console.log("Error validating queue object in local storage; resetting queue.", error);
return createEmptyQueue();
}
@@ -91,29 +93,30 @@ export function useShoppingListItemActions(shoppingListId: string) {
function mergeListItemsByLatest(
list1: ShoppingListItemOut[],
- list2: ShoppingListItemOut[]
+ list2: ShoppingListItemOut[],
) {
const mergedList = [...list1];
list2.forEach((list2Item) => {
- const conflictingItem = mergedList.find((item) => item.id === list2Item.id)
- if (conflictingItem &&
- list2Item.updatedAt && conflictingItem.updatedAt &&
- list2Item.updatedAt > conflictingItem.updatedAt) {
- mergedList.splice(mergedList.indexOf(conflictingItem), 1, list2Item)
- } else if (!conflictingItem) {
- mergedList.push(list2Item)
+ const conflictingItem = mergedList.find(item => item.id === list2Item.id);
+ if (conflictingItem
+ && list2Item.updatedAt && conflictingItem.updatedAt
+ && list2Item.updatedAt > conflictingItem.updatedAt) {
+ mergedList.splice(mergedList.indexOf(conflictingItem), 1, list2Item);
}
- })
- return mergedList
+ else if (!conflictingItem) {
+ mergedList.push(list2Item);
+ }
+ });
+ return mergedList;
}
async function getList() {
const response = await api.shopping.lists.getOne(shoppingListId);
- if (window.$nuxt.isOffline && response.data) {
+ if (!isOnline.value && response.data) {
const createAndUpdateQueues = mergeListItemsByLatest(queue.update, queue.create);
response.data.listItems = mergeListItemsByLatest(response.data.listItems ?? [], createAndUpdateQueues);
}
- return response.data
+ return response.data;
}
function createItem(item: ShoppingListItemOut) {
@@ -174,8 +177,8 @@ export function useShoppingListItemActions(shoppingListId: string) {
}
/**
- * Processes the queue items and returns whether the processing was successful.
- */
+ * Processes the queue items and returns whether the processing was successful.
+ */
async function processQueueItems(
action: (items: ShoppingListItemOut[]) => Promise>,
itemQueueType: ItemQueueType,
@@ -186,7 +189,8 @@ export function useShoppingListItemActions(shoppingListId: string) {
if (!queueItems.length) {
return true;
}
- } catch (error) {
+ }
+ catch (error) {
console.log(`Error fetching queue items of type ${itemQueueType}:`, error);
clearQueueItems(itemQueueType);
return false;
@@ -196,11 +200,12 @@ export function useShoppingListItemActions(shoppingListId: string) {
const itemsToProcess = [...queueItems];
await action(itemsToProcess)
.then(() => {
- if (window.$nuxt.isOnline) {
+ if (isOnline.value) {
clearQueueItems(itemQueueType, itemsToProcess.map(item => item.id));
}
});
- } catch (error) {
+ }
+ catch (error) {
console.log(`Error processing queue items of type ${itemQueueType}:`, error);
clearQueueItems(itemQueueType);
return false;
@@ -224,13 +229,13 @@ export function useShoppingListItemActions(shoppingListId: string) {
// We send each bulk request one at a time, since the backend may merge items
// "failures" here refers to an actual error, rather than failing to reach the backend
let failures = 0;
- if (!(await processQueueItems((items) => api.shopping.items.deleteMany(items), "delete"))) failures++;
- if (!(await processQueueItems((items) => api.shopping.items.updateMany(items), "update"))) failures++;
- if (!(await processQueueItems((items) => api.shopping.items.createMany(items), "create"))) failures++;
+ if (!(await processQueueItems(items => api.shopping.items.deleteMany(items), "delete"))) failures++;
+ if (!(await processQueueItems(items => api.shopping.items.updateMany(items), "update"))) failures++;
+ if (!(await processQueueItems(items => api.shopping.items.createMany(items), "create"))) failures++;
// If we're online, or the queue is empty, the queue is fully processed, so we're up to date
// Otherwise, if all three queue processes failed, we've already reset the queue, so we need to reset the date
- if (window.$nuxt.isOnline || queueEmpty.value || failures === 3) {
+ if (isOnline.value || queueEmpty.value || failures === 3) {
queue.lastUpdate = Date.now();
}
}
diff --git a/frontend/composables/use-text-color.ts b/frontend/composables/use-text-color.ts
index 1abddde93..51f8233f0 100644
--- a/frontend/composables/use-text-color.ts
+++ b/frontend/composables/use-text-color.ts
@@ -1,4 +1,3 @@
-// @ts-ignore missing color types
import Color from "@sphinxxxx/color-conversion";
const LIGHT_COLOR = "white";
@@ -32,7 +31,8 @@ export function getTextColor(bgColor: string | undefined): string {
});
const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
return L > ACCESSIBILITY_THRESHOLD ? DARK_COLOR : LIGHT_COLOR;
- } catch (error) {
+ }
+ catch (error) {
console.warn(error);
return DARK_COLOR;
}
diff --git a/frontend/composables/use-toast.ts b/frontend/composables/use-toast.ts
index 96cfb5746..efd79cc41 100644
--- a/frontend/composables/use-toast.ts
+++ b/frontend/composables/use-toast.ts
@@ -1,5 +1,3 @@
-import { reactive } from "@nuxtjs/composition-api";
-
interface Toast {
open: boolean;
text: string;
diff --git a/frontend/composables/use-user.ts b/frontend/composables/use-user.ts
index e7b44c66b..b4c2485a3 100644
--- a/frontend/composables/use-user.ts
+++ b/frontend/composables/use-user.ts
@@ -1,6 +1,5 @@
-import { useAsync, ref } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
-import { UserIn, UserOut } from "~/lib/api/types/user";
+import type { UserIn, UserOut } from "~/lib/api/types/user";
/*
TODO: Potentially combine useAllUsers and useUser by delaying the get all users functionality
@@ -10,38 +9,16 @@ to control whether the object is substantiated... but some of the others rely on
export const useAllUsers = function () {
const api = useUserApi();
- const loading = ref(false);
-
- function getAllUsers() {
- loading.value = true;
- const asyncKey = String(Date.now());
- const allUsers = useAsync(async () => {
- const { data } = await api.users.getAll();
- if (data) {
- return data.items;
- } else {
- return null;
- }
- }, asyncKey);
-
- loading.value = false;
- return allUsers;
- }
-
- async function refreshAllUsers() {
- loading.value = true;
+ const asyncKey = String(Date.now());
+ const { data: users, refresh: refreshAllUsers } = useLazyAsyncData(asyncKey, async () => {
const { data } = await api.users.getAll();
-
if (data) {
- users.value = data.items;
- } else {
- users.value = null;
+ return data.items;
}
-
- loading.value = false;
- }
-
- const users = getAllUsers();
+ else {
+ return null;
+ }
+ });
return { users, refreshAllUsers };
};
@@ -52,10 +29,10 @@ export const useUser = function (refreshFunc: CallableFunction | null = null) {
function getUser(id: string) {
loading.value = true;
- const user = useAsync(async () => {
+ const user = useAsyncData(id, async () => {
const { data } = await api.users.getOne(id);
return data;
- }, id);
+ });
loading.value = false;
return user;
diff --git a/frontend/composables/use-users/preferences.ts b/frontend/composables/use-users/preferences.ts
index 78f6f8041..6b03c58f3 100644
--- a/frontend/composables/use-users/preferences.ts
+++ b/frontend/composables/use-users/preferences.ts
@@ -1,7 +1,6 @@
-import { Ref, useContext } from "@nuxtjs/composition-api";
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
-import { RegisteredParser, TimelineEventType } from "~/lib/api/types/recipe";
-import { QueryFilterJSON } from "~/lib/api/types/response";
+import type { RegisteredParser, TimelineEventType } from "~/lib/api/types/recipe";
+import type { QueryFilterJSON } from "~/lib/api/types/response";
export interface UserPrintPreferences {
imagePosition: string;
@@ -67,7 +66,7 @@ export function useUserMealPlanPreferences(): Ref {
{
numberOfDays: 7,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -83,7 +82,7 @@ export function useUserPrintPreferences(): Ref {
showDescription: true,
showNotes: true,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -92,7 +91,7 @@ export function useUserPrintPreferences(): Ref {
}
export function useUserSortPreferences(): Ref {
- const { $globals } = useContext();
+ const { $globals } = useNuxtApp();
const fromStorage = useLocalStorage(
"recipe-section-preferences",
@@ -103,7 +102,7 @@ export function useUserSortPreferences(): Ref {
sortIcon: $globals.icons.sortAlphabeticalAscending,
useMobileCards: false,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -117,7 +116,7 @@ export function useUserSearchQuerySession(): Ref {
{
recipe: "",
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -125,7 +124,6 @@ export function useUserSearchQuerySession(): Ref {
return fromStorage;
}
-
export function useShoppingListPreferences(): Ref {
const fromStorage = useLocalStorage(
"shopping-list-preferences",
@@ -133,7 +131,7 @@ export function useShoppingListPreferences(): Ref {
viewAllLists: false,
viewByLabel: true,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -148,7 +146,7 @@ export function useTimelinePreferences(): Ref {
orderDirection: "asc",
types: ["info", "system", "comment"] as TimelineEventType[],
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -162,7 +160,7 @@ export function useParsingPreferences(): Ref {
{
parser: "nlp",
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -176,7 +174,7 @@ export function useCookbookPreferences(): Ref {
{
hideOtherHouseholds: false,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
@@ -197,7 +195,7 @@ export function useRecipeFinderPreferences(): Ref {
includeFoodsOnHand: true,
includeToolsOnHand: true,
},
- { mergeDefaults: true }
+ { mergeDefaults: true },
// we cast to a Ref because by default it will return an optional type ref
// but since we pass defaults we know all properties are set.
) as unknown as Ref;
diff --git a/frontend/composables/use-users/user-form.ts b/frontend/composables/use-users/user-form.ts
index 79b889f3f..a68c986ba 100644
--- a/frontend/composables/use-users/user-form.ts
+++ b/frontend/composables/use-users/user-form.ts
@@ -1,78 +1,77 @@
-import { useContext } from "@nuxtjs/composition-api";
import { fieldTypes } from "../forms";
-import { AutoFormItems } from "~/types/auto-forms";
+import type { AutoFormItems } from "~/types/auto-forms";
export const useUserForm = () => {
- const { i18n } = useContext();
+ const i18n = useI18n();
const userForm: AutoFormItems = [
{
- section: i18n.tc("user.user-details"),
- label: i18n.tc("user.user-name"),
+ section: i18n.t("user.user-details"),
+ label: i18n.t("user.user-name"),
varName: "username",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
- label: i18n.tc("user.full-name"),
+ label: i18n.t("user.full-name"),
varName: "fullName",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
- label: i18n.tc("user.email"),
+ label: i18n.t("user.email"),
varName: "email",
type: fieldTypes.TEXT,
rules: ["required"],
},
{
- label: i18n.tc("user.password"),
+ label: i18n.t("user.password"),
varName: "password",
disableUpdate: true,
type: fieldTypes.PASSWORD,
rules: ["required", "minLength:8"],
},
{
- label: i18n.tc("user.authentication-method"),
+ label: i18n.t("user.authentication-method"),
varName: "authMethod",
type: fieldTypes.SELECT,
- hint: i18n.tc("user.authentication-method-hint"),
+ hint: i18n.t("user.authentication-method-hint"),
disableCreate: true,
options: [{ text: "Mealie" }, { text: "LDAP" }, { text: "OIDC" }],
},
{
- section: i18n.tc("user.permissions"),
- label: i18n.tc("user.administrator"),
+ section: i18n.t("user.permissions"),
+ label: i18n.t("user.administrator"),
varName: "admin",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
- label: i18n.tc("user.user-can-invite-other-to-group"),
+ label: i18n.t("user.user-can-invite-other-to-group"),
varName: "canInvite",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
- label: i18n.tc("user.user-can-manage-group"),
+ label: i18n.t("user.user-can-manage-group"),
varName: "canManage",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
- label: i18n.tc("user.user-can-organize-group-data"),
+ label: i18n.t("user.user-can-organize-group-data"),
varName: "canOrganize",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
- label: i18n.tc("user.user-can-manage-household"),
+ label: i18n.t("user.user-can-manage-household"),
varName: "canManageHousehold",
type: fieldTypes.BOOLEAN,
rules: ["required"],
},
{
- label: i18n.tc("user.enable-advanced-features"),
+ label: i18n.t("user.enable-advanced-features"),
varName: "advanced",
type: fieldTypes.BOOLEAN,
rules: ["required"],
diff --git a/frontend/composables/use-users/user-ratings.ts b/frontend/composables/use-users/user-ratings.ts
index 0f82cd718..d827e64c0 100644
--- a/frontend/composables/use-users/user-ratings.ts
+++ b/frontend/composables/use-users/user-ratings.ts
@@ -1,17 +1,16 @@
-import { ref, useContext } from "@nuxtjs/composition-api";
import { useUserApi } from "~/composables/api";
-import { UserRatingSummary } from "~/lib/api/types/user";
+import type { UserRatingSummary } from "~/lib/api/types/user";
const userRatings = ref([]);
const loading = ref(false);
const ready = ref(false);
export const useUserSelfRatings = function () {
- const { $auth } = useContext();
+ const $auth = useMealieAuth();
const api = useUserApi();
async function refreshUserRatings() {
- if (!$auth.user || loading.value) {
+ if (!$auth.user.value || loading.value) {
return;
}
@@ -24,7 +23,7 @@ export const useUserSelfRatings = function () {
async function setRating(slug: string, rating: number | null, isFavorite: boolean | null) {
loading.value = true;
- const userId = $auth.user?.id || "";
+ const userId = $auth.user.value?.id || "";
await api.users.setRating(userId, slug, rating, isFavorite);
loading.value = false;
await refreshUserRatings();
@@ -39,5 +38,5 @@ export const useUserSelfRatings = function () {
refreshUserRatings,
setRating,
ready,
- }
-}
+ };
+};
diff --git a/frontend/composables/use-users/user-registration-form.ts b/frontend/composables/use-users/user-registration-form.ts
index af27549bb..f61b26d56 100644
--- a/frontend/composables/use-users/user-registration-form.ts
+++ b/frontend/composables/use-users/user-registration-form.ts
@@ -1,6 +1,5 @@
-import { ref, Ref, useContext } from "@nuxtjs/composition-api";
import { useAsyncValidator } from "~/composables/use-validators";
-import { VForm } from "~/types/vuetify";
+import type { VForm } from "~/types/vuetify";
import { usePublicApi } from "~/composables/api/api-client";
const domAccountForm = ref(null);
@@ -12,7 +11,8 @@ const password2 = ref("");
const advancedOptions = ref(false);
export const useUserRegistrationForm = () => {
- const { i18n } = useContext();
+ const i18n = useI18n();
+
function safeValidate(form: Ref) {
if (form.value && form.value.validate) {
return form.value.validate();
@@ -29,15 +29,15 @@ export const useUserRegistrationForm = () => {
const { validate: validateUsername, valid: validUsername } = useAsyncValidator(
username,
(v: string) => publicApi.validators.username(v),
- i18n.tc("validation.username-is-taken"),
- usernameErrorMessages
+ i18n.t("validation.username-is-taken"),
+ usernameErrorMessages,
);
const emailErrorMessages = ref([]);
const { validate: validateEmail, valid: validEmail } = useAsyncValidator(
email,
(v: string) => publicApi.validators.email(v),
- i18n.tc("validation.email-is-taken"),
- emailErrorMessages
+ i18n.t("validation.email-is-taken"),
+ emailErrorMessages,
);
const accountDetails = {
username,
@@ -60,7 +60,7 @@ export const useUserRegistrationForm = () => {
};
// ================================================================
// Provide Credentials
- const passwordMatch = () => password1.value === password2.value || i18n.tc("user.password-must-match");
+ const passwordMatch = () => password1.value === password2.value || i18n.t("user.password-must-match");
const credentials = {
password1,
password2,
@@ -68,7 +68,7 @@ export const useUserRegistrationForm = () => {
reset: () => {
credentials.password1.value = "";
credentials.password2.value = "";
- }
+ },
};
return {
diff --git a/frontend/composables/use-utils.ts b/frontend/composables/use-utils.ts
index 05ad53c96..fafda6bba 100644
--- a/frontend/composables/use-utils.ts
+++ b/frontend/composables/use-utils.ts
@@ -1,17 +1,10 @@
-import { IncomingMessage } from "connect";
-import { useDark } from "@vueuse/core";
-import { useContext } from "@nuxtjs/composition-api";
+import { useDark, useToggle } from "@vueuse/core";
export const useToggleDarkMode = () => {
const isDark = useDark();
- const { $vuetify } = useContext();
+ const toggleDark = useToggle(isDark);
- function toggleDark() {
- isDark.value = !$vuetify.theme.dark;
- $vuetify.theme.dark = !$vuetify.theme.dark;
- }
-
- return toggleDark;
+ return () => toggleDark();
};
export const useAsyncKey = function () {
@@ -21,34 +14,13 @@ export const useAsyncKey = function () {
export const titleCase = function (str: string) {
return str
.split(" ")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
};
-export function detectServerBaseUrl(req?: IncomingMessage | null) {
- if (!req || req === undefined) {
- return "";
- }
- if (req.headers.referer) {
- const url = new URL(req.headers.referer);
- return `${url.protocol}//${url.host}`;
- } else if (req.headers.host) {
- // TODO Socket.encrypted doesn't exist. What is needed here?
- // @ts-ignore See above
- const protocol = req.socket.encrypted ? "https:" : "http:";
- return `${protocol}//${req.headers.host}`;
- } else if (req.socket.remoteAddress) {
- // @ts-ignore See above
- const protocol = req.socket.encrypted ? "https:" : "http:";
- return `${protocol}//${req.socket.localAddress || ""}:${req.socket.localPort || ""}`;
- }
-
- return "";
-}
-
export function uuid4() {
- return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
- (parseInt(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (parseInt(c) / 4)))).toString(16)
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
+ (parseInt(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (parseInt(c) / 4)))).toString(16),
);
}
@@ -61,7 +33,8 @@ export function deepCopy(obj: T): T {
if (obj === null) {
// null => null
rv = null;
- } else {
+ }
+ else {
switch (Object.prototype.toString.call(obj)) {
case "[object Array]":
// It's an array, create a new array with
@@ -81,7 +54,6 @@ export function deepCopy(obj: T): T {
// Some other kind of object, deep-copy its
// properties into a new object
rv = Object.keys(obj).reduce(function (prev, key) {
- // @ts-ignore This is hard to make type-safe
prev[key] = deepCopy(obj[key]);
return prev;
}, {});
diff --git a/frontend/composables/use-validators.ts b/frontend/composables/use-validators.ts
index fc7fd6ad8..3f03d331f 100644
--- a/frontend/composables/use-validators.ts
+++ b/frontend/composables/use-validators.ts
@@ -1,6 +1,5 @@
-import { ref, Ref } from "@nuxtjs/composition-api";
-import { RequestResponse } from "~/lib/api/types/non-generated";
-import { ValidationResponse } from "~/lib/api/types/response";
+import type { RequestResponse } from "~/lib/api/types/non-generated";
+import type { ValidationResponse } from "~/lib/api/types/response";
import { required, email, whitespace, url, minLength, maxLength } from "~/lib/validators";
export const validators = {
@@ -21,7 +20,7 @@ export const useAsyncValidator = (
value: Ref,
validatorFunc: (v: string) => Promise>,
validatorMessage: string,
- errorMessages: Ref
+ errorMessages: Ref,
) => {
const valid = ref(false);
diff --git a/frontend/composables/useMealieAuth.ts b/frontend/composables/useMealieAuth.ts
new file mode 100644
index 000000000..41e3a63c4
--- /dev/null
+++ b/frontend/composables/useMealieAuth.ts
@@ -0,0 +1,61 @@
+import { ref, watch, computed } from "vue";
+import type { UserOut } from "~/lib/api/types/user";
+
+export const useMealieAuth = function () {
+ const auth = useAuth();
+ const { setToken } = useAuthState();
+ const { $axios } = useNuxtApp();
+
+ // User Management
+ const lastUser = ref(null);
+ const user = computed(() => lastUser.value);
+
+ watch(
+ () => auth.data.value,
+ (val) => {
+ if (val) {
+ lastUser.value = val as UserOut;
+ }
+ else {
+ lastUser.value = null;
+ }
+ },
+ { immediate: true },
+ );
+
+ // Auth Status Management
+ const lastAuthStatus = ref(auth.status.value);
+ const loggedIn = computed(() => lastAuthStatus.value === "authenticated");
+
+ watch(
+ () => auth.status.value,
+ (val) => {
+ if (val !== "loading") {
+ lastAuthStatus.value = val;
+ }
+ },
+ { immediate: true },
+ );
+
+ async function signIn(...params: Parameters) {
+ await auth.signIn(...params);
+ refreshCookie(useRuntimeConfig().public.AUTH_TOKEN);
+ }
+
+ async function oauthSignIn() {
+ const params = new URLSearchParams(window.location.search);
+ const { data: token } = await $axios.get<{ access_token: string; token_type: "bearer" }>("/api/auth/oauth/callback", { params });
+ setToken(token.access_token);
+ await auth.getSession();
+ }
+
+ return {
+ user,
+ loggedIn,
+ signIn,
+ signOut: auth.signOut,
+ signUp: auth.signUp,
+ refresh: auth.refresh,
+ oauthSignIn,
+ };
+};
diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs
new file mode 100644
index 000000000..954debd00
--- /dev/null
+++ b/frontend/eslint.config.mjs
@@ -0,0 +1,24 @@
+// @ts-check
+import stylisticJs from "@stylistic/eslint-plugin-js";
+import withNuxt from "./.nuxt/eslint.config.mjs";
+
+export default withNuxt({
+ plugins: {
+ "@stylistic/js": stylisticJs,
+ },
+ // Your custom configs here
+ rules: {
+ "@typescript-eslint/no-explicit-any": "off",
+ "vue/no-mutating-props": "warn",
+ "vue/no-v-html": "warn",
+ "object-curly-newline": "off",
+ "consistent-list-newline": "off",
+ "vue/first-attribute-linebreak": "off",
+ "@stylistic/js/no-tabs": ["error", { allowIndentationTabs: true }],
+ "@stylistic/no-tabs": ["error", { allowIndentationTabs: true }],
+ "@stylistic/no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
+ "vue/max-attributes-per-line": "off",
+ "vue/html-indent": "off",
+ "vue/html-closing-bracket-newline": "off",
+ },
+});
diff --git a/frontend/i18n.config.ts b/frontend/i18n.config.ts
new file mode 100644
index 000000000..6600f7385
--- /dev/null
+++ b/frontend/i18n.config.ts
@@ -0,0 +1,57 @@
+/* eslint-disable @typescript-eslint/no-require-imports */
+const datetimeFormats = {
+ // CODE_GEN_ID: DATE_LOCALES
+ "el-GR": require("./lang/dateTimeFormats/el-GR.json"),
+ "it-IT": require("./lang/dateTimeFormats/it-IT.json"),
+ "ko-KR": require("./lang/dateTimeFormats/ko-KR.json"),
+ "es-ES": require("./lang/dateTimeFormats/es-ES.json"),
+ "ja-JP": require("./lang/dateTimeFormats/ja-JP.json"),
+ "bg-BG": require("./lang/dateTimeFormats/bg-BG.json"),
+ "zh-CN": require("./lang/dateTimeFormats/zh-CN.json"),
+ "tr-TR": require("./lang/dateTimeFormats/tr-TR.json"),
+ "ar-SA": require("./lang/dateTimeFormats/ar-SA.json"),
+ "hu-HU": require("./lang/dateTimeFormats/hu-HU.json"),
+ "pt-PT": require("./lang/dateTimeFormats/pt-PT.json"),
+ "no-NO": require("./lang/dateTimeFormats/no-NO.json"),
+ "sv-SE": require("./lang/dateTimeFormats/sv-SE.json"),
+ "ro-RO": require("./lang/dateTimeFormats/ro-RO.json"),
+ "sk-SK": require("./lang/dateTimeFormats/sk-SK.json"),
+ "uk-UA": require("./lang/dateTimeFormats/uk-UA.json"),
+ "fr-CA": require("./lang/dateTimeFormats/fr-CA.json"),
+ "pl-PL": require("./lang/dateTimeFormats/pl-PL.json"),
+ "da-DK": require("./lang/dateTimeFormats/da-DK.json"),
+ "pt-BR": require("./lang/dateTimeFormats/pt-BR.json"),
+ "de-DE": require("./lang/dateTimeFormats/de-DE.json"),
+ "ca-ES": require("./lang/dateTimeFormats/ca-ES.json"),
+ "sr-SP": require("./lang/dateTimeFormats/sr-SP.json"),
+ "cs-CZ": require("./lang/dateTimeFormats/cs-CZ.json"),
+ "fr-FR": require("./lang/dateTimeFormats/fr-FR.json"),
+ "fr-BE": require("./lang/dateTimeFormats/fr-BE.json"),
+ "zh-TW": require("./lang/dateTimeFormats/zh-TW.json"),
+ "af-ZA": require("./lang/dateTimeFormats/af-ZA.json"),
+ "ru-RU": require("./lang/dateTimeFormats/ru-RU.json"),
+ "he-IL": require("./lang/dateTimeFormats/he-IL.json"),
+ "nl-NL": require("./lang/dateTimeFormats/nl-NL.json"),
+ "en-US": require("./lang/dateTimeFormats/en-US.json"),
+ "en-GB": require("./lang/dateTimeFormats/en-GB.json"),
+ "fi-FI": require("./lang/dateTimeFormats/fi-FI.json"),
+ "vi-VN": require("./lang/dateTimeFormats/vi-VN.json"),
+ "sl-SI": require("./lang/dateTimeFormats/sl-SI.json"),
+ "lv-LV": require("./lang/dateTimeFormats/lv-LV.json"),
+ "is-IS": require("./lang/dateTimeFormats/is-IS.json"),
+ "gl-ES": require("./lang/dateTimeFormats/gl-ES.json"),
+ "lt-LT": require("./lang/dateTimeFormats/lt-LT.json"),
+ "hr-HR": require("./lang/dateTimeFormats/hr-HR.json"),
+ // END: DATE_LOCALES
+};
+
+export default defineI18nConfig(() => {
+ return {
+ legacy: false,
+ locale: "en-US",
+ availableLocales: Object.keys(datetimeFormats),
+ datetimeFormats,
+ fallbackLocale: "en-US",
+ fallbackWarn: true,
+ };
+});
diff --git a/frontend/lang/locales/af-ZA.ts b/frontend/lang/locales/af-ZA.ts
new file mode 100644
index 000000000..69d482a6b
--- /dev/null
+++ b/frontend/lang/locales/af-ZA.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { af: $vuetify } = await import("vuetify/locale");
+ const { default: afZA } = await import("../messages/af-ZA.json");
+ return {
+ ...afZA,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ar-SA.ts b/frontend/lang/locales/ar-SA.ts
new file mode 100644
index 000000000..74d59ffa5
--- /dev/null
+++ b/frontend/lang/locales/ar-SA.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ar: $vuetify } = await import("vuetify/locale");
+ const { default: arSA } = await import("../messages/ar-SA.json");
+ return {
+ ...arSA,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/bg-BG.ts b/frontend/lang/locales/bg-BG.ts
new file mode 100644
index 000000000..ca69a612d
--- /dev/null
+++ b/frontend/lang/locales/bg-BG.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { bg: $vuetify } = await import("vuetify/locale");
+ const { default: bgBG } = await import("../messages/bg-BG.json");
+ return {
+ ...bgBG,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ca-ES.ts b/frontend/lang/locales/ca-ES.ts
new file mode 100644
index 000000000..5cdbfab33
--- /dev/null
+++ b/frontend/lang/locales/ca-ES.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ca: $vuetify } = await import("vuetify/locale");
+ const { default: caES } = await import("../messages/ca-ES.json");
+ return {
+ ...caES,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/cs-CZ.ts b/frontend/lang/locales/cs-CZ.ts
new file mode 100644
index 000000000..1480cac09
--- /dev/null
+++ b/frontend/lang/locales/cs-CZ.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { cs: $vuetify } = await import("vuetify/locale");
+ const { default: csCZ } = await import("../messages/cs-CZ.json");
+ return {
+ ...csCZ,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/da-DK.ts b/frontend/lang/locales/da-DK.ts
new file mode 100644
index 000000000..c5558fe15
--- /dev/null
+++ b/frontend/lang/locales/da-DK.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { da: $vuetify } = await import("vuetify/locale");
+ const { default: daDK } = await import("../messages/da-DK.json");
+ return {
+ ...daDK,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/de-DE.ts b/frontend/lang/locales/de-DE.ts
new file mode 100644
index 000000000..de9fafe79
--- /dev/null
+++ b/frontend/lang/locales/de-DE.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { de: $vuetify } = await import("vuetify/locale");
+ const { default: deDE } = await import("../messages/de-DE.json");
+ return {
+ ...deDE,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/el-GR.ts b/frontend/lang/locales/el-GR.ts
new file mode 100644
index 000000000..96f82afab
--- /dev/null
+++ b/frontend/lang/locales/el-GR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { el: $vuetify } = await import("vuetify/locale");
+ const { default: elGR } = await import("../messages/el-GR.json");
+ return {
+ ...elGR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/en-GB.ts b/frontend/lang/locales/en-GB.ts
new file mode 100644
index 000000000..42ef54f31
--- /dev/null
+++ b/frontend/lang/locales/en-GB.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { en: $vuetify } = await import("vuetify/locale");
+ const { default: enGB } = await import("../messages/en-GB.json");
+ return {
+ ...enGB,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/en-US.ts b/frontend/lang/locales/en-US.ts
new file mode 100644
index 000000000..a62cd883c
--- /dev/null
+++ b/frontend/lang/locales/en-US.ts
@@ -0,0 +1,9 @@
+// File was already correctly named
+export default defineI18nLocale(async () => {
+ const { en: $vuetify } = await import("vuetify/locale");
+ const { default: enUS } = await import("../messages/en-US.json");
+ return {
+ ...enUS,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/es-ES.ts b/frontend/lang/locales/es-ES.ts
new file mode 100644
index 000000000..1c3dd3ef6
--- /dev/null
+++ b/frontend/lang/locales/es-ES.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { es: $vuetify } = await import("vuetify/locale");
+ const { default: esES } = await import("../messages/es-ES.json");
+ return {
+ ...esES,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/et-EE.ts b/frontend/lang/locales/et-EE.ts
new file mode 100644
index 000000000..a46facfe5
--- /dev/null
+++ b/frontend/lang/locales/et-EE.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { et: $vuetify } = await import("vuetify/locale");
+ const { default: etEE } = await import("../messages/et-EE.json");
+ return {
+ ...etEE,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/fi-FI.ts b/frontend/lang/locales/fi-FI.ts
new file mode 100644
index 000000000..e588ba905
--- /dev/null
+++ b/frontend/lang/locales/fi-FI.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { fi: $vuetify } = await import("vuetify/locale");
+ const { default: fiFI } = await import("../messages/fi-FI.json");
+ return {
+ ...fiFI,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/fr-BE.ts b/frontend/lang/locales/fr-BE.ts
new file mode 100644
index 000000000..21099d7cf
--- /dev/null
+++ b/frontend/lang/locales/fr-BE.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { fr: $vuetify } = await import("vuetify/locale");
+ const { default: frBE } = await import("../messages/fr-BE.json");
+ return {
+ ...frBE,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/fr-CA.ts b/frontend/lang/locales/fr-CA.ts
new file mode 100644
index 000000000..41cf9d11f
--- /dev/null
+++ b/frontend/lang/locales/fr-CA.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { fr: $vuetify } = await import("vuetify/locale");
+ const { default: frCA } = await import("../messages/fr-CA.json");
+ return {
+ ...frCA,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/fr-FR.ts b/frontend/lang/locales/fr-FR.ts
new file mode 100644
index 000000000..767608453
--- /dev/null
+++ b/frontend/lang/locales/fr-FR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { fr: $vuetify } = await import("vuetify/locale");
+ const { default: frFR } = await import("../messages/fr-FR.json");
+ return {
+ ...frFR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/gl-ES.ts b/frontend/lang/locales/gl-ES.ts
new file mode 100644
index 000000000..1be279512
--- /dev/null
+++ b/frontend/lang/locales/gl-ES.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { es: $vuetify } = await import("vuetify/locale");
+ const { default: glES } = await import("../messages/gl-ES.json");
+ return {
+ ...glES,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/he-IL.ts b/frontend/lang/locales/he-IL.ts
new file mode 100644
index 000000000..b812b4d9b
--- /dev/null
+++ b/frontend/lang/locales/he-IL.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { he: $vuetify } = await import("vuetify/locale");
+ const { default: heIL } = await import("../messages/he-IL.json");
+ return {
+ ...heIL,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/hr-HR.ts b/frontend/lang/locales/hr-HR.ts
new file mode 100644
index 000000000..92b119e7e
--- /dev/null
+++ b/frontend/lang/locales/hr-HR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { hr: $vuetify } = await import("vuetify/locale");
+ const { default: hrHR } = await import("../messages/hr-HR.json");
+ return {
+ ...hrHR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/hu-HU.ts b/frontend/lang/locales/hu-HU.ts
new file mode 100644
index 000000000..71846d2d3
--- /dev/null
+++ b/frontend/lang/locales/hu-HU.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { hu: $vuetify } = await import("vuetify/locale");
+ const { default: huHU } = await import("../messages/hu-HU.json");
+ return {
+ ...huHU,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/is-IS.ts b/frontend/lang/locales/is-IS.ts
new file mode 100644
index 000000000..26d742ffe
--- /dev/null
+++ b/frontend/lang/locales/is-IS.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { en: $vuetify } = await import("vuetify/locale");
+ const { default: isIS } = await import("../messages/is-IS.json");
+ return {
+ ...isIS,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/it-IT.ts b/frontend/lang/locales/it-IT.ts
new file mode 100644
index 000000000..f91d411ce
--- /dev/null
+++ b/frontend/lang/locales/it-IT.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { it: $vuetify } = await import("vuetify/locale");
+ const { default: itIT } = await import("../messages/it-IT.json");
+ return {
+ ...itIT,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ja-JP.ts b/frontend/lang/locales/ja-JP.ts
new file mode 100644
index 000000000..93dd4a642
--- /dev/null
+++ b/frontend/lang/locales/ja-JP.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ja: $vuetify } = await import("vuetify/locale");
+ const { default: jaJP } = await import("../messages/ja-JP.json");
+ return {
+ ...jaJP,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ko-KR.ts b/frontend/lang/locales/ko-KR.ts
new file mode 100644
index 000000000..c7bb18b83
--- /dev/null
+++ b/frontend/lang/locales/ko-KR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ko: $vuetify } = await import("vuetify/locale");
+ const { default: koKR } = await import("../messages/ko-KR.json");
+ return {
+ ...koKR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/lt-LT.ts b/frontend/lang/locales/lt-LT.ts
new file mode 100644
index 000000000..2a3743c08
--- /dev/null
+++ b/frontend/lang/locales/lt-LT.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { lt: $vuetify } = await import("vuetify/locale");
+ const { default: ltLT } = await import("../messages/lt-LT.json");
+ return {
+ ...ltLT,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/lv-LV.ts b/frontend/lang/locales/lv-LV.ts
new file mode 100644
index 000000000..62af43898
--- /dev/null
+++ b/frontend/lang/locales/lv-LV.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { lv: $vuetify } = await import("vuetify/locale");
+ const { default: lvLV } = await import("../messages/lv-LV.json");
+ return {
+ ...lvLV,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/nl-NL.ts b/frontend/lang/locales/nl-NL.ts
new file mode 100644
index 000000000..27580a1a6
--- /dev/null
+++ b/frontend/lang/locales/nl-NL.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { nl: $vuetify } = await import("vuetify/locale");
+ const { default: nlNL } = await import("../messages/nl-NL.json");
+ return {
+ ...nlNL,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/no-NO.ts b/frontend/lang/locales/no-NO.ts
new file mode 100644
index 000000000..58e0aa82a
--- /dev/null
+++ b/frontend/lang/locales/no-NO.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { no: $vuetify } = await import("vuetify/locale");
+ const { default: noNO } = await import("../messages/no-NO.json");
+ return {
+ ...noNO,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/pl-PL.ts b/frontend/lang/locales/pl-PL.ts
new file mode 100644
index 000000000..a3e8c876d
--- /dev/null
+++ b/frontend/lang/locales/pl-PL.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { pl: $vuetify } = await import("vuetify/locale");
+ const { default: plPL } = await import("../messages/pl-PL.json");
+ return {
+ ...plPL,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/pt-BR.ts b/frontend/lang/locales/pt-BR.ts
new file mode 100644
index 000000000..e4826e217
--- /dev/null
+++ b/frontend/lang/locales/pt-BR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { pt: $vuetify } = await import("vuetify/locale");
+ const { default: ptBR } = await import("../messages/pt-BR.json");
+ return {
+ ...ptBR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/pt-PT.ts b/frontend/lang/locales/pt-PT.ts
new file mode 100644
index 000000000..c39411842
--- /dev/null
+++ b/frontend/lang/locales/pt-PT.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { pt: $vuetify } = await import("vuetify/locale");
+ const { default: ptPT } = await import("../messages/pt-PT.json");
+ return {
+ ...ptPT,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ro-RO.ts b/frontend/lang/locales/ro-RO.ts
new file mode 100644
index 000000000..ac6b17eb2
--- /dev/null
+++ b/frontend/lang/locales/ro-RO.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ro: $vuetify } = await import("vuetify/locale");
+ const { default: roRO } = await import("../messages/ro-RO.json");
+ return {
+ ...roRO,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/ru-RU.ts b/frontend/lang/locales/ru-RU.ts
new file mode 100644
index 000000000..a4e49a7ba
--- /dev/null
+++ b/frontend/lang/locales/ru-RU.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { ru: $vuetify } = await import("vuetify/locale");
+ const { default: ruRU } = await import("../messages/ru-RU.json");
+ return {
+ ...ruRU,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/sk-SK.ts b/frontend/lang/locales/sk-SK.ts
new file mode 100644
index 000000000..58af984b2
--- /dev/null
+++ b/frontend/lang/locales/sk-SK.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { sk: $vuetify } = await import("vuetify/locale");
+ const { default: skSK } = await import("../messages/sk-SK.json");
+ return {
+ ...skSK,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/sl-SI.ts b/frontend/lang/locales/sl-SI.ts
new file mode 100644
index 000000000..a1e9509a4
--- /dev/null
+++ b/frontend/lang/locales/sl-SI.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { sl: $vuetify } = await import("vuetify/locale");
+ const { default: slSI } = await import("../messages/sl-SI.json");
+ return {
+ ...slSI,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/sr-SP.ts b/frontend/lang/locales/sr-SP.ts
new file mode 100644
index 000000000..1d7a12073
--- /dev/null
+++ b/frontend/lang/locales/sr-SP.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { en: $vuetify } = await import("vuetify/locale");
+ const { default: srSP } = await import("../messages/sr-SP.json");
+ return {
+ ...srSP,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/sv-SE.ts b/frontend/lang/locales/sv-SE.ts
new file mode 100644
index 000000000..ae74be77a
--- /dev/null
+++ b/frontend/lang/locales/sv-SE.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { sv: $vuetify } = await import("vuetify/locale");
+ const { default: svSE } = await import("../messages/sv-SE.json");
+ return {
+ ...svSE,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/tr-TR.ts b/frontend/lang/locales/tr-TR.ts
new file mode 100644
index 000000000..4f1e139a3
--- /dev/null
+++ b/frontend/lang/locales/tr-TR.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { tr: $vuetify } = await import("vuetify/locale");
+ const { default: trTR } = await import("../messages/tr-TR.json");
+ return {
+ ...trTR,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/uk-UA.ts b/frontend/lang/locales/uk-UA.ts
new file mode 100644
index 000000000..ec969f0f1
--- /dev/null
+++ b/frontend/lang/locales/uk-UA.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { uk: $vuetify } = await import("vuetify/locale");
+ const { default: ukUA } = await import("../messages/uk-UA.json");
+ return {
+ ...ukUA,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/vi-VN.ts b/frontend/lang/locales/vi-VN.ts
new file mode 100644
index 000000000..c2af5f76b
--- /dev/null
+++ b/frontend/lang/locales/vi-VN.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { vi: $vuetify } = await import("vuetify/locale");
+ const { default: viVN } = await import("../messages/vi-VN.json");
+ return {
+ ...viVN,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/zh-CN.ts b/frontend/lang/locales/zh-CN.ts
new file mode 100644
index 000000000..7bdacd44e
--- /dev/null
+++ b/frontend/lang/locales/zh-CN.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { zhHans: $vuetify } = await import("vuetify/locale");
+ const { default: zhCN } = await import("../messages/zh-CN.json");
+ return {
+ ...zhCN,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/locales/zh-TW.ts b/frontend/lang/locales/zh-TW.ts
new file mode 100644
index 000000000..966a12a22
--- /dev/null
+++ b/frontend/lang/locales/zh-TW.ts
@@ -0,0 +1,8 @@
+export default defineI18nLocale(async () => {
+ const { zhHant: $vuetify } = await import("vuetify/locale");
+ const { default: zhTW } = await import("../messages/zh-TW.json");
+ return {
+ ...zhTW,
+ $vuetify,
+ };
+});
diff --git a/frontend/lang/messages/hr-HR.json b/frontend/lang/messages/hr-HR.json
index 38b3d4248..abffb8266 100644
--- a/frontend/lang/messages/hr-HR.json
+++ b/frontend/lang/messages/hr-HR.json
@@ -718,7 +718,7 @@
"unable-to-delete-backup": "Ne Mogu Obrisati Sigurnosnu Kopiju.",
"experimental-description": "Backups are total snapshots of the database and data directory of the site. This includes all data and cannot be set to exclude subsets of data. You can think of this as a snapshot of Mealie at a specific time. These serve as a database agnostic way to export and import data, or back up the site to an external location.",
"backup-restore": "Sigurnosno kopiranje/vraΔanje",
- "back-restore-description": "VraΔanje ove sigurnosne kopije Δe prepisati sve trenutne podatke u vaΕ‘oj bazi podataka i direktoriju podataka i zamijeniti ih sadrΕΎajem ove sigurnosne kopije. Ova radnja je {ne-moΕΎe-se-povratiti}. Ako se vraΔanje uspjeΕ‘no izvrΕ‘i, bit Δete odjavljeni iz sustava.",
+ "back-restore-description": "VraΔanje ove sigurnosne kopije Δe prepisati sve trenutne podatke u vaΕ‘oj bazi podataka i direktoriju podataka i zamijeniti ih sadrΕΎajem ove sigurnosne kopije. Ova radnja je {cannot-be-undone}. Ako se vraΔanje uspjeΕ‘no izvrΕ‘i, bit Δete odjavljeni iz sustava.",
"cannot-be-undone": "Ova radnja ne moΕΎe se poniΕ‘titi - koristite je oprezno.",
"postgresql-note": "If you are using PostgreSQL, please review the {backup-restore-process} prior to restoring.",
"backup-restore-process-in-the-documentation": "postupak sigurnosne kopije/obnove opisan je u dokumentaciji",
diff --git a/frontend/layouts/admin.vue b/frontend/layouts/admin.vue
index 416337919..36f214dff 100644
--- a/frontend/layouts/admin.vue
+++ b/frontend/layouts/admin.vue
@@ -1,5 +1,16 @@
+
+
+
+
+ {{ $globals.icons.menu }}
+
+
+
-
-
-
-
- {{ $globals.icons.menu }}
-
-
-
+
+
+
-
diff --git a/frontend/layouts/basic.vue b/frontend/layouts/basic.vue
index c5fe64bf7..2eb9958a9 100644
--- a/frontend/layouts/basic.vue
+++ b/frontend/layouts/basic.vue
@@ -1,22 +1,24 @@
+
-
+
-
+
+
+
-
diff --git a/frontend/layouts/blank.vue b/frontend/layouts/blank.vue
index d3f7c88a0..83d44e823 100644
--- a/frontend/layouts/blank.vue
+++ b/frontend/layouts/blank.vue
@@ -1,8 +1,12 @@
+
-
+
{{ $t("demo.info_message_with_version", { version: version }) }}
@@ -10,24 +14,26 @@
-
+
+
+
diff --git a/frontend/layouts/error.vue b/frontend/layouts/error.vue
index 3f32383d8..55c8a224e 100644
--- a/frontend/layouts/error.vue
+++ b/frontend/layouts/error.vue
@@ -1,37 +1,52 @@
-
+
- {{ $t("page.404-page-not-found") }}
+
+ {{ $t("page.404-page-not-found") }}
+
-
4
-
+
+ 4
+
+
{{ $globals.icons.primary }}
- 4
+
+ 4
+
-
+
-
- {{ button.icon }}
+
+
+ {{ button.icon }}
+
{{ button.text }}
-
+
@@ -119,6 +140,7 @@ export default defineComponent({
h1 {
font-size: 20px;
}
+
p {
padding-bottom: 0 !important;
margin-bottom: 0 !important;
diff --git a/frontend/lib/api/admin/admin-about.ts b/frontend/lib/api/admin/admin-about.ts
index 83eb02a88..f15f71a53 100644
--- a/frontend/lib/api/admin/admin-about.ts
+++ b/frontend/lib/api/admin/admin-about.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { AdminAboutInfo, CheckAppConfig } from "~/lib/api/types/admin";
+import type { AdminAboutInfo, CheckAppConfig } from "~/lib/api/types/admin";
const prefix = "/api";
diff --git a/frontend/lib/api/admin/admin-analytics.ts b/frontend/lib/api/admin/admin-analytics.ts
index 7a8901d4a..26e0bddf6 100644
--- a/frontend/lib/api/admin/admin-analytics.ts
+++ b/frontend/lib/api/admin/admin-analytics.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { MealieAnalytics } from "~/lib/api/types/analytics";
+import type { MealieAnalytics } from "~/lib/api/types/analytics";
const prefix = "/api";
diff --git a/frontend/lib/api/admin/admin-backups.ts b/frontend/lib/api/admin/admin-backups.ts
index 936b08a55..0b3a8a87d 100644
--- a/frontend/lib/api/admin/admin-backups.ts
+++ b/frontend/lib/api/admin/admin-backups.ts
@@ -1,6 +1,6 @@
import { BaseAPI } from "../base/base-clients";
-import { AllBackups } from "~/lib/api/types/admin";
-import { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/lib/api/types/response";
+import type { AllBackups } from "~/lib/api/types/admin";
+import type { ErrorResponse, FileTokenResponse, SuccessResponse } from "~/lib/api/types/response";
const prefix = "/api";
diff --git a/frontend/lib/api/admin/admin-debug.ts b/frontend/lib/api/admin/admin-debug.ts
index bfbc4cbc4..af6babfab 100644
--- a/frontend/lib/api/admin/admin-debug.ts
+++ b/frontend/lib/api/admin/admin-debug.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { DebugResponse } from "~/lib/api/types/admin";
+import type { DebugResponse } from "~/lib/api/types/admin";
const prefix = "/api";
diff --git a/frontend/lib/api/admin/admin-groups.ts b/frontend/lib/api/admin/admin-groups.ts
index fbe372faa..36a70cc19 100644
--- a/frontend/lib/api/admin/admin-groups.ts
+++ b/frontend/lib/api/admin/admin-groups.ts
@@ -1,6 +1,7 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { GroupBase, GroupInDB } from "~/lib/api/types/user";
-import { GroupAdminUpdate } from "~/lib/api/types/group";
+import type { GroupBase, GroupInDB } from "~/lib/api/types/user";
+import type { GroupAdminUpdate } from "~/lib/api/types/group";
+
const prefix = "/api";
const routes = {
diff --git a/frontend/lib/api/admin/admin-households.ts b/frontend/lib/api/admin/admin-households.ts
index 1e49723b7..85aa2bce8 100644
--- a/frontend/lib/api/admin/admin-households.ts
+++ b/frontend/lib/api/admin/admin-households.ts
@@ -1,5 +1,6 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin } from "~/lib/api/types/household";
+import type { HouseholdCreate, HouseholdInDB, UpdateHouseholdAdmin } from "~/lib/api/types/household";
+
const prefix = "/api";
const routes = {
diff --git a/frontend/lib/api/admin/admin-maintenance.ts b/frontend/lib/api/admin/admin-maintenance.ts
index 51f4121be..eebc86d0b 100644
--- a/frontend/lib/api/admin/admin-maintenance.ts
+++ b/frontend/lib/api/admin/admin-maintenance.ts
@@ -1,6 +1,6 @@
import { BaseAPI } from "../base/base-clients";
-import { SuccessResponse } from "~/lib/api/types/response";
-import { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/lib/api/types/admin";
+import type { SuccessResponse } from "~/lib/api/types/response";
+import type { MaintenanceLogs, MaintenanceStorageDetails, MaintenanceSummary } from "~/lib/api/types/admin";
const prefix = "/api";
diff --git a/frontend/lib/api/admin/admin-users.ts b/frontend/lib/api/admin/admin-users.ts
index 478d7c642..21af7e9e1 100644
--- a/frontend/lib/api/admin/admin-users.ts
+++ b/frontend/lib/api/admin/admin-users.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { ForgotPassword, PasswordResetToken, UnlockResults, UserIn, UserOut } from "~/lib/api/types/user";
+import type { ForgotPassword, PasswordResetToken, UnlockResults, UserIn, UserOut } from "~/lib/api/types/user";
const prefix = "/api";
diff --git a/frontend/lib/api/base/base-clients.ts b/frontend/lib/api/base/base-clients.ts
index d993216eb..636747e02 100644
--- a/frontend/lib/api/base/base-clients.ts
+++ b/frontend/lib/api/base/base-clients.ts
@@ -1,6 +1,6 @@
-import { Recipe } from "../types/recipe";
-import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
-import { QueryValue, route } from "~/lib/api/base/route";
+import type { Recipe } from "../types/recipe";
+import type { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
+import { type QueryValue, route } from "~/lib/api/base/route";
export interface CrudAPIInterface {
requests: ApiRequestInstance;
@@ -23,8 +23,26 @@ export abstract class BaseAPI {
export abstract class BaseCRUDAPIReadOnly
extends BaseAPI
implements CrudAPIInterface {
- abstract baseRoute: (string);
- abstract itemRoute(itemId: string | number): string;
+ public baseRoute: string;
+ public itemRouteFn: (itemId: string | number) => string;
+
+ constructor(
+ requests: ApiRequestInstance,
+ baseRoute: string,
+ itemRoute: (itemId: string | number) => string,
+ ) {
+ super(requests);
+ this.baseRoute = baseRoute;
+ this.itemRouteFn = itemRoute;
+ }
+
+ get baseRouteValue() {
+ return this.baseRoute;
+ }
+
+ itemRoute(itemId: string | number): string {
+ return this.itemRouteFn(itemId);
+ }
async getAll(page = 1, perPage = -1, params = {} as Record) {
params = Object.fromEntries(Object.entries(params).filter(([_, v]) => v !== null && v !== undefined));
diff --git a/frontend/lib/api/base/route.ts b/frontend/lib/api/base/route.ts
index 302b6d697..1074efa06 100644
--- a/frontend/lib/api/base/route.ts
+++ b/frontend/lib/api/base/route.ts
@@ -27,7 +27,8 @@ export function route(rest: string, params: Record | null =
for (const item of value) {
url.searchParams.append(key, String(item));
}
- } else {
+ }
+ else {
url.searchParams.append(key, String(value));
}
}
diff --git a/frontend/lib/api/client-admin.ts b/frontend/lib/api/client-admin.ts
index 274630430..1352edba9 100644
--- a/frontend/lib/api/client-admin.ts
+++ b/frontend/lib/api/client-admin.ts
@@ -6,7 +6,7 @@ import { AdminBackupsApi } from "./admin/admin-backups";
import { AdminMaintenanceApi } from "./admin/admin-maintenance";
import { AdminAnalyticsApi } from "./admin/admin-analytics";
import { AdminDebugAPI } from "./admin/admin-debug";
-import { ApiRequestInstance } from "~/lib/api/types/non-generated";
+import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class AdminAPI {
public about: AdminAboutAPI;
diff --git a/frontend/lib/api/client-public.ts b/frontend/lib/api/client-public.ts
index 7745c3c54..14046f5ed 100644
--- a/frontend/lib/api/client-public.ts
+++ b/frontend/lib/api/client-public.ts
@@ -1,7 +1,7 @@
import { ValidatorsApi } from "./public/validators";
import { ExploreApi } from "./public/explore";
import { SharedApi } from "./public/shared";
-import { ApiRequestInstance } from "~/lib/api/types/non-generated";
+import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class PublicApi {
public validators: ValidatorsApi;
diff --git a/frontend/lib/api/client-user.ts b/frontend/lib/api/client-user.ts
index 444a17734..7d786f6f2 100644
--- a/frontend/lib/api/client-user.ts
+++ b/frontend/lib/api/client-user.ts
@@ -24,7 +24,7 @@ import { MultiPurposeLabelsApi } from "./user/group-multiple-purpose-labels";
import { GroupEventNotifierApi } from "./user/group-event-notifier";
import { MealPlanRulesApi } from "./user/group-mealplan-rules";
import { GroupDataSeederApi } from "./user/group-seeder";
-import { ApiRequestInstance } from "~/lib/api/types/non-generated";
+import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
export class UserApiClient {
public recipes: RecipeAPI;
diff --git a/frontend/lib/api/public/explore/cookbooks.ts b/frontend/lib/api/public/explore/cookbooks.ts
index 67e051630..ee7e6230c 100644
--- a/frontend/lib/api/public/explore/cookbooks.ts
+++ b/frontend/lib/api/public/explore/cookbooks.ts
@@ -11,10 +11,11 @@ const routes = {
};
export class PublicCookbooksApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.cookbooksGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.cookbooksGroupSlugCookbookId(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.cookbooksGroupSlug(groupSlug),
+ (itemId: string | number) => routes.cookbooksGroupSlugCookbookId(groupSlug, itemId)
+ );
}
}
diff --git a/frontend/lib/api/public/explore/foods.ts b/frontend/lib/api/public/explore/foods.ts
index 5367c449e..43a591e73 100644
--- a/frontend/lib/api/public/explore/foods.ts
+++ b/frontend/lib/api/public/explore/foods.ts
@@ -11,10 +11,11 @@ const routes = {
};
export class PublicFoodsApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.foodsGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.foodsGroupSlugFoodId(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.foodsGroupSlug(groupSlug),
+ (itemId: string | number) => routes.foodsGroupSlugFoodId(groupSlug, itemId)
+ );
}
}
diff --git a/frontend/lib/api/public/explore/households.ts b/frontend/lib/api/public/explore/households.ts
index 188d75053..8dc3f490d 100644
--- a/frontend/lib/api/public/explore/households.ts
+++ b/frontend/lib/api/public/explore/households.ts
@@ -11,10 +11,11 @@ const routes = {
};
export class PublicHouseholdApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.householdsGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.householdsGroupSlugHouseholdSlug(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.householdsGroupSlug(groupSlug),
+ (itemId: string | number) => routes.householdsGroupSlugHouseholdSlug(groupSlug, itemId)
+ );
}
}
diff --git a/frontend/lib/api/public/explore/organizers.ts b/frontend/lib/api/public/explore/organizers.ts
index 1a24866c6..8d1339ed7 100644
--- a/frontend/lib/api/public/explore/organizers.ts
+++ b/frontend/lib/api/public/explore/organizers.ts
@@ -15,28 +15,31 @@ const routes = {
};
export class PublicCategoriesApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.categoriesGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.categoriesGroupSlugCategoryId(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.categoriesGroupSlug(groupSlug),
+ (itemId: string | number) => routes.categoriesGroupSlugCategoryId(groupSlug, itemId)
+ );
}
}
export class PublicTagsApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.tagsGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.tagsGroupSlugTagId(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.tagsGroupSlug(groupSlug),
+ (itemId: string | number) => routes.tagsGroupSlugTagId(groupSlug, itemId)
+ );
}
}
export class PublicToolsApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.toolsGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.toolsGroupSlugToolId(this.groupSlug, itemId);
-
- constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ constructor(requests: ApiRequestInstance, groupSlug: string) {
+ super(
+ requests,
+ routes.toolsGroupSlug(groupSlug),
+ (itemId: string | number) => routes.toolsGroupSlugToolId(groupSlug, itemId)
+ );
}
}
diff --git a/frontend/lib/api/public/explore/recipes.ts b/frontend/lib/api/public/explore/recipes.ts
index 978e4a6d6..cf9f8b3ba 100644
--- a/frontend/lib/api/public/explore/recipes.ts
+++ b/frontend/lib/api/public/explore/recipes.ts
@@ -13,11 +13,12 @@ const routes = {
};
export class PublicRecipeApi extends BaseCRUDAPIReadOnly {
- baseRoute = routes.recipesGroupSlug(this.groupSlug);
- itemRoute = (itemId: string | number) => routes.recipesGroupSlugRecipeSlug(this.groupSlug, itemId);
-
constructor(requests: ApiRequestInstance, private readonly groupSlug: string) {
- super(requests);
+ super(
+ requests,
+ routes.recipesGroupSlug(groupSlug),
+ (itemId: string | number) => routes.recipesGroupSlugRecipeSlug(groupSlug, itemId)
+ );
}
async search(rsq: RecipeSearchQuery) {
@@ -26,7 +27,7 @@ export class PublicRecipeApi extends BaseCRUDAPIReadOnly {
async getSuggestions(q: RecipeSuggestionQuery, foods: string[] | null = null, tools: string[]| null = null) {
return await this.requests.get(
- route(`${this.baseRoute}/suggestions`, { ...q, foods, tools })
+ route(`${routes.recipesGroupSlug(this.groupSlug)}/suggestions`, { ...q, foods, tools })
);
}
}
diff --git a/frontend/lib/api/types/admin.ts b/frontend/lib/api/types/admin.ts
index c330d1776..95b431bc9 100644
--- a/frontend/lib/api/types/admin.ts
+++ b/frontend/lib/api/types/admin.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/analytics.ts b/frontend/lib/api/types/analytics.ts
index f0eb9d489..43cb23663 100644
--- a/frontend/lib/api/types/analytics.ts
+++ b/frontend/lib/api/types/analytics.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/cookbook.ts b/frontend/lib/api/types/cookbook.ts
index 3a34d5983..2daf20aaa 100644
--- a/frontend/lib/api/types/cookbook.ts
+++ b/frontend/lib/api/types/cookbook.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/events.ts b/frontend/lib/api/types/events.ts
index 205c3012c..4a41060b2 100644
--- a/frontend/lib/api/types/events.ts
+++ b/frontend/lib/api/types/events.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/group.ts b/frontend/lib/api/types/group.ts
index bc2fbcf62..1f72c09c5 100644
--- a/frontend/lib/api/types/group.ts
+++ b/frontend/lib/api/types/group.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/household.ts b/frontend/lib/api/types/household.ts
index 4291af758..3f635879c 100644
--- a/frontend/lib/api/types/household.ts
+++ b/frontend/lib/api/types/household.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/labels.ts b/frontend/lib/api/types/labels.ts
index a8fc4c046..9d44342cc 100644
--- a/frontend/lib/api/types/labels.ts
+++ b/frontend/lib/api/types/labels.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/meal-plan.ts b/frontend/lib/api/types/meal-plan.ts
index eede212c9..aa28e6349 100644
--- a/frontend/lib/api/types/meal-plan.ts
+++ b/frontend/lib/api/types/meal-plan.ts
@@ -1,10 +1,12 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
*/
+import type { HouseholdSummary } from "./household";
+
export type PlanEntryType = "breakfast" | "lunch" | "dinner" | "side";
export type PlanRulesDay = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday" | "unset";
export type PlanRulesType = "breakfast" | "lunch" | "dinner" | "side" | "unset";
@@ -42,6 +44,9 @@ export interface PlanRulesOut {
householdId: string;
id: string;
queryFilter?: QueryFilterJSON;
+ categories?: RecipeCategory[];
+ tags?: RecipeTag[];
+ households?: HouseholdSummary[];
}
export interface QueryFilterJSON {
parts?: QueryFilterJSONPart[];
diff --git a/frontend/lib/api/types/non-generated.ts b/frontend/lib/api/types/non-generated.ts
index 79ebd9183..1a9797ccd 100644
--- a/frontend/lib/api/types/non-generated.ts
+++ b/frontend/lib/api/types/non-generated.ts
@@ -1,4 +1,4 @@
-import { AxiosResponse } from "axios";
+import type { AxiosResponse } from "axios";
export type NoUndefinedField = { [P in keyof T]-?: NoUndefinedField> };
diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts
index a526e602e..e87f95bdf 100644
--- a/frontend/lib/api/types/recipe.ts
+++ b/frontend/lib/api/types/recipe.ts
@@ -226,7 +226,7 @@ export interface Recipe {
groupId?: string;
name?: string | null;
slug?: string;
- image?: unknown;
+ image?: string;
recipeServings?: number;
recipeYieldQuantity?: number;
recipeYield?: string | null;
@@ -512,7 +512,7 @@ export interface ScrapeRecipeTest {
url: string;
useOpenAI?: boolean;
}
-export interface SlugResponse {}
+export interface SlugResponse { }
export interface TagIn {
name: string;
}
diff --git a/frontend/lib/api/types/reports.ts b/frontend/lib/api/types/reports.ts
index 2b763275e..e06788a0f 100644
--- a/frontend/lib/api/types/reports.ts
+++ b/frontend/lib/api/types/reports.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/response.ts b/frontend/lib/api/types/response.ts
index dfa8a54f4..d77f44084 100644
--- a/frontend/lib/api/types/response.ts
+++ b/frontend/lib/api/types/response.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
diff --git a/frontend/lib/api/types/user.ts b/frontend/lib/api/types/user.ts
index a29586b3a..58ef25f6a 100644
--- a/frontend/lib/api/types/user.ts
+++ b/frontend/lib/api/types/user.ts
@@ -1,5 +1,5 @@
/* tslint:disable */
-/* eslint-disable */
+
/**
/* This file was automatically generated from pydantic models by running pydantic2ts.
/* Do not modify it by hand - just update the pydantic models and then re-run the script
@@ -197,6 +197,7 @@ export interface UserBase {
canManage?: boolean;
canManageHousehold?: boolean;
canOrganize?: boolean;
+ advancedOptions?: boolean;
}
export interface UserIn {
id?: string | null;
diff --git a/frontend/lib/api/user/backups.ts b/frontend/lib/api/user/backups.ts
index 774eb14c1..3781a280a 100644
--- a/frontend/lib/api/user/backups.ts
+++ b/frontend/lib/api/user/backups.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { AllBackups, BackupOptions, CreateBackup } from "~/lib/api/types/admin";
+import type { AllBackups, BackupOptions, CreateBackup } from "~/lib/api/types/admin";
const prefix = "/api";
diff --git a/frontend/lib/api/user/email.ts b/frontend/lib/api/user/email.ts
index 30b840b09..a7bbc1b48 100644
--- a/frontend/lib/api/user/email.ts
+++ b/frontend/lib/api/user/email.ts
@@ -1,7 +1,7 @@
import { BaseAPI } from "../base/base-clients";
-import { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/household";
-import { ForgotPassword } from "~/lib/api/types/user";
-import { EmailTest } from "~/lib/api/types/admin";
+import type { EmailInitationResponse, EmailInvitation } from "~/lib/api/types/household";
+import type { ForgotPassword } from "~/lib/api/types/user";
+import type { EmailTest } from "~/lib/api/types/admin";
const routes = {
base: "/api/admin/email",
diff --git a/frontend/lib/api/user/group-cookbooks.ts b/frontend/lib/api/user/group-cookbooks.ts
index 798b8cdd7..9d5631558 100644
--- a/frontend/lib/api/user/group-cookbooks.ts
+++ b/frontend/lib/api/user/group-cookbooks.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
+import type { CreateCookBook, RecipeCookBook, UpdateCookBook } from "~/lib/api/types/cookbook";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-event-notifier.ts b/frontend/lib/api/user/group-event-notifier.ts
index 9f9f4d981..da51fd1d4 100644
--- a/frontend/lib/api/user/group-event-notifier.ts
+++ b/frontend/lib/api/user/group-event-notifier.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/household";
+import type { GroupEventNotifierCreate, GroupEventNotifierOut, GroupEventNotifierUpdate } from "~/lib/api/types/household";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-mealplan-rules.ts b/frontend/lib/api/user/group-mealplan-rules.ts
index 87679dd4c..d657c9d84 100644
--- a/frontend/lib/api/user/group-mealplan-rules.ts
+++ b/frontend/lib/api/user/group-mealplan-rules.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
+import type { PlanRulesCreate, PlanRulesOut } from "~/lib/api/types/meal-plan";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-mealplan.ts b/frontend/lib/api/user/group-mealplan.ts
index fcc31f2e3..22d4d4d70 100644
--- a/frontend/lib/api/user/group-mealplan.ts
+++ b/frontend/lib/api/user/group-mealplan.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
+import type { CreatePlanEntry, CreateRandomEntry, ReadPlanEntry, UpdatePlanEntry } from "~/lib/api/types/meal-plan";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-migrations.ts b/frontend/lib/api/user/group-migrations.ts
index ac6d8d91f..1083662ce 100644
--- a/frontend/lib/api/user/group-migrations.ts
+++ b/frontend/lib/api/user/group-migrations.ts
@@ -1,6 +1,6 @@
import { BaseAPI } from "../base/base-clients";
-import { ReportSummary } from "~/lib/api/types/reports";
-import { SupportedMigrations } from "~/lib/api/types/group";
+import type { ReportSummary } from "~/lib/api/types/reports";
+import type { SupportedMigrations } from "~/lib/api/types/group";
const prefix = "/api";
export interface MigrationPayload {
diff --git a/frontend/lib/api/user/group-multiple-purpose-labels.ts b/frontend/lib/api/user/group-multiple-purpose-labels.ts
index 2761d0bd8..54cbf2520 100644
--- a/frontend/lib/api/user/group-multiple-purpose-labels.ts
+++ b/frontend/lib/api/user/group-multiple-purpose-labels.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { MultiPurposeLabelCreate, MultiPurposeLabelOut, MultiPurposeLabelUpdate } from "~/lib/api/types/labels";
+import type { MultiPurposeLabelCreate, MultiPurposeLabelOut, MultiPurposeLabelUpdate } from "~/lib/api/types/labels";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-recipe-actions.ts b/frontend/lib/api/user/group-recipe-actions.ts
index d49cdecec..2753b3465 100644
--- a/frontend/lib/api/user/group-recipe-actions.ts
+++ b/frontend/lib/api/user/group-recipe-actions.ts
@@ -1,19 +1,19 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/household";
+import type { CreateGroupRecipeAction, GroupRecipeActionOut } from "~/lib/api/types/household";
const prefix = "/api";
const routes = {
- groupRecipeActions: `${prefix}/households/recipe-actions`,
- groupRecipeActionsId: (id: string | number) => `${prefix}/households/recipe-actions/${id}`,
- groupRecipeActionsIdTriggerRecipeSlug: (id: string | number, recipeSlug: string) => `${prefix}/households/recipe-actions/${id}/trigger/${recipeSlug}`,
- };
+ groupRecipeActions: `${prefix}/households/recipe-actions`,
+ groupRecipeActionsId: (id: string | number) => `${prefix}/households/recipe-actions/${id}`,
+ groupRecipeActionsIdTriggerRecipeSlug: (id: string | number, recipeSlug: string) => `${prefix}/households/recipe-actions/${id}/trigger/${recipeSlug}`,
+};
- export class GroupRecipeActionsAPI extends BaseCRUDAPI {
- baseRoute = routes.groupRecipeActions;
- itemRoute = routes.groupRecipeActionsId;
+export class GroupRecipeActionsAPI extends BaseCRUDAPI {
+ baseRoute = routes.groupRecipeActions;
+ itemRoute = routes.groupRecipeActionsId;
- async triggerAction(id: string | number, recipeSlug: string) {
- return await this.requests.post(routes.groupRecipeActionsIdTriggerRecipeSlug(id, recipeSlug), {});
- }
+ async triggerAction(id: string | number, recipeSlug: string, scaledAmount: number) {
+ return await this.requests.post(routes.groupRecipeActionsIdTriggerRecipeSlug(id, recipeSlug), { scaledAmount });
}
+}
diff --git a/frontend/lib/api/user/group-reports.ts b/frontend/lib/api/user/group-reports.ts
index 6b0cac7fa..b4d5adce4 100644
--- a/frontend/lib/api/user/group-reports.ts
+++ b/frontend/lib/api/user/group-reports.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { ReportCategory, ReportOut, ReportSummary } from "~/lib/api/types/reports";
+import type { ReportCategory, ReportOut, ReportSummary } from "~/lib/api/types/reports";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-seeder.ts b/frontend/lib/api/user/group-seeder.ts
index b9fc5b6be..b7595a4a7 100644
--- a/frontend/lib/api/user/group-seeder.ts
+++ b/frontend/lib/api/user/group-seeder.ts
@@ -1,6 +1,6 @@
import { BaseAPI } from "../base/base-clients";
-import { SuccessResponse } from "~/lib/api/types/response";
-import { SeederConfig } from "~/lib/api/types/group";
+import type { SuccessResponse } from "~/lib/api/types/response";
+import type { SeederConfig } from "~/lib/api/types/group";
const prefix = "/api";
diff --git a/frontend/lib/api/user/group-shopping-lists.ts b/frontend/lib/api/user/group-shopping-lists.ts
index 1eccb05f7..d5f74d291 100644
--- a/frontend/lib/api/user/group-shopping-lists.ts
+++ b/frontend/lib/api/user/group-shopping-lists.ts
@@ -1,6 +1,6 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { ApiRequestInstance } from "~/lib/api/types/non-generated";
-import {
+import type { ApiRequestInstance } from "~/lib/api/types/non-generated";
+import type {
ShoppingListAddRecipeParamsBulk,
ShoppingListCreate,
ShoppingListItemCreate,
diff --git a/frontend/lib/api/user/group-webhooks.ts b/frontend/lib/api/user/group-webhooks.ts
index 8ced73f16..395818c5f 100644
--- a/frontend/lib/api/user/group-webhooks.ts
+++ b/frontend/lib/api/user/group-webhooks.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreateWebhook, ReadWebhook } from "~/lib/api/types/household";
+import type { CreateWebhook, ReadWebhook } from "~/lib/api/types/household";
const prefix = "/api";
diff --git a/frontend/lib/api/user/groups.ts b/frontend/lib/api/user/groups.ts
index 9e379c9cd..bd3611d9c 100644
--- a/frontend/lib/api/user/groups.ts
+++ b/frontend/lib/api/user/groups.ts
@@ -1,8 +1,8 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { PaginationData } from "../types/non-generated";
-import { QueryValue } from "../base/route";
-import { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
-import {
+import type { PaginationData } from "../types/non-generated";
+import type { QueryValue } from "../base/route";
+import type { GroupBase, GroupInDB, GroupSummary, UserSummary } from "~/lib/api/types/user";
+import type {
GroupAdminUpdate,
GroupStorage,
ReadGroupPreferences,
diff --git a/frontend/lib/api/user/households.ts b/frontend/lib/api/user/households.ts
index 9995059dd..363af940d 100644
--- a/frontend/lib/api/user/households.ts
+++ b/frontend/lib/api/user/households.ts
@@ -1,8 +1,8 @@
import { BaseCRUDAPIReadOnly } from "../base/base-clients";
-import { PaginationData } from "../types/non-generated";
-import { QueryValue } from "../base/route";
-import { UserOut } from "~/lib/api/types/user";
-import {
+import type { PaginationData } from "../types/non-generated";
+import type { QueryValue } from "../base/route";
+import type { UserOut } from "~/lib/api/types/user";
+import type {
HouseholdInDB,
HouseholdStatistics,
ReadHouseholdPreferences,
@@ -27,7 +27,7 @@ const routes = {
invitation: `${prefix}/households/invitations`,
householdsId: (id: string | number) => `${prefix}/groups/households/${id}`,
- householdsSelfRecipesSlug: (recipeSlug: string) => `${prefix}/households/self/recipes/${recipeSlug}`,
+ householdsSelfRecipesSlug: (recipeSlug: string) => `${prefix}/households/self/recipes/${recipeSlug}`,
};
export class HouseholdAPI extends BaseCRUDAPIReadOnly