feat: hosting access tab (#5995)

* feat: implement access tab with dummy data

* fix: spacing

* feat: qa

* feat: implement backend

* qa: qa pass

* feat: fix user "search"

* fix: lint

* feat: change to bitfield

* feat: fix fields

* fix: lint

* fix: lint

* feat: hook up api

* feat: fix permissions

* feat: audit log table event start

* feat: better mobile mode for audit log table

* feat: i18n

* feat: qa

* feat: enforce permissions

* feat: email template start

* feat: qa

* fix: tooltip bug

* feat: qa

* impl: sse support in api-client

* feat: sse impl

* fix: desync path

* feat: time frame picker from analytics

* feat: QA

* fix: spacing

* fix: permisison audit log entries

* fix: hosting manage page shared server detection

* fix: lint

* feat: qa + lint

* feat: audit log table sort by time

* feat: finish frontend panel stuff

* fix: lint

* fix: backend alignment

* fix: lint

* fix: supress friend errors

* feat: qa

* fix: qa

* fix: lint

* fix: utils barrel

* fix: safari cookies in dev

* fix: pin nuxt

* feat: fixes + notif fix

* fix: notifications

* feat: qa

* fix: notification sync not happening immediately

* fix: qa

* fix: qa

* feat: qa

* blog + prepr

* feat: toast shit

* blog images

* thumbnail update one last time

* prepr

* feat: use reinvite route

* update images

* fix: reinvite stuff

* fix: lint

* fix: alignment of save bar

* fix: notif sizing

* fix: split up access

* fix: lint

* fix: lint

* fix: link

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-06-04 15:58:01 +00:00
committed by GitHub
co-authored by Prospector
parent 58ad58f958
commit bd97ace974
227 changed files with 15578 additions and 2153 deletions
@@ -13,7 +13,38 @@
@mouseenter="stopTimer(item)"
@mouseleave="setNotificationTimer(item)"
>
<NotificationToast
v-if="item.toast"
:type="item.toast.type"
:actor-name="item.toast.actorName"
:actor-avatar-url="item.toast.actorAvatarUrl"
:entity-name="item.toast.entityName"
:entity-icon-url="item.toast.entityIconUrl"
:status-text="item.toast.statusText"
:progress="item.toast.progress"
:waiting="item.toast.waiting"
@accept="handleToastAction(item, item.toast.onAccept)"
@decline="handleToastAction(item, item.toast.onDecline)"
@dismiss="handleToastAction(item, item.toast.onDismiss)"
@launch="handleToastAction(item, item.toast.onLaunch)"
@open-actor="item.toast.onOpenActor?.()"
@open-instance="handleToastAction(item, item.toast.onOpenInstance)"
/>
<div v-else-if="isDownloadNotification(item)" class="flex flex-col gap-4">
<NotificationToast
v-for="progressItem in downloadToastItems(item)"
:key="progressItem.id"
type="instance-download"
:entity-name="progressItem.title || item.title"
:entity-icon-url="progressItem.iconUrl ?? item.iconUrl ?? MinecraftServerIcon"
:status-text="downloadStatusText(progressItem)"
:progress="progressItem.progress"
:waiting="progressItem.waiting"
@dismiss="dismiss(item.id)"
/>
</div>
<div
v-else
class="flex w-full flex-col gap-3 overflow-hidden rounded-2xl bg-bg-raised shadow-xl border-surface-5 border-solid border p-4"
>
<div class="flex flex-col gap-2 w-full">
@@ -118,6 +149,7 @@ import {
DownloadIcon,
InfoIcon,
IssuesIcon,
MinecraftServerIcon,
XCircleIcon,
XIcon,
} from '@modrinth/assets'
@@ -127,9 +159,11 @@ import {
injectPopupNotificationManager,
type PopupNotification,
type PopupNotificationButton,
type PopupNotificationProgressItem,
} from '../../providers'
import ButtonStyled from '../base/ButtonStyled.vue'
import ProgressBar from '../base/ProgressBar.vue'
import NotificationToast from '../notifications/NotificationToast.vue'
const popupNotificationManager = injectPopupNotificationManager()
const notifications = computed<PopupNotification[]>(() =>
@@ -141,6 +175,34 @@ const setNotificationTimer = (n: PopupNotification) =>
popupNotificationManager.setNotificationTimer(n)
const dismiss = (id: string | number) => popupNotificationManager.removeNotification(id)
function isDownloadNotification(item: PopupNotification) {
return (
item.type === 'download' &&
(!!item.progressItems?.length || item.progress != null || item.waiting)
)
}
function downloadToastItems(item: PopupNotification): PopupNotificationProgressItem[] {
if (item.progressItems?.length) {
return item.progressItems
}
return [
{
id: `${item.id}`,
title: item.title,
text: item.text,
iconUrl: item.iconUrl,
progress: item.progress ?? 0,
waiting: item.waiting ?? false,
},
]
}
function downloadStatusText(progressItem: PopupNotificationProgressItem) {
return progressItem.text?.replace(/^\d+%\s*/, '') ?? ''
}
function handleButtonClick(id: string | number, btn: PopupNotificationButton) {
btn.action()
if (!btn.keepOpen) {
@@ -148,6 +210,11 @@ function handleButtonClick(id: string | number, btn: PopupNotificationButton) {
}
}
async function handleToastAction(item: PopupNotification, action?: () => void | Promise<void>) {
popupNotificationManager.removeNotification(item.id)
await action?.()
}
function progressColorForType(type: PopupNotification['type']) {
if (type === 'error') {
return 'red'
@@ -179,8 +246,9 @@ withDefaults(
top: calc(var(--top-bar-height, 3rem) + 1.5rem);
right: 1.5rem;
z-index: 200;
width: 520px;
max-width: calc(100vw - 3rem);
width: min(420px, calc(100vw - 1.5rem));
min-width: min(420px, calc(100vw - 1.5rem));
max-width: min(420px, calc(100vw - 1.5rem));
display: flex;
flex-direction: column;
gap: 0.75rem;
@@ -192,8 +260,6 @@ withDefaults(
@media screen and (max-width: 500px) {
.popup-notification-group {
width: calc(100% - 1.5rem);
max-width: none;
right: 0.75rem;
}
}