222 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2025-12-10 11:03:24 +01:00
import {Alert, Button, ConfigProvider, Drawer, Flex, Layout, message, theme, Typography} from 'antd'
2024-12-30 23:50:15 +01:00
import {Link, Navigate, Route, Routes, useLocation, useNavigate} from 'react-router-dom'
import TextPage from './pages/TextPage'
import DomainSearchPage from './pages/search/DomainSearchPage'
import EntitySearchPage from './pages/search/EntitySearchPage'
2025-09-14 17:59:26 +02:00
import TldPage from './pages/infrastructure/TldPage'
2024-12-30 23:50:15 +01:00
import StatisticsPage from './pages/StatisticsPage'
import WatchlistPage from './pages/tracking/WatchlistPage'
import UserPage from './pages/UserPage'
2025-11-01 01:00:31 +01:00
import type {PropsWithChildren} from 'react'
2025-12-10 11:03:24 +01:00
import React, {useCallback, useEffect, useMemo, useState} from 'react'
import {getConfiguration, getUser, type InstanceConfig} from './utils/api'
import LoginPage from './pages/LoginPage'
2024-12-30 23:50:15 +01:00
import ConnectorPage from './pages/tracking/ConnectorPage'
import NotFoundPage from './pages/NotFoundPage'
import useBreakpoint from './hooks/useBreakpoint'
import {Sider} from './components/Sider'
import {jt, t} from 'ttag'
2025-11-01 18:31:00 +01:00
import {MenuOutlined} from '@ant-design/icons'
2024-12-30 23:50:15 +01:00
import TrackedDomainPage from './pages/tracking/TrackedDomainPage'
2025-09-14 18:09:04 +02:00
import IcannRegistrarPage from "./pages/infrastructure/IcannRegistrarPage"
import type {AuthContextType} from "./contexts"
2025-12-09 13:34:50 +01:00
import {AuthenticatedContext, ConfigurationContext} from "./contexts"
2024-08-04 02:10:47 +02:00
2024-12-24 18:02:33 +01:00
const PROJECT_LINK = 'https://github.com/maelgangloff/domain-watchdog'
const LICENSE_LINK = 'https://www.gnu.org/licenses/agpl-3.0.txt'
2025-12-10 11:03:24 +01:00
const ProjectLink = <Typography.Link key="projectLink" target='_blank' href={PROJECT_LINK}>Domain
Watchdog</Typography.Link>
const LicenseLink = <Typography.Link key="licenceLink" target='_blank' rel='license'
href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link>
2025-12-10 11:03:24 +01:00
function SiderWrapper(props: PropsWithChildren<{
sidebarCollapsed: boolean,
setSidebarCollapsed: (collapsed: boolean) => void
}>): React.ReactElement {
2025-11-01 00:39:10 +01:00
const {sidebarCollapsed, setSidebarCollapsed, children} = props
const sm = useBreakpoint('sm')
const location = useLocation()
useEffect(() => {
if (sm) {
setSidebarCollapsed(false)
}
}, [location])
if (sm) {
return <Drawer
2025-12-10 11:03:24 +01:00
placement="left"
open={sidebarCollapsed}
onClose={() => setSidebarCollapsed(false)}
closeIcon={null}
styles={{body: {padding: 0, height: '100%', background: '#001529'}}}
width='200px'>
2025-11-01 00:39:10 +01:00
{children}
</Drawer>
} else {
return <Layout.Sider
2025-12-10 11:03:24 +01:00
collapsible
breakpoint='sm'
width={220}
trigger={null}
collapsed={sidebarCollapsed && sm}
{...(sm ? {collapsedWidth: 0} : {})}>
2025-11-01 00:39:10 +01:00
{children}
</Layout.Sider>
}
}
2024-12-30 23:50:15 +01:00
export default function App(): React.ReactElement {
const navigate = useNavigate()
2024-07-26 18:31:47 +02:00
const location = useLocation()
2024-07-30 06:13:31 +02:00
const sm = useBreakpoint('sm')
2024-07-26 18:31:47 +02:00
2025-10-31 23:57:12 +01:00
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
2025-12-10 11:03:24 +01:00
const [isAuthenticated, setIsAuthenticated] = useState<boolean | undefined>(undefined)
const [configuration, setConfiguration] = useState<InstanceConfig | undefined>(undefined)
const [darkMode, setDarkMode] = useState(false)
2025-12-10 14:49:40 +01:00
const [dismissLoginAlert, setDismissLoginAlert] = useState(() => localStorage.getItem('dismiss-login-alert') === 'true')
const windowQuery = window.matchMedia('(prefers-color-scheme:dark)')
2025-12-09 13:34:50 +01:00
const [messageApi, contextHolder] = message.useMessage()
const authContextValue: AuthContextType = useMemo(() => ({
isAuthenticated,
2024-07-26 18:31:47 +02:00
setIsAuthenticated
}), [isAuthenticated])
2024-08-23 22:24:11 +02:00
const configContextValue = useMemo(() => ({
configuration,
}), [configuration])
2024-12-20 20:21:05 +01:00
const darkModeChange = useCallback((event: MediaQueryListEvent) => {
setDarkMode(event.matches)
}, [])
useEffect(() => {
2024-12-30 23:50:15 +01:00
windowQuery.addEventListener('change', darkModeChange)
2024-12-20 20:21:05 +01:00
return () => {
2024-12-30 23:50:15 +01:00
windowQuery.removeEventListener('change', darkModeChange)
2024-12-20 20:21:05 +01:00
}
}, [windowQuery, darkModeChange])
2025-12-10 14:49:40 +01:00
useEffect(() => localStorage.setItem('dismiss-login-alert', dismissLoginAlert.toString()), [dismissLoginAlert])
2024-12-20 20:21:05 +01:00
useEffect(() => {
setDarkMode(windowQuery.matches)
getConfiguration().then(configuration => {
setConfiguration(configuration)
getUser().then(() => {
setIsAuthenticated(true)
if (location.pathname === '/login') navigate('/home')
}).catch(() => {
setIsAuthenticated(false)
const pathname = location.pathname
2025-12-10 11:03:24 +01:00
if (configuration.publicRdapLookupEnabled) return navigate('/search/domain')
if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) return navigate('/home')
})
2025-12-09 13:34:50 +01:00
}).catch(() => messageApi.error(t`Unable to contact the server, please reload the page.`))
2024-12-20 20:21:05 +01:00
}, [])
2024-12-30 23:50:15 +01:00
return (
<ConfigProvider
theme={{
algorithm: darkMode ? theme.darkAlgorithm : undefined
2024-12-30 23:50:15 +01:00
}}
>
<ConfigurationContext.Provider value={configContextValue}>
2025-12-10 11:03:24 +01:00
<AuthenticatedContext.Provider value={authContextValue}>
2025-12-10 14:49:40 +01:00
{!dismissLoginAlert && (configuration?.registerEnabled || configuration?.ssoLogin) && isAuthenticated === false && !['/login'].includes(location.pathname) &&
2025-12-10 11:03:24 +01:00
<Alert
type="warning"
2025-12-10 12:41:43 +01:00
message={t`Please log in to access all features, monitor domains, and manage your Connectors.`}
action={<Link to='/login'><Button>{t`Log in`}</Button></Link>}
2025-12-10 14:49:40 +01:00
onClose={() => setDismissLoginAlert(true)}
2025-12-10 11:03:24 +01:00
banner closable/>
}
<Layout hasSider style={{minHeight: '100vh'}}>
<SiderWrapper sidebarCollapsed={sidebarCollapsed} setSidebarCollapsed={setSidebarCollapsed}>
<Sider/>
</SiderWrapper>
<Layout>
<Layout.Header style={{padding: 0}}>
{sm &&
<Button type="text" style={{marginLeft: 8}}
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}>
<MenuOutlined/>
</Button>
}
</Layout.Header>
<Layout.Content style={sm ? {margin: '24px 0'} : {margin: '24px 16px 0'}}>
<div style={{
padding: sm ? 8 : 24,
minHeight: 360
}}
2024-12-30 23:50:15 +01:00
>
2025-12-10 11:03:24 +01:00
{contextHolder}
<Routes>
<Route path='/' element={<Navigate
to={configuration?.publicRdapLookupEnabled ? '/search/domain' : '/home'}/>}/>
<Route path='/home' element={<TextPage resource='home.md'/>}/>
<Route path='/search/domain' element={<DomainSearchPage/>}/>
<Route path='/search/domain/:query' element={<DomainSearchPage/>}/>
<Route path='/search/entity' element={<EntitySearchPage/>}/>
<Route path='/infrastructure/tld' element={<TldPage/>}/>
<Route path='/infrastructure/icann' element={<IcannRegistrarPage/>}/>
<Route path='/tracking/watchlist' element={<WatchlistPage/>}/>
<Route path='/tracking/domains' element={<TrackedDomainPage/>}/>
<Route path='/tracking/connectors' element={<ConnectorPage/>}/>
<Route path='/stats' element={<StatisticsPage/>}/>
<Route path='/user' element={<UserPage/>}/>
<Route path='/faq' element={<TextPage resource='faq.md'/>}/>
<Route path='/tos' element={<TextPage resource='tos.md'/>}/>
<Route path='/privacy' element={<TextPage resource='privacy.md'/>}/>
<Route path='/login' element={<LoginPage/>}/>
<Route path='*' element={<NotFoundPage/>}/>
</Routes>
</div>
</Layout.Content>
<Layout.Footer style={{textAlign: 'center'}}>
<Flex gap='middle' wrap justify='center'>
<Link to='/tos' rel='terms-of-service'><Button type='text'>{t`TOS`}</Button></Link>
<Link to='/privacy' rel='privacy-policy'><Button
type='text'>{t`Privacy Policy`}</Button></Link>
<Link to='/faq'><Button type='text'>{t`FAQ`}</Button></Link>
<Button target='_blank'
href='https://domainwatchdog.eu'
type='text'>
2025-11-01 18:31:00 +01:00
{t`Documentation`}
</Button>
2025-12-10 11:03:24 +01:00
<Button target='_blank'
href={PROJECT_LINK}
type='text'>
2025-11-01 18:31:00 +01:00
{t`Source code`}
</Button>
2025-12-10 11:03:24 +01:00
<Button target='_blank'
href={PROJECT_LINK + '/issues'}
type='text'>
2025-11-01 18:31:00 +01:00
{t`Submit an issue`}
2024-12-30 23:50:15 +01:00
</Button>
2025-12-10 11:03:24 +01:00
</Flex>
<Typography.Paragraph style={{marginTop: '1em'}}>
{jt`${ProjectLink} is an open source project distributed under the ${LicenseLink} license.`}
</Typography.Paragraph>
</Layout.Footer>
</Layout>
2024-12-30 23:50:15 +01:00
</Layout>
2025-12-10 11:03:24 +01:00
</AuthenticatedContext.Provider>
</ConfigurationContext.Provider>
2024-12-30 23:50:15 +01:00
</ConfigProvider>
)
}