mirror of
https://github.com/modrinth/code.git
synced 2026-09-05 06:19:11 +00:00
qa: shared instances post release (#6893)
* fix: 20 user cap * qa: invite modal dropdown + other + clamp limits * fix: hide filters if just one type * feat: update req banner * feat: invite management frontend * fix: moderation issues with shared instances * feat: instance icon improvements * fix: better offline handling * fix: lint * feat: show avatar in install to play modal * feat: smaller qa points * fix: fmt * fix: fmt * fix: yeet svg * fix: 50 user limit
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
<UpdateToPlayModal ref="updateToPlayModal" :instance="instance" />
|
||||
<SharedInstanceUpdateModal
|
||||
ref="sharedInstanceUpdateModal"
|
||||
@accepted="hideAcceptedSharedInstanceUpdate"
|
||||
@complete="handleSharedInstanceUpdateComplete"
|
||||
@shared-instance-unavailable="handleSharedInstanceUnavailable"
|
||||
@report="(event) => reportSharedInstance(event, true)"
|
||||
/>
|
||||
@@ -62,8 +64,10 @@
|
||||
:shared-instance-expected-user-id="sharedInstanceExpectedUserId"
|
||||
:shared-instance-role="instance.shared_instance?.role"
|
||||
:shared-instance-signed-out="sharedInstanceSignedOut"
|
||||
:shared-instance-update-available="showSharedInstanceUpdateAdmonition"
|
||||
@published="fetchInstance"
|
||||
@delete="requestInstanceDeletion"
|
||||
@review-update="reviewSharedInstanceUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div :class="['p-6 pt-4', { 'min-h-0 flex-1 overflow-y-auto': isFixedRender }]">
|
||||
@@ -223,6 +227,7 @@ const sharedInstanceUpdateModal = ref<InstanceType<typeof SharedInstanceUpdateMo
|
||||
const sharedInstanceReportModal = ref<InstanceType<typeof SharedInstanceInstallModal>>()
|
||||
const deleteConfirmModal = ref<InstanceType<typeof ConfirmDeleteInstanceModal>>()
|
||||
const selectedInstanceToDelete = ref<GameInstance | null>(null)
|
||||
const hiddenSharedInstanceUpdateKey = ref<string | null>(null)
|
||||
|
||||
const { notifySharedInstanceError, notifySharedInstanceUnavailable } = useSharedInstanceErrors()
|
||||
|
||||
@@ -253,8 +258,19 @@ const {
|
||||
signedOut: sharedInstanceSignedOut,
|
||||
unavailableManager: sharedInstanceUnavailableManager,
|
||||
unavailableReason: sharedInstanceUnavailableReason,
|
||||
updatePreview: sharedInstanceUpdatePreview,
|
||||
wrongAccount: sharedInstanceWrongAccount,
|
||||
} = sharedInstanceState
|
||||
const sharedInstanceUpdateKey = computed(() => {
|
||||
const instanceId = instance.value?.id
|
||||
const latestVersion = sharedInstanceUpdatePreview.value?.latestVersion
|
||||
return instanceId && latestVersion !== undefined ? `${instanceId}:${latestVersion}` : null
|
||||
})
|
||||
const showSharedInstanceUpdateAdmonition = computed(
|
||||
() =>
|
||||
sharedInstanceUpdatePreview.value?.updateAvailable === true &&
|
||||
sharedInstanceUpdateKey.value !== hiddenSharedInstanceUpdateKey.value,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
@@ -530,6 +546,37 @@ async function handleSharedInstanceUnavailable(
|
||||
setSharedInstanceUnavailable(reason)
|
||||
}
|
||||
|
||||
function reviewSharedInstanceUpdate(event: MouseEvent) {
|
||||
const currentInstance = instance.value
|
||||
const preview = sharedInstanceUpdatePreview.value
|
||||
if (
|
||||
!currentInstance ||
|
||||
currentInstance.shared_instance?.role !== 'member' ||
|
||||
!preview?.updateAvailable
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
sharedInstanceUpdateModal.value?.show(
|
||||
currentInstance,
|
||||
preview,
|
||||
async () => {
|
||||
await fetchInstance()
|
||||
},
|
||||
event,
|
||||
)
|
||||
}
|
||||
|
||||
function hideAcceptedSharedInstanceUpdate() {
|
||||
hiddenSharedInstanceUpdateKey.value = sharedInstanceUpdateKey.value
|
||||
}
|
||||
|
||||
function handleSharedInstanceUpdateComplete(successful: boolean) {
|
||||
if (!successful && hiddenSharedInstanceUpdateKey.value === sharedInstanceUpdateKey.value) {
|
||||
hiddenSharedInstanceUpdateKey.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const startInstance = async (context: string) => {
|
||||
if (!instance.value || instance.value.quarantined) return
|
||||
if (checkingSharedInstanceLaunch.value || loading.value || playing.value) return
|
||||
@@ -540,17 +587,16 @@ const startInstance = async (context: string) => {
|
||||
!!instance.value.shared_instance && !sharedInstanceActionsLocked.value && !offline.value
|
||||
|
||||
if (canCheckSharedInstanceUpdate) {
|
||||
let preview: Awaited<ReturnType<typeof refreshSharedInstanceUpdatePreview>>
|
||||
let preview: Awaited<ReturnType<typeof refreshSharedInstanceUpdatePreview>> = null
|
||||
checkingSharedInstanceLaunch.value = true
|
||||
try {
|
||||
preview = await refreshSharedInstanceUpdatePreview()
|
||||
} catch (error) {
|
||||
if (isSharedInstanceUnavailableError(error)) {
|
||||
await handleSharedInstanceUnavailable(getSharedInstanceUnavailableReason(error))
|
||||
} else {
|
||||
notifySharedInstanceError(error)
|
||||
return
|
||||
}
|
||||
return
|
||||
notifySharedInstanceError(error)
|
||||
} finally {
|
||||
checkingSharedInstanceLaunch.value = false
|
||||
}
|
||||
@@ -657,7 +703,7 @@ async function reportSharedInstance(event?: MouseEvent, closeUpdateModal = false
|
||||
if (closeUpdateModal) sharedInstanceUpdateModal.value?.hide()
|
||||
sharedInstanceReportModal.value?.showReport(preview, event)
|
||||
} catch (error) {
|
||||
handleError(error as Error)
|
||||
notifySharedInstanceError(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,19 @@
|
||||
:link="inviteLink.link.value"
|
||||
:link-expires-at="inviteLink.details.value?.expiresAt"
|
||||
:link-max-uses="inviteLink.details.value?.maxUses"
|
||||
:link-max-uses-limit="remainingUserSlots"
|
||||
:update-invite-link="inviteLink.update"
|
||||
:user-profile-link="userProfileLink"
|
||||
:can-invite="!members.exclusiveMutationPending.value && !inviteLink.pending.value"
|
||||
:can-invite="
|
||||
hasRemainingUserSlots &&
|
||||
!members.exclusiveMutationPending.value &&
|
||||
!inviteLink.pending.value
|
||||
"
|
||||
:invite-disabled-message="
|
||||
hasRemainingUserSlots
|
||||
? undefined
|
||||
: formatMessage(messages.userLimitReached, { limit: SHARED_INSTANCE_USER_LIMIT })
|
||||
"
|
||||
@invite="invitePlayer"
|
||||
@cancel="cancelInvite"
|
||||
/>
|
||||
@@ -37,10 +47,19 @@
|
||||
@state-change="publishState = $event"
|
||||
/>
|
||||
|
||||
<SharedInstanceShareEmptyState
|
||||
v-if="unableToConnect"
|
||||
:heading="formatMessage(messages.unableToConnectHeading)"
|
||||
:description="formatMessage(messages.unableToConnectDescription)"
|
||||
/>
|
||||
|
||||
<div v-else-if="membersTableLoading" class="h-64" aria-hidden="true" />
|
||||
|
||||
<SharedInstanceMembersTable
|
||||
v-if="members.rows.value.length > 0"
|
||||
v-else-if="showMembersTable"
|
||||
:rows="members.rows.value"
|
||||
:actions-locked="sharedInstanceActionsLocked"
|
||||
:invite-disabled="!hasRemainingUserSlots"
|
||||
:invite-pending="inviteLink.pending.value"
|
||||
:push-update-disabled="
|
||||
instance.install_stage !== 'installed' || publishState !== 'idle' || !!offline
|
||||
@@ -107,7 +126,7 @@
|
||||
<ButtonStyled color="brand"
|
||||
><button
|
||||
class="!h-10"
|
||||
:disabled="inviteLink.pending.value"
|
||||
:disabled="inviteLink.pending.value || !hasRemainingUserSlots"
|
||||
@click="showInvitePlayers($event)"
|
||||
>
|
||||
<SpinnerIcon
|
||||
@@ -137,16 +156,17 @@ import {
|
||||
type InvitePlayersUser,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { useQueryClient } from '@tanstack/vue-query'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, ref, toRef, watch } from 'vue'
|
||||
|
||||
import ModrinthAccountRequiredModal from '@/components/ui/modal/ModrinthAccountRequiredModal.vue'
|
||||
import SharedInstancePublishModal from '@/components/ui/shared-instances/SharedInstancePublishModal.vue'
|
||||
import {
|
||||
getSharedInstanceUnavailableReason,
|
||||
isSharedInstancesApiError,
|
||||
isSharedInstanceUnavailableError,
|
||||
} from '@/helpers/install'
|
||||
import { edit } from '@/helpers/instance'
|
||||
import { can_current_user_use_shared_instances, edit } from '@/helpers/instance'
|
||||
import type { ModrinthAuthFlow } from '@/helpers/mr_auth.ts'
|
||||
import {
|
||||
sharedInstanceErrorMessages,
|
||||
@@ -159,7 +179,7 @@ import { injectSharedInstanceState } from '../use-shared-instance-state'
|
||||
import SharedInstanceMembersTable from './shared-instance-members-table.vue'
|
||||
import SharedInstanceRemoveMemberModal from './shared-instance-remove-member-modal.vue'
|
||||
import SharedInstanceShareEmptyState from './shared-instance-share-empty-state.vue'
|
||||
import type { ShareRow } from './shared-instance-share-types'
|
||||
import { SHARED_INSTANCE_USER_LIMIT, type ShareRow } from './shared-instance-share-types'
|
||||
import { useSharedInstanceInviteCandidates } from './use-shared-instance-invite-candidates'
|
||||
import { useSharedInstanceInviteLink } from './use-shared-instance-invite-link'
|
||||
import { useSharedInstanceMembers } from './use-shared-instance-members'
|
||||
@@ -182,6 +202,7 @@ const actionsLocked = sharedInstanceState.shareActionsLocked
|
||||
const sharedInstanceActionsLocked = actionsLocked
|
||||
const currentUserId = computed(() => auth.user.value?.id ?? null)
|
||||
const isSignedIn = computed(() => !!auth.session_token.value)
|
||||
const sharedInstancesApiUnavailable = ref(false)
|
||||
const accountRequiredModal = ref<InstanceType<typeof ModrinthAccountRequiredModal>>()
|
||||
const invitePlayersModal = ref<InstanceType<typeof InvitePlayersModal>>()
|
||||
const unlinkModal = ref<InstanceType<typeof ConfirmUnlinkModal>>()
|
||||
@@ -198,10 +219,22 @@ function notifyOperationError(error: unknown) {
|
||||
sharedInstanceState.unavailableManager.value,
|
||||
)
|
||||
} else {
|
||||
if (isSharedInstancesApiError(error)) sharedInstancesApiUnavailable.value = true
|
||||
notifySharedInstanceError(error)
|
||||
}
|
||||
}
|
||||
|
||||
const eligibilityQuery = useQuery({
|
||||
queryKey: computed(() => ['shared-instance-eligibility', currentUserId.value]),
|
||||
queryFn: can_current_user_use_shared_instances,
|
||||
enabled: () => isSignedIn.value && !!currentUserId.value,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
refetchOnMount: 'always',
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
})
|
||||
|
||||
const members = useSharedInstanceMembers({
|
||||
instance,
|
||||
currentUserId,
|
||||
@@ -209,6 +242,10 @@ const members = useSharedInstanceMembers({
|
||||
actionsLocked,
|
||||
onError: notifyOperationError,
|
||||
})
|
||||
const remainingUserSlots = computed(() =>
|
||||
Math.max(0, SHARED_INSTANCE_USER_LIMIT - members.rows.value.length),
|
||||
)
|
||||
const hasRemainingUserSlots = computed(() => remainingUserSlots.value > 0)
|
||||
const {
|
||||
inviteFriends,
|
||||
search: searchInviteUsers,
|
||||
@@ -221,6 +258,7 @@ const {
|
||||
})
|
||||
const inviteLink = useSharedInstanceInviteLink(
|
||||
computed(() => props.instance.id),
|
||||
remainingUserSlots,
|
||||
notifyOperationError,
|
||||
)
|
||||
|
||||
@@ -239,6 +277,29 @@ const lockedActionButton = computed(() =>
|
||||
const sharedInstanceUnavailableReason = sharedInstanceState.unavailableReason
|
||||
const sharedInstanceUnavailable = computed(() => !!sharedInstanceUnavailableReason.value)
|
||||
const sharedInstanceUnavailableManager = sharedInstanceState.unavailableManager
|
||||
const unableToConnect = computed(
|
||||
() =>
|
||||
sharedInstancesApiUnavailable.value ||
|
||||
isSharedInstancesApiError(eligibilityQuery.error.value) ||
|
||||
isSharedInstancesApiError(members.query.error.value),
|
||||
)
|
||||
const membersTableLoading = computed(
|
||||
() =>
|
||||
members.rows.value.length === 0 &&
|
||||
!!props.instance.shared_instance &&
|
||||
(members.query.data.value === undefined || members.query.isFetching.value) &&
|
||||
!sharedInstanceUnavailable.value &&
|
||||
!sharedInstanceActionsLocked.value,
|
||||
)
|
||||
const showMembersTable = computed(
|
||||
() =>
|
||||
members.rows.value.length > 0 ||
|
||||
(!!props.instance.shared_instance &&
|
||||
members.query.data.value !== undefined &&
|
||||
!members.query.isFetching.value &&
|
||||
!sharedInstanceUnavailable.value &&
|
||||
!sharedInstanceActionsLocked.value),
|
||||
)
|
||||
const requiresUnlink = computed(
|
||||
() =>
|
||||
props.instance.link?.type === 'imported_modpack' &&
|
||||
@@ -253,6 +314,15 @@ const importedModpackBackupTip = computed(() =>
|
||||
|
||||
const messages = defineMessages({
|
||||
signInButton: { id: 'app.instance.share.sign-in.button', defaultMessage: 'Sign in' },
|
||||
unableToConnectHeading: {
|
||||
id: 'app.instance.share.unable-to-connect.heading',
|
||||
defaultMessage: 'Unable to connect',
|
||||
},
|
||||
unableToConnectDescription: {
|
||||
id: 'app.instance.share.unable-to-connect.description',
|
||||
defaultMessage:
|
||||
'The shared instances service is not accessible at the moment, please try again later',
|
||||
},
|
||||
noFriendsInvitedHeading: {
|
||||
id: 'app.instance.share.empty.heading',
|
||||
defaultMessage: 'No friends invited',
|
||||
@@ -265,6 +335,10 @@ const messages = defineMessages({
|
||||
id: 'app.instance.share.empty.invite-friends-button',
|
||||
defaultMessage: 'Invite friends',
|
||||
},
|
||||
userLimitReached: {
|
||||
id: 'app.instance.share.invite-modal.user-limit-reached',
|
||||
defaultMessage: 'This instance has reached the {limit}-user limit.',
|
||||
},
|
||||
shareModalHeader: {
|
||||
id: 'app.instance.share.invite-modal.heading',
|
||||
defaultMessage: 'Share {name}',
|
||||
@@ -304,7 +378,7 @@ const messages = defineMessages({
|
||||
})
|
||||
|
||||
function invitePlayer(payload: InvitePlayersInvitePayload) {
|
||||
if (actionsLocked.value) return
|
||||
if (actionsLocked.value || !hasRemainingUserSlots.value) return
|
||||
if (payload.source === 'search') void requestFriend(payload.user)
|
||||
members.invite(payload.user)
|
||||
}
|
||||
@@ -315,6 +389,7 @@ function cancelInvite(user: InvitePlayersUser) {
|
||||
async function showInvitePlayers(event?: MouseEvent) {
|
||||
if (actionsLocked.value) return
|
||||
if (!isSignedIn.value) return signInToShare(event)
|
||||
if (!hasRemainingUserSlots.value) return
|
||||
if (requiresUnlink.value) return unlinkModal.value?.show()
|
||||
if (await inviteLink.ensure()) invitePlayersModal.value?.show(event)
|
||||
}
|
||||
@@ -353,6 +428,20 @@ function signInToShare(event?: MouseEvent) {
|
||||
void accountRequiredModal.value?.show(event)
|
||||
}
|
||||
|
||||
watch(
|
||||
[eligibilityQuery.error, members.query.error],
|
||||
(errors) => {
|
||||
for (const error of errors) {
|
||||
if (isSharedInstancesApiError(error)) notifyOperationError(error)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
watch([eligibilityQuery.data, members.query.data], ([eligibility, memberRows]) => {
|
||||
if (eligibility !== undefined && memberRows !== undefined) {
|
||||
sharedInstancesApiUnavailable.value = false
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => props.instance.id,
|
||||
() => {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<ButtonStyled color="brand">
|
||||
<button
|
||||
class="flex !h-10 shrink-0 items-center gap-2"
|
||||
:disabled="invitePending"
|
||||
:disabled="invitePending || inviteDisabled"
|
||||
@click="emit('invite', $event)"
|
||||
>
|
||||
<SpinnerIcon v-if="invitePending" class="animate-spin" aria-hidden="true" />
|
||||
@@ -35,7 +35,7 @@
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<div v-if="hasMultipleMethods" class="flex flex-wrap items-center gap-1.5">
|
||||
<FilterIcon class="size-5 shrink-0 text-secondary" aria-hidden="true" />
|
||||
<button
|
||||
:class="filterClass(methodFilter === 'all')"
|
||||
@@ -67,7 +67,9 @@
|
||||
>
|
||||
<template #empty-state
|
||||
><div class="flex h-64 items-center justify-center text-secondary">
|
||||
No users match your filters.
|
||||
{{
|
||||
formatMessage(rows.length === 0 ? messages.noUsersJoined : messages.noUsersMatchFilters)
|
||||
}}
|
||||
</div></template
|
||||
>
|
||||
<template #cell-username="{ row }">
|
||||
@@ -157,7 +159,7 @@ import {
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
type MethodFilter,
|
||||
@@ -170,6 +172,7 @@ import {
|
||||
const props = defineProps<{
|
||||
rows: ShareRow[]
|
||||
actionsLocked?: boolean
|
||||
inviteDisabled?: boolean
|
||||
invitePending?: boolean
|
||||
pushUpdateDisabled?: boolean
|
||||
pushUpdatePending?: boolean
|
||||
@@ -191,6 +194,7 @@ const methodFilterOptions: Array<{ id: ShareMethod; label: string }> = [
|
||||
{ id: 'direct', label: methodLabels.direct },
|
||||
{ id: 'link', label: methodLabels.link },
|
||||
]
|
||||
const hasMultipleMethods = computed(() => new Set(props.rows.map((row) => row.method)).size > 1)
|
||||
const columns = computed<TableColumn<ShareTableColumn>[]>(() => {
|
||||
const result: TableColumn<ShareTableColumn>[] = [
|
||||
{
|
||||
@@ -286,11 +290,23 @@ function filterClass(active: boolean) {
|
||||
]
|
||||
}
|
||||
|
||||
watch(hasMultipleMethods, (multiple) => {
|
||||
if (!multiple) methodFilter.value = 'all'
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
pushUpdate: {
|
||||
id: 'app.instance.admonitions.shared-instance.publish-button',
|
||||
defaultMessage: 'Push update',
|
||||
},
|
||||
noUsersJoined: {
|
||||
id: 'app.instance.share.members.empty',
|
||||
defaultMessage: 'No users have joined yet',
|
||||
},
|
||||
noUsersMatchFilters: {
|
||||
id: 'app.instance.share.members.no-filter-results',
|
||||
defaultMessage: 'No users match your filters.',
|
||||
},
|
||||
})
|
||||
function userProfileLink(username: string) {
|
||||
return !username || username.includes('@') ? undefined : `/user/${encodeURIComponent(username)}`
|
||||
|
||||
@@ -2,6 +2,8 @@ export type ShareMethod = 'direct' | 'link'
|
||||
export type MethodFilter = ShareMethod | 'all'
|
||||
export type ShareTableColumn = 'username' | 'lastPlayed' | 'joined' | 'method' | 'actions'
|
||||
|
||||
export const SHARED_INSTANCE_USER_LIMIT = 50
|
||||
|
||||
export type ShareRow = {
|
||||
id: string
|
||||
username: string
|
||||
|
||||
@@ -5,8 +5,11 @@ import { config } from '@/config'
|
||||
import { toError } from '@/helpers/errors'
|
||||
import { create_shared_instance_invite_link } from '@/helpers/instance'
|
||||
|
||||
const DEFAULT_INVITE_LINK_MAX_USES = 10
|
||||
|
||||
export function useSharedInstanceInviteLink(
|
||||
instanceId: Ref<string>,
|
||||
maxInviteUses: Ref<number>,
|
||||
onError: (error: unknown) => void,
|
||||
) {
|
||||
const details = ref<Awaited<ReturnType<typeof create_shared_instance_invite_link>>>()
|
||||
@@ -20,10 +23,12 @@ export function useSharedInstanceInviteLink(
|
||||
async function ensure() {
|
||||
if (details.value) return true
|
||||
if (pending.value) return false
|
||||
const maxUses = Math.min(DEFAULT_INVITE_LINK_MAX_USES, Math.floor(maxInviteUses.value))
|
||||
if (maxUses <= 0) return false
|
||||
|
||||
pending.value = true
|
||||
try {
|
||||
details.value = await create_shared_instance_invite_link(instanceId.value)
|
||||
details.value = await create_shared_instance_invite_link(instanceId.value, { maxUses })
|
||||
return true
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
@@ -35,6 +40,8 @@ export function useSharedInstanceInviteLink(
|
||||
|
||||
async function update(settings: InviteLinkSettings) {
|
||||
if (!details.value) return
|
||||
const maxInviteLinkUses = Math.floor(maxInviteUses.value)
|
||||
if (maxInviteLinkUses <= 0) return
|
||||
|
||||
pending.value = true
|
||||
try {
|
||||
@@ -44,7 +51,7 @@ export function useSharedInstanceInviteLink(
|
||||
)
|
||||
details.value = await create_shared_instance_invite_link(instanceId.value, {
|
||||
maxAgeSeconds,
|
||||
maxUses: settings.maxUses,
|
||||
maxUses: Math.min(settings.maxUses, maxInviteLinkUses),
|
||||
replaceInviteId: details.value.inviteId,
|
||||
})
|
||||
} catch (error) {
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
} from '@/helpers/instance'
|
||||
import type { GameInstance } from '@/helpers/types'
|
||||
|
||||
import { normalizeInviteKey, type ShareRow } from './shared-instance-share-types'
|
||||
import {
|
||||
normalizeInviteKey,
|
||||
SHARED_INSTANCE_USER_LIMIT,
|
||||
type ShareRow,
|
||||
} from './shared-instance-share-types'
|
||||
|
||||
type MembersQueryKey = readonly ['sharedInstanceUsers', string]
|
||||
|
||||
@@ -54,6 +58,7 @@ export function useSharedInstanceMembers(options: {
|
||||
queryFn: ({ queryKey }) => fetchRows(queryKey),
|
||||
enabled: () =>
|
||||
options.isSignedIn.value && !!options.instance.value.id && !options.actionsLocked.value,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
refetchOnMount: 'always',
|
||||
refetchOnReconnect: false,
|
||||
@@ -139,6 +144,7 @@ export function useSharedInstanceMembers(options: {
|
||||
if (
|
||||
options.actionsLocked.value ||
|
||||
exclusiveMutationPending.value ||
|
||||
rows.value.length >= SHARED_INSTANCE_USER_LIMIT ||
|
||||
invitingUserIds.has(normalizedId) ||
|
||||
find(user.id, user.username)
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user