Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 20x 1x 1x 20x 1x 1x 10x 1x 10x 1x 1x 1x 2x 1x 10x 20x 20x 20x 20x 20x 20x 20x 3x 3x 2x 1x 1x 3x 2x 2x 1x 1x | <template>
<PageLayout :title="t('navigation.invitations')" class="invitations-page">
<div v-if="loading" class="row">
<div class="col-12 text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">{{ t('common.loading') }}</span>
</div>
</div>
</div>
<div v-else-if="!loading && invitations.length === 0" class="text-center py-4">
<p class="text-muted">{{ t('invitation.noPending') }}</p>
</div>
<VirtualCardGrid
v-else
:items="invitations"
:has-more="rolesStore.invitationsHasMore"
:is-loading="loading"
@load-more="rolesStore.loadMoreInvitations()"
>
<template #card="{ item: invitation }">
<UnifiedContentCard>
<h5 class="card-title mb-3">
<font-awesome-icon icon="fa-solid fa-envelope" class="me-2 text-primary" />
{{ invitation.flowName || invitation.flowId }}
</h5>
<div v-if="invitation.flowSlug" class="mb-3">
<div class="text-muted small mb-1">Slug</div>
<div>{{ invitation.flowSlug }}</div>
</div>
<div class="mb-3">
<div class="text-muted small mb-1">Invited by</div>
<div>{{ invitation.invitedBy }}</div>
</div>
<div class="mb-3">
<div class="text-muted small mb-1">Date</div>
<div>
{{ invitation.joinedAt ? new Date(invitation.joinedAt).toLocaleDateString() : '' }}
</div>
</div>
<div v-if="invitation.roleIds?.length > 0" class="mb-3">
<div class="text-muted small mb-1">Roles</div>
<div class="d-flex flex-wrap gap-1">
<span v-for="roleId in invitation.roleIds" :key="roleId" class="badge bg-primary">
{{ roleId }}
</span>
</div>
</div>
<template #footer>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-success flex-fill" @click="accept(invitation.flowId)">
<font-awesome-icon icon="fa-solid fa-check" class="me-1" />
Accept
</button>
<button
class="btn btn-sm btn-outline-danger flex-fill"
@click="decline(invitation.flowId)"
>
<font-awesome-icon icon="fa-solid fa-times" class="me-1" />
Decline
</button>
</div>
</template>
</UnifiedContentCard>
</template>
</VirtualCardGrid>
</PageLayout>
</template>
<script setup lang="ts">
// @implements UC-INVITE-002.1
// @implements UC-INVITE-003.1
import PageLayout from '@/components/PageLayout.vue'
import VirtualCardGrid from '@/components/VirtualCardGrid.vue'
import UnifiedContentCard from '@/components/UnifiedContentCard.vue'
import { onMounted } from 'vue'
import { useRolesStore } from '@/stores/roles'
import { useFlowsStore } from '@/stores/flows'
import { storeToRefs } from 'pinia'
import { useRouter } from 'vue-router'
import { useUILanguage } from '@/composables/useUILanguage'
const rolesStore = useRolesStore()
const flowsStore = useFlowsStore()
const router = useRouter()
const { t } = useUILanguage()
const { invitations, invitationsLoading: loading } = storeToRefs(rolesStore)
onMounted(async () => {
await rolesStore.fetchInvitations()
})
async function accept(flowId: string) {
try {
await flowsStore.acceptInvitation(flowId)
router.push('/')
} catch (error) {
console.error('Failed to accept invitation:', error)
alert('Failed to accept invitation')
}
}
async function decline(flowId: string) {
if (!confirm('Are you sure you want to decline this invitation?')) return
try {
await rolesStore.declineInvitation(flowId)
} catch (error) {
console.error('Failed to decline invitation:', error)
alert('Failed to decline invitation')
}
}
</script>
<style scoped>
.invitations-page :deep(.page-content) {
padding: 0 !important;
}
</style>
|