diff --git a/.gitignore b/.gitignore index 88de70f24..338786e21 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ app_data/backups/* app_data/debug/* app_data/img/* app_data/migration/* +app_data/users/* #Exception to keep folders !mealie/dist/.gitkeep diff --git a/.vscode/settings.json b/.vscode/settings.json index b2cd9fb84..aba2561a7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "python.formatting.provider": "black", - "python.pythonPath": ".venv/bin/python3.9", + "python.pythonPath": "./.venv/bin/python3.9", "python.linting.pylintEnabled": true, "python.linting.enabled": true, "python.autoComplete.extraPaths": ["mealie", "mealie/mealie"], diff --git a/docs/docs/getting-started/site-settings.md b/docs/docs/getting-started/site-settings.md index 51fe5a688..d3d43ae79 100644 --- a/docs/docs/getting-started/site-settings.md +++ b/docs/docs/getting-started/site-settings.md @@ -1,6 +1,6 @@ # Site Settings Panel !!! danger - As this is still a **BETA** It is recommended that you backup your data often and store in more than one place. Ad-hear to backup best practices with the [3-2-1 Backup Rule](https://en.wikipedia.org/wiki/Backup) + As this is still a **BETA** It is recommended that you backup your data often and store in more than one place. Ad-hear to backup best practices with the [3-2-1 Backup Rule](https://en.wikipedia.org/wiki/Backup). Prior to upgrading you **should** perform a backup to limit any data loss. ## General Settings In your site settings page you can select several options to change the layout of your homepage. You can choose to display the recent recipes, how many cards to show for each section, and which category sections to display. You can additionally select which language to use by default. Note the currently homepage settings are saved in your browser. In the future a database entry will be made for site settings so the homepage is consistent across users. @@ -12,7 +12,7 @@ Color themes can be created and set from the UI in the settings page. You can se ![](../gifs/theme-demo-v2.gif) -!!! note +!!! tip Theme data is stored in localstorage in the browser. Calling "Save colors and apply theme will refresh the local storage with the selected theme as well save the theme to the database. diff --git a/docs/docs/getting-started/users.md b/docs/docs/getting-started/users.md new file mode 100644 index 000000000..cea439abd --- /dev/null +++ b/docs/docs/getting-started/users.md @@ -0,0 +1,32 @@ +# User Managemenet + +## Overview +The basic relationship and ownership of recipes and meal plans is based on a user and group model where users are owners of recipes and groups are owners of meal plans. By default all users will be added to the default group. If a recipe is added through a migration or through a backup where no user exists ownership will be set to the default Admin, the original user provided by Mealie. To fully understand how to structure your users and groups, you'll need to know how each role is used in the website. + +!!! info ":fontawesome-solid-user-cog: Admins" + Mealie admins are super users that have access to all user data (excluding passwords). All admins can perform administrative tasks like adding users, resetting user passwords, backing up the database, migrating data, and managing site settings. Administrators can also access restricted recipes that are marked hidden or with editing disabled by the owner. + +!!! info ":fontawesome-solid-users: User Groups" + User groups, or "family" groups are a collection of users that are associated together. Users belonging to groups will have access to their associated meal plans and associated pages. This is currently the only feature of groups. + +!!! info ":fontawesome-solid-user: User" + A single user created by an Admin that has basic privileges to edit their profile, create and edit recipes they own. Edit recipes that are not hidden and are marked editable. + +## Startup +On the first startup you'll need to login to Mealie using the default username and password `changeme@email.com` and `MyPassword` or the default set through the env variable. On first login you'll be required to reset your password. After resetting your password you should also change your email address as appropriate. This will be used for logins on all future requests. + +!!! tip + Your default password environmental variable will be the default password for all new users that are created. This is stored in plain text and should not be used **any where** else. + + +## Creating and Editing Users +// TODO + +## Creating Groups +// TODO + +## Password Reset +// TODO + +## Examples Use Cases +// TODO \ No newline at end of file diff --git a/docs/docs/img/app_diagram.drawio.svg b/docs/docs/img/app_diagram.drawio.svg index d8fa0877a..30e458ba5 100644 --- a/docs/docs/img/app_diagram.drawio.svg +++ b/docs/docs/img/app_diagram.drawio.svg @@ -1,135 +1,230 @@ - + - - - - - + + + -
-
+
+
- Models +

+ User Groups +

+

+ User groups, or "family" groups are a collection of users that are associated together. Users belonging to groups will have access to their associated mealplans and associated pages. This is currently the only feature of groups. +

- - Models + + User Groups... - + + + -
-
+
+
- Database +

+ Admins +

+

+ Mealie admins are super users that have access to all user data (excluding passwords). Perform administrative tasks like adding users, resetting user passwords, backing up the database, migrating data, and managing site settings. Administrators can also access restricted recipes that are marked hidden or uneditable by the user. +

- - Database + + Admins... - - - - - - - - + + + -
-
+
+
- Python Dictionary +

+ Users +

+

+ A single user created by an Admin that has basic privlages to edit their profile, create and edit recipes they own. Edit recipes that are not hidden and are marked editable. +

- - Python Dictio... + + Users... - - - - - + + + -
-
+
+
- Pydantic Model +

+ Database Relationships +

+

+ The basic relationship and ownership is diagramed below. In short users are owners of recipes and groups are the owners of meal-plans. By default all users will be added to the "default" group. If a recipe is added through a migration or through a backup where no user exists ownership will be set to the default Admin. +

- - Pydantic Mo... + + Database Relationships... - - - - - - + + + + + + Recipe + + + + + - owners: list[Users] + + + - editable: boolean + + + - hidden: boolean + + + + + + + + User + + + + + - admin: boolean + + + - group: list[Group] + + + - recipes: list[Recipe] + + + + + + + + MealPlan + + + + + - group: Group + + + + + + + + Group + + + + + - users: list[Users] + + + - mealplans list[MealPlan] + + -
+
-
- Services +
+ User.group is backfilled by a Groups object
- - Services + + User.group is bac... - + + + + + + + + + -
+
-
- API Endpoints +
+ User.recipes is backfilled by Recipe objects
- - API Endpoin... + + User.recipes is b... + + + + + + +
+
+
+ Group.mealplan is backfilled by MealPlan objects +
+
+
+
+ + Group.mealplan is...
- - - - + Viewer does not support full SVG 1.1 diff --git a/docs/docs/img/app_diagram.png b/docs/docs/img/app_diagram.png new file mode 100644 index 000000000..7f3356257 Binary files /dev/null and b/docs/docs/img/app_diagram.png differ diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 41b57ec76..bfbca9612 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -29,6 +29,7 @@ nav: - Getting Started: - Installation: "getting-started/install.md" - Working With Recipes: "getting-started/recipes.md" + - User Management: "getting-started/users.md" - Planning Meals: "getting-started/meal-planner.md" - Site Settings: "getting-started/site-settings.md" - Backups and Exports: "getting-started/backups-and-exports.md" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9a665fe1a..88566a2ed 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,16 +23,6 @@ "markdown-it-sup": "^1.0.0", "markdown-it-task-lists": "^2.1.1", "markdown-it-toc-and-anchor": "^4.2.0" - }, - "dependencies": { - "markdown-it-katex": { - "version": "npm:@iktakahiro/markdown-it-katex@4.0.1", - "resolved": "https://registry.npmjs.org/@iktakahiro/markdown-it-katex/-/markdown-it-katex-4.0.1.tgz", - "integrity": "sha512-kGFooO7fIOgY34PSG8ZNVsUlKhhNoqhzW2kq94TNGa8COzh73PO4KsEoPOsQVG1mEAe8tg7GqG0FoVao0aMHaw==", - "requires": { - "katex": "^0.12.0" - } - } } }, "@babel/code-frame": { @@ -2016,6 +2006,16 @@ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "cacache": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", @@ -2042,6 +2042,53 @@ "unique-filename": "^1.1.1" } }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2058,6 +2105,16 @@ "minipass": "^3.1.1" } }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + }, "terser-webpack-plugin": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", @@ -2074,6 +2131,18 @@ "terser": "^4.6.12", "webpack-sources": "^1.4.3" } + }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.1.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", + "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + } } } }, @@ -7551,6 +7620,14 @@ "resolved": "https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz", "integrity": "sha512-32SSfZqSzqyAmmQ4SHvhxbFqSzPDqsZgMHDwxqPzp+v+t8RsmqsBZRG+RfRQskJko9PfKC2/oxyOs4Yg/CfiRw==" }, + "markdown-it-katex": { + "version": "npm:@iktakahiro/markdown-it-katex@4.0.1", + "resolved": "https://registry.npmjs.org/@iktakahiro/markdown-it-katex/-/markdown-it-katex-4.0.1.tgz", + "integrity": "sha512-kGFooO7fIOgY34PSG8ZNVsUlKhhNoqhzW2kq94TNGa8COzh73PO4KsEoPOsQVG1mEAe8tg7GqG0FoVao0aMHaw==", + "requires": { + "katex": "^0.12.0" + } + }, "markdown-it-mark": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz", @@ -11876,87 +11953,6 @@ } } }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.1.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.1.2.tgz", - "integrity": "sha512-8QTxh+Fd+HB6fiL52iEVLKqE9N1JSlMXLR92Ijm6g8PZrwIxckgpqjPDWRP5TWxdiPaHR+alUWsnu1ShQOwt+Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "optional": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "vue-router": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 78e21ae02..3c6f48bcc 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -31,10 +31,10 @@ - - - - + + + + @@ -46,6 +46,7 @@ import SearchBar from "@/components/UI/Search/SearchBar"; import AddRecipeFab from "@/components/UI/AddRecipeFab"; import LanguageMenu from "@/components/UI/LanguageMenu"; import Vuetify from "./plugins/vuetify"; +import { user } from "@/mixins/user"; export default { name: "App", @@ -57,6 +58,8 @@ export default { LanguageMenu, }, + mixins: [user], + watch: { $route() { this.search = false; diff --git a/frontend/src/api/api-utils.js b/frontend/src/api/api-utils.js index 6fcba6b88..d3cb95228 100644 --- a/frontend/src/api/api-utils.js +++ b/frontend/src/api/api-utils.js @@ -1,7 +1,7 @@ const baseURL = "/api/"; import axios from "axios"; import utils from "@/utils"; -import { store } from "../store/store"; +import { store } from "../store"; axios.defaults.headers.common[ "Authorization" diff --git a/frontend/src/api/backup.js b/frontend/src/api/backup.js index dd9d80b57..2b00d0d03 100644 --- a/frontend/src/api/backup.js +++ b/frontend/src/api/backup.js @@ -1,6 +1,6 @@ import { baseURL } from "./api-utils"; import { apiReq } from "./api-utils"; -import { store } from "../store/store"; +import { store } from "../store"; const backupBase = baseURL + "backups/"; diff --git a/frontend/src/api/category.js b/frontend/src/api/category.js index 20b72d458..22054058b 100644 --- a/frontend/src/api/category.js +++ b/frontend/src/api/category.js @@ -5,8 +5,8 @@ const prefix = baseURL + "categories"; const categoryURLs = { get_all: `${prefix}`, - get_category: (category) => `${prefix}/${category}`, - delete_category: (category) => `${prefix}/${category}`, + get_category: category => `${prefix}/${category}`, + delete_category: category => `${prefix}/${category}`, }; export default { diff --git a/frontend/src/api/migration.js b/frontend/src/api/migration.js index 5a4b40dc4..fc6310253 100644 --- a/frontend/src/api/migration.js +++ b/frontend/src/api/migration.js @@ -1,6 +1,6 @@ import { baseURL } from "./api-utils"; import { apiReq } from "./api-utils"; -import { store } from "../store/store"; +import { store } from "../store"; const migrationBase = baseURL + "migrations"; diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index dc2163ead..4a29762b4 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -1,6 +1,6 @@ import { baseURL } from "./api-utils"; import { apiReq } from "./api-utils"; -import { store } from "../store/store"; +import { store } from "../store"; import { router } from "../main"; import qs from "qs"; diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index aa3d8a1e1..90123e733 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -11,6 +11,7 @@ const usersURLs = { users: `${userPrefix}`, self: `${userPrefix}/self`, userID: id => `${userPrefix}/${id}`, + password: id => `${userPrefix}/${id}/password`, }; export default { @@ -42,6 +43,10 @@ export default { let response = await apiReq.put(usersURLs.userID(user.id), user); return response.data; }, + async changePassword(id, password) { + let response = await apiReq.put(usersURLs.password(id), password); + return response.data; + }, async delete(id) { let response = await apiReq.delete(usersURLs.userID(id)); return response.data; diff --git a/frontend/src/components/Admin/AdminSidebar.vue b/frontend/src/components/Admin/AdminSidebar.vue index 0e98a94b3..c4fbee3af 100644 --- a/frontend/src/components/Admin/AdminSidebar.vue +++ b/frontend/src/components/Admin/AdminSidebar.vue @@ -22,13 +22,22 @@ > @@ -50,7 +59,7 @@ - + + + \ No newline at end of file diff --git a/frontend/src/components/UI/CardSection.vue b/frontend/src/components/UI/CardSection.vue index 21d749660..b3c2ea9b2 100644 --- a/frontend/src/components/UI/CardSection.vue +++ b/frontend/src/components/UI/CardSection.vue @@ -31,7 +31,7 @@ - + + + + + +
diff --git a/frontend/src/components/UI/UploadBtn.vue b/frontend/src/components/UI/UploadBtn.vue index 7f1caeeb5..193f66d49 100644 --- a/frontend/src/components/UI/UploadBtn.vue +++ b/frontend/src/components/UI/UploadBtn.vue @@ -2,8 +2,8 @@ - mdi-cloud-upload - {{ $t("general.upload") }} + {{ icon }} + {{ text ? text : defaultText }} @@ -13,18 +13,27 @@ import api from "@/api"; export default { props: { url: String, + text: { default: "Upload" }, + icon: { default: "mdi-cloud-upload" }, + fileName: { defaul: "archive" }, }, data: () => ({ file: null, isSelecting: false, }), + computed: { + defaultText() { + return this.$t("general.upload"); + }, + }, + methods: { async upload() { if (this.file != null) { this.isSelecting = true; let formData = new FormData(); - formData.append("archive", this.file); + formData.append(this.fileName, this.file); await api.utils.uploadFile(this.url, formData); diff --git a/frontend/src/main.js b/frontend/src/main.js index d8040e6b2..a6092aca4 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,7 +1,7 @@ import Vue from "vue"; import App from "./App.vue"; import vuetify from "./plugins/vuetify"; -import store from "./store/store"; +import store from "./store"; import VueRouter from "vue-router"; import { routes } from "./routes"; import i18n from "./i18n"; diff --git a/frontend/src/mixins/initials.js b/frontend/src/mixins/initials.js new file mode 100644 index 000000000..9f874df09 --- /dev/null +++ b/frontend/src/mixins/initials.js @@ -0,0 +1,17 @@ +export const initials = { + computed: { + initials() { + const allNames = this.user.fullName.trim().split(" "); + const initials = allNames.reduce( + (acc, curr, index) => { + if (index === 0 || index === allNames.length - 1) { + acc = `${acc}${curr.charAt(0).toUpperCase()}`; + } + return acc; + }, + [""] + ); + return initials; + }, + }, +}; diff --git a/frontend/src/mixins/user.js b/frontend/src/mixins/user.js new file mode 100644 index 000000000..2d500bea4 --- /dev/null +++ b/frontend/src/mixins/user.js @@ -0,0 +1,24 @@ +import { store } from "@/store"; +export const user = { + computed: { + user() { + return store.getters.getUserData; + }, + loggedIn() { + return store.getters.getIsLoggedIn; + }, + initials() { + const allNames = this.user.fullName.trim().split(" "); + const initials = allNames.reduce( + (acc, curr, index) => { + if (index === 0 || index === allNames.length - 1) { + acc = `${acc}${curr.charAt(0).toUpperCase()}`; + } + return acc; + }, + [""] + ); + return initials; + }, + }, +}; diff --git a/frontend/src/mixins/validators.js b/frontend/src/mixins/validators.js new file mode 100644 index 000000000..dc7bb314b --- /dev/null +++ b/frontend/src/mixins/validators.js @@ -0,0 +1,15 @@ +export const validators = { + data() { + return { + emailRule: v => + !v || + /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(v) || + "E-mail must be valid", + + existsRule: value => !!value || "Field Required", + + minRule: v => + v.length >= 8 || "Use 8 characters or more for your password", + }; + }, +}; diff --git a/frontend/src/pages/404Page.vue b/frontend/src/pages/404Page.vue index f36fdc917..0b31adbfb 100644 --- a/frontend/src/pages/404Page.vue +++ b/frontend/src/pages/404Page.vue @@ -1,5 +1,5 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/index.vue b/frontend/src/pages/Admin/index.vue index 7913d20a4..6c0f2483f 100644 --- a/frontend/src/pages/Admin/index.vue +++ b/frontend/src/pages/Admin/index.vue @@ -1,18 +1,73 @@ diff --git a/frontend/src/pages/AllRecipesPage.vue b/frontend/src/pages/AllRecipesPage.vue index 7df8c422a..9989769f2 100644 --- a/frontend/src/pages/AllRecipesPage.vue +++ b/frontend/src/pages/AllRecipesPage.vue @@ -1,5 +1,5 @@ diff --git a/frontend/src/pages/MealPlanPage.vue b/frontend/src/pages/MealPlanPage.vue index eb87f34b7..864480745 100644 --- a/frontend/src/pages/MealPlanPage.vue +++ b/frontend/src/pages/MealPlanPage.vue @@ -1,5 +1,5 @@