fix: 20 user cap

This commit is contained in:
Calum H. (IMB11)
2026-07-27 12:03:29 +01:00
parent 372e2bf47b
commit af9cb8a625
7 changed files with 70 additions and 19 deletions
@@ -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"
/>
@@ -41,6 +51,7 @@
v-if="members.rows.value.length > 0"
: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 +118,7 @@
<ButtonStyled color="brand"
><button
class="!h-10"
:disabled="inviteLink.pending.value"
:disabled="inviteLink.pending.value || !hasRemainingUserSlots"
@click="showInvitePlayers($event)"
>
<SpinnerIcon
@@ -160,7 +171,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'
@@ -210,6 +221,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,
@@ -222,6 +237,7 @@ const {
})
const inviteLink = useSharedInstanceInviteLink(
computed(() => props.instance.id),
remainingUserSlots,
notifyOperationError,
)
@@ -266,6 +282,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}',
@@ -305,7 +325,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)
}
@@ -316,6 +336,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)
}
@@ -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" />
@@ -171,6 +171,7 @@ import {
const props = defineProps<{
rows: ShareRow[]
actionsLocked?: boolean
inviteDisabled?: boolean
invitePending?: boolean
pushUpdateDisabled?: boolean
pushUpdatePending?: boolean
@@ -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 = 20
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]
@@ -139,6 +143,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)
) {