Merge branch 'bugfix/responsive' into develop

This commit is contained in:
Maël Gangloff
2025-11-01 17:55:13 +01:00
11 changed files with 194 additions and 127 deletions

View File

@@ -1,4 +1,4 @@
import {Button, ConfigProvider, FloatButton, Layout, Space, theme, Tooltip, Typography} from 'antd' import {Button, ConfigProvider, Drawer, Flex, FloatButton, Layout, theme, Tooltip, 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'
@@ -7,7 +7,8 @@ import TldPage from './pages/infrastructure/TldPage'
import StatisticsPage from './pages/StatisticsPage' 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 React, {useCallback, useEffect, useMemo, useState} from 'react' import type {PropsWithChildren} from 'react'
import React, { useCallback, useEffect, useMemo, useState} from 'react'
import {getUser} from './utils/api' import {getUser} from './utils/api'
import LoginPage, {AuthenticatedContext} from './pages/LoginPage' import LoginPage, {AuthenticatedContext} from './pages/LoginPage'
import ConnectorPage from './pages/tracking/ConnectorPage' import ConnectorPage from './pages/tracking/ConnectorPage'
@@ -15,21 +16,56 @@ import NotFoundPage from './pages/NotFoundPage'
import useBreakpoint from './hooks/useBreakpoint' import useBreakpoint from './hooks/useBreakpoint'
import {Sider} from './components/Sider' import {Sider} from './components/Sider'
import {jt, t} from 'ttag' import {jt, t} from 'ttag'
import {BugOutlined, InfoCircleOutlined, MergeOutlined} from '@ant-design/icons' import {BugOutlined, InfoCircleOutlined, MergeOutlined, MenuOutlined} from '@ant-design/icons'
import TrackedDomainPage from './pages/tracking/TrackedDomainPage' import TrackedDomainPage from './pages/tracking/TrackedDomainPage'
import IcannRegistrarPage from "./pages/infrastructure/IcannRegistrarPage" import IcannRegistrarPage from "./pages/infrastructure/IcannRegistrarPage"
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 target='_blank' href={PROJECT_LINK}>Domain Watchdog</Typography.Link> const ProjectLink = <Typography.Link key="projectLink" target='_blank' href={PROJECT_LINK}>Domain Watchdog</Typography.Link>
const LicenseLink = <Typography.Link target='_blank' href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link> const LicenseLink = <Typography.Link key="licenceLink" target='_blank' href={LICENSE_LINK}>AGPL-3.0-or-later</Typography.Link>
function SiderWrapper(props: PropsWithChildren<{sidebarCollapsed: boolean, setSidebarCollapsed: (collapsed: boolean) => void}>): React.ReactElement {
const {sidebarCollapsed, setSidebarCollapsed, children} = props
const sm = useBreakpoint('sm')
const location = useLocation()
useEffect(() => {
if (sm) {
setSidebarCollapsed(false)
}
}, [location])
if (sm) {
return <Drawer
placement="left"
open={sidebarCollapsed}
onClose={() => setSidebarCollapsed(false)}
closeIcon={null}
styles={{body: {padding: 0, height: '100%', background: '#001529'}}}
width='200px'>
{children}
</Drawer>
} else {
return <Layout.Sider
collapsible
breakpoint='sm'
width={220}
trigger={null}
collapsed={sidebarCollapsed && sm}
{...(sm ? {collapsedWidth: 0} : {})}>
{children}
</Layout.Sider>
}
}
export default function App(): React.ReactElement { export default function App(): React.ReactElement {
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const sm = useBreakpoint('sm') const sm = useBreakpoint('sm')
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false) const [isAuthenticated, setIsAuthenticated] = useState(false)
const authenticated = useCallback((authenticated: boolean) => { const authenticated = useCallback((authenticated: boolean) => {
@@ -75,15 +111,20 @@ export default function App(): React.ReactElement {
> >
<AuthenticatedContext.Provider value={contextValue}> <AuthenticatedContext.Provider value={contextValue}>
<Layout hasSider style={{minHeight: '100vh'}}> <Layout hasSider style={{minHeight: '100vh'}}>
{/* Ant will use a break-off tab to toggle the collapse of the sider when collapseWidth = 0 */} <SiderWrapper sidebarCollapsed={sidebarCollapsed} setSidebarCollapsed={setSidebarCollapsed}>
<Layout.Sider collapsible breakpoint='sm' width={220} {...(sm ? {collapsedWidth: 0} : {})}>
<Sider isAuthenticated={isAuthenticated}/> <Sider isAuthenticated={isAuthenticated}/>
</Layout.Sider> </SiderWrapper>
<Layout> <Layout>
<Layout.Header style={{padding: 0}}/> <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'}}> <Layout.Content style={sm ? {margin: '24px 0'} : {margin: '24px 16px 0'}}>
<div style={{ <div style={{
padding: 24, padding: sm ? 8 : 24,
minHeight: 360 minHeight: 360
}} }}
> >
@@ -116,7 +157,7 @@ export default function App(): React.ReactElement {
</div> </div>
</Layout.Content> </Layout.Content>
<Layout.Footer style={{textAlign: 'center'}}> <Layout.Footer style={{textAlign: 'center'}}>
<Space size='middle' wrap align='center'> <Flex gap='middle' wrap justify='center'>
<Link to='/tos'><Button type='text'>{t`TOS`}</Button></Link> <Link to='/tos'><Button type='text'>{t`TOS`}</Button></Link>
<Link to='/privacy'><Button type='text'>{t`Privacy Policy`}</Button></Link> <Link to='/privacy'><Button type='text'>{t`Privacy Policy`}</Button></Link>
<Link to='/faq'><Button type='text'>{t`FAQ`}</Button></Link> <Link to='/faq'><Button type='text'>{t`FAQ`}</Button></Link>
@@ -129,7 +170,7 @@ export default function App(): React.ReactElement {
>{t`Documentation`} >{t`Documentation`}
</Button> </Button>
</Typography.Link> </Typography.Link>
</Space> </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>

View File

@@ -1,6 +1,6 @@
import {Button, Form, Input, message, Space} from 'antd' import {Button, Flex, Form, Input, message} from 'antd'
import {t} from 'ttag' import {t} from 'ttag'
import React, {useContext, useEffect} from 'react' import React, {useContext, useEffect, useState} from 'react'
import {getUser, login} from '../utils/api' import {getUser, login} from '../utils/api'
import {AuthenticatedContext} from '../pages/LoginPage' import {AuthenticatedContext} from '../pages/LoginPage'
import {useNavigate} from 'react-router-dom' import {useNavigate} from 'react-router-dom'
@@ -16,6 +16,7 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
const navigate = useNavigate() const navigate = useNavigate()
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const {setIsAuthenticated} = useContext(AuthenticatedContext) const {setIsAuthenticated} = useContext(AuthenticatedContext)
const [loading, setLoading] = useState(false)
useEffect(() => { useEffect(() => {
getUser().then(() => { getUser().then(() => {
@@ -25,12 +26,15 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
}, []) }, [])
const onFinish = (data: FieldType) => { const onFinish = (data: FieldType) => {
setLoading(true)
login(data.username, data.password).then(() => { login(data.username, data.password).then(() => {
setIsAuthenticated(true) setIsAuthenticated(true)
navigate('/home') navigate('/home')
}).catch((e) => { }).catch((e) => {
setIsAuthenticated(false) setIsAuthenticated(false)
showErrorAPI(e, messageApi) showErrorAPI(e, messageApi)
setLoading(false)
}) })
} }
return ( return (
@@ -43,6 +47,7 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
style={{maxWidth: 600}} style={{maxWidth: 600}}
onFinish={onFinish} onFinish={onFinish}
autoComplete='off' autoComplete='off'
disabled={loading}
> >
<Form.Item <Form.Item
label={t`Email address`} label={t`Email address`}
@@ -60,18 +65,15 @@ export function LoginForm({ssoLogin}: { ssoLogin?: boolean }) {
<Input.Password/> <Input.Password/>
</Form.Item> </Form.Item>
<Space> <Flex wrap justify="center" gap="middle">
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type='primary' htmlType='submit'> <Button type='primary' htmlType='submit'>
{t`Submit`} {t`Submit`}
</Button> </Button>
</Form.Item> {ssoLogin &&
{ssoLogin && <Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type='dashed' htmlType='button' href='/login/oauth'> <Button type='dashed' htmlType='button' href='/login/oauth'>
{t`Log in with SSO`} {t`Log in with SSO`}
</Button> </Button>}
</Form.Item>} </Flex>
</Space>
</Form> </Form>
</> </>
) )

View File

@@ -22,16 +22,16 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
<> <>
<Divider/> <Divider/>
{connectors.map(connector => { {connectors.map(connector => {
const createdAt = <Typography.Text strong> const createdAt = <Typography.Text strong key={"createdAt"}>
{new Date(connector.createdAt).toLocaleString()} {new Date(connector.createdAt).toLocaleString()}
</Typography.Text> </Typography.Text>
const {watchlistCount} = connector const {watchlistCount} = connector
const connectorName = Object.keys(ConnectorProvider).find(p => ConnectorProvider[p as keyof typeof ConnectorProvider] === connector.provider) const connectorName = Object.keys(ConnectorProvider).find(p => ConnectorProvider[p as keyof typeof ConnectorProvider] === connector.provider)
return <> return <Card
{contextHolder} hoverable
<Card key={connector.id}
hoverable title={<Space> title={<Space>
{t`Connector ${connectorName}`}<Typography.Text code>{connector.id}</Typography.Text> {t`Connector ${connectorName}`}<Typography.Text code>{connector.id}</Typography.Text>
</Space>} </Space>}
size='small' size='small'
@@ -45,6 +45,7 @@ export function ConnectorsList({connectors, onDelete}: { connectors: ConnectorEl
><DeleteFilled style={{color: token.colorError}}/> ><DeleteFilled style={{color: token.colorError}}/>
</Popconfirm>} </Popconfirm>}
> >
{contextHolder}
<Typography.Paragraph>{jt`Creation date: ${createdAt}`}</Typography.Paragraph> <Typography.Paragraph>{jt`Creation date: ${createdAt}`}</Typography.Paragraph>
<Typography.Paragraph>{t`Used in: ${watchlistCount} Watchlist`}</Typography.Paragraph> <Typography.Paragraph>{t`Used in: ${watchlistCount} Watchlist`}</Typography.Paragraph>
<Card.Meta description={ <Card.Meta description={
@@ -58,7 +59,6 @@ The creation date corresponds to the date on which you consented to the creation
</> </>
}/> }/>
</Card> </Card>
</>
} }
)} )}
</> </>

View File

@@ -22,11 +22,13 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import {DomainToTag} from '../../../utils/functions/DomainToTag' import {DomainToTag} from '../../../utils/functions/DomainToTag'
import {isDomainLocked} from "../../../utils/functions/isDomainLocked" import {isDomainLocked} from "../../../utils/functions/isDomainLocked"
import useBreakpoint from "../../../hooks/useBreakpoint"
export function TrackedDomainTable() { export function TrackedDomainTable() {
const REDEMPTION_NOTICE = ( const REDEMPTION_NOTICE = (
<Tooltip <Tooltip
title={t`At least one domain name is in redemption period and will potentially be deleted soon`} title={t`At least one domain name is in redemption period and will potentially be deleted soon`}
key="redeptionNotice"
> >
<Tag color={eppStatusCodeToColor('redemption period')}>redemption period</Tag> <Tag color={eppStatusCodeToColor('redemption period')}>redemption period</Tag>
</Tooltip> </Tooltip>
@@ -35,6 +37,7 @@ export function TrackedDomainTable() {
const PENDING_DELETE_NOTICE = ( const PENDING_DELETE_NOTICE = (
<Tooltip <Tooltip
title={t`At least one domain name is pending deletion and will soon become available for registration again`} title={t`At least one domain name is pending deletion and will soon become available for registration again`}
key="pendingDeleteNotice"
> >
<Tag color={eppStatusCodeToColor('pending delete')}>pending delete</Tag> <Tag color={eppStatusCodeToColor('pending delete')}>pending delete</Tag>
</Tooltip> </Tooltip>
@@ -53,6 +56,7 @@ export function TrackedDomainTable() {
const [dataTable, setDataTable] = useState<TableRow[]>([]) const [dataTable, setDataTable] = useState<TableRow[]>([])
const [total, setTotal] = useState<number>() const [total, setTotal] = useState<number>()
const [specialNotice, setSpecialNotice] = useState<ReactElement[]>([]) const [specialNotice, setSpecialNotice] = useState<ReactElement[]>([])
const sm = useBreakpoint('sm')
const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation() const rdapStatusCodeDetailTranslated = rdapStatusCodeDetailTranslation()
@@ -220,6 +224,7 @@ export function TrackedDomainTable() {
text: <Tooltip text: <Tooltip
placement='bottomLeft' placement='bottomLeft'
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined} title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
key={s}
> >
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> <Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip>, </Tooltip>,
@@ -268,7 +273,8 @@ export function TrackedDomainTable() {
fetchData({page, itemsPerPage}) fetchData({page, itemsPerPage})
} }
}} }}
scroll={{y: '50vh'}} scroll={sm ? {} : {y: '50vh'}}
size={sm ? 'small' : 'large'}
/> />
</Skeleton> </Skeleton>
} }

View File

@@ -10,4 +10,8 @@
body { body {
margin: 0; margin: 0;
font-family: "Noto Color Emoji", sans-serif; font-family: "Noto Color Emoji", sans-serif;
@media (prefers-color-scheme: dark) {
background: #000000;
}
} }

View File

@@ -6,6 +6,7 @@ import {LoginForm} from '../components/LoginForm'
import type { InstanceConfig} from '../utils/api' import type { InstanceConfig} from '../utils/api'
import {getConfiguration} from '../utils/api' import {getConfiguration} from '../utils/api'
import {RegisterForm} from '../components/RegisterForm' import {RegisterForm} from '../components/RegisterForm'
import useBreakpoint from "../hooks/useBreakpoint"
export const AuthenticatedContext = createContext< export const AuthenticatedContext = createContext<
{ {
@@ -22,6 +23,7 @@ export const AuthenticatedContext = createContext<
export default function LoginPage() { export default function LoginPage() {
const [wantRegister, setWantRegister] = useState<boolean>(false) const [wantRegister, setWantRegister] = useState<boolean>(false)
const [configuration, setConfiguration] = useState<InstanceConfig>() const [configuration, setConfiguration] = useState<InstanceConfig>()
const md = useBreakpoint('md')
const toggleWantRegister = () => { const toggleWantRegister = () => {
setWantRegister(!wantRegister) setWantRegister(!wantRegister)
@@ -31,24 +33,32 @@ export default function LoginPage() {
getConfiguration().then(setConfiguration) getConfiguration().then(setConfiguration)
}, []) }, [])
const grid = [
<Card.Grid key="form" style={{width: md ? '100%' : '50%', textAlign: 'center'}} hoverable={false}>
{wantRegister ? <RegisterForm/> : <LoginForm ssoLogin={configuration?.ssoLogin}/>}
{
configuration?.registerEnabled &&
<Button
type='link'
block
style={{marginTop: '1em'}}
onClick={toggleWantRegister}
>{wantRegister ? t`Log in` : t`Create an account`}
</Button>
}
</Card.Grid>,
<Card.Grid key="ads" style={{width: md ? '100%' : '50%'}} hoverable={false}>
<TextPage resource='ads.md'/>
</Card.Grid>
]
if (md) {
grid.reverse()
}
return ( return (
<Card title={wantRegister ? t`Register` : t`Log in`} style={{width: '100%'}}> <Card title={wantRegister ? t`Register` : t`Log in`} style={{width: '100%'}}>
<Card.Grid style={{width: '50%', textAlign: 'center'}} hoverable={false}> {grid}
{wantRegister ? <RegisterForm/> : <LoginForm ssoLogin={configuration?.ssoLogin}/>}
{
configuration?.registerEnabled &&
<Button
type='link'
block
style={{marginTop: '1em'}}
onClick={toggleWantRegister}
>{wantRegister ? t`Log in` : t`Create an account`}
</Button>
}
</Card.Grid>
<Card.Grid style={{width: '50%'}} hoverable={false}>
<TextPage resource='ads.md'/>
</Card.Grid>
</Card> </Card>
) )
} }

View File

@@ -1,7 +1,7 @@
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import type { Statistics} from '../utils/api' import type { Statistics} from '../utils/api'
import {getStatistics} from '../utils/api' import {getStatistics} from '../utils/api'
import {Card, Col, Divider, Row, Statistic, Tooltip} from 'antd' import {Card, Col, Divider, Flex, Row, Statistic, Tooltip} from 'antd'
import {t} from 'ttag' import {t} from 'ttag'
import { import {
AimOutlined, AimOutlined,
@@ -104,20 +104,19 @@ export default function StatisticsPage() {
</Col> </Col>
</Row> </Row>
<Divider/> <Divider/>
<Row gutter={16} justify='center' align='middle'> <Flex gap={16} wrap justify='center' align='middle'>
{stats?.domainCount {stats?.domainCount
.sort((a, b) => b.domain - a.domain) .sort((a, b) => b.domain - a.domain)
.map(({domain, tld}) => <Col key={tld} span={4}> .map(({domain, tld}) =>
<Card bordered={false}> <Card key={tld} bordered={false}>
<Statistic <Statistic
loading={stats === undefined} loading={stats === undefined}
title={tld ? tld : t`TLD`} title={tld ? tld : t`TLD`}
value={domain} value={domain}
valueStyle={{color: 'darkorange'}} valueStyle={{color: 'darkorange'}}
/> />
</Card> </Card>)}
</Col>)} </Flex>
</Row>
</> </>
) )
} }

View File

@@ -5,6 +5,7 @@ import {t} from 'ttag'
import type {ColumnType} from 'antd/es/table' import type {ColumnType} from 'antd/es/table'
import {CheckCircleOutlined, SettingOutlined, CloseCircleOutlined} from "@ant-design/icons" import {CheckCircleOutlined, SettingOutlined, CloseCircleOutlined} from "@ant-design/icons"
import {getIcannAccreditations} from "../../utils/api/icann-accreditations" import {getIcannAccreditations} from "../../utils/api/icann-accreditations"
import useBreakpoint from "../../hooks/useBreakpoint"
const {Text, Paragraph} = Typography const {Text, Paragraph} = Typography
@@ -19,6 +20,7 @@ function RegistrarListTable(filters: FiltersType) {
name: string name: string
} }
const sm = useBreakpoint('sm')
const [dataTable, setDataTable] = useState<TableRow[]>([]) const [dataTable, setDataTable] = useState<TableRow[]>([])
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
@@ -63,14 +65,15 @@ function RegistrarListTable(filters: FiltersType) {
fetchData({...filters, page, itemsPerPage}) fetchData({...filters, page, itemsPerPage})
} }
}} }}
scroll={sm ? {} : {y: '50vh'}}
scroll={{y: '50vh'}} size={sm ? 'small' : 'large'}
/> />
) )
} }
export default function IcannRegistrarPage() { export default function IcannRegistrarPage() {
const [activeTabKey, setActiveTabKey] = useState<string>('Accredited') const [activeTabKey, setActiveTabKey] = useState<string>('Accredited')
const sm = useBreakpoint('sm')
const contentList: Record<string, React.ReactNode> = { const contentList: Record<string, React.ReactNode> = {
Accredited: <> Accredited: <>
@@ -125,6 +128,7 @@ export default function IcannRegistrarPage() {
activeTabKey={activeTabKey} activeTabKey={activeTabKey}
key={activeTabKey} key={activeTabKey}
onTabChange={(k: string) => setActiveTabKey(k)} onTabChange={(k: string) => setActiveTabKey(k)}
size={sm ? 'small' : 'default'}
> >
{contentList[activeTabKey]} {contentList[activeTabKey]}

View File

@@ -11,6 +11,7 @@ import {getCountryCode} from '../../utils/functions/getCountryCode'
import {tldToEmoji} from '../../utils/functions/tldToEmoji' import {tldToEmoji} from '../../utils/functions/tldToEmoji'
import {BankOutlined, FlagOutlined, GlobalOutlined, TrademarkOutlined} from "@ant-design/icons" import {BankOutlined, FlagOutlined, GlobalOutlined, TrademarkOutlined} from "@ant-design/icons"
import {Link} from "react-router-dom" import {Link} from "react-router-dom"
import useBreakpoint from "../../hooks/useBreakpoint"
const {Text, Paragraph} = Typography const {Text, Paragraph} = Typography
@@ -30,6 +31,7 @@ function TldTable(filters: FiltersType) {
Country?: string Country?: string
} }
const sm = useBreakpoint('sm')
const [dataTable, setDataTable] = useState<TableRow[]>([]) const [dataTable, setDataTable] = useState<TableRow[]>([])
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
@@ -110,14 +112,15 @@ function TldTable(filters: FiltersType) {
fetchData({...filters, page, itemsPerPage}) fetchData({...filters, page, itemsPerPage})
} }
}} }}
scroll={sm ? {} : {y: '50vh'}}
scroll={{y: '50vh'}} size={sm ? 'small' : 'large'}
/> />
) )
} }
export default function TldPage() { export default function TldPage() {
const [activeTabKey, setActiveTabKey] = useState<string>('gTLD') const [activeTabKey, setActiveTabKey] = useState<string>('gTLD')
const sm = useBreakpoint("sm")
const contentList: Record<string, React.ReactNode> = { const contentList: Record<string, React.ReactNode> = {
sTLD: <> sTLD: <>
@@ -185,6 +188,7 @@ export default function TldPage() {
activeTabKey={activeTabKey} activeTabKey={activeTabKey}
key={activeTabKey} key={activeTabKey}
onTabChange={(k: string) => setActiveTabKey(k)} onTabChange={(k: string) => setActiveTabKey(k)}
size={sm ? 'small' : 'default'}
> >
{contentList[activeTabKey]} {contentList[activeTabKey]}

View File

@@ -10,6 +10,7 @@ export function statusToTag(s: string) {
<Tooltip <Tooltip
placement='bottomLeft' placement='bottomLeft'
title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined} title={rdapStatusCodeDetailTranslated[s as keyof typeof rdapStatusCodeDetailTranslated] || undefined}
key={s}
> >
<Tag color={eppStatusCodeToColor(s)}>{s}</Tag> <Tag color={eppStatusCodeToColor(s)}>{s}</Tag>
</Tooltip> </Tooltip>

View File

@@ -3,44 +3,44 @@ 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:120 #: assets/App.tsx:161
msgid "TOS" msgid "TOS"
msgstr "" msgstr ""
#: assets/App.tsx:121 #: assets/App.tsx:162
msgid "Privacy Policy" msgid "Privacy Policy"
msgstr "" msgstr ""
#: assets/App.tsx:122 #: assets/App.tsx:163
msgid "FAQ" msgid "FAQ"
msgstr "" msgstr ""
#: assets/App.tsx:129 #: assets/App.tsx:170
msgid "Documentation" msgid "Documentation"
msgstr "" msgstr ""
#: assets/App.tsx:134 #: assets/App.tsx:175
#, javascript-format #, javascript-format
msgid "" msgid ""
"${ ProjectLink } is an open source project distributed under the ${ " "${ ProjectLink } is an open source project distributed under the ${ "
"LicenseLink } license." "LicenseLink } license."
msgstr "" msgstr ""
#: assets/App.tsx:147 #: assets/App.tsx:188
msgid "Official git repository" msgid "Official git repository"
msgstr "" msgstr ""
#: assets/App.tsx:150 #: assets/App.tsx:191
msgid "Submit an issue" msgid "Submit an issue"
msgstr "" msgstr ""
#: assets/components/LoginForm.tsx:48 #: assets/components/LoginForm.tsx:53
#: assets/components/RegisterForm.tsx:37 #: assets/components/RegisterForm.tsx:37
msgid "Email address" msgid "Email address"
msgstr "" msgstr ""
#: assets/components/LoginForm.tsx:50 #: assets/components/LoginForm.tsx:55
#: assets/components/LoginForm.tsx:58 #: assets/components/LoginForm.tsx:63
#: assets/components/RegisterForm.tsx:39 #: assets/components/RegisterForm.tsx:39
#: assets/components/RegisterForm.tsx:47 #: assets/components/RegisterForm.tsx:47
#: assets/components/search/DomainSearchBar.tsx:28 #: assets/components/search/DomainSearchBar.tsx:28
@@ -72,22 +72,22 @@ msgstr ""
msgid "Required" msgid "Required"
msgstr "" msgstr ""
#: assets/components/LoginForm.tsx:56 #: assets/components/LoginForm.tsx:61
#: assets/components/RegisterForm.tsx:45 #: assets/components/RegisterForm.tsx:45
#: assets/utils/providers/forms/EppConnectorForm.tsx:138 #: assets/utils/providers/forms/EppConnectorForm.tsx:138
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: assets/components/LoginForm.tsx:66 #: assets/components/LoginForm.tsx:70
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
#: assets/components/LoginForm.tsx:71 #: assets/components/LoginForm.tsx:74
msgid "Log in with SSO" msgid "Log in with SSO"
msgstr "" msgstr ""
#: assets/components/RegisterForm.tsx:54 #: assets/components/RegisterForm.tsx:54
#: assets/pages/LoginPage.tsx:35 #: assets/pages/LoginPage.tsx:60
msgid "Register" msgid "Register"
msgstr "" msgstr ""
@@ -97,22 +97,22 @@ msgid "Registration"
msgstr "" msgstr ""
#: assets/components/search/DomainLifecycleSteps.tsx:24 #: assets/components/search/DomainLifecycleSteps.tsx:24
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:133 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:137
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: assets/components/search/DomainLifecycleSteps.tsx:29 #: assets/components/search/DomainLifecycleSteps.tsx:29
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:111 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:115
msgid "Auto-Renew Grace Period" msgid "Auto-Renew Grace Period"
msgstr "" msgstr ""
#: assets/components/search/DomainLifecycleSteps.tsx:35 #: assets/components/search/DomainLifecycleSteps.tsx:35
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:119 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:123
msgid "Redemption Grace Period" msgid "Redemption Grace Period"
msgstr "" msgstr ""
#: assets/components/search/DomainLifecycleSteps.tsx:40 #: assets/components/search/DomainLifecycleSteps.tsx:40
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:127 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:131
msgid "Pending Delete" msgid "Pending Delete"
msgstr "" msgstr ""
@@ -124,7 +124,7 @@ msgid ""
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:64 #: assets/components/search/DomainResult.tsx:64
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:89 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:93
msgid "Registry Lock" msgid "Registry Lock"
msgstr "" msgstr ""
@@ -135,7 +135,7 @@ msgid ""
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:75 #: assets/components/search/DomainResult.tsx:75
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:95 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:99
msgid "Registrar Lock" msgid "Registrar Lock"
msgstr "" msgstr ""
@@ -146,12 +146,12 @@ msgid ""
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:84 #: assets/components/search/DomainResult.tsx:84
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:101 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:105
msgid "DNSSEC" msgid "DNSSEC"
msgstr "" msgstr ""
#: assets/components/search/DomainResult.tsx:90 #: assets/components/search/DomainResult.tsx:90
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:215 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:219
msgid "EPP Status Codes" msgid "EPP Status Codes"
msgstr "" msgstr ""
@@ -177,7 +177,7 @@ msgid "Search"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:43 #: assets/components/Sider.tsx:43
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:175 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:179
msgid "Domain" msgid "Domain"
msgstr "" msgstr ""
@@ -192,7 +192,7 @@ msgstr ""
#: assets/components/Sider.tsx:70 #: assets/components/Sider.tsx:70
#: assets/pages/StatisticsPage.tsx:114 #: assets/pages/StatisticsPage.tsx:114
#: assets/pages/infrastructure/TldPage.tsx:79 #: assets/pages/infrastructure/TldPage.tsx:81
msgid "TLD" msgid "TLD"
msgstr "" msgstr ""
@@ -250,13 +250,13 @@ msgid "Log out"
msgstr "" msgstr ""
#: assets/components/Sider.tsx:154 #: assets/components/Sider.tsx:154
#: assets/pages/LoginPage.tsx:35 #: assets/pages/LoginPage.tsx:46
#: assets/pages/LoginPage.tsx:45 #: assets/pages/LoginPage.tsx:60
msgid "Log in" msgid "Log in"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorForm.tsx:36 #: assets/components/tracking/connector/ConnectorForm.tsx:36
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:49 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:51
#: assets/utils/functions/rdapTranslation.ts:12 #: assets/utils/functions/rdapTranslation.ts:12
msgid "Registrar" msgid "Registrar"
msgstr "" msgstr ""
@@ -328,17 +328,17 @@ msgstr ""
msgid "No" msgid "No"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorsList.tsx:48 #: assets/components/tracking/connector/ConnectorsList.tsx:49
#, javascript-format #, javascript-format
msgid "Creation date: ${ createdAt }" msgid "Creation date: ${ createdAt }"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorsList.tsx:49 #: assets/components/tracking/connector/ConnectorsList.tsx:50
#, javascript-format #, javascript-format
msgid "Used in: ${ watchlistCount } Watchlist" msgid "Used in: ${ watchlistCount } Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorsList.tsx:52 #: assets/components/tracking/connector/ConnectorsList.tsx:53
msgid "" msgid ""
"You can stop using a connector at any time. To delete a connector, you must " "You can stop using a connector at any time. To delete a connector, you must "
"remove it from each linked Watchlist.\n" "remove it from each linked Watchlist.\n"
@@ -348,7 +348,7 @@ msgid ""
"withdrawal and were of the minimum age to consent to these conditions." "withdrawal and were of the minimum age to consent to these conditions."
msgstr "" msgstr ""
#: assets/components/tracking/connector/ConnectorsList.tsx:56 #: assets/components/tracking/connector/ConnectorsList.tsx:57
msgid "The Providers conditions are accessible by following this hyperlink." msgid "The Providers conditions are accessible by following this hyperlink."
msgstr "" msgstr ""
@@ -404,66 +404,66 @@ msgstr ""
msgid "Enable the Watchlist" msgid "Enable the Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:29 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:30
msgid "" msgid ""
"At least one domain name is in redemption period and will potentially be " "At least one domain name is in redemption period and will potentially be "
"deleted soon" "deleted soon"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:37 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:39
msgid "" msgid ""
"At least one domain name is pending deletion and will soon become available " "At least one domain name is pending deletion and will soon become available "
"for registration again" "for registration again"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:143 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:147
msgid "Estimated number of days until WHOIS removal" msgid "Estimated number of days until WHOIS removal"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:146 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:150
#, javascript-format #, javascript-format
msgid "J ${ expiresInDays }" msgid "J ${ expiresInDays }"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:152 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:156
msgid "Deletion is imminent" msgid "Deletion is imminent"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:181 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:185
msgid "Status" msgid "Status"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:187 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:191
msgid "Options" msgid "Options"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:193 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:197
msgid "Expiration date" msgid "Expiration date"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:207 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:211
msgid "Updated at" msgid "Updated at"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:235 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:240
msgid "No tracked domain names were found, please create your first Watchlist" msgid "No tracked domain names were found, please create your first Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:238 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:243
msgid "Create now" msgid "Create now"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:244 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:249
msgid "" msgid ""
"Please note that this table does not include domain names marked as expired " "Please note that this table does not include domain names marked as expired "
"or those with an unknown expiration date" "or those with an unknown expiration date"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:249 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:254
msgid "At least one domain name you are tracking requires special attention" msgid "At least one domain name you are tracking requires special attention"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/TrackedDomainTable.tsx:255 #: assets/components/tracking/watchlist/TrackedDomainTable.tsx:260
msgid "The domain names below are subject to special monitoring" msgid "The domain names below are subject to special monitoring"
msgstr "" msgstr ""
@@ -475,10 +475,6 @@ msgstr ""
msgid "Update a Watchlist" msgid "Update a Watchlist"
msgstr "" msgstr ""
#: assets/components/tracking/watchlist/UpdateWatchlistButton.tsx:56
msgid "Cancel"
msgstr ""
#: assets/components/tracking/watchlist/WatchlistCard.tsx:54 #: assets/components/tracking/watchlist/WatchlistCard.tsx:54
msgid "This Watchlist is not linked to a Connector." msgid "This Watchlist is not linked to a Connector."
msgstr "" msgstr ""
@@ -568,31 +564,31 @@ msgstr ""
msgid "Reset" msgid "Reset"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:43 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:45
msgid "ID" msgid "ID"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:77 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:80
msgid "" msgid ""
"An accredited number means that ICANN's contract with the registrar is " "An accredited number means that ICANN's contract with the registrar is "
"ongoing." "ongoing."
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:82 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:85
msgid "A reserved number can be used by TLD registries for specific operations." msgid "A reserved number can be used by TLD registries for specific operations."
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:87 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:90
msgid "" msgid ""
"A terminated number means that ICANN's contract with the registrar has been " "A terminated number means that ICANN's contract with the registrar has been "
"terminated." "terminated."
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:96 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:99
msgid "This page lists ICANN-accredited registrars." msgid "This page lists ICANN-accredited registrars."
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:99 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:102
msgid "" msgid ""
"The list is officially published and maintained by the Internet Assigned " "The list is officially published and maintained by the Internet Assigned "
"Numbers Authority (IANA), the organization responsible for managing the " "Numbers Authority (IANA), the organization responsible for managing the "
@@ -600,63 +596,63 @@ msgid ""
"name extensions)." "name extensions)."
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:111 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:114
#: assets/utils/functions/rdapTranslation.ts:125 #: assets/utils/functions/rdapTranslation.ts:125
msgid "Accredited" msgid "Accredited"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:116 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:119
#: assets/utils/functions/rdapTranslation.ts:126 #: assets/utils/functions/rdapTranslation.ts:126
msgid "Reserved" msgid "Reserved"
msgstr "" msgstr ""
#: assets/pages/infrastructure/IcannRegistrarPage.tsx:121 #: assets/pages/infrastructure/IcannRegistrarPage.tsx:124
#: assets/utils/functions/rdapTranslation.ts:124 #: assets/utils/functions/rdapTranslation.ts:124
msgid "Terminated" msgid "Terminated"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:86 #: assets/pages/infrastructure/TldPage.tsx:88
msgid "Flag" msgid "Flag"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:89 #: assets/pages/infrastructure/TldPage.tsx:91
msgid "Country" msgid "Country"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:96 #: assets/pages/infrastructure/TldPage.tsx:98
msgid "Registry Operator" msgid "Registry Operator"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:124 #: assets/pages/infrastructure/TldPage.tsx:127
msgid "" msgid ""
"Top-level domains sponsored by specific organizations that set rules for " "Top-level domains sponsored by specific organizations that set rules for "
"registration and use, often related to particular interest groups or " "registration and use, often related to particular interest groups or "
"industries." "industries."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:129 #: assets/pages/infrastructure/TldPage.tsx:132
msgid "" msgid ""
"Generic top-level domains open to everyone, not restricted by specific " "Generic top-level domains open to everyone, not restricted by specific "
"criteria, representing various themes or industries." "criteria, representing various themes or industries."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:134 #: assets/pages/infrastructure/TldPage.tsx:137
msgid "" msgid ""
"Generic top-level domains associated with specific brands, allowing " "Generic top-level domains associated with specific brands, allowing "
"companies to use their own brand names as domains." "companies to use their own brand names as domains."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:139 #: assets/pages/infrastructure/TldPage.tsx:142
msgid "" msgid ""
"Top-level domains based on country codes, identifying websites according to " "Top-level domains based on country codes, identifying websites according to "
"their country of origin." "their country of origin."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:148 #: assets/pages/infrastructure/TldPage.tsx:151
msgid "This page presents all active TLDs in the root zone database." msgid "This page presents all active TLDs in the root zone database."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:151 #: assets/pages/infrastructure/TldPage.tsx:154
msgid "" msgid ""
"IANA provides the list of currently active TLDs, regardless of their type, " "IANA provides the list of currently active TLDs, regardless of their type, "
"and ICANN provides the list of gTLDs.\n" "and ICANN provides the list of gTLDs.\n"
@@ -668,23 +664,23 @@ msgid ""
"At the same time, the list of root RDAP servers is updated." "At the same time, the list of root RDAP servers is updated."
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:166 #: assets/pages/infrastructure/TldPage.tsx:169
msgid "Generic Top-Level-Domains" msgid "Generic Top-Level-Domains"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:171 #: assets/pages/infrastructure/TldPage.tsx:174
msgid "Country-Code Top-Level-Domains" msgid "Country-Code Top-Level-Domains"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:176 #: assets/pages/infrastructure/TldPage.tsx:179
msgid "Brand Generic Top-Level-Domains" msgid "Brand Generic Top-Level-Domains"
msgstr "" msgstr ""
#: assets/pages/infrastructure/TldPage.tsx:181 #: assets/pages/infrastructure/TldPage.tsx:184
msgid "Sponsored Top-Level-Domains" msgid "Sponsored Top-Level-Domains"
msgstr "" msgstr ""
#: assets/pages/LoginPage.tsx:45 #: assets/pages/LoginPage.tsx:46
msgid "Create an account" msgid "Create an account"
msgstr "" msgstr ""