Added alerts
This commit is contained in:
parent
84a76151e4
commit
cc3f18ad70
7 changed files with 146 additions and 3 deletions
|
|
@ -15,13 +15,19 @@
|
||||||
<div class="viewport">
|
<div class="viewport">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<alert-toast />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import data from './data.js'
|
import data from './data.js'
|
||||||
|
import AlertToast from './components/AlertToast.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
'alert-toast': AlertToast
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentRoute() {
|
currentRoute() {
|
||||||
return this.$route.path
|
return this.$route.path
|
||||||
|
|
|
||||||
10
src/alert.js
Normal file
10
src/alert.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
const subscribers = [];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
subscribe(callback) {
|
||||||
|
subscribers.push(callback);
|
||||||
|
},
|
||||||
|
show(message) { // { message, heading, type: ["success", "error", "info"] }
|
||||||
|
subscribers.forEach(callback => callback(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/assets/notification-error.svg
Normal file
12
src/assets/notification-error.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>error-filled</title>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="add" fill="#000000" transform="translate(42.666667, 42.666667)">
|
||||||
|
<path d="M213.333333,3.55271368e-14 C331.136,3.55271368e-14 426.666667,95.5306667 426.666667,213.333333 C426.666667,331.136 331.136,426.666667 213.333333,426.666667 C95.5306667,426.666667 3.55271368e-14,331.136 3.55271368e-14,213.333333 C3.55271368e-14,95.5306667 95.5306667,3.55271368e-14 213.333333,3.55271368e-14 Z M262.250667,134.250667 L213.333333,183.168 L164.416,134.250667 L134.250667,164.416 L183.168,213.333333 L134.250667,262.250667 L164.416,292.416 L213.333333,243.498667 L262.250667,292.416 L292.416,262.250667 L243.498667,213.333333 L292.416,164.416 L262.250667,134.250667 Z" id="Combined-Shape">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
2
src/assets/notification-info.svg
Normal file
2
src/assets/notification-info.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zm-1.5-5.009c0-.867.659-1.491 1.491-1.491.85 0 1.509.624 1.509 1.491 0 .867-.659 1.509-1.509 1.509-.832 0-1.491-.642-1.491-1.509zM11.172 6a.5.5 0 0 0-.499.522l.306 7a.5.5 0 0 0 .5.478h1.043a.5.5 0 0 0 .5-.478l.305-7a.5.5 0 0 0-.5-.522h-1.655z" fill="#000000"/></svg>
|
||||||
|
After Width: | Height: | Size: 607 B |
12
src/assets/notification-success.svg
Normal file
12
src/assets/notification-success.svg
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>success-filled</title>
|
||||||
|
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="add-copy-2" fill="#000000" transform="translate(42.666667, 42.666667)">
|
||||||
|
<path d="M213.333333,3.55271368e-14 C95.51296,3.55271368e-14 3.55271368e-14,95.51296 3.55271368e-14,213.333333 C3.55271368e-14,331.153707 95.51296,426.666667 213.333333,426.666667 C331.153707,426.666667 426.666667,331.153707 426.666667,213.333333 C426.666667,95.51296 331.153707,3.55271368e-14 213.333333,3.55271368e-14 Z M293.669333,137.114453 L323.835947,167.281067 L192,299.66912 L112.916693,220.585813 L143.083307,190.4192 L192,239.335893 L293.669333,137.114453 Z" id="Shape">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1,014 B |
98
src/components/AlertToast.vue
Normal file
98
src/components/AlertToast.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<div v-if="showAlert" :class="['alert', type]" @click="dismiss">
|
||||||
|
<img v-if="icon" :src="icon" alt="Notification icon" />
|
||||||
|
<div class="message-container">
|
||||||
|
<h4 class="heading">{{ heading }}</h4>
|
||||||
|
<p class="message">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
/* Display as a toast, in the bottom right corner */
|
||||||
|
/* Place the icon to the left for the full height, then have the heading and message stacked to the right */
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 1em;
|
||||||
|
right: 1em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert img {
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
margin-right: 1em;
|
||||||
|
|
||||||
|
/* Invert svg colors */
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.error {
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.success {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.info {
|
||||||
|
background-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import alert from '@/alert';
|
||||||
|
|
||||||
|
const alertIcons = {
|
||||||
|
error: require('@/assets/notification-error.svg'),
|
||||||
|
success: require('@/assets/notification-success.svg'),
|
||||||
|
info: require('@/assets/notification-info.svg')
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
@ -36,7 +36,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import data from '@/data.js'
|
import data from '@/data.js';
|
||||||
|
import alert from '@/alert.js';
|
||||||
|
|
||||||
import RecipeSearchBox from '@/components/recipes/RecipeSearchBox.vue';
|
import RecipeSearchBox from '@/components/recipes/RecipeSearchBox.vue';
|
||||||
import RecipeCard from '@/components/recipes/RecipeCard.vue';
|
import RecipeCard from '@/components/recipes/RecipeCard.vue';
|
||||||
import DatePicker from './DatePicker.vue';
|
import DatePicker from './DatePicker.vue';
|
||||||
|
|
@ -115,10 +117,11 @@ export default {
|
||||||
const meal = await data.saveMeal(this.meal);
|
const meal = await data.saveMeal(this.meal);
|
||||||
if (meal?.id >= 0) {
|
if (meal?.id >= 0) {
|
||||||
this.$router.push(`/meals/${meal.id}`);
|
this.$router.push(`/meals/${meal.id}`);
|
||||||
|
alert.show({ heading: 'Meal saved', message: 'Meal saved successfully', type: 'success' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
alert('Failed to save meal');
|
alert.show({ heading: 'Error saving meal', message: 'An error occurred while saving the meal', type: 'error' });
|
||||||
},
|
},
|
||||||
onEditAdditionalIngredients(editing) {
|
onEditAdditionalIngredients(editing) {
|
||||||
if (editing && this.meal.extra_ingredients.length === 0) {
|
if (editing && this.meal.extra_ingredients.length === 0) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue