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
@@ -5,8 +5,10 @@
}}</span>
<Combobox
v-model="ctx.modpackSearchProjectId.value"
v-tooltip="ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined"
:options="ctx.modpackSearchOptions.value"
searchable
:disabled="ctx.finishDisabled.value"
:search-placeholder="formatMessage(messages.searchModpackPlaceholder)"
:no-options-message="
searchLoading
@@ -29,13 +31,23 @@
</div>
<div class="flex gap-3">
<ButtonStyled type="outlined">
<button class="flex-1" @click="triggerFileInput">
<button
v-tooltip="ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined"
class="flex-1"
:disabled="ctx.finishDisabled.value"
@click="triggerFileInput"
>
<ImportIcon />
{{ formatMessage(messages.importModpack) }}
</button>
</ButtonStyled>
<ButtonStyled color="brand">
<button class="flex-1" @click="ctx.browseModpacks()">
<button
v-tooltip="ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined"
class="flex-1"
:disabled="ctx.finishDisabled.value"
@click="ctx.browseModpacks()"
>
<CompassIcon />
{{ formatMessage(messages.browseModpacks) }}
</button>
@@ -87,6 +99,8 @@ const messages = defineMessages({
})
function proceedWithModpack() {
if (ctx.finishDisabled.value) return
debug('proceedWithModpack:', {
flowType: ctx.flowType,
modpackSelection: ctx.modpackSelection.value,
@@ -196,6 +210,8 @@ watch(
)
async function triggerFileInput() {
if (ctx.finishDisabled.value) return
const picked = await filePicker.pickModpackFile({
readFile: ctx.flowType !== 'instance',
})
@@ -186,6 +186,8 @@ export interface CreationFlowContextValue {
// Loading state (set when finish() is called, cleared on reset)
loading: Ref<boolean>
finishDisabled: ComputedRef<boolean>
finishDisabledTooltip: ComputedRef<string | undefined>
// Backup state (set by InlineBackupCreator in reset-server flow)
isBackingUp: Ref<boolean>
@@ -232,6 +234,8 @@ export interface CreationFlowOptions {
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
getLoaderManifest?: LoaderManifestResolver
finishDisabled?: ComputedRef<boolean>
finishDisabledTooltip?: ComputedRef<string | undefined>
}
export function createCreationFlowContext(
@@ -257,6 +261,8 @@ export function createCreationFlowContext(
const searchModpacks = options.searchModpacks!
const getProjectVersions = options.getProjectVersions!
const getLoaderManifest = options.getLoaderManifest ?? null
const finishDisabled = options.finishDisabled ?? computed(() => false)
const finishDisabledTooltip = options.finishDisabledTooltip ?? computed(() => undefined)
const setupType = ref<SetupType | null>(null)
const isImportMode = ref(false)
@@ -502,6 +508,8 @@ export function createCreationFlowContext(
}
function finish() {
if (finishDisabled.value) return
debug('finish() called, state:', {
setupType: setupType.value,
selectedLoader: selectedLoader.value,
@@ -585,6 +593,8 @@ export function createCreationFlowContext(
importSearchQuery,
hardReset,
loading,
finishDisabled,
finishDisabledTooltip,
isBackingUp,
cancelBackup,
modal,
@@ -10,7 +10,7 @@
</template>
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { computed, useTemplateRef } from 'vue'
import type { ComponentExposed } from 'vue-component-type-helpers'
import MultiStageModal from '../../base/MultiStageModal.vue'
@@ -38,6 +38,8 @@ const props = withDefaults(
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
getLoaderManifest?: LoaderManifestResolver
finishDisabled?: boolean
finishDisabledTooltip?: string
}>(),
{
type: 'world',
@@ -78,6 +80,8 @@ const ctx = createCreationFlowContext(
searchModpacks: props.searchModpacks,
getProjectVersions: props.getProjectVersions,
getLoaderManifest: props.getLoaderManifest,
finishDisabled: computed(() => props.finishDisabled ?? false),
finishDisabledTooltip: computed(() => props.finishDisabledTooltip),
},
)
provideCreationFlowContext(ctx)
@@ -46,12 +46,14 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
icon: PlusIcon,
iconPosition: 'before' as const,
color: 'brand' as const,
disabled,
disabled: disabled || ctx.finishDisabled.value,
loading: ctx.loading.value,
tooltip: ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined,
onClick: () => ctx.finish(),
}
}
const finishDisabled = !goesToNextStage && ctx.finishDisabled.value
return {
label: ctx.formatMessage(
goesToNextStage ? commonMessages.continueButton : creationFlowMessages.finishButton,
@@ -59,7 +61,8 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
icon: goesToNextStage ? RightArrowIcon : null,
iconPosition: 'after' as const,
color: goesToNextStage ? undefined : ('brand' as const),
disabled,
disabled: disabled || finishDisabled,
tooltip: finishDisabled ? ctx.finishDisabledTooltip.value : undefined,
onClick: () => {
if (goesToNextStage) {
ctx.modal.value?.nextStage()
@@ -51,8 +51,10 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
icon: isFinish ? PlusIcon : RightArrowIcon,
iconPosition: isFinish ? ('before' as const) : ('after' as const),
color: isReset ? ('red' as const) : isFinish ? ('brand' as const) : undefined,
disabled: isForwardBlocked(ctx) || ctx.isBackingUp.value,
disabled:
isForwardBlocked(ctx) || ctx.isBackingUp.value || (isFinish && ctx.finishDisabled.value),
loading: isFinish && ctx.loading.value,
tooltip: isFinish && ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined,
onClick: () => {
if (isFinish) {
ctx.finish()
@@ -38,7 +38,8 @@ export const stageConfig: StageConfigInput<CreationFlowContextValue> = {
icon: DownloadIcon,
iconPosition: 'before' as const,
color: 'brand' as const,
disabled: count === 0,
disabled: count === 0 || ctx.finishDisabled.value,
tooltip: ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined,
onClick: () => ctx.finish(),
}
},