- Resolve gameServerRegistry.json's null deep_link entries against the live drop-db catalog (WoW, EverQuest, Among Us match real Game rows; OpenDAoC and the Goldberg relay genuinely have none, left null and documented why). - Fix the "0 online now" panel bug: the community landing page's persistent-world quick-strip was inferring presence from PlaySession name-matching (presenceService.getServerPresence), which hardcodes available=true even with zero matches -- always showing a fake "0" for EQEmu instead of the honest unavailable state. Switched it to the same probe-backed GameServer feed ServerStatusPanel already uses (joined by the shared wow/eqemu/daoc slug), so known-zero and count-unavailable never render the same. - Header people icon now opens a friends flyout (list, incoming requests with accept/decline, entry into chat with a friend) instead of going nowhere, mirroring the existing notifications bell's Menu/MenuButton/MenuItems pattern; badge shows pending request count. Mobile sidebar links it straight to /community/friends. - Community gets a hover submenu in the main nav (desktop) / inline sublist (mobile) listing Friends, Chat, Devices, Mods, Servers, Issues, Achievements. - User dropdown menu gets a "Your profile" entry resolved from the signed-in user's own username (/community/profile/<username>), not hardcoded.
144 lines
4.5 KiB
Vue
144 lines
4.5 KiB
Vue
<template>
|
|
<PanelWidget class="flex-col gap-y-2">
|
|
<div class="border-b border-zinc-700 pb-3 p-2">
|
|
<div
|
|
class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap"
|
|
>
|
|
<div class="ml-4 mt-2">
|
|
<h3 class="text-base font-semibold text-zinc-100 text-sm">
|
|
{{ $t("community.friends.title") }}
|
|
</h3>
|
|
</div>
|
|
<div class="ml-4 mt-2 shrink-0">
|
|
<NuxtLink
|
|
to="/community/friends"
|
|
type="button"
|
|
class="text-sm text-zinc-400"
|
|
>
|
|
<i18n-t
|
|
keypath="community.friends.viewAll"
|
|
tag="span"
|
|
scope="global"
|
|
>
|
|
<template #arrow>
|
|
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
|
</template>
|
|
</i18n-t>
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-y-3 max-h-[400px] overflow-y-scroll px-2">
|
|
<!-- Incoming friend requests -- the "invites" half of the ask, surfaced
|
|
right at the entry point instead of only on the full page. -->
|
|
<div
|
|
v-if="props.friends && props.friends.incomingRequests.length > 0"
|
|
class="flex flex-col gap-y-2"
|
|
>
|
|
<h4 class="text-xs font-semibold uppercase tracking-wide text-zinc-500">
|
|
{{ $t("community.friends.incoming") }}
|
|
</h4>
|
|
<div
|
|
v-for="req in props.friends.incomingRequests"
|
|
:key="req.id"
|
|
class="flex items-center justify-between gap-x-2"
|
|
>
|
|
<div class="flex items-center gap-x-2 min-w-0">
|
|
<UserAvatar
|
|
:avatar-url="req.user.avatarUrl"
|
|
:name="req.user.displayName"
|
|
:size-px="28"
|
|
/>
|
|
<span class="text-zinc-100 text-sm truncate">{{
|
|
req.user.displayName
|
|
}}</span>
|
|
</div>
|
|
<div class="flex gap-x-1.5 shrink-0">
|
|
<button
|
|
type="button"
|
|
class="rounded-md bg-emerald-600 px-2 py-1 text-xs font-semibold text-white hover:bg-emerald-500 duration-200"
|
|
@click="accept(req.id)"
|
|
>
|
|
{{ $t("community.friends.accept") }}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="rounded-md bg-zinc-700 px-2 py-1 text-xs font-semibold text-zinc-200 hover:bg-zinc-600 duration-200"
|
|
@click="decline(req.id)"
|
|
>
|
|
{{ $t("community.friends.decline") }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="h-0.5 rounded-full w-full bg-zinc-800 mt-1" />
|
|
</div>
|
|
|
|
<!-- Friends list, with a direct entry into chat with each one. -->
|
|
<div
|
|
v-for="friend in props.friends?.friends ?? []"
|
|
:key="friend.id"
|
|
class="flex items-center justify-between gap-x-2"
|
|
>
|
|
<NuxtLink
|
|
:to="`/community/profile/${friend.username}`"
|
|
class="flex items-center gap-x-2 min-w-0"
|
|
>
|
|
<UserAvatar
|
|
:avatar-url="friend.avatarUrl"
|
|
:name="friend.displayName"
|
|
:size-px="28"
|
|
/>
|
|
<span class="text-zinc-100 text-sm truncate">{{
|
|
friend.displayName
|
|
}}</span>
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
:to="`/community/chat?dm=${friend.id}`"
|
|
class="text-xs text-blue-400 hover:text-blue-300 shrink-0"
|
|
>
|
|
{{ $t("community.friends.message") }}
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
v-if="
|
|
props.friends &&
|
|
props.friends.friends.length === 0 &&
|
|
props.friends.incomingRequests.length === 0
|
|
"
|
|
class="text-sm text-zinc-400 p-3 text-center w-full"
|
|
>
|
|
{{ $t("community.friends.noFriends") }}
|
|
</div>
|
|
<div
|
|
v-else-if="!props.friends"
|
|
class="text-sm text-zinc-400 p-3 text-center w-full"
|
|
>
|
|
{{ $t("common.srLoading") }}
|
|
</div>
|
|
</PanelWidget>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { FriendsListResult } from "~/composables/community-friends";
|
|
|
|
const props = defineProps<{
|
|
friends: FriendsListResult | null;
|
|
}>();
|
|
|
|
async function accept(requestId: string) {
|
|
await $dropFetch(`/api/v1/community/friends/requests/${requestId}/accept`, {
|
|
method: "POST",
|
|
});
|
|
await refreshCommunityFriends();
|
|
}
|
|
|
|
async function decline(requestId: string) {
|
|
await $dropFetch(`/api/v1/community/friends/requests/${requestId}/decline`, {
|
|
method: "POST",
|
|
});
|
|
await refreshCommunityFriends();
|
|
}
|
|
</script>
|