feat: throw 401 errors when a user doesn't have permissions (#5984)
* feat: throw 401 errors when a user doesn't have permissions * remove pointless message * prepr
This commit is contained in:
@@ -7,7 +7,18 @@
|
|||||||
<Logo404 />
|
<Logo404 />
|
||||||
</div>
|
</div>
|
||||||
<div class="error-box" :class="{ 'has-bot': !is404 }">
|
<div class="error-box" :class="{ 'has-bot': !is404 }">
|
||||||
<img v-if="!is404" :src="SadRinthbot" alt="Sad Modrinth bot" class="error-box__sad-bot" />
|
<img
|
||||||
|
v-if="is401"
|
||||||
|
:src="AnnoyedRinthbot"
|
||||||
|
alt="Annoyed Modrinth bot"
|
||||||
|
class="error-box__sad-bot"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
v-else-if="!is404"
|
||||||
|
:src="SadRinthbot"
|
||||||
|
alt="Sad Modrinth bot"
|
||||||
|
class="error-box__sad-bot"
|
||||||
|
/>
|
||||||
<div v-if="!is404" class="error-box__top-glow" />
|
<div v-if="!is404" class="error-box__top-glow" />
|
||||||
<div class="error-box__body">
|
<div class="error-box__body">
|
||||||
<h1 class="error-box__title">{{ formatMessage(errorMessages.title) }}</h1>
|
<h1 class="error-box__title">{{ formatMessage(errorMessages.title) }}</h1>
|
||||||
@@ -15,6 +26,33 @@
|
|||||||
{{ formatMessage(errorMessages.subtitle) }}
|
{{ formatMessage(errorMessages.subtitle) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="is401" class="flex flex-col gap-4">
|
||||||
|
<template v-if="auth.user">
|
||||||
|
<p class="m-0">
|
||||||
|
{{ formatMessage(unauthorizedMessages.signedInAsLabel) }}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 rounded-2xl border border-solid border-surface-5 bg-surface-4 p-4"
|
||||||
|
>
|
||||||
|
<Avatar :src="auth.user.avatar_url" size="32px" />
|
||||||
|
<span class="font-medium text-contrast">{{ auth.user.username }}</span>
|
||||||
|
|
||||||
|
<ButtonStyled color="red" type="transparent">
|
||||||
|
<button type="button" class="ml-auto" @click="logout">
|
||||||
|
{{ formatMessage(commonMessages.signOutButton) }}
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ButtonStyled color="brand">
|
||||||
|
<nuxt-link class="button-like w-fit" :to="signInRoute">
|
||||||
|
<LogInIcon />
|
||||||
|
{{ formatMessage(commonMessages.signInButton) }}
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
<div class="error-box__body">
|
<div class="error-box__body">
|
||||||
<p v-if="errorMessages.list_title" class="error-box__list-title">
|
<p v-if="errorMessages.list_title" class="error-box__list-title">
|
||||||
{{ formatMessage(errorMessages.list_title) }}
|
{{ formatMessage(errorMessages.list_title) }}
|
||||||
@@ -51,9 +89,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { SadRinthbot } from '@modrinth/assets'
|
import { AnnoyedRinthbot, LogInIcon, SadRinthbot } from '@modrinth/assets'
|
||||||
import {
|
import {
|
||||||
|
Avatar,
|
||||||
|
ButtonStyled,
|
||||||
|
commonMessages,
|
||||||
defineMessage,
|
defineMessage,
|
||||||
|
defineMessages,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
LoadingBar,
|
LoadingBar,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
@@ -65,6 +107,8 @@ import {
|
|||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import Logo404 from '~/assets/images/404.svg'
|
import Logo404 from '~/assets/images/404.svg'
|
||||||
|
import { getSignInRouteObj } from '~/composables/auth.js'
|
||||||
|
import { logout } from '~/composables/user.js'
|
||||||
|
|
||||||
import { createModrinthClient } from './helpers/api.ts'
|
import { createModrinthClient } from './helpers/api.ts'
|
||||||
import { FrontendNotificationManager } from './providers/frontend-notifications.ts'
|
import { FrontendNotificationManager } from './providers/frontend-notifications.ts'
|
||||||
@@ -103,6 +147,17 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const is404 = computed(() => props.error.statusCode === 404)
|
const is404 = computed(() => props.error.statusCode === 404)
|
||||||
|
const is401 = computed(() => props.error.statusCode === 401)
|
||||||
|
|
||||||
|
const unauthorizedMessages = defineMessages({
|
||||||
|
signedInAsLabel: {
|
||||||
|
id: 'error.generic.401.signed-in-as',
|
||||||
|
defaultMessage: "You're currently signed in as:",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const signInRoute = computed(() => getSignInRouteObj(route))
|
||||||
|
|
||||||
const errorMessages = computed(
|
const errorMessages = computed(
|
||||||
() =>
|
() =>
|
||||||
routeMessages.find((x) => x.match(route))?.messages[props.error.statusCode] ??
|
routeMessages.find((x) => x.match(route))?.messages[props.error.statusCode] ??
|
||||||
@@ -112,10 +167,6 @@ const errorMessages = computed(
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
watch(route, () => {
|
|
||||||
console.log(route)
|
|
||||||
})
|
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
404: {
|
404: {
|
||||||
title: defineMessage({
|
title: defineMessage({
|
||||||
@@ -138,6 +189,12 @@ const messages = {
|
|||||||
'This page has been blocked for legal reasons, such as government censorship or ongoing legal proceedings.',
|
'This page has been blocked for legal reasons, such as government censorship or ongoing legal proceedings.',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
401: {
|
||||||
|
title: defineMessage({
|
||||||
|
id: 'error.generic.401.title',
|
||||||
|
defaultMessage: `You don't have access to this page`,
|
||||||
|
}),
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
title: defineMessage({
|
title: defineMessage({
|
||||||
id: 'error.generic.default.title',
|
id: 'error.generic.default.title',
|
||||||
@@ -345,7 +402,7 @@ const routeMessages = [
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a:not(.button-like) {
|
||||||
color: var(--color-brand);
|
color: var(--color-brand);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
@@ -387,20 +444,24 @@ const routeMessages = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 2rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 900;
|
font-weight: 600;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
font-size: 1.25rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__list-title {
|
&__list-title {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@
|
|||||||
{
|
{
|
||||||
id: 'review-projects',
|
id: 'review-projects',
|
||||||
color: 'orange',
|
color: 'orange',
|
||||||
link: '/moderation/',
|
link: '/moderation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'tech-review',
|
id: 'tech-review',
|
||||||
|
|||||||
@@ -1244,6 +1244,12 @@
|
|||||||
"error.collection.404.title": {
|
"error.collection.404.title": {
|
||||||
"message": "Collection not found"
|
"message": "Collection not found"
|
||||||
},
|
},
|
||||||
|
"error.generic.401.signed-in-as": {
|
||||||
|
"message": "You're currently signed in as:"
|
||||||
|
},
|
||||||
|
"error.generic.401.title": {
|
||||||
|
"message": "You don't have access to this page"
|
||||||
|
},
|
||||||
"error.generic.404.subtitle": {
|
"error.generic.404.subtitle": {
|
||||||
"message": "The page you were looking for doesn't seem to exist."
|
"message": "The page you were looking for doesn't seem to exist."
|
||||||
},
|
},
|
||||||
|
|||||||
13
apps/frontend/src/middleware/staff.ts
Normal file
13
apps/frontend/src/middleware/staff.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { isStaff } from '@modrinth/utils'
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(async () => {
|
||||||
|
const auth = await useAuth()
|
||||||
|
|
||||||
|
if (!auth.value.user || !isStaff(auth.value.user)) {
|
||||||
|
throw createError({
|
||||||
|
fatal: true,
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Unauthorized',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -3,47 +3,49 @@
|
|||||||
<Teleport v-if="flags.projectBackground" to="#fixed-background-teleport">
|
<Teleport v-if="flags.projectBackground" to="#fixed-background-teleport">
|
||||||
<ProjectBackgroundGradient :project="project" />
|
<ProjectBackgroundGradient :project="project" />
|
||||||
</Teleport>
|
</Teleport>
|
||||||
<div v-if="route.name.startsWith('type-id-settings')" class="normal-page no-sidebar">
|
<template v-if="isSettings">
|
||||||
<div class="normal-page__header">
|
<div v-if="canAccessSettings" class="normal-page no-sidebar">
|
||||||
<div
|
<div class="normal-page__header">
|
||||||
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
<div
|
||||||
>
|
class="mb-4 flex flex-wrap items-center gap-x-2 gap-y-3 border-0 border-b-[1px] border-solid border-divider pb-4 text-lg font-semibold"
|
||||||
<nuxt-link
|
|
||||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
|
|
||||||
class="flex items-center gap-2 hover:underline hover:brightness-[--hover-brightness]"
|
|
||||||
>
|
>
|
||||||
<Avatar :src="project.icon_url" size="32px" />
|
<nuxt-link
|
||||||
{{ project.title }}
|
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}`"
|
||||||
</nuxt-link>
|
class="flex items-center gap-2 hover:underline hover:brightness-[--hover-brightness]"
|
||||||
<ChevronRightIcon />
|
>
|
||||||
<span class="flex grow font-extrabold text-contrast">{{
|
<Avatar :src="project.icon_url" size="32px" />
|
||||||
formatMessage(messages.settingsTitle)
|
{{ project.title }}
|
||||||
}}</span>
|
</nuxt-link>
|
||||||
<div class="flex gap-2">
|
<ChevronRightIcon />
|
||||||
<ButtonStyled>
|
<span class="flex grow font-extrabold text-contrast">{{
|
||||||
<nuxt-link to="/dashboard/projects"
|
formatMessage(messages.settingsTitle)
|
||||||
><ListIcon /> {{ formatMessage(messages.visitProjectsDashboard) }}
|
}}</span>
|
||||||
</nuxt-link>
|
<div class="flex gap-2">
|
||||||
</ButtonStyled>
|
<ButtonStyled>
|
||||||
|
<nuxt-link to="/dashboard/projects"
|
||||||
|
><ListIcon /> {{ formatMessage(messages.visitProjectsDashboard) }}
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ProjectMemberHeader
|
||||||
|
v-if="currentMember && false"
|
||||||
|
:project="project"
|
||||||
|
:versions="versions"
|
||||||
|
:current-member="currentMember"
|
||||||
|
:is-settings="isSettings"
|
||||||
|
:set-processing="setProcessing"
|
||||||
|
:all-members="allMembers"
|
||||||
|
:update-members="invalidateProject"
|
||||||
|
:auth="auth"
|
||||||
|
:tags="tags"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="normal-page__content">
|
||||||
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
<ProjectMemberHeader
|
|
||||||
v-if="currentMember && false"
|
|
||||||
:project="project"
|
|
||||||
:versions="versions"
|
|
||||||
:current-member="currentMember"
|
|
||||||
:is-settings="route.name.startsWith('type-id-settings')"
|
|
||||||
:set-processing="setProcessing"
|
|
||||||
:all-members="allMembers"
|
|
||||||
:update-members="invalidateProject"
|
|
||||||
:auth="auth"
|
|
||||||
:tags="tags"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-page__content">
|
</template>
|
||||||
<NuxtPage />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<NewModal
|
<NewModal
|
||||||
@@ -811,7 +813,7 @@
|
|||||||
:project="project"
|
:project="project"
|
||||||
:versions="versions"
|
:versions="versions"
|
||||||
:current-member="currentMember"
|
:current-member="currentMember"
|
||||||
:is-settings="route.name.startsWith('type-id-settings')"
|
:is-settings="isSettings"
|
||||||
:route-name="route.name"
|
:route-name="route.name"
|
||||||
:set-processing="setProcessing"
|
:set-processing="setProcessing"
|
||||||
:collapsed="collapsedChecklist"
|
:collapsed="collapsedChecklist"
|
||||||
@@ -1826,6 +1828,8 @@ const { data: organizationRaw } = useQuery({
|
|||||||
// Return null when the project no longer belongs to an organization.
|
// Return null when the project no longer belongs to an organization.
|
||||||
const organization = computed(() => (projectRaw.value?.organization ? organizationRaw.value : null))
|
const organization = computed(() => (projectRaw.value?.organization ? organizationRaw.value : null))
|
||||||
|
|
||||||
|
const isSettings = computed(() => route.name.startsWith('type-id-settings'))
|
||||||
|
|
||||||
// Transform versionsV3 to be same shape as versionsV2 for compatibility in project pages
|
// Transform versionsV3 to be same shape as versionsV2 for compatibility in project pages
|
||||||
const versionsRaw = computed(() => {
|
const versionsRaw = computed(() => {
|
||||||
return (versionsV3.value ?? []).map((v) => {
|
return (versionsV3.value ?? []).map((v) => {
|
||||||
@@ -2262,11 +2266,27 @@ const currentMember = computed(() => {
|
|||||||
return val
|
return val
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const canAccessSettings = computed(() => !!currentMember.value?.accepted)
|
||||||
|
|
||||||
const hasEditDetailsPermission = computed(() => {
|
const hasEditDetailsPermission = computed(() => {
|
||||||
const EDIT_DETAILS = 1 << 2
|
const EDIT_DETAILS = 1 << 2
|
||||||
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
return (currentMember.value?.permissions & EDIT_DETAILS) === EDIT_DETAILS
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[isSettings, currentMember],
|
||||||
|
() => {
|
||||||
|
if (isSettings.value && !canAccessSettings.value) {
|
||||||
|
showError({
|
||||||
|
fatal: true,
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Unauthorized',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync', immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const projectTypeDisplay = computed(() => {
|
const projectTypeDisplay = computed(() => {
|
||||||
if (!project.value) return ''
|
if (!project.value) return ''
|
||||||
return formatProjectType(
|
return formatProjectType(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="canAccess">
|
||||||
<section class="universal-card">
|
<section class="universal-card">
|
||||||
<h2>Project status</h2>
|
<h2>Project status</h2>
|
||||||
<Badge :type="project.status" />
|
<Badge :type="project.status" />
|
||||||
@@ -107,7 +107,7 @@ import {
|
|||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import { computed } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
|
import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
|
||||||
import {
|
import {
|
||||||
@@ -122,6 +122,22 @@ import {
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { projectV2: project, currentMember, invalidate } = injectProjectPageContext()
|
const { projectV2: project, currentMember, invalidate } = injectProjectPageContext()
|
||||||
|
|
||||||
|
const canAccess = computed(() => !!currentMember.value)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[currentMember, project],
|
||||||
|
() => {
|
||||||
|
if (project.value && !canAccess.value) {
|
||||||
|
showError({
|
||||||
|
fatal: true,
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Unauthorized',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync', immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
const client = injectModrinthClient()
|
const client = injectModrinthClient()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
middleware: ['auth', 'staff'],
|
||||||
|
})
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
robots: 'noindex',
|
robots: 'noindex',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { FolderIcon, ReportIcon, ShieldCheckIcon } from '@modrinth/assets'
|
|||||||
import { Chips, defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
import { Chips, defineMessages, NavTabs, useVIntl } from '@modrinth/ui'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: ['auth', 'staff'],
|
||||||
})
|
})
|
||||||
|
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
|
|||||||
@@ -9,54 +9,56 @@
|
|||||||
>
|
>
|
||||||
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
||||||
<template v-if="routeHasSettings">
|
<template v-if="routeHasSettings">
|
||||||
<div class="normal-page__sidebar">
|
<template v-if="canAccessSettings">
|
||||||
<div
|
<div class="normal-page__sidebar">
|
||||||
class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4"
|
<div
|
||||||
>
|
class="bg-surface mb-4 flex flex-col rounded-xl border border-solid border-surface-4 p-4"
|
||||||
<div class="flex items-center gap-4">
|
>
|
||||||
<Avatar size="sm" :src="organization.icon_url" />
|
<div class="flex items-center gap-4">
|
||||||
<div class="flex flex-col justify-center gap-1">
|
<Avatar size="sm" :src="organization.icon_url" />
|
||||||
<h2 class="m-0 text-base">
|
<div class="flex flex-col justify-center gap-1">
|
||||||
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
<h2 class="m-0 text-base">
|
||||||
{{ organization.name }}
|
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
||||||
</nuxt-link>
|
{{ organization.name }}
|
||||||
</h2>
|
</nuxt-link>
|
||||||
<span>
|
</h2>
|
||||||
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
<span>
|
||||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
||||||
</span>
|
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<NavStack
|
<NavStack
|
||||||
:items="[
|
:items="[
|
||||||
{
|
{
|
||||||
link: `/organization/${organization.slug}/settings`,
|
link: `/organization/${organization.slug}/settings`,
|
||||||
label: 'Overview',
|
label: 'Overview',
|
||||||
icon: SettingsIcon,
|
icon: SettingsIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: `/organization/${organization.slug}/settings/members`,
|
link: `/organization/${organization.slug}/settings/members`,
|
||||||
label: 'Members',
|
label: 'Members',
|
||||||
icon: UsersIcon,
|
icon: UsersIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: `/organization/${organization.slug}/settings/projects`,
|
link: `/organization/${organization.slug}/settings/projects`,
|
||||||
label: 'Projects',
|
label: 'Projects',
|
||||||
icon: BoxIcon,
|
icon: BoxIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: `/organization/${organization.slug}/settings/analytics`,
|
link: `/organization/${organization.slug}/settings/analytics`,
|
||||||
label: 'Analytics',
|
label: 'Analytics',
|
||||||
icon: ChartIcon,
|
icon: ChartIcon,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-page__content">
|
<div class="normal-page__content">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="normal-page__header py-4">
|
<div class="normal-page__header py-4">
|
||||||
@@ -527,6 +529,22 @@ const { currentMember } = organizationContext
|
|||||||
|
|
||||||
provideOrganizationContext(organizationContext)
|
provideOrganizationContext(organizationContext)
|
||||||
|
|
||||||
|
const canAccessSettings = computed(() => !!currentMember.value?.accepted)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[routeHasSettings, currentMember],
|
||||||
|
() => {
|
||||||
|
if (routeHasSettings.value && !canAccessSettings.value) {
|
||||||
|
showError({
|
||||||
|
fatal: true,
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Unauthorized',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ flush: 'sync', immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
organization,
|
organization,
|
||||||
(org) => {
|
(org) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user