feat: update front layout

This commit is contained in:
Maël Gangloff 2024-07-26 18:31:47 +02:00
parent a15c1b2c2f
commit 467d0efa1c
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
13 changed files with 272 additions and 177 deletions

View File

@ -7,12 +7,14 @@ import {
FileSearchOutlined, FileSearchOutlined,
InfoCircleOutlined, InfoCircleOutlined,
LineChartOutlined, LineChartOutlined,
LoginOutlined,
LogoutOutlined,
QuestionCircleOutlined, QuestionCircleOutlined,
SearchOutlined, SearchOutlined,
TeamOutlined, TeamOutlined,
UserOutlined UserOutlined
} from "@ant-design/icons"; } from "@ant-design/icons";
import {Navigate, Route, Routes, useNavigate} from "react-router-dom"; import {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";
@ -23,171 +25,204 @@ 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/UserPage";
import LoginPage from "./pages/LoginPage"; import React, {useCallback, useEffect, useMemo, useState} from "react";
import React, {useEffect, useState} from "react";
import {getUser} from "./utils/api"; import {getUser} from "./utils/api";
import FAQPage from "./pages/FAQPage";
import LoginPage, {AuthenticatedContext} from "./pages/LoginPage";
import ConnectorsPage from "./pages/tracking/ConnectorsPage";
import NotFoundPage from "./pages/NotFoundPage";
export default function App() { export default function App() {
const { const {
token: {colorBgContainer, borderRadiusLG}, token: {colorBgContainer, borderRadiusLG},
} = theme.useToken() } = theme.useToken()
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
const navigate = useNavigate()
const navigate = useNavigate()
const [isAuthenticated, setIsAuthenticated] = useState(false)
const location = useLocation()
const authenticated = useCallback((authenticated: boolean) => {
setIsAuthenticated(authenticated)
}, []);
const contextValue = useMemo(() => ({
authenticated,
setIsAuthenticated
}), [authenticated, setIsAuthenticated]);
useEffect(() => { useEffect(() => {
getUser().then(() => setIsAuthenticated(true)).catch(() => setIsAuthenticated(false)) getUser().then(() => {
setIsAuthenticated(true)
if (location.pathname === '/login') navigate('/search/domain')
}).catch(() => {
setIsAuthenticated(false)
if (location.pathname !== '/login') navigate('/login')
})
}, []); }, []);
return <Layout hasSider style={{minHeight: '100vh'}}> const menuItems = [
<Layout.Sider> {
<Menu key: '1',
defaultSelectedKeys={['1-1']} label: 'Search',
defaultOpenKeys={['1', '2', '3']} children: [
mode="inline" {
theme="dark" key: '1-1',
items={[ icon: <SearchOutlined/>,
{ label: 'Domain',
key: '1', title: 'Domain Finder',
label: 'Search', disabled: !isAuthenticated,
children: [ onClick: () => navigate('/search/domain')
{ },
key: '1-1', {
icon: <SearchOutlined/>, key: '1-2',
label: 'Domain', icon: <TeamOutlined/>,
title: 'Domain Finder', label: 'Entity',
disabled: !isAuthenticated, title: 'Entity Finder',
onClick: () => navigate('/search/domain') disabled: !isAuthenticated,
}, onClick: () => navigate('/search/entity')
{ },
key: '1-2', {
icon: <TeamOutlined/>, key: '1-3',
label: 'Entity', icon: <CloudServerOutlined/>,
title: 'Entity Finder', label: 'Nameserver',
disabled: !isAuthenticated, title: 'Nameserver Finder',
onClick: () => navigate('/search/entity') disabled: !isAuthenticated,
}, onClick: () => navigate('/search/nameserver')
{ }
key: '1-3', ]
icon: <CloudServerOutlined/>, },
label: 'Nameserver', {
title: 'Nameserver Finder', key: '2',
disabled: !isAuthenticated, label: 'Information',
onClick: () => navigate('/search/nameserver') children: [
} {
] key: '2-1',
}, icon: <BankOutlined/>,
{ label: 'TLD',
key: '2', disabled: !isAuthenticated,
label: 'Information', onClick: () => navigate('/info/tld')
children: [ },
{ {
key: '2-1', key: '2-2',
icon: <BankOutlined/>, icon: <LineChartOutlined/>,
label: 'TLD', label: 'Statistics',
disabled: !isAuthenticated, disabled: !isAuthenticated,
onClick: () => navigate('/info/tld') onClick: () => navigate('/info/stats')
}, }
{ ]
key: '2-2', },
icon: <LineChartOutlined/>, {
label: 'Statistics', key: '3',
disabled: !isAuthenticated, label: 'Tracking',
onClick: () => navigate('/info/stats') children: [
} {
] key: '3-1',
}, icon: <Badge count={0} size="small"><FileSearchOutlined
{ shape="square"/></Badge>,
key: '3', label: 'My Watchlists',
label: 'Tracking', disabled: !isAuthenticated,
children: [ onClick: () => navigate('/tracking/watchlist')
{ },
key: '3-1', {
icon: <Badge count={0} size="small"><FileSearchOutlined key: '3-2',
shape="square"/></Badge>, icon: <ApiOutlined/>,
label: 'My Watchlists', label: 'My connectors',
disabled: !isAuthenticated, disabled: !isAuthenticated,
onClick: () => navigate('/tracking/watchlist') onClick: () => navigate('/tracking/connectors')
}, }
{ ]
key: '3-2', },
icon: <ApiOutlined/>, {
label: 'My connectors', key: '4',
disabled: !isAuthenticated, icon: <UserOutlined/>,
onClick: () => navigate('/tracking/connectors') label: 'My Account',
} disabled: !isAuthenticated,
] onClick: () => navigate('/user')
}, },
{ {
key: '4', key: '5',
icon: <UserOutlined/>, icon: <QuestionCircleOutlined/>,
label: 'My Account', label: 'FAQ',
disabled: !isAuthenticated, onClick: () => navigate('/faq')
onClick: () => navigate('/user') },
}, {
{ key: '6',
key: '5', icon: <InfoCircleOutlined/>,
icon: <QuestionCircleOutlined/>, label: 'TOS',
label: 'FAQ', onClick: () => navigate('/tos')
onClick: () => navigate('/faq') },
}, {
{ key: '7',
key: '6', icon: <FileProtectOutlined/>,
icon: <InfoCircleOutlined/>, label: 'Privacy Policy',
label: 'TOS', onClick: () => navigate('/privacy')
onClick: () => navigate('/tos') }
}, ]
{
key: '7',
icon: <FileProtectOutlined/>,
label: 'Privacy Policy',
onClick: () => navigate('/privacy')
}
]}
/>
<div className="demo-logo-vertical"></div>
</Layout.Sider>
<Layout>
<Layout.Header style={{padding: 0, background: colorBgContainer}}/>
<Layout.Content style={{margin: '24px 16px 0'}}>
<div style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}>
<Routes>
<Route path="/tos" element={<TextPage markdown={tos}/>}/>
<Route path="/privacy" element={<TextPage markdown={privacy}/>}/>
{isAuthenticated ? return <AuthenticatedContext.Provider value={contextValue}>
<> <Layout hasSider style={{minHeight: '100vh'}}>
<Route path="/" element={<Navigate to="/search/domain"/>}/> <Layout.Sider>
<Menu
defaultSelectedKeys={['1-1']}
defaultOpenKeys={['1', '2', '3']}
mode="inline"
theme="dark"
items={[...menuItems, isAuthenticated ? {
key: '8',
icon: <LogoutOutlined/>,
label: 'Log out',
danger: true,
onClick: () => window.location.replace("/logout")
} : {
key: '8',
icon: <LoginOutlined/>,
label: 'Log in',
onClick: () => navigate('/login')
}]}
/>
<div className="demo-logo-vertical"></div>
</Layout.Sider>
<Layout>
<Layout.Header style={{padding: 0, background: colorBgContainer}}/>
<Layout.Content style={{margin: '24px 16px 0'}}>
<div style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}>
<Route path="/search/domain" element={<DomainSearchPage/>}/> <Routes>
<Route path="/search/entity" element={<EntitySearchPage/>}/> <Route path="/" element={<Navigate to="/search/domain"/>}/>
<Route path="/search/nameserver" element={<NameserverSearchPage/>}/>
<Route path="/info/tld" element={<TldPage/>}/> <Route path="/search/domain" element={<DomainSearchPage/>}/>
<Route path="/info/stats" element={<StatisticsPage/>}/> <Route path="/search/entity" element={<EntitySearchPage/>}/>
<Route path="/search/nameserver" element={<NameserverSearchPage/>}/>
<Route path="/tracking/watchlist" element={<WatchlistsPage/>}/> <Route path="/info/tld" element={<TldPage/>}/>
<Route path="/info/stats" element={<StatisticsPage/>}/>
<Route path="/user" element={<UserPage/>}/> <Route path="/tracking/watchlist" element={<WatchlistsPage/>}/>
</> <Route path="/tracking/connectors" element={<ConnectorsPage/>}/>
:
<Route path="*" element={<LoginPage/>}/>
}
</Routes> <Route path="/user" element={<UserPage/>}/>
</div>
</Layout.Content> <Route path="/faq" element={<FAQPage/>}/>
<Layout.Footer style={{textAlign: 'center'}}> <Route path="/tos" element={<TextPage markdown={tos}/>}/>
Domain Watchdog ©{new Date().getFullYear()} Created by Maël Gangloff <Route path="/privacy" element={<TextPage markdown={privacy}/>}/>
</Layout.Footer>
<Route path="/login" element={<LoginPage/>}/>
<Route path="*" element={<NotFoundPage/>}/>
</Routes>
</div>
</Layout.Content>
<Layout.Footer style={{textAlign: 'center'}}>
Domain Watchdog ©{new Date().getFullYear()} Created by Maël Gangloff
</Layout.Footer>
</Layout>
</Layout> </Layout>
</Layout> </AuthenticatedContext.Provider>
} }

