feat: add skeleton on Domain Finder page

This commit is contained in:
Maël Gangloff 2024-07-27 17:02:21 +02:00
parent 68392dd8ec
commit eaab8ce1b2
No known key found for this signature in database
GPG Key ID: 11FDC81C24A7F629
2 changed files with 135 additions and 107 deletions

View File

@ -11,6 +11,7 @@ import {
Input, Input,
List, List,
message, message,
Skeleton,
Space, Space,
Tag, Tag,
Timeline, Timeline,
@ -21,6 +22,7 @@ import {
ClockCircleOutlined, ClockCircleOutlined,
DeleteOutlined, DeleteOutlined,
IdcardOutlined, IdcardOutlined,
ReloadOutlined,
SearchOutlined, SearchOutlined,
ShareAltOutlined, ShareAltOutlined,
SignatureOutlined, SignatureOutlined,
@ -40,16 +42,17 @@ type FieldType = {
export default function DomainSearchPage() { export default function DomainSearchPage() {
const [domain, setDomain] = useState<Domain | null>(null) const [domain, setDomain] = useState<Domain | null>()
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const onFinish: FormProps<FieldType>['onFinish'] = (values) => { const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
setDomain(null)
getDomain(values.ldhName).then(d => { getDomain(values.ldhName).then(d => {
setDomain(d) setDomain(d)
messageApi.success('Found !') messageApi.success('Found !')
}).catch((e: AxiosError) => { }).catch((e: AxiosError) => {
const data = e?.response?.data as { detail: string } const data = e?.response?.data as { detail: string }
setDomain(null) setDomain(undefined)
messageApi.error(data.detail ?? 'An error occurred') messageApi.error(data.detail ?? 'An error occurred')
}) })
} }
@ -77,113 +80,120 @@ export default function DomainSearchPage() {
min: 2 min: 2
}]} }]}
> >
<Input size="large" prefix={<SearchOutlined/>} placeholder="example.com"/> <Input size="large" prefix={<SearchOutlined/>} placeholder="example.com" autoFocus={true}
autoComplete='off'/>
</Form.Item> </Form.Item>
</Form> </Form>
{ <Skeleton loading={domain === null} active>
domain && {
(!domain.deleted ? <Space direction="vertical" size="middle" style={{width: '100%'}}> domain &&
<Badge.Ribbon text={`.${domain.tld.tld.toUpperCase()} (${domain.tld.type})`} (!domain.deleted ? <Space direction="vertical" size="middle" style={{width: '100%'}}>
color={ <Badge.Ribbon text={`.${domain.tld.tld.toUpperCase()} (${domain.tld.type})`}
domain.tld.type === 'ccTLD' ? 'purple' : color={
(domain.tld.type === 'gTLD' && domain.tld.specification13) ? "volcano" : domain.tld.type === 'ccTLD' ? 'purple' :
domain.tld.type === 'gTLD' ? "green" (domain.tld.type === 'gTLD' && domain.tld.specification13) ? "volcano" :
: "cyan" domain.tld.type === 'gTLD' ? "green"
}> : "cyan"
<Card title={`${domain.ldhName}${domain.handle ? ' (' + domain.handle + ')' : ''}`} }>
size="small"> <Card title={`${domain.ldhName}${domain.handle ? ' (' + domain.handle + ')' : ''}`}
{domain.status.length > 0 && size="small">
<> {domain.status.length > 0 &&
<Divider orientation="left">EPP Status Codes</Divider> <>
<Flex gap="4px 0" wrap> <Divider orientation="left">EPP Status Codes</Divider>
{ <Flex gap="4px 0" wrap>
domain.status.map(s => {
<Tag color={s === 'active' ? 'green' : 'blue'}>{s}</Tag> domain.status.map(s =>
) <Tag color={s === 'active' ? 'green' : 'blue'}>{s}</Tag>
} )
</Flex>
</>
}
<Divider orientation="left">Timeline</Divider>
<Timeline
mode="right"
items={domain.events
.sort((e1, e2) => new Date(e2.date).getTime() - new Date(e1.date).getTime())
.map(({action, date}) => {
let color, dot
if (action === 'registration') {
color = 'green'
dot = <SignatureOutlined style={{fontSize: '16px'}}/>
} else if (action === 'expiration') {
color = 'red'
dot = <ClockCircleOutlined style={{fontSize: '16px'}}/>
} else if (action === 'transfer') {
color = 'orange'
dot = <ShareAltOutlined style={{fontSize: '16px'}}/>
} else if (action === 'last changed') {
color = 'blue'
dot = <SyncOutlined style={{fontSize: '16px'}}/>
} else if (action === 'deletion') {
color = 'red'
dot = <DeleteOutlined style={{fontSize: '16px'}}/>
} }
return { </Flex>
label: new Date(date).toUTCString(), </>
children: action,
color,
dot,
pending: new Date(date).getTime() > new Date().getTime()
}
}
)
} }
/> <Divider orientation="left">Timeline</Divider>
{ <Timeline
domain.entities.length > 0 && mode="right"
<> items={domain.events
<Divider orientation="left">Entities</Divider> .sort((e1, e2) => new Date(e2.date).getTime() - new Date(e1.date).getTime())
<List .map(({action, date}) => {
className="demo-loadmore-list"
itemLayout="horizontal"
dataSource={domain.entities}
renderItem={(e) => {
const jCard = vCard.fromJSON(e.entity.jCard)
let name = ''
if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) name = jCard.data.fn.valueOf()
return <List.Item> let color, dot
<List.Item.Meta if (action === 'registration') {
avatar={<Avatar style={{backgroundColor: '#87d068'}} color = 'green'
icon={e.roles.includes('registrant') ? dot = <SignatureOutlined style={{fontSize: '16px'}}/>
<SignatureOutlined/> : e.roles.includes('registrar') ? } else if (action === 'expiration') {
<BankOutlined/> : color = 'red'
e.roles.includes('technical') ? dot = <ClockCircleOutlined style={{fontSize: '16px'}}/>
<ToolOutlined/> : } else if (action === 'transfer') {
e.roles.includes('administrative') ? color = 'orange'
<IdcardOutlined/> : dot = <ShareAltOutlined style={{fontSize: '16px'}}/>
<UserOutlined/>}/>} } else if (action === 'last changed') {
title={e.entity.handle} color = 'blue'
description={name} dot = <SyncOutlined style={{fontSize: '16px'}}/>
/> } else if (action === 'deletion') {
<div>{e.roles.join(', ')}</div> color = 'red'
</List.Item> dot = <DeleteOutlined style={{fontSize: '16px'}}/>
}} } else if (action === 'reregistration') {
/> color = 'green'
</> dot = <ReloadOutlined style={{fontSize: '16px'}}/>
} }
</Card>
</Badge.Ribbon> return {
</Space> label: new Date(date).toUTCString(),
: <Empty children: action,
description={ color,
<Typography.Text> dot,
Although the domain exists in my database, it has been deleted from the WHOIS by its pending: new Date(date).getTime() > new Date().getTime()
registrar. }
</Typography.Text> }
}/>) )
} }
/>
{
domain.entities.length > 0 &&
<>
<Divider orientation="left">Entities</Divider>
<List
className="demo-loadmore-list"
itemLayout="horizontal"
dataSource={domain.entities}
renderItem={(e) => {
const jCard = vCard.fromJSON(e.entity.jCard)
let name = ''
if (jCard.data.fn !== undefined && !Array.isArray(jCard.data.fn)) name = jCard.data.fn.valueOf()
return <List.Item>
<List.Item.Meta
avatar={<Avatar style={{backgroundColor: '#87d068'}}
icon={e.roles.includes('registrant') ?
<SignatureOutlined/> : e.roles.includes('registrar') ?
<BankOutlined/> :
e.roles.includes('technical') ?
<ToolOutlined/> :
e.roles.includes('administrative') ?
<IdcardOutlined/> :
<UserOutlined/>}/>}
title={e.entity.handle}
description={name}
/>
<div>{e.roles.join(', ')}</div>
</List.Item>
}}
/>
</>
}
</Card>
</Badge.Ribbon>
</Space>
: <Empty
description={
<Typography.Text>
Although the domain exists in my database, it has been deleted from the WHOIS by its
registrar.
</Typography.Text>
}/>)
}
</Skeleton>
</Card> </Card>
</Flex> </Flex>
} }

View File

@ -1,7 +1,25 @@
import React from "react"; import React, {useEffect, useState} from "react";
import {Card, Flex, Skeleton, Typography} from "antd";
import {getUser, User} from "../../utils/api";
export default function UserPage() { export default function UserPage() {
return <p>
My Account Page const [user, setUser] = useState<User | null>(null)
</p>
useEffect(() => {
getUser().then(setUser)
}, [])
return <Skeleton loading={user === null} active>
<Flex gap="middle" align="center" justify="center" vertical>
<Card title="My Account">
<Typography.Paragraph>
Username : {user?.email}
</Typography.Paragraph>
<Typography.Paragraph>
Roles : {user?.roles.join(',')}
</Typography.Paragraph>
</Card>
</Flex>
</Skeleton>
} }