This commit is contained in:
j3d1 2026-09-05 16:20:44 +02:00
parent 9814ec7ecd
commit 197f173097
14 changed files with 170 additions and 89 deletions

View file

@ -1,9 +1,9 @@
<template> <template>
<span style="position: relative; display: inline-block;"> <span style="position: relative; display: inline-block;">
<div class="delete-button"> <div class="delete-button">
<button class="btn btn-danger btn-sm" @click="triggerDelete"> <guarded-button class="btn btn-danger btn-sm" :confirm-message="confirmMessage" @confirm="$emit('delete')">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</button> </guarded-button>
</div> </div>
<slot></slot> <slot></slot>
</span> </span>
@ -20,10 +20,12 @@
<script> <script>
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import GuardedButton from "@/components/GuardedButton.vue";
export default { export default {
name: "DeletableWrapper", name: "DeletableWrapper",
components: { components: {
GuardedButton,
...BIcons ...BIcons
}, },
props: { props: {
@ -33,12 +35,6 @@ export default {
default: null default: null
} }
}, },
emits: ["delete"], emits: ["delete"]
methods: {
triggerDelete() {
if (this.confirmMessage && !confirm(this.confirmMessage)) return;
this.$emit("delete");
}
}
} }
</script> </script>

View file

@ -0,0 +1,43 @@
<template>
<component :is="tag" v-bind="rootProps" :class="{unguarded: shiftHeld}" @click="handleClick">
<slot></slot>
</component>
</template>
<script>
import {reactive} from "vue"
const shiftState = reactive({held: false})
window.addEventListener("keydown", event => shiftState.held = event.shiftKey)
window.addEventListener("keyup", event => shiftState.held = event.shiftKey)
export default {
name: "GuardedButton",
props: {
confirmMessage: {
type: String,
default: null
},
tag: {
type: String,
default: "button"
}
},
emits: ["confirm"],
computed: {
rootProps() {
return this.tag === "a" ? {href: "#"} : {}
},
shiftHeld() {
return shiftState.held
}
},
methods: {
handleClick(event) {
if (this.tag === "a") event.preventDefault()
if (this.confirmMessage && !event.shiftKey && !confirm(this.confirmMessage)) return
this.$emit("confirm", event)
}
}
}
</script>

View file

