mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: update front layout
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
TeamOutlined,
|
||||
UserOutlined
|
||||
} 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 tos from "./content/tos.md";
|
||||
import privacy from "./content/privacy.md";
|
||||
@@ -24,7 +24,7 @@ import NameserverSearchPage from "./pages/search/NameserverSearchPage";
|
||||
import TldPage from "./pages/info/TldPage";
|
||||
import StatisticsPage from "./pages/info/StatisticsPage";
|
||||
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 {getUser} from "./utils/api";
|
||||
import FAQPage from "./pages/FAQPage";
|
||||
@@ -38,9 +38,12 @@ export default function App() {
|
||||
} = theme.useToken()
|
||||
|
||||
const navigate = useNavigate()
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const location = useLocation()
|
||||
|
||||
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
|
||||
|
||||
const authenticated = useCallback((authenticated: boolean) => {
|
||||
setIsAuthenticated(authenticated)
|
||||
}, []);
|
||||
@@ -65,6 +68,7 @@ export default function App() {
|
||||
{
|
||||
key: '1',
|
||||
label: 'Search',
|
||||
icon: <SearchOutlined/>,
|
||||
children: [
|
||||
{
|
||||
key: '1-1',
|
||||
@@ -95,11 +99,13 @@ export default function App() {
|
||||
{
|
||||
key: '2',
|
||||
label: 'Information',
|
||||
icon: <InfoCircleOutlined/>,
|
||||
children: [
|
||||
{
|
||||
key: '2-1',
|
||||
icon: <BankOutlined/>,
|
||||
label: 'TLD',
|
||||
title: 'TLD list',
|
||||
disabled: !isAuthenticated,
|
||||
onClick: () => navigate('/info/tld')
|
||||
},
|
||||
@@ -115,11 +121,11 @@ export default function App() {
|
||||
{
|
||||
key: '3',
|
||||
label: 'Tracking',
|
||||
icon: <FileSearchOutlined/>,
|
||||
children: [
|
||||
{
|
||||
key: '3-1',
|
||||
icon: <Badge count={0} size="small"><FileSearchOutlined
|
||||
shape="square"/></Badge>,
|
||||
icon: <Badge count={0} size="small"><FileSearchOutlined/></Badge>,
|
||||
label: 'My Watchlists',
|
||||
disabled: !isAuthenticated,
|
||||
onClick: () => navigate('/tracking/watchlist')
|
||||
@@ -135,10 +141,29 @@ export default function App() {
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: 'My Watchdog',
|
||||
icon: <UserOutlined/>,
|
||||
label: 'My Account',
|
||||
disabled: !isAuthenticated,
|
||||
onClick: () => navigate('/user')
|
||||
children: [
|
||||
{
|
||||
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',
|
||||
@@ -146,27 +171,16 @@ export default function App() {
|
||||
label: '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}>
|
||||
<Layout hasSider style={{minHeight: '100vh'}}>
|
||||
<Layout.Sider>
|
||||
<Layout.Sider collapsible>
|
||||
<Menu
|
||||
defaultSelectedKeys={['1-1']}
|
||||
defaultOpenKeys={['1', '2', '3']}
|
||||
defaultOpenKeys={['1', '2', '3', '4']}
|
||||
mode="inline"
|
||||
theme="dark"
|
||||
items={[...menuItems, isAuthenticated ? {
|
||||
@@ -220,7 +234,8 @@ export default function App() {
|
||||
</div>
|
||||
</Layout.Content>
|
||||
<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>
|
||||
</Layout>
|
||||
|
||||
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import {HashRouter} from "react-router-dom";
|
||||
|
||||
import './index.css'
|
||||
import 'antd/dist/reset.css';
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
|
||||
@@ -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 {login} from "../utils/api";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
@@ -28,7 +28,6 @@ export default function Page() {
|
||||
|
||||
return <Flex gap="middle" align="center" justify="center" vertical><Card
|
||||
title="Log in"
|
||||
style={{width: 500}}
|
||||
>
|
||||
{error &&
|
||||
<Alert
|
||||
@@ -68,6 +67,11 @@ export default function Page() {
|
||||
Submit
|
||||
</Button>
|
||||
</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>
|
||||
</Card>
|
||||
</Flex>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Page() {
|
||||
return <p>
|
||||
Hey
|
||||
</p>
|
||||
}
|
||||
@@ -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() {
|
||||
return <p>
|
||||
Tld Page
|
||||
</p>
|
||||
return <>
|
||||
<Paragraph>
|
||||
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'/>
|
||||
</>
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
7
assets/pages/watchdog/UserPage.tsx
Normal file
7
assets/pages/watchdog/UserPage.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export default function UserPage() {
|
||||
return <p>
|
||||
My Account Page
|
||||
</p>
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export async function request<T = any, R = AxiosResponse<T>, D = any>(config: Ax
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
...config.headers,
|
||||
Accept: 'application/json'
|
||||
Accept: 'application/ld+json'
|
||||
}
|
||||
}
|
||||
return await axios.request<T, R, D>(axiosConfig)
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Tld {
|
||||
specification13: boolean
|
||||
}
|
||||
|
||||
export async function getTldList(params: object): Promise<Tld[]> {
|
||||
export async function getTldList(params: object): Promise<any> {
|
||||
return (await request<Tld[]>({
|
||||
url: 'tld',
|
||||
params,
|
||||
|
||||
@@ -21,7 +21,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/tld',
|
||||
paginationItemsPerPage: 200,
|
||||
normalizationContext: ['groups' => ['tld:list']]
|
||||
),
|
||||
new Get(
|
||||
|
||||
Reference in New Issue
Block a user