Skip to content
6 changes: 2 additions & 4 deletions frontend/src/components/VaultList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,15 @@
<div v-if="ownedVaults?.some(ownedVault => ownedVault.id == vault.id) && !isCommunityLicense && settings?.enableEmergencyAccess">
<EmergencyBadge
v-if="settings && settings.defaultMinMembers > emergencyAccessMembers(vault).length"
type="insufficientCouncilMembers"
type="warning"
:title="t('emergencyAccess.badge.insufficientCouncilMembers.title')"
:message="t('emergencyAccess.badge.insufficientCouncilMembers.message', [settings.defaultMinMembers])"
position="right"
/>
<EmergencyBadge
v-else-if="vault.requiredEmergencyKeyShares > emergencyAccessMembers(vault).length"
type="broken"
type="error"
:title="t('emergencyAccess.badge.broken.title')"
:message="t('emergencyAccess.badge.broken.message')"
position="right"
/>
</div>
<div class="ml-5 shrink-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,22 +99,19 @@
<div class="flex flex-wrap items-center gap-2 sm:justify-end" @click.stop>
<EmergencyBadge
v-if="isBroken(vault)"
type="broken"
type="error"
:title="t('emergencyAccess.badge.broken.title')"
:message="t('emergencyAccess.badge.broken.message')"
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<EmergencyBadge
v-if="settings && settings.defaultMinMembers > emergencyAccessMembers(vault).length"
type="insufficientCouncilMembers"
type="warning"
:title="t('emergencyAccess.badge.insufficientCouncilMembers.title')"
:message="t('emergencyAccess.badge.insufficientCouncilMembers.message', [settings.defaultMinMembers])"
position="right"
/>

<EmergencyBadge
v-else-if="vault.requiredEmergencyKeyShares === emergencyAccessMembers(vault).length"
type="noRedundancy"
type="warning"
:title="t('emergencyAccess.badge.noRedundancy.title')"
:message="t('emergencyAccess.badge.noRedundancy.message')"
/>
Expand Down Expand Up @@ -155,10 +152,9 @@
<div class="mt-auto pt-2 flex flex-wrap items-center gap-2">
<EmergencyBadge
v-if="!isEmergencyKeyShareHolder(vault)"
type="notCouncil"
type="warning"
:title="t('emergencyAccess.badge.notCouncil.title')"
:message="t('emergencyAccess.badge.notCouncil.message')"
position="left"
/>
<EmergencyProcessButton
v-if="getProcessByType(vault, 'COUNCIL_CHANGE')"
Expand Down
121 changes: 30 additions & 91 deletions frontend/src/components/emergencyaccess/EmergencyBadge.vue
Original file line number Diff line number Diff line change
@@ -1,107 +1,46 @@
<template>
<div v-if="type !== 'none'" class="relative mr-3 group/badge">
<!-- Badge -->
<span
class="inline-flex items-center gap-2 rounded-full px-2 py-2 text-xs font-medium cursor-default ring-1"
:class="badgeClasses"
<div class="group mr-3 inline-block" @click.stop.prevent>
<button
type="button"
class="inline-flex items-center gap-2 rounded-full p-2 text-xs font-medium cursor-default ring-1 outline-none focus-visible:ring-2"
:class="isError ? 'bg-red-100 ring-red-300/70 text-red-800' : 'bg-yellow-50 ring-yellow-300/70 text-yellow-800'"
:style="{ anchorName: anchor }"
:aria-label="title"
>
<ExclamationTriangleIcon class="h-4 w-4" :class="iconColor" />
</span>

<!-- Tooltip -->
<div
class="invisible opacity-0 group-hover/badge:visible group-hover/badge:opacity-100 transition-opacity duration-150 absolute -top-2 transform -translate-y-full w-max max-w-xs z-20"
:class="positionClasses"
<ExclamationTriangleIcon class="size-4" :class="isError ? 'text-red-600' : 'text-yellow-500'" />
</button>

<div
class="tooltip-panel fixed mb-2 z-20 px-2 py-1 rounded shadow-sm border text-xs hyphens-auto invisible opacity-0 transition-[opacity,visibility] duration-150 group-hover:visible group-hover:opacity-100 group-has-focus-visible:visible group-has-focus-visible:opacity-100 pointer-coarse:group-focus-within:visible pointer-coarse:group-focus-within:opacity-100"
:class="isError ? 'bg-red-50 border-red-300 text-red-900' : 'bg-yellow-50 border-yellow-300 text-yellow-900'"
:style="{ positionAnchor: anchor }"
role="tooltip"
>
<div class="px-2 py-1 rounded shadow-sm text-xs hyphens-auto border relative" :class="tooltipClasses">
<b>{{ title }}</b><br />
<span>{{ message }}</span>

<!-- Arrow -->
<div
class="absolute bottom-0 transform translate-y-1/2 rotate-45 w-2 h-2 border-r border-b"
:class="[arrowClasses, arrowPositionClasses]"
></div>
</div>
<p class="font-bold">{{ title }}</p>
<p>{{ message }}</p>
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import { computed, useId } from 'vue';
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';

const props = defineProps<{
type: 'notCouncil' | 'broken' | 'noRedundancy' | 'insufficientCouncilMembers' | 'none';
type: 'warning' | 'error';
title: string;
message: string;
position?: 'center' | 'left' | 'right';
}>();

const positionClasses = computed(() => {
switch (props.position) {
case 'left':
return 'left-0';
case 'right':
return 'right-0';
case 'center':
default:
return 'left-1/2 -translate-x-1/2';
}
});

const badgeClasses = computed(() => {
switch (props.type) {
case 'notCouncil':
case 'insufficientCouncilMembers':
case 'noRedundancy':
return 'bg-yellow-50 ring-yellow-300/70 text-yellow-800';
case 'broken':
return 'bg-red-100 ring-red-300/70 text-red-800';
default:
return '';
}
});

const tooltipClasses = computed(() => {
switch (props.type) {
case 'notCouncil':
case 'insufficientCouncilMembers':
case 'noRedundancy':
return 'bg-yellow-50 border-yellow-300 text-yellow-900';
case 'broken':
return 'bg-red-50 border-red-300 text-red-900';
default:
return '';
}
});

const arrowClasses = computed(() => {
switch (props.type) {
case 'notCouncil':
case 'insufficientCouncilMembers':
case 'noRedundancy':
return 'bg-yellow-50 border-yellow-300';
case 'broken':
return 'bg-red-50 border-red-300';
default:
return '';
}
});

const arrowPositionClasses = computed(() => {
switch (props.position) {
case 'left':
return 'left-2';
case 'right':
return 'right-2';
case 'center':
default:
return 'left-1/2 -translate-x-1/2';
}
});

const iconColor = computed(() => {
return props.type === 'broken' ? 'text-red-600' : 'text-yellow-500';
});
const anchor = `--ea-anchor-${useId()}`;
const isError = computed(() => props.type === 'error');
</script>

<style scoped>
.tooltip-panel {
bottom: anchor(top);
left: max(1rem, calc(anchor(center) - 10rem));
right: max(1rem, calc(anchor(center) - 10rem));
position-try-fallbacks: flip-block;
}
</style>