View File

@ -14,7 +14,6 @@ function Index() {
<HashRouter> <HashRouter>
<App/> <App/>
</HashRouter> </HashRouter>
); );
} }

8
assets/pages/FAQPage.tsx Normal file
View File

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

View File

@ -1,7 +1,74 @@
import React from "react"; import React, {createContext, useContext, useEffect, useState} from "react";
import {Alert, Button, Card, Flex, Form, Input} from "antd";
import {login} from "../utils/api";
import {useNavigate} from "react-router-dom";
type FieldType = {
username: string;
password: string;
};
export const AuthenticatedContext = createContext<any>(null)
export default function Page() { export default function Page() {
return <p>
Login Page const [error, setError] = useState()
</p> const navigate = useNavigate()
const {isAuthenticated, setIsAuthenticated} = useContext(AuthenticatedContext)
const onFinish = (data: FieldType) => {
login(data.username, data.password).then(() => {
setIsAuthenticated(true)
navigate('/search/domain')
}).catch((e) => {
setIsAuthenticated(false)
setError(e.response.data.message)
})
}
return <Flex gap="middle" align="center" justify="center" vertical><Card
title="Log in"
style={{width: 500}}
>
{error &&
<Alert
type='error'
message='Error'
banner={true}
role='role'
description={error}
style={{marginBottom: '1em'}}
/>}
<Form
name="basic"
labelCol={{span: 8}}
wrapperCol={{span: 16}}
style={{maxWidth: 600}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label="Username"
name="username"
rules={[{required: true, message: 'Required'}]}
>
<Input/>
</Form.Item>
<Form.Item<FieldType>
label="Password"
name="password"
rules={[{required: true, message: 'Required'}]}
>
<Input.Password/>
</Form.Item>
<Form.Item wrapperCol={{offset: 8, span: 16}}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</Card>
</Flex>
} }

View File

@ -1,4 +1,4 @@
import {Button, Result} from "antd"; import {Result} from "antd";
import React from "react"; import React from "react";
@ -7,6 +7,5 @@ export default function NotFoundPage() {
status="404" status="404"
title="404" title="404"
subTitle="Sorry, the page you visited does not exist." subTitle="Sorry, the page you visited does not exist."
extra={<Button type="primary">Back Home</Button>}
/> />
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function StatisticsPage() { export default function StatisticsPage() {
return <p> return <p>
Tld Statistics Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function TldPage() { export default function TldPage() {
return <p> return <p>
Tld Tld Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function DomainSearchPage() { export default function DomainSearchPage() {
return <p> return <p>
Domain Search Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function EntitySearchPage() { export default function EntitySearchPage() {
return <p> return <p>
Entity Search Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function NameserverSearchPage() { export default function NameserverSearchPage() {
return <p> return <p>
NS Finder NS Search Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function ConnectorsPage() { export default function ConnectorsPage() {
return <p> return <p>
Connectors Page
</p> </p>
} }

View File

@ -2,6 +2,6 @@ import React from "react";
export default function WatchlistsPage() { export default function WatchlistsPage() {
return <p> return <p>
Watchlists Page
</p> </p>
} }

View File

@ -8,21 +8,8 @@ interface Tld {
} }
export async function getTldList(params: object): Promise<Tld[]> { export async function getTldList(params: object): Promise<Tld[]> {
let page = 1 return (await request<Tld[]>({
let response = (await request<Tld[]>({
url: 'tld', url: 'tld',
params: {...params, page}, params,
})).data })).data
const tldList: Tld[] = response;
while (response.length !== 0) {
page++
response = (await request<Tld[]>({
url: 'tld',
params: {...params, page},
})).data
tldList.push(...response)
}
return tldList
} }