mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 12:42:15 +01:00
add linter, prettier and typescript support for jetstream (#1)
* add linter, prettier and typescript support for jetstream * add github actions for lint, build and typecheck * add composer install to build action * fix composer lock * add ext-intl and fixed php version * fix intl php extension install * add tip php extension to github npm build action * fix php version to 8.3.1 * change github php base action * fix primary button default type * updated typescript types from Team to Organization --------- Co-authored-by: Gregor Vostrak <gregorvostrak@Gregors-MacBook-Pro.local>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { router, useForm, usePage } from '@inertiajs/vue3';
|
||||
import ActionMessage from '@/Components/ActionMessage.vue';
|
||||
import ActionSection from '@/Components/ActionSection.vue';
|
||||
@@ -13,57 +13,79 @@ import PrimaryButton from '@/Components/PrimaryButton.vue';
|
||||
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||
import SectionBorder from '@/Components/SectionBorder.vue';
|
||||
import TextInput from '@/Components/TextInput.vue';
|
||||
import type {
|
||||
Organization,
|
||||
OrganizationInvitation,
|
||||
User,
|
||||
} from '@/types/models';
|
||||
import type { Membership, Permissions, Role } from '@/types/jetstream';
|
||||
|
||||
const props = defineProps({
|
||||
team: Object,
|
||||
availableRoles: Array,
|
||||
userPermissions: Object,
|
||||
type UserWithMembership = User & { membership: Membership };
|
||||
|
||||
const props = defineProps<{
|
||||
team: Organization;
|
||||
availableRoles: Role[];
|
||||
userPermissions: Permissions;
|
||||
}>();
|
||||
|
||||
const users = computed(() => {
|
||||
return props.team.users as Array<UserWithMembership>;
|
||||
});
|
||||
|
||||
const page = usePage();
|
||||
const page = usePage<{
|
||||
auth: {
|
||||
user: User;
|
||||
};
|
||||
}>();
|
||||
|
||||
const addTeamMemberForm = useForm({
|
||||
email: '',
|
||||
role: null,
|
||||
role: null as string | null,
|
||||
});
|
||||
|
||||
const updateRoleForm = useForm({
|
||||
role: null,
|
||||
role: null as string | null,
|
||||
});
|
||||
|
||||
const leaveTeamForm = useForm({});
|
||||
const removeTeamMemberForm = useForm({});
|
||||
|
||||
const currentlyManagingRole = ref(false);
|
||||
const managingRoleFor = ref(null);
|
||||
const managingRoleFor = ref<User | null>(null);
|
||||
const confirmingLeavingTeam = ref(false);
|
||||
const teamMemberBeingRemoved = ref(null);
|
||||
const teamMemberBeingRemoved = ref<User | null>(null);
|
||||
|
||||
const addTeamMember = () => {
|
||||
addTeamMemberForm.post(route('team-members.store', props.team), {
|
||||
addTeamMemberForm.post(route('team-members.store', props.team.id), {
|
||||
errorBag: 'addTeamMember',
|
||||
preserveScroll: true,
|
||||
onSuccess: () => addTeamMemberForm.reset(),
|
||||
});
|
||||
};
|
||||
|
||||
const cancelTeamInvitation = (invitation) => {
|
||||
router.delete(route('team-invitations.destroy', invitation), {
|
||||
const cancelTeamInvitation = (invitation: OrganizationInvitation) => {
|
||||
router.delete(route('team-invitations.destroy', invitation.id), {
|
||||
preserveScroll: true,
|
||||
});
|
||||
};
|
||||
|
||||
const manageRole = (teamMember) => {
|
||||
const manageRole = (teamMember: User & { membership: Membership }) => {
|
||||
managingRoleFor.value = teamMember;
|
||||
updateRoleForm.role = teamMember.membership.role;
|
||||
currentlyManagingRole.value = true;
|
||||
};
|
||||
|
||||
const updateRole = () => {
|
||||
updateRoleForm.put(route('team-members.update', [props.team, managingRoleFor.value]), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => currentlyManagingRole.value = false,
|
||||
});
|
||||
updateRoleForm.put(
|
||||
route('team-members.update', {
|
||||
team: props.team.id,
|
||||
user: managingRoleFor.value?.id,
|
||||
}),
|
||||
{
|
||||
preserveScroll: true,
|
||||
onSuccess: () => (currentlyManagingRole.value = false),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const confirmLeavingTeam = () => {
|
||||
@@ -71,24 +93,32 @@ const confirmLeavingTeam = () => {
|
||||
};
|
||||
|
||||
const leaveTeam = () => {
|
||||
leaveTeamForm.delete(route('team-members.destroy', [props.team, page.props.auth.user]));
|
||||
leaveTeamForm.delete(
|
||||
route('team-members.destroy', [props.team.id, page.props.auth.user.id])
|
||||
);
|
||||
};
|
||||
|
||||
const confirmTeamMemberRemoval = (teamMember) => {
|
||||
const confirmTeamMemberRemoval = (teamMember: User) => {
|
||||
teamMemberBeingRemoved.value = teamMember;
|
||||
};
|
||||
|
||||
const removeTeamMember = () => {
|
||||
removeTeamMemberForm.delete(route('team-members.destroy', [props.team, teamMemberBeingRemoved.value]), {
|
||||
errorBag: 'removeTeamMember',
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
onSuccess: () => teamMemberBeingRemoved.value = null,
|
||||
});
|
||||
removeTeamMemberForm.delete(
|
||||
route('team-members.destroy', {
|
||||
team: props.team.id,
|
||||
user: teamMemberBeingRemoved.value?.id,
|
||||
}),
|
||||
{
|
||||
errorBag: 'removeTeamMember',
|
||||
preserveScroll: true,
|
||||
preserveState: true,
|
||||
onSuccess: () => (teamMemberBeingRemoved.value = null),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const displayableRole = (role) => {
|
||||
return props.availableRoles.find(r => r.key === role).name;
|
||||
const displayableRole = (role: string) => {
|
||||
return props.availableRoles.find((r) => r.key === role)?.name;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -99,18 +129,19 @@ const displayableRole = (role) => {
|
||||
|
||||
<!-- Add Organization Member -->
|
||||
<FormSection @submitted="addTeamMember">
|
||||
<template #title>
|
||||
Add Team Member
|
||||
</template>
|
||||
<template #title> Add Team Member</template>
|
||||
|
||||
<template #description>
|
||||
Add a new team member to your team, allowing them to collaborate with you.
|
||||
Add a new team member to your team, allowing them to
|
||||
collaborate with you.
|
||||
</template>
|
||||
|
||||
<template #form>
|
||||
<div class="col-span-6">
|
||||
<div class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
|
||||
Please provide the email address of the person you would like to add to this team.
|
||||
<div
|
||||
class="max-w-xl text-sm text-gray-600 dark:text-gray-400">
|
||||
Please provide the email address of the person you
|
||||
would like to add to this team.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -121,39 +152,75 @@ const displayableRole = (role) => {
|
||||
id="email"
|
||||
v-model="addTeamMemberForm.email"
|
||||
type="email"
|
||||
class="mt-1 block w-full"
|
||||
/>
|
||||
<InputError :message="addTeamMemberForm.errors.email" class="mt-2" />
|
||||
class="mt-1 block w-full" />
|
||||
<InputError
|
||||
:message="addTeamMemberForm.errors.email"
|
||||
class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Role -->
|
||||
<div v-if="availableRoles.length > 0" class="col-span-6 lg:col-span-4">
|
||||
<div
|
||||
v-if="availableRoles.length > 0"
|
||||
class="col-span-6 lg:col-span-4">
|
||||
<InputLabel for="roles" value="Role" />
|
||||
<InputError :message="addTeamMemberForm.errors.role" class="mt-2" />
|
||||
<InputError
|
||||
:message="addTeamMemberForm.errors.role"
|
||||
class="mt-2" />
|
||||
|
||||
<div class="relative z-0 mt-1 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer">
|
||||
<div
|
||||
class="relative z-0 mt-1 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer">
|
||||
<button
|
||||
v-for="(role, i) in availableRoles"
|
||||
:key="role.key"
|
||||
type="button"
|
||||
class="relative px-4 py-3 inline-flex w-full rounded-lg focus:z-10 focus:outline-none focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600"
|
||||
:class="{'border-t border-gray-200 dark:border-gray-700 focus:border-none rounded-t-none': i > 0, 'rounded-b-none': i != Object.keys(availableRoles).length - 1}"
|
||||
@click="addTeamMemberForm.role = role.key"
|
||||
>
|
||||
<div :class="{'opacity-50': addTeamMemberForm.role && addTeamMemberForm.role != role.key}">
|
||||
:class="{
|
||||
'border-t border-gray-200 dark:border-gray-700 focus:border-none rounded-t-none':
|
||||
i > 0,
|
||||
'rounded-b-none':
|
||||
i !=
|
||||
Object.keys(availableRoles).length - 1,
|
||||
}"
|
||||
@click="addTeamMemberForm.role = role.key">
|
||||
<div
|
||||
:class="{
|
||||
'opacity-50':
|
||||
addTeamMemberForm.role &&
|
||||
addTeamMemberForm.role != role.key,
|
||||
}">
|
||||
<!-- Role Name -->
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400" :class="{'font-semibold': addTeamMemberForm.role == role.key}">
|
||||
<div
|
||||
class="text-sm text-gray-600 dark:text-gray-400"
|
||||
:class="{
|
||||
'font-semibold':
|
||||
addTeamMemberForm.role ==
|
||||
role.key,
|
||||
}">
|
||||
{{ role.name }}
|
||||
</div>
|
||||
|
||||
<svg v-if="addTeamMemberForm.role == role.key" class="ms-2 h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
v-if="
|
||||
addTeamMemberForm.role ==
|
||||
role.key
|
||||
"
|
||||
class="ms-2 h-5 w-5 text-green-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Role Description -->
|
||||
<div class="mt-2 text-xs text-gray-600 dark:text-gray-400 text-start">
|
||||
<div
|
||||
class="mt-2 text-xs text-gray-600 dark:text-gray-400 text-start">
|
||||
{{ role.description }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,34 +230,45 @@ const displayableRole = (role) => {
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<ActionMessage :on="addTeamMemberForm.recentlySuccessful" class="me-3">
|
||||
<ActionMessage
|
||||
:on="addTeamMemberForm.recentlySuccessful"
|
||||
class="me-3">
|
||||
Added.
|
||||
</ActionMessage>
|
||||
|
||||
<PrimaryButton :class="{ 'opacity-25': addTeamMemberForm.processing }" :disabled="addTeamMemberForm.processing">
|
||||
<PrimaryButton
|
||||
:class="{ 'opacity-25': addTeamMemberForm.processing }"
|
||||
:disabled="addTeamMemberForm.processing">
|
||||
Add
|
||||
</PrimaryButton>
|
||||
</template>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
||||
<div v-if="team.team_invitations.length > 0 && userPermissions.canAddTeamMembers">
|
||||
<div
|
||||
v-if="
|
||||
team.team_invitations.length > 0 &&
|
||||
userPermissions.canAddTeamMembers
|
||||
">
|
||||
<SectionBorder />
|
||||
|
||||
<!-- Organization Member Invitations -->
|
||||
<ActionSection class="mt-10 sm:mt-0">
|
||||
<template #title>
|
||||
Pending Team Invitations
|
||||
</template>
|
||||
<template #title> Pending Team Invitations</template>
|
||||
|
||||
<template #description>
|
||||
These people have been invited to your team and have been sent an invitation email. They may join the team by accepting the email invitation.
|
||||
These people have been invited to your team and have been
|
||||
sent an invitation email. They may join the team by
|
||||
accepting the email invitation.
|
||||
</template>
|
||||
|
||||
<!-- Pending Organization Member Invitation List -->
|
||||
<template #content>
|
||||
<div class="space-y-6">
|
||||
<div v-for="invitation in team.team_invitations" :key="invitation.id" class="flex items-center justify-between">
|
||||
<div
|
||||
v-for="invitation in team.team_invitations"
|
||||
:key="invitation.id"
|
||||
class="flex items-center justify-between">
|
||||
<div class="text-gray-600 dark:text-gray-400">
|
||||
{{ invitation.email }}
|
||||
</div>
|
||||
@@ -200,8 +278,7 @@ const displayableRole = (role) => {
|
||||
<button
|
||||
v-if="userPermissions.canRemoveTeamMembers"
|
||||
class="cursor-pointer ms-6 text-sm text-red-500 focus:outline-none"
|
||||
@click="cancelTeamInvitation(invitation)"
|
||||
>
|
||||
@click="cancelTeamInvitation(invitation)">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
@@ -211,14 +288,12 @@ const displayableRole = (role) => {
|
||||
</ActionSection>
|
||||
</div>
|
||||
|
||||
<div v-if="team.users.length > 0">
|
||||
<div v-if="users.length > 0">
|
||||
<SectionBorder />
|
||||
|
||||
<!-- Manage Organization Members -->
|
||||
<ActionSection class="mt-10 sm:mt-0">
|
||||
<template #title>
|
||||
Team Members
|
||||
</template>
|
||||
<template #title> Team Members</template>
|
||||
|
||||
<template #description>
|
||||
All of the people that are part of this team.
|
||||
@@ -227,9 +302,15 @@ const displayableRole = (role) => {
|
||||
<!-- Organization Member List -->
|
||||
<template #content>
|
||||
<div class="space-y-6">
|
||||
<div v-for="user in team.users" :key="user.id" class="flex items-center justify-between">
|
||||
<div
|
||||
v-for="user in users"
|
||||
:key="user.id"
|
||||
class="flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<img class="w-8 h-8 rounded-full object-cover" :src="user.profile_photo_url" :alt="user.name">
|
||||
<img
|
||||
class="w-8 h-8 rounded-full object-cover"
|
||||
:src="user.profile_photo_url"
|
||||
:alt="user.name" />
|
||||
<div class="ms-4 dark:text-white">
|
||||
{{ user.name }}
|
||||
</div>
|
||||
@@ -238,32 +319,36 @@ const displayableRole = (role) => {
|
||||
<div class="flex items-center">
|
||||
<!-- Manage Organization Member Role -->
|
||||
<button
|
||||
v-if="userPermissions.canUpdateTeamMembers && availableRoles.length"
|
||||
v-if="
|
||||
userPermissions.canUpdateTeamMembers &&
|
||||
availableRoles.length
|
||||
"
|
||||
class="ms-2 text-sm text-gray-400 underline"
|
||||
@click="manageRole(user)"
|
||||
>
|
||||
@click="manageRole(user)">
|
||||
{{ displayableRole(user.membership.role) }}
|
||||
</button>
|
||||
|
||||
<div v-else-if="availableRoles.length" class="ms-2 text-sm text-gray-400">
|
||||
<div
|
||||
v-else-if="availableRoles.length"
|
||||
class="ms-2 text-sm text-gray-400">
|
||||
{{ displayableRole(user.membership.role) }}
|
||||
</div>
|
||||
|
||||
<!-- Leave Organization -->
|
||||
<button
|
||||
v-if="$page.props.auth.user.id === user.id"
|
||||
v-if="page.props.auth.user.id === user.id"
|
||||
class="cursor-pointer ms-6 text-sm text-red-500"
|
||||
@click="confirmLeavingTeam"
|
||||
>
|
||||
@click="confirmLeavingTeam">
|
||||
Leave
|
||||
</button>
|
||||
|
||||
<!-- Remove Organization Member -->
|
||||
<button
|
||||
v-else-if="userPermissions.canRemoveTeamMembers"
|
||||
v-else-if="
|
||||
userPermissions.canRemoveTeamMembers
|
||||
"
|
||||
class="cursor-pointer ms-6 text-sm text-red-500"
|
||||
@click="confirmTeamMemberRemoval(user)"
|
||||
>
|
||||
@click="confirmTeamMemberRemoval(user)">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
@@ -274,36 +359,64 @@ const displayableRole = (role) => {
|
||||
</div>
|
||||
|
||||
<!-- Role Management Modal -->
|
||||
<DialogModal :show="currentlyManagingRole" @close="currentlyManagingRole = false">
|
||||
<template #title>
|
||||
Manage Role
|
||||
</template>
|
||||
<DialogModal
|
||||
:show="currentlyManagingRole"
|
||||
@close="currentlyManagingRole = false">
|
||||
<template #title> Manage Role</template>
|
||||
|
||||
<template #content>
|
||||
<div v-if="managingRoleFor">
|
||||
<div class="relative z-0 mt-1 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer">
|
||||
<div
|
||||
class="relative z-0 mt-1 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer">
|
||||
<button
|
||||
v-for="(role, i) in availableRoles"
|
||||
:key="role.key"
|
||||
type="button"
|
||||
class="relative px-4 py-3 inline-flex w-full rounded-lg focus:z-10 focus:outline-none focus:border-indigo-500 dark:focus:border-indigo-600 focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-600"
|
||||
:class="{'border-t border-gray-200 dark:border-gray-700 focus:border-none rounded-t-none': i > 0, 'rounded-b-none': i !== Object.keys(availableRoles).length - 1}"
|
||||
@click="updateRoleForm.role = role.key"
|
||||
>
|
||||
<div :class="{'opacity-50': updateRoleForm.role && updateRoleForm.role !== role.key}">
|
||||
:class="{
|
||||
'border-t border-gray-200 dark:border-gray-700 focus:border-none rounded-t-none':
|
||||
i > 0,
|
||||
'rounded-b-none':
|
||||
i !==
|
||||
Object.keys(availableRoles).length - 1,
|
||||
}"
|
||||
@click="updateRoleForm.role = role.key">
|
||||
<div
|
||||
:class="{
|
||||
'opacity-50':
|
||||
updateRoleForm.role &&
|
||||
updateRoleForm.role !== role.key,
|
||||
}">
|
||||
<!-- Role Name -->
|
||||
<div class="flex items-center">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400" :class="{'font-semibold': updateRoleForm.role === role.key}">
|
||||
<div
|
||||
class="text-sm text-gray-600 dark:text-gray-400"
|
||||
:class="{
|
||||
'font-semibold':
|
||||
updateRoleForm.role ===
|
||||
role.key,
|
||||
}">
|
||||
{{ role.name }}
|
||||
</div>
|
||||
|
||||
<svg v-if="updateRoleForm.role == role.key" class="ms-2 h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
<svg
|
||||
v-if="updateRoleForm.role == role.key"
|
||||
class="ms-2 h-5 w-5 text-green-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Role Description -->
|
||||
<div class="mt-2 text-xs text-gray-600 dark:text-gray-400">
|
||||
<div
|
||||
class="mt-2 text-xs text-gray-600 dark:text-gray-400">
|
||||
{{ role.description }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -321,18 +434,17 @@ const displayableRole = (role) => {
|
||||
class="ms-3"
|
||||
:class="{ 'opacity-25': updateRoleForm.processing }"
|
||||
:disabled="updateRoleForm.processing"
|
||||
@click="updateRole"
|
||||
>
|
||||
@click="updateRole">
|
||||
Save
|
||||
</PrimaryButton>
|
||||
</template>
|
||||
</DialogModal>
|
||||
|
||||
<!-- Leave Organization Confirmation Modal -->
|
||||
<ConfirmationModal :show="confirmingLeavingTeam" @close="confirmingLeavingTeam = false">
|
||||
<template #title>
|
||||
Leave Team
|
||||
</template>
|
||||
<ConfirmationModal
|
||||
:show="confirmingLeavingTeam"
|
||||
@close="confirmingLeavingTeam = false">
|
||||
<template #title> Leave Team</template>
|
||||
|
||||
<template #content>
|
||||
Are you sure you would like to leave this team?
|
||||
@@ -347,18 +459,17 @@ const displayableRole = (role) => {
|
||||
class="ms-3"
|
||||
:class="{ 'opacity-25': leaveTeamForm.processing }"
|
||||
:disabled="leaveTeamForm.processing"
|
||||
@click="leaveTeam"
|
||||
>
|
||||
@click="leaveTeam">
|
||||
Leave
|
||||
</DangerButton>
|
||||
</template>
|
||||
</ConfirmationModal>
|
||||
|
||||
<!-- Remove Organization Member Confirmation Modal -->
|
||||
<ConfirmationModal :show="teamMemberBeingRemoved" @close="teamMemberBeingRemoved = null">
|
||||
<template #title>
|
||||
Remove Team Member
|
||||
</template>
|
||||
<ConfirmationModal
|
||||
:show="!!teamMemberBeingRemoved"
|
||||
@close="teamMemberBeingRemoved = null">
|
||||
<template #title> Remove Team Member</template>
|
||||
|
||||
<template #content>
|
||||
Are you sure you would like to remove this person from the team?
|
||||
@@ -373,8 +484,7 @@ const displayableRole = (role) => {
|
||||
class="ms-3"
|
||||
:class="{ 'opacity-25': removeTeamMemberForm.processing }"
|
||||
:disabled="removeTeamMemberForm.processing"
|
||||
@click="removeTeamMember"
|
||||
>
|
||||
@click="removeTeamMember">
|
||||
Remove
|
||||
</DangerButton>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user