diff --git a/app/communication-template/[id]/edit/page.tsx b/app/communication-template/[id]/edit/page.tsx index 6b004e30..bd06f4e2 100644 --- a/app/communication-template/[id]/edit/page.tsx +++ b/app/communication-template/[id]/edit/page.tsx @@ -1,8 +1,11 @@ 'use client' +import { useTitle } from 'react-use' import { CommunicationTemplateForm } from 'components/communication-template/form' export default function CommunicationTemplateEditPage({ params: { id } }) { + useTitle(`Edit Communication Templates #${id}`) + return (

diff --git a/app/communication-template/create/page.tsx b/app/communication-template/create/page.tsx index 430fb34f..2efeed2e 100644 --- a/app/communication-template/create/page.tsx +++ b/app/communication-template/create/page.tsx @@ -1,8 +1,11 @@ 'use client' +import { useTitle } from 'react-use' import { CommunicationTemplateForm } from 'components/communication-template/form' export default function CommunicationTemplateCreatePage() { + useTitle(`Create Communication Templates`) + return (

diff --git a/app/communication-template/page.tsx b/app/communication-template/page.tsx index aa69b781..cc38a0f2 100644 --- a/app/communication-template/page.tsx +++ b/app/communication-template/page.tsx @@ -4,6 +4,7 @@ import format from 'date-fns/format' import { useState } from 'react' import Link from 'next/link' import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/20/solid' +import { useTitle } from 'react-use' import { LabelChip } from '@/common/labels' import { Loading, LoadingFailed } from '@/common/Loader' @@ -17,6 +18,8 @@ export default function CommunicationTemplatePage() { string | undefined >() + useTitle(`Communication Templates`) + if (isLoading) { return } diff --git a/app/events/[id]/page.tsx b/app/events/[id]/page.tsx index 390527ec..dbda8284 100644 --- a/app/events/[id]/page.tsx +++ b/app/events/[id]/page.tsx @@ -1,15 +1,14 @@ 'use client' import { useQuery } from '@tanstack/react-query' +import { useTitle } from 'react-use' import client from '@/lib/client' import { Loading, LoadingFailed } from '@/common/Loader' import { EventView } from '@/mod-event/View' +import { MOD_EVENT_TITLES } from '@/mod-event/constants' export default function EventViewPage({ params }: { params: { id: string } }) { const id = decodeURIComponent(params.id) - const { - data: event, - error, - } = useQuery({ + const { data: event, error } = useQuery({ queryKey: ['event', { id }], queryFn: async () => { const { data } = await client.api.com.atproto.admin.getModerationEvent( @@ -20,6 +19,14 @@ export default function EventViewPage({ params }: { params: { id: string } }) { }, }) + let pageTitle = `Moderation Event #${id}` + if (event) { + const eventTitle = + MOD_EVENT_TITLES[event.event.$type as string] || 'Moderation' + pageTitle = `${eventTitle} Event #${id}` + } + useTitle(pageTitle) + if (error) { return } diff --git a/app/events/page-content.tsx b/app/events/page-content.tsx index d1490aaf..07f55904 100644 --- a/app/events/page-content.tsx +++ b/app/events/page-content.tsx @@ -1,3 +1,4 @@ +import { useTitle } from 'react-use' import { ModEventList } from '@/mod-event/EventList' import { emitEvent } from '@/mod-event/helpers/emitEvent' import { ComAtprotoAdminEmitModerationEvent } from '@atproto/api' @@ -19,6 +20,8 @@ export default function EventListPageContent() { router.push((pathname ?? '') + '?' + newParams.toString()) } + useTitle(`Moderation Events`) + return (
diff --git a/app/reports/page-content.tsx b/app/reports/page-content.tsx index 186838f4..7161a7aa 100644 --- a/app/reports/page-content.tsx +++ b/app/reports/page-content.tsx @@ -1,5 +1,5 @@ 'use client' -import { useContext, useCallback, Suspense } from 'react' +import { useContext, useCallback, Suspense, useEffect } from 'react' import { ReadonlyURLSearchParams, usePathname, @@ -22,6 +22,7 @@ import { AuthContext } from '@/shell/AuthContext' import { ButtonGroup } from '@/common/buttons' import { useFluentReportSearch } from '@/reports/useFluentReportSearch' import { SubjectTable } from 'components/subject/table' +import { useTitle } from 'react-use' const TABS = [ { @@ -48,6 +49,42 @@ const TABS = [ { key: 'all', name: 'All', href: '/reports' }, ] +const buildPageTitle = ({ + currentTab, + takendown, + includeMuted, + appealed, +}: { + currentTab: string + takendown: boolean + includeMuted: boolean + appealed: boolean +}) => { + const titleFromTab = + currentTab === 'all' + ? `All subjects` + : `${currentTab[0].toUpperCase()}${currentTab.slice(1)}` + const additionalFragments: string[] = [] + + if (takendown) { + additionalFragments.push('Taken Down') + } + + if (includeMuted) { + additionalFragments.push('Include Muted') + } + + if (appealed) { + additionalFragments.push('Appealed') + } + + const additionalTitle = additionalFragments.length + ? ` (${additionalFragments.join(', ')})` + : '' + const title = `Queue - ${titleFromTab}${additionalTitle}` + return title +} + const ResolvedFilters = () => { const router = useRouter() const pathname = usePathname() @@ -197,6 +234,14 @@ export const ReportsPageContent = () => { ), ) + const pageTitle = buildPageTitle({ + currentTab, + takendown, + includeMuted, + appealed, + }) + useTitle(pageTitle) + return ( <> diff --git a/app/repositories/[id]/[...record]/page.tsx b/app/repositories/[id]/[...record]/page.tsx index 55ee3694..ad81ea5a 100644 --- a/app/repositories/[id]/[...record]/page.tsx +++ b/app/repositories/[id]/[...record]/page.tsx @@ -15,6 +15,36 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { ModActionPanelQuick } from 'app/actions/ModActionPanel/QuickAction' import { emitEvent } from '@/mod-event/helpers/emitEvent' import { useEffect } from 'react' +import { useTitle } from 'react-use' + +const buildPageTitle = ({ + handle, + collection, + rkey, +}: { + handle?: string + collection?: string + rkey?: string +}) => { + let title = `Record Details` + + if (collection) { + const titleFromCollection = collection.split('.').pop() + if (titleFromCollection) { + title = + titleFromCollection[0].toUpperCase() + titleFromCollection.slice(1) + } + } + + if (handle) { + title += ` - ${handle}` + } + + if (rkey) { + title += ` - ${rkey}` + } + return title +} export default function Record({ params, @@ -116,6 +146,13 @@ export default function Record({ } }, [data, reportUri]) + const pageTitle = buildPageTitle({ + handle: data?.record?.repo.handle, + rkey, + collection, + }) + useTitle(pageTitle) + if (error) { return } diff --git a/app/repositories/[id]/page-content.tsx b/app/repositories/[id]/page-content.tsx index abfe7c93..2a8f29b2 100644 --- a/app/repositories/[id]/page-content.tsx +++ b/app/repositories/[id]/page-content.tsx @@ -6,7 +6,29 @@ import { ComAtprotoAdminEmitModerationEvent } from '@atproto/api' import { ModActionPanelQuick } from 'app/actions/ModActionPanel/QuickAction' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { emitEvent } from '@/mod-event/helpers/emitEvent' +import { useTitle } from 'react-use' +const buildPageTitle = ({ + handle, + tab, +}: { + handle: string + tab: string | null +}) => { + let title = `Repository Details` + const titleFragments: string[] = [title] + const titleFromTab = tab ? tab[0].toUpperCase() + tab.slice(1) : '' + + if (titleFromTab) { + titleFragments.unshift(titleFromTab) + } + + if (handle) { + titleFragments.unshift(handle) + } + + return titleFragments.join(' - ') +} export function RepositoryViewPageContent({ id }: { id: string }) { const { error, @@ -27,6 +49,13 @@ export function RepositoryViewPageContent({ id }: { id: string }) { } router.push((pathname ?? '') + '?' + newParams.toString()) } + const tab = searchParams.get('tab') + + const pageTitle = buildPageTitle({ + handle: profile?.handle || repo?.handle || id, + tab, + }) + useTitle(pageTitle) return ( <> diff --git a/app/repositories/page-content.tsx b/app/repositories/page-content.tsx index 9de8d7cf..11d6bb39 100644 --- a/app/repositories/page-content.tsx +++ b/app/repositories/page-content.tsx @@ -3,6 +3,8 @@ import { RepositoriesTable } from '@/repositories/RepositoriesTable' import { useSearchParams } from 'next/navigation' import { useInfiniteQuery } from '@tanstack/react-query' import client from '@/lib/client' +import { useEffect } from 'react' +import { useTitle } from 'react-use' export default function RepositoriesListPage() { const params = useSearchParams() @@ -22,6 +24,14 @@ export default function RepositoriesListPage() { }, getNextPageParam: (lastPage) => lastPage.cursor, }) + + let pageTitle = `Repositories` + if (term) { + pageTitle += ` - ${term}` + } + + useTitle(pageTitle) + const repos = data?.pages.flatMap((page) => page.repos) ?? [] return ( <> diff --git a/app/subject-status/page.tsx b/app/subject-status/page.tsx index 9c0ace5a..86247abe 100644 --- a/app/subject-status/page.tsx +++ b/app/subject-status/page.tsx @@ -4,6 +4,8 @@ import client from '@/lib/client' import { Loading, LoadingFailed } from '@/common/Loader' import { useSearchParams } from 'next/navigation' import { SubjectStatusView } from '@/subject/StatusView' +import { useEffect } from 'react' +import { useTitle } from 'react-use' export default function SubjectStatus() { const params = useSearchParams() @@ -21,6 +23,14 @@ export default function SubjectStatus() { }, }) + let pageTitle = `Subject Status` + + if (data?.subjectStatuses[0]) { + pageTitle = `${data.subjectStatuses[0].subjectRepoHandle} - ${pageTitle}` + } + + useTitle(pageTitle) + if (status === 'loading') { return } diff --git a/app/surprise-me/page.tsx b/app/surprise-me/page.tsx index cb74bc46..11892276 100644 --- a/app/surprise-me/page.tsx +++ b/app/surprise-me/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' -import { useInterval } from 'react-use' +import { useEffect, useState } from 'react' +import { useInterval, useTitle } from 'react-use' import dynamic from 'next/dynamic' // The game package uses some client only code so we can't really import and use it here because that breaks SSR for some reason @@ -38,6 +38,8 @@ const Timer = () => { // Right now, we only serve the tetris game here, in the future, we want to rotate // between a few games/fun activities which is why it's named "surprise me" export default function SurpriseMePage() { + useTitle(`Take a break!`) + return ( <> {/* This is valid jsx but because of a known bug, typescript is confused */} diff --git a/components/shell/LoginModal.tsx b/components/shell/LoginModal.tsx index 92986d0e..d165b05e 100644 --- a/components/shell/LoginModal.tsx +++ b/components/shell/LoginModal.tsx @@ -43,6 +43,13 @@ export function LoginModal() { } }, []) + useEffect(() => { + const title = `Ozone - Authenticate` + if (!isLoggedIn) { + document.title = title + } + }, [isLoggedIn]) + const onSubmit = async (e: FormEvent) => { e.preventDefault() e.stopPropagation()