From 5d655b9613b086f4eeb82d73ac32fb6091e84133 Mon Sep 17 00:00:00 2001 From: klemek Date: Tue, 14 Jul 2026 11:23:33 +0200 Subject: [PATCH] feat: toasts --- README.md | 6 ++-- bun.lock | 25 ++++++++++++++- package.json | 1 + resources/lang/en.json | 11 +++++++ resources/lang/fr.json | 11 +++++++ resources/ts/App.vue | 2 ++ resources/ts/components/AlertItem.vue | 23 ++++++++++++++ resources/ts/components/AlertsContainer.vue | 13 ++++++++ resources/ts/components/TaskItem.vue | 21 ++++++++++--- resources/ts/components/TaskList.vue | 34 ++++++++++++++++++--- resources/ts/main.ts | 5 ++- resources/ts/stores/alerts.ts | 29 ++++++++++++++++++ resources/ts/types.ts | 7 +++++ 13 files changed, 174 insertions(+), 14 deletions(-) create mode 100644 resources/ts/components/AlertItem.vue create mode 100644 resources/ts/components/AlertsContainer.vue create mode 100644 resources/ts/stores/alerts.ts diff --git a/README.md b/README.md index e568155..ceef665 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ - [x] options in cookies - [x] Empty list message - [x] change lang -- [ ] toasts -- [ ] handle errors +- [x] toasts +- [x] handle errors - [ ] list per URL - [ ] home screen -- [ ] lang in cookies +- [x] lang in cookies - [ ] simple view - [ ] Dockerfile - [ ] README diff --git a/bun.lock b/bun.lock index 876827d..e175cd1 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@lucide/vue": "^1.23.0", "cron-parser": "^5.6.1", "date-fns": "^4.4.0", + "pinia": "^3.0.4", "vue": "^3.5.38", "vue-i18n": "^11.4.6", }, @@ -349,7 +350,7 @@ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.39", "", { "dependencies": { "@vue/compiler-dom": "3.5.39", "@vue/shared": "3.5.39" } }, "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw=="], - "@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], + "@vue/devtools-api": ["@vue/devtools-api@7.7.10", "", { "dependencies": { "@vue/devtools-kit": "^7.7.10" } }, "sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA=="], "@vue/devtools-core": ["@vue/devtools-core@8.1.5", "", { "dependencies": { "@vue/devtools-kit": "^8.1.5", "@vue/devtools-shared": "^8.1.5" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-5e5jQOEssCdZA1wlFEUkIDtb+cAOWuLNWJ52fm4PBWbF7e3oTnM2fneaL42E5lJoolAaUQ678tv/XEb3h4e86Q=="], @@ -423,6 +424,8 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], + "cron-parser": ["cron-parser@5.6.1", "", { "dependencies": { "luxon": "^3.7.2" } }, "sha512-QBm4o1PwZiuY7KFbVvW7FLC8bozy7YWzv+Fz6KRS7sQghzcbDZCGxr/Bc5b6TQreAoSwuWVP491dIcK0THCX6A=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -543,6 +546,8 @@ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], "isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], @@ -615,6 +620,8 @@ "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -667,6 +674,8 @@ "pidtree": ["pidtree@0.6.1", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw=="], + "pinia": ["pinia@3.0.4", "", { "dependencies": { "@vue/devtools-api": "^7.7.7" }, "peerDependencies": { "typescript": ">=4.5.0", "vue": "^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw=="], + "postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], "postcss-selector-parser": ["postcss-selector-parser@6.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="], @@ -691,6 +700,8 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rolldown": ["rolldown@1.1.3", "", { "dependencies": { "@oxc-project/types": "=0.137.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.3", "@rolldown/binding-darwin-arm64": "1.1.3", "@rolldown/binding-darwin-x64": "1.1.3", "@rolldown/binding-freebsd-x64": "1.1.3", "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", "@rolldown/binding-linux-arm64-gnu": "1.1.3", "@rolldown/binding-linux-arm64-musl": "1.1.3", "@rolldown/binding-linux-ppc64-gnu": "1.1.3", "@rolldown/binding-linux-s390x-gnu": "1.1.3", "@rolldown/binding-linux-x64-gnu": "1.1.3", "@rolldown/binding-linux-x64-musl": "1.1.3", "@rolldown/binding-openharmony-arm64": "1.1.3", "@rolldown/binding-wasm32-wasi": "1.1.3", "@rolldown/binding-win32-arm64-msvc": "1.1.3", "@rolldown/binding-win32-x64-msvc": "1.1.3" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -751,8 +762,12 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], + "supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "sync-child-process": ["sync-child-process@1.0.2", "", { "dependencies": { "sync-message-port": "^1.0.0" } }, "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA=="], @@ -853,6 +868,8 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@vue/devtools-api/@vue/devtools-kit": ["@vue/devtools-kit@7.7.10", "", { "dependencies": { "@vue/devtools-shared": "^7.7.10", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -865,8 +882,14 @@ "vite-dev-rpc/birpc": ["birpc@4.0.0", "", {}, "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw=="], + "vue-i18n/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="], + "@vue/devtools-api/@vue/devtools-kit/@vue/devtools-shared": ["@vue/devtools-shared@7.7.10", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ=="], + + "@vue/devtools-api/@vue/devtools-kit/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/package.json b/package.json index 256ebf1..4d2f87d 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@lucide/vue": "^1.23.0", "cron-parser": "^5.6.1", "date-fns": "^4.4.0", + "pinia": "^3.0.4", "vue": "^3.5.38", "vue-i18n": "^11.4.6" }, diff --git a/resources/lang/en.json b/resources/lang/en.json index fd3635e..06ee22d 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -58,5 +58,16 @@ "last_checked": "Show last checked time", "next_reset": "Show next reset time", "separate_completed": "Separate completed tasks below" + }, + "alerts": { + "task_updated": "Task updated", + "task_update_error": "Could not update task", + "task_refresh_error": "Could not refresh tasks", + "task_fetch_error": "Could not fetch tasks", + "task_created": "Task created", + "task_create_error": "Could not create task", + "task_cloned": "Task cloned", + "task_deleted": "Task deleted", + "task_delete_error": "Could not delete task" } } diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 1d2c099..f71dc8d 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -58,5 +58,16 @@ "last_checked": "Afficher la dernière completion", "next_reset": "Afficher la prochaine réinitialisation", "separate_completed": "Séparer les tâches complétées plus bas" + }, + "alerts": { + "task_updated": "Tâche mise a jour", + "task_update_error": "Impossible de mettre à jour la tâche", + "task_refresh_error": "Impossible de rafraîchir la liste", + "task_fetch_error": "Impossible de récuperer la liste", + "task_created": "Tâche crée", + "task_create_error": "Impossible de créer la tâche", + "task_cloned": "Tâche clonée", + "task_deleted": "Tâche supprimée", + "task_delete_error": "Impossible de supprimer la tâche" } } diff --git a/resources/ts/App.vue b/resources/ts/App.vue index ab07161..23e91b5 100644 --- a/resources/ts/App.vue +++ b/resources/ts/App.vue @@ -2,6 +2,7 @@ import { ref, onMounted, defineAsyncComponent } from "vue"; const TaskList = defineAsyncComponent(() => import("@/components/TaskList.vue")); const PageFooter = defineAsyncComponent(() => import("@/components/PageFooter.vue")); +const AlertsContainer = defineAsyncComponent(() => import("@/components/AlertsContainer.vue")); const visible = ref(false); @@ -23,5 +24,6 @@ onMounted(() => { + diff --git a/resources/ts/components/AlertItem.vue b/resources/ts/components/AlertItem.vue new file mode 100644 index 0000000..5198dc8 --- /dev/null +++ b/resources/ts/components/AlertItem.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/ts/components/AlertsContainer.vue b/resources/ts/components/AlertsContainer.vue new file mode 100644 index 0000000..93db07b --- /dev/null +++ b/resources/ts/components/AlertsContainer.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/ts/components/TaskItem.vue b/resources/ts/components/TaskItem.vue index 0cd6ba5..94ccf1e 100644 --- a/resources/ts/components/TaskItem.vue +++ b/resources/ts/components/TaskItem.vue @@ -6,6 +6,8 @@ import { updateTask } from "@/api/tasks"; const CronInput = defineAsyncComponent(() => import("@/components/CronInput.vue")); import { taskCompleted, taskNextReset } from "@/lib/tasks"; import { relativeTime } from "@/lib/dates"; +import { useAlertStore } from "@/stores/alerts"; +import { useI18n } from "vue-i18n"; const emit = defineEmits<(e: 'clone' | 'delete', task: Task) => void>(); @@ -20,15 +22,22 @@ const refreshKey = ref(0); const checked = computed(() => taskCompleted(task.value)); const nextReset = computed(() => taskNextReset(task.value)); +const i18n = useI18n(); + +const { alertSuccess, alertError } = useAlertStore(); + function onClose() { editMode.value = false; } function onSave() { - void updateTask(task.value.id, { name: editName.value, reset_cron: editCron.value }) + updateTask(task.value.id, { name: editName.value, reset_cron: editCron.value }) .then((newTask) => { task.value = newTask; - }); + alertSuccess(i18n.t('alerts.task_updated')); + }).catch(() => { + alertError(i18n.t('alerts.task_update_error')) + }) task.value.name = editName.value; task.value.reset_cron = editCron.value; editMode.value = false; @@ -36,14 +45,18 @@ function onSave() { function onCheck() { if (!checked.value) { - void updateTask(task.value.id, { check_date: (new Date()).toISOString(), previous_check_date: task.value.check_date?.toISOString() }) + updateTask(task.value.id, { check_date: (new Date()).toISOString(), previous_check_date: task.value.check_date?.toISOString() }) .then((newTask) => { task.value = newTask; + }).catch(() => { + alertError(i18n.t('alerts.task_update_error')) }); } else { - void updateTask(task.value.id, { check_date: null, previous_check_date: task.value.check_date?.toISOString() }) + updateTask(task.value.id, { check_date: null, previous_check_date: task.value.check_date?.toISOString() }) .then((newTask) => { task.value = newTask; + }).catch(() => { + alertError(i18n.t('alerts.task_update_error')) }); } } diff --git a/resources/ts/components/TaskList.vue b/resources/ts/components/TaskList.vue index 72b4d02..6736b3e 100644 --- a/resources/ts/components/TaskList.vue +++ b/resources/ts/components/TaskList.vue @@ -7,6 +7,8 @@ const TaskItem = defineAsyncComponent(() => import("@/components/TaskItem.vue")) import { taskCompleted, sortTasks } from "@/lib/tasks"; import { SortType } from "@/enums"; import { booleanCookieRef, enumCookieRef } from "@/lib/cookies"; +import { useAlertStore } from "@/stores/alerts"; +import { useI18n } from "vue-i18n"; const taskInput = ref(""); const tasks = ref([]); @@ -22,13 +24,23 @@ const hasCompleted = computed(() => tasks.value.some(taskCompleted)); const empty = computed(() => tasks.value.length === 0); const allCompleted = computed(() => tasks.value.every(taskCompleted)); +const i18n = useI18n(); + +const { alertSuccess, alertError, alertWarning } = useAlertStore(); + function fetchTasks() { - void getTasks(list.value) + getTasks(list.value) .then((data: Task[]) => { tasks.value = data; reSortTasks(); setTimeout(fetchTasks, 10000); - }); + }).catch(() => { + if (tasks.value.length !== 0) { + alertWarning(i18n.t('alerts.task_refresh_error')); + } else { + alertError(i18n.t('alerts.task_fetch_error')); + } + }) } function reSortTasks() { @@ -37,13 +49,17 @@ function reSortTasks() { function onNewTask() { if (taskInput.value.trim()) { - void createTask(list.value, { + createTask(list.value, { name: taskInput.value.trim(), reset_cron: null, }) .then((data: Task) => { tasks.value.push(data); reSortTasks(); + alertSuccess(i18n.t('alerts.task_created')); + }) + .catch(() => { + alertError(i18n.t('alerts.task_create_error')) }) .finally(() => { taskInput.value = ""; @@ -52,16 +68,24 @@ function onNewTask() { } function onCloneTask(task: Task) { - void createTask(list.value, { + createTask(list.value, { name: task.name, reset_cron: task.reset_cron, }).then((data: Task) => { tasks.value.push(data); + alertSuccess(i18n.t('alerts.task_cloned')); + }).catch(() => { + alertError(i18n.t('alerts.task_create_error')) }) } function onDeleteTask(task: Task) { - void deleteTask(task.id); + deleteTask(task.id) + .then(() => { + alertSuccess(i18n.t('alerts.task_deleted')); + }).catch(() => { + alertError(i18n.t('alerts.task_delete_error')) + }) tasks.value.splice(tasks.value.indexOf(task), 1); } diff --git a/resources/ts/main.ts b/resources/ts/main.ts index af3bf10..550717d 100644 --- a/resources/ts/main.ts +++ b/resources/ts/main.ts @@ -2,6 +2,7 @@ import { createApp } from "vue"; import { createI18n } from "vue-i18n"; import App from "@/App.vue"; import { getLang } from "@/lib/lang"; +import { createPinia } from "pinia"; const en = await import("@lang/en.json"); const fr = await import("@lang/fr.json"); @@ -14,4 +15,6 @@ const i18n = createI18n({ }, }); -createApp(App).use(i18n).mount("#app"); +const pinia = createPinia(); + +createApp(App).use(i18n).use(pinia).mount("#app"); diff --git a/resources/ts/stores/alerts.ts b/resources/ts/stores/alerts.ts new file mode 100644 index 0000000..48a8dd1 --- /dev/null +++ b/resources/ts/stores/alerts.ts @@ -0,0 +1,29 @@ +import { ref } from 'vue'; +import { defineStore } from 'pinia'; +import type { Alert } from '@/types'; + +export const useAlertStore = defineStore('alerts', () => { + const alerts = ref([]); + + function addAlert(message: string, type: 'info' | 'success' | 'warning' | 'error', seconds: number) { + alerts.value.push({ message, type, created: new Date(), seconds }); + } + + function alertSuccess(message: string, seconds = 2) { + addAlert(message, 'success', seconds); + } + + function alertInfo(message: string, seconds = 3) { + addAlert(message, 'info', seconds); + } + + function alertWarning(message: string, seconds = 4) { + addAlert(message, 'warning', seconds); + } + + function alertError(message: string, seconds = 5) { + addAlert(message, 'error', seconds); + } + + return { alerts, alertSuccess, alertInfo, alertWarning, alertError }; +}) diff --git a/resources/ts/types.ts b/resources/ts/types.ts index 4576577..6ac30ff 100644 --- a/resources/ts/types.ts +++ b/resources/ts/types.ts @@ -39,3 +39,10 @@ export interface TaskUpdateData { check_date?: string | null; previous_check_date?: string | null; } + +export interface Alert { + message: string + type: 'info' | 'success' | 'warning' | 'error' + created: Date + seconds: number +}