add invitation notifications to nav
badge on profile icon + dropdown item to open invitations modal
This commit is contained in:
parent
c9f31f9059
commit
d2c16d908d
1 changed files with 72 additions and 10 deletions
|
|
@ -5,13 +5,17 @@
|
||||||
import { m } from '$lib/paraglide/messages'
|
import { m } from '$lib/paraglide/messages'
|
||||||
import { page } from '$app/stores'
|
import { page } from '$app/stores'
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
|
import { createQuery } from '@tanstack/svelte-query'
|
||||||
|
import { crewQueries } from '$lib/api/queries/crew.queries'
|
||||||
import Button from './ui/Button.svelte'
|
import Button from './ui/Button.svelte'
|
||||||
import Icon from './Icon.svelte'
|
import Icon from './Icon.svelte'
|
||||||
import DropdownItem from './ui/dropdown/DropdownItem.svelte'
|
import DropdownItem from './ui/dropdown/DropdownItem.svelte'
|
||||||
|
import NotificationBadge from './ui/NotificationBadge.svelte'
|
||||||
import { DropdownMenu } from 'bits-ui'
|
import { DropdownMenu } from 'bits-ui'
|
||||||
import type { UserCookie } from '$lib/types/UserCookie'
|
import type { UserCookie } from '$lib/types/UserCookie'
|
||||||
import { getAvatarSrc, getAvatarSrcSet } from '$lib/utils/avatar'
|
import { getAvatarSrc, getAvatarSrcSet } from '$lib/utils/avatar'
|
||||||
import UserSettingsModal from './UserSettingsModal.svelte'
|
import UserSettingsModal from './UserSettingsModal.svelte'
|
||||||
|
import InvitationsModal from './crew/InvitationsModal.svelte'
|
||||||
import { authStore } from '$lib/stores/auth.store'
|
import { authStore } from '$lib/stores/auth.store'
|
||||||
|
|
||||||
// Props from layout data
|
// Props from layout data
|
||||||
|
|
@ -128,6 +132,18 @@
|
||||||
// Settings modal state
|
// Settings modal state
|
||||||
let settingsModalOpen = $state(false)
|
let settingsModalOpen = $state(false)
|
||||||
|
|
||||||
|
// Invitations modal state
|
||||||
|
let invitationsModalOpen = $state(false)
|
||||||
|
|
||||||
|
// Query for pending invitations (only when authenticated)
|
||||||
|
const pendingInvitationsQuery = createQuery(() => ({
|
||||||
|
...crewQueries.pendingInvitations(),
|
||||||
|
enabled: isAuth
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Derived count of pending invitations
|
||||||
|
const pendingInvitationCount = $derived(pendingInvitationsQuery.data?.length ?? 0)
|
||||||
|
|
||||||
// Handle logout
|
// Handle logout
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -223,16 +239,23 @@
|
||||||
aria-label="Your account"
|
aria-label="Your account"
|
||||||
class="profile-link"
|
class="profile-link"
|
||||||
>
|
>
|
||||||
{#if avatarSrc}
|
<span class="avatar-container">
|
||||||
<img
|
{#if avatarSrc}
|
||||||
src={avatarSrc}
|
<img
|
||||||
srcset={avatarSrcSet}
|
src={avatarSrc}
|
||||||
alt={username}
|
srcset={avatarSrcSet}
|
||||||
class="user-avatar"
|
alt={username}
|
||||||
width="24"
|
class="user-avatar"
|
||||||
height="24"
|
width="24"
|
||||||
/>
|
height="24"
|
||||||
{/if}
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if pendingInvitationCount > 0}
|
||||||
|
<span class="avatar-badge">
|
||||||
|
<NotificationBadge count={pendingInvitationCount} />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
<span>{username}</span>
|
<span>{username}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -270,6 +293,14 @@
|
||||||
<DropdownItem>
|
<DropdownItem>
|
||||||
<a href={crewHref}>Crew</a>
|
<a href={crewHref}>Crew</a>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
<DropdownItem>
|
||||||
|
<button class="dropdown-button-with-badge" onclick={() => (invitationsModalOpen = true)}>
|
||||||
|
<span>Invitations</span>
|
||||||
|
{#if pendingInvitationCount > 0}
|
||||||
|
<NotificationBadge count={pendingInvitationCount} showCount />
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</DropdownItem>
|
||||||
{/if}
|
{/if}
|
||||||
<DropdownItem>
|
<DropdownItem>
|
||||||
<button onclick={() => (settingsModalOpen = true)}>
|
<button onclick={() => (settingsModalOpen = true)}>
|
||||||
|
|
@ -333,6 +364,15 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Invitations Modal -->
|
||||||
|
{#if isAuth}
|
||||||
|
<InvitationsModal
|
||||||
|
bind:open={invitationsModalOpen}
|
||||||
|
invitations={pendingInvitationsQuery.data ?? []}
|
||||||
|
isLoading={pendingInvitationsQuery.isLoading}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '$src/themes/colors' as colors;
|
@use '$src/themes/colors' as colors;
|
||||||
@use '$src/themes/effects' as effects;
|
@use '$src/themes/effects' as effects;
|
||||||
|
|
@ -499,12 +539,34 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: spacing.$unit-half;
|
gap: spacing.$unit-half;
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown button with badge (for Invitations)
|
||||||
|
:global(.dropdown-button-with-badge) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
gap: spacing.$unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style the nav buttons to match link dimensions
|
// Style the nav buttons to match link dimensions
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue