Skip to content
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

fix: mark notifications as read #110

Merged
merged 5 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions protobufs/api/v1/users.proto
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ message SearchResponse {
}

message ListNotificationsRequest {
bool only_unread = 1;
PaginationRequest pagination = 2 [(buf.validate.field).required = true];
bool unread_only = 1;
bool mark_as_read = 2;
PaginationRequest pagination = 3 [(buf.validate.field).required = true];
}
message ListNotificationsResponse {
repeated Notification notifications = 1;
Expand Down
172 changes: 91 additions & 81 deletions server/pkg/pb/api/v1/users.pb.go

Large diffs are not rendered by default.

33 changes: 30 additions & 3 deletions server/pkg/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,12 @@ func ListNotificationsWithOnlyUnread(onlyUnread bool) ListNotificationsOpt {
}
}

func ListNotificationsOrderByCreatedAtDESC() ListNotificationsOpt {
return func() qm.QueryMod {
return qm.OrderBy(fmt.Sprintf("%s DESC", orm.NotificationColumns.CreatedAt))
}
}

func (r *Repo) ListNotifications(ctx context.Context, opts ...ListNotificationsOpt) (orm.NotificationSlice, error) {
query := make([]qm.QueryMod, 0, len(opts))
for _, opt := range opts {
Expand All @@ -1059,7 +1065,7 @@ func CountNotificationsWithUserID(userID string) CountNotificationsOpt {
}
}

func CountNotificationsWithOnlyUnread(onlyUnread bool) CountNotificationsOpt {
func CountNotificationsWithUnreadOnly(onlyUnread bool) CountNotificationsOpt {
return func() qm.QueryMod {
if !onlyUnread {
return nil
Expand All @@ -1086,8 +1092,29 @@ func (r *Repo) CountNotifications(ctx context.Context, opts ...CountNotification
return count, nil
}

func ListNotificationsOrderByCreatedAtDESC() ListNotificationsOpt {
type MarkNotificationsAsReadOpt func() qm.QueryMod

func MarkNotificationsAsReadByUserID(userID string) MarkNotificationsAsReadOpt {
return func() qm.QueryMod {
return qm.OrderBy(fmt.Sprintf("%s DESC", orm.NotificationColumns.CreatedAt))
return orm.NotificationWhere.UserID.EQ(userID)
}
}

func (r *Repo) MarkNotificationsAsRead(ctx context.Context, opts ...MarkNotificationsAsReadOpt) error {
query := make([]qm.QueryMod, 0, len(opts))
for _, opt := range opts {
query = append(query, opt())
}

if query == nil {
return nil
}

if _, err := orm.Notifications(query...).UpdateAll(ctx, r.executor(), orm.M{
orm.NotificationColumns.ReadAt: time.Now().UTC(),
}); err != nil {
return fmt.Errorf("notifications update: %w", err)
}

return nil
}
13 changes: 10 additions & 3 deletions server/rpc/v1/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,13 @@ func (h *userHandler) ListFollowees(ctx context.Context, req *connect.Request[v1
}, nil
}

func (h *userHandler) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) {
func (h *userHandler) ListNotifications(ctx context.Context, req *connect.Request[v1.ListNotificationsRequest]) (*connect.Response[v1.ListNotificationsResponse], error) { //nolint:cyclop // TODO: Simplify this method.
log := xcontext.MustExtractLogger(ctx)
userID := xcontext.MustExtractUserID(ctx)

total, err := h.repo.CountNotifications(ctx,
repo.CountNotificationsWithUserID(userID),
repo.CountNotificationsWithOnlyUnread(req.Msg.GetOnlyUnread()),
repo.CountNotificationsWithUnreadOnly(req.Msg.GetUnreadOnly()),
)
if err != nil {
log.Error("failed to count notifications", zap.Error(err))
Expand All @@ -175,7 +175,7 @@ func (h *userHandler) ListNotifications(ctx context.Context, req *connect.Reques
notifications, err := h.repo.ListNotifications(ctx,
repo.ListNotificationsWithLimit(limit+1),
repo.ListNotificationsWithUserID(userID),
repo.ListNotificationsWithOnlyUnread(req.Msg.GetOnlyUnread()),
repo.ListNotificationsWithOnlyUnread(req.Msg.GetUnreadOnly()),
repo.ListNotificationsOrderByCreatedAtDESC(),
)
if err != nil {
Expand Down Expand Up @@ -223,6 +223,13 @@ func (h *userHandler) ListNotifications(ctx context.Context, req *connect.Reques
return nil, connect.NewError(connect.CodeInternal, nil)
}

if req.Msg.GetMarkAsRead() {
if err = h.repo.MarkNotificationsAsRead(ctx, repo.MarkNotificationsAsReadByUserID(userID)); err != nil {
log.Error("failed to mark notifications as read", zap.Error(err))
return nil, connect.NewError(connect.CodeInternal, nil)
}
}

return &connect.Response[v1.ListNotificationsResponse]{
Msg: &v1.ListNotificationsResponse{
Notifications: parseNotificationSliceToPB(paginated.Items, payloads, users, workouts),
Expand Down
5 changes: 5 additions & 0 deletions web/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { useAuthStore } from '@/stores/auth'
import { useNotificationStore } from '@/stores/notifications.ts'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { RefreshAccessTokenOrLogout, ScheduleTokenRefresh } from '@/jwt/jwt'

Expand All @@ -18,9 +19,13 @@ app.use(pinia)

const init = async () => {
const authStore = useAuthStore()
const notificationStore = useNotificationStore()
if (authStore.accessToken) {
await RefreshAccessTokenOrLogout()
authStore.setAccessTokenRefreshInterval(ScheduleTokenRefresh())

await notificationStore.fetchUnreadNotifications()
notificationStore.setRefreshInterval()
}

app.mount('#app')
Expand Down
13 changes: 9 additions & 4 deletions web/src/proto/api/v1/users_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { Message } from "@bufbuild/protobuf";
* Describes the file api/v1/users.proto.
*/
export const file_api_v1_users: GenFile = /*@__PURE__*/
fileDesc("ChJhcGkvdjEvdXNlcnMucHJvdG8SBmFwaS52MSImCg5HZXRVc2VyUmVxdWVzdBIUCgJpZBgBIAEoCUIIukgFcgOwAQEiLQoPR2V0VXNlclJlc3BvbnNlEhoKBHVzZXIYASABKAsyDC5hcGkudjEuVXNlciIsCg1Gb2xsb3dSZXF1ZXN0EhsKCWZvbGxvd19pZBgBIAEoCUIIukgFcgOwAQEiEAoORm9sbG93UmVzcG9uc2UiMAoPVW5mb2xsb3dSZXF1ZXN0Eh0KC3VuZm9sbG93X2lkGAEgASgJQgi6SAVyA7ABASISChBVbmZvbGxvd1Jlc3BvbnNlIjUKFExpc3RGb2xsb3dlcnNSZXF1ZXN0Eh0KC2ZvbGxvd2VyX2lkGAEgASgJQgi6SAVyA7ABASI4ChVMaXN0Rm9sbG93ZXJzUmVzcG9uc2USHwoJZm9sbG93ZXJzGAEgAygLMgwuYXBpLnYxLlVzZXIiNQoUTGlzdEZvbGxvd2Vlc1JlcXVlc3QSHQoLZm9sbG93ZWVfaWQYASABKAlCCLpIBXIDsAEBIjgKFUxpc3RGb2xsb3dlZXNSZXNwb25zZRIfCglmb2xsb3dlZXMYASADKAsyDC5hcGkudjEuVXNlciJeCg1TZWFyY2hSZXF1ZXN0EhYKBXF1ZXJ5GAEgASgJQge6SARyAhADEjUKCnBhZ2luYXRpb24YAiABKAsyGS5hcGkudjEuUGFnaW5hdGlvblJlcXVlc3RCBrpIA8gBASJdCg5TZWFyY2hSZXNwb25zZRIbCgV1c2VycxgBIAMoCzIMLmFwaS52MS5Vc2VyEi4KCnBhZ2luYXRpb24YAiABKAsyGi5hcGkudjEuUGFnaW5hdGlvblJlc3BvbnNlImYKGExpc3ROb3RpZmljYXRpb25zUmVxdWVzdBITCgtvbmx5X3VucmVhZBgBIAEoCBI1CgpwYWdpbmF0aW9uGAIgASgLMhkuYXBpLnYxLlBhZ2luYXRpb25SZXF1ZXN0Qga6SAPIAQEieAoZTGlzdE5vdGlmaWNhdGlvbnNSZXNwb25zZRIrCg1ub3RpZmljYXRpb25zGAEgAygLMhQuYXBpLnYxLk5vdGlmaWNhdGlvbhIuCgpwYWdpbmF0aW9uGAIgASgLMhouYXBpLnYxLlBhZ2luYXRpb25SZXNwb25zZSLNAQoMTm90aWZpY2F0aW9uEgoKAmlkGAEgASgJEhgKEG5vdGlmaWVkX2F0X3VuaXgYAiABKAMSPgoPd29ya291dF9jb21tZW50GAMgASgLMiMuYXBpLnYxLk5vdGlmaWNhdGlvbi5Xb3Jrb3V0Q29tbWVudEgAGk8KDldvcmtvdXRDb21tZW50EhsKBWFjdG9yGAEgASgLMgwuYXBpLnYxLlVzZXISIAoHd29ya291dBgCIAEoCzIPLmFwaS52MS5Xb3Jrb3V0QgYKBHR5cGUylgQKC1VzZXJTZXJ2aWNlEjwKA0dldBIWLmFwaS52MS5HZXRVc2VyUmVxdWVzdBoXLmFwaS52MS5HZXRVc2VyUmVzcG9uc2UiBIi1GAESPQoGRm9sbG93EhUuYXBpLnYxLkZvbGxvd1JlcXVlc3QaFi5hcGkudjEuRm9sbG93UmVzcG9uc2UiBIi1GAESQwoIVW5mb2xsb3cSFy5hcGkudjEuVW5mb2xsb3dSZXF1ZXN0GhguYXBpLnYxLlVuZm9sbG93UmVzcG9uc2UiBIi1GAESUgoNTGlzdEZvbGxvd2VycxIcLmFwaS52MS5MaXN0Rm9sbG93ZXJzUmVxdWVzdBodLmFwaS52MS5MaXN0Rm9sbG93ZXJzUmVzcG9uc2UiBIi1GAESUgoNTGlzdEZvbGxvd2VlcxIcLmFwaS52MS5MaXN0Rm9sbG93ZWVzUmVxdWVzdBodLmFwaS52MS5MaXN0Rm9sbG93ZWVzUmVzcG9uc2UiBIi1GAESPQoGU2VhcmNoEhUuYXBpLnYxLlNlYXJjaFJlcXVlc3QaFi5hcGkudjEuU2VhcmNoUmVzcG9uc2UiBIi1GAESXgoRTGlzdE5vdGlmaWNhdGlvbnMSIC5hcGkudjEuTGlzdE5vdGlmaWNhdGlvbnNSZXF1ZXN0GiEuYXBpLnYxLkxpc3ROb3RpZmljYXRpb25zUmVzcG9uc2UiBIi1GAFCiwEKCmNvbS5hcGkudjFCClVzZXJzUHJvdG9QAVo4Z2l0aHViLmNvbS9jcmxzc24vZ2V0c3Ryb25nZXIvc2VydmVyL3BrZy9wYi9hcGkvdjE7YXBpdjGiAgNBWFiqAgZBcGkuVjHKAgZBcGlcVjHiAhJBcGlcVjFcR1BCTWV0YWRhdGHqAgdBcGk6OlYxYgZwcm90bzM", [file_api_v1_options, file_api_v1_shared, file_api_v1_workouts, file_google_protobuf_timestamp, file_buf_validate_validate]);
fileDesc("ChJhcGkvdjEvdXNlcnMucHJvdG8SBmFwaS52MSImCg5HZXRVc2VyUmVxdWVzdBIUCgJpZBgBIAEoCUIIukgFcgOwAQEiLQoPR2V0VXNlclJlc3BvbnNlEhoKBHVzZXIYASABKAsyDC5hcGkudjEuVXNlciIsCg1Gb2xsb3dSZXF1ZXN0EhsKCWZvbGxvd19pZBgBIAEoCUIIukgFcgOwAQEiEAoORm9sbG93UmVzcG9uc2UiMAoPVW5mb2xsb3dSZXF1ZXN0Eh0KC3VuZm9sbG93X2lkGAEgASgJQgi6SAVyA7ABASISChBVbmZvbGxvd1Jlc3BvbnNlIjUKFExpc3RGb2xsb3dlcnNSZXF1ZXN0Eh0KC2ZvbGxvd2VyX2lkGAEgASgJQgi6SAVyA7ABASI4ChVMaXN0Rm9sbG93ZXJzUmVzcG9uc2USHwoJZm9sbG93ZXJzGAEgAygLMgwuYXBpLnYxLlVzZXIiNQoUTGlzdEZvbGxvd2Vlc1JlcXVlc3QSHQoLZm9sbG93ZWVfaWQYASABKAlCCLpIBXIDsAEBIjgKFUxpc3RGb2xsb3dlZXNSZXNwb25zZRIfCglmb2xsb3dlZXMYASADKAsyDC5hcGkudjEuVXNlciJeCg1TZWFyY2hSZXF1ZXN0EhYKBXF1ZXJ5GAEgASgJQge6SARyAhADEjUKCnBhZ2luYXRpb24YAiABKAsyGS5hcGkudjEuUGFnaW5hdGlvblJlcXVlc3RCBrpIA8gBASJdCg5TZWFyY2hSZXNwb25zZRIbCgV1c2VycxgBIAMoCzIMLmFwaS52MS5Vc2VyEi4KCnBhZ2luYXRpb24YAiABKAsyGi5hcGkudjEuUGFnaW5hdGlvblJlc3BvbnNlInwKGExpc3ROb3RpZmljYXRpb25zUmVxdWVzdBITCgt1bnJlYWRfb25seRgBIAEoCBIUCgxtYXJrX2FzX3JlYWQYAiABKAgSNQoKcGFnaW5hdGlvbhgDIAEoCzIZLmFwaS52MS5QYWdpbmF0aW9uUmVxdWVzdEIGukgDyAEBIngKGUxpc3ROb3RpZmljYXRpb25zUmVzcG9uc2USKwoNbm90aWZpY2F0aW9ucxgBIAMoCzIULmFwaS52MS5Ob3RpZmljYXRpb24SLgoKcGFnaW5hdGlvbhgCIAEoCzIaLmFwaS52MS5QYWdpbmF0aW9uUmVzcG9uc2UizQEKDE5vdGlmaWNhdGlvbhIKCgJpZBgBIAEoCRIYChBub3RpZmllZF9hdF91bml4GAIgASgDEj4KD3dvcmtvdXRfY29tbWVudBgDIAEoCzIjLmFwaS52MS5Ob3RpZmljYXRpb24uV29ya291dENvbW1lbnRIABpPCg5Xb3Jrb3V0Q29tbWVudBIbCgVhY3RvchgBIAEoCzIMLmFwaS52MS5Vc2VyEiAKB3dvcmtvdXQYAiABKAsyDy5hcGkudjEuV29ya291dEIGCgR0eXBlMpYECgtVc2VyU2VydmljZRI8CgNHZXQSFi5hcGkudjEuR2V0VXNlclJlcXVlc3QaFy5hcGkudjEuR2V0VXNlclJlc3BvbnNlIgSItRgBEj0KBkZvbGxvdxIVLmFwaS52MS5Gb2xsb3dSZXF1ZXN0GhYuYXBpLnYxLkZvbGxvd1Jlc3BvbnNlIgSItRgBEkMKCFVuZm9sbG93EhcuYXBpLnYxLlVuZm9sbG93UmVxdWVzdBoYLmFwaS52MS5VbmZvbGxvd1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlcnMSHC5hcGkudjEuTGlzdEZvbGxvd2Vyc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vyc1Jlc3BvbnNlIgSItRgBElIKDUxpc3RGb2xsb3dlZXMSHC5hcGkudjEuTGlzdEZvbGxvd2Vlc1JlcXVlc3QaHS5hcGkudjEuTGlzdEZvbGxvd2Vlc1Jlc3BvbnNlIgSItRgBEj0KBlNlYXJjaBIVLmFwaS52MS5TZWFyY2hSZXF1ZXN0GhYuYXBpLnYxLlNlYXJjaFJlc3BvbnNlIgSItRgBEl4KEUxpc3ROb3RpZmljYXRpb25zEiAuYXBpLnYxLkxpc3ROb3RpZmljYXRpb25zUmVxdWVzdBohLmFwaS52MS5MaXN0Tm90aWZpY2F0aW9uc1Jlc3BvbnNlIgSItRgBQosBCgpjb20uYXBpLnYxQgpVc2Vyc1Byb3RvUAFaOGdpdGh1Yi5jb20vY3Jsc3NuL2dldHN0cm9uZ2VyL3NlcnZlci9wa2cvcGIvYXBpL3YxO2FwaXYxogIDQVhYqgIGQXBpLlYxygIGQXBpXFYx4gISQXBpXFYxXEdQQk1ldGFkYXRh6gIHQXBpOjpWMWIGcHJvdG8z", [file_api_v1_options, file_api_v1_shared, file_api_v1_workouts, file_google_protobuf_timestamp, file_buf_validate_validate]);

/**
* @generated from message api.v1.GetUserRequest
Expand Down Expand Up @@ -230,12 +230,17 @@ export const SearchResponseSchema: GenMessage<SearchResponse> = /*@__PURE__*/
*/
export type ListNotificationsRequest = Message<"api.v1.ListNotificationsRequest"> & {
/**
* @generated from field: bool only_unread = 1;
* @generated from field: bool unread_only = 1;
*/
onlyUnread: boolean;
unreadOnly: boolean;

/**
* @generated from field: api.v1.PaginationRequest pagination = 2;
* @generated from field: bool mark_as_read = 2;
*/
markAsRead: boolean;

/**
* @generated from field: api.v1.PaginationRequest pagination = 3;
*/
pagination?: PaginationRequest;
};
Expand Down
34 changes: 34 additions & 0 deletions web/src/stores/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { PaginationRequest } from '@/proto/api/v1/shared_pb.ts'

import { ref } from 'vue'
import { defineStore } from 'pinia'
import { create } from '@bufbuild/protobuf'
import { UserClient } from '@/clients/clients.ts'
import { ListNotificationsRequestSchema } from '@/proto/api/v1/users_pb.ts'

export const useNotificationStore = defineStore(
'notifications',
() => {
const unreadCount = ref(0)
const refreshInterval = ref(0)

const fetchUnreadNotifications = async () => {
const req = create(ListNotificationsRequestSchema, {
markAsRead: false,
pagination: {
pageLimit: 1,
} as PaginationRequest,
unreadOnly: true,
})
const res = await UserClient.listNotifications(req)
unreadCount.value = Number(res.pagination?.totalResults)
}

const setRefreshInterval = () => {
// TODO: Implement Server-Sent Events for real-time updates.
refreshInterval.value = window.setInterval(fetchUnreadNotifications, 60000)
}

return { fetchUnreadNotifications, setRefreshInterval, unreadCount }
}
)
32 changes: 6 additions & 26 deletions web/src/ui/components/NavigationMobile.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<script setup lang="ts">
import type { PaginationRequest } from '@/proto/api/v1/shared_pb.ts'

import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { create } from '@bufbuild/protobuf'
import { UserClient } from '@/clients/clients.ts'
import { ListNotificationsRequestSchema } from '@/proto/api/v1/users_pb.ts'
import { useNotificationStore } from '@/stores/notifications.ts'
import {
BellIcon,
BookOpenIcon,
Expand All @@ -22,7 +17,7 @@ import {
} from '@heroicons/vue/24/solid'

const route = useRoute()
const unreadCount = ref(0)
const notificationStore = useNotificationStore()

const isActive = (basePath: string) => {
return route.path.startsWith(basePath)
Expand All @@ -40,23 +35,6 @@ const navigation = [
{ href: '/notifications', icon: BellIcon, iconActive: BellSolidIcon, name: 'Notifications' },
{ href: '/profile', icon: UserIcon, iconActive: UserSolidIcon, name: 'Profile' },
]

const fetchUnreadNotifications = async () => {
const req = create(ListNotificationsRequestSchema, {
onlyUnread: true,
pagination: {
pageLimit: 1,
} as PaginationRequest
})
const res = await UserClient.listNotifications(req)
unreadCount.value = Number(res.pagination?.totalResults)
}

onMounted(() => {
fetchUnreadNotifications()
// TODO: Implement Server-Sent Events for real-time updates.
setInterval(fetchUnreadNotifications, 60000)
})
</script>

<template>
Expand All @@ -72,9 +50,11 @@ onMounted(() => {
class="h-6 w-6"
/>
<span
v-if="item.href === '/notifications'"
v-if="item.href === '/notifications' && notificationStore.unreadCount > 0"
class="badge"
>{{ unreadCount }}</span>
>
{{ notificationStore.unreadCount }}
</span>
</RouterLink>
</nav>
</template>
Expand Down
9 changes: 5 additions & 4 deletions web/src/ui/components/NotificationWorkoutComment.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Notification } from '@/proto/api/v1/users_pb.ts'

import { formatUnixToRelativeDateTime } from '@/utils/datetime.ts'
import { ChatBubbleLeftRightIcon } from '@heroicons/vue/24/outline'

const props = defineProps<{
Expand All @@ -13,23 +14,23 @@ const props = defineProps<{
:to="`/workouts/${props.notification.type.value?.workout?.id}`"
class="flex w-full items-center gap-x-3"
>
<ChatBubbleLeftRightIcon class="w-6 h-6" />
<div class="w-full">
<div>
<span class="font-medium">
<span class="font-semibold">
{{ props.notification.type.value?.actor?.firstName }}
{{ props.notification.type.value?.actor?.lastName }}
</span>
commented on your
<span class="font-medium">
<span class="font-semibold">
{{ props.notification.type.value?.workout?.name }}
</span>
workout
</div>
<p class="text-xs text-gray-700 mt-1">
1 week ago
{{ formatUnixToRelativeDateTime(props.notification.notifiedAtUnix) }}
</p>
</div>
<ChatBubbleLeftRightIcon class="w-6 h-6" />
</RouterLink>
</template>

Expand Down
10 changes: 7 additions & 3 deletions web/src/ui/notifications/ListNotifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import type { PaginationRequest } from '@/proto/api/v1/shared_pb.ts'
import { onMounted, ref } from 'vue'
import { create } from '@bufbuild/protobuf'
import { UserClient } from '@/clients/clients.ts'
import { useNotificationStore } from '@/stores/notifications.ts'
import NotificationWorkoutComment from '@/ui/components/NotificationWorkoutComment.vue'
import { ListNotificationsRequestSchema, type Notification } from '@/proto/api/v1/users_pb.ts'

const notifications = ref([] as Notification[])
const pageToken = ref(new Uint8Array(0))
const notificationStore = useNotificationStore()

const fetchUnreadNotifications = async () => {
const req = create(ListNotificationsRequestSchema, {
onlyUnread: false,
markAsRead: true,
pagination: {
pageLimit: 100,
pageToken: pageToken.value,
} as PaginationRequest,
unreadOnly: false,
})

const res = await UserClient.listNotifications(req)
Expand All @@ -28,8 +31,9 @@ const fetchUnreadNotifications = async () => {
}
}

onMounted(() => {
fetchUnreadNotifications()
onMounted(async () => {
await fetchUnreadNotifications()
await notificationStore.fetchUnreadNotifications()
})
</script>

Expand Down
7 changes: 7 additions & 0 deletions web/src/utils/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ export const formatToRelativeDateTime = (date: Timestamp | undefined): string =>
if (relative === '0 seconds ago') return 'Just now'
return relative
}

export const formatUnixToRelativeDateTime = (timestamp: bigint | undefined): string => {
if (!timestamp) return ''
const relative = DateTime.fromSeconds(Number(timestamp)).toRelative()
if (relative === '0 seconds ago') return 'Just now'
return relative
}