diff --git a/README.md b/README.md index dbb23d3..e8aebb7 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,29 @@ # doof-front ## Project setup + ``` npm install ``` ### Compiles and hot-reloads for development + ``` npm run serve ``` ### Compiles and minifies for production + ``` npm run build ``` ### Lints and fixes files + ``` npm run lint ``` ### Customize configuration + See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/babel.config.js b/babel.config.js index e955840..c1b783e 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,5 +1,3 @@ module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] + presets: ['@vue/cli-plugin-babel/preset'], } diff --git a/jsconfig.json b/jsconfig.json index 4aafc5f..b6bd4c8 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,15 +5,8 @@ "baseUrl": "./", "moduleResolution": "node", "paths": { - "@/*": [ - "src/*" - ] + "@/*": ["src/*"] }, - "lib": [ - "esnext", - "dom", - "dom.iterable", - "scripthost" - ] + "lib": ["esnext", "dom", "dom.iterable", "scripthost"] } } diff --git a/public/index.html b/public/index.html index 3e5a139..6fc0c4f 100644 --- a/public/index.html +++ b/public/index.html @@ -1,15 +1,18 @@ - + - - - - + + + + <%= htmlWebpackPlugin.options.title %>
diff --git a/refactor-strategy.md b/refactor-strategy.md index f250cf2..c8d0090 100644 --- a/refactor-strategy.md +++ b/refactor-strategy.md @@ -5,6 +5,7 @@ This document outlines a pragmatic, step-by-step refactor plan to improve struct Last updated: 2025-10-18 ## Goals + - Separate concerns (routing, HTTP/API, mapping/normalization, UI logic) - Improve readability and testability - Establish light-weight standards (naming, lint/format) without blocking development @@ -13,6 +14,7 @@ Last updated: 2025-10-18 ## Phases and Steps ### Phase 1 — Routing and Auth (Foundational) + - [x] Extract router into `src/router/index.js` with named routes - [x] Add route meta `requiresAuth` and a global auth guard - [x] Remove auth-redirect from `App.vue` (handled by guard instead) @@ -21,6 +23,7 @@ Last updated: 2025-10-18 Outcome: Routing logic is centralized and testable; pages redirect consistently based on auth. ### Phase 2 — API Layer Split (Incremental) + - [x] Add `src/api/http.js` wrapper for JSON fetch with error handling and env-based base URL - [x] Add `src/api/mappers/mealMapper.js` to normalize Meal data (dates) - [x] Add `src/api/meals.js` and migrate MealPlan API calls (get upcoming, mark consumed, delete) @@ -31,6 +34,7 @@ Outcome: Routing logic is centralized and testable; pages redirect consistently Outcome: Feature modules call cohesive services; logic for mapping/normalization is isolated and testable. ### Phase 3 — Composables (UI-Facing Logic) + - [x] Add `src/composables/useAuth.js` (user ref, ensureAuth) - [x] Add `src/composables/useMeals.js` (fetch and mutate meals) - [x] Refactor pages to use composables and ` diff --git a/src/alert.js b/src/alert.js index f75b124..56f963f 100644 --- a/src/alert.js +++ b/src/alert.js @@ -1,10 +1,11 @@ -const subscribers = []; +const subscribers = [] export default { - subscribe(callback) { - subscribers.push(callback); - }, - show(message) { // { message, heading, type: ["success", "error", "info"] } - subscribers.forEach(callback => callback(message)); - } -} \ No newline at end of file + subscribe(callback) { + subscribers.push(callback) + }, + show(message) { + // { message, heading, type: ["success", "error", "info"] } + subscribers.forEach((callback) => callback(message)) + }, +} diff --git a/src/api/http.js b/src/api/http.js index d5fbc7f..d95a992 100644 --- a/src/api/http.js +++ b/src/api/http.js @@ -6,14 +6,19 @@ async function request(path, options = {}) { credentials: 'include', ...options, headers: { - 'Accept': 'application/json', + Accept: 'application/json', ...(options.body ? { 'Content-Type': 'application/json' } : {}), ...(options.headers || {}), }, }) if (!resp.ok) { let message = `${resp.status} ${resp.statusText}` - try { const err = await resp.json(); message = err.message || message } catch (_) { /* ignore */ } + try { + const err = await resp.json() + message = err.message || message + } catch (_) { + /* ignore */ + } const error = new Error(message) error.status = resp.status throw error diff --git a/src/api/mappers/shoppingListMapper.js b/src/api/mappers/shoppingListMapper.js index 51a17e6..f59a309 100644 --- a/src/api/mappers/shoppingListMapper.js +++ b/src/api/mappers/shoppingListMapper.js @@ -5,10 +5,14 @@ function toDate(value) { return value ? new Date(value) : value } -function attachItemRefs(items, { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup }) { +function attachItemRefs( + items, + { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup } +) { if (!Array.isArray(items)) return for (const item of items) { - if (item.ingredient_id && ingredients_lookup) item.ingredient = ingredients_lookup[item.ingredient_id] + if (item.ingredient_id && ingredients_lookup) + item.ingredient = ingredients_lookup[item.ingredient_id] if (item.meal_id && meals_lookup) item.meal = meals_lookup[item.meal_id] if (item.recipe_id && recipes_lookup) item.recipe = recipes_lookup[item.recipe_id] if (item.list_id && shopping_list_lookup) item.list = shopping_list_lookup[item.list_id] diff --git a/src/api/recipes.js b/src/api/recipes.js index ae041bf..527851e 100644 --- a/src/api/recipes.js +++ b/src/api/recipes.js @@ -26,7 +26,7 @@ export async function parseRecipe(url) { } export async function parseIngredients(lines) { - const params = lines.map(line => `ingredients=${encodeURIComponent(line)}`).join('&') + const params = lines.map((line) => `ingredients=${encodeURIComponent(line)}`).join('&') return http.get(`/recipes/ingredients/parse?${params}`) } diff --git a/src/components/ActionItem.vue b/src/components/ActionItem.vue index 98523b7..d1b6123 100644 --- a/src/components/ActionItem.vue +++ b/src/components/ActionItem.vue @@ -1,37 +1,36 @@ \ No newline at end of file + diff --git a/src/components/AlertToast.vue b/src/components/AlertToast.vue index e8b3659..4fe6afc 100644 --- a/src/components/AlertToast.vue +++ b/src/components/AlertToast.vue @@ -1,98 +1,92 @@ \ No newline at end of file +export default { + name: 'AlertToast', + data() { + return { + showAlert: false, + heading: '', + message: '', + type: '', + } + }, + computed: { + icon() { + return this.type && alertIcons[this.type] ? alertIcons[this.type] : null + }, + }, + mounted() { + alert.subscribe(this.show) + }, + methods: { + show({ heading, message, type }) { + this.heading = heading + this.message = message + this.type = type + this.showAlert = true + setTimeout(() => { + this.showAlert = false + }, 5000) + }, + dismiss() { + this.showAlert = false + }, + }, +} + diff --git a/src/components/LoginPage.vue b/src/components/LoginPage.vue index aa1ec76..3302992 100644 --- a/src/components/LoginPage.vue +++ b/src/components/LoginPage.vue @@ -1,108 +1,102 @@ \ No newline at end of file + alert('Login failed') + }, + }, +} + diff --git a/src/components/ingredients/CompactParsedIngredient.vue b/src/components/ingredients/CompactParsedIngredient.vue index f5120c8..bc48b4d 100644 --- a/src/components/ingredients/CompactParsedIngredient.vue +++ b/src/components/ingredients/CompactParsedIngredient.vue @@ -1,56 +1,70 @@ \ No newline at end of file + diff --git a/src/components/ingredients/EditableIngredientsPanel.vue b/src/components/ingredients/EditableIngredientsPanel.vue index 0c6f3f6..3f1f694 100644 --- a/src/components/ingredients/EditableIngredientsPanel.vue +++ b/src/components/ingredients/EditableIngredientsPanel.vue @@ -1,107 +1,102 @@ \ No newline at end of file + diff --git a/src/components/ingredients/IngredientLine.vue b/src/components/ingredients/IngredientLine.vue index 2552f1e..52dab0c 100644 --- a/src/components/ingredients/IngredientLine.vue +++ b/src/components/ingredients/IngredientLine.vue @@ -1,68 +1,77 @@ - \ No newline at end of file diff --git a/src/components/meals/DatePicker.vue b/src/components/meals/DatePicker.vue index c278f83..17f5933 100644 --- a/src/components/meals/DatePicker.vue +++ b/src/components/meals/DatePicker.vue @@ -1,121 +1,117 @@ - - - - - \ No newline at end of file + return days + }, + }, + methods: { + formatDay(date) { + const options = { weekday: 'long', day: 'numeric', month: 'numeric' } + return date.toLocaleDateString('en-AU', options) + }, + selectDate(date) { + this.selectedDate = this.formatSelectedDate(date) + this.showDatePicker = false + + // Emit custom event + this.$emit('date-selected', date) + }, + formatSelectedDate(date) { + const options = { weekday: 'long', day: 'numeric', month: 'numeric' } + return date.toLocaleDateString('en-AU', options) + }, + }, +} + + + diff --git a/src/components/meals/EditMealPage.vue b/src/components/meals/EditMealPage.vue index 791f2e5..fdb64e9 100644 --- a/src/components/meals/EditMealPage.vue +++ b/src/components/meals/EditMealPage.vue @@ -1,60 +1,94 @@ \ No newline at end of file + diff --git a/src/components/meals/MealCard.vue b/src/components/meals/MealCard.vue index 170cf33..444aeab 100644 --- a/src/components/meals/MealCard.vue +++ b/src/components/meals/MealCard.vue @@ -1,85 +1,88 @@ - + \ No newline at end of file + diff --git a/src/components/meals/MealPlanPage.vue b/src/components/meals/MealPlanPage.vue index 09e0faf..5dbb0e5 100644 --- a/src/components/meals/MealPlanPage.vue +++ b/src/components/meals/MealPlanPage.vue @@ -1,81 +1,94 @@ \ No newline at end of file + diff --git a/src/components/meals/PersonList.vue b/src/components/meals/PersonList.vue index 881487b..d01a0c9 100644 --- a/src/components/meals/PersonList.vue +++ b/src/components/meals/PersonList.vue @@ -1,171 +1,187 @@ \ No newline at end of file + if (person?.id >= 0 && !this.people.find((p) => p.id === person.id)) { + this.$emit('add-person', person) + } + + this.searchName = '' + this.searchResults = [] + this.isAddingPerson = false + }, + removePerson(person) { + this.$emit('remove-person', person) + }, + }, +} + diff --git a/src/components/recipes/EditRecipePage.vue b/src/components/recipes/EditRecipePage.vue index 08c0f0b..512480d 100644 --- a/src/components/recipes/EditRecipePage.vue +++ b/src/components/recipes/EditRecipePage.vue @@ -1,153 +1,163 @@ \ No newline at end of file + diff --git a/src/components/recipes/RecipeCard.vue b/src/components/recipes/RecipeCard.vue index d0d01e9..1bee7a5 100644 --- a/src/components/recipes/RecipeCard.vue +++ b/src/components/recipes/RecipeCard.vue @@ -1,46 +1,44 @@ \ No newline at end of file + diff --git a/src/components/recipes/RecipeSearchBox.vue b/src/components/recipes/RecipeSearchBox.vue index aaf80fe..987990a 100644 --- a/src/components/recipes/RecipeSearchBox.vue +++ b/src/components/recipes/RecipeSearchBox.vue @@ -1,99 +1,112 @@ \ No newline at end of file + diff --git a/src/components/recipes/RecipesPage.vue b/src/components/recipes/RecipesPage.vue index c1be309..c359afb 100644 --- a/src/components/recipes/RecipesPage.vue +++ b/src/components/recipes/RecipesPage.vue @@ -1,18 +1,25 @@ \ No newline at end of file + diff --git a/src/components/shopping/CurrentShoppingListPage.vue b/src/components/shopping/CurrentShoppingListPage.vue index 932452d..cdbca95 100644 --- a/src/components/shopping/CurrentShoppingListPage.vue +++ b/src/components/shopping/CurrentShoppingListPage.vue @@ -1,136 +1,142 @@ \ No newline at end of file + diff --git a/src/components/shopping/MealSelectionList.vue b/src/components/shopping/MealSelectionList.vue index a7649f8..34bd391 100644 --- a/src/components/shopping/MealSelectionList.vue +++ b/src/components/shopping/MealSelectionList.vue @@ -1,160 +1,166 @@ \ No newline at end of file + const opacity = 0.7 + return { + background: `linear-gradient(to bottom, rgba(255, 255, 255, ${opacity}) 0%, rgba(255, 255, 255, ${opacity}) 100%), url('${imageUrl}') center/cover no-repeat`, + } + }, + mealCheckChanged(event) { + const mealId = parseInt(event.target.id) + const meal = this.meals.find((m) => m.id === mealId) + if (event.target.checked) { + this.$emit('meal-selected', meal) + } else { + this.$emit('meal-unselected', meal) + } + }, + isChecked(meal) { + for (const checkedMeal of this.checked) { + if (checkedMeal.id === meal.id) { + return true + } + } + return false + }, + }, +} + diff --git a/src/components/shopping/MyShoppingPage.vue b/src/components/shopping/MyShoppingPage.vue index f0aab4a..a162717 100644 --- a/src/components/shopping/MyShoppingPage.vue +++ b/src/components/shopping/MyShoppingPage.vue @@ -1,11 +1,17 @@ - + \ No newline at end of file + diff --git a/src/components/shopping/PurchasedShoppingListPage.vue b/src/components/shopping/PurchasedShoppingListPage.vue index 9447907..84d5e88 100644 --- a/src/components/shopping/PurchasedShoppingListPage.vue +++ b/src/components/shopping/PurchasedShoppingListPage.vue @@ -1,32 +1,30 @@ \ No newline at end of file + diff --git a/src/components/shopping/ShoppingListItem.vue b/src/components/shopping/ShoppingListItem.vue index c2e7442..88a5017 100644 --- a/src/components/shopping/ShoppingListItem.vue +++ b/src/components/shopping/ShoppingListItem.vue @@ -1,174 +1,214 @@ \ No newline at end of file + return calculateTotals(purchasedNotEaten.map((item) => item.ingredient)) + }, + required() { + return this.shoppingListItemGroup.shoppingListItems.filter((item) => !item.list_id) + }, + purchased() { + return this.shoppingListItemGroup.shoppingListItems.filter((item) => item.list_id) + }, + lastPurchased() { + const purchasedItems = this.shoppingListItemGroup.shoppingListItems.filter( + (item) => item.list_id + ) + if (purchasedItems.length === 0) { + return null + } + // Find the most recently purchased item + return purchasedItems.reduce((latest, item) => { + const itemDate = item?.meal?.suggested_date || item?.created_at + return !latest || (itemDate && itemDate > latest) ? itemDate : latest + }, null) + }, + }, + methods: { + getFriendlyDate(date) { + if (!date) return '' + + return ago(date) + }, + formatQuantity(quantity) { + const log10 = Math.log10(quantity) + if (log10 < 0) { + return quantity.toPrecision(2) + } else if (log10 < 1) { + return quantity.toFixed(1) + } else { + return quantity.toFixed(0) + } + }, + }, +} + diff --git a/src/components/shopping/shopping.js b/src/components/shopping/shopping.js index f55731e..a469766 100644 --- a/src/components/shopping/shopping.js +++ b/src/components/shopping/shopping.js @@ -1,45 +1,42 @@ export function groupsToItems(groups) { - return groups.map(group => group.shoppingListItems).flat(); + return groups.map((group) => group.shoppingListItems).flat() } export function uniqueMeals(shoppingListItems) { - const mealsWithDuplicates = shoppingListItems - .map(item => item.meal) - .filter(m => m); + const mealsWithDuplicates = shoppingListItems.map((item) => item.meal).filter((m) => m) - const mealsLookup = mealsWithDuplicates.reduce(((acc, meal) => { acc[meal.id] ??= meal; return acc; }), {}); + const mealsLookup = mealsWithDuplicates.reduce((acc, meal) => { + acc[meal.id] ??= meal + return acc + }, {}) - return Object.values(mealsLookup); + return Object.values(mealsLookup) } export function itemsToGroups(shoppingListItems) { - const ingredients_by_product_id = {}; - const ingredients_by_name = {}; - for (const item of shoppingListItems) { - if (item.ingredient.product) { - let group = ingredients_by_product_id[item.ingredient.product.id]; - if (!group) { - group = ingredients_by_product_id[item.ingredient.product.id] = { - product: item.ingredient.product, - shoppingListItems: [] - }; - } - group.shoppingListItems.push(item); + const ingredients_by_product_id = {} + const ingredients_by_name = {} + for (const item of shoppingListItems) { + if (item.ingredient.product) { + let group = ingredients_by_product_id[item.ingredient.product.id] + if (!group) { + group = ingredients_by_product_id[item.ingredient.product.id] = { + product: item.ingredient.product, + shoppingListItems: [], } - else { - let group = ingredients_by_name[item.ingredient.name]; - if (!group) { - group = ingredients_by_name[item.ingredient.name] = { - name: item.ingredient.name, - shoppingListItems: [] - }; - } - group.shoppingListItems.push(item); + } + group.shoppingListItems.push(item) + } else { + let group = ingredients_by_name[item.ingredient.name] + if (!group) { + group = ingredients_by_name[item.ingredient.name] = { + name: item.ingredient.name, + shoppingListItems: [], } + } + group.shoppingListItems.push(item) } + } - return [ - ...Object.values(ingredients_by_product_id), - ...Object.values(ingredients_by_name) - ] -} \ No newline at end of file + return [...Object.values(ingredients_by_product_id), ...Object.values(ingredients_by_name)] +} diff --git a/src/data.js b/src/data.js index 322bf2a..6b91d88 100644 --- a/src/data.js +++ b/src/data.js @@ -1,332 +1,390 @@ -const BASE_URL = window.location.href.replace(/^(https?:\/\/[^/]+).*/, "$1/api"); +const BASE_URL = window.location.href.replace(/^(https?:\/\/[^/]+).*/, '$1/api') const datesToFix = { - Meal: { fields: [ "suggested_date", "purchase_date", "consumed_date" ] }, - CurrentShoppingList: { dependants: l => ({ ShoppingListItem: [l.outstanding_items, l.requested_meals, l.purchased_items], Meal: Object.values(l.meals_lookup), Recipe: Object.values(l.recipes_lookup), Ingredient: Object.values(l.ingredients_lookup), ShoppingList: Object.values(l.shopping_list_lookup) }) }, - PurchasedShoppingList: { dependants: l => ({ ShoppingListItem: l.items, Meal: Object.values(l.meals_lookup), Recipe: Object.values(l.recipes_lookup), Ingredient: Object.values(l.ingredients_lookup), ShoppingList: l.list }) }, - ShoppingList: { fields: [ "created_date" ], dependants: l => ({ ShoppingListItem: l.items }) }, - ShoppingListItem: { fields: [ "created_date" ], dependants: r => ({ Meal: r.meal, Recipe: r.recipe, Ingredient: r.ingredient, ShoppingList: r.list }) }, - Recipe: { fields: [ "date_created", "date_hidden" ], }, -}; + Meal: { fields: ['suggested_date', 'purchase_date', 'consumed_date'] }, + CurrentShoppingList: { + dependants: (l) => ({ + ShoppingListItem: [l.outstanding_items, l.requested_meals, l.purchased_items], + Meal: Object.values(l.meals_lookup), + Recipe: Object.values(l.recipes_lookup), + Ingredient: Object.values(l.ingredients_lookup), + ShoppingList: Object.values(l.shopping_list_lookup), + }), + }, + PurchasedShoppingList: { + dependants: (l) => ({ + ShoppingListItem: l.items, + Meal: Object.values(l.meals_lookup), + Recipe: Object.values(l.recipes_lookup), + Ingredient: Object.values(l.ingredients_lookup), + ShoppingList: l.list, + }), + }, + ShoppingList: { fields: ['created_date'], dependants: (l) => ({ ShoppingListItem: l.items }) }, + ShoppingListItem: { + fields: ['created_date'], + dependants: (r) => ({ + Meal: r.meal, + Recipe: r.recipe, + Ingredient: r.ingredient, + ShoppingList: r.list, + }), + }, + Recipe: { fields: ['date_created', 'date_hidden'] }, +} const fixDates = (obj, type) => { - if (!obj) return; + if (!obj) return - if (Array.isArray(obj)) { - for (const item of obj) { - fixDates(item, type); - } + if (Array.isArray(obj)) { + for (const item of obj) { + fixDates(item, type) } + } - const toFix = datesToFix[type]; - if (!toFix) return; + const toFix = datesToFix[type] + if (!toFix) return - if (toFix.fields) { - for (const field of toFix.fields) { - if (obj[field]) { - obj[field] = new Date(obj[field]); - } - } + if (toFix.fields) { + for (const field of toFix.fields) { + if (obj[field]) { + obj[field] = new Date(obj[field]) + } } + } - if (toFix.dependants) { - for (const [key, value] of Object.entries(toFix.dependants(obj))) { - if (Array.isArray(value)) { - for (const item of value) { - fixDates(item, key); - } - } - else { - fixDates(value, key); - } + if (toFix.dependants) { + for (const [key, value] of Object.entries(toFix.dependants(obj))) { + if (Array.isArray(value)) { + for (const item of value) { + fixDates(item, key) } + } else { + fixDates(value, key) + } } + } } const setPurchasedShoppingListReferences = (purchasedShoppingList) => { - if (!purchasedShoppingList) return; - const { ingredients_lookup, meals_lookup, recipes_lookup, } = purchasedShoppingList; - const shopping_list_lookup = { [purchasedShoppingList.list.id]: purchasedShoppingList.list }; - setShoppingListItemReferences(purchasedShoppingList.list.items, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup); + if (!purchasedShoppingList) return + const { ingredients_lookup, meals_lookup, recipes_lookup } = purchasedShoppingList + const shopping_list_lookup = { [purchasedShoppingList.list.id]: purchasedShoppingList.list } + setShoppingListItemReferences( + purchasedShoppingList.list.items, + ingredients_lookup, + meals_lookup, + recipes_lookup, + shopping_list_lookup + ) } const setCurrentShoppingListReferences = (currentShoppingList) => { - if (!currentShoppingList) return; - const { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup } = currentShoppingList; - const allShoppingListItems = [...currentShoppingList.outstanding_items, ...currentShoppingList.requested_meals, ...currentShoppingList.purchased_items]; + if (!currentShoppingList) return + const { ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup } = + currentShoppingList + const allShoppingListItems = [ + ...currentShoppingList.outstanding_items, + ...currentShoppingList.requested_meals, + ...currentShoppingList.purchased_items, + ] - setShoppingListItemReferences(allShoppingListItems, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup); + setShoppingListItemReferences( + allShoppingListItems, + ingredients_lookup, + meals_lookup, + recipes_lookup, + shopping_list_lookup + ) } -const setShoppingListItemReferences = (shoppingListItems, ingredients_lookup, meals_lookup, recipes_lookup, shopping_list_lookup) => { - if (!shoppingListItems) return; +const setShoppingListItemReferences = ( + shoppingListItems, + ingredients_lookup, + meals_lookup, + recipes_lookup, + shopping_list_lookup +) => { + if (!shoppingListItems) return - for (const item of shoppingListItems) { - if (item.ingredient_id) { - item.ingredient = ingredients_lookup[item.ingredient_id]; - } - if (item.meal_id) { - item.meal = meals_lookup[item.meal_id]; - } - if (item.list_id) { - item.list = shopping_list_lookup[item.list_id]; - } - if (item.recipe_id) { - item.recipe = recipes_lookup[item.recipe_id]; - } + for (const item of shoppingListItems) { + if (item.ingredient_id) { + item.ingredient = ingredients_lookup[item.ingredient_id] } + if (item.meal_id) { + item.meal = meals_lookup[item.meal_id] + } + if (item.list_id) { + item.list = shopping_list_lookup[item.list_id] + } + if (item.recipe_id) { + item.recipe = recipes_lookup[item.recipe_id] + } + } } -let user = null; +let user = null export default { - async markMealConsumed(meal_id) { - const response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal_id)}/consumed`, { - method: "POST", - credentials: "include", - }); + async markMealConsumed(meal_id) { + const response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal_id)}/consumed`, { + method: 'POST', + credentials: 'include', + }) - const meal = await response.json(); - fixDates(meal, "Meal"); + const meal = await response.json() + fixDates(meal, 'Meal') - return meal; - }, - async getUpcomingMeals(from, to) { - const response = await fetch(BASE_URL + "/meals/upcoming?from=" + from.toISOString() + "&to=" + to.toISOString()); - const meals = await response.json(); - fixDates(meals, "Meal"); + return meal + }, + async getUpcomingMeals(from, to) { + const response = await fetch( + BASE_URL + '/meals/upcoming?from=' + from.toISOString() + '&to=' + to.toISOString() + ) + const meals = await response.json() + fixDates(meals, 'Meal') - return meals.sort((a, b) => a.suggested_date - b.suggested_date); - }, - async getMeal(id) { - var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`); - const meal = await response.json(); - fixDates(meal, "Meal"); + return meals.sort((a, b) => a.suggested_date - b.suggested_date) + }, + async getMeal(id) { + var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`) + const meal = await response.json() + fixDates(meal, 'Meal') - return meal; - }, - async deleteMeal(id) { - var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, { - method: "DELETE", - }); + return meal + }, + async deleteMeal(id) { + var response = await fetch(BASE_URL + `/meals/${encodeURIComponent(id)}`, { + method: 'DELETE', + }) - return await response.json(); - }, - async searchRecipes(query) { - const response = await fetch(BASE_URL + "/recipes?q=" + encodeURIComponent(query)); - const recipes = await response.json(); - fixDates(recipes, "Recipe"); + return await response.json() + }, + async searchRecipes(query) { + const response = await fetch(BASE_URL + '/recipes?q=' + encodeURIComponent(query)) + const recipes = await response.json() + fixDates(recipes, 'Recipe') - return recipes; - }, - async parseRecipe(url) { - const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, { credentials: "include" }); - const recipe = await response.json(); - fixDates(recipe, "Recipe"); + return recipes + }, + async parseRecipe(url) { + const response = await fetch(BASE_URL + `/recipes/parse?url=${encodeURIComponent(url)}`, { + credentials: 'include', + }) + const recipe = await response.json() + fixDates(recipe, 'Recipe') - return recipe; - }, - async getRecipe(id) { - const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`); - const recipe = await response.json(); - fixDates(recipe, "Recipe"); + return recipe + }, + async getRecipe(id) { + const response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`) + const recipe = await response.json() + fixDates(recipe, 'Recipe') - return recipe; - }, - async parseProduct(ingredient, url) { - const body = { - url, tags: [ingredient.name, ingredient.line], - }; - - const response = await fetch(BASE_URL + "/products", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(body), - }); - - return await response.json(); - }, - async parseIngredients(lines) { - const params = lines.map(line => "ingredients=" + encodeURIComponent(line)).join("&"); - const response = await fetch(BASE_URL + "/recipes/ingredients/parse?" + params); - return await response.json(); - }, - async saveRecipe(recipe) { - const response = await fetch(BASE_URL + "/recipes", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(recipe), - }); - - const saved = await response.json(); - fixDates(saved, "Recipe"); - - return saved; - }, - async saveMeal(meal) { - let response = null; - if (meal.id >= 0) { - response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, { - method: "PUT", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(meal), - }); - } else { - response = await fetch(BASE_URL + "/meals", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(meal), - }); - } - - const saved = await response.json(); - fixDates(saved, "Meal"); - - return saved; - }, - async currentUser() { - if (user) { - return user; - } - - var cookie = decodeURIComponent(document.cookie).split(";").find(cookie => cookie.trimStart().startsWith("user_id=")); - if (cookie) { - const response = await fetch(BASE_URL + "/auth/refresh", { - method: "POST", - credentials: "include", - }); - - if (response.ok) { - user = await response.json(); - } - } - - return user; - }, - async login(username) { - const response = await fetch(BASE_URL + "/auth/login", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ username }), - }); - - if (response.ok) { - user = await response.json(); - } - - return user; - }, - async deleteRecipe(id) { - var response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`, { - method: "DELETE", - credentials: "include", - }); - - const recipe = await response.json(); - fixDates(recipe, "Recipe"); - return recipe; - }, - async searchPerson(name) { - const response = await fetch(BASE_URL + "/persons?q=" + encodeURIComponent(name)); - return await response.json(); - }, - async getMyShoppingList() { - const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { credentials: "include" }); - const ingredients = await response.json(); - fixDates(ingredients, 'Ingredient'); - - return ingredients; - }, - async saveMyShoppingList(list) { - const response = await fetch(BASE_URL + "/shopping/current/me/ingredients", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(list), - }); - - const ingredients = await response.json(); - fixDates(ingredients, 'Ingredient'); - - return ingredients; - }, - async getShoppingList(id) { - const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`); - - const purchasedShoppingList = await response.json(); - fixDates(purchasedShoppingList, "PurchasedShoppingList"); - setPurchasedShoppingListReferences(purchasedShoppingList); - - return purchasedShoppingList.list; - }, - async getCurrentShoppingList() { - const response = await fetch(BASE_URL + "/shopping/current"); - - const lst = await response.json(); - fixDates(lst, "CurrentShoppingList"); - setCurrentShoppingListReferences(lst); - - return lst; - }, - async purchaseShoppingList(completed_requests) { - const response = await fetch(BASE_URL + "/shopping/", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ items: completed_requests }), - }); - - const purchasedShoppingList = await response.json(); - fixDates(purchasedShoppingList, "PurchasedShoppingList"); - setPurchasedShoppingListReferences(purchasedShoppingList); - - return purchasedShoppingList.list; - }, - async requestMeal(meal_id) { - const response = await fetch(BASE_URL + "/shopping/current/meals/me", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ meal_id }), - }); - - const requests = await response.json(); - fixDates(requests, "ShoppingListItem"); - - return requests; - }, - async unrequestMeal(meal_id) { - const response = await fetch(BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`, { - method: "DELETE", - credentials: "include", - }); - - if (!response.ok) { - throw new Error("Failed to unrequest meal"); - } - }, - async getPersonsInHome() { - const response = await fetch(BASE_URL + "/persons"); - return await response.json(); + return recipe + }, + async parseProduct(ingredient, url) { + const body = { + url, + tags: [ingredient.name, ingredient.line], } -} \ No newline at end of file + + const response = await fetch(BASE_URL + '/products', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + + return await response.json() + }, + async parseIngredients(lines) { + const params = lines.map((line) => 'ingredients=' + encodeURIComponent(line)).join('&') + const response = await fetch(BASE_URL + '/recipes/ingredients/parse?' + params) + return await response.json() + }, + async saveRecipe(recipe) { + const response = await fetch(BASE_URL + '/recipes', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(recipe), + }) + + const saved = await response.json() + fixDates(saved, 'Recipe') + + return saved + }, + async saveMeal(meal) { + let response = null + if (meal.id >= 0) { + response = await fetch(BASE_URL + `/meals/${encodeURIComponent(meal.id)}`, { + method: 'PUT', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(meal), + }) + } else { + response = await fetch(BASE_URL + '/meals', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(meal), + }) + } + + const saved = await response.json() + fixDates(saved, 'Meal') + + return saved + }, + async currentUser() { + if (user) { + return user + } + + var cookie = decodeURIComponent(document.cookie) + .split(';') + .find((cookie) => cookie.trimStart().startsWith('user_id=')) + if (cookie) { + const response = await fetch(BASE_URL + '/auth/refresh', { + method: 'POST', + credentials: 'include', + }) + + if (response.ok) { + user = await response.json() + } + } + + return user + }, + async login(username) { + const response = await fetch(BASE_URL + '/auth/login', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username }), + }) + + if (response.ok) { + user = await response.json() + } + + return user + }, + async deleteRecipe(id) { + var response = await fetch(BASE_URL + `/recipes/${encodeURIComponent(id)}`, { + method: 'DELETE', + credentials: 'include', + }) + + const recipe = await response.json() + fixDates(recipe, 'Recipe') + return recipe + }, + async searchPerson(name) { + const response = await fetch(BASE_URL + '/persons?q=' + encodeURIComponent(name)) + return await response.json() + }, + async getMyShoppingList() { + const response = await fetch(BASE_URL + '/shopping/current/me/ingredients', { + credentials: 'include', + }) + const ingredients = await response.json() + fixDates(ingredients, 'Ingredient') + + return ingredients + }, + async saveMyShoppingList(list) { + const response = await fetch(BASE_URL + '/shopping/current/me/ingredients', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(list), + }) + + const ingredients = await response.json() + fixDates(ingredients, 'Ingredient') + + return ingredients + }, + async getShoppingList(id) { + const response = await fetch(BASE_URL + `/shopping/${encodeURIComponent(id)}`) + + const purchasedShoppingList = await response.json() + fixDates(purchasedShoppingList, 'PurchasedShoppingList') + setPurchasedShoppingListReferences(purchasedShoppingList) + + return purchasedShoppingList.list + }, + async getCurrentShoppingList() { + const response = await fetch(BASE_URL + '/shopping/current') + + const lst = await response.json() + fixDates(lst, 'CurrentShoppingList') + setCurrentShoppingListReferences(lst) + + return lst + }, + async purchaseShoppingList(completed_requests) { + const response = await fetch(BASE_URL + '/shopping/', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ items: completed_requests }), + }) + + const purchasedShoppingList = await response.json() + fixDates(purchasedShoppingList, 'PurchasedShoppingList') + setPurchasedShoppingListReferences(purchasedShoppingList) + + return purchasedShoppingList.list + }, + async requestMeal(meal_id) { + const response = await fetch(BASE_URL + '/shopping/current/meals/me', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ meal_id }), + }) + + const requests = await response.json() + fixDates(requests, 'ShoppingListItem') + + return requests + }, + async unrequestMeal(meal_id) { + const response = await fetch( + BASE_URL + `/shopping/current/meals/${encodeURIComponent(meal_id)}`, + { + method: 'DELETE', + credentials: 'include', + } + ) + + if (!response.ok) { + throw new Error('Failed to unrequest meal') + } + }, + async getPersonsInHome() { + const response = await fetch(BASE_URL + '/persons') + return await response.json() + }, +} diff --git a/src/dateformats.js b/src/dateformats.js index 5425fde..8ae1891 100644 --- a/src/dateformats.js +++ b/src/dateformats.js @@ -1,25 +1,25 @@ function plural(num, unit) { - num = Math.floor(num); - return num + " " + unit + (num === 1 ? "" : "s"); + num = Math.floor(num) + return num + ' ' + unit + (num === 1 ? '' : 's') } export function ago(date) { - const now = new Date(); - const diff = now - date; - if (diff < 1000) { - return "just now"; - } - if (diff < 60 * 1000) { - return plural(diff / 1000, "second") + " ago"; - } - if (diff < 60 * 60 * 1000) { - return plural(diff / (60 * 1000), "minute") + " ago"; - } - if (diff < 24 * 60 * 60 * 1000) { - return plural(diff / (60 * 60 * 1000), "hour") + " ago"; - } - if (diff < 7 * 24 * 60 * 60 * 1000) { - return plural(diff / (24 * 60 * 60 * 1000), "day") + " ago"; - } - return date.toLocaleDateString(); -} \ No newline at end of file + const now = new Date() + const diff = now - date + if (diff < 1000) { + return 'just now' + } + if (diff < 60 * 1000) { + return plural(diff / 1000, 'second') + ' ago' + } + if (diff < 60 * 60 * 1000) { + return plural(diff / (60 * 1000), 'minute') + ' ago' + } + if (diff < 24 * 60 * 60 * 1000) { + return plural(diff / (60 * 60 * 1000), 'hour') + ' ago' + } + if (diff < 7 * 24 * 60 * 60 * 1000) { + return plural(diff / (24 * 60 * 60 * 1000), 'day') + ' ago' + } + return date.toLocaleDateString() +} diff --git a/src/main.js b/src/main.js index 6d74738..4b58570 100644 --- a/src/main.js +++ b/src/main.js @@ -8,4 +8,4 @@ const router = createAppRouter(() => currentUser()) const app = createApp(App) app.use(router) -app.mount('#app') \ No newline at end of file +app.mount('#app') diff --git a/src/router/index.js b/src/router/index.js index 1d2a9d3..2fcaf01 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -5,7 +5,8 @@ const LoginPage = () => import('@/components/LoginPage.vue') const RecipesPage = () => import('@/components/recipes/RecipesPage.vue') const MealPlanPage = () => import('@/components/meals/MealPlanPage.vue') const MyShoppingPage = () => import('@/components/shopping/MyShoppingPage.vue') -const PurchasedShoppingListPage = () => import('@/components/shopping/PurchasedShoppingListPage.vue') +const PurchasedShoppingListPage = () => + import('@/components/shopping/PurchasedShoppingListPage.vue') const CurrentShoppingListPage = () => import('@/components/shopping/CurrentShoppingListPage.vue') const EditMealPage = () => import('@/components/meals/EditMealPage.vue') const EditRecipePage = () => import('@/components/recipes/EditRecipePage.vue') @@ -15,14 +16,47 @@ export function createAppRouter(getCurrentUser) { { path: '/', redirect: { name: 'mealplan' } }, { path: '/login', name: 'login', component: LoginPage }, { path: '/mealplan', name: 'mealplan', component: MealPlanPage, meta: { requiresAuth: true } }, - { path: '/shopping', name: 'shopping', component: MyShoppingPage, meta: { requiresAuth: true } }, - { path: '/shopping/current', name: 'shopping-current', component: CurrentShoppingListPage, meta: { requiresAuth: true } }, - { path: '/shopping/:id', name: 'shopping-list', component: PurchasedShoppingListPage, props: true, meta: { requiresAuth: true } }, + { + path: '/shopping', + name: 'shopping', + component: MyShoppingPage, + meta: { requiresAuth: true }, + }, + { + path: '/shopping/current', + name: 'shopping-current', + component: CurrentShoppingListPage, + meta: { requiresAuth: true }, + }, + { + path: '/shopping/:id', + name: 'shopping-list', + component: PurchasedShoppingListPage, + props: true, + meta: { requiresAuth: true }, + }, { path: '/recipes', name: 'recipes', component: RecipesPage, meta: { requiresAuth: true } }, - { path: '/recipes/add', name: 'recipe-add', component: EditRecipePage, meta: { requiresAuth: true } }, - { path: '/recipes/:id', name: 'recipe-edit', component: EditRecipePage, props: true, meta: { requiresAuth: true } }, + { + path: '/recipes/add', + name: 'recipe-add', + component: EditRecipePage, + meta: { requiresAuth: true }, + }, + { + path: '/recipes/:id', + name: 'recipe-edit', + component: EditRecipePage, + props: true, + meta: { requiresAuth: true }, + }, { path: '/meals/add', name: 'meal-add', component: EditMealPage, meta: { requiresAuth: true } }, - { path: '/meals/:id', name: 'meal-edit', component: EditMealPage, props: true, meta: { requiresAuth: true } }, + { + path: '/meals/:id', + name: 'meal-edit', + component: EditMealPage, + props: true, + meta: { requiresAuth: true }, + }, ] const router = createRouter({ @@ -36,7 +70,9 @@ export function createAppRouter(getCurrentUser) { try { const user = await getCurrentUser() if (user) return true - } catch (_) { /* ignore */ } + } catch (_) { + /* ignore */ + } return { name: 'login', query: { redirect: to.fullPath } } }) diff --git a/src/units.js b/src/units.js index 1d34d76..59f4a4a 100644 --- a/src/units.js +++ b/src/units.js @@ -1,142 +1,142 @@ export const equivalentUnits = { - 'kg': { - 'kgs': 1, - 'kilograms': 1, - 'kilogram': 1, + kg: { + kgs: 1, + kilograms: 1, + kilogram: 1, - 'g': 1000, - 'gram': 1000, - 'grams': 1000, + g: 1000, + gram: 1000, + grams: 1000, - 'lb': 2.20462, - 'lbs': 2.20462, - 'pound': 2.20462, - 'pounds': 2.20462, - }, - 'litres': { - 'l': 1, - 'liter': 1, - 'litre': 1, + lb: 2.20462, + lbs: 2.20462, + pound: 2.20462, + pounds: 2.20462, + }, + litres: { + l: 1, + liter: 1, + litre: 1, - 'ml': 1000, - 'milliliters': 1000, - 'milliliter': 1000, + ml: 1000, + milliliters: 1000, + milliliter: 1000, - 'fl oz': 33.814, - 'fluid ounce': 33.814, - 'fluid ounces': 33.814, + 'fl oz': 33.814, + 'fluid ounce': 33.814, + 'fluid ounces': 33.814, - 'cup': 4.22675, - 'cups': 4.22675, + cup: 4.22675, + cups: 4.22675, - 'tbsp': 67.628, - 'tablespoon': 67.628, - 'tablespoons': 67.628, + tbsp: 67.628, + tablespoon: 67.628, + tablespoons: 67.628, - 'tsp': 202.884, - 'teaspoon': 202.884, - 'teaspoons': 202.884, + tsp: 202.884, + teaspoon: 202.884, + teaspoons: 202.884, - 'pt': 2.11338, - 'pint': 2.11338, - 'pints': 2.11338, + pt: 2.11338, + pint: 2.11338, + pints: 2.11338, - 'qt': 1.05669, - 'quart': 1.05669, - 'quarts': 1.05669, + qt: 1.05669, + quart: 1.05669, + quarts: 1.05669, - 'gal': 0.264172, - 'gallon': 0.264172, - 'gallons': 0.264172, + gal: 0.264172, + gallon: 0.264172, + gallons: 0.264172, - 'oz': 35.1951, - 'ounce': 35.1951, - }, - 'items': { - 'item': 1, - 'items': 1, - 'pcs': 1, - 'piece': 1, - 'pieces': 1, - 'florets': 8, // Broccoli - 'head': 1, // Broccoli - 'heads': 1, // Broccoli - 'slice': 10, // Bread - 'slices': 10, // Bread - 'loaf': 1, // Bread - 'loaves': 1, // Bread - 'cloves': 8, // Garlic - 'bulb': 1, // Garlic - 'bulbs': 1, // Garlic - 'stalk': 1, // Celery - 'stalks': 1, // Celery - 'bunch': 1, // Cilantro - 'bunches': 1, // Cilantro - 'sprig': 1, // Cilantro - 'sprigs': 1, // Cilantro - 'cans': 1, // Canned goods - 'can': 1, // Canned goods - 'pack': 1, // Packaged goods - 'packs': 1, // Packaged goods - 'package': 1, // Packaged goods - 'packages': 1, // Packaged goods - 'container': 1, // Packaged goods - 'containers': 1, // Packaged goods - }, + oz: 35.1951, + ounce: 35.1951, + }, + items: { + item: 1, + items: 1, + pcs: 1, + piece: 1, + pieces: 1, + florets: 8, // Broccoli + head: 1, // Broccoli + heads: 1, // Broccoli + slice: 10, // Bread + slices: 10, // Bread + loaf: 1, // Bread + loaves: 1, // Bread + cloves: 8, // Garlic + bulb: 1, // Garlic + bulbs: 1, // Garlic + stalk: 1, // Celery + stalks: 1, // Celery + bunch: 1, // Cilantro + bunches: 1, // Cilantro + sprig: 1, // Cilantro + sprigs: 1, // Cilantro + cans: 1, // Canned goods + can: 1, // Canned goods + pack: 1, // Packaged goods + packs: 1, // Packaged goods + package: 1, // Packaged goods + packages: 1, // Packaged goods + container: 1, // Packaged goods + containers: 1, // Packaged goods + }, } function getBaseUnit(unit) { - for (const unitType in equivalentUnits) { - if (unit in equivalentUnits[unitType]) { - return unitType; - } + for (const unitType in equivalentUnits) { + if (unit in equivalentUnits[unitType]) { + return unitType } + } - return null; + return null } export function getConversionFactor(unit) { - if (unit in equivalentUnits) { - return { unit, factor: 1 }; - } + if (unit in equivalentUnits) { + return { unit, factor: 1 } + } - const unitLower = unit.toLowerCase(); - if (unitLower in equivalentUnits) { - return { unit: unitLower, factor: 1 }; - } - - const baseUnit = getBaseUnit(unit); - if (baseUnit) { - return { - unit: baseUnit, - factor: equivalentUnits[baseUnit][unit], - }; - } + const unitLower = unit.toLowerCase() + if (unitLower in equivalentUnits) { + return { unit: unitLower, factor: 1 } + } - const baseUnitLower = getBaseUnit(unitLower); - if (baseUnitLower) { - return { - unit: baseUnitLower, - factor: equivalentUnits[baseUnitLower][unitLower], - }; + const baseUnit = getBaseUnit(unit) + if (baseUnit) { + return { + unit: baseUnit, + factor: equivalentUnits[baseUnit][unit], } + } - return null; + const baseUnitLower = getBaseUnit(unitLower) + if (baseUnitLower) { + return { + unit: baseUnitLower, + factor: equivalentUnits[baseUnitLower][unitLower], + } + } + + return null } export function calculateTotals(quantityList) { - const totals = {}; - for (const quantity of quantityList) { - const baseUnit = getConversionFactor(quantity.unit) ?? { unit: quantity.unit, factor: 1 }; - const factor = baseUnit.factor; - const unit = baseUnit.unit; + const totals = {} + for (const quantity of quantityList) { + const baseUnit = getConversionFactor(quantity.unit) ?? { unit: quantity.unit, factor: 1 } + const factor = baseUnit.factor + const unit = baseUnit.unit - if (!totals[unit]) { - totals[unit] = 0; - } - - totals[unit] += quantity.quantity / factor; + if (!totals[unit]) { + totals[unit] = 0 } - return Object.keys(totals).map(unit => ({ unit, quantity: totals[unit] })); -} \ No newline at end of file + totals[unit] += quantity.quantity / factor + } + + return Object.keys(totals).map((unit) => ({ unit, quantity: totals[unit] })) +} diff --git a/vue.config.js b/vue.config.js index 910e297..523a634 100644 --- a/vue.config.js +++ b/vue.config.js @@ -1,4 +1,4 @@ const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ - transpileDependencies: true + transpileDependencies: true, })