chore(front): modernize DangerousButton (destructive action confirmation dialog)

This commit is contained in:
upsiflu 2025-02-06 18:55:14 +01:00
parent cae4d7c0f4
commit 60463d405e
21 changed files with 169 additions and 380 deletions

View File

@ -13,9 +13,11 @@ import { useStore } from '~/store'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import useLogger from '~/composables/useLogger'
import DangerousButton from '~/components/common/DangerousButton.vue'
import SubsonicTokenForm from '~/components/auth/SubsonicTokenForm.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue'
import PasswordInput from '~/components/forms/PasswordInput.vue'
import Input from '~/components/ui/Input.vue'
import Layout from '~/components/ui/Layout.vue'
import Header from '~/components/ui/Header.vue'
@ -412,13 +414,9 @@ fetchOwnedApps()
<dangerous-button
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
:action="submitPassword"
:title="t('components.auth.Settings.modal.changePassword.header')"
>
{{ t('components.auth.Settings.button.password') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.changePassword.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
@ -558,22 +556,14 @@ fetchOwnedApps()
<dangerous-button
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
@confirm="revokeApp(app.client_id)"
:title="t('components.auth.Settings.modal.revokeApp.header', {app: app.name})"
>
{{ t('components.auth.Settings.button.revoke') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.revokeApp.header', {app: app.name}) }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.auth.Settings.modal.revokeApp.content.warning') }}
</p>
{{ t('components.auth.Settings.modal.revokeApp.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.revokeAccess') }}
</div>
{{ t('components.auth.Settings.button.revokeAccess') }}
</template>
</dangerous-button>
</td>
@ -648,29 +638,20 @@ fetchOwnedApps()
>
{{ t('components.auth.Settings.button.edit') }}
</Link>
<Button
color="destructive"
<DangerousButton
:is-loading="isDeleting.has(app.client_id)"
class="tiny"
@confirm="deleteApp(app.client_id)"
:title="t('components.auth.Settings.modal.deleteApp.header', {app: app.name})"
>
{{ t('components.auth.Settings.button.remove') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.deleteApp.header', {app: app.name}) }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.auth.Settings.modal.deleteApp.content.warning') }}
</p>
{{ t('components.auth.Settings.modal.deleteApp.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.removeApp') }}
</div>
{{ t('components.auth.Settings.button.removeApp') }}
</template>
</Button>
</DangerousButton>
</td>
</tr>
</tbody>
@ -804,26 +785,18 @@ fetchOwnedApps()
/>
</div>
<dangerous-button
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']"
:is-loading="isDeletingAccount"
:disabled="!deleteAccountPassword || undefined"
:class="{danger: deleteAccountPassword}"
:action="deleteAccount"
:title="t('components.auth.Settings.modal.deleteAccount.header')"
>
{{ t('components.auth.Settings.button.deleteAccount') }}
<template #modal-header>
<p>
{{ t('components.auth.Settings.modal.deleteAccount.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('components.auth.Settings.modal.deleteAccount.content.warning') }}
</p>
</div>
{{ t('components.auth.Settings.modal.deleteAccount.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.auth.Settings.button.deleteAccountConfirm') }}
</div>
{{ t('components.auth.Settings.button.deleteAccountConfirm') }}
</template>
</dangerous-button>
</Layout>

View File

@ -6,6 +6,8 @@ import { computed, ref } from 'vue'
import { useStore } from '~/store'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import PasswordInput from '~/components/forms/PasswordInput.vue'
import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue'
@ -147,29 +149,20 @@ fetchToken()
:default-show="showToken"
/>
</div>
<Button
<DangerousButton
v-if="token"
destructive
:is-loading="isLoading"
:action="requestNewToken"
:title="t('components.auth.SubsonicTokenForm.modal.newPassword.header')"
>
{{ t('components.auth.SubsonicTokenForm.button.newPassword') }}
<template #modal-header>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.newPassword.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.newPassword.content.warning') }}
</p>
{{ t('components.auth.SubsonicTokenForm.modal.newPassword.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }}
</div>
{{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }}
</template>
</Button>
</DangerousButton>
<Button
primary
v-else
@ -178,30 +171,20 @@ fetchToken()
>
{{ t('components.auth.SubsonicTokenForm.button.confirmNewPassword') }}
</Button>
<Button
primary
<DangerousButton
v-if="token"
:is-loading="isLoading"
color="destructive"
:action="disable"
:title="t('components.auth.SubsonicTokenForm.modal.disableSubsonic.header')"
>
{{ t('components.auth.SubsonicTokenForm.button.disable') }}
<template #modal-header>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.content.warning') }}
</p>
{{ t('components.auth.SubsonicTokenForm.modal.disableSubsonic.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.auth.SubsonicTokenForm.button.confirmDisable') }}
</div>
{{ t('components.auth.SubsonicTokenForm.button.confirmDisable') }}
</template>
</Button>
</DangerousButton>
</template>
</form>
</template>

View File

@ -4,6 +4,8 @@ import type { BackendError } from '~/types'
import { ref, computed, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import DangerousButton from '~/components/common/DangerousButton.vue'
import axios from 'axios'
interface Action {
@ -203,33 +205,26 @@ const launchAction = async () => {
<div class="field">
<dangerous-button
v-if="selectAll || currentAction?.isDangerous"
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']"
:disabled="checked.length === 0 || undefined"
:isLoading="isLoading"
:confirm-color="currentAction?.confirmColor ?? 'success'"
:aria-label="labels.performAction"
@confirm="launchAction"
:title="t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount)"
>
{{ t('components.common.ActionTable.button.go') }}
<template #modal-header>
<p>
<span key="1">
{{ t('components.common.ActionTable.modal.performAction.header', { action: currentActionName }, affectedObjectsCount) }}
</span>
</p>
</template>
<template #modal-content>
<p>
<template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }}
</template>
<span v-else>
{{ t('components.common.ActionTable.modal.performAction.content.warning') }}
</span>
</p>
<template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }}
</template>
<span v-else>
{{ t('components.common.ActionTable.modal.performAction.content.warning') }}
</span>
</template>
<template #modal-confirm>
<div :aria-label="labels.performAction">
<span :aria-label="labels.performAction">
{{ t('components.common.ActionTable.button.launch') }}
</div>
</span>
</template>
</dangerous-button>
<button

View File

@ -2,27 +2,23 @@
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue'
import Button from '~/components/ui/Button.vue'
import Modal from '~/components/ui/Modal.vue'
interface Events {
(e: 'confirm'): void
}
interface Props {
action?: () => void
disabled?: boolean
confirmColor?: 'destructive' | 'primary'
}
// Note that properties such as [disabled] and 'destructive' | 'primary' are inherited.
const props = defineProps<{
title?: string
action?: () => void,
confirmColor?:'success' | 'danger'
}>()
const { t } = useI18n()
const emit = defineEmits<Events>()
const props = withDefaults(defineProps<Props>(), {
action: () => undefined, // -> Button.onClick prop
disabled: false, // -> Pure html/css. Just add attribute `disabled` and the button is inert.
confirmColor: 'destructive'
})
const showModal = ref(false)
@ -36,22 +32,16 @@ const confirm = () => {
<template>
<Button
destructive
:class="{ 'is-disabled': disabled }"
:disabled="disabled"
@click.prevent.stop="showModal = true"
>
<slot />
<SemanticModal
v-model:show="showModal"
:title="t('components.common.DangerousButton.header.confirm')"
class="small"
<Modal
destructive
v-model="showModal"
:title="title || t('components.common.DangerousButton.header.confirm')"
:cancel="t('components.common.DangerousButton.button.cancel')"
>
<h4 class="header">
<slot name="modal-header">
{{ t('components.common.DangerousButton.header.confirm') }}
</slot>
</h4>
<div class="scrolling content">
<div class="description">
<slot name="modal-content" />
@ -59,14 +49,7 @@ const confirm = () => {
</div>
<template #actions>
<Button
secondary
outline
@click="showModal = false"
>
{{ t('components.common.DangerousButton.button.cancel') }}
</Button>
<Button
:color="confirmColor"
v-bind="{[{success: 'primary', danger: 'destructive'}[confirmColor || 'success']]: true}"
@click="confirm"
>
<slot name="modal-confirm">
@ -74,6 +57,6 @@ const confirm = () => {
</slot>
</Button>
</template>
</SemanticModal>
</Modal>
</Button>
</template>

View File

@ -334,26 +334,16 @@ const alertProps = computed(() => {
<!--TODO: Make Dangerous Button hand through isLoading prop -->
<dangerous-button
v-if="canDelete"
:class="['ui', {loading: isLoading}, 'basic danger button']"
:isLoading="isLoading"
:action="remove"
:title="t('components.library.EditCard.modal.delete.header')"
>
{{ t('components.library.EditCard.button.delete') }}
<template #modal-header>
<p>
{{ t('components.library.EditCard.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('components.library.EditCard.modal.content.warning') }}
</p>
</div>
{{ t('components.library.EditCard.modal.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('components.library.EditCard.button.delete') }}
</p>
{{ t('components.library.EditCard.button.delete') }}
</template>
</dangerous-button>
</template>

View File

@ -7,6 +7,8 @@ import { useI18n } from 'vue-i18n'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Events {
(e: 'save', data: InstancePolicy): void
(e: 'delete'): void
@ -238,24 +240,16 @@ const remove = async () => {
</button>
<dangerous-button
v-if="object"
class="ui right floated basic danger button"
style="float: right;"
@confirm="remove"
:title="t('components.manage.moderation.InstancePolicyForm.modal.delete.header')"
>
{{ t('components.manage.moderation.InstancePolicyForm.button.delete') }}
<template #modal-header>
<p>
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }}
</p>
{{ t('components.manage.moderation.InstancePolicyForm.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }}
</div>
{{ t('components.manage.moderation.InstancePolicyForm.button.confirm') }}
</template>
</dangerous-button>
</form>

View File

@ -5,11 +5,12 @@ import { useMarkdownRaw } from '~/composables/useMarkdown'
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Events {
(e: 'deleted', uuid: string): void
}
@ -64,27 +65,19 @@ const remove = async (note: Note) => {
</div>
<div class="meta">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic borderless mini button']"
:is-loading="isLoading"
low-height
@confirm="remove(note)"
icon="bi-trash"
:title="t('components.manage.moderation.NotesThread.modal.delete.header')"
>
<i class="trash icon" />
{{ t('components.manage.moderation.NotesThread.button.delete') }}
<template #modal-header>
<p>
{{ t('components.manage.moderation.NotesThread.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }}
</p>
</div>
{{ t('components.manage.moderation.NotesThread.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('components.manage.moderation.NotesThread.button.delete') }}
</p>
{{ t('components.manage.moderation.NotesThread.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -11,6 +11,7 @@ import InstancePolicyModal from '~/components/manage/moderation/InstancePolicyMo
import ReportCategoryDropdown from '~/components/moderation/ReportCategoryDropdown.vue'
import NotesThread from '~/components/manage/moderation/NotesThread.vue'
import NoteForm from '~/components/manage/moderation/NoteForm.vue'
import DangerousButton from '~/components/common/DangerousButton.vue'
import useReportConfigs from '~/composables/moderation/useReportConfigs'
import useErrorHandler from '~/composables/useErrorHandler'
@ -446,21 +447,17 @@ const handleRemovedNote = (uuid: string) => {
>
<dangerous-button
v-if="action.dangerous && action.show(obj)"
:class="['ui', {loading: isLoading}, 'button']"
:is-loading="isLoading"
:action="action.handler"
:title="action.modalHeader"
:icon="`${action.iconColor} ${action.icon}`"
>
<i :class="[action.iconColor, action.icon, 'icon']" />&nbsp;
{{ action.label }}
<template #modal-header>
<p>{{ action.modalHeader }}</p>
</template>
<template #modal-content>
<div>
<p>{{ action.modalContent }}</p>
</div>
{{ action.modalContent }}
</template>
<template #modal-confirm>
<p>{{ action.modalConfirmLabel }}</p>
{{ action.modalConfirmLabel }}
</template>
</dangerous-button>
</template>

View File

@ -13,7 +13,9 @@ import { useStore } from '~/store'
import draggable from 'vuedraggable'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import PlaylistForm from '~/components/playlists/Form.vue'
import Layout from '~/components/ui/Layout.vue'
import Button from '~/components/ui/Button.vue'
import Alert from '~/components/ui/Alert.vue'
@ -237,25 +239,17 @@ const insertMany = async (insertedTracks: number[], allowDuplicates: boolean) =>
<dangerous-button
:disabled="tracks.length === 0"
class="ui labeled right floated danger icon button"
icon="bi-eraser-fill"
style="float: right;"
:action="clearPlaylist"
:title="t('components.playlists.Editor.modal.clearPlaylist.header', { playlist: playlist?.name })"
>
<i class="eraser icon" />
{{ t('components.playlists.Editor.button.clear') }}
<template #modal-header>
<p>
{{ t('components.playlists.Editor.modal.clearPlaylist.header', {playlist: playlist?.name}) }}
</p>
</template>
<template #modal-content>
<p>
{{ t('components.playlists.Editor.modal.clearPlaylist.content.warning') }}
</p>
{{ t('components.playlists.Editor.modal.clearPlaylist.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('components.playlists.Editor.button.clear') }}
</div>
{{ t('components.playlists.Editor.button.clear') }}
</template>
</dangerous-button>
<div class="ui hidden divider" />

View File

@ -12,6 +12,8 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props {
id: number
}
@ -172,26 +174,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.ChannelDetail.modal.delete.header')"
>
{{ t('views.admin.ChannelDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.ChannelDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.ChannelDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.ChannelDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.ChannelDetail.button.delete') }}
</p>
{{ t('views.admin.ChannelDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -7,6 +7,7 @@ import { useStore } from '~/store'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue'
@ -193,26 +194,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.AlbumDetail.modal.delete.header')"
>
{{ t('views.admin.library.AlbumDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.AlbumDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.AlbumDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.AlbumDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.AlbumDetail.button.delete') }}
</p>
{{ t('views.admin.library.AlbumDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -7,6 +7,7 @@ import { useStore } from '~/store'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import FetchButton from '~/components/federation/FetchButton.vue'
import TagsList from '~/components/tags/List.vue'
@ -181,37 +182,27 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</button>
</div>
<div class="ui buttons">
<router-link
<Link solid secondary
v-if="object.is_local"
:to="{name: 'library.artists.edit', params: {id: object.id }}"
class="ui labeled icon button"
icon="bi-pencil-fill"
>
<i class="edit icon" />
{{ t('views.admin.library.ArtistDetail.button.edit') }}
</router-link>
</Link>
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.ArtistDetail.modal.delete.header')"
>
{{ t('views.admin.library.ArtistDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.ArtistDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.ArtistDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.ArtistDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.ArtistDetail.button.delete') }}
</p>
{{ t('views.admin.library.ArtistDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -13,6 +13,8 @@ import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler'
import useLogger from '~/composables/useLogger'
import DangerousButton from '~/components/common/DangerousButton.vue'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Props {
@ -168,26 +170,16 @@ const updateObj = async (attr: string) => {
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.LibraryDetail.modal.delete.header')"
>
{{ t('views.admin.library.LibraryDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.LibraryDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.LibraryDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.LibraryDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.LibraryDetail.button.delete') }}
</p>
{{ t('views.admin.library.LibraryDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -9,6 +9,8 @@ import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props {
id: number
}
@ -105,26 +107,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.TagDetail.modal.delete.header')"
>
{{ t('views.admin.library.TagDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.TagDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.TagDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.TagDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.TagDetail.button.delete') }}
</p>
{{ t('views.admin.library.TagDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -12,6 +12,8 @@ import TagsList from '~/components/tags/List.vue'
import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props {
id: number
}
@ -193,26 +195,16 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.TrackDetail.modal.delete.header')"
>
{{ t('views.admin.library.TrackDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.TrackDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.TrackDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.TrackDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.TrackDetail.button.delete') }}
</p>
{{ t('views.admin.library.TrackDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -15,6 +15,8 @@ import ImportStatusModal from '~/components/library/ImportStatusModal.vue'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import useErrorHandler from '~/composables/useErrorHandler'
import DangerousButton from '~/components/common/DangerousButton.vue'
interface Props {
id: number
}
@ -155,26 +157,16 @@ const showUploadDetailModal = ref(false)
</div>
<div class="ui buttons">
<dangerous-button
:class="['ui', {loading: isLoading}, 'basic danger button']"
:is-loading="isLoading"
:action="remove"
:title="t('views.admin.library.UploadDetail.modal.delete.header')"
>
{{ t('views.admin.library.UploadDetail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.admin.library.UploadDetail.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.admin.library.UploadDetail.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.admin.library.UploadDetail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.admin.library.UploadDetail.button.delete') }}
</p>
{{ t('views.admin.library.UploadDetail.button.delete') }}
</template>
</dangerous-button>
</div>

View File

@ -11,6 +11,7 @@ import axios from 'axios'
import useErrorHandler from '~/composables/useErrorHandler'
import useReport from '~/composables/moderation/useReport'
import DangerousButton from '~/components/common/DangerousButton.vue'
import SubscribeButton from '~/components/channels/SubscribeButton.vue'
import ChannelForm from '~/components/audio/ChannelForm.vue'
import EmbedWizard from '~/components/audio/EmbedWizard.vue'
@ -305,22 +306,13 @@ const updateSubscriptionCount = (delta: number) => {
</PopoverItem>
<dangerous-button
v-if="object"
:class="['ui', {loading: isLoading}, 'item']"
:title="t('views.channels.DetailBase.button.delete')"
:is-loading="isLoading"
icon="bi-trash"
@confirm="remove()"
>
<i class="bi bi-trash" />
{{ t('views.channels.DetailBase.button.delete') }}
<template #modal-header>
<p>
{{ t('views.channels.DetailBase.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.channels.DetailBase.modal.delete.content.warning') }}
</p>
</div>
{{ t('views.channels.DetailBase.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>

View File

@ -9,6 +9,8 @@ import axios from 'axios'
import useSharedLabels from '~/composables/locale/useSharedLabels'
import DangerousButton from '~/components/common/DangerousButton.vue'
const PRIVACY_LEVELS = ['me', 'instance', 'everyone'] as PrivacyLevel[]
interface Events {
@ -169,25 +171,16 @@ const remove = async () => {
</button>
<dangerous-button
v-if="library"
type="button"
class="ui right floated basic danger button"
style="float: right;"
@confirm="remove"
:title="t('views.content.libraries.Form.modal.delete.header')"
>
{{ t('views.content.libraries.Form.button.delete') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Form.modal.delete.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('views.content.libraries.Form.modal.delete.content.warning') }}
</p>
{{ t('views.content.libraries.Form.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('views.content.libraries.Form.button.confirm') }}
</div>
{{ t('views.content.libraries.Form.button.confirm') }}
</template>
</dangerous-button>
</form>

View File

@ -10,6 +10,8 @@ import useErrorHandler from '~/composables/useErrorHandler'
import axios from 'axios'
import { useI18n } from 'vue-i18n'
import DangerousButton from '~/components/common/DangerousButton.vue'
const { t } = useI18n()
const quotaStatus = ref()
@ -111,24 +113,16 @@ const purgeErroredFiles = () => purge('errored')
</router-link>
<dangerous-button
class="ui basic tiny button"
low-height
:action="purgePendingFiles"
:title="t('views.content.libraries.Quota.modal.purgePending.header')"
>
{{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgePending.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgePending.content.description') }}
</p>
{{ t('views.content.libraries.Quota.modal.purgePending.content.description') }}
</template>
<template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }}
</div>
{{ t('views.content.libraries.Quota.button.purge') }}
</template>
</dangerous-button>
</div>
@ -153,24 +147,16 @@ const purgeErroredFiles = () => purge('errored')
{{ t('views.content.libraries.Quota.link.viewFiles') }}
</router-link>
<dangerous-button
class="ui basic tiny button"
low-height
:action="purgeSkippedFiles"
:title="t('views.content.libraries.Quota.modal.purgeSkipped.header')"
>
{{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgeSkipped.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgeSkipped.content.description') }}
</p>
{{ t('views.content.libraries.Quota.modal.purgeSkipped.content.description') }}
</template>
<template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }}
</div>
{{ t('views.content.libraries.Quota.button.purge') }}
</template>
</dangerous-button>
</div>
@ -195,24 +181,16 @@ const purgeErroredFiles = () => purge('errored')
{{ t('views.content.libraries.Quota.link.viewFiles') }}
</router-link>
<dangerous-button
class="ui basic tiny button"
low-height
:action="purgeErroredFiles"
:title="t('views.content.libraries.Quota.modal.purgeErrored.header')"
>
{{ t('views.content.libraries.Quota.button.purge') }}
<template #modal-header>
<p>
{{ t('views.content.libraries.Quota.modal.purgeErrored.header') }}
</p>
</template>
<template #modal-content>
<p>
{{ t('views.content.libraries.Quota.modal.purgeErrored.content.description') }}
</p>
{{ t('views.content.libraries.Quota.modal.purgeErrored.content.description') }}
</template>
<template #modal-confirm>
<div>
{{ t('views.content.libraries.Quota.button.purge') }}
</div>
{{ t('views.content.libraries.Quota.button.purge') }}
</template>
</dangerous-button>
</div>

View File

@ -8,6 +8,7 @@ import { useStore } from '~/store'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import RadioButton from '~/components/radios/Button.vue'
import Card from '~/components/ui/Card.vue'
import OptionsButton from '~/components/ui/button/Options.vue'
@ -321,26 +322,15 @@ const isOpen = ref(false)
</template>
<template v-else-if="library.follow.approved">
<dangerous-button
:class="['ui', 'button']"
:action="unfollow"
:title="t('views.content.remote.Card.modal.unfollow.header')"
>
{{ t('views.content.remote.Card.button.unfollow') }}
<template #modal-header>
<p>
{{ t('views.content.remote.Card.modal.unfollow.header') }}
</p>
</template>
<template #modal-content>
<div>
<p>
{{ t('views.content.remote.Card.modal.unfollow.content.warning') }}
</p>
</div>
{{ t('views.content.remote.Card.modal.unfollow.content.warning') }}
</template>
<template #modal-confirm>
<div>
{{ t('views.content.remote.Card.button.unfollow') }}
</div>
{{ t('views.content.remote.Card.button.unfollow') }}
</template>
</dangerous-button>
</template>

View File

@ -8,12 +8,13 @@ import { useStore } from '~/store'
import axios from 'axios'
import DangerousButton from '~/components/common/DangerousButton.vue'
import TrackTable from '~/components/audio/track/Table.vue'
import RadioButton from '~/components/radios/Button.vue'
import Layout from '~/components/ui/Layout.vue'
import Header from '~/components/ui/Header.vue'
import Section from '~/components/ui/Section.vue'
import Spacer from '~/components/ui/Spacer.vue'
import Pagination from '~/components/ui/Pagination.vue'
import Alert from '~/components/ui/Alert.vue'
import Button from '~/components/ui/Button.vue'
@ -98,24 +99,16 @@ const deleteRadio = async () => {
{{ t('views.radios.Detail.button.edit') }}
</Button>
<dangerous-button
class="ui labeled danger icon button"
:action="deleteRadio"
:title="t('views.radios.Detail.modal.delete.header', {radio: radio.name})"
icon="bi-trash"
>
<i class="bi bi-trash" /> {{ t('views.radios.Detail.button.delete') }}
<template #modal-header>
<p>
{{ t('views.radios.Detail.modal.delete.header', {radio: radio.name}) }}
</p>
</template>
{{ t('views.radios.Detail.button.delete') }}
<template #modal-content>
<p>
{{ t('views.radios.Detail.modal.delete.content.warning') }}
</p>
{{ t('views.radios.Detail.modal.delete.content.warning') }}
</template>
<template #modal-confirm>
<p>
{{ t('views.radios.Detail.button.confirm') }}
</p>
{{ t('views.radios.Detail.button.confirm') }}
</template>
</dangerous-button>
</template>
@ -127,9 +120,8 @@ const deleteRadio = async () => {
<track-table :tracks="tracks" />
<Pagination
v-if="totalTracks > 25"
v-model:current="page"
:paginate-by="25"
:total="totalTracks"
v-model:page="page"
:pages="Math.ceil(totalTracks / 25)"
/>
</Section>
<Alert