-
-
-
+
+
+
{{ $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: day === "unset" ? $t('meal-plan.to-all-days') : $t('meal-plan.on-days', [day]),
+ mealTypeCriteria: entryType === "unset" ? $t('meal-plan.for-all-meal-types') : $t('meal-plan.for-type-meal-types', [entryType]),
+ }) }}
-
diff --git a/frontend/components/Domain/Household/GroupWebhookEditor.vue b/frontend/components/Domain/Household/GroupWebhookEditor.vue
index 55bae8df8..131e93c77 100644
--- a/frontend/components/Domain/Household/GroupWebhookEditor.vue
+++ b/frontend/components/Domain/Household/GroupWebhookEditor.vue
@@ -1,27 +1,44 @@
-
-
-
-
+
+
+
+
-
diff --git a/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue b/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
index e5c889938..31fe28f0e 100644
--- a/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
+++ b/frontend/components/Domain/Household/HouseholdPreferencesEditor.vue
@@ -1,159 +1,116 @@
-
-
-
-
-
-
- {{ $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/Recipe/RecipeCardImage.vue b/frontend/components/Domain/Recipe/RecipeCardImage.vue
index abc3cc7d5..e2e8d9069 100644
--- a/frontend/components/Domain/Recipe/RecipeCardImage.vue
+++ b/frontend/components/Domain/Recipe/RecipeCardImage.vue
@@ -2,6 +2,7 @@
-
+
-
-
+
+
{{ $globals.icons.primary }}
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeCardSection.vue b/frontend/components/Domain/Recipe/RecipeCardSection.vue
index 64fb9c0b3..712789675 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/RecipeContextMenu.vue b/frontend/components/Domain/Recipe/RecipeContextMenu.vue
index b6165feff..d7c423e80 100644
--- a/frontend/components/Domain/Recipe/RecipeContextMenu.vue
+++ b/frontend/components/Domain/Recipe/RecipeContextMenu.vue
@@ -8,10 +8,16 @@
:title="$t('recipe.delete-recipe')"
color="error"
:icon="$globals.icons.alertCircle"
+ can-confirm
@confirm="deleteRecipe()"
>
- {{ $t("recipe.delete-confirmation") }}
+
+ {{ $t("recipe.admin-delete-confirmation") }}
+
+
+ {{ $t("recipe.delete-confirmation") }}
+
+ />
@@ -47,22 +55,21 @@
max-width="290px"
min-width="auto"
>
-
+
+ />
+ item-title="text"
+ item-value="value"
+ />
-
-
- {{ icon }}
+
+
+
+ {{ icon }}
+
-
+
-
- {{ item.icon }}
-
+
+
+ {{ item.icon }}
+
+
{{ item.title }}
-
-
- {{ $tc("recipe.recipe-actions") }}
+
+
+
+ {{ $globals.icons.linkVariantPlus }}
+
-
-
-
- {{ action.title }}
-
-
-
-
+
+ {{ action.title }}
+
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue b/frontend/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue
index 6b0741f53..c25477511 100644
--- a/frontend/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue
+++ b/frontend/components/Domain/Recipe/RecipeDataAliasManagerDialog.vue
@@ -1,41 +1,29 @@
-
+
-
+
-
+
- {{ $t('data-pages.create-alias') }}
+
+ {{ $t('data-pages.create-alias') }}
{{ $globals.icons.create }}
@@ -45,97 +33,75 @@
-
diff --git a/frontend/components/Domain/Recipe/RecipeDataTable.vue b/frontend/components/Domain/Recipe/RecipeDataTable.vue
index 620cdffd0..1836bad57 100644
--- a/frontend/components/Domain/Recipe/RecipeDataTable.vue
+++ b/frontend/components/Domain/Recipe/RecipeDataTable.vue
@@ -3,60 +3,72 @@
v-model="selected"
item-key="id"
show-select
- sort-by="dateAdded"
- sort-desc
+ :sort-by="sortBy"
:headers="headers"
:items="recipes"
:items-per-page="15"
class="elevation-0"
:loading="loading"
- @input="setValue(selected)"
+ return-object
>
-
-
- |
- 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..a55fd14c1 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") }}
-
-
+
+
-
+
-
-
+
+
{{ recipeSection.recipeName }}
-
+
-
- ({{ $tc("recipe.quantity") }}: {{ recipeSection.recipeScale }})
+
+ ({{ $t("recipe.quantity") }}: {{ recipeSection.recipeScale }})
@@ -73,36 +109,44 @@
v-for="(ingredientSection, ingredientSectionIndex) in recipeSection.ingredientSections"
:key="recipeSection.recipeId + recipeSectionIndex + ingredientSectionIndex"
>
-
+
{{ ingredientSection.sectionName }}
@@ -114,12 +158,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',
},
]"
@@ -131,15 +175,14 @@
-
diff --git a/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue b/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
index cdf371078..920641f95 100644
--- a/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
+++ b/frontend/components/Domain/Recipe/RecipeIngredientEditor.vue
@@ -1,102 +1,169 @@
+
-
-
-
+
-
- {{ $globals.icons.arrowUpDown }}
-
+
+
+ {{ $globals.icons.arrowUpDown }}
+
+
-
+
+
+
+
+
+ {{ $globals.icons.alert }}
+
+
+
+ {{ unitErrorTooltip }}
+
+
+
- {{ $t("recipe.press-enter-to-create") }}
+
+ {{ $t("recipe.press-enter-to-create") }}
+
-
+
-
+
+
+
+
+
+ {{ $globals.icons.alert }}
+
+
+
+ {{ foodErrorTooltip }}
+
+
+
- {{ $t("recipe.press-enter-to-create") }}
+
+ {{ $t("recipe.press-enter-to-create") }}
+
-
+
-
+
-
- {{ $globals.icons.arrowUpDown }}
-
-
+ />
-
- {{ $t("recipe.original-text-with-value", { originalText: value.originalText }) }}
+
+ {{ $t("recipe.original-text-with-value", { originalText: model.originalText }) }}
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeIngredients.vue b/frontend/components/Domain/Recipe/RecipeIngredients.vue
index 63f793b32..06ac087ca 100644
--- a/frontend/components/Domain/Recipe/RecipeIngredients.vue
+++ b/frontend/components/Domain/Recipe/RecipeIngredients.vue
@@ -1,92 +1,102 @@
-
-
{{ $t("recipe.ingredients") }}
-
+
+
+ {{ $t("recipe.ingredients") }}
+
+
-
+
- {{ ingredient.title }}
-
+
+ {{ ingredient.title }}
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
+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..e6710973f 100644
--- a/frontend/components/Domain/Recipe/RecipeNutrition.vue
+++ b/frontend/components/Domain/Recipe/RecipeNutrition.vue
@@ -4,84 +4,89 @@
{{ $t("recipe.nutrition") }}
-
+
-
+
+ density="compact"
+ :model-value="modelValue[key]"
+ :label="labels[key].label"
+ :suffix="labels[key].suffix"
+ type="number"
+ autocomplete="off"
+ variant="underlined"
+ @update:model-value="updateValue(key, $event)"
+ />
-
-
-
-
- {{ item.label }}
- {{ item.value }}
- {{ item.suffix }}
-
-
+
+
+
+ {{ item.label }}
+
+ {{ item.value }}
+
+ {{ item.suffix }}
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
index 91f447886..18b9ac672 100644
--- a/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerDialog.vue
@@ -1,36 +1,58 @@
-
+
-
-
- {{ itemType === Organizer.Tool ? $globals.icons.potSteam :
- itemType === Organizer.Category ? $globals.icons.categories :
- $globals.icons.tags }}
+
+
+ {{ itemType === Organizer.Tool ? $globals.icons.potSteam
+ : itemType === Organizer.Category ? $globals.icons.categories
+ : $globals.icons.tags }}
{{ properties.title }}
-
+
-
+
-
+ />
+
-
-
-
+
+
+
@@ -38,119 +60,93 @@
-
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue b/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
index d00172b01..e450378cf 100644
--- a/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerPage.vue
@@ -1,6 +1,9 @@
-
+
-{{ $t("general.confirm-delete-generic-with-name", { name: $t(translationKey) }) }}
- {{ deleteTarget.name }}
+ {{ $t("general.confirm-delete-generic-with-name", { name: $t(translationKey) }) }}
+
+ {{ deleteTarget.name }}
+
-
+
-
-
+
+
@@ -27,32 +46,61 @@
-
+ />
-
-
+
+
{{ icon }}
-
+
-
-
+
+
-
-
+
+
-
-
+
+
{{ icon }}
@@ -60,7 +108,7 @@
{{ item.name }}
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
index 26dd1a84d..6706eb582 100644
--- a/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
+++ b/frontend/components/Domain/Recipe/RecipeOrganizerSelector.vue
@@ -1,220 +1,183 @@
-
+
- {{ 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..c39cb0468 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..52e4af3c1 100644
--- a/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
+++ b/frontend/components/Domain/Recipe/RecipePage/RecipePageParts/RecipePageScale.vue
@@ -1,62 +1,38 @@
-
-
-
-
- {{ $t("recipe.edit-scale") }}
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipePage/index.ts b/frontend/components/Domain/Recipe/RecipePage/index.ts
deleted file mode 100644
index 836372a2a..000000000
--- a/frontend/components/Domain/Recipe/RecipePage/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import RecipePage from "./RecipePage.vue";
-
-export default RecipePage;
diff --git a/frontend/components/Domain/Recipe/RecipePrintContainer.vue b/frontend/components/Domain/Recipe/RecipePrintContainer.vue
index e8ff679a2..7338a0de4 100644
--- a/frontend/components/Domain/Recipe/RecipePrintContainer.vue
+++ b/frontend/components/Domain/Recipe/RecipePrintContainer.vue
@@ -1,28 +1,24 @@
-
-
-
+
+
+
-
@@ -39,6 +35,8 @@ export default defineComponent({
.v-main {
display: block;
+ padding: 0 !important;
+ margin: 0 !important;
}
.v-main__wrap {
diff --git a/frontend/components/Domain/Recipe/RecipePrintView.vue b/frontend/components/Domain/Recipe/RecipePrintView.vue
index e1d792cb5..68168dcaa 100644
--- a/frontend/components/Domain/Recipe/RecipePrintView.vue
+++ b/frontend/components/Domain/Recipe/RecipePrintView.vue
@@ -1,47 +1,53 @@
+
-
-
+
-
+
-
+
{{ $globals.icons.primary }}
{{ recipe.name }}
-
-
-
+
+
+
{{ $globals.icons.potSteam }}
-
-
+
+
-
-
+
@@ -51,22 +57,28 @@
- {{ $t("recipe.ingredients") }}
-
+ {{ $t("recipe.ingredients") }}
+
+
-
+
{{ ingredientSection.ingredients[0].title }}
-
@@ -74,19 +86,35 @@
-
-
{{ $t("recipe.instructions") }}
-
+
+ {{ $t("recipe.instructions") }}
+
+
-
+
{{ step.title }}
- {{ step.summary ? step.summary : $t("recipe.step-index", { step: stepIndex + instructionSection.stepOffset + 1 }) }}
-
+
+ {{ step.summary ? step.summary : $t("recipe.step-index", {
+ step: stepIndex
+ + instructionSection.stepOffset
+ + 1,
+ }) }}
+
+
@@ -94,13 +122,19 @@
-
+
-
+
@@ -108,13 +142,17 @@
-
{{ $t("recipe.nutrition") }}
+
+ {{ $t("recipe.nutrition") }}
+
-
+
{{ labels[key].label }} |
{{ value ? (labels[key].suffix ? `${value} ${labels[key].suffix}` : value) : '-' }} |
@@ -122,26 +160,23 @@
-
-
+
+
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeRating.vue b/frontend/components/Domain/Recipe/RecipeRating.vue
index e8cd6c041..e9ee6e904 100644
--- a/frontend/components/Domain/Recipe/RecipeRating.vue
+++ b/frontend/components/Domain/Recipe/RecipeRating.vue
@@ -1,31 +1,30 @@
-
-
+
-
@@ -33,10 +32,10 @@
diff --git a/frontend/components/Domain/Recipe/RecipeSearchFilterSelector.vue b/frontend/components/Domain/Recipe/RecipeSearchFilterSelector.vue
deleted file mode 100644
index 5b57e787e..000000000
--- a/frontend/components/Domain/Recipe/RecipeSearchFilterSelector.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
- {{ $t("search.include") }}
-
-
- {{ $t("search.exclude") }}
-
-
-
-
- {{ $t("search.and") }}
-
-
- {{ $t("search.or") }}
-
-
-
-
-
-
-
-
diff --git a/frontend/components/Domain/Recipe/RecipeSettingsMenu.vue b/frontend/components/Domain/Recipe/RecipeSettingsMenu.vue
index 85f4652ab..7e5fff235 100644
--- a/frontend/components/Domain/Recipe/RecipeSettingsMenu.vue
+++ b/frontend/components/Domain/Recipe/RecipeSettingsMenu.vue
@@ -1,9 +1,18 @@
-
-
-
-
+
+
+
+
{{ $globals.icons.cog }}
{{ $t("general.settings") }}
@@ -15,32 +24,24 @@
{{ $t("recipe.recipe-settings") }}
-
+
-
+
-
diff --git a/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue b/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
index a0e0e8cb8..a2dc3f55a 100644
--- a/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
+++ b/frontend/components/Domain/Recipe/RecipeSettingsSwitches.vue
@@ -1,51 +1,38 @@
+ />
-
diff --git a/frontend/components/Domain/Recipe/RecipeSuggestion.vue b/frontend/components/Domain/Recipe/RecipeSuggestion.vue
index b6b100877..25e044ccb 100644
--- a/frontend/components/Domain/Recipe/RecipeSuggestion.vue
+++ b/frontend/components/Domain/Recipe/RecipeSuggestion.vue
@@ -12,23 +12,21 @@
/>
-
+
-
{{ organizer.icon }}
-
- {{ $tc("recipe-finder.missing") }}:
+
+
+ {{ $t("recipe-finder.missing") }}:
-
+
{{ organizer.getLabel(item.item) }}
@@ -41,10 +39,9 @@
-
diff --git a/frontend/components/Domain/Recipe/RecipeTimeCard.vue b/frontend/components/Domain/Recipe/RecipeTimeCard.vue
index 4aa555ce5..f5abbe4ce 100644
--- a/frontend/components/Domain/Recipe/RecipeTimeCard.vue
+++ b/frontend/components/Domain/Recipe/RecipeTimeCard.vue
@@ -1,105 +1,130 @@
-
+
-
-
-
- {{ $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 }}
+
-
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..7677f4f57 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..9b9068891 100644
--- a/frontend/components/global/BaseCardSectionTitle.vue
+++ b/frontend/components/global/BaseCardSectionTitle.vue
@@ -7,25 +7,30 @@
'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..c130509fe 100644
--- a/frontend/composables/api/api-client.ts
+++ b/frontend/composables/api/api-client.ts
@@ -1,18 +1,18 @@
-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, AxiosRequestConfig } from "axios";
+import type { Composer } from "vue-i18n";
+import type { ApiRequestInstance, RequestResponse } from "~/lib/api/types/non-generated";
import { AdminAPI, PublicApi, UserApi } from "~/lib/api";
import { PublicExploreApi } from "~/lib/api/client-public";
const request = {
async safe(
- funcCall: (url: string, data: U) => Promise>,
+ funcCall: (url: string, data: U, config?: AxiosRequestConfig) => Promise>,
url: string,
- data: U
+ data: U,
+ config?: AxiosRequestConfig,
): Promise> {
let error = null;
- const response = await funcCall(url, data).catch(function (e) {
+ const response = await funcCall(url, data, config).catch(function (e) {
console.log(e);
// Insert Generic Error Handling Here
error = e;
@@ -22,11 +22,11 @@ const request = {
},
};
-function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
+function getRequests(axiosInstance: AxiosInstance): ApiRequestInstance {
return {
- async get(url: string, params = {}): Promise> {
+ async get(url: string, params = {}, config?: AxiosRequestConfig): Promise> {
let error = null;
- const response = await axiosInstance.get(url, params).catch((e) => {
+ const response = await axiosInstance.get(url, { ...config, params }).catch((e) => {
error = e;
});
if (response != null) {
@@ -35,52 +35,52 @@ function getRequests(axiosInstance: NuxtAxiosInstance): ApiRequestInstance {
return { response: null, error, data: null };
},
- async post(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
- return await request.safe(axiosInstance.post, url, data);
+ async post(url: string, data: U, config?: AxiosRequestConfig) {
+ return await request.safe(axiosInstance.post, url, data, config);
},
- async put(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
- return await request.safe(axiosInstance.put, url, data);
+ async put(url: string, data: U, config?: AxiosRequestConfig) {
+ return await request.safe(axiosInstance.put, url, data, config);
},
- async patch>(url: string, data: U) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
- return await request.safe(axiosInstance.patch, url, data);
+ async patch>(url: string, data: U, config?: AxiosRequestConfig) {
+ return await request.safe(axiosInstance.patch, url, data, config);
},
- async delete(url: string) {
- // eslint-disable-next-line @typescript-eslint/unbound-method
- return await request.safe(axiosInstance.delete, url, undefined);
+ async delete(url: string, config?: AxiosRequestConfig) {
+ return await request.safe(axiosInstance.delete, url, undefined, config);
},
};
}
-export const useRequests = function (): ApiRequestInstance {
- const { $axios, i18n } = useContext();
+export const useRequests = function (i18n?: Composer): ApiRequestInstance {
+ const { $axios } = useNuxtApp();
+ if (!i18n) {
+ // Only works in a setup block
+ i18n = useI18n();
+ }
- $axios.setHeader("Accept-Language", i18n.locale);
+ $axios.defaults.headers.common["Accept-Language"] = i18n.locale.value;
return getRequests($axios);
};
-export const useAdminApi = function (): AdminAPI {
- const requests = useRequests();
+export const useAdminApi = function (i18n?: Composer): AdminAPI {
+ const requests = useRequests(i18n);
return new AdminAPI(requests);
};
-export const useUserApi = function (): UserApi {
- const requests = useRequests();
+export const useUserApi = function (i18n?: Composer): UserApi {
+ const requests = useRequests(i18n);
return new UserApi(requests);
};
-export const usePublicApi = function (): PublicApi {
- const requests = useRequests();
+export const usePublicApi = function (i18n?: Composer): PublicApi {
+ const requests = useRequests(i18n);
return new PublicApi(requests);
};
-export const usePublicExploreApi = function (groupSlug: string): PublicExploreApi {
- const requests = useRequests();
+export const usePublicExploreApi = function (groupSlug: string, i18n?: Composer): PublicExploreApi {
+ const requests = useRequests(i18n);
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..3fa16a674 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());
+function ingredientMatchesWord(ingredient: RecipeIngredient, word: string) {
+ const searchText = parseIngredientText(ingredient);
+ 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));
+export function useExtractIngredientReferences(recipeIngredients: RecipeIngredient[], activeRefs: string[], text: string): Set {
+ 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)))
+ .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..398c42da3 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 => ({
@@ -16,33 +16,27 @@ describe(parseIngredientText.name, () => {
...overrides,
});
- test("uses ingredient note if disableAmount: true", () => {
- const ingredient = createRecipeIngredient({ note: "foo" });
-
- expect(parseIngredientText(ingredient, true)).toEqual("foo");
- });
-
test("adds note section if note present", () => {
const ingredient = createRecipeIngredient({ note: "custom note" });
- expect(parseIngredientText(ingredient, false)).toContain("custom note");
+ expect(parseIngredientText(ingredient)).toContain("custom note");
});
test("ingredient text with fraction", () => {
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: { fraction: true, id: "1", name: "cup" } });
- expect(parseIngredientText(ingredient, false, 1, true)).contain("11").and.to.contain("2");
+ expect(parseIngredientText(ingredient, 1, true)).contain("11").and.to.contain("2");
});
test("ingredient text with fraction when unit is null", () => {
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: undefined });
- expect(parseIngredientText(ingredient, false, 1, true)).contain("11").and.to.contain("2");
+ expect(parseIngredientText(ingredient, 1, true)).contain("11").and.to.contain("2");
});
test("ingredient text with fraction no formatting", () => {
const ingredient = createRecipeIngredient({ quantity: 1.5, unit: { fraction: true, id: "1", name: "cup" } });
- const result = parseIngredientText(ingredient, false, 1, false);
+ const result = parseIngredientText(ingredient, 1, false);
expect(result).not.contain("<");
expect(result).not.contain(">");
@@ -52,86 +46,86 @@ describe(parseIngredientText.name, () => {
test("sanitizes html", () => {
const ingredient = createRecipeIngredient({ note: "" });
- expect(parseIngredientText(ingredient, false)).not.toContain("
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 69b47c973..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
@@ -10,6 +10,7 @@ export interface AdminAboutInfo {
version: string;
demoStatus: boolean;
allowSignup: boolean;
+ allowPasswordLogin: boolean;
defaultGroupSlug?: string | null;
defaultHouseholdSlug?: string | null;
enableOidc: boolean;
@@ -41,6 +42,7 @@ export interface AppInfo {
version: string;
demoStatus: boolean;
allowSignup: boolean;
+ allowPasswordLogin: boolean;
defaultGroupSlug?: string | null;
defaultHouseholdSlug?: string | null;
enableOidc: boolean;
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 36e2b991d..fde0a9c8e 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..b01fe7245 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
@@ -22,7 +22,6 @@ export interface CreateHouseholdPreferences {
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
- recipeDisableAmount?: boolean;
}
export interface CreateInviteToken {
uses: number;
@@ -191,7 +190,6 @@ export interface ReadHouseholdPreferences {
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
- recipeDisableAmount?: boolean;
id: string;
}
export interface HouseholdUserSummary {
@@ -269,7 +267,6 @@ export interface SaveHouseholdPreferences {
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
- recipeDisableAmount?: boolean;
householdId: string;
}
export interface SaveInviteToken {
@@ -303,8 +300,6 @@ export interface RecipeIngredient {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean | null;
- disableAmount?: boolean;
display?: string;
title?: string | null;
originalText?: string | null;
@@ -409,8 +404,6 @@ export interface ShoppingListItemBase {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean;
- disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
@@ -427,8 +420,6 @@ export interface ShoppingListItemCreate {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean;
- disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
@@ -453,8 +444,6 @@ export interface ShoppingListItemOut {
unit?: IngredientUnit | null;
food?: IngredientFood | null;
note?: string | null;
- isFood?: boolean;
- disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
@@ -494,8 +483,6 @@ export interface ShoppingListItemUpdate {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean;
- disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
@@ -513,8 +500,6 @@ export interface ShoppingListItemUpdateBulk {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean;
- disableAmount?: boolean | null;
display?: string;
shoppingListId: string;
checked?: boolean;
@@ -679,14 +664,11 @@ export interface UpdateHouseholdPreferences {
recipeShowAssets?: boolean;
recipeLandscapeView?: boolean;
recipeDisableComments?: boolean;
- recipeDisableAmount?: boolean;
}
export interface RecipeIngredientBase {
quantity?: number | null;
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean | null;
- disableAmount?: boolean | null;
display?: string;
}
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..801fdd1ca 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 { AxiosRequestConfig, AxiosResponse } from "axios";
export type NoUndefinedField = { [P in keyof T]-?: NoUndefinedField> };
@@ -9,11 +9,11 @@ export interface RequestResponse {
}
export interface ApiRequestInstance {
- get(url: string, data?: unknown): Promise>;
- post(url: string, data: unknown): Promise>;
- put(url: string, data: U): Promise>;
- patch>(url: string, data: U): Promise>;
- delete(url: string): Promise>;
+ get(url: string, data?: unknown, config?: AxiosRequestConfig): Promise>;
+ post(url: string, data: unknown, config?: AxiosRequestConfig): Promise>;
+ put(url: string, data: U, config?: AxiosRequestConfig): Promise>;
+ patch>(url: string, data: U, config?: AxiosRequestConfig): Promise>;
+ delete(url: string, config?: AxiosRequestConfig): Promise>;
}
export interface PaginationData {
diff --git a/frontend/lib/api/types/recipe.ts b/frontend/lib/api/types/recipe.ts
index a526e602e..29e4f98bd 100644
--- a/frontend/lib/api/types/recipe.ts
+++ b/frontend/lib/api/types/recipe.ts
@@ -31,7 +31,6 @@ export interface RecipeSettings {
showAssets?: boolean;
landscapeView?: boolean;
disableComments?: boolean;
- disableAmount?: boolean;
locked?: boolean;
}
export interface AssignTags {
@@ -212,8 +211,6 @@ export interface RecipeIngredient {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean | null;
- disableAmount?: boolean;
display?: string;
title?: string | null;
originalText?: string | null;
@@ -226,7 +223,7 @@ export interface Recipe {
groupId?: string;
name?: string | null;
slug?: string;
- image?: unknown;
+ image?: string;
recipeServings?: number;
recipeYieldQuantity?: number;
recipeYield?: string | null;
@@ -347,8 +344,6 @@ export interface RecipeIngredientBase {
unit?: IngredientUnit | CreateIngredientUnit | null;
food?: IngredientFood | CreateIngredientFood | null;
note?: string | null;
- isFood?: boolean | null;
- disableAmount?: boolean | null;
display?: string;
}
export interface RecipeLastMade {
@@ -512,7 +507,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..f97ddc53f 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, recipeScale: number) {
+ return await this.requests.post(routes.groupRecipeActionsIdTriggerRecipeSlug(id, recipeSlug), { recipe_scale: recipeScale });
}
+}
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 {
diff --git a/frontend/lib/api/user/organizer-categories.ts b/frontend/lib/api/user/organizer-categories.ts
index 758952f78..c8249c380 100644
--- a/frontend/lib/api/user/organizer-categories.ts
+++ b/frontend/lib/api/user/organizer-categories.ts
@@ -1,6 +1,6 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
-import { CategoryIn, RecipeCategoryResponse } from "~/lib/api/types/recipe";
+import type { CategoryIn, RecipeCategoryResponse } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
diff --git a/frontend/lib/api/user/organizer-tags.ts b/frontend/lib/api/user/organizer-tags.ts
index eb6ef9ad4..0d6685cee 100644
--- a/frontend/lib/api/user/organizer-tags.ts
+++ b/frontend/lib/api/user/organizer-tags.ts
@@ -1,6 +1,6 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
-import { RecipeTagResponse, TagIn } from "~/lib/api/types/recipe";
+import type { RecipeTagResponse, TagIn } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
diff --git a/frontend/lib/api/user/organizer-tools.ts b/frontend/lib/api/user/organizer-tools.ts
index c3830984d..37a5f22e3 100644
--- a/frontend/lib/api/user/organizer-tools.ts
+++ b/frontend/lib/api/user/organizer-tools.ts
@@ -1,6 +1,6 @@
import { BaseCRUDAPI } from "../base/base-clients";
import { config } from "../config";
-import { RecipeTool, RecipeToolCreate, RecipeToolResponse } from "~/lib/api/types/recipe";
+import type { RecipeTool, RecipeToolCreate, RecipeToolResponse } from "~/lib/api/types/recipe";
const prefix = config.PREFIX + "/organizers";
diff --git a/frontend/lib/api/user/recipe-bulk-actions.ts b/frontend/lib/api/user/recipe-bulk-actions.ts
index 1f95d439e..a1f234e69 100644
--- a/frontend/lib/api/user/recipe-bulk-actions.ts
+++ b/frontend/lib/api/user/recipe-bulk-actions.ts
@@ -1,10 +1,10 @@
import { BaseAPI } from "../base/base-clients";
-import { AssignCategories, AssignSettings, AssignTags, DeleteRecipes, ExportRecipes } from "~/lib/api/types/recipe";
-import { GroupDataExport } from "~/lib/api/types/group";
+import type { AssignCategories, AssignSettings, AssignTags, DeleteRecipes, ExportRecipes } from "~/lib/api/types/recipe";
+import type { GroupDataExport } from "~/lib/api/types/group";
// Many bulk actions return nothing
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
-interface BulkActionResponse {}
+
+type BulkActionResponse = object;
const prefix = "/api";
diff --git a/frontend/lib/api/user/recipe-foods.ts b/frontend/lib/api/user/recipe-foods.ts
index ec3f26080..3ec604729 100644
--- a/frontend/lib/api/user/recipe-foods.ts
+++ b/frontend/lib/api/user/recipe-foods.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreateIngredientFood, IngredientFood } from "~/lib/api/types/recipe";
+import type { CreateIngredientFood, IngredientFood } from "~/lib/api/types/recipe";
const prefix = "/api";
@@ -14,7 +14,6 @@ export class FoodAPI extends BaseCRUDAPI {
itemRoute = routes.foodsFood;
merge(fromId: string, toId: string) {
- // @ts-ignore TODO: fix this
return this.requests.put(routes.merge, { fromFood: fromId, toFood: toId });
}
}
diff --git a/frontend/lib/api/user/recipe-units.ts b/frontend/lib/api/user/recipe-units.ts
index be8228a4c..13b7b02c2 100644
--- a/frontend/lib/api/user/recipe-units.ts
+++ b/frontend/lib/api/user/recipe-units.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import { CreateIngredientUnit, IngredientUnit } from "~/lib/api/types/recipe";
+import type { CreateIngredientUnit, IngredientUnit } from "~/lib/api/types/recipe";
const prefix = "/api";
@@ -14,7 +14,6 @@ export class UnitAPI extends BaseCRUDAPI {
itemRoute = routes.unitsUnit;
merge(fromId: string, toId: string) {
- // @ts-ignore TODO: fix this
return this.requests.put(routes.merge, { fromUnit: fromId, toUnit: toId });
}
}
diff --git a/frontend/lib/api/user/recipes/recipe-comments.ts b/frontend/lib/api/user/recipes/recipe-comments.ts
index 6d9122dcb..73a3b85b2 100644
--- a/frontend/lib/api/user/recipes/recipe-comments.ts
+++ b/frontend/lib/api/user/recipes/recipe-comments.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../../base/base-clients";
-import { RecipeCommentCreate, RecipeCommentOut, RecipeCommentUpdate } from "~/lib/api/types/recipe";
+import type { RecipeCommentCreate, RecipeCommentOut, RecipeCommentUpdate } from "~/lib/api/types/recipe";
const prefix = "/api";
diff --git a/frontend/lib/api/user/recipes/recipe-share.ts b/frontend/lib/api/user/recipes/recipe-share.ts
index 437078a54..306d378f6 100644
--- a/frontend/lib/api/user/recipes/recipe-share.ts
+++ b/frontend/lib/api/user/recipes/recipe-share.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../../base/base-clients";
-import { RecipeShareToken, RecipeShareTokenCreate } from "~/lib/api/types/recipe";
+import type { RecipeShareToken, RecipeShareTokenCreate } from "~/lib/api/types/recipe";
const prefix = "/api";
diff --git a/frontend/lib/api/user/recipes/recipe.ts b/frontend/lib/api/user/recipes/recipe.ts
index b022efaac..c2e923c2f 100644
--- a/frontend/lib/api/user/recipes/recipe.ts
+++ b/frontend/lib/api/user/recipes/recipe.ts
@@ -2,7 +2,7 @@ import { BaseCRUDAPI } from "../../base/base-clients";
import { route } from "../../base";
import { CommentsApi } from "./recipe-comments";
import { RecipeShareApi } from "./recipe-share";
-import {
+import type {
Recipe,
CreateRecipe,
RecipeAsset,
@@ -17,7 +17,7 @@ import {
RecipeTimelineEventOut,
RecipeTimelineEventUpdate,
} from "~/lib/api/types/recipe";
-import { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
+import type { ApiRequestInstance, PaginationData } from "~/lib/api/types/non-generated";
export type Parser = "nlp" | "brute" | "openai";
@@ -113,9 +113,9 @@ export class RecipeAPI extends BaseCRUDAPI {
});
}
- async getSuggestions(q: RecipeSuggestionQuery, foods: string[] | null = null, tools: string[]| null = null) {
+ async getSuggestions(q: RecipeSuggestionQuery, foods: string[] | null = null, tools: string[] | null = null) {
return await this.requests.get(
- route(routes.recipesSuggestions, { ...q, foods, tools })
+ route(routes.recipesSuggestions, { ...q, foods, tools }),
);
}
@@ -157,17 +157,19 @@ export class RecipeAPI extends BaseCRUDAPI {
return await this.requests.post(routes.recipesCreateUrlBulk, payload);
}
- async createOneFromImage(fileObject: Blob | File, fileName: string, translateLanguage: string | null = null) {
+ async createOneFromImages(fileObjects: (Blob | File)[], translateLanguage: string | null = null) {
const formData = new FormData();
- formData.append("images", fileObject);
- formData.append("extension", fileName.split(".").pop() ?? "");
- let apiRoute = routes.recipesCreateFromImage
+ fileObjects.forEach((file) => {
+ formData.append("images", file);
+ });
+
+ let apiRoute = routes.recipesCreateFromImage;
if (translateLanguage) {
- apiRoute = `${apiRoute}?translateLanguage=${translateLanguage}`
+ apiRoute = `${apiRoute}?translateLanguage=${translateLanguage}`;
}
- return await this.requests.post(apiRoute, formData);
+ return await this.requests.post(apiRoute, formData, { timeout: 120000 });
}
async parseIngredients(parser: Parser, ingredients: Array) {
@@ -197,7 +199,7 @@ export class RecipeAPI extends BaseCRUDAPI {
}
async updateLastMade(recipeSlug: string, timestamp: string) {
- return await this.requests.patch(routes.recipesSlugLastMade(recipeSlug), { timestamp })
+ return await this.requests.patch(routes.recipesSlugLastMade(recipeSlug), { timestamp });
}
async createTimelineEvent(payload: RecipeTimelineEventIn) {
@@ -207,7 +209,7 @@ export class RecipeAPI extends BaseCRUDAPI {
async updateTimelineEvent(eventId: string, payload: RecipeTimelineEventUpdate) {
return await this.requests.put(
routes.recipesTimelineEventId(eventId),
- payload
+ payload,
);
}
@@ -217,10 +219,7 @@ export class RecipeAPI extends BaseCRUDAPI {
async getAllTimelineEvents(page = 1, perPage = -1, params = {} as any) {
return await this.requests.get>(
- routes.recipesTimelineEvent,
- {
- params: { page, perPage, ...params },
- }
+ routes.recipesTimelineEvent, { page, perPage, ...params },
);
}
diff --git a/frontend/lib/api/user/upload.ts b/frontend/lib/api/user/upload.ts
index d5e873028..8f742b51e 100644
--- a/frontend/lib/api/user/upload.ts
+++ b/frontend/lib/api/user/upload.ts
@@ -2,6 +2,8 @@ import { BaseAPI } from "../base/base-clients";
export class UploadFile extends BaseAPI {
file(url: string, fileObject: any) {
- return this.requests.post(url, fileObject);
+ const { $axios } = useNuxtApp();
+ return $axios.post(url, fileObject, { headers: { "Content-Type": "multipart/form-data" } });
+ // return this.requests.post(url, fileObject);
}
}
diff --git a/frontend/lib/api/user/user-registration.ts b/frontend/lib/api/user/user-registration.ts
index e1921d4cf..2eebcc288 100644
--- a/frontend/lib/api/user/user-registration.ts
+++ b/frontend/lib/api/user/user-registration.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { CreateUserRegistration } from "~/lib/api/types/user";
+import type { CreateUserRegistration } from "~/lib/api/types/user";
const prefix = "/api";
diff --git a/frontend/lib/api/user/users.ts b/frontend/lib/api/user/users.ts
index a5fc8519a..c1dbfed2f 100644
--- a/frontend/lib/api/user/users.ts
+++ b/frontend/lib/api/user/users.ts
@@ -1,5 +1,5 @@
import { BaseCRUDAPI } from "../base/base-clients";
-import {
+import type {
ChangePassword,
DeleteTokenResponse,
LongLiveTokenIn,
diff --git a/frontend/lib/api/user/utils.ts b/frontend/lib/api/user/utils.ts
index cca69ecf3..7837f5816 100644
--- a/frontend/lib/api/user/utils.ts
+++ b/frontend/lib/api/user/utils.ts
@@ -1,5 +1,5 @@
import { BaseAPI } from "../base/base-clients";
-import { FileTokenResponse } from "~/lib/api/types/response";
+import type { FileTokenResponse } from "~/lib/api/types/response";
const prefix = "/api";
diff --git a/frontend/lib/icons/icon-type.ts b/frontend/lib/icons/icon-type.ts
index c85214f53..b9ec07c7e 100644
--- a/frontend/lib/icons/icon-type.ts
+++ b/frontend/lib/icons/icon-type.ts
@@ -1,3 +1,3 @@
-import { icons } from "./icons";
+import type { icons } from "./icons";
export type Icon = typeof icons;
diff --git a/frontend/lib/icons/icons.ts b/frontend/lib/icons/icons.ts
index cb72d19ed..6866adf04 100644
--- a/frontend/lib/icons/icons.ts
+++ b/frontend/lib/icons/icons.ts
@@ -66,6 +66,7 @@ import {
mdiAlert,
mdiCheckboxMarkedCircle,
mdiInformation,
+ mdiInformationOutline,
mdiInformationVariant,
mdiBellAlert,
mdiRefreshCircle,
@@ -105,6 +106,7 @@ import {
mdiCalendarWeekBegin,
mdiOpenInNew,
mdiCheck,
+ mdiCheckBold,
mdiBroom,
mdiCartCheck,
mdiArrowLeftBold,
@@ -147,7 +149,9 @@ import {
mdiSilverwareForkKnife,
mdiCodeTags,
mdiKnife,
- mdiCookie
+ mdiCookie,
+ mdiBellPlus,
+ mdiLinkVariantPlus,
} from "@mdi/js";
export const icons = {
@@ -172,6 +176,7 @@ export const icons = {
arrowUpDown: mdiDrag,
backupRestore: mdiBackupRestore,
bellAlert: mdiBellAlert,
+ bellPlus: mdiBellPlus,
broom: mdiBroom,
calendar: mdiCalendar,
calendarMinus: mdiCalendarMinus,
@@ -181,6 +186,7 @@ export const icons = {
calendarWeekBegin: mdiCalendarWeekBegin,
cartCheck: mdiCartCheck,
check: mdiCheck,
+ checkBold: mdiCheckBold,
checkboxBlankOutline: mdiCheckboxBlankOutline,
checkboxOutline: mdiCheckboxOutline,
checkboxMarkedCircle: mdiCheckboxMarkedCircle,
@@ -228,8 +234,10 @@ export const icons = {
household: mdiHomeAccount,
import: mdiImport,
information: mdiInformation,
+ informationOutline: mdiInformationOutline,
informationVariant: mdiInformationVariant,
link: mdiLink,
+ linkVariantPlus: mdiLinkVariantPlus,
lock: mdiLock,
logout: mdiLogout,
menu: mdiMenu,
diff --git a/frontend/lib/validators/inputs.test.ts b/frontend/lib/validators/inputs.test.ts
index 7bb0f3515..6b8ff2119 100644
--- a/frontend/lib/validators/inputs.test.ts
+++ b/frontend/lib/validators/inputs.test.ts
@@ -1,5 +1,6 @@
import { expect, test } from "vitest";
import { required, email, whitespace, url, minLength, maxLength } from "./inputs";
+
export { scorePassword } from "./password";
test("validator required", () => {
diff --git a/frontend/lib/validators/inputs.ts b/frontend/lib/validators/inputs.ts
index 44bdfd402..96a03866c 100644
--- a/frontend/lib/validators/inputs.ts
+++ b/frontend/lib/validators/inputs.ts
@@ -1,5 +1,5 @@
-const EMAIL_REGEX =
- /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+const EMAIL_REGEX
+ = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
diff --git a/frontend/middleware/admin-only.ts b/frontend/middleware/admin-only.ts
index b55f603d5..2b05f5114 100644
--- a/frontend/middleware/admin-only.ts
+++ b/frontend/middleware/admin-only.ts
@@ -1,10 +1,7 @@
-interface AdminRedirectParams {
- $auth: any
- redirect: (path: string) => void
-}
-export default function ({ $auth, redirect }: AdminRedirectParams) {
- // If the user is not an admin redirect to the home page
- if (!$auth.user.admin) {
- return redirect("/")
- }
-}
+export default defineNuxtRouteMiddleware(() => {
+ const { user } = useMealieAuth();
+ // If the user is not an admin redirect to the home page
+ if (!user.value?.admin) {
+ navigateTo("/");
+ }
+});
diff --git a/frontend/middleware/advanced-only.ts b/frontend/middleware/advanced-only.ts
index 378e3044b..12b02d44b 100644
--- a/frontend/middleware/advanced-only.ts
+++ b/frontend/middleware/advanced-only.ts
@@ -1,11 +1,8 @@
-interface AdvancedOnlyRedirectParams {
- $auth: any
- redirect: (path: string) => void
-}
-export default function ({ $auth, redirect }: AdvancedOnlyRedirectParams) {
- // If the user is not allowed to access advanced features redirect to the home page
- if (!$auth.user.advanced) {
- console.warn("User is not allowed to access advanced features");
- return redirect("/")
- }
-}
+export default defineNuxtRouteMiddleware(() => {
+ const { user } = useMealieAuth();
+ // If the user is not allowed to access advanced features redirect to the home page
+ if (!user.value?.advanced) {
+ console.warn("User is not allowed to access advanced features");
+ navigateTo("/");
+ }
+});
diff --git a/frontend/middleware/can-manage-household-only.ts b/frontend/middleware/can-manage-household-only.ts
index 0e4509e9e..2bde9c674 100644
--- a/frontend/middleware/can-manage-household-only.ts
+++ b/frontend/middleware/can-manage-household-only.ts
@@ -1,11 +1,7 @@
-interface CanManageRedirectParams {
- $auth: any
- redirect: (path: string) => void
-}
-export default function ({ $auth, redirect }: CanManageRedirectParams) {
+const { user } = useMealieAuth();
+export default defineNuxtRouteMiddleware(() => {
// If the user is not allowed to manage group settings redirect to the home page
- if (!$auth.user?.canManageHousehold) {
- console.warn("User is not allowed to manage household settings");
- return redirect("/");
+ if (!user.value?.canManageHousehold) {
+ navigateTo("/");
}
-}
+});
diff --git a/frontend/middleware/can-manage-only.ts b/frontend/middleware/can-manage-only.ts
index 9c09819ca..4b5cd48b0 100644
--- a/frontend/middleware/can-manage-only.ts
+++ b/frontend/middleware/can-manage-only.ts
@@ -1,12 +1,8 @@
-interface CanManageRedirectParams {
- $auth: any
- redirect: (path: string) => void
-}
-export default function ({ $auth, redirect }: CanManageRedirectParams) {
+export default defineNuxtRouteMiddleware(() => {
+ const { user } = useMealieAuth();
// If the user is not allowed to manage group settings redirect to the home page
- console.log($auth.user)
- if (!$auth.user.canManage) {
+ if (!user.value?.canManage) {
console.warn("User is not allowed to manage group settings");
- return redirect("/")
+ navigateTo("/");
}
-}
+});
diff --git a/frontend/middleware/can-organize-only.ts b/frontend/middleware/can-organize-only.ts
index 93d6c5c5a..0127a67d7 100644
--- a/frontend/middleware/can-organize-only.ts
+++ b/frontend/middleware/can-organize-only.ts
@@ -1,11 +1,8 @@
-interface CanOrganizeRedirectParams {
- $auth: any
- redirect: (path: string) => void
-}
-export default function ({ $auth, redirect }: CanOrganizeRedirectParams) {
- // If the user is not allowed to organize redirect to the home page
- if (!$auth.user.canOrganize) {
- console.warn("User is not allowed to organize data");
- return redirect("/")
- }
-}
+export default defineNuxtRouteMiddleware(() => {
+ const { user } = useMealieAuth();
+ // If the user is not allowed to organize data redirect to the home page
+ if (!user.value?.canOrganize) {
+ console.warn("User is not allowed to organize data");
+ navigateTo("/");
+ }
+});
diff --git a/frontend/middleware/group-only.ts b/frontend/middleware/group-only.ts
index 84f28f127..efd457c4b 100644
--- a/frontend/middleware/group-only.ts
+++ b/frontend/middleware/group-only.ts
@@ -1,12 +1,7 @@
-interface GroupOnlyRedirectParams {
- $auth: any
- route: any
- redirect: (path: string) => void
-}
-
-export default function ({ $auth, route, redirect }: GroupOnlyRedirectParams) {
+export default defineNuxtRouteMiddleware((to) => {
+ const { user } = useMealieAuth();
// this can only be used for routes that have a groupSlug parameter (e.g. /g/:groupSlug/...)
- if (route.params.groupSlug !== $auth.user.groupSlug) {
- redirect("/")
+ if (to.params.groupSlug !== user.value?.groupSlug) {
+ navigateTo("/");
}
-}
+});
diff --git a/frontend/middleware/pwa-share-target-redirect.global.ts b/frontend/middleware/pwa-share-target-redirect.global.ts
new file mode 100644
index 000000000..071436733
--- /dev/null
+++ b/frontend/middleware/pwa-share-target-redirect.global.ts
@@ -0,0 +1,10 @@
+export default defineNuxtRouteMiddleware((to) => {
+ if (to.path === "/r/create/url") {
+ const { user } = useMealieAuth();
+ const groupSlug = user.value?.groupSlug;
+ if (!groupSlug) {
+ return navigateTo("/login", { redirectCode: 301 });
+ }
+ return navigateTo(`/g/${groupSlug}${to.fullPath}`, { redirectCode: 301 });
+ }
+});
diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js
deleted file mode 100644
index 5ac923a44..000000000
--- a/frontend/nuxt.config.js
+++ /dev/null
@@ -1,538 +0,0 @@
-export default {
- // Global page headers: https://go.nuxtjs.dev/config-head
- target: "static",
- head: {
- title: "Mealie",
- meta: [
- { hid: "og:type", property: "og:type", content: "website" },
- { hid: "og:title", property: "og:title", content: "Mealie" },
- { hid: "og:site_name", property: "og:site_name", content: "Mealie" },
- {
- hid: "og:description",
- property: "og:description",
- content: "Mealie is a recipe management app for your kitchen.",
- },
- {
- hid: "og:image",
- property: "og:image",
- content:
- "https://raw.githubusercontent.com/mealie-recipes/mealie/9571816ac4eed5beacfc0abf6c03eff1427fd0eb/frontend/static/icons/android-chrome-512x512.png",
- },
- { charset: "utf-8" },
- { name: "viewport", content: "width=device-width, initial-scale=1" },
- {
- hid: "description",
- name: "description",
- content: "Mealie is a recipe management app for your kitchen.",
- },
- ],
- link: [
- { hid: "favicon", rel: "icon", type: "image/x-icon", href: "/favicon.ico", "data-n-head": "ssr" },
- { hid: "shortcut icon", rel: "shortcut icon", type: "image/png", href: "/icons/icon-x64.png", "data-n-head": "ssr" },
- { hid: "apple-touch-icon", rel: "apple-touch-icon", type: "image/png", href: "/icons/apple-touch-icon.png", "data-n-head": "ssr" },
- { hid: "mask-icon", rel: "mask-icon", href: "/icons/safari-pinned-tab.svg", "data-n-head": "ssr" }
- ],
- },
-
- env: {
- GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
- },
-
- router: {
- base: process.env.SUB_PATH || "",
- },
-
- layoutTransition: {
- name: "layout",
- mode: "out-in",
- },
-
- // Global CSS: https://go.nuxtjs.dev/config-css
- css: [{ src: "~/assets/css/main.css" }, { src: "~/assets/css/main.css" }, { src: "~/assets/style-overrides.scss" }],
-
- // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
- plugins: ["~/plugins/globals.ts", "~/plugins/theme.ts", "~/plugins/toast.client.ts", "~/plugins/dark-mode.client.ts"],
-
- // Auto import components: https://go.nuxtjs.dev/config-components
- components: true,
-
- // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
- buildModules: [
- // https://go.nuxtjs.dev/typescript
- [
- "@nuxt/typescript-build",
- // Fix slow builds
- // https://github.com/nuxt/nuxt.js/issues/8310#issuecomment-734984360
- {
- typeCheck: {
- typescript: {
- enabled: true,
- mode: "write-tsbuildinfo",
- },
- },
- },
- ],
- // https://go.nuxtjs.dev/vuetify
- "@nuxtjs/vuetify",
- // https://composition-api.nuxtjs.org/getting-started/setup
- "@nuxtjs/composition-api/module",
- // https://vite.nuxtjs.org/getting-started/installation
- "nuxt-vite",
- // https://google-fonts.nuxtjs.org/setup
- "@nuxtjs/google-fonts",
- ],
-
- // Modules: https://go.nuxtjs.dev/config-modules
- modules: [
- // https://go.nuxtjs.dev/axios
- "@nuxtjs/axios",
- // https://go.nuxtjs.dev/pwa
- ...(process.env.NODE_ENV === "production" ? ["@nuxtjs/pwa"] : []),
- // https://i18n.nuxtjs.org/setup
- "@nuxtjs/i18n",
- // https://auth.nuxtjs.org/guide/setup
- "@nuxtjs/auth-next",
- // https://github.com/nuxt-community/proxy-module
- [
- "@nuxtjs/proxy",
- {
- logProvider: () => {
- const provider = {
- log: console.log,
- debug: console.log,
- info: console.info,
- warn: console.warn,
- error: console.error,
- };
-
- return provider;
- },
- logLevel: "debug",
- },
- ],
- ],
-
- googleFonts: {
- fontsPath: "/assets/fonts",
- download: true,
- families: {
- Roboto: [100, 300, 400, 500, 700, 900],
- },
- },
-
- auth: {
- redirect: {
- login: "/login",
- logout: "/login",
- callback: "/login",
- home: "/",
- },
- cookie: {
- prefix: "mealie.auth.",
- options: {
- expires: 7,
- path: "/",
- },
- },
- rewriteRedirects: false,
- // Options
- strategies: {
- local: {
- resetOnError: true,
- token: {
- property: "access_token",
- global: true,
- // required: true,
- // type: 'Bearer'
- },
- user: {
- property: "",
- autoFetch: true,
- },
- endpoints: {
- login: {
- url: "api/auth/token",
- method: "post",
- propertyName: "access_token",
- },
- refresh: { url: "api/auth/refresh", method: "post" },
- logout: { url: "api/auth/logout", method: "post" },
- user: { url: "api/users/self", method: "get" },
- },
- },
- oidc: {
- scheme: "local",
- resetOnError: true,
- token: {
- property: "access_token",
- global: true,
- },
- user: {
- property: "",
- autoFetch: true,
- },
- endpoints: {
- login: {
- url: "api/auth/oauth/callback",
- method: "get",
- },
- logout: { url: "api/auth/logout", method: "post" },
- user: { url: "api/users/self", method: "get" },
- },
- },
- },
- },
-
- i18n: {
- locales: [
- // CODE_GEN_ID: MESSAGE_LOCALES
- { code: "lv-LV", file: "lv-LV.json" },
- { code: "el-GR", file: "el-GR.json" },
- { code: "it-IT", file: "it-IT.json" },
- { code: "ko-KR", file: "ko-KR.json" },
- { code: "es-ES", file: "es-ES.json" },
- { code: "ja-JP", file: "ja-JP.json" },
- { code: "bg-BG", file: "bg-BG.json" },
- { code: "zh-CN", file: "zh-CN.json" },
- { code: "tr-TR", file: "tr-TR.json" },
- { code: "ar-SA", file: "ar-SA.json" },
- { code: "hu-HU", file: "hu-HU.json" },
- { code: "pt-PT", file: "pt-PT.json" },
- { code: "no-NO", file: "no-NO.json" },
- { code: "sv-SE", file: "sv-SE.json" },
- { code: "ro-RO", file: "ro-RO.json" },
- { code: "sk-SK", file: "sk-SK.json" },
- { code: "uk-UA", file: "uk-UA.json" },
- { code: "lt-LT", file: "lt-LT.json" },
- { code: "fr-CA", file: "fr-CA.json" },
- { code: "pl-PL", file: "pl-PL.json" },
- { code: "hr-HR", file: "hr-HR.json" },
- { code: "da-DK", file: "da-DK.json" },
- { code: "pt-BR", file: "pt-BR.json" },
- { code: "de-DE", file: "de-DE.json" },
- { code: "ca-ES", file: "ca-ES.json" },
- { code: "sr-SP", file: "sr-SP.json" },
- { code: "cs-CZ", file: "cs-CZ.json" },
- { code: "gl-ES", file: "gl-ES.json" },
- { code: "fr-FR", file: "fr-FR.json" },
- { code: "fr-BE", file: "fr-BE.json" },
- { code: "zh-TW", file: "zh-TW.json" },
- { code: "af-ZA", file: "af-ZA.json" },
- { code: "is-IS", file: "is-IS.json" },
- { code: "sl-SI", file: "sl-SI.json" },
- { code: "ru-RU", file: "ru-RU.json" },
- { code: "he-IL", file: "he-IL.json" },
- { code: "nl-NL", file: "nl-NL.json" },
- { code: "en-US", file: "en-US.json" },
- { code: "en-GB", file: "en-GB.json" },
- { code: "fi-FI", file: "fi-FI.json" },
- { code: "vi-VN", file: "vi-VN.json" },
- // END: MESSAGE_LOCALES
- ],
- lazy: true,
- strategy: "no_prefix",
- langDir: "lang/messages",
- detectBrowserLanguage: {
- useCookie: true,
- alwaysRedirect: true,
- },
- defaultLocale: "en-US",
- vueI18n: {
- 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
- },
- fallbackLocale: "en-US",
- },
- },
-
- // Axios module configuration: https://go.nuxtjs.dev/config-axios
- axios: {
- proxy: true,
- credentials: true,
- },
-
- publicRuntimeConfig: {
- GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
- SUB_PATH: process.env.SUB_PATH || "",
- axios: {
- browserBaseURL: process.env.SUB_PATH || "",
- },
- // ==============================================
- // Theme Runtime Config
- useDark: process.env.THEME_USE_DARK || false,
- themes: {
- dark: {
- primary: process.env.THEME_DARK_PRIMARY || "#E58325",
- accent: process.env.THEME_DARK_ACCENT || "#007A99",
- secondary: process.env.THEME_DARK_SECONDARY || "#973542",
- success: process.env.THEME_DARK_SUCCESS || "#43A047",
- info: process.env.THEME_DARK_INFO || "#1976d2",
- warning: process.env.THEME_DARK_WARNING || "#FF6D00",
- error: process.env.THEME_DARK_ERROR || "#EF5350",
- background: "#1E1E1E",
- },
- light: {
- primary: process.env.THEME_LIGHT_PRIMARY || "#E58325",
- accent: process.env.THEME_LIGHT_ACCENT || "#007A99",
- secondary: process.env.THEME_DARK_SECONDARY || "#973542",
- success: process.env.THEME_DARK_SUCCESS || "#43A047",
- info: process.env.THEME_LIGHT_INFO || "#1976d2",
- warning: process.env.THEME_LIGHT_WARNING || "#FF6D00",
- error: process.env.THEME_LIGHT_ERROR || "#EF5350",
- },
- },
- },
-
- privateRuntimeConfig: {},
-
- proxy: {
- // See Proxy section
- [`${process.env.SUB_PATH || ""}api`]: {
- pathRewrite: {
- [`${process.env.SUB_PATH || ""}api`]: "/api", // rewrite path
- },
- changeOrigin: true,
- target: process.env.API_URL || "http://localhost:9000",
- xfwd: true,
- },
- "/api": {
- changeOrigin: true,
- target: process.env.API_URL || "http://localhost:9000",
- xfwd: true,
- },
- "/docs": {
- changeOrigin: true,
- target: process.env.API_URL || "http://localhost:9000",
- xfwd: true,
- },
- "/openapi.json": {
- changeOrigin: true,
- target: process.env.API_URL || "http://localhost:9000",
- xfwd: true,
- },
- },
-
- // PWA module configuration: https://go.nuxtjs.dev/pwa
- pwa: {
- meta: {
- /* meta options */
- name: "Mealie",
- description: "Mealie is a recipe management and meal planning app",
- theme_color: process.env.THEME_LIGHT_PRIMARY || "#E58325",
- ogSiteName: "Mealie",
- },
- manifest: {
- start_url: "/",
- scope: "/",
- lang: "en",
- dir: "auto",
- name: "Mealie",
- short_name: "Mealie",
- crossorigin: "use-credentials",
- id: "mealie",
- description: "Mealie is a recipe management and meal planning app",
- theme_color: process.env.THEME_LIGHT_PRIMARY || "#E58325",
- background_color: "#FFFFFF",
- display: "standalone",
- display_override: [
- "standalone",
- "minimal-ui",
- "browser",
- "window-controls-overlay"
- ],
- share_target: {
- action: "/r/create/url",
- method: "GET",
- params: {
- /* title and url are not currently used in Mealie. If there are issues
- with sharing, uncommenting those lines might help solve the puzzle. */
- // "title": "title",
- "text": "recipe_import_url",
- // "url": "url",
- },
- },
- icons: [
- {
- src: "/icons/android-chrome-192x192.png",
- sizes: "192x192",
- type: "image/png",
- purpose: "any",
- },
- {
- src: "/icons/android-chrome-512x512.png",
- sizes: "512x512",
- type: "image/png",
- purpose: "any",
- },
- {
- src: "/icons/android-chrome-maskable-192x192.png",
- sizes: "192x192",
- type: "image/png",
- purpose: "maskable",
- },
- {
- src: "/icons/android-chrome-maskable-512x512.png",
- sizes: "512x512",
- type: "image/png",
- purpose: "maskable",
- },
- ],
- screenshots: [
- {
- "src": "/screenshots/home-narrow.png",
- "sizes": "1600x2420",
- "form_factor": "narrow",
- "label": "Home Page"
- },
- {
- "src": "/screenshots/recipe-narrow.png",
- "sizes": "1600x2420",
- "form_factor": "narrow",
- "label": "Recipe Page"
- },
- {
- "src": "/screenshots/editor-narrow.png",
- "sizes": "1600x2420",
- "form_factor": "narrow",
- "label": "Editor Page"
- },
- {
- "src": "/screenshots/parser-narrow.png",
- "sizes": "1600x2420",
- "form_factor": "narrow",
- "label": "Parser Page"
- },
- {
- "src": "/screenshots/home-wide.png",
- "sizes": "2560x1460",
- "form_factor": "wide",
- "label": "Home Page"
- },
- {
- "src": "/screenshots/recipe-wide.png",
- "sizes": "2560x1460",
- "form_factor": "wide",
- "label": "Recipe Page"
- },
- {
- "src": "/screenshots/editor-wide.png",
- "sizes": "2560x1460",
- "form_factor": "wide",
- "label": "Editor Page"
- },
- {
- "src": "/screenshots/parser-wide.png",
- "sizes": "2560x1460",
- "form_factor": "wide",
- "label": "Parser Page"
- }
- ],
- "shortcuts": [
- {
- "name": "Shopping Lists",
- "short_name": "Shopping Lists",
- "description": "Open the shopping lists",
- "url": "/shopping-lists",
- "icons": [
- {
- "src": "/icons/mdiFormatListChecks-192x192.png",
- "sizes": "192x192",
- },
- {
- "src": "/icons/mdiFormatListChecks-96x96.png",
- "sizes": "96x96",
- }
- ]
- },
- {
- "name": "Meal Planner",
- "short_name": "Meal Planner",
- "description": "Open the meal planner",
- "url": "/household/mealplan/planner/view",
- "icons": [
- {
- "src": "/icons/mdiCalendarMultiselect-192x192.png",
- "sizes": "192x192",
- },
- {
- "src": "/icons/mdiCalendarMultiselect-96x96.png",
- "sizes": "96x96",
- }
- ]
- },
- ],
- prefer_related_applications: false,
- handle_links: "preferred",
- categories: [
- "food"
- ],
- launch_handler: {
- "client_mode": ["focus-existing", "auto"]
- },
- edge_side_panel: {
- "preferred_width": 400
- }
- },
- icon: false, // disables the icon module
- },
-
- // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
- vuetify: {
- optionsPath: "./vuetify.options.js",
- },
-
- // Build Configuration: https://go.nuxtjs.dev/config-build
- build: {
- // https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-build
- analyze: false,
- babel: {
- plugins: [
- ["@babel/plugin-proposal-private-property-in-object", { loose: true }],
- // ["@nuxtjs/composition-api/dist/babel-plugin"],
- ],
- },
- transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : null,
- },
-};
diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts
new file mode 100644
index 000000000..965ce4bdb
--- /dev/null
+++ b/frontend/nuxt.config.ts
@@ -0,0 +1,416 @@
+import { defineNuxtConfig } from "nuxt/config";
+import commonjs from "vite-plugin-commonjs";
+
+const AUTH_TOKEN = "mealie.auth.token";
+
+export default defineNuxtConfig({
+ // Global page headers: https://go.nuxtjs.dev/config-head
+ // target: "static",
+
+ modules: [
+ "@vite-pwa/nuxt",
+ "@nuxtjs/i18n",
+ "@sidebase/nuxt-auth",
+ "@nuxt/fonts",
+ "vuetify-nuxt-module",
+ "@nuxt/eslint",
+ ],
+ ssr: false,
+
+ components: [
+ {
+ path: "~/components",
+ pathPrefix: false,
+ },
+ ],
+ devtools: {
+ enabled: false,
+ },
+ app: {
+ baseURL: process.env.SUB_PATH || "",
+
+ head: {
+ title: "Mealie",
+ meta: [
+ { property: "og:type", content: "website" },
+ { property: "og:title", content: "Mealie" },
+ { property: "og:site_name", content: "Mealie" },
+ {
+ property: "og:description",
+ content: "Mealie is a recipe management app for your kitchen.",
+ },
+ {
+ property: "og:image",
+ content:
+ "https://raw.githubusercontent.com/mealie-recipes/mealie/9571816ac4eed5beacfc0abf6c03eff1427fd0eb/frontend/static/icons/android-chrome-512x512.png",
+ },
+ { charset: "utf-8" },
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
+ {
+ name: "description",
+ content: "Mealie is a recipe management app for your kitchen.",
+ },
+ ],
+ link: [
+ { "rel": "icon", "type": "image/x-icon", "href": "/favicon.ico", "data-n-head": "ssr" },
+ { "rel": "shortcut icon", "type": "image/png", "href": "/icons/icon-x64.png", "data-n-head": "ssr" },
+ { "rel": "apple-touch-icon", "type": "image/png", "href": "/icons/apple-touch-icon.png", "data-n-head": "ssr" },
+ { "rel": "mask-icon", "href": "/icons/safari-pinned-tab.svg", "data-n-head": "ssr" },
+ ],
+ },
+
+ /* viewTransition: {
+ name: "layout",
+ mode: "out-in",
+ }, */
+ viewTransition: true,
+ },
+
+ css: ["~/assets/main.css", "~/assets/style-overrides.scss"],
+
+ runtimeConfig: {
+ sessionPassword: process.env.SESSION_PASSWORD || "password-with-at-least-32-characters",
+ apiUrl: process.env.API_URL || "http://localhost:9000",
+ public: {
+ AUTH_TOKEN,
+ GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || undefined,
+ SUB_PATH: process.env.SUB_PATH || "",
+ // ==============================================
+ // Theme Runtime Config
+ useDark: Boolean(process.env.THEME_USE_DARK) || false,
+ themes: {
+ dark: {
+ primary: process.env.THEME_DARK_PRIMARY || "#E58325",
+ accent: process.env.THEME_DARK_ACCENT || "#007A99",
+ secondary: process.env.THEME_DARK_SECONDARY || "#973542",
+ success: process.env.THEME_DARK_SUCCESS || "#43A047",
+ info: process.env.THEME_DARK_INFO || "#1976d2",
+ warning: process.env.THEME_DARK_WARNING || "#FF6D00",
+ error: process.env.THEME_DARK_ERROR || "#EF5350",
+ background: "#1E1E1E",
+ },
+ light: {
+ primary: process.env.THEME_LIGHT_PRIMARY || "#E58325",
+ accent: process.env.THEME_LIGHT_ACCENT || "#007A99",
+ secondary: process.env.THEME_DARK_SECONDARY || "#973542",
+ success: process.env.THEME_DARK_SUCCESS || "#43A047",
+ info: process.env.THEME_LIGHT_INFO || "#1976d2",
+ warning: process.env.THEME_LIGHT_WARNING || "#FF6D00",
+ error: process.env.THEME_LIGHT_ERROR || "#EF5350",
+ },
+ },
+ },
+ },
+ dir: {
+ static: "static",
+ },
+
+ // Build Configuration: https://go.nuxtjs.dev/config-build
+ build: {
+ // https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-build
+ analyze: false,
+ /* babel: {
+ plugins: [
+ ["@babel/plugin-proposal-private-property-in-object", { loose: true }],
+ ],
+ }, */
+ transpile: process.env.NODE_ENV !== "production" ? [/@vue[\\/]composition-api/] : [],
+ },
+ future: {
+ compatibilityVersion: 3,
+ },
+
+ compatibilityDate: "2025-03-28",
+
+ nitro: {
+ baseURL: process.env.SUB_PATH || "",
+ },
+
+ vite: {
+ plugins: [
+ commonjs(),
+ ],
+ },
+
+ auth: {
+ isEnabled: true,
+ // disableServerSideAuth: true,
+ originEnvKey: "AUTH_ORIGIN",
+ baseURL: "/api",
+ provider: {
+ type: "local",
+ endpoints: {
+ signIn: { path: "/auth/token", method: "post" },
+ signOut: { path: "/auth/logout", method: "post" },
+ getSession: { path: "/users/self", method: "get" },
+ },
+ token: {
+ signInResponseTokenPointer: "/access_token",
+ type: "Bearer",
+ cookieName: AUTH_TOKEN,
+ maxAgeInSeconds: 604800, // 7 days
+ },
+ pages: {
+ login: "/login",
+ },
+ },
+ },
+
+ // eslint rules
+ eslint: {
+ config: {
+ formatters: true,
+ stylistic: {
+ indent: 2,
+ semi: true,
+ quotes: "double",
+ commaDangle: "always-multiline",
+ },
+ },
+ },
+ fonts: {
+ defaults: {
+ weights: ["100 900"],
+ styles: ["normal", "italic"],
+ subsets: ["cyrillic-ext", "cyrillic", "greek-ext", "greek", "vietnamese", "latin-ext", "latin"],
+ },
+ },
+
+ i18n: {
+ locales: [
+ // CODE_GEN_ID: MESSAGE_LOCALES
+ { code: "af-ZA", file: "af-ZA.ts", dir: "ltr" },
+ { code: "ar-SA", file: "ar-SA.ts", dir: "rtl" },
+ { code: "bg-BG", file: "bg-BG.ts", dir: "ltr" },
+ { code: "ca-ES", file: "ca-ES.ts", dir: "ltr" },
+ { code: "cs-CZ", file: "cs-CZ.ts", dir: "ltr" },
+ { code: "da-DK", file: "da-DK.ts", dir: "ltr" },
+ { code: "de-DE", file: "de-DE.ts", dir: "ltr" },
+ { code: "el-GR", file: "el-GR.ts", dir: "ltr" },
+ { code: "en-GB", file: "en-GB.ts", dir: "ltr" },
+ { code: "en-US", file: "en-US.ts", dir: "ltr" },
+ { code: "es-ES", file: "es-ES.ts", dir: "ltr" },
+ { code: "et-EE", file: "et-EE.ts", dir: "ltr" },
+ { code: "fi-FI", file: "fi-FI.ts", dir: "ltr" },
+ { code: "fr-BE", file: "fr-BE.ts", dir: "ltr" },
+ { code: "fr-CA", file: "fr-CA.ts", dir: "ltr" },
+ { code: "fr-FR", file: "fr-FR.ts", dir: "ltr" },
+ { code: "gl-ES", file: "gl-ES.ts", dir: "ltr" },
+ { code: "he-IL", file: "he-IL.ts", dir: "rtl" },
+ { code: "hr-HR", file: "hr-HR.ts", dir: "ltr" },
+ { code: "hu-HU", file: "hu-HU.ts", dir: "ltr" },
+ { code: "is-IS", file: "is-IS.ts", dir: "ltr" },
+ { code: "it-IT", file: "it-IT.ts", dir: "ltr" },
+ { code: "ja-JP", file: "ja-JP.ts", dir: "ltr" },
+ { code: "ko-KR", file: "ko-KR.ts", dir: "ltr" },
+ { code: "lt-LT", file: "lt-LT.ts", dir: "ltr" },
+ { code: "lv-LV", file: "lv-LV.ts", dir: "ltr" },
+ { code: "nl-NL", file: "nl-NL.ts", dir: "ltr" },
+ { code: "no-NO", file: "no-NO.ts", dir: "ltr" },
+ { code: "pl-PL", file: "pl-PL.ts", dir: "ltr" },
+ { code: "pt-BR", file: "pt-BR.ts", dir: "ltr" },
+ { code: "pt-PT", file: "pt-PT.ts", dir: "ltr" },
+ { code: "ro-RO", file: "ro-RO.ts", dir: "ltr" },
+ { code: "ru-RU", file: "ru-RU.ts", dir: "ltr" },
+ { code: "sk-SK", file: "sk-SK.ts", dir: "ltr" },
+ { code: "sl-SI", file: "sl-SI.ts", dir: "ltr" },
+ { code: "sr-SP", file: "sr-SP.ts", dir: "ltr" },
+ { code: "sv-SE", file: "sv-SE.ts", dir: "ltr" },
+ { code: "tr-TR", file: "tr-TR.ts", dir: "ltr" },
+ { code: "uk-UA", file: "uk-UA.ts", dir: "ltr" },
+ { code: "vi-VN", file: "vi-VN.ts", dir: "ltr" },
+ { code: "zh-CN", file: "zh-CN.ts", dir: "ltr" },
+ { code: "zh-TW", file: "zh-TW.ts", dir: "ltr" },
+ // END: MESSAGE_LOCALES
+ ],
+ strategy: "no_prefix",
+ lazy: true,
+ types: "composition",
+ langDir: "./../lang/locales", // note: we need to up one ../ because the default root of lang dir is the /frontend/i18n, which can not be configured
+ defaultLocale: "en-US",
+ detectBrowserLanguage: {
+ useCookie: true,
+ alwaysRedirect: true,
+ fallbackLocale: "en-US",
+ },
+ compilation: {
+ strictMessage: false,
+ escapeHtml: true,
+ },
+ vueI18n: "./../i18n.config.ts", // note: we need to up one ../ because the default root of lang dir is the /frontend/i18n, which can not be configured
+ },
+
+ // PWA module configuration: https://go.nuxtjs.dev/pwa
+ pwa: {
+ mode: process.env.NODE_ENV === "production" ? "production" : "development",
+ registerType: "autoUpdate",
+ useCredentials: true,
+ manifest: {
+ start_url: "/",
+ scope: "/",
+ lang: "en",
+ name: "Mealie",
+ short_name: "Mealie",
+ id: "mealie",
+ description: "Mealie is a recipe management and meal planning app",
+ theme_color: process.env.THEME_LIGHT_PRIMARY || "#E58325",
+ background_color: "#FFFFFF",
+ display: "standalone",
+ display_override: [
+ "standalone",
+ "minimal-ui",
+ "browser",
+ "window-controls-overlay",
+ ],
+ share_target: {
+ action: "/r/create/url",
+ method: "GET",
+ params: {
+ /* title and url are not currently used in Mealie. If there are issues
+ with sharing, uncommenting those lines might help solve the puzzle. */
+ // "title": "title",
+ text: "recipe_import_url",
+ // "url": "url",
+ },
+ },
+ icons: [
+ {
+ src: "/icons/android-chrome-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "/icons/android-chrome-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "any",
+ },
+ {
+ src: "/icons/android-chrome-maskable-192x192.png",
+ sizes: "192x192",
+ type: "image/png",
+ purpose: "maskable",
+ },
+ {
+ src: "/icons/android-chrome-maskable-512x512.png",
+ sizes: "512x512",
+ type: "image/png",
+ purpose: "maskable",
+ },
+ ],
+ screenshots: [
+ {
+ src: "/screenshots/home-narrow.png",
+ sizes: "1600x2420",
+ form_factor: "narrow",
+ label: "Home Page",
+ },
+ {
+ src: "/screenshots/recipe-narrow.png",
+ sizes: "1600x2420",
+ form_factor: "narrow",
+ label: "Recipe Page",
+ },
+ {
+ src: "/screenshots/editor-narrow.png",
+ sizes: "1600x2420",
+ form_factor: "narrow",
+ label: "Editor Page",
+ },
+ {
+ src: "/screenshots/parser-narrow.png",
+ sizes: "1600x2420",
+ form_factor: "narrow",
+ label: "Parser Page",
+ },
+ {
+ src: "/screenshots/home-wide.png",
+ sizes: "2560x1460",
+ form_factor: "wide",
+ label: "Home Page",
+ },
+ {
+ src: "/screenshots/recipe-wide.png",
+ sizes: "2560x1460",
+ form_factor: "wide",
+ label: "Recipe Page",
+ },
+ {
+ src: "/screenshots/editor-wide.png",
+ sizes: "2560x1460",
+ form_factor: "wide",
+ label: "Editor Page",
+ },
+ {
+ src: "/screenshots/parser-wide.png",
+ sizes: "2560x1460",
+ form_factor: "wide",
+ label: "Parser Page",
+ },
+ ],
+ shortcuts: [
+ {
+ name: "Shopping Lists",
+ short_name: "Shopping Lists",
+ description: "Open the shopping lists",
+ url: "/shopping-lists",
+ icons: [
+ {
+ src: "/icons/mdiFormatListChecks-192x192.png",
+ sizes: "192x192",
+ },
+ {
+ src: "/icons/mdiFormatListChecks-96x96.png",
+ sizes: "96x96",
+ },
+ ],
+ },
+ {
+ name: "Meal Planner",
+ short_name: "Meal Planner",
+ description: "Open the meal planner",
+ url: "/household/mealplan/planner/view",
+ icons: [
+ {
+ src: "/icons/mdiCalendarMultiselect-192x192.png",
+ sizes: "192x192",
+ },
+ {
+ src: "/icons/mdiCalendarMultiselect-96x96.png",
+ sizes: "96x96",
+ },
+ ],
+ },
+ ],
+ prefer_related_applications: false,
+ handle_links: "preferred",
+ categories: [
+ "food",
+ ],
+ launch_handler: {
+ client_mode: ["focus-existing", "auto"],
+ },
+ edge_side_panel: {
+ preferred_width: 400,
+ },
+ },
+ },
+
+ // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
+ vuetify: {
+ moduleOptions: {},
+ vuetifyOptions: {
+ icons: {
+ defaultSet: "mdi-svg",
+ },
+ // Theme Config set at runtime by /plugins/theme.ts
+ // This config doesn't do anything.
+ theme: {},
+ locale: {
+ locale: "en-US",
+ fallback: "en-US",
+ },
+ },
+ },
+});
diff --git a/frontend/package.json b/frontend/package.json
index 9f28744c2..b12f3fd6f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,64 +1,62 @@
{
"name": "mealie",
-"version": "2.7.1",
+"version": "3.0.2",
"private": true,
"scripts": {
- "dev": "nuxt",
+ "dev": "nuxt dev",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate --spa",
- "lint:js": "eslint --ext \".ts,.js,.vue\" --ignore-path .gitignore .",
+ "lint:js": "eslint .",
"lint": "yarn lint:js",
+ "lint:log": "yarn lint:js --debug",
"test": "vitest",
- "test:ci": "vitest --watch=false"
+ "test:ci": "vitest --watch=false",
+ "cleanup": "nuxt cleanup"
},
"lint-staged": {
"*.{ts,js,vue}": "eslint"
},
"dependencies": {
- "@adapttive/vue-markdown": "^4.0.1",
- "@mdi/js": "^7.1.96",
- "@nuxtjs/auth-next": "5.0.0-1667386184.dfbbb54",
- "@nuxtjs/axios": "^5.13.6",
- "@nuxtjs/i18n": "7.2.0",
+ "@mdi/js": "^7.4.47",
+ "@nuxt/eslint": "1.2.0",
+ "@nuxt/fonts": "^0.11.4",
+ "@nuxtjs/i18n": "^9.2.1",
"@nuxtjs/proxy": "^2.1.0",
- "@nuxtjs/pwa": "3.2.0",
- "@vueuse/core": "^9.9.0",
- "core-js": "^3.27.0",
- "date-fns": "^2.29.3",
- "fuse.js": "^6.6.2",
- "isomorphic-dompurify": "^1.0.0",
- "nuxt": "^2.17.3",
- "v-jsoneditor": "^1.4.5",
- "vue-advanced-cropper": "^1.11.6",
- "vuedraggable": "^2.24.3",
- "vuetify": "^2.6.13"
+ "@sidebase/nuxt-auth": "0.10.0",
+ "@vite-pwa/nuxt": "0.10.6",
+ "@vueuse/core": "^12.7.0",
+ "axios": "^1.8.1",
+ "date-fns": "^4.1.0",
+ "fuse.js": "^7.1.0",
+ "isomorphic-dompurify": "^2.22.0",
+ "json-editor-vue": "^0.18.1",
+ "marked": "^15.0.12",
+ "next-auth": "~4.21.1",
+ "nuxt": "^3.15.4",
+ "typescript": "5.3",
+ "vite": "^6.2.0",
+ "vite-plugin-commonjs": "^0.10.4",
+ "vue-advanced-cropper": "^2.8.9",
+ "vue-draggable-plus": "^0.6.0",
+ "vuetify-nuxt-module": "0.18.3"
},
"devDependencies": {
- "@babel/eslint-parser": "^7.19.1",
- "@nuxt/types": "^2.16.0",
- "@nuxt/typescript-build": "^2.1.0",
- "@nuxtjs/composition-api": "^0.33.1",
- "@nuxtjs/eslint-config-typescript": "^12.0.0",
- "@nuxtjs/eslint-module": "4.0.2",
- "@nuxtjs/google-fonts": "2.0.0",
- "@nuxtjs/vuetify": "^1.12.1",
- "@types/sortablejs": "^1.15.0",
- "eslint": "^8.30.0",
- "eslint-config-prettier": "^8.5.0",
+ "@nuxt/types": "^2.18.1",
+ "@nuxtjs/eslint-config-typescript": "^12.1.0",
+ "@nuxtjs/eslint-module": "^4.1.0",
+ "@stylistic/eslint-plugin-js": "^4.2.0",
+ "@types/sortablejs": "^1.15.8",
+ "eslint": "^9.22.0",
+ "eslint-config-prettier": "^10.0.2",
+ "eslint-plugin-format": "^1.0.1",
"eslint-plugin-nuxt": "^4.0.0",
- "eslint-plugin-prettier": "^4.2.1",
- "eslint-plugin-vue": "9.10.0",
- "lint-staged": "^13.1.0",
- "nuxt-vite": "0.2.3",
- "prettier": "^2.8.1",
- "vitest": "^0.29.0"
+ "eslint-plugin-prettier": "^5.2.3",
+ "eslint-plugin-vue": "^9.32.0",
+ "lint-staged": "^15.4.3",
+ "prettier": "^3.5.2",
+ "sass-embedded": "^1.85.1",
+ "vitest": "^3.0.7"
},
- "resolutions": {
- "@nuxtjs/i18n/**/ufo": "0.7.9",
- "vue-demi": "^0.13.11",
- "vue-template-compiler": "2.7.16",
- "postcss-preset-env": "^7.0.0",
- "typescript": "^4.9.5"
- }
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/frontend/pages/admin.vue b/frontend/pages/admin.vue
new file mode 100644
index 000000000..c71d8e422
--- /dev/null
+++ b/frontend/pages/admin.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/frontend/pages/admin/backups.vue b/frontend/pages/admin/backups.vue
index 7457d7f7e..f76d426e7 100644
--- a/frontend/pages/admin/backups.vue
+++ b/frontend/pages/admin/backups.vue
@@ -4,9 +4,10 @@
@@ -15,60 +16,83 @@
-
-
+
+
-
+
{{ $t('settings.backup.cannot-be-undone') }}
-
+
-
+
- {{ $t('settings.backup.backup-restore-process-in-the-documentation') }}
+ {{
+ $t('settings.backup.backup-restore-process-in-the-documentation') }}
-
+
{{ $t('') }}
-
+ />
-
- {{ $globals.icons.database }}
+
+
+ {{ $globals.icons.database }}
+
{{ $t('settings.backup.restore-backup') }}
{{ selected }}
-
+
-
+
-
+
-
- {{ $t("settings.backup.create-heading") }}
-
+
+
+ {{ $t("settings.backup.create-heading") }}
+
+
-
+
{{ $d(Date.parse(item.date), "medium") }}
-
+
{{ $globals.icons.delete }}
- {}"/>
-
-