diff --git a/assets/components/tracking/watchlist/WatchlistSelectionModal.tsx b/assets/components/tracking/watchlist/WatchlistSelectionModal.tsx
new file mode 100644
index 0000000..f8bd5cf
--- /dev/null
+++ b/assets/components/tracking/watchlist/WatchlistSelectionModal.tsx
@@ -0,0 +1,78 @@
+import React, {useEffect, useState} from "react"
+import {Flex, Modal, ModalProps, Select, Tag, Typography} from "antd"
+import {getWatchlists, Watchlist} from "../../../utils/api"
+import {t} from 'ttag'
+import {DomainToTag} from "../../../utils/functions/DomainToTag"
+
+function WatchlistOption({watchlist}: {watchlist: Watchlist}) {
+ return
+ {watchlist.name}
+
+ {watchlist.domains.map(d => )}
+
+
+}
+
+interface WatchlistSelectionModalProps {
+ onFinish: (watchlist: Watchlist) => Promise|void
+ description?: string
+ open?: boolean
+ modalProps?: Partial
+}
+
+export default function WatchlistSelectionModal(props: WatchlistSelectionModalProps) {
+ const [watchlists, setWatchlists] = useState()
+ const [selectedWatchlist, setSelectedWatchlist] = useState()
+ const [validationLoading, setValidationLoading] = useState(false)
+
+ useEffect(() => {
+ getWatchlists().then(list => setWatchlists(list["hydra:member"]))
+ }, [])
+
+ const onFinish = () => {
+ const promise = props.onFinish(selectedWatchlist as Watchlist)
+
+ if (promise) {
+ setValidationLoading(true)
+ promise.finally(() => {
+ setSelectedWatchlist(undefined)
+ setValidationLoading(false)
+ })
+ } else {
+ setSelectedWatchlist(undefined)
+ }
+ }
+
+ return
+
+
+ {
+ props.description
+ || t`Select one of your available watchlists`
+ }
+
+
+
+}
diff --git a/assets/pages/search/DomainSearchPage.tsx b/assets/pages/search/DomainSearchPage.tsx
index deedd04..8aa06b0 100644
--- a/assets/pages/search/DomainSearchPage.tsx
+++ b/assets/pages/search/DomainSearchPage.tsx
@@ -1,8 +1,10 @@
import React, {useEffect, useState} from 'react'
-import type { FormProps} from 'antd'
+import type {FormProps} from 'antd'
+import {Tooltip} from 'antd'
+import {FloatButton} from 'antd'
import {Empty, Flex, message, Skeleton} from 'antd'
-import type {Domain} from '../../utils/api'
-import { getDomain} from '../../utils/api'
+import {addDomainToWatchlist, Domain, Watchlist} from '../../utils/api'
+import {getDomain} from '../../utils/api'
import type {AxiosError} from 'axios'
import {t} from 'ttag'
import type { FieldType} from '../../components/search/DomainSearchBar'
@@ -10,17 +12,18 @@ import {DomainSearchBar} from '../../components/search/DomainSearchBar'
import {DomainResult} from '../../components/search/DomainResult'
import {showErrorAPI} from '../../utils/functions/showErrorAPI'
import {useNavigate, useParams} from 'react-router-dom'
+import {CaretUpOutlined, PlusOutlined} from '@ant-design/icons'
+import WatchlistSelectionModal from "../../components/tracking/watchlist/WatchlistSelectionModal";
export default function DomainSearchPage() {
const {query} = useParams()
const [domain, setDomain] = useState()
- const [loading, setLoading] = useState(false)
+ const [loading, setLoading] = useState(false)
+ const [addToWatchlistModal, setAddToWatchlistModal] = useState(false)
const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate()
-
-
const onFinish: FormProps['onFinish'] = (values) => {
navigate('/search/domain/' + values.ldhName)
@@ -41,7 +44,15 @@ export default function DomainSearchPage() {
onFinish({ldhName: query, isRefreshForced: false})
}, [])
- return (
+ const addToWatchlist = async (watchlist: Watchlist) => {
+ await addDomainToWatchlist(watchlist, domain!.ldhName).then(() => {
+ setAddToWatchlistModal(false)
+ }).catch((e: AxiosError) => {
+ showErrorAPI(e, messageApi)
+ })
+ }
+
+ return <>
{contextHolder}
@@ -57,5 +68,29 @@ export default function DomainSearchPage() {
}
- )
+ {domain
+ && }
+ >
+
+ } onClick={() => setAddToWatchlistModal(true)} />
+
+
+ }
+ setAddToWatchlistModal(false),
+ onClose: () => setAddToWatchlistModal(false),
+ }}
+ />
+ >
}
diff --git a/assets/utils/api/watchlist.ts b/assets/utils/api/watchlist.ts
index 2dbf8f9..d706a38 100644
--- a/assets/utils/api/watchlist.ts
+++ b/assets/utils/api/watchlist.ts
@@ -44,6 +44,13 @@ export async function patchWatchlist(token: string, watchlist: Partial '/api/domains/' + d.ldhName)
+ domains.push('/api/domains/' + ldhName)
+
+ return patchWatchlist(watchlist.token, {domains})
+}
+
export async function deleteWatchlist(token: string): Promise {
await request({
method: 'DELETE',
diff --git a/assets/utils/functions/DomainToTag.tsx b/assets/utils/functions/DomainToTag.tsx
index d757892..5efad10 100644
--- a/assets/utils/functions/DomainToTag.tsx
+++ b/assets/utils/functions/DomainToTag.tsx
@@ -6,32 +6,38 @@ import React from 'react'
import type {Event} from "../api"
import {t} from "ttag"
-export function DomainToTag({domain}: { domain: { ldhName: string, deleted: boolean, status: string[], events?: Event[] } }) {
- return (
-
-
- e.action === 'last changed' &&
- !e.deleted &&
- ((new Date().getTime() - new Date(e.date).getTime()) < 7*24*60*60*1e3)
- ) !== undefined} color='blue' title={t`The domain name was updated less than a week ago.`}>
-
- : domain.status.includes('redemption period')
- ?
- : domain.status.includes('pending delete') ? : null
- }
- >{punycode.toUnicode(domain.ldhName)}
-
-
-
- )
+export function DomainToTag({domain, link}: { domain: { ldhName: string, deleted: boolean, status: string[], events?: Event[] }, link?: boolean }) {
+ const tag =
+ e.action === 'last changed' &&
+ !e.deleted &&
+ ((new Date().getTime() - new Date(e.date).getTime()) < 7*24*60*60*1e3)
+ ) !== undefined} color='blue' title={t`The domain name was updated less than a week ago.`}>
+
+ : domain.status.includes('redemption period')
+ ?
+ : domain.status.includes('pending delete') ? : null
+ }
+ >{punycode.toUnicode(domain.ldhName)}
+
+
+
+ if (link ?? true) {
+ return (
+
+ {tag}
+
+ )
+ } else {
+ return tag
+ }
}