1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-02 20:15:24 +02:00

More localization (#358)

* Translate missing items on About page

* Localize import summary dialog

* Make site menu translation reactive

* Localize import options

* Include semi colon in string

* Move API texts to frontend + better status codes

* Provide feedback to user when no meal is planned

* Fix API tests after latest rework

* Add warning for API changes in changelog

* Refactor API texts handling

* Refactor API texts handling #2

* Better API feedback

* Rearrange strings hierarchy

* Add messages upon recipe updated

* Fix 'recipe effected' typo

* Remove snackbar usage in backend

* Translate toolbox

* Provide feedback for tags CRUD

* Fix messed up merge

* Translate sign-up form

* Better feedback for sign-up CRUD

* Refactor log-in API texts handling

* No error message when user is not authenticated

* Remove unimportant console log
This commit is contained in:
sephrat 2021-04-29 18:22:45 +02:00 committed by GitHub
parent 861020ffe0
commit 1e5edc7434
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 890 additions and 606 deletions

View file

@ -1,75 +1,57 @@
const baseURL = "/api/";
import axios from "axios";
import utils from "@/utils";
import { store } from "../store";
import utils from "@/utils";
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${store.getters.getToken}`;
function processResponse(response) {
try {
utils.notify.show(response.data.snackbar.text, response.data.snackbar.type);
} catch (err) {
return;
function handleError(error, getText) {
if(getText) {
utils.notify.error(getText(error.response));
}
return false;
}
function handleResponse(response, getText) {
if(response && getText) {
const successText = getText(response);
utils.notify.success(successText);
}
return response;
}
return;
function defaultErrorText(response) {
return response.statusText;
}
function defaultSuccessText(response) {
return response.statusText;
}
const apiReq = {
post: async function(url, data) {
let response = await axios.post(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return error.response;
}
});
processResponse(response);
return response;
post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.post(url, data).catch(function(error) { handleError(error, getErrorText) });
return handleResponse(response, getSuccessText);
},
put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.put(url, data).catch(function(error) { handleError(error, getErrorText) });
return handleResponse(response, getSuccessText);
},
patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
const response = await axios.patch(url, data).catch(function(error) { handleError(error, getErrorText) });
return handleResponse(response, getSuccessText);
},
put: async function(url, data) {
let response = await axios.put(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
} else return;
});
processResponse(response);
return response;
},
patch: async function(url, data) {
let response = await axios.patch(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
} else return;
});
processResponse(response);
return response;
get: function(url, data, getErrorText = defaultErrorText) {
return axios.get(url, data).catch(function(error) { handleError(error, getErrorText) });
},
get: async function(url, data) {
let response = await axios.get(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
} else return;
});
processResponse(response);
return response;
},
delete: async function(url, data) {
let response = await axios.delete(url, data).catch(function(error) {
if (error.response) {
processResponse(error.response);
return response;
}
});
processResponse(response);
return response;
delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText ) {
const response = await axios.delete(url, data).catch( function(error) { handleError(error, getErrorText) } );
return handleResponse(response, getSuccessText);
},
async download(url) {

View file

@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
const backupBase = baseURL + "backups/";
@ -40,7 +41,12 @@ export const backupAPI = {
* @param {string} fileName
*/
async delete(fileName) {
await apiReq.delete(backupURLs.deleteBackup(fileName));
return apiReq.delete(
backupURLs.deleteBackup(fileName),
null,
function() { return i18n.t('settings.backup.unable-to-delete-backup'); },
function() { return i18n.t('settings.backup.backup-deleted'); }
);
},
/**
* Creates a backup on the serve given a set of options
@ -48,8 +54,12 @@ export const backupAPI = {
* @returns
*/
async create(options) {
let response = apiReq.post(backupURLs.createBackup, options);
return response;
return apiReq.post(
backupURLs.createBackup,
options,
function() { return i18n.t('settings.backup.error-creating-backup-see-log-file'); },
function(response) { return i18n.t('settings.backup.backup-created-at-response-export_path', {path: response.data.export_path}); }
);
},
/**
* Downloads a file from the server. I don't actually think this is used?

View file

@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
const prefix = baseURL + "categories";
@ -22,29 +23,44 @@ export const categoryAPI = {
return response.data;
},
async create(name) {
let response = await apiReq.post(categoryURLs.getAll, { name: name });
store.dispatch("requestCategories");
return response.data;
const response = await apiReq.post(
categoryURLs.getAll,
{ name: name },
function() { return i18n.t('category.category-creation-failed'); },
function() { return i18n.t('category.category-created'); }
);
if(response) {
store.dispatch("requestCategories");
return response.data;
}
},
async getRecipesInCategory(category) {
let response = await apiReq.get(categoryURLs.getCategory(category));
return response.data;
},
async update(name, newName, overrideRequest = false) {
let response = await apiReq.put(categoryURLs.updateCategory(name), {
name: newName,
});
if (!overrideRequest) {
const response = await apiReq.put(
categoryURLs.updateCategory(name),
{ name: newName },
function() { return i18n.t('category.category-update-failed'); },
function() { return i18n.t('category.category-updated'); }
);
if (response && !overrideRequest) {
store.dispatch("requestCategories");
return response.data;
}
return response.data;
},
async delete(category, overrideRequest = false) {
let response = await apiReq.delete(categoryURLs.deleteCategory(category));
if (!overrideRequest) {
const response = await apiReq.delete(
categoryURLs.deleteCategory(category),
null,
function() { return i18n.t('category.category-deletion-failed'); },
function() { return i18n.t('category.category-deleted'); }
);
if (response && !overrideRequest) {
store.dispatch("requestCategories");
}
return response.data;
return response;
},
};
@ -68,28 +84,48 @@ export const tagAPI = {
return response.data;
},
async create(name) {
let response = await apiReq.post(tagURLs.getAll, { name: name });
store.dispatch("requestTags");
return response.data;
const response = await apiReq.post(
tagURLs.getAll,
{ name: name },
function() { return i18n.t('tag.tag-creation-failed'); },
function() { return i18n.t('tag.tag-created'); }
);
if(response) {
store.dispatch("requestTags");
return response.data;
}
},
async getRecipesInTag(tag) {
let response = await apiReq.get(tagURLs.getTag(tag));
return response.data;
},
async update(name, newName, overrideRequest = false) {
let response = await apiReq.put(tagURLs.updateTag(name), { name: newName });
const response = await apiReq.put(
tagURLs.updateTag(name),
{ name: newName },
function() { return i18n.t('tag.tag-update-failed'); },
function() { return i18n.t('tag.tag-updated'); }
);
if (!overrideRequest) {
store.dispatch("requestTags");
if(response) {
if (!overrideRequest) {
store.dispatch("requestTags");
}
return response.data;
}
return response.data;
},
async delete(tag, overrideRequest = false) {
let response = await apiReq.delete(tagURLs.deleteTag(tag));
if (!overrideRequest) {
store.dispatch("requestTags");
const response = await apiReq.delete(
tagURLs.deleteTag(tag),
null,
function() { return i18n.t('tag.tag-deletion-failed'); },
function() { return i18n.t('tag.tag-deleted'); }
);
if(response) {
if (!overrideRequest) {
store.dispatch("requestTags");
}
return response.data;
}
return response.data;
},
};

View file

@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
const groupPrefix = baseURL + "groups";
const groupsURLs = {
@ -10,25 +11,58 @@ const groupsURLs = {
update: id => `${groupPrefix}/${id}`,
};
function deleteErrorText(response) {
switch(response.data.detail) {
case 'GROUP_WITH_USERS':
return i18n.t('group.cannot-delete-group-with-users');
case 'GROUP_NOT_FOUND':
return i18n.t('group.group-not-found');
case 'DEFAULT_GROUP':
return i18n.t('group.cannot-delete-default-group');
default:
return i18n.t('group.group-deletion-failed');
}
}
export const groupAPI = {
async allGroups() {
let response = await apiReq.get(groupsURLs.groups);
return response.data;
},
async create(name) {
let response = await apiReq.post(groupsURLs.create, { name: name });
return response.data;
create(name) {
return apiReq.post(
groupsURLs.create,
{ name: name },
function() { return i18n.t('group.user-group-creation-failed'); },
function() { return i18n.t('group.user-group-created'); }
);
},
async delete(id) {
let response = await apiReq.delete(groupsURLs.delete(id));
return response.data;
delete(id) {
return apiReq.delete(
groupsURLs.delete(id),
null,
deleteErrorText,
function() { return i18n.t('group.group-deleted'); }
);
},
async current() {
let response = await apiReq.get(groupsURLs.current);
return response.data;
const response = await apiReq.get(
groupsURLs.current,
null,
null);
if(response) {
return response.data;
}
},
async update(data) {
let response = await apiReq.put(groupsURLs.update(data.id), data);
return response.data;
update(data) {
return apiReq.put(
groupsURLs.update(data.id),
data,
function() { return i18n.t('group.error-updating-group'); },
function() { return i18n.t('settings.group-settings-updated'); }
);
},
};

View file

@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
const prefix = baseURL + "meal-plans/";
@ -15,9 +16,13 @@ const mealPlanURLs = {
};
export const mealplanAPI = {
async create(postBody) {
let response = await apiReq.post(mealPlanURLs.create, postBody);
return response;
create(postBody) {
return apiReq.post(
mealPlanURLs.create,
postBody,
function() { return i18n.t('meal-plan.mealplan-creation-failed')},
function() { return i18n.t('meal-plan.mealplan-created'); }
);
},
async all() {
@ -35,14 +40,21 @@ export const mealplanAPI = {
return response;
},
async delete(id) {
let response = await apiReq.delete(mealPlanURLs.delete(id));
return response;
delete(id) {
return apiReq.delete(mealPlanURLs.delete(id),
null,
function() { return i18n.t('meal-plan.mealplan-deletion-failed'); },
function() { return i18n.t('meal-plan.mealplan-deleted'); }
);
},
async update(id, body) {
let response = await apiReq.put(mealPlanURLs.update(id), body);
return response;
update(id, body) {
return apiReq.put(
mealPlanURLs.update(id),
body,
function() { return i18n.t('meal-plan.mealplan-update-failed'); },
function() { return i18n.t('meal-plan.mealplan-updated'); }
);
},
async shoppingList(id) {

View file

@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "../store";
import i18n from '@/i18n.js';
const migrationBase = baseURL + "migrations";
@ -17,8 +18,13 @@ export const migrationAPI = {
return response.data;
},
async delete(folder, file) {
let response = await apiReq.delete(migrationURLs.delete(folder, file));
return response.data;
const response = await apiReq.delete(
migrationURLs.delete(folder, file),
null,
function() { return i18n.t('general.file-folder-not-found'); },
function() { return i18n.t('migration.migration-data-removed'); }
);
return response;
},
async import(folder, file) {
let response = await apiReq.post(migrationURLs.import(folder, file));

View file

@ -1,7 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "../store";
import { router } from "../main";
import i18n from '@/i18n.js';
const prefix = baseURL + "recipes/";
@ -26,9 +26,12 @@ export const recipeAPI = {
* @returns {string} Recipe Slug
*/
async createByURL(recipeURL) {
let response = await apiReq.post(recipeURLs.createByURL, {
url: recipeURL,
});
const response = await apiReq.post(
recipeURLs.createByURL,
{ url: recipeURL },
function() { return i18n.t('recipe.recipe-creation-failed'); },
function() { return i18n.t('recipe.recipe-created'); }
);
store.dispatch("requestRecentRecipes");
return response;
@ -43,7 +46,12 @@ export const recipeAPI = {
},
async create(recipeData) {
let response = await apiReq.post(recipeURLs.create, recipeData);
const response = await apiReq.post(
recipeURLs.create,
recipeData,
function() { return i18n.t('recipe.recipe-creation-failed'); },
function() { return i18n.t('recipe.recipe-created'); }
);
store.dispatch("requestRecentRecipes");
return response.data;
},
@ -53,14 +61,24 @@ export const recipeAPI = {
return response.data;
},
async updateImage(recipeSlug, fileObject) {
const fd = new FormData();
fd.append("image", fileObject);
fd.append("extension", fileObject.name.split(".").pop());
let response = apiReq.put(recipeURLs.updateImage(recipeSlug), fd);
return response;
},
updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) {
const formData = new FormData();
formData.append("image", fileObject);
formData.append("extension", fileObject.name.split(".").pop());
let successMessage = null;
if(!overrideSuccessMsg) {
successMessage = function() { return overrideSuccessMsg ? null : i18n.t('recipe.recipe-image-updated'); };
}
return apiReq.put(
recipeURLs.updateImage(recipeSlug),
formData,
function() { return i18n.t('general.image-upload-failed'); },
successMessage
);
},
async createAsset(recipeSlug, fileObject, name, icon) {
const fd = new FormData();
fd.append("file", fileObject);
@ -70,17 +88,27 @@ export const recipeAPI = {
let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
return response;
},
async updateImagebyURL(slug, url) {
const response = apiReq.post(recipeURLs.updateImage(slug), { url: url });
return response;
updateImagebyURL(slug, url) {
return apiReq.post(
recipeURLs.updateImage(slug),
{ url: url },
function() { return i18n.t('general.image-upload-failed'); },
function() { return i18n.t('recipe.recipe-image-updated'); }
);
},
async update(data) {
console.log(data)
let response = await apiReq.put(recipeURLs.update(data.slug), data);
store.dispatch("patchRecipe", response.data);
return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request
let response = await apiReq.put(
recipeURLs.update(data.slug),
data,
function() { return i18n.t('recipe.recipe-update-failed'); },
function() { return i18n.t('recipe.recipe-updated'); }
);
if(response) {
store.dispatch("patchRecipe", response.data);
return response.data.slug; // ! Temporary until I rewrite to refresh page without additional request
}
},
async patch(data) {
@ -89,10 +117,13 @@ export const recipeAPI = {
return response.data;
},
async delete(recipeSlug) {
await apiReq.delete(recipeURLs.delete(recipeSlug));
store.dispatch("requestRecentRecipes");
router.push(`/`);
delete(recipeSlug) {
return apiReq.delete(
recipeURLs.delete(recipeSlug),
null,
function() { return i18n.t('recipe.unable-to-delete-recipe'); },
function() { return i18n.t('recipe.recipe-deleted'); }
);
},
async allSummary(start = 0, limit = 9999) {

View file

@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
const signUpPrefix = baseURL + "users/sign-ups";
@ -16,15 +17,25 @@ export const signupAPI = {
return response.data;
},
async createToken(data) {
let response = await apiReq.post(signUpURLs.createToken, data);
let response = await apiReq.post(
signUpURLs.createToken,
data,
function() { return i18n.t('signup.sign-up-link-creation-failed'); },
function() { return i18n.t('signup.sign-up-link-created'); }
);
return response.data;
},
async deleteToken(token) {
let response = await apiReq.delete(signUpURLs.deleteToken(token));
return response.data;
return await apiReq.delete(signUpURLs.deleteToken(token),
null,
function() { return i18n.t('signup.sign-up-token-deletion-failed'); },
function() { return i18n.t('signup.sign-up-token-deleted'); }
);
},
async createUser(token, data) {
let response = await apiReq.post(signUpURLs.createUser(token), data);
return response.data;
return apiReq.post(signUpURLs.createUser(token), data,
function() { return i18n.t('user.you-are-not-allowed-to-create-a-user'); },
function() { return i18n.t('user.user-created'); }
);
},
};

View file

@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import { store } from "@/store";
import i18n from '@/i18n.js';
const settingsBase = baseURL + "site-settings";
@ -19,9 +20,16 @@ export const siteSettingsAPI = {
},
async update(body) {
let response = await apiReq.put(settingsURLs.updateSiteSettings, body);
store.dispatch("requestSiteSettings");
return response.data;
const response = await apiReq.put(
settingsURLs.updateSiteSettings,
body,
function() { return i18n.t('settings.settings-update-failed'); },
function() { return i18n.t('settings.settings-updated'); }
);
if(response) {
store.dispatch("requestSiteSettings");
}
return response;
},
async getPages() {
@ -34,23 +42,39 @@ export const siteSettingsAPI = {
return response.data;
},
async createPage(body) {
let response = await apiReq.post(settingsURLs.customPages, body);
return response.data;
createPage(body) {
return apiReq.post(
settingsURLs.customPages,
body,
function() { return i18n.t('page.page-creation-failed'); },
function() { return i18n.t('page.new-page-created'); }
);
},
async deletePage(id) {
let response = await apiReq.delete(settingsURLs.customPage(id));
return response.data;
return await apiReq.delete(
settingsURLs.customPage(id),
null,
function() { return i18n.t('page.page-deletion-failed'); },
function() { return i18n.t('page.page-deleted'); });
},
async updatePage(body) {
let response = await apiReq.put(settingsURLs.customPage(body.id), body);
return response.data;
updatePage(body) {
return apiReq.put(
settingsURLs.customPage(body.id),
body,
function() { return i18n.t('page.page-update-failed'); },
function() { return i18n.t('page.page-updated'); }
);
},
async updateAllPages(allPages) {
let response = await apiReq.put(settingsURLs.customPages, allPages);
let response = await apiReq.put(
settingsURLs.customPages,
allPages,
function() { return i18n.t('page.pages-update-failed'); },
function() { return i18n.t('page.pages-updated'); }
);
return response;
},
};

View file

@ -1,5 +1,6 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
const prefix = baseURL + "themes";
@ -23,21 +24,31 @@ export const themeAPI = {
},
async create(postBody) {
let response = await apiReq.post(settingsURLs.createTheme, postBody);
return response.data;
return await apiReq.post(
settingsURLs.createTheme,
postBody,
function() { return i18n.t('settings.theme.error-creating-theme-see-log-file'); },
function() { return i18n.t('settings.theme.theme-saved'); });
},
async update(themeName, colors) {
update(themeName, colors) {
const body = {
name: themeName,
colors: colors,
};
let response = await apiReq.put(settingsURLs.updateTheme(themeName), body);
return response.data;
return apiReq.put(
settingsURLs.updateTheme(themeName),
body,
function() { return i18n.t('settings.theme.error-updating-theme'); },
function() { return i18n.t('settings.theme.theme-updated'); });
},
async delete(themeName) {
let response = await apiReq.delete(settingsURLs.deleteTheme(themeName));
return response.data;
delete(themeName) {
return apiReq.delete(
settingsURLs.deleteTheme(themeName),
null,
function() { return i18n.t('settings.theme.error-deleting-theme'); },
function() { return i18n.t('settings.theme.theme-deleted'); }
);
},
};

View file

@ -1,15 +1,16 @@
import { apiReq } from "./api-utils";
import i18n from '@/i18n.js';
export const utilsAPI = {
// import { api } from "@/api";
async uploadFile(url, fileObject) {
uploadFile(url, fileObject) {
console.log("API Called");
let response = await apiReq.post(url, fileObject, {
headers: {
"Content-Type": "multipart/form-data",
},
});
return response.data;
return apiReq.post(
url,
fileObject,
function() { return i18n.t('general.failure-uploading-file'); },
function() { return i18n.t('general.file-uploaded'); }
);
},
};

View file

@ -1,6 +1,7 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
import axios from "axios";
import i18n from '@/i18n.js';
const authPrefix = baseURL + "auth";
const userPrefix = baseURL + "users";
@ -17,13 +18,23 @@ const usersURLs = {
resetPassword: id => `${userPrefix}/${id}/reset-password`,
};
function deleteErrorText(response) {
switch(response.data.detail) {
case 'SUPER_USER':
return i18n.t('user.error-cannot-delete-super-user');
default:
return i18n.t('user.you-are-not-allowed-to-delete-this-user');
}
}
export const userAPI = {
async login(formData) {
let response = await apiReq.post(authURLs.token, formData, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
let response = await apiReq.post(
authURLs.token,
formData,
function() { return i18n.t('user.incorrect-username-or-password'); },
function() { return i18n.t('user.user-successfully-logged-in'); }
);
return response;
},
async refresh() {
@ -36,9 +47,13 @@ export const userAPI = {
let response = await apiReq.get(usersURLs.users);
return response.data;
},
async create(user) {
let response = await apiReq.post(usersURLs.users, user);
return response.data;
create(user) {
return apiReq.post(
usersURLs.users,
user,
function() { return i18n.t('user.user-creation-failed'); },
function() { return i18n.t('user.user-created'); }
);
},
async self() {
let response = await apiReq.get(usersURLs.self);
@ -48,20 +63,37 @@ export const userAPI = {
let response = await apiReq.get(usersURLs.userID(id));
return response.data;
},
async update(user) {
let response = await apiReq.put(usersURLs.userID(user.id), user);
return response.data;
update(user) {
return apiReq.put(
usersURLs.userID(user.id),
user,
function() { return i18n.t('user.user-update-failed'); },
function() { return i18n.t('user.user-updated'); }
);
},
async changePassword(id, password) {
let response = await apiReq.put(usersURLs.password(id), password);
return response.data;
changePassword(id, password) {
return apiReq.put(
usersURLs.password(id),
password,
function() { return i18n.t('user.existing-password-does-not-match'); },
function() { return i18n.t('user.password-updated'); }
);
},
async delete(id) {
let response = await apiReq.delete(usersURLs.userID(id));
return response.data;
delete(id) {
return apiReq.delete(
usersURLs.userID(id),
null,
deleteErrorText,
function() { return i18n.t('user.user-deleted'); }
);
},
async resetPassword(id) {
let response = await apiReq.put(usersURLs.resetPassword(id));
return response.data;
resetPassword(id) {
return apiReq.put(
usersURLs.resetPassword(id),
null,
function() { return i18n.t('user.password-reset-failed'); },
function() { return i18n.t('user.password-has-been-reset-to-the-default-password'); }
);
},
};

View file

@ -90,7 +90,7 @@ export default {
computed: {
inputLabel() {
if (!this.showLabel) return null;
return this.tagSelector ? this.$t('recipe.tags') : this.$t('recipe.categories');
return this.tagSelector ? this.$t('tag.tags') : this.$t('recipe.categories');
},
activeItems() {
let ItemObjects = [];

View file

@ -7,7 +7,7 @@
mdi-import
</v-icon>
<v-toolbar-title class="headline">
Import Summary
{{ $t("settings.backup.import-summary") }}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
@ -18,8 +18,8 @@
<div>
<h3>{{ values.title }}</h3>
</div>
<div class="success--text">Success: {{ values.success }}</div>
<div class="error--text">Failed: {{ values.failure }}</div>
<div class="success--text">{{ $t("general.success-count", { count: values.success }) }}</div>
<div class="error--text">{{ $t("general.failed-count", { count: values.failure }) }}</div>
</v-card-text>
</div>
</v-row>
@ -28,7 +28,7 @@
<v-tab>{{ $t("general.recipes") }}</v-tab>
<v-tab>{{ $t("general.themes") }}</v-tab>
<v-tab>{{ $t("general.settings") }}</v-tab>
<v-tab> Pages </v-tab>
<v-tab> {{ $t("settings.pages") }} </v-tab>
<v-tab>{{ $t("general.users") }}</v-tab>
<v-tab>{{ $t("general.groups") }}</v-tab>
</v-tabs>
@ -59,24 +59,30 @@ export default {
userData: [],
groupData: [],
pageData: [],
importHeaders: [
{
text: "Status",
value: "status",
},
{
text: "Name",
align: "start",
sortable: true,
value: "name",
},
{ text: "Exception", value: "data-table-expand", align: "center" },
],
allDataTables: [],
}),
computed: {
importHeaders() {
return [
{
text: this.$t('general.status'),
value: "status",
},
{
text: this.$t('general.name'),
align: "start",
sortable: true,
value: "name",
},
{
text: this.$t('general.exception'),
value: "data-table-expand",
align: "center"
},
]
},
recipeNumbers() {
return this.calculateNumbers(this.$t("general.recipes"), this.recipeData);
},
@ -96,7 +102,7 @@ export default {
return this.calculateNumbers(this.$t("general.groups"), this.groupData);
},
pageNumbers() {
return this.calculateNumbers("Pages", this.pageData);
return this.calculateNumbers(this.$t("settings.pages"), this.pageData);
},
allNumbers() {
return [

View file

@ -91,18 +91,12 @@ export default {
let formData = new FormData();
formData.append("username", this.user.email);
formData.append("password", this.user.password);
let key;
try {
key = await api.users.login(formData);
} catch {
const response = await api.users.login(formData);
if (!response) {
this.error = true;
}
if (key.status != 200) {
this.error = true;
this.loading = false;
} else {
this.clear();
this.$store.commit("setToken", key.data.access_token);
this.$store.commit("setToken", response.data.access_token);
this.$emit("logged-in");
}

View file

@ -13,13 +13,13 @@
class="mr-2"
>
</v-progress-circular>
<v-toolbar-title class="headline"> Sign Up </v-toolbar-title>
<v-toolbar-title class="headline">
{{$t('signup.sign-up')}}
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-card-text>
Welcome to Mealie! To become a user of this instance you are required to
have a valid invitation link. If you haven't recieved an invitation you
are unable to sign-up. To recieve a link, contact the sites administrator.
{{$t('signup.welcome-to-mealie')}}
<v-divider class="mt-3"></v-divider>
<v-form ref="signUpForm" @submit.prevent="signUp">
<v-text-field
@ -28,7 +28,7 @@
prepend-icon="mdi-account"
validate-on-blur
:rules="[existsRule]"
label="Display Name"
:label="$t('signup.display-name')"
type="email"
></v-text-field>
<v-text-field
@ -59,7 +59,7 @@
:type="showPassword ? 'text' : 'password'"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
:rules="[
user.password === user.passwordConfirm || 'Password must match',
user.password === user.passwordConfirm || $t('user.password-must-match'),
]"
@click:append="showPassword = !showPassword"
></v-text-field>
@ -71,11 +71,11 @@
block="block"
type="submit"
>
Sign Up
{{$t('signup.sign-up')}}
</v-btn>
</v-card-actions>
<v-alert dense v-if="error" outlined class="mt-3 mb-0" type="error">
Error Signing Up
{{$t('signup.error-signing-up')}}
</v-alert>
</v-form>
</v-card-text>
@ -132,18 +132,16 @@ export default {
admin: false,
};
let successUser = false;
if (this.$refs.signUpForm.validate()) {
let response = await api.signUps.createUser(this.token, userData);
successUser = response.snackbar.text.includes("Created");
if (await api.signUps.createUser(this.token, userData)) {
this.$emit("user-created");
this.$router.push("/");
}
}
this.$emit("user-created");
this.loading = false;
if (successUser) {
this.$router.push("/");
}
},
},
};

View file

@ -36,8 +36,9 @@ export default {
return utils.getDateAsPythonDate(dateObject);
},
async update() {
await api.mealPlans.update(this.mealPlan.uid, this.mealPlan);
this.$emit("updated");
if (await api.mealPlans.update(this.mealPlan.uid, this.mealPlan)) {
this.$emit("updated");
}
},
},
};

View file

@ -197,11 +197,12 @@ export default {
endDate: this.endDate,
meals: this.meals,
};
await api.mealPlans.create(mealBody);
this.$emit(CREATE_EVENT);
this.meals = [];
this.startDate = null;
this.endDate = null;
if (await api.mealPlans.create(mealBody)) {
this.$emit(CREATE_EVENT);
this.meals = [];
this.startDate = null;
this.endDate = null;
}
},
getImage(image) {

View file

@ -6,7 +6,7 @@
<v-icon left>
mdi-image
</v-icon>
{{ $t("recipe.image") }}
{{ $t("general.image") }}
</v-btn>
</template>
<v-card width="400">
@ -71,8 +71,9 @@ export default {
},
async getImageFromURL() {
this.loading = true;
const response = await api.recipes.updateImagebyURL(this.slug, this.url);
if (response) this.$emit(REFRESH_EVENT);
if (await api.recipes.updateImagebyURL(this.slug, this.url)) {
this.$emit(REFRESH_EVENT);
}
this.loading = false;
},
},

View file

@ -74,7 +74,7 @@
:show-label="false"
/>
<h2 class="mt-4">{{ $t("recipe.tags") }}</h2>
<h2 class="mt-4">{{ $t("tag.tags") }}</h2>
<CategoryTagSelector
:return-object="false"
v-model="value.tags"

View file

@ -44,7 +44,7 @@
</v-card>
<v-card class="mt-2" v-if="tags.length > 0">
<v-card-title class="py-2">
{{ $t("recipe.tags") }}
{{ $t("tag.tags") }}
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
@ -69,7 +69,7 @@
</v-row>
<div v-if="!medium">
<RecipeChips :title="$t('recipe.categories')" :items="categories" />
<RecipeChips :title="$t('recipe.tags')" :items="tags" />
<RecipeChips :title="$t('tag.tags')" :items="tags" />
<Nutrition :value="nutrition" :edit="false" />
<Assets :value="assets" :edit="false" :slug="slug" />
</div>

View file

@ -55,10 +55,10 @@ export default {
let formData = new FormData();
formData.append(this.fileName, this.file);
await api.utils.uploadFile(this.url, formData);
if(await api.utils.uploadFile(this.url, formData)) {
this.$emit(UPLOAD_EVENT);
}
this.isSelecting = false;
this.$emit(UPLOAD_EVENT);
}
},
onButtonClick() {

View file

@ -105,17 +105,15 @@ export default {
async createRecipe() {
if (this.$refs.urlForm.validate()) {
this.processing = true;
let response = await api.recipes.createByURL(this.recipeURL);
if (response.status !== 201) {
this.error = true;
this.processing = false;
return;
}
this.addRecipe = false;
const response = await api.recipes.createByURL(this.recipeURL);
this.processing = false;
this.recipeURL = "";
this.$router.push(`/recipe/${response.data}`);
if (response) {
this.addRecipe = false;
this.recipeURL = "";
this.$router.push(`/recipe/${response.data}`);
} else {
this.error = true;
}
}
},

View file

@ -44,9 +44,9 @@ export default {
components: {
LoginDialog,
},
data: function() {
return {
items: [
computed: {
items() {
return [
{
icon: "mdi-account",
title: "Login",
@ -83,10 +83,8 @@ export default {
nav: "/admin",
restricted: true,
},
],
};
},
computed: {
]
},
filteredItems() {
if (this.loggedIn) {
return this.items.filter(x => x.restricted == true);

View file

@ -13,11 +13,22 @@
"demo": "Demo",
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"version": "Version"
},
"category": {
"category-created": "Category created",
"category-creation-failed": "Category creation failed",
"category-deleted": "Category Deleted",
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
},
"general": {
"apply": "Apply",
"cancel": "Cancel",
@ -30,29 +41,40 @@
"download": "Download",
"edit": "Edit",
"enabled": "Enabled",
"exception": "Exception",
"failed-count": "Failed: {count}",
"failure-uploading-file": "Failure uploading file",
"field-required": "Field Required",
"file-folder-not-found": "File/folder not found",
"file-uploaded": "File uploaded",
"filter": "Filter",
"friday": "Friday",
"get": "Get",
"groups": "Groups",
"image": "Image",
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"monday": "Monday",
"name": "Name",
"no": "No",
"ok": "OK",
"options": "Options",
"options": "Options:",
"random": "Random",
"recent": "Recent",
"recipes": "Recipes",
"rename-object": "Rename {0}",
"reset": "Reset",
"saturday": "Saturday",
"save": "Save",
"settings": "Settings",
"sort": "Sort",
"sort-alphabetically": "A-Z",
"status": "Status",
"submit": "Submit",
"success-count": "Success: {count}",
"sunday": "Sunday",
"templates": "Templates",
"templates": "Templates:",
"themes": "Themes",
"thursday": "Thursday",
"token": "Token",
@ -64,6 +86,25 @@
"wednesday": "Wednesday",
"yes": "Yes"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
"cannot-delete-default-group": "Cannot delete default group",
"cannot-delete-group-with-users": "Cannot delete group with users",
"confirm-group-deletion": "Confirm Group Deletion",
"create-group": "Create Group",
"error-updating-group": "Error updating group",
"group": "Group",
"group-deleted": "Group deleted",
"group-deletion-failed": "Group deletion failed",
"group-id-with-value": "Group ID: {groupID}",
"group-name": "Group Name",
"group-not-found": "Group not found",
"groups": "Groups",
"groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
"user-group": "User Group",
"user-group-created": "User Group Created",
"user-group-creation-failed": "User Group Creation Failed"
},
"meal-plan": {
"create-a-new-meal-plan": "Create a New Meal Plan",
"dinner-this-week": "Dinner This Week",
@ -73,6 +114,14 @@
"group": "Group (Beta)",
"meal-planner": "Meal Planner",
"meal-plans": "Meal Plans",
"mealplan-created": "Mealplan created",
"mealplan-creation-failed": "Mealplan creation failed",
"mealplan-deleted": "Mealplan Deleted",
"mealplan-deletion-failed": "Mealplan deletion failed",
"mealplan-update-failed": "Mealplan update failed",
"mealplan-updated": "Mealplan Updated",
"no-meal-plan-defined-yet": "No meal plan defined yet",
"no-meal-planned-for-today": "No meal planned for today",
"only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans",
"planner": "Planner",
"quick-week": "Quick Week",
@ -84,6 +133,7 @@
"description": "Migrate data from Chowdown",
"title": "Chowdown"
},
"migration-data-removed": "Migration data removed",
"nextcloud": {
"description": "Migrate data from a Nextcloud Cookbook intance",
"title": "Nextcloud Cookbook"
@ -102,23 +152,30 @@
"page": {
"all-recipes": "All Recipes",
"home-page": "Home Page",
"new-page-created": "New page created",
"page-creation-failed": "Page creation failed",
"page-deleted": "Page deleted",
"page-deletion-failed": "Page deletion failed",
"page-update-failed": "Page update failed",
"page-updated": "Page updated",
"pages-update-failed": "Pages update failed",
"pages-updated": "Pages updated",
"recent": "Recent"
},
"recipe": {
"assets": "Assets",
"add-key": "Add Key",
"api-extras": "API Extras",
"assets": "Assets",
"calories": "Calories",
"calories-suffix": "calories",
"carbohydrate-content": "Carbohydrate",
"categories": "Categories",
"delete-confirmation": "Are you sure you want to delete this recipe?",
"delete-recipe": "Delete Recipe",
"description": "Description",
"fat-content": "Fat",
"fiber-content": "Fiber",
"carbohydrate-content": "Carbohydrate",
"grams": "grams",
"image": "Image",
"ingredient": "Ingredient",
"ingredients": "Ingredients",
"instructions": "Instructions",
@ -135,27 +192,32 @@
"perform-time": "Cook Time",
"prep-time": "Prep Time",
"protein-content": "Protein",
"recipe-created": "Recipe created",
"recipe-creation-failed": "Recipe creation failed",
"recipe-deleted": "Recipe deleted",
"recipe-image": "Recipe Image",
"recipe-image-updated": "Recipe image updated",
"recipe-name": "Recipe Name",
"recipe-update-failed": "Recipe update failed",
"recipe-updated": "Recipe updated",
"servings": "Servings",
"sodium-content": "Sodium",
"step-index": "Step: {step}",
"sugar-content": "Sugar",
"tags": "Tags",
"title": "Title",
"total-time": "Total Time",
"unable-to-delete-recipe": "Unable to Delete Recipe",
"view-recipe": "View Recipe"
},
"search": {
"search-mealie": "Search Mealie (press /)",
"search-placeholder": "Search...",
"max-results": "Max Results",
"category-filter": "Category Filter",
"and": "and",
"exclude": "Exclude",
"include": "Include",
"max-results": "Max Results",
"or": "Or",
"and": "and",
"search": "Search",
"search-mealie": "Search Mealie (press /)",
"search-placeholder": "Search...",
"tag-filter": "Tag Filter"
},
"settings": {
@ -163,10 +225,15 @@
"admin-settings": "Admin Settings",
"available-backups": "Available Backups",
"backup": {
"backup-created-at-response-export_path": "Backup Created at {path}",
"backup-deleted": "Backup deleted",
"backup-tag": "Backup Tag",
"create-heading": "Create a Backup",
"error-creating-backup-see-log-file": "Error Creating Backup. See Log File",
"full-backup": "Full Backup",
"partial-backup": "Partial Backup"
"import-summary": "Import Summary",
"partial-backup": "Partial Backup",
"unable-to-delete-backup": "Unable to Delete Backup."
},
"backup-and-exports": "Backups",
"backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.",
@ -175,6 +242,7 @@
"custom-pages": "Custom Pages",
"edit-page": "Edit Page",
"first-day-of-week": "First day of the week",
"group-settings-updated": "Group Settings Updated",
"homepage": {
"all-categories": "All Categories",
"card-per-section": "Card Per Section",
@ -190,9 +258,12 @@
"migrations": "Migrations",
"new-page": "New Page",
"page-name": "Page Name",
"pages": "Pages",
"profile": "Profile",
"remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries",
"set-new-time": "Set New Time",
"settings-update-failed": "Settings update failed",
"settings-updated": "Settings updated",
"site-settings": "Site Settings",
"theme": {
"accent": "Accent",
@ -203,6 +274,9 @@
"default-to-system": "Default to system",
"delete-theme": "Delete Theme",
"error": "Error",
"error-creating-theme-see-log-file": "Error creating theme. See log file.",
"error-deleting-theme": "Error deleting theme",
"error-updating-theme": "Error updating theme",
"info": "Info",
"light": "Light",
"primary": "Primary",
@ -210,51 +284,69 @@
"select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.",
"success": "Success",
"theme": "Theme",
"theme-deleted": "Theme deleted",
"theme-name": "Theme Name",
"theme-name-is-required": "Theme Name is required.",
"theme-saved": "Theme Saved",
"theme-settings": "Theme Settings",
"theme-updated": "Theme updated",
"warning": "Warning"
},
"toolbox": {
"assign-all": "Assign All",
"bulk-assign": "Bulk Assign",
"new-name": "New Name",
"no-unused-items": "No Unused Items",
"recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected",
"remove-unused": "Remove Unused",
"title-case-all": "Title Case All",
"toolbox": "Toolbox"
},
"webhooks": {
"meal-planner-webhooks": "Meal Planner Webhooks",
"test-webhooks": "Test Webhooks",
"the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at",
"webhook-url": "Webhook URL"
},
"toolbox": {
"toolbox": "Toolbox",
"new-name": "New Name",
"recipes-effected": "Recipes Effected",
"title-case-all": "Title Case All",
"no-unused-items": "No Unused Items",
"remove-unused": "Remove Unused",
"assign-all": "Assign All",
"bulk-assign": "Bulk Assign"
}
},
"signup": {
"display-name": "Display Name",
"error-signing-up": "Error Signing Up",
"sign-up": "Sign Up",
"sign-up-link-created": "Sign up link created",
"sign-up-link-creation-failed": "Sign up link creation failed",
"sign-up-links": "Sign Up Links",
"sign-up-token-deleted": "Sign Up Token Deleted",
"sign-up-token-deletion-failed": "Sign up token deletion failed",
"welcome-to-mealie": "Welcome to Mealie! To become a user of this instance you are required to have a valid invitation link. If you haven't recieved an invitation you are unable to sign-up. To recieve a link, contact the sites administrator."
},
"tag": {
"tag-created": "Tag created",
"tag-creation-failed": "Tag creation failed",
"tag-deleted": "Tag deleted",
"tag-deletion-failed": "Tag deletion failed",
"tag-update-failed": "Tag update failed",
"tag-updated": "Tag updated",
"tags": "Tags"
},
"user": {
"admin": "Admin",
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
"are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link <b>{link}<b/>?",
"are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user <b>{activeName} ID: {activeId}<b/>?",
"confirm-group-deletion": "Confirm Group Deletion",
"confirm-link-deletion": "Confirm Link Deletion",
"confirm-password": "Confirm Password",
"confirm-user-deletion": "Confirm User Deletion",
"could-not-validate-credentials": "Could Not Validate Credentials",
"create-group": "Create Group",
"create-link": "Create Link",
"create-user": "Create User",
"current-password": "Current Password",
"e-mail-must-be-valid": "E-mail must be valid",
"edit-user": "Edit User",
"email": "Email",
"error-cannot-delete-super-user": "Error! Cannot Delete Super User",
"existing-password-does-not-match": "Existing password does not match",
"full-name": "Full Name",
"group": "Group",
"group-id-with-value": "Group ID: {groupID}",
"group-name": "Group Name",
"groups": "Groups",
"groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
"incorrect-username-or-password": "Incorrect username or password",
"link-id": "Link ID",
"link-name": "Link Name",
"login": "Login",
@ -262,20 +354,29 @@
"new-password": "New Password",
"new-user": "New User",
"password": "Password",
"password-has-been-reset-to-the-default-password": "Password has been reset to the default password",
"password-must-match": "Password must match",
"password-reset-failed": "Password reset failed",
"password-updated": "Password updated",
"reset-password": "Reset Password",
"sign-in": "Sign in",
"sign-up-links": "Sign Up Links",
"total-mealplans": "Total MealPlans",
"total-users": "Total Users",
"upload-photo": "Upload Photo",
"use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password",
"user-group": "User Group",
"user-created": "User created",
"user-creation-failed": "User creation failed",
"user-deleted": "User deleted",
"user-id": "User ID",
"user-id-with-value": "User ID: {id}",
"user-password": "User Password",
"user-successfully-logged-in": "User Successfully Logged In",
"user-update-failed": "User update failed",
"user-updated": "User updated",
"users": "Users",
"webhook-time": "Webhook Time",
"webhooks-enabled": "Webhooks Enabled"
"webhooks-enabled": "Webhooks Enabled",
"you-are-not-allowed-to-create-a-user": "You are not allowed to create a user",
"you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user"
}
}

View file

@ -23,11 +23,11 @@
<v-card-actions>
<v-spacer></v-spacer>
<TheDownloadBtn
button-text="Download Recipe JSON"
:button-text="$t('about.download-recipe-json')"
download-url="/api/debug/last-recipe-json"
/>
<TheDownloadBtn
button-text="Download Log"
:button-text="$t('about.download-log')"
download-url="/api/debug/log"
/>
</v-card-actions>

View file

@ -62,17 +62,21 @@ export default {
},
async importBackup(data) {
this.$emit("loading");
let response = await api.backups.import(data.name, data);
const response = await api.backups.import(data.name, data);
if(response) {
let importData = response.data;
this.$emit("finished", importData);
} else {
this.$emit("finished");
}
let importData = response.data;
this.$emit("finished", importData);
},
deleteBackup(data) {
async deleteBackup(data) {
this.$emit("loading");
api.backups.delete(data.name);
this.selectedBackup = null;
if (await api.backups.delete(data.name)) {
this.selectedBackup = null;
}
this.backupLoading = false;
this.$emit("finished");

View file

@ -28,7 +28,7 @@ export default {
},
pages: {
value: true,
text: "Pages",
text: this.$t("settings.pages"),
},
themes: {
value: true,

View file

@ -20,11 +20,11 @@
<v-card-text class="mt-n4">
<v-row>
<v-col sm="4">
<p>{{ $t("general.options") }}:</p>
<p>{{ $t("general.options") }}</p>
<ImportOptions @update-options="updateOptions" class="mt-5" />
</v-col>
<v-col>
<p>{{ $t("general.templates") }}:</p>
<p>{{ $t("general.templates") }}</p>
<v-checkbox
v-for="template in availableTemplates"
:key="template"
@ -97,10 +97,11 @@ export default {
templates: this.selectedTemplates,
};
await api.backups.create(data);
if (await api.backups.create(data)) {
this.$emit("created");
}
this.loading = false;
this.$emit("created");
},
appendTemplate(templateName) {
if (this.selectedTemplates.includes(templateName)) {

View file

@ -2,9 +2,9 @@
<div>
<ConfirmationDialog
ref="deleteGroupConfirm"
:title="$t('user.confirm-group-deletion')"
:title="$t('group.confirm-group-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-group', {
$t('group.are-you-sure-you-want-to-delete-the-group', {
groupName: group.name,
})
"
@ -18,7 +18,7 @@
<v-card-title class="py-1">{{ group.name }}</v-card-title>
<v-divider></v-divider>
<v-subheader>{{
$t("user.group-id-with-value", { groupID: group.id })
$t("group.group-id-with-value", { groupID: group.id })
}}</v-subheader>
<v-list-item-group color="primary">
<v-list-item v-for="property in groupProps" :key="property.text">
@ -91,8 +91,9 @@ export default {
this.$refs.deleteGroupConfirm.open();
},
async deleteGroup() {
await api.groups.delete(this.group.id);
this.$emit(RENDER_EVENT);
if (await api.groups.delete(this.group.id)) {
this.$emit(RENDER_EVENT);
}
},
closeGroupDelete() {
console.log("Close Delete");

View file

@ -24,7 +24,7 @@
v-bind="attrs"
v-on="on"
>
{{ $t("user.create-group") }}
{{ $t("group.create-group") }}
</v-btn>
</template>
<v-card>
@ -34,7 +34,7 @@
</v-icon>
<v-toolbar-title class="headline">
{{ $t("user.create-group") }}
{{ $t("group.create-group") }}
</v-toolbar-title>
<v-spacer></v-spacer>
@ -43,7 +43,7 @@
<v-card-text>
<v-text-field
v-model="newGroupName"
:label="$t('user.group-name')"
:label="$t('group.group-name')"
:rules="[existsRule]"
></v-text-field>
</v-card-text>
@ -104,12 +104,11 @@ export default {
methods: {
async createGroup() {
this.groupLoading = true;
let response = await api.groups.create(this.newGroupName);
if (response.created) {
this.groupLoading = false;
if (await api.groups.create(this.newGroupName)) {
this.groupDialog = false;
this.$store.dispatch("requestAllGroups");
}
this.groupLoading = false;
},
},
};

View file

@ -1,7 +1,7 @@
<template>
<v-card outlined class="mt-n1">
<ConfirmationDialog
ref="deleteUserDialog"
ref="deleteTokenDialog"
:title="$t('user.confirm-link-deletion')"
:message="
$t('user.are-you-sure-you-want-to-delete-the-link', {
@ -9,7 +9,7 @@
})
"
icon="mdi-alert"
@confirm="deleteUser"
@confirm="deleteToken"
:width="450"
@close="closeDelete"
/>
@ -18,7 +18,7 @@
mdi-link-variant
</v-icon>
<v-toolbar-title class="headine">
{{ $t("user.sign-up-links") }}
{{ $t("signup.sign-up-links") }}
</v-toolbar-title>
<v-spacer> </v-spacer>
@ -181,9 +181,10 @@ export default {
this.links = await api.signUps.getAll();
},
async deleteUser() {
await api.signUps.deleteToken(this.activeId);
this.initialize();
async deleteToken() {
if (await api.signUps.deleteToken(this.activeId)) {
this.initialize();
}
},
editItem(item) {
@ -197,7 +198,7 @@ export default {
this.activeName = item.name;
this.editedIndex = this.links.indexOf(item);
this.editedItem = Object.assign({}, item);
this.$refs.deleteUserDialog.open();
this.$refs.deleteTokenDialog.open();
},
deleteItemConfirm() {

View file

@ -72,7 +72,7 @@
dense
v-model="editedItem.group"
:items="existingGroups"
:label="$t('user.user-group')"
:label="$t('group.user-group')"
></v-select>
</v-col>
<v-col cols="12" sm="12" md="6" v-if="showPassword">
@ -94,7 +94,7 @@
<v-card-actions>
<v-btn color="info" text @click="resetPassword">
Reset Password
{{$t('user.reset-password')}}
</v-btn>
<v-spacer></v-spacer>
<v-btn color="grey" text @click="close">
@ -165,7 +165,7 @@ export default {
},
{ text: this.$t("user.full-name"), value: "fullName" },
{ text: this.$t("user.email"), value: "email" },
{ text: this.$t("user.group"), value: "group" },
{ text: this.$t("group.group"), value: "group" },
{ text: this.$t("user.admin"), value: "admin" },
{ text: "", value: "actions", sortable: false, align: "center" },
],
@ -223,8 +223,9 @@ export default {
},
async deleteUser() {
await api.users.delete(this.activeId);
this.initialize();
if (await api.users.delete(this.activeId)) {
this.initialize();
}
},
editItem(item) {
@ -264,17 +265,27 @@ export default {
async save() {
if (this.editedIndex > -1) {
await api.users.update(this.editedItem);
this.close();
this.updateUser();
} else if (this.$refs.newUser.validate()) {
await api.users.create(this.editedItem);
this.close();
this.createUser();
}
await this.initialize();
},
resetPassword() {
api.users.resetPassword(this.editedItem.id);
},
async createUser() {
if(await api.users.create(this.editedItem)) {
this.close();
}
},
async updateUser() {
if(await api.users.update(this.editedItem)) {
this.close();
}
}
},
};
</script>

View file

@ -16,12 +16,12 @@
</v-tab>
<v-tab>
{{ $t("user.sign-up-links") }}
{{ $t("signup.sign-up-links") }}
<v-icon>mdi-account-plus-outline</v-icon>
</v-tab>
<v-tab>
{{ $t("user.groups") }}
{{ $t("group.groups") }}
<v-icon>mdi-account-group</v-icon>
</v-tab>
</v-tabs>

View file

@ -135,9 +135,10 @@ export default {
this.groupSettings.webhookUrls.splice(index, 1);
},
async saveGroupSettings() {
await api.groups.update(this.groupSettings);
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
if (await api.groups.update(this.groupSettings)) {
await this.$store.dispatch("requestCurrentGroup");
this.getSiteSettings();
}
},
testWebhooks() {
api.settings.testWebhooks();

View file

@ -86,9 +86,10 @@ export default {
};
},
methods: {
deleteMigration(file_name) {
api.migrations.delete(this.folder, file_name);
this.$emit("refresh");
async deleteMigration(file_name) {
if (await api.migrations.delete(this.folder, file_name)) {
this.$emit("refresh");
}
},
async importMigration(file_name) {
this.loading = true;

View file

@ -55,11 +55,11 @@
>
</v-text-field>
<v-text-field
:label="$t('user.group')"
:label="$t('group.group')"
readonly
v-model="user.group"
persistent-hint
:hint="$t('user.groups-can-only-be-set-by-administrators')"
:hint="$t('group.groups-can-only-be-set-by-administrators')"
>
</v-text-field>
</v-form>
@ -201,11 +201,13 @@ export default {
},
async updateUser() {
this.loading = true;
let newKey = await api.users.update(this.user);
this.$store.commit("setToken", newKey.access_token);
this.refreshProfile();
this.loading = false;
this.$store.dispatch("requestUserData");
const response = await api.users.update(this.user);
if(response) {
this.$store.commit("setToken", response.data.access_token);
this.refreshProfile();
this.loading = false;
this.$store.dispatch("requestUserData");
}
},
async changePassword() {
this.paswordLoading = true;
@ -215,7 +217,9 @@ export default {
};
if (this.$refs.passChange.validate()) {
await api.users.changePassword(this.user.id, data);
if (await api.users.changePassword(this.user.id, data)) {
this.$emit("refresh");
}
}
this.paswordLoading = false;
},

View file

@ -82,14 +82,18 @@ export default {
this.$refs.categoryFormSelector.setInit(this.page.categories);
},
async submitForm() {
let response;
if (this.create) {
await api.siteSettings.createPage(this.page);
response = await api.siteSettings.createPage(this.page);
} else {
await api.siteSettings.updatePage(this.page);
response = await api.siteSettings.updatePage(this.page);
}
if (response) {
this.pageDialog = false;
this.page.categories = [];
this.$emit(NEW_PAGE_EVENT);
}
this.pageDialog = false;
this.page.categories = [];
this.$emit(NEW_PAGE_EVENT);
},
},
};

View file

@ -109,9 +109,10 @@ export default {
element.position = index;
});
await api.siteSettings.updateAllPages(this.customPages);
if (await api.siteSettings.updateAllPages(this.customPages)) {
this.getPages();
}
this.getPages();
},
editPage(index) {
this.editPageData.data = this.customPages[index];

View file

@ -214,7 +214,7 @@ export default {
this.settings.language = val;
},
deleteCategoryfromDatabase(category) {
api.categories.delete(category);
api.categories.delete(category);
},
async getOptions() {
this.settings = await api.siteSettings.get();
@ -223,8 +223,9 @@ export default {
this.settings.categories.splice(index, 1);
},
async saveSettings() {
await api.siteSettings.update(this.settings);
this.getOptions();
if (await api.siteSettings.update(this.settings)) {
this.getOptions();
}
},
},
};

View file

@ -70,11 +70,11 @@ export default {
},
async deleteSelectedTheme() {
//Delete Theme from DB
await api.themes.delete(this.theme.name);
//Get the new list of available from DB
this.availableThemes = await api.themes.requestAll();
this.$emit(DELETE_EVENT);
if (await api.themes.delete(this.theme.name)) {
//Get the new list of available from DB
this.availableThemes = await api.themes.requestAll();
this.$emit(DELETE_EVENT);
}
},
async saveThemes() {
this.$store.commit("setTheme", this.theme);

View file

@ -171,9 +171,11 @@ export default {
* Create the new Theme and select it.
*/
async appendTheme(NewThemeDialog) {
await api.themes.create(NewThemeDialog);
this.availableThemes.push(NewThemeDialog);
this.$store.commit("setTheme", NewThemeDialog);
const response = await api.themes.create(NewThemeDialog);
if (response) {
this.availableThemes.push(NewThemeDialog);
this.$store.commit("setTheme", NewThemeDialog);
}
},
setStoresDarkMode() {
this.$store.commit("setDarkMode", this.selectedDarkMode);
@ -181,8 +183,8 @@ export default {
/**
* This will save the current colors and make the selected theme live.
*/
async saveThemes() {
await api.themes.update(
saveThemes() {
api.themes.update(
this.selectedTheme.name,
this.selectedTheme.colors
);

View file

@ -4,7 +4,7 @@
ref="assignDialog"
title-icon="mdi-tag"
color="primary"
title="Bulk Assign"
:title="$t('settings.toolbox.bulk-assign')"
:loading="loading"
modal-width="700"
:top="true"
@ -13,7 +13,7 @@
<v-text-field
v-model="search"
autocomplete="off"
label="Keyword"
:label="$t('general.keyword')"
></v-text-field>
<CategoryTagSelector
:tag-selector="false"
@ -44,7 +44,7 @@
<v-card-title class="headline"> </v-card-title>
<CardSection
class="px-2 pb-2"
:title="`${results.length || 0} Recipes Effected`"
:title="$tc('settings.toolbox.recipes-affected', results.length || 0)"
:mobile-cards="true"
:recipes="results"
:single-column="true"

View file

@ -7,7 +7,7 @@
:title="
$t('general.delete') +
' ' +
(isTags ? $t('recipe.tags') : $t('recipe.categories'))
(isTags ? $t('tag.tags') : $t('recipe.categories'))
"
:loading="loading"
modal-width="400"

View file

@ -18,8 +18,7 @@
</v-form>
<template slot="below-actions">
<v-card-title class="headline">
{{ renameTarget.recipes.length || 0 }}
{{ $t("settings.toolbox.recipes-effected") }}
{{ $tc("settings.toolbox.recipes-affected", renameTarget.recipes.length || 0) }}
</v-card-title>
<MobileRecipeCard
class="ml-2 mr-2 mt-2 mb-2"
@ -94,10 +93,10 @@
<v-card-title class="py-1">{{ item.name }}</v-card-title>
<v-spacer></v-spacer>
<v-btn small text color="info" @click="openEditDialog(item)">
Edit
{{$t('general.edit')}}
</v-btn>
<v-btn small text color="error" @click="deleteItem(item.slug)"
>Delete
<v-btn small text color="error" @click="deleteItem(item.slug)">
{{$t('general.delete')}}
</v-btn>
</v-card-actions>
</v-card>
@ -177,7 +176,7 @@ export default {
}
this.renameTarget = {
title: `Rename ${item.name}`,
title:this.$t('general.rename-object', [item.name]),
name: item.name,
slug: item.slug,
newName: "",

View file

@ -16,7 +16,7 @@
</v-tab>
<v-tab>
{{ $t("recipe.tags") }}
{{ $t("tag.tags") }}
<v-icon>mdi-tag-multiple-outline</v-icon>
</v-tab>
</v-tabs>

View file

@ -128,8 +128,9 @@ export default {
this.requestMeals();
},
async deletePlan(id) {
await api.mealPlans.delete(id);
this.requestMeals();
if (await api.mealPlans.delete(id)) {
this.requestMeals();
}
},
openShoppingList(id) {
this.$refs.shoppingList.openDialog(id);

View file

@ -52,6 +52,7 @@
<script>
import { api } from "@/api";
import utils from "@/utils";
export default {
data() {
return {
@ -60,6 +61,9 @@ export default {
},
async mounted() {
this.mealPlan = await api.mealPlans.thisWeek();
if(!this.mealPlan) {
utils.notify.warning(this.$t('meal-plan.no-meal-plan-defined-yet'))
}
},
methods: {
getOrder(index) {

View file

@ -101,7 +101,7 @@ export default {
let slug = await api.recipes.create(this.recipeDetails);
if (this.fileObject) {
await api.recipes.updateImage(slug, this.fileObject);
api.recipes.updateImage(slug, this.fileObject, true);
}
this.isLoading = false;

View file

@ -80,6 +80,8 @@ import RecipeEditor from "@/components/Recipe/RecipeEditor";
import RecipeTimeCard from "@/components/Recipe/RecipeTimeCard.vue";
import EditorButtonRow from "@/components/Recipe/EditorButtonRow";
import { user } from "@/mixins/user";
import store from "@/store";
import { router } from "@/routes";
export default {
components: {
@ -166,8 +168,12 @@ export default {
return api.recipes.recipeImage(image) + "&rnd=" + this.imageKey;
}
},
deleteRecipe() {
api.recipes.delete(this.recipeDetails.slug);
async deleteRecipe() {
let response = await api.recipes.delete(this.recipeDetails.slug);
if (response) {
store.dispatch("requestRecentRecipes");
router.push(`/`);
}
},
validateRecipe() {
if (this.jsonEditor) {
@ -176,18 +182,19 @@ export default {
return this.$refs.recipeEditor.validateRecipe();
}
},
async saveImage() {
async saveImage(overrideSuccessMsg = false) {
if (this.fileObject) {
await api.recipes.updateImage(this.recipeDetails.slug, this.fileObject);
if (api.recipes.updateImage(this.recipeDetails.slug, this.fileObject, overrideSuccessMsg)) {
this.imageKey += 1;
}
}
this.imageKey += 1;
},
async saveRecipe() {
if (this.validateRecipe()) {
let slug = await api.recipes.update(this.recipeDetails);
if (this.fileObject) {
this.saveImage();
this.saveImage(true);
}
this.form = false;

View file

@ -26,7 +26,7 @@
<v-row dense class="mt-0 flex-row align-center justify-space-around">
<v-col>
<h3 class="pl-2 text-center headline">
{{ $t("search.category-filter") }}
{{ $t("category.category-filter") }}
</h3>
<FilterSelector class="mb-1" @update="updateCatParams" />
<CategoryTagSelector

View file

@ -2,6 +2,9 @@ import Planner from "@/pages/MealPlan/Planner";
import ThisWeek from "@/pages/MealPlan/ThisWeek";
import { api } from "@/api";
import i18n from '@/i18n.js';
import utils from "@/utils";
export const mealRoutes = [
{
path: "/meal-plan/planner",
@ -21,7 +24,12 @@ export const mealRoutes = [
path: "/meal-plan/today",
beforeEnter: async (_to, _from, next) => {
await todaysMealRoute().then(redirect => {
next(redirect);
if(redirect) {
next(redirect);
} else {
utils.notify.error(i18n.t('meal-plan.no-meal-planned-for-today'));
next(_from);
}
});
},
},
@ -29,5 +37,9 @@ export const mealRoutes = [
async function todaysMealRoute() {
const response = await api.mealPlans.today();
return "/recipe/" + response.data;
if (response.status == 200 && response.data) {
return "/recipe/" + response.data;
} else {
return null;
}
}