feat: update front layout

This commit is contained in:
Maël Gangloff
2024-07-26 23:55:12 +02:00
parent f72f6df546
commit 133850da1c
9 changed files with 214 additions and 40 deletions

View File

@@ -14,7 +14,7 @@ import {
TeamOutlined, TeamOutlined,
UserOutlined UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import {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 tos from "./content/tos.md"; import tos from "./content/tos.md";
import privacy from "./content/privacy.md"; import privacy from "./content/privacy.md";
@@ -24,7 +24,7 @@ import NameserverSearchPage from "./pages/search/NameserverSearchPage";
import TldPage from "./pages/info/TldPage"; import TldPage from "./pages/info/TldPage";
import StatisticsPage from "./pages/info/StatisticsPage"; import StatisticsPage from "./pages/info/StatisticsPage";
import WatchlistsPage from "./pages/tracking/WatchlistsPage"; import WatchlistsPage from "./pages/tracking/WatchlistsPage";
import UserPage from "./pages/UserPage"; import UserPage from "./pages/watchdog/UserPage";
import React, {useCallback, useEffect, useMemo, useState} from "react"; import React, {useCallback, useEffect, useMemo, useState} from "react";
import {getUser} from "./utils/api"; import {getUser} from "./utils/api";
import FAQPage from "./pages/FAQPage"; import FAQPage from "./pages/FAQPage";
@@ -38,9 +38,12 @@ export default function App() {
} = theme.useToken() } = theme.useToken()
const navigate = useNavigate() const navigate = useNavigate()
const [isAuthenticated, setIsAuthenticated] = useState(false)
const location = useLocation() const location = useLocation()
const [isAuthenticated, setIsAuthenticated] = useState(false)
const authenticated = useCallback((authenticated: boolean) => { const authenticated = useCallback((authenticated: boolean) => {
setIsAuthenticated(authenticated) setIsAuthenticated(authenticated)
}, []); }, []);
@@ -65,6 +68,7 @@ export default function App() {
{ {
key: '1', key: '1',
label: 'Search', label: 'Search',
icon: <SearchOutlined/>,
children: [ children: [
{ {
key: '1-1', key: '1-1',
@@ -95,11 +99,13 @@ export default function App() {
{ {
key: '2', key: '2',
label: 'Information', label: 'Information',
icon: <InfoCircleOutlined/>,
children: [ children: [
{ {
key: '2-1', key: '2-1',
icon: <BankOutlined/>, icon: <BankOutlined/>,
label: 'TLD', label: 'TLD',
title: 'TLD list',
disabled: !isAuthenticated, disabled: !isAuthenticated,
onClick: () => navigate('/info/tld') onClick: () => navigate('/info/tld')
}, },
@@ -115,11 +121,11 @@ export default function App() {
{ {
key: '3', key: '3',
label: 'Tracking', label: 'Tracking',
icon: <FileSearchOutlined/>,
children: [ children: [
{ {
key: '3-1', key: '3-1',
icon: <Badge count={0} size="small"><FileSearchOutlined icon: <Badge count={0} size="small"><FileSearchOutlined/></Badge>,
shape="square"/></Badge>,
label: 'My Watchlists', label: 'My Watchlists',
disabled: !isAuthenticated, disabled: !isAuthenticated,
onClick: () => navigate('/tracking/watchlist') onClick: () => navigate('/tracking/watchlist')
@@ -135,10 +141,29 @@ export default function App() {
}, },
{ {
key: '4', key: '4',
label: 'My Watchdog',
icon: <UserOutlined/>, icon: <UserOutlined/>,
label: 'My Account', children: [
disabled: !isAuthenticated, {
onClick: () => navigate('/user') key: '4-1',
icon: <UserOutlined/>,
label: 'My Account',
disabled: !isAuthenticated,
onClick: () => navigate('/user')
},
{
key: '4-2',
icon: <InfoCircleOutlined/>,
label: 'TOS',
onClick: () => navigate('/tos')
},
{
key: '4-3',
icon: <FileProtectOutlined/>,
label: 'Privacy Policy',
onClick: () => navigate('/privacy')
}
]
}, },
{ {
key: '5', key: '5',
@@ -146,27 +171,16 @@ export default function App() {
label: 'FAQ', label: 'FAQ',
onClick: () => navigate('/faq') onClick: () => navigate('/faq')
}, },
{
key: '6',
icon: <InfoCircleOutlined/>,
label: 'TOS',
onClick: () => navigate('/tos')
},
{
key: '7',
icon: <FileProtectOutlined/>,
label: 'Privacy Policy',
onClick: () => navigate('/privacy')
}
] ]
return <AuthenticatedContext.Provider value={contextValue}> return <AuthenticatedContext.Provider value={contextValue}>
<Layout hasSider style={{minHeight: '100vh'}}> <Layout hasSider style={{minHeight: '100vh'}}>
<Layout.Sider> <Layout.Sider collapsible>
<Menu <Menu
defaultSelectedKeys={['1-1']} defaultSelectedKeys={['1-1']}
defaultOpenKeys={['1', '2', '3']} defaultOpenKeys={['1', '2', '3', '4']}
mode="inline" mode="inline"
theme="dark" theme="dark"
items={[...menuItems, isAuthenticated ? { items={[...menuItems, isAuthenticated ? {
@@ -220,7 +234,8 @@ export default function App() {
</div> </div>
</Layout.Content> </Layout.Content>
<Layout.Footer style={{textAlign: 'center'}}> <Layout.Footer style={{textAlign: 'center'}}>
Domain Watchdog ©{new Date().getFullYear()} Created by Maël Gangloff <Link to='https://github.com/maelgangloff/domain-watchdog'>Domain
Watchdog</Link> ©{new Date().getFullYear()} Created by Maël Gangloff
</Layout.Footer> </Layout.Footer>
</Layout> </Layout>
</Layout> </Layout>

View File

@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import {HashRouter} from "react-router-dom"; import {HashRouter} from "react-router-dom";
import './index.css' import 'antd/dist/reset.css';
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement) const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)

View File

@@ -1,4 +1,4 @@
import React, {createContext, useContext, useEffect, useState} from "react"; import React, {createContext, useContext, useState} from "react";
import {Alert, Button, Card, Flex, Form, Input} from "antd"; import {Alert, Button, Card, Flex, Form, Input} from "antd";
import {login} from "../utils/api"; import {login} from "../utils/api";
import {useNavigate} from "react-router-dom"; import {useNavigate} from "react-router-dom";
@@ -28,7 +28,6 @@ export default function Page() {
return <Flex gap="middle" align="center" justify="center" vertical><Card return <Flex gap="middle" align="center" justify="center" vertical><Card
title="Log in" title="Log in"
style={{width: 500}}
> >
{error && {error &&
<Alert <Alert
@@ -68,6 +67,11 @@ export default function Page() {
Submit Submit
</Button> </Button>
</Form.Item> </Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" htmlType="button" href="/login/oauth">
Log in with SSO
</Button>
</Form.Item>
</Form> </Form>
</Card> </Card>
</Flex> </Flex>

View File

@@ -1,7 +0,0 @@
import React from "react";
export default function Page() {
return <p>
Hey
</p>
}

View File

@@ -1,7 +1,163 @@
import React from "react"; import React, {useEffect, useState} from "react";
import {Collapse, Divider, Table, Typography} from "antd";
import {getTldList, Tld} from "../../utils/api";
const {Text, Paragraph} = Typography
type TldType = 'iTLD' | 'sTLD' | 'gTLD' | 'ccTLD'
type FiltersType = { type: TldType, contractTerminated?: boolean, specification13?: boolean }
const toEmoji = (tld: string) => String.fromCodePoint(
...getCountryCode(tld)
.toUpperCase()
.split('')
.map((char) => 127397 + char.charCodeAt(0)
)
)
const getCountryCode = (tld: string): string => {
const exceptions = {uk: 'gb', su: 'ru', tp: 'tl'}
if (tld in exceptions) return exceptions[tld as keyof typeof exceptions]
return tld
}
const regionNames = new Intl.DisplayNames(['en'], {type: 'region'})
function TldTable(filters: FiltersType) {
const [dataTable, setDataTable] = useState<Tld[]>([])
const [total, setTotal] = useState(0)
const fetchData = (params: FiltersType & { page: number }) => {
getTldList(params).then((data) => {
setTotal(data['hydra:totalItems'])
setDataTable(data['hydra:member'].map((tld: Tld) => {
switch (filters.type) {
case 'ccTLD':
return {
TLD: tld.tld,
Flag: toEmoji(tld.tld),
Country: regionNames.of(getCountryCode(tld.tld)) ?? '-'
}
case 'gTLD':
return {
TLD: tld.tld,
Operator: tld.registryOperator
}
default:
return {
TLD: tld.tld
}
}
}))
})
}
useEffect(() => {
fetchData({...filters, page: 1})
}, [])
let columns = [
{
title: "TLD",
dataIndex: "TLD",
width: 20
}
]
if (filters.type === 'ccTLD') columns = [...columns, {
title: "Flag",
dataIndex: "Flag",
width: 20
}, {
title: "Country",
dataIndex: "Country",
width: 200
}]
if (filters.type === 'gTLD') columns = [...columns, {
title: "Registry Operator",
dataIndex: "Operator",
width: 50
}]
return <Table
columns={columns}
dataSource={dataTable}
pagination={{
total,
pageSize: 30,
hideOnSinglePage: true,
onChange: (page, pageSize) => {
fetchData({...filters, page})
}
}}
scroll={{y: 240}}
/>
}
export default function TldPage() { export default function TldPage() {
return <p> return <>
Tld Page <Paragraph>
</p> This page presents all active TLDs in the root zone database.
</Paragraph>
<Paragraph>
IANA provides the list of currently active
TLDs, regardless of their type, and ICANN provides the list of gTLDs.
In most cases, the two-letter ccTLD assigned to a country is made in accordance with the ISO 3166-1
standard.
This data is updated every month. Three HTTP requests are needed for the complete update of TLDs in Domain
Watchdog (two requests to IANA and one to ICANN).
At the same time, the list of root RDAP servers is updated.
</Paragraph>
<Divider/>
<Collapse
size="large"
items={[
{
key: 'sTLD',
label: 'Sponsored Top-Level-Domains',
children: <>
<Text>Top-level domains sponsored by specific organizations that set rules for
registration and use, often related to particular interest groups or
industries.</Text>
<Divider/>
<TldTable type='sTLD'/>
</>
},
{
key: 'gTLD',
label: 'Generic Top-Level-Domains',
children: <>
<Text>Generic top-level domains open to everyone, not restricted by specific
criteria, representing various themes or industries.</Text>
<Divider/>
<TldTable type='gTLD' contractTerminated={false} specification13={false}/>
</>
},
{
key: 'ngTLD',
label: 'Brand Generic Top-Level-Domains',
children: <>
<Text>Generic top-level domains associated with specific brands, allowing companies
to use their own brand names as domains.</Text>
<Divider/>
<TldTable type='gTLD' contractTerminated={false} specification13={true}/>
</>
},
{
key: 'ccTLD',
label: 'Country-Code Top-Level-Domains',
children: <>
<Text>Top-level domains based on country codes, identifying websites according to
their country of origin.</Text>
<Divider/><TldTable type='ccTLD'/>
</>
}
]}
/>
</>
} }

View File

@@ -0,0 +1,7 @@
import React from "react";
export default function UserPage() {
return <p>
My Account Page
</p>
}

View File

@@ -55,7 +55,7 @@ export async function request<T = any, R = AxiosResponse<T>, D = any>(config: Ax
withCredentials: true, withCredentials: true,
headers: { headers: {
...config.headers, ...config.headers,
Accept: 'application/json' Accept: 'application/ld+json'
} }
} }
return await axios.request<T, R, D>(axiosConfig) return await axios.request<T, R, D>(axiosConfig)

View File

@@ -7,7 +7,7 @@ interface Tld {
specification13: boolean specification13: boolean
} }
export async function getTldList(params: object): Promise<Tld[]> { export async function getTldList(params: object): Promise<any> {
return (await request<Tld[]>({ return (await request<Tld[]>({
url: 'tld', url: 'tld',
params, params,

View File

@@ -21,7 +21,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
operations: [ operations: [
new GetCollection( new GetCollection(
uriTemplate: '/tld', uriTemplate: '/tld',
paginationItemsPerPage: 200,
normalizationContext: ['groups' => ['tld:list']] normalizationContext: ['groups' => ['tld:list']]
), ),
new Get( new Get(