feat: add authentication banner

This commit is contained in:
Maël Gangloff 2025-12-10 11:03:24 +01:00
parent 7b04d1889e
commit 5c2c74cfb4
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
5 changed files with 132 additions and 117 deletions

View File

@ -1,4 +1,4 @@
import {Button, ConfigProvider, Drawer, Flex, Layout, message, theme, Typography} from 'antd' import {Alert, Button, ConfigProvider, Drawer, Flex, Layout, message, theme, Typography} from 'antd'
import {Link, Navigate, Route, Routes, useLocation, useNavigate} from 'react-router-dom' import {Link, Navigate, Route, Routes, useLocation, useNavigate} from 'react-router-dom'
import TextPage from './pages/TextPage' import TextPage from './pages/TextPage'
import DomainSearchPage from './pages/search/DomainSearchPage' import DomainSearchPage from './pages/search/DomainSearchPage'
@ -8,7 +8,7 @@ import StatisticsPage from './pages/StatisticsPage'
import WatchlistPage from './pages/tracking/WatchlistPage' import WatchlistPage from './pages/tracking/WatchlistPage'
import UserPage from './pages/UserPage' import UserPage from './pages/UserPage'
import type {PropsWithChildren} from 'react' import type {PropsWithChildren} from 'react'
import React, { useCallback, useEffect, useMemo, useState} from 'react' import React, {useCallback, useEffect, useMemo, useState} from 'react'
import {getConfiguration, getUser, type InstanceConfig} from './utils/api' import {getConfiguration, getUser, type InstanceConfig} from './utils/api'
import LoginPage from './pages/LoginPage' import LoginPage from './pages/LoginPage'
import ConnectorPage from './pages/tracking/ConnectorPage' import ConnectorPage from './pages/tracking/ConnectorPage'
@ -25,10 +25,15 @@ import {AuthenticatedContext, ConfigurationContext} from "./contexts"
const PROJECT_LINK = 'https://github.com/maelgangloff/domain-watchdog' const PROJECT_LINK = 'https://github.com/maelgangloff/domain-watchdog'
const LICENSE_LINK = 'https://www.gnu.org/licenses/agpl-3.0.txt' const LICENSE_LINK = 'https://www.gnu.org/licenses/agpl-3.0.txt'
const ProjectLink = <Typography.Link key="projectLink" target='_blank' href={PROJECT_LINK}>Domain Watchdog</Typography.Link> const ProjectLink = <Typography.Link key="projectLink" target='_blank' href={PROJECT_LINK}>Domain
const LicenseLink = <Typography.Link key="licenceLink" target='_blank' rel='license' href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link> Watchdog</Typography.Link>
const LicenseLink = <Typography.Link key="licenceLink" target='_blank' rel='license'
href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link>
function SiderWrapper(props: PropsWithChildren<{sidebarCollapsed: boolean, setSidebarCollapsed: (collapsed: boolean) => void}>): React.ReactElement { function SiderWrapper(props: PropsWithChildren<{
sidebarCollapsed: boolean,
setSidebarCollapsed: (collapsed: boolean) => void
}>): React.ReactElement {
const {sidebarCollapsed, setSidebarCollapsed, children} = props const {sidebarCollapsed, setSidebarCollapsed, children} = props
const sm = useBreakpoint('sm') const sm = useBreakpoint('sm')
const location = useLocation() const location = useLocation()
@ -41,22 +46,22 @@ function SiderWrapper(props: PropsWithChildren<{sidebarCollapsed: boolean, setSi
if (sm) { if (sm) {
return <Drawer return <Drawer
placement="left" placement="left"
open={sidebarCollapsed} open={sidebarCollapsed}
onClose={() => setSidebarCollapsed(false)} onClose={() => setSidebarCollapsed(false)}
closeIcon={null} closeIcon={null}
styles={{body: {padding: 0, height: '100%', background: '#001529'}}} styles={{body: {padding: 0, height: '100%', background: '#001529'}}}
width='200px'> width='200px'>
{children} {children}
</Drawer> </Drawer>
} else { } else {
return <Layout.Sider return <Layout.Sider
collapsible collapsible
breakpoint='sm' breakpoint='sm'
width={220} width={220}
trigger={null} trigger={null}
collapsed={sidebarCollapsed && sm} collapsed={sidebarCollapsed && sm}
{...(sm ? {collapsedWidth: 0} : {})}> {...(sm ? {collapsedWidth: 0} : {})}>
{children} {children}
</Layout.Sider> </Layout.Sider>
} }
@ -68,8 +73,7 @@ export default function App(): React.ReactElement {
const sm = useBreakpoint('sm') const sm = useBreakpoint('sm')
const [sidebarCollapsed, setSidebarCollapsed] = useState(false) const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState<boolean | undefined>(undefined)
const [configuration, setConfiguration] = useState<InstanceConfig | undefined>(undefined) const [configuration, setConfiguration] = useState<InstanceConfig | undefined>(undefined)
const [darkMode, setDarkMode] = useState(false) const [darkMode, setDarkMode] = useState(false)
@ -108,7 +112,7 @@ export default function App(): React.ReactElement {
}).catch(() => { }).catch(() => {
setIsAuthenticated(false) setIsAuthenticated(false)
const pathname = location.pathname const pathname = location.pathname
if(configuration.publicRdapLookupEnabled) return navigate('/search/domain') if (configuration.publicRdapLookupEnabled) return navigate('/search/domain')
if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) return navigate('/home') if (!['/login', '/tos', '/faq', '/privacy'].includes(pathname)) return navigate('/home')
}) })
}).catch(() => messageApi.error(t`Unable to contact the server, please reload the page.`)) }).catch(() => messageApi.error(t`Unable to contact the server, please reload the page.`))
@ -121,91 +125,92 @@ export default function App(): React.ReactElement {
}} }}
> >
<ConfigurationContext.Provider value={configContextValue}> <ConfigurationContext.Provider value={configContextValue}>
<AuthenticatedContext.Provider value={authContextValue}> <AuthenticatedContext.Provider value={authContextValue}>
<Layout hasSider style={{minHeight: '100vh'}}> {(configuration?.registerEnabled || configuration?.ssoLogin) && isAuthenticated === false && location.pathname !== '/login' &&
<SiderWrapper sidebarCollapsed={sidebarCollapsed} setSidebarCollapsed={setSidebarCollapsed}> <Alert
<Sider /> type="warning"
</SiderWrapper> message={t`Please authenticate to take advantage of all features, monitor domains and manage your Connectors.`}
<Layout> action={<Link to='/login'><Button>{t`Log in`}`</Button></Link>}
<Layout.Header style={{padding: 0}}> banner closable/>
{sm && }
<Button type="text" style={{marginLeft: 8}} onClick={() => setSidebarCollapsed(!sidebarCollapsed)}> <Layout hasSider style={{minHeight: '100vh'}}>
<MenuOutlined /> <SiderWrapper sidebarCollapsed={sidebarCollapsed} setSidebarCollapsed={setSidebarCollapsed}>
</Button> <Sider/>
} </SiderWrapper>
</Layout.Header> <Layout>
<Layout.Content style={sm ? {margin: '24px 0'} : {margin: '24px 16px 0'}}> <Layout.Header style={{padding: 0}}>
<div style={{ {sm &&
padding: sm ? 8 : 24, <Button type="text" style={{marginLeft: 8}}
minHeight: 360 onClick={() => setSidebarCollapsed(!sidebarCollapsed)}>
}} <MenuOutlined/>
> </Button>
{contextHolder} }
<Routes> </Layout.Header>
<Route path='/' element={<Navigate to={configuration?.publicRdapLookupEnabled ? '/search/domain' : '/home'}/>}/> <Layout.Content style={sm ? {margin: '24px 0'} : {margin: '24px 16px 0'}}>
<Route path='/home' element={<TextPage resource='home.md'/>}/> <div style={{
padding: sm ? 8 : 24,
<Route path='/search/domain' element={<DomainSearchPage/>}/> minHeight: 360
<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>
<Typography.Link
target='_blank'
href='https://domainwatchdog.eu'
> >
<Button type='text'> {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'>
{t`Documentation`} {t`Documentation`}
</Button> </Button>
</Typography.Link> <Button target='_blank'
<Typography.Link href={PROJECT_LINK}
target='_blank' type='text'>
href={PROJECT_LINK}
>
<Button type='text'>
{t`Source code`} {t`Source code`}
</Button> </Button>
</Typography.Link> <Button target='_blank'
<Typography.Link href={PROJECT_LINK + '/issues'}
target='_blank' type='text'>
href={PROJECT_LINK + '/issues'}
>
<Button type='text'>
{t`Submit an issue`} {t`Submit an issue`}
</Button> </Button>
</Typography.Link> </Flex>
</Flex> <Typography.Paragraph style={{marginTop: '1em'}}>
<Typography.Paragraph style={{marginTop: '1em'}}> {jt`${ProjectLink} is an open source project distributed under the ${LicenseLink} license.`}
{jt`${ProjectLink} is an open source project distributed under the ${LicenseLink} license.`} </Typography.Paragraph>
</Typography.Paragraph> </Layout.Footer>
</Layout.Footer> </Layout>
</Layout> </Layout>
</Layout> </AuthenticatedContext.Provider>
</AuthenticatedContext.Provider>
</ConfigurationContext.Provider> </ConfigurationContext.Provider>
</ConfigProvider> </ConfigProvider>
) )

View File

@ -12,7 +12,7 @@ function getWhoisRemoveTimelineEvent(expiresInDays: number) {
const locale = navigator.language.split('-')[0] const locale = navigator.language.split('-')[0]
const sm = useBreakpoint('sm') const sm = useBreakpoint('sm')
const eventName = t`Estimated removal` const eventName = t`Estimated removal`
const eventDetail = t`Estimated date when the WHOIS record is removed` const eventDetail = t`Estimated WHOIS removal date. This is the earliest date this record would be deleted, according to ICANN's standard lifecycle. Note that some registries have their own lifecycles.`
const dateStr = const dateStr =
<Typography.Text> <Typography.Text>

View File

@ -13,12 +13,12 @@ export const ConfigurationContext = createContext<ConfigurationContextType>({
export type AuthContextType = { export type AuthContextType = {
isAuthenticated: boolean isAuthenticated?: boolean
setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean>> setIsAuthenticated: React.Dispatch<React.SetStateAction<boolean | undefined>>
} }
export const AuthenticatedContext = createContext<AuthContextType>({ export const AuthenticatedContext = createContext<AuthContextType>({
isAuthenticated: false, isAuthenticated: undefined,
setIsAuthenticated: () => { setIsAuthenticated: () => {
}, },
}) })

View File

@ -1,6 +1,6 @@
import React, {useContext, useEffect, useState} from 'react' import React, {useContext, useEffect, useState} from 'react'
import type {FormProps} from 'antd' import type {FormProps} from 'antd'
import {Empty, Flex, FloatButton, message, Skeleton} from 'antd' import { Empty, Flex, FloatButton, message, Skeleton} from 'antd'
import type {Domain, Watchlist} from '../../utils/api' import type {Domain, Watchlist} from '../../utils/api'
import {addDomainToWatchlist, getDomain} from '../../utils/api' import {addDomainToWatchlist, getDomain} from '../../utils/api'
import type {AxiosError} from 'axios' import type {AxiosError} from 'axios'

View File

@ -3,35 +3,48 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n"
#: assets/App.tsx:114 #: assets/App.tsx:118
msgid "Unable to contact the server, please reload the page." msgid "Unable to contact the server, please reload the page."
msgstr "" msgstr ""
#: assets/App.tsx:174 #: assets/App.tsx:132
msgid ""
"Please authenticate to take advantage of all features, monitor domains and "
"manage your Connectors."
msgstr ""
#: assets/App.tsx:133
#: assets/components/Sider.tsx:158
#: assets/pages/LoginPage.tsx:36
#: assets/pages/LoginPage.tsx:50
msgid "Log in"
msgstr ""
#: assets/App.tsx:187
msgid "TOS" msgid "TOS"
msgstr "" msgstr ""
#: assets/App.tsx:175 #: assets/App.tsx:189
msgid "Privacy Policy" msgid "Privacy Policy"
msgstr "" msgstr ""
#: assets/App.tsx:176 #: assets/App.tsx:190
msgid "FAQ" msgid "FAQ"
msgstr "" msgstr ""
#: assets/App.tsx:182 #: assets/App.tsx:194
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
#: assets/App.tsx:190 #: assets/App.tsx:199
msgid "Source code" msgid "Source code"
msgstr "" msgstr ""
#: assets/App.tsx:198 #: assets/App.tsx:204
msgid "Submit an issue" msgid "Submit an issue"
msgstr "" msgstr ""
#: assets/App.tsx:203 #: assets/App.tsx:208
#, javascript-format #, javascript-format
msgid "" msgid ""
"${ ProjectLink } is an open source project distributed under the ${ " "${ ProjectLink } is an open source project distributed under the ${ "
@ -177,7 +190,10 @@ msgid "Estimated removal"
msgstr "" msgstr ""
#: assets/components/search/EventTimeline.tsx:15 #: assets/components/search/EventTimeline.tsx:15
msgid "Estimated date when the WHOIS record is removed" msgid ""
"Estimated WHOIS removal date. This is the earliest date this record would "
"be deleted, according to ICANN's standard lifecycle. Note that some "
"registries have their own lifecycles."
msgstr "" msgstr ""
#: assets/components/Sider.tsx:35 #: assets/components/Sider.tsx:35
@ -261,12 +277,6 @@ msgstr ""
msgid "Log out" msgid "Log out"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:158
#: assets/pages/LoginPage.tsx:36
#: assets/pages/LoginPage.tsx:50
msgid "Log in"
msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:36 #: assets/components/tracking/connector/ConnectorForm.tsx:36
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:51 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:51
#: assets/utils/functions/rdapTranslation.ts:12 #: assets/utils/functions/rdapTranslation.ts:12