@ -38,10 +38,12 @@
<div v-if="photos.length > 0" class="photo-gallery mb-4"> <div v-if="photos.length > 0" class="photo-gallery mb-4">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Captured Photos ({{ photos.length }})</h6> <h6 class="mb-0">Captured Photos ({{ photos.length }})</h6>
<button class="btn btn-sm btn-outline-danger" @click="clearAllPhotos"> <guarded-button class="btn btn-sm btn-outline-danger"
confirm-message="Are you sure you want to remove all photos?"
@confirm="clearAllPhotos">
<b-icon-trash class="me-1"></b-icon-trash> <b-icon-trash class="me-1"></b-icon-trash>
Clear All Clear All
</button> </guarded-button>
</div> </div>
<div class="row"> <div class="row">
<div v-for="(photo, index) in photos" :key="index" class="col-sm-6 col-md-4 col-lg-3 mb-3"> <div v-for="(photo, index) in photos" :key="index" class="col-sm-6 col-md-4 col-lg-3 mb-3">
@ -279,6 +281,7 @@
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import {mapActions, mapState} from "vuex"; import {mapActions, mapState} from "vuex";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import DragDropFileSource from "@/components/inputs/DragDropFileSource.vue"; import DragDropFileSource from "@/components/inputs/DragDropFileSource.vue";
import CameraFileSource from "@/components/inputs/CameraFileSource.vue"; import CameraFileSource from "@/components/inputs/CameraFileSource.vue";
import FsFileSource from "@/components/inputs/FsFileSource.vue"; import FsFileSource from "@/components/inputs/FsFileSource.vue";
@ -350,6 +353,7 @@ export default {
components: { components: {
WebcamFileSource, WebcamFileSource,
AuthenticatedImage, AuthenticatedImage,
GuardedButton,
DragDropFileSource, DragDropFileSource,
CameraFileSource, CameraFileSource,
FsFileSource, FsFileSource,
@ -500,14 +504,12 @@ export default {
}, },
async clearAllPhotos() { async clearAllPhotos() {
if (confirm('Are you sure you want to remove all photos?')) {
const removed = this.photos; const removed = this.photos;
this.photos = []; this.photos = [];
await Promise.all(removed.map(photo => await Promise.all(removed.map(photo =>
this.unstageFile({lifetime_id: this.workflowInstance.id, file_hash: photo.hash}) this.unstageFile({lifetime_id: this.workflowInstance.id, file_hash: photo.hash})
.catch(error => console.error('Failed to unstage photo:', error)) .catch(error => console.error('Failed to unstage photo:', error))
)); ));
}
}, },
proceedFromStep1() { proceedFromStep1() {

View file

@ -46,10 +46,12 @@
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
Edit Edit
</a--> </a-->
<a href="#" class="align-middle" @click="tryDropFriend(friend)"> <guarded-button tag="a" class="align-middle"
:confirm-message='`Are you sure you want to remove "${friend.handle}" as a friend?`'
@confirm="tryDropFriend(friend)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
Delete Delete
</a> </guarded-button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -107,6 +109,7 @@ import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
import UserNameTag from "@/components/UserNameTag.vue"; import UserNameTag from "@/components/UserNameTag.vue";
import GuardedButton from "@/components/GuardedButton.vue";
export default { export default {
name: 'Inventory', name: 'Inventory',
@ -114,6 +117,7 @@ export default {
BaseLayout, BaseLayout,
AuthenticatedImage, AuthenticatedImage,
UserNameTag, UserNameTag,
GuardedButton,
...BIcons ...BIcons
}, },
data() { data() {
@ -173,7 +177,6 @@ export default {
}) })
}, },
tryDropFriend(friend) { tryDropFriend(friend) {
if (!confirm(`Are you sure you want to remove "${friend.handle}" as a friend?`)) return
this.dropFriend(friend).then((ok) => { this.dropFriend(friend).then((ok) => {
if (ok) { if (ok) {
delete this.friends[friend.handle] delete this.friends[friend.handle]

View file

@ -40,10 +40,12 @@
<user-name-tag :user="member"/> <user-name-tag :user="member"/>
</td> </td>
<td class="table-action"> <td class="table-action">
<a href="#" class="align-middle" @click.prevent="tryRemoveMember(member)"> <guarded-button tag="a" class="align-middle"
:confirm-message='`Are you sure you want to remove "${member.handle}" from ${group.handle}?`'
@confirm="tryRemoveMember(member)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
Remove Remove
</a> </guarded-button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -61,6 +63,7 @@ import {mapActions} from "vuex";
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import UserNameTag from "@/components/UserNameTag.vue"; import UserNameTag from "@/components/UserNameTag.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {decodeHandleFromUrl} from "@/router"; import {decodeHandleFromUrl} from "@/router";
export default { export default {
@ -68,6 +71,7 @@ export default {
components: { components: {
BaseLayout, BaseLayout,
UserNameTag, UserNameTag,
GuardedButton,
...BIcons ...BIcons
}, },
props: { props: {
@ -107,7 +111,6 @@ export default {
}) })
}, },
tryRemoveMember(member) { tryRemoveMember(member) {
if (!confirm(`Are you sure you want to remove "${member.handle}" from ${this.group.handle}?`)) return
this.removeGroupMember({groupHandle: this.group.handle, identityId: member.id}) this.removeGroupMember({groupHandle: this.group.handle, identityId: member.id})
.then(() => { .then(() => {
this.refresh() this.refresh()

View file

@ -61,9 +61,11 @@
<router-link v-if="canEdit" :to="itemEditRoute(item)" title="Edit"> <router-link v-if="canEdit" :to="itemEditRoute(item)" title="Edit">
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
</router-link> </router-link>
<a v-if="canEdit" href="#" title="Delete" @click.prevent="tryDeleteItem(item)"> <guarded-button v-if="canEdit" tag="a" title="Delete"
:confirm-message='`Are you sure you want to delete "${item.name}"? This cannot be undone.`'
@confirm="tryDeleteItem(item)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</a> </guarded-button>
<router-link v-if="shortIdLink(item)" :to="shortIdLink(item)"> <router-link v-if="shortIdLink(item)" :to="shortIdLink(item)">
<b-icon-link></b-icon-link> <b-icon-link></b-icon-link>
</router-link> </router-link>
@ -93,10 +95,12 @@
<span class="float-right">{{ item.owned_quantity }}</span> <span class="float-right">{{ item.owned_quantity }}</span>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button v-if="canEdit" class="btn btn-danger btn-sm" <guarded-button v-if="canEdit" class="btn btn-danger btn-sm"
title="Delete" @click="tryDeleteItem(item)"> title="Delete"
:confirm-message='`Are you sure you want to delete "${item.name}"? This cannot be undone.`'
@confirm="tryDeleteItem(item)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</button> </guarded-button>
<router-link v-if="canEdit" :to="itemEditRoute(item)" <router-link v-if="canEdit" :to="itemEditRoute(item)"
class="btn btn-primary btn-sm" title="Edit"> class="btn btn-primary btn-sm" title="Edit">
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
@ -135,10 +139,12 @@
<span class="float-right">{{ item.owned_quantity }}</span> <span class="float-right">{{ item.owned_quantity }}</span>
</div> </div>
<div class="btn-group mt-auto"> <div class="btn-group mt-auto">
<button v-if="canEdit" class="btn btn-danger btn-sm" <guarded-button v-if="canEdit" class="btn btn-danger btn-sm"
title="Delete" @click="tryDeleteItem(item)"> title="Delete"
:confirm-message='`Are you sure you want to delete "${item.name}"? This cannot be undone.`'
@confirm="tryDeleteItem(item)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</button> </guarded-button>
<router-link v-if="canEdit" :to="itemEditRoute(item)" <router-link v-if="canEdit" :to="itemEditRoute(item)"
class="btn btn-primary btn-sm" title="Edit"> class="btn btn-primary btn-sm" title="Edit">
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
@ -171,6 +177,7 @@ import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import AuthenticatedImage from "../components/AuthenticatedImage.vue"; import AuthenticatedImage from "../components/AuthenticatedImage.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {shortenedRoute, encodeHandleForUrl, decodeHandleFromUrl} from "@/router"; import {shortenedRoute, encodeHandleForUrl, decodeHandleFromUrl} from "@/router";
export default { export default {
@ -185,6 +192,7 @@ export default {
components: { components: {
AuthenticatedImage, AuthenticatedImage,
BaseLayout, BaseLayout,
GuardedButton,
...BIcons ...BIcons
}, },
computed: { computed: {
@ -241,7 +249,6 @@ export default {
return this.fetchFriendInventoryItems({friendHandle: this.selectedOwner}) return this.fetchFriendInventoryItems({friendHandle: this.selectedOwner})
}, },
tryDeleteItem(item) { tryDeleteItem(item) {
if (!confirm(`Are you sure you want to delete "${item.name}"? This cannot be undone.`)) return
this.deleteInventoryItem(item).then(() => { this.deleteInventoryItem(item).then(() => {
this.fetchItemsForOwner() this.fetchItemsForOwner()
}) })
@ -331,4 +338,11 @@ export default {
column-count: 6; column-count: 6;
} }
} }
.unguarded.btn-danger,
.unguarded.btn-danger:hover,
.unguarded.btn-danger:focus {
background-color: orange;
border-color: orange;
}
</style> </style>

View file

@ -45,11 +45,12 @@
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
Edit Edit
</button> </button>
<button type="submit" class="btn btn-danger" <guarded-button class="btn btn-danger"
@click="tryDeleteItem"> :confirm-message='`Are you sure you want to delete "${item.name}"? This cannot be undone.`'
@confirm="tryDeleteItem">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
Delete Delete
</button> </guarded-button>
<button v-if="canPrint" class="btn btn-secondary" <button v-if="canPrint" class="btn btn-secondary"
@click="$router.push({name: 'print', query: {kind: 'item', userHandle: decodedHandle, id: id}})"> @click="$router.push({name: 'print', query: {kind: 'item', userHandle: decodedHandle, id: id}})">
<b-icon-printer></b-icon-printer> <b-icon-printer></b-icon-printer>
@ -68,6 +69,7 @@ import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import {mapActions, mapGetters, mapState} from "vuex"; import {mapActions, mapGetters, mapState} from "vuex";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {decodeHandleFromUrl, ownerOverviewRoute} from "@/router"; import {decodeHandleFromUrl, ownerOverviewRoute} from "@/router";
export default { export default {
@ -75,6 +77,7 @@ export default {
components: { components: {
AuthenticatedImage, AuthenticatedImage,
BaseLayout, BaseLayout,
GuardedButton,
...BIcons ...BIcons
}, },
props: { props: {
@ -118,7 +121,6 @@ export default {
this.item = await this.fetchItemByHandle({handle: this.decodedHandle, id: this.id}) || {} this.item = await this.fetchItemByHandle({handle: this.decodedHandle, id: this.id}) || {}
}, },
tryDeleteItem() { tryDeleteItem() {
if (!confirm(`Are you sure you want to delete "${this.item.name}"? This cannot be undone.`)) return
this.deleteInventoryItem(this.item).then(() => this.$router.push(ownerOverviewRoute(this.item))) this.deleteInventoryItem(this.item).then(() => this.$router.push(ownerOverviewRoute(this.item)))
} }
}, },

View file

@ -35,10 +35,11 @@
<router-link class="list-group-item list-group-item-action" :to="{name: 'data'}" <router-link class="list-group-item list-group-item-action" :to="{name: 'data'}"
active-class="active">Your data active-class="active">Your data
</router-link> </router-link>
<a class="list-group-item list-group-item-action delete" @click="deleteAccount" <guarded-button tag="a" class="list-group-item list-group-item-action delete"
href="#"> confirm-message="Are you sure you want to permanently delete your account? All your data will be deleted and you will not be able to log back in. This cannot be undone."
@confirm="deleteAccount">
Delete account Delete account
</a> </guarded-button>
</div> </div>
</div> </div>
</div> </div>
@ -55,11 +56,12 @@
<script> <script>
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {mapActions, mapGetters, mapMutations} from "vuex"; import {mapActions, mapGetters, mapMutations} from "vuex";
export default { export default {
name: 'Settings', name: 'Settings',
components: {BaseLayout}, components: {BaseLayout, GuardedButton},
data() { data() {
return { return {
deleteError: null, deleteError: null,
@ -74,10 +76,6 @@ export default {
...mapMutations(['logout']), ...mapMutations(['logout']),
async deleteAccount() { async deleteAccount() {
this.deleteError = null; this.deleteError = null;
if (!confirm('Are you sure you want to permanently delete your account? ' +
'All your data will be deleted and you will not be able to log back in. This cannot be undone.')) {
return;
}
try { try {
const servers = await this.getHomeServers(); const servers = await this.getHomeServers();
const response = await servers.delete(this.signAuth, '/api/v1/account/'); const response = await servers.delete(this.signAuth, '/api/v1/account/');

View file

@ -58,9 +58,11 @@
<router-link :to="locationEditRoute(location)" title="Edit"> <router-link :to="locationEditRoute(location)" title="Edit">
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
</router-link> </router-link>
<a href="#" title="Delete" @click.prevent="tryDeleteLocation(location)"> <guarded-button tag="a" title="Delete"
:confirm-message='`Are you sure you want to delete "${location.name}"? This cannot be undone.`'
@confirm="tryDeleteLocation(location)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</a> </guarded-button>
<router-link v-if="shortIdLink(location)" :to="shortIdLink(location)"> <router-link v-if="shortIdLink(location)" :to="shortIdLink(location)">
<b-icon-link></b-icon-link> <b-icon-link></b-icon-link>
</router-link> </router-link>
@ -91,10 +93,12 @@
<small>{{ location.description }}</small> <small>{{ location.description }}</small>
</div> </div>
<div class="btn-group mt-auto"> <div class="btn-group mt-auto">
<button class="btn btn-danger btn-sm" <guarded-button class="btn btn-danger btn-sm"
title="Delete" @click="tryDeleteLocation(location)"> title="Delete"
:confirm-message='`Are you sure you want to delete "${location.name}"? This cannot be undone.`'
@confirm="tryDeleteLocation(location)">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
</button> </guarded-button>
<router-link :to="locationEditRoute(location)" <router-link :to="locationEditRoute(location)"
class="btn btn-primary btn-sm" title="Edit"> class="btn btn-primary btn-sm" title="Edit">
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
@ -126,6 +130,7 @@
import {mapActions, mapGetters, mapState} from "vuex"; import {mapActions, mapGetters, mapState} from "vuex";
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {shortenedRoute, encodeHandleForUrl, decodeHandleFromUrl} from "@/router"; import {shortenedRoute, encodeHandleForUrl, decodeHandleFromUrl} from "@/router";
export default { export default {
@ -139,6 +144,7 @@ export default {
}, },
components: { components: {
BaseLayout, BaseLayout,
GuardedButton,
...BIcons ...BIcons
}, },
computed: { computed: {
@ -186,7 +192,6 @@ export default {
: this.fetchGroupStorageLocations({groupHandle: this.selectedOwner}) : this.fetchGroupStorageLocations({groupHandle: this.selectedOwner})
}, },
tryDeleteLocation(location) { tryDeleteLocation(location) {
if (!confirm(`Are you sure you want to delete "${location.name}"? This cannot be undone.`)) return
this.deleteStorageLocation(location).then(() => { this.deleteStorageLocation(location).then(() => {
this.fetchLocationsForOwner() this.fetchLocationsForOwner()
}) })
@ -242,4 +247,11 @@ export default {
.btn-group.mt-auto .btn { .btn-group.mt-auto .btn {
flex: 1; flex: 1;
} }
.unguarded.btn-danger,
.unguarded.btn-danger:hover,
.unguarded.btn-danger:focus {
background-color: orange;
border-color: orange;
}
</style> </style>

View file

@ -38,11 +38,12 @@
<b-icon-pencil-square></b-icon-pencil-square> <b-icon-pencil-square></b-icon-pencil-square>
Edit Edit
</button> </button>
<button type="submit" class="btn btn-danger" <guarded-button class="btn btn-danger"
@click="tryDeleteLocation"> :confirm-message='`Are you sure you want to delete "${location.name}"? This cannot be undone.`'
@confirm="tryDeleteLocation">
<b-icon-trash></b-icon-trash> <b-icon-trash></b-icon-trash>
Delete Delete
</button> </guarded-button>
<button class="btn btn-secondary" <button class="btn btn-secondary"
@click="$router.push({name: 'print', query: {kind: 'storage-location', userHandle: decodedHandle, id: id}})"> @click="$router.push({name: 'print', query: {kind: 'storage-location', userHandle: decodedHandle, id: id}})">
<b-icon-printer></b-icon-printer> <b-icon-printer></b-icon-printer>
@ -58,6 +59,7 @@
<script> <script>
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {mapActions, mapGetters, mapState} from "vuex"; import {mapActions, mapGetters, mapState} from "vuex";
import {decodeHandleFromUrl, ownerLocationOverviewRoute} from "@/router"; import {decodeHandleFromUrl, ownerLocationOverviewRoute} from "@/router";
@ -65,6 +67,7 @@ export default {
name: "StorageLocationDetail", name: "StorageLocationDetail",
components: { components: {
BaseLayout, BaseLayout,
GuardedButton,
...BIcons ...BIcons
}, },
props: { props: {
@ -105,7 +108,6 @@ export default {
this.location = await this.fetchStorageLocationByHandle({handle: this.decodedHandle, id: this.id}) || {} this.location = await this.fetchStorageLocationByHandle({handle: this.decodedHandle, id: this.id}) || {}
}, },
tryDeleteLocation() { tryDeleteLocation() {
if (!confirm(`Are you sure you want to delete "${this.location.name}"? This cannot be undone.`)) return
this.deleteStorageLocation(this.location).then(() => this.$router.push(ownerLocationOverviewRoute(this.location))) this.deleteStorageLocation(this.location).then(() => this.$router.push(ownerLocationOverviewRoute(this.location)))
} }
}, },

View file

@ -19,10 +19,12 @@
<b-icon-arrow-left class="me-1"></b-icon-arrow-left> <b-icon-arrow-left class="me-1"></b-icon-arrow-left>
Back Back
</button> </button>
<button v-if="canAbort" class="btn btn-outline-danger" @click="abortWorkflow" :disabled="loading"> <guarded-button v-if="canAbort" class="btn btn-outline-danger"
:confirm-message='`Are you sure you want to abort "${getWorkflowDisplayName || workflowInstance.title}"?`'
@confirm="abortWorkflow" :disabled="loading">
<b-icon-x-circle class="me-1"></b-icon-x-circle> <b-icon-x-circle class="me-1"></b-icon-x-circle>
Abort Abort
</button> </guarded-button>
</div> </div>
</div> </div>
@ -199,6 +201,7 @@
<script> <script>
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { getWorkflow, getWorkflowComponent } from '@/workflows.js'; import { getWorkflow, getWorkflowComponent } from '@/workflows.js';
@ -206,7 +209,8 @@ export default {
name: 'WorkflowDetail', name: 'WorkflowDetail',
components: { components: {
...BIcons, ...BIcons,
BaseLayout BaseLayout,
GuardedButton
}, },
props: { props: {
id: { id: {
@ -416,8 +420,6 @@ export default {
}, },
async abortWorkflow() { async abortWorkflow() {
const displayName = this.getWorkflowDisplayName || this.workflowInstance.title;
if (!confirm(`Are you sure you want to abort "${displayName}"?`)) return;
try { try {
this.loading = true; this.loading = true;
await this.deleteWorkflow(this.workflowInstance.id); await this.deleteWorkflow(this.workflowInstance.id);

View file

@ -54,11 +54,12 @@
:disabled="loading"> :disabled="loading">
<b-icon-eye></b-icon-eye> <b-icon-eye></b-icon-eye>
</button> </button>
<button class="btn btn-sm btn-outline-danger" <guarded-button class="btn btn-sm btn-outline-danger"
@click="abortWorkflowInstance(workflow)" :confirm-message='`Are you sure you want to abort "${getWorkflowDisplayName(workflow)}"?`'
@confirm="abortWorkflowInstance(workflow)"
:disabled="loading"> :disabled="loading">
<b-icon-x-circle></b-icon-x-circle> <b-icon-x-circle></b-icon-x-circle>
</button> </guarded-button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -132,6 +133,7 @@
<script> <script>
import * as BIcons from "bootstrap-icons-vue"; import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue"; import BaseLayout from "@/components/BaseLayout.vue";
import GuardedButton from "@/components/GuardedButton.vue";
import {mapState, mapActions} from 'vuex'; import {mapState, mapActions} from 'vuex';
import {getAllWorkflows, getWorkflow, buildWorkflowApiPayload} from '@/workflows.js'; import {getAllWorkflows, getWorkflow, buildWorkflowApiPayload} from '@/workflows.js';
@ -139,7 +141,8 @@ export default {
name: 'Workflows', name: 'Workflows',
components: { components: {
...BIcons, ...BIcons,
BaseLayout BaseLayout,
GuardedButton
}, },
data() { data() {
return { return {
@ -264,7 +267,6 @@ export default {
}, },
async abortWorkflowInstance(workflow) { async abortWorkflowInstance(workflow) {
const displayName = this.getWorkflowDisplayName(workflow); const displayName = this.getWorkflowDisplayName(workflow);
if (confirm(`Are you sure you want to abort "${displayName}"?`)) {
try { try {
this.loading = true; this.loading = true;
this.error = null; this.error = null;
@ -279,7 +281,6 @@ export default {
} }
} }
} }
}
} }
</script> </script>

View file

@ -32,9 +32,11 @@
<fs-file-source @input="uploadPicture"> <fs-file-source @input="uploadPicture">
<span class="btn btn-primary"><i class="fas fa-upload"></i> Upload</span> <span class="btn btn-primary"><i class="fas fa-upload"></i> Upload</span>
</fs-file-source> </fs-file-source>
<button class="btn btn-outline-secondary ml-2" type="button" @click="removePicture"> <guarded-button class="btn btn-outline-secondary ml-2" type="button"
confirm-message="Are you sure you want to remove your profile picture?"
@confirm="removePicture">
Remove Remove
</button> </guarded-button>
</div> </div>
<small>For best results, use an image at least 128px by <small>For best results, use an image at least 128px by
128px in .jpg format</small> 128px in .jpg format</small>
@ -107,10 +109,11 @@
import {mapActions, mapState} from "vuex"; import {mapActions, mapState} from "vuex";
import AuthenticatedImage from "@/components/AuthenticatedImage.vue"; import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
import FsFileSource from "@/components/inputs/FsFileSource.vue"; import FsFileSource from "@/components/inputs/FsFileSource.vue";
import GuardedButton from "@/components/GuardedButton.vue";
export default { export default {
name: 'Account', name: 'Account',
components: {AuthenticatedImage, FsFileSource}, components: {AuthenticatedImage, FsFileSource, GuardedButton},
computed: { computed: {
...mapState(['user', 'user_profile']), ...mapState(['user', 'user_profile']),
profilePictureSrc() { profilePictureSrc() {
@ -130,7 +133,6 @@ export default {
await this.updateUserProfilePicture({file: image}); await this.updateUserProfilePicture({file: image});
}, },
async removePicture() { async removePicture() {
if (!confirm('Are you sure you want to remove your profile picture?')) return;
await this.updateUserProfilePicture({file: null}); await this.updateUserProfilePicture({file: null});
} }
}, },

View file

@ -37,9 +37,11 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label <label class="form-label
d-block">Delete your data</label> d-block">Delete your data</label>
<button type="button" class="btn btn-danger" @click="deleteData"> <guarded-button type="button" class="btn btn-danger"
confirm-message="Are you sure you want to permanently delete all your data (inventory, locations, settings, friends and files)? Your account itself will stay - this cannot be undone."
@confirm="deleteData">
Delete Delete
</button> </guarded-button>
<br> <br>
<small>Delete all your data including photos, videos, <small>Delete all your data including photos, videos,
comments, profile information and more</small> comments, profile information and more</small>
@ -78,12 +80,15 @@
<script> <script>
import {mapActions, mapGetters} from 'vuex'; import {mapActions, mapGetters} from 'vuex';
import router from "@/router"; import router from "@/router";
import GuardedButton from "@/components/GuardedButton.vue";
//import VueQrcode from '@chenfengyuan/vue-qrcode'; //import VueQrcode from '@chenfengyuan/vue-qrcode';
export default { export default {
name: 'Data', name: 'Data',
components: {}, components: {
GuardedButton
},
data: () => ({ data: () => ({
selectedFile: null, selectedFile: null,
localUserIdentityRecord: null, localUserIdentityRecord: null,
@ -106,10 +111,6 @@ export default {
async deleteData() { async deleteData() {
this.deleteError = null; this.deleteError = null;
this.deleteSuccess = null; this.deleteSuccess = null;
if (!confirm('Are you sure you want to permanently delete all your data (inventory, locations, ' +
'settings, friends and files)? Your account itself will stay - this cannot be undone.')) {
return;
}
try { try {
const servers = await this.getHomeServers(); const servers = await this.getHomeServers();
const response = await servers.delete(this.signAuth, '/api/v1/account_data/'); const response = await servers.delete(this.signAuth, '/api/v1/account_data/');