The News page was a hardcoded "under construction" stub, and the Friends/ Alerts header icons did nothing -- both replaced with real Vue pages/ widgets backed by the server's existing news/notifications/community APIs, reached through a generic authenticated REST bridge (community_api.rs: api_get/api_post/api_delete) and a websocket bridge for live chat/presence (community_ws.rs, mirrors the reqwest_websocket pattern remote.rs already uses for the auth-code exchange). - News: GET /api/v1/client/news, rendered with micromark, with real loading/empty/error states. - Alerts: GET/POST /api/v1/notifications/*, polled (not the notifications websocket -- see notifications.ts for why), unread badge + mark one/all read. - Friends: full friends composable (list/requests/search/presence) plus a header dropdown and a /community/friends management page. - Chat: /community/chat, rooms (server/global/title/DM) over the live community websocket with a REST history/read-state load on room switch. - Achievements: per-game card on the library detail page and a per-user summary on the new read-only profile page (/community/profile/:username). - The Community tab's "opens in your system browser" page is gone; /community is now a real in-app hub (activity feed + server list) linking into friends/chat. Also fixes a real auth gap found by actually running the built client against the live server: notifications/community routes are gated by aclManager.getUserIdACL, which only accepts a session cookie or an opaque Bearer APIToken -- never the client's own short-lived signed JWT (generate_authorization_header). The client already had a bridge for this (POST /api/v1/client/user/webtoken, JWT-authenticated, mints an opaque token), but CLIENT_WEBTOKEN_ACLS never granted it the notifications/ community scopes, so the exchange succeeded and the minted token still got 403'd on every one of these routes. Extended that ACL list (04.auth-init.ts) and switched community_api.rs/community_ws.rs to mint and use that webtoken instead of the JWT for these specific calls. Client version bumped to 0.4.2 (tauri.conf.json).
85 lines
2.4 KiB
Vue
85 lines
2.4 KiB
Vue
<template>
|
|
<div class="h-16 bg-zinc-950 flex flex-row justify-between">
|
|
<div class="flex flex-row grow items-center pl-5 pr-2 py-3">
|
|
<div class="inline-flex items-center gap-x-10">
|
|
<NuxtLink to="/store">
|
|
<Wordmark class="h-8 mb-0.5" />
|
|
</NuxtLink>
|
|
<nav class="inline-flex items-center mt-0.5">
|
|
<ol class="inline-flex items-center gap-x-6">
|
|
<NuxtLink
|
|
v-for="(nav, navIdx) in navigation"
|
|
:class="[
|
|
'transition uppercase font-display font-semibold text-md',
|
|
navIdx === currentNavigation
|
|
? 'text-zinc-100'
|
|
: 'text-zinc-400 hover:text-zinc-200',
|
|
]"
|
|
:href="nav.route"
|
|
>
|
|
{{ nav.label }}
|
|
</NuxtLink>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div
|
|
@mousedown="() => window.startDragging()"
|
|
class="flex cursor-pointer grow h-full"
|
|
/>
|
|
<div class="inline-flex items-center">
|
|
<ol class="inline-flex gap-3">
|
|
<HeaderProtonSupportWidget />
|
|
<HeaderQueueWidget :object="currentQueueObject" />
|
|
<li>
|
|
<HeaderFriendsWidget />
|
|
</li>
|
|
<li>
|
|
<HeaderNotificationsWidget />
|
|
</li>
|
|
<OfflineHeaderWidget v-if="state?.status === AppStatus.Offline" />
|
|
<HeaderUserWidget />
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
<WindowControl />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { AppStatus, type NavigationItem } from "../types";
|
|
import HeaderWidget from "./HeaderWidget.vue";
|
|
import HeaderFriendsWidget from "./HeaderFriendsWidget.vue";
|
|
import HeaderNotificationsWidget from "./HeaderNotificationsWidget.vue";
|
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
|
|
const window = getCurrentWindow();
|
|
const state = useAppState();
|
|
|
|
const navigation: Array<NavigationItem> = [
|
|
{
|
|
prefix: "/store",
|
|
route: "/store",
|
|
label: "Store",
|
|
},
|
|
{
|
|
prefix: "/library",
|
|
route: "/library",
|
|
label: "Library",
|
|
},
|
|
{
|
|
prefix: "/community",
|
|
route: "/community",
|
|
label: "Community",
|
|
},
|
|
{
|
|
prefix: "/news",
|
|
route: "/news",
|
|
label: "News",
|
|
},
|
|
];
|
|
|
|
const { currentNavigation } = useCurrentNavigationIndex(navigation);
|
|
|
|
const queue = useQueueState();
|
|
const currentQueueObject = computed(() => queue.value.queue.at(0));
|
|
</script>
|