-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add manager profile pages #618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
1138f42
7a30e5c
f783ba4
90d7e78
2000225
45cd27c
762a9ff
27794c1
d28e1ad
0256240
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,96 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| <script setup lang="ts"> | ||||||||||||||||||||||||||||||||||||||||||||||
| import type { EulerLabelEntity } from '~/entities/euler/labels' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { getEulerLabelEntityLogo } from '~/entities/euler/labels' | ||||||||||||||||||||||||||||||||||||||||||||||
| import { getEulerLabelEntityDisplayName, getEulerLabelEntitySlug, getManagerProfilePath } from '~/utils/manager-profile' | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const props = withDefaults(defineProps<{ | ||||||||||||||||||||||||||||||||||||||||||||||
| entities: EulerLabelEntity[] | ||||||||||||||||||||||||||||||||||||||||||||||
| label?: string | ||||||||||||||||||||||||||||||||||||||||||||||
| showAvatar?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||
| spanLink?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||
| textClass?: string | ||||||||||||||||||||||||||||||||||||||||||||||
| avatarClass?: string | ||||||||||||||||||||||||||||||||||||||||||||||
| dataKey?: string | ||||||||||||||||||||||||||||||||||||||||||||||
| dataField?: string | ||||||||||||||||||||||||||||||||||||||||||||||
| disabled?: boolean | ||||||||||||||||||||||||||||||||||||||||||||||
| }>(), { | ||||||||||||||||||||||||||||||||||||||||||||||
| label: '', | ||||||||||||||||||||||||||||||||||||||||||||||
| showAvatar: true, | ||||||||||||||||||||||||||||||||||||||||||||||
| spanLink: false, | ||||||||||||||||||||||||||||||||||||||||||||||
| textClass: 'text-p2 text-content-primary hover:text-accent-600 underline transition-colors', | ||||||||||||||||||||||||||||||||||||||||||||||
| avatarClass: 'icon--20', | ||||||||||||||||||||||||||||||||||||||||||||||
| dataKey: '', | ||||||||||||||||||||||||||||||||||||||||||||||
| dataField: '', | ||||||||||||||||||||||||||||||||||||||||||||||
| disabled: false, | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const { entities: entityMap } = useEulerLabels() | ||||||||||||||||||||||||||||||||||||||||||||||
| const route = useRoute() | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const displayName = computed(() => props.label || getEulerLabelEntityDisplayName(props.entities)) | ||||||||||||||||||||||||||||||||||||||||||||||
| const entityLogos = computed(() => props.entities.map(entity => getEulerLabelEntityLogo(entity.logo))) | ||||||||||||||||||||||||||||||||||||||||||||||
| const primarySlug = computed(() => { | ||||||||||||||||||||||||||||||||||||||||||||||
| const first = props.entities[0] | ||||||||||||||||||||||||||||||||||||||||||||||
| return first ? getEulerLabelEntitySlug(entityMap, first) : '' | ||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||
| const to = computed(() => ({ | ||||||||||||||||||||||||||||||||||||||||||||||
| path: getManagerProfilePath(primarySlug.value), | ||||||||||||||||||||||||||||||||||||||||||||||
| query: { network: route.query.network }, | ||||||||||||||||||||||||||||||||||||||||||||||
| })) | ||||||||||||||||||||||||||||||||||||||||||||||
| const isLinked = computed(() => Boolean(primarySlug.value) && props.entities.length === 1 && !props.disabled) | ||||||||||||||||||||||||||||||||||||||||||||||
| const fallbackTextClass = computed(() => | ||||||||||||||||||||||||||||||||||||||||||||||
| props.textClass.replace('hover:text-accent-600 underline transition-colors', ''), | ||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const goToManager = () => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!isLinked.value) return | ||||||||||||||||||||||||||||||||||||||||||||||
| void navigateTo(to.value) | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const onSpanKeydown = (event: KeyboardEvent) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (event.key !== 'Enter' && event.key !== ' ') return | ||||||||||||||||||||||||||||||||||||||||||||||
| event.preventDefault() | ||||||||||||||||||||||||||||||||||||||||||||||
| event.stopPropagation() | ||||||||||||||||||||||||||||||||||||||||||||||
| goToManager() | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <template> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span class="inline-flex min-w-0 items-center gap-6"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <BaseAvatar | ||||||||||||||||||||||||||||||||||||||||||||||
| v-if="showAvatar" | ||||||||||||||||||||||||||||||||||||||||||||||
| :class="avatarClass" | ||||||||||||||||||||||||||||||||||||||||||||||
| :label="displayName" | ||||||||||||||||||||||||||||||||||||||||||||||
| :src="entityLogos" | ||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||||||||||||||||||||
| v-if="spanLink && isLinked" | ||||||||||||||||||||||||||||||||||||||||||||||
| role="link" | ||||||||||||||||||||||||||||||||||||||||||||||
| tabindex="0" | ||||||||||||||||||||||||||||||||||||||||||||||
| :class="textClass" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-id="dataKey && dataField ? 'data-point' : undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-key="dataKey || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-field="dataField || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-value="displayName" | ||||||||||||||||||||||||||||||||||||||||||||||
| @click.stop.prevent="goToManager" | ||||||||||||||||||||||||||||||||||||||||||||||
| @keydown="onSpanKeydown" | ||||||||||||||||||||||||||||||||||||||||||||||
| >{{ displayName }}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win Clickable In the ♿ Suggested accessibility attributes <span
v-if="spanLink && isLinked"
:class="textClass"
+ role="link"
+ tabindex="0"
:data-id="dataKey && dataField ? 'data-point' : undefined"
:data-key="dataKey || undefined"
:data-field="dataField || undefined"
:data-value="displayName"
`@click.stop.prevent`="goToManager"
+ `@keydown.enter.stop.prevent`="goToManager"
+ `@keydown.space.stop.prevent`="goToManager"
>{{ displayName }}</span>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| <NuxtLink | ||||||||||||||||||||||||||||||||||||||||||||||
| v-else-if="isLinked" | ||||||||||||||||||||||||||||||||||||||||||||||
| :to="to" | ||||||||||||||||||||||||||||||||||||||||||||||
| :class="textClass" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-id="dataKey && dataField ? 'data-point' : undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-key="dataKey || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-field="dataField || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-value="displayName" | ||||||||||||||||||||||||||||||||||||||||||||||
| >{{ displayName }}</NuxtLink> | ||||||||||||||||||||||||||||||||||||||||||||||
| <span | ||||||||||||||||||||||||||||||||||||||||||||||
| v-else | ||||||||||||||||||||||||||||||||||||||||||||||
| :class="fallbackTextClass" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-id="dataKey && dataField ? 'data-point' : undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-key="dataKey || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-field="dataField || undefined" | ||||||||||||||||||||||||||||||||||||||||||||||
| :data-value="displayName" | ||||||||||||||||||||||||||||||||||||||||||||||
| >{{ displayName }}</span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||
| </template> | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,6 @@ import { formatAssetValue } from '~/utils/sdk-prices' | |
| import { useEulerProductOfVault, useEulerEntitiesOfVault } from '~/composables/useEulerLabels' | ||
| import { isVaultGovernanceLimited, isVaultRecentlyAdded, isVaultKeyring, isVaultCyclicalNote } from '~/utils/eulerLabelsUtils' | ||
| import { withVaultIntrinsicApy, getVaultIntrinsicApy, getVaultIntrinsicApyInfo } from '~/utils/vault-intrinsic-apy' | ||
| import { getEulerLabelEntityLogo } from '~/entities/euler/labels' | ||
| import { isVaultBlockedByCountry } from '~/composables/useGeoBlock' | ||
| import { formatNumber, compactNumber, formatCompactUsdValue } from '~/utils/string-utils' | ||
| import BaseLoadableContent from '~/components/base/BaseLoadableContent.vue' | ||
|
|
@@ -32,10 +31,6 @@ const entityName = computed(() => { | |
| if (entities.length === 2) return `${entities[0].name} & ${entities[1].name}` | ||
| return `${entities[0].name} & others` | ||
| }) | ||
| const entityLogos = computed(() => { | ||
| if (!entityName.value || entities.length === 0) return [] | ||
| return entities.map(e => getEulerLabelEntityLogo(e.logo)) | ||
| }) | ||
| const isEscrow = computed(() => getVaultCategory(vault.address) === 'escrow') | ||
| const isBorrowable = computed(() => isVaultBorrowable(vault)) | ||
| const displayName = computed(() => { | ||
|
|
@@ -303,18 +298,14 @@ watchEffect(async () => { | |
| class="flex items-center gap-6" | ||
| :class="{ 'opacity-20': isGovernanceLimited }" | ||
| > | ||
| <BaseAvatar | ||
| class="icon--20" | ||
| <ManagerEntityLink | ||
| :entities="entities" | ||
| :label="entityName" | ||
| :src="entityLogos" | ||
| /> | ||
| <span | ||
| class="text-p2 text-content-primary truncate" | ||
| data-id="data-point" | ||
| span-link | ||
| text-class="text-p2 text-content-primary hover:text-accent-600 underline transition-colors truncate" | ||
| :data-key="vault.address.toLowerCase()" | ||
| data-field="risk-manager" | ||
| :data-value="entityName" | ||
| >{{ entityName }}</span> | ||
| /> | ||
|
Comment on lines
+301
to
+308
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Make the manager-profile affordance keyboard accessible. Using 🤖 Prompt for AI Agents |
||
| </div> | ||
| <div | ||
| v-else | ||
|
|
@@ -475,12 +466,12 @@ watchEffect(async () => { | |
| class="flex items-center gap-8" | ||
| :class="{ 'opacity-20': isGovernanceLimited }" | ||
| > | ||
| <BaseAvatar | ||
| class="icon--20" | ||
| <ManagerEntityLink | ||
| :entities="entities" | ||
| :label="entityName" | ||
| :src="entityLogos" | ||
| span-link | ||
| text-class="text-p2 text-content-primary hover:text-accent-600 underline transition-colors truncate" | ||
| /> | ||
| <span class="text-p2 text-content-primary truncate">{{ entityName }}</span> | ||
| </div> | ||
| <div | ||
| v-else | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Suggestion:
spanLinkmakes the manager destination mouse-clickable, but it renders as a plain<span>with nohref,role="link",tabindex, or keyboard handler. Most of the new vault-card call sites use this mode to avoid nested anchors, so keyboard and screen-reader users do not get the same navigation affordance as mouse users. Consider making the span mode an accessible link-like control (for examplerole="link",tabindex="0", and Enter/Space handling) or otherwise restructuring the card/link relationship so the shared component stays accessible everywhere.