feat: users row actions "Cancel invite" on Pending rows and "Renew invite" on Expired rows
This commit is contained in:
1 parent
1ece778caf
commit
142325730d
14 files changed
+1156
-35
No files matched your search
@@ -85,22 +85,24 @@ function onCancel() {
|
||||
}"
|
||||
:ui="{
|
||||
content:
|
||||
'divide-y-0 shadow-(--shadow-modal) ring-0 sm:top-[220px] sm:max-w-110 sm:translate-y-0 max-sm:!inset-x-4 max-sm:!top-auto max-sm:!bottom-4 max-sm:!left-4 max-sm:!w-auto max-sm:!max-w-none max-sm:!translate-x-0 max-sm:!translate-y-0',
|
||||
header: 'min-h-0 px-6 pt-6 pb-0 sm:px-6 max-sm:px-5 max-sm:pt-5',
|
||||
title: 'text-lg max-sm:text-[1.0625rem]/6',
|
||||
description: 'mt-2 text-base text-toned max-sm:text-[0.9375rem]/[23px]',
|
||||
body: 'px-6 pt-4 pb-0 sm:px-6 sm:pt-4 sm:pb-0 max-sm:px-5',
|
||||
footer: 'p-6 sm:px-6 max-sm:p-5',
|
||||
'divide-y-0 shadow-(--shadow-modal) ring-0 sm:top-35 sm:max-w-115 sm:translate-y-0 max-sm:!inset-x-4 max-sm:!top-auto max-sm:!bottom-4 max-sm:!left-4 max-sm:!w-auto max-sm:!max-w-none max-sm:!translate-x-0 max-sm:!translate-y-0',
|
||||
header: 'min-h-0 px-6 pt-5 pb-0 sm:px-6',
|
||||
title: 'text-lg/6 font-semibold',
|
||||
description: 'mt-1 text-sm text-muted text-pretty',
|
||||
body: 'px-6 pt-5 pb-0 sm:px-6 sm:pt-5 sm:pb-0',
|
||||
footer: 'px-6 py-5 sm:px-6',
|
||||
}"
|
||||
>
|
||||
<template v-if="error" #body>
|
||||
<template #body>
|
||||
<UAlert
|
||||
v-if="error"
|
||||
color="error"
|
||||
icon="i-lucide-circle-x"
|
||||
:description="error"
|
||||
variant="soft"
|
||||
:close="false"
|
||||
role="alert"
|
||||
:ui="{ root: 'gap-2 px-3 py-2.5', description: 'text-sm text-toned' }"
|
||||
/>
|
||||
</template>
|
||||
<template #footer>
|
||||
@@ -110,7 +112,7 @@ function onCancel() {
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
block
|
||||
class="h-11 justify-center text-[0.9375rem] text-highlighted disabled:text-dimmed disabled:opacity-100 sm:h-8 sm:w-auto sm:px-3 sm:text-sm"
|
||||
class="h-11 justify-center text-sm text-highlighted disabled:text-dimmed disabled:opacity-100 sm:h-8 sm:w-auto sm:px-3"
|
||||
:disabled="pending"
|
||||
@click="onCancel"
|
||||
>
|
||||
@@ -121,7 +123,7 @@ function onCancel() {
|
||||
:loading="pending"
|
||||
:disabled="pending"
|
||||
block
|
||||
class="h-11 justify-center text-[0.9375rem] sm:h-8 sm:w-auto sm:px-3 sm:text-sm"
|
||||
class="h-11 justify-center text-sm sm:h-8 sm:w-auto sm:px-3"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{ confirmText }}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import InviteStatusBadge from '@/components/InviteStatusBadge.vue'
|
||||
import RoleBadge from '@/components/RoleBadge.vue'
|
||||
import UserRowAction from '@/components/UserRowAction.vue'
|
||||
import UserEmailCell from '@/components/UserEmailCell.vue'
|
||||
import { formatCreatedDate } from '@/lib/relativeDate'
|
||||
import {
|
||||
@@ -16,6 +17,13 @@ defineProps<{
|
||||
loading: boolean
|
||||
sessionUserId?: number
|
||||
highlightedKey?: string
|
||||
canManage?: boolean
|
||||
busyKeys?: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: [item: UserListItem]
|
||||
renew: [item: UserListItem]
|
||||
}>()
|
||||
|
||||
function rowKey(item: UserListItem) {
|
||||
@@ -56,6 +64,15 @@ function rowKey(item: UserListItem) {
|
||||
<InviteStatusBadge :status="item.status" size="sm" />
|
||||
<span>Created {{ formatCreatedDate(item.created_at) }}</span>
|
||||
</div>
|
||||
<div v-if="canManage && item.kind === 'invite'" class="flex pt-1">
|
||||
<UserRowAction
|
||||
:item="item"
|
||||
large
|
||||
:busy="busyKeys?.includes(rowKey(item))"
|
||||
@cancel="emit('cancel', item)"
|
||||
@renew="emit('renew', item)"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { UserListItem } from '@/composables/useUsers'
|
||||
|
||||
const props = defineProps<{
|
||||
item: UserListItem
|
||||
busy?: boolean
|
||||
large?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ cancel: []; renew: [] }>()
|
||||
|
||||
const actions = {
|
||||
pending: {
|
||||
label: 'Cancel invite',
|
||||
icon: 'i-lucide-user-x',
|
||||
textClass: 'text-error',
|
||||
event: 'cancel',
|
||||
},
|
||||
expired: {
|
||||
label: 'Renew invite',
|
||||
icon: 'i-lucide-refresh-cw',
|
||||
textClass: 'text-highlighted',
|
||||
event: 'renew',
|
||||
},
|
||||
} as const
|
||||
|
||||
const action = computed(() => {
|
||||
return props.item.kind === 'invite' ? actions[props.item.status] : null
|
||||
})
|
||||
|
||||
const label = computed(() => (props.busy ? 'Renewing…' : (action.value?.label ?? '')))
|
||||
const icon = computed(() => (props.busy ? 'i-lucide-loader-circle' : action.value?.icon))
|
||||
const sizeClass = computed(() =>
|
||||
props.large ? 'h-11 gap-2 px-3.5 text-sm' : 'h-7 gap-1.5 px-2.25 text-xs',
|
||||
)
|
||||
const iconClass = computed(() => (props.large ? 'size-3.5' : 'size-3.25'))
|
||||
|
||||
function onClick() {
|
||||
if (props.busy || !action.value) {
|
||||
return
|
||||
}
|
||||
if (action.value.event === 'cancel') {
|
||||
emit('cancel')
|
||||
} else {
|
||||
emit('renew')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UButton
|
||||
v-if="action"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
:icon="icon"
|
||||
:disabled="busy"
|
||||
class="shrink-0 border border-accented bg-default font-medium whitespace-nowrap ring-0"
|
||||
:class="[sizeClass, action.textClass, { 'opacity-50': busy }]"
|
||||
:ui="{ leadingIcon: iconClass }"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ label }}
|
||||
</UButton>
|
||||
</template>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import InviteStatusBadge from '@/components/InviteStatusBadge.vue'
|
||||
import RoleBadge from '@/components/RoleBadge.vue'
|
||||
import UserRowAction from '@/components/UserRowAction.vue'
|
||||
import UserEmailCell from '@/components/UserEmailCell.vue'
|
||||
import { formatCreatedDate } from '@/lib/relativeDate'
|
||||
import {
|
||||
@@ -19,6 +20,13 @@ defineProps<{
|
||||
loading: boolean
|
||||
sessionUserId?: number
|
||||
highlightedKey?: string
|
||||
canManage?: boolean
|
||||
busyKeys?: string[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: [item: UserListItem]
|
||||
renew: [item: UserListItem]
|
||||
}>()
|
||||
|
||||
function rowKey(item: UserListItem) {
|
||||
@@ -74,7 +82,15 @@ function rowKey(item: UserListItem) {
|
||||
<span><RoleBadge :role="userRole(item)" /></span>
|
||||
<span><InviteStatusBadge :status="item.status" /></span>
|
||||
<span class="text-muted">{{ formatCreatedDate(item.created_at) }}</span>
|
||||
<span></span>
|
||||
<span class="flex justify-end">
|
||||
<UserRowAction
|
||||
v-if="canManage"
|
||||
:item="item"
|
||||
:busy="busyKeys?.includes(rowKey(item))"
|
||||
@cancel="emit('cancel', item)"
|
||||
@renew="emit('renew', item)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user