Compare commits

...

2 commits

Author SHA1 Message Date
3a9df965c2
feat: implement Confirmation modal on kick and ban context menu actions
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
ci/woodpecker/pr/build-and-publish Pipeline was successful
2025-08-16 12:01:36 +02:00
78b7732411
feat: add Confirmation modal to prompt users to confirm or cancel dangerous actions 2025-08-16 11:59:48 +02:00
5 changed files with 120 additions and 7 deletions

View file

@ -59,6 +59,9 @@
<MessageMedia v-if="mediaLinks.length" :links="mediaLinks"/> <MessageMedia v-if="mediaLinks.length" :links="mediaLinks"/>
</div> </div>
</div> </div>
<ModalConfirmation v-if="confirmationModal && confirmationModal.show" :action-name="confirmationModal.actionName"
:display-name="getDisplayName(props.message.member)" :callback="confirmationModal.callback"
:onClose="resetConfirmationModal" :onCancel="resetConfirmationModal" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -67,7 +70,7 @@ import { parse } from 'marked';
import type { MessageProps } from '~/types/props'; import type { MessageProps } from '~/types/props';
import MessageMedia from './MessageMedia.vue'; import MessageMedia from './MessageMedia.vue';
import MessageReply from './UserInterface/MessageReply.vue'; import MessageReply from './UserInterface/MessageReply.vue';
import type { ContextMenuInterface, ContextMenuItem } from '~/types/interfaces'; import type { ContextMenuInterface, ContextMenuItem, IConfirmationModal } from '~/types/interfaces';
const { getDisplayName } = useProfile() const { getDisplayName } = useProfile()
const { getUser } = useAuth() const { getUser } = useAuth()
@ -88,6 +91,10 @@ const date = uuidToDate(props.message.uuid);
const currentDate: Date = new Date() const currentDate: Date = new Date()
const confirmationModal = ref<IConfirmationModal>();
const memberMenuItems = ref<ContextMenuItem[]>([]);
console.log("[MSG] message to render:", props.message); console.log("[MSG] message to render:", props.message);
console.log("author:", props.message.member); console.log("author:", props.message.member);
console.log("[MSG] reply message:", props.replyMessage); console.log("[MSG] reply message:", props.replyMessage);
@ -155,6 +162,9 @@ onMounted(async () => {
}; };
console.log("media links:", mediaLinks); console.log("media links:", mediaLinks);
console.log("[CONFIRM] modal:", confirmationModal.value);
memberMenuItems.value = await createMemberContextMenuItems(props.message.member, route.params.serverId as string, confirmationModal);
}); });
//function toggleTooltip(e: Event) { //function toggleTooltip(e: Event) {
@ -176,8 +186,6 @@ if (props.message.member.user.uuid == me!.uuid /* || check message delete permis
messageMenuItems.splice(Math.min(2, messageMenuItems.length), 0, { name: "Delete (WIP)", icon: "lucide:trash", type: "danger", callback: () => {} }); messageMenuItems.splice(Math.min(2, messageMenuItems.length), 0, { name: "Delete (WIP)", icon: "lucide:trash", type: "danger", callback: () => {} });
} }
const memberMenuItems = await createMemberContextMenuItems(props.message.member, route.params.serverId as string);
function getDayDifference(date1: Date, date2: Date) { function getDayDifference(date1: Date, date2: Date) {
const midnight1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); const midnight1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
const midnight2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); const midnight2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
@ -189,6 +197,13 @@ function getDayDifference(date1: Date, date2: Date) {
return Math.round(dayDifference); return Math.round(dayDifference);
} }
function resetConfirmationModal() {
console.log("[CONFIRM] resetting");
if (confirmationModal) {
confirmationModal.value = { show: false, actionName: "", callback: () => {} };
}
}
</script> </script>
<style scoped> <style scoped>

View file

@ -25,6 +25,11 @@ onMounted(() => {
if (props.onCancel) dialog.value.addEventListener("cancel", props.onCancel); if (props.onCancel) dialog.value.addEventListener("cancel", props.onCancel);
} }
}); });
function close() { dialog.value?.close() }
defineExpose({ close });
</script> </script>
<style scoped> <style scoped>

View file

@ -0,0 +1,54 @@
<template>
<ModalBase ref="modal" class="confirmation-modal" :obscure="true" :onClose="props.onClose" :onCancel="props.onCancel">
<div class="confirmation-modal-body">
<div>
<h1 class="confirmation-modal-message">Are you sure you would like to {{ props.actionName.toLowerCase() }} {{ props.displayName }}?</h1>
</div>
<div class="confirmation-modal-buttons">
<Button :variant="'normal'" :text="'Cancel'" @click="closeModal()" />
<Button :variant="'scary'" :text="'Confirm'" :callback="props.callback()" />
</div>
</div>
</ModalBase>
</template>
<script lang="ts" setup>
import Button from '../UserInterface/Button.vue';
const props = defineProps<{ actionName: string, displayName?: string, callback: CallableFunction, onClose: () => void, onCancel: () => void }>();
const modal = ref<{ close: () => void }>();
function closeModal() {
const test = document.getElementsByClassName("confirmation-modal")[0];
console.log("[CONFIRM] modal rah:", test);
if (modal.value) {
modal.value.close();
}
}
</script>
<style>
.confirmation-modal-body {
display: flex;
flex-direction: column;
background-color: var(--modal-background-color);
align-items: center;
text-align: center;
padding: 1rem;
color: var(--text-color);
border: .1rem solid var(--primary-color);
}
.confirmation-modal-buttons {
display: flex;
gap: 1rem;
}
.confirmation-modal-message {
font-size: 1.5em;
}
</style>

View file

@ -140,3 +140,8 @@ export interface NavbarOptions {
isDirectMessages?: boolean isDirectMessages?: boolean
} }
export interface IConfirmationModal {
show: boolean,
actionName: string,
callback: CallableFunction
}

View file

@ -1,7 +1,7 @@
import { Permission } from "~/types/enums"; import { Permission } from "~/types/enums";
import type { ContextMenuItem, GuildMemberResponse } from "~/types/interfaces"; import type { ContextMenuItem, GuildMemberResponse, IConfirmationModal } from "~/types/interfaces";
export default async (member: GuildMemberResponse, guildId: string) => { export default async (member: GuildMemberResponse, guildId: string, confirmationModal?: Ref<IConfirmationModal | undefined>) => {
const menuItems: ContextMenuItem[] = []; const menuItems: ContextMenuItem[] = [];
const { fetchMeMember } = useApi(); const { fetchMeMember } = useApi();
@ -19,12 +19,46 @@ export default async (member: GuildMemberResponse, guildId: string) => {
console.log("[MENUITEM] member is not me"); console.log("[MENUITEM] member is not me");
if (hasPermission(me.value, Permission.KickMember)) { if (hasPermission(me.value, Permission.KickMember)) {
console.log("[MENUITEM] has kick member permission"); console.log("[MENUITEM] has kick member permission");
menuItems.splice(Math.min(3, menuItems.length), 0, { name: "Kick", icon: "lucide:user-x", type: "danger", callback: async () => await kickMember(member.uuid) }); menuItems.splice(Math.min(3, menuItems.length), 0, {
name: "Kick",
icon: "lucide:user-x",
type: "danger",
callback: async () => {
if (confirmationModal) {
console.log("[CONFIRM] HEYO THERE!!");
confirmationModal.value = {
actionName: "kick",
callback: async () => await kickMember(member.uuid),
show: true
}
} else {
console.log("[CONFIRM] no modal");
await kickMember(member.uuid)
}
}
});
} }
if (hasPermission(me.value, Permission.BanMember)) { if (hasPermission(me.value, Permission.BanMember)) {
console.log("[MENUITEM] has ban permission"); console.log("[MENUITEM] has ban permission");
menuItems.splice(Math.min(4, menuItems.length), 0, { name: "Ban (WIP)", icon: "lucide:ban", type: "danger", callback: async () => await banMember(guildId, member.uuid) }); menuItems.splice(Math.min(4, menuItems.length), 0, {
name: "Ban (WIP)",
icon: "lucide:ban",
type: "danger",
callback: async () => {
if (confirmationModal) {
console.log("[CONFIRM] HEYO THERE!! 2");
confirmationModal.value = {
actionName: "ban",
callback: async () => await banMember(member.guild_uuid, member.uuid),
show: true
}
} else {
console.log("[CONFIRM] no modal 2");
await banMember(member.guild_uuid, member.uuid)
}
}
});
} }
} }