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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ yarn-error.log*
next-env.d.ts

.vercel

# Supabase CLI scratch
supabase/.temp/
10 changes: 6 additions & 4 deletions app/(dashboard)/adminguard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
'use client'

import { useEffect, useState } from 'react'
import { useEffect, useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { getAuthedUser } from '@/lib/auth'
import { PageSkeleton } from '@/app/_components/skeleton'

export default function AdminGuard({ children }: { children: React.ReactNode }) {
const [checking, setChecking] = useState(true)
const router = useRouter()
const supabase = createClient()
const supabase = useMemo(() => createClient(), [])

useEffect(() => {
const checkAdmin = async () => {
const { data: { user } } = await supabase.auth.getUser()
const user = await getAuthedUser(supabase)
if (!user || !user.app_metadata?.is_admin) {
router.push('/my-rooms')
} else {
Expand All @@ -21,7 +23,7 @@ export default function AdminGuard({ children }: { children: React.ReactNode })
checkAdmin()
}, [])

if (checking) return null
if (checking) return <PageSkeleton />

return <>{children}</>
}
3 changes: 2 additions & 1 deletion app/(dashboard)/administrator/booking-settings-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useEffect, useState } from 'react'
import { createBrowserClient } from '@supabase/ssr'
import { getAuthedUser } from '@/lib/auth'
import { Skeleton } from '@/app/_components/skeleton'

function BookingSettingsTabSkeleton() {
Expand Down Expand Up @@ -87,7 +88,7 @@ export default function BookingSettingsTab() {
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
supabase.auth.getUser().then(({ data: { user } }) => {
getAuthedUser(supabase).then((user) => {
if (
user?.app_metadata?.admin_role === 'Vice President of Operational Affairs' ||
user?.app_metadata?.admin_role === 'Executive Vice President' ||
Expand Down
13 changes: 10 additions & 3 deletions app/(dashboard)/administrator/bookings-tab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import BookingModal from './booking-modal'
import AdminCancelModal from './admin-cancel-modal'
import OneTimeForm from './one-time-form'
Expand Down Expand Up @@ -218,10 +218,17 @@ export default function BookingsTab() {
})
}

const staticDataLoaded = useRef(false)

useEffect(() => {
fetchBookings(showAll)
fetchBodies()
fetchSemesters()
// Bodies and semesters don't depend on the "show all" toggle, so they only
// need fetching once per mount rather than on every toggle.
if (!staticDataLoaded.current) {
staticDataLoaded.current = true
fetchBodies()
fetchSemesters()
}
}, [showAll])

const sortedWeekly = loading ? [] : [...weekly].sort((a, b) => {
Expand Down
22 changes: 7 additions & 15 deletions app/(dashboard)/administrator/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use client'

import { useState, useEffect } from 'react'
import { useState } from 'react'
import AdminGuard from '../adminguard'
import { useCounts } from '../counts-context'
import RequestsTab from './requests-tab'
import CancellationsTab from './cancellations-tab'
import BookingsTab from './bookings-tab'
Expand All @@ -12,18 +13,9 @@ type Tab = 'Requests' | 'Cancellations' | 'Bookings' | 'SGA Spaces' | 'Advanced

export default function AdministratorPage() {
const [activeTab, setActiveTab] = useState<Tab>('Bookings')
const [counts, setCounts] = useState({ requests: 0, cancellations: 0, revisions: 0, total: 0 })

useEffect(() => {
const fetchCounts = async () => {
const res = await fetch('/api/administrator/counts')
if (res.ok) {
const data = await res.json()
setCounts(data)
}
}
fetchCounts()
}, [])
// Shared with the layout's sidebar badge instead of refetching the same
// endpoint on every Administrator page load.
const { counts, refreshCounts } = useCounts()

const tabBadge = (tab: Tab) => {
if (tab === 'Requests') return counts.requests + counts.revisions
Expand Down Expand Up @@ -58,8 +50,8 @@ export default function AdministratorPage() {
</div>

<div>
{activeTab === 'Requests' && <RequestsTab onCountChange={() => fetch('/api/administrator/counts').then(r => r.json()).then(d => setCounts(d))} />}
{activeTab === 'Cancellations' && <CancellationsTab onCountChange={() => fetch('/api/administrator/counts').then(r => r.json()).then(d => setCounts(d))} />}
{activeTab === 'Requests' && <RequestsTab onCountChange={refreshCounts} />}
{activeTab === 'Cancellations' && <CancellationsTab onCountChange={refreshCounts} />}
{activeTab === 'Bookings' && <BookingsTab />}
{activeTab === 'SGA Spaces' && <SGASpacesTab />}
{activeTab === 'Advanced Settings' && <AdvancedSettingsTab />}
Expand Down
3 changes: 2 additions & 1 deletion app/(dashboard)/administrator/users-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useState } from 'react'
import { Skeleton } from '@/app/_components/skeleton'
import { createClient } from '@/lib/supabase/client'
import { getAuthedUser } from '@/lib/auth'

function UsersTabSkeleton() {
return (
Expand Down Expand Up @@ -124,7 +125,7 @@ export default function UsersTab() {
fetchBodies()
fetchMembershipRequests()
const supabase = createClient()
supabase.auth.getUser().then(({ data: { user } }) => {
getAuthedUser(supabase).then((user) => {
setCurrentUserRole(user?.app_metadata?.admin_role ?? null)
})
}, [])
Expand Down
10 changes: 6 additions & 4 deletions app/(dashboard)/authguard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
'use client'

import { useEffect, useState } from 'react'
import { useEffect, useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { getAuthedUser } from '@/lib/auth'
import { PageSkeleton } from '@/app/_components/skeleton'

export default function AuthGuard({ children }: { children: React.ReactNode }) {
const [checking, setChecking] = useState(true)
const router = useRouter()
const supabase = createClient()
const supabase = useMemo(() => createClient(), [])

useEffect(() => {
const checkAuth = async () => {
const { data: { user } } = await supabase.auth.getUser()
const user = await getAuthedUser(supabase)
if (!user) {
router.push('/')
return
Expand Down Expand Up @@ -39,7 +41,7 @@ export default function AuthGuard({ children }: { children: React.ReactNode }) {
checkAuth()
}, [])

if (checking) return null
if (checking) return <PageSkeleton />

return <>{children}</>
}
38 changes: 38 additions & 0 deletions app/(dashboard)/counts-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'

import { createContext, useContext } from 'react'

export type Counts = {
requests: number
cancellations: number
revisions: number
membership_requests: number
total: number
}

export const EMPTY_COUNTS: Counts = {
requests: 0,
cancellations: 0,
revisions: 0,
membership_requests: 0,
total: 0,
}

type CountsContextValue = {
counts: Counts
refreshCounts: () => void
}

/**
* The dashboard layout already fetches /api/administrator/counts for the sidebar
* badge. Sharing it here stops the Administrator page from fetching the exact
* same endpoint a second time on every load.
*/
export const CountsContext = createContext<CountsContextValue>({
counts: EMPTY_COUNTS,
refreshCounts: () => {},
})

export function useCounts() {
return useContext(CountsContext)
}
10 changes: 6 additions & 4 deletions app/(dashboard)/eventsguard.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
'use client'

import { useEffect, useState } from 'react'
import { useEffect, useState, useMemo } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/lib/supabase/client'
import { getAuthedUser } from '@/lib/auth'
import { PageSkeleton } from '@/app/_components/skeleton'

export default function EventsGuard({ children }: { children: React.ReactNode }) {
const [checking, setChecking] = useState(true)
const router = useRouter()
const supabase = createClient()
const supabase = useMemo(() => createClient(), [])

useEffect(() => {
const checkAccess = async () => {
const { data: { user } } = await supabase.auth.getUser()
const user = await getAuthedUser(supabase)
if (!user || (!user.app_metadata?.is_admin && !user.app_metadata?.iems_role)) {
router.push('/my-rooms')
} else {
Expand All @@ -21,7 +23,7 @@ export default function EventsGuard({ children }: { children: React.ReactNode })
checkAccess()
}, [])

if (checking) return null
if (checking) return <PageSkeleton />

return <>{children}</>
}
Loading
Loading