Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-otters-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"enspire": minor
---

admin page
43 changes: 39 additions & 4 deletions app/components/custom/sidebar.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<script setup lang="ts">
import type { AllClubs } from '@@/types/api/user/all_clubs'
import type { getRoleResponse } from '~~/utils/user-roles'
import { useClerk, useUser } from 'vue-clerk'
import { isAdmin } from '~~/utils/user-roles'

const { user } = useUser()
const config = useRuntimeConfig()
const route = useRoute()

const clerk = useClerk()

Expand All @@ -14,10 +17,17 @@ function signOutHandler() {
const isPresidentOrVicePresident = ref(false)
useState('isEnspireLoading').value = true

const { data: clubs, suspense } = useQuery<AllClubs>({
const { data: clubs, suspense: __s1 } = useQuery<AllClubs>({
queryKey: ['/api/user/all_clubs'],
})
await suspense()
await __s1()

const isUserAdmin = ref(false)
const { data: roleData, suspense: __s2 } = useQuery<getRoleResponse>({
queryKey: ['/api/user/check-role'],
})
await __s2()
isUserAdmin.value = isAdmin(roleData.value)

if (clubs.value) {
useState('isEnspireLoading').value = false
Expand Down Expand Up @@ -102,6 +112,13 @@ const sidebarData = ref({
]
: []),
],
admin: [
{
name: '社团文件',
url: '/admin/manage-files',
icon: 'lucide:lock-keyhole',
},
],
})
</script>

Expand Down Expand Up @@ -171,7 +188,7 @@ const sidebarData = ref({
v-for="item in sidebarData.school"
:key="item.name"
class="rounded"
:class="{ 'bg-foreground/10': $route.path === item.url }"
:class="{ 'bg-foreground/10': route.path === item.url }"
>
<SidebarMenuButton as-child>
<NuxtLink :href="item.url">
Expand Down Expand Up @@ -206,7 +223,7 @@ const sidebarData = ref({
:key="subItem.title"
class="flex items-center"
>
<div v-if="$route.path === subItem.url" class="border-text mr-2 h-4 w-1 border-l-2 border-foreground rounded -ml-3" />
<div v-if="route.path === subItem.url" class="border-text mr-2 h-4 w-1 border-l-2 border-foreground rounded -ml-3" />
<SidebarMenuSubButton as-child>
<NuxtLink :href="subItem.url">
<span>{{ subItem.title }}</span>
Expand All @@ -219,6 +236,24 @@ const sidebarData = ref({
</Collapsible>
</SidebarMenu>
</SidebarGroup>
<SidebarGroup v-if="isUserAdmin" class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>管理</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem
v-for="item in sidebarData.admin"
:key="item.name"
class="rounded"
:class="{ 'bg-foreground/10': route.path === item.url }"
>
<SidebarMenuButton as-child>
<NuxtLink :href="item.url">
<Icon :name="item.icon" size="1.1em" />
<span>{{ item.name }}</span>
</NuxtLink>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
Expand Down
39 changes: 39 additions & 0 deletions app/components/ui/progress/Progress.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import {
ProgressIndicator,
ProgressRoot,
type ProgressRootProps,
} from 'radix-vue'
import { computed, type HTMLAttributes } from 'vue'

const props = withDefaults(
defineProps<ProgressRootProps & { class?: HTMLAttributes['class'] }>(),
{
modelValue: 0,
},
)

const delegatedProps = computed(() => {
const { class: _, ...delegated } = props

return delegated
})
</script>

<template>
<ProgressRoot
v-bind="delegatedProps"
:class="
cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary',
props.class,
)
"
>
<ProgressIndicator
class="h-full w-full flex-1 bg-primary transition-all"
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`"
/>
</ProgressRoot>
</template>
1 change: 1 addition & 0 deletions app/components/ui/progress/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Progress } from './Progress.vue'
33 changes: 33 additions & 0 deletions app/middleware/admin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Roles } from '@prisma/client'
import { until } from '@vueuse/core'
import { useClerk, useClerkProvider } from 'vue-clerk'
import { getRole, isAdmin } from '~~/utils/user-roles'

export default defineNuxtRouteMiddleware(async () => {
// Modified from auth.ts

const nuxtApp = useNuxtApp()
const clerk = useClerk()
const { isClerkLoaded } = useClerkProvider()

if (import.meta.client) {
if (nuxtApp.isHydrating && nuxtApp.payload.serverRendered)
return

await until(isClerkLoaded).toBe(true)
if (clerk.loaded && clerk.user?.id == null)
return navigateTo('/sign-in')
const response = await $fetch('/api/user/check-role')
if (!isAdmin(response))
return abortNavigation()
}

if (import.meta.server) {
const id = nuxtApp.ssrContext?.event.context.auth?.userId
if (id == null)
return navigateTo('/sign-in')
const response = await getRole(id)
if (!isAdmin(response))
return abortNavigation()
}
})
Loading
Loading