Skip to content

Commit fb29d62

Browse files
authored
Merge pull request #193 from dapplets/xen-tg-app
feat: Add News Monitor page (issue#94, issue#97, issue#192)
2 parents b03dcb5 + 3f53ef2 commit fb29d62

File tree

12 files changed

+468
-15
lines changed

12 files changed

+468
-15
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const ArrowForwardIcon = () => (
2+
<svg
3+
xmlns="http://www.w3.org/2000/svg"
4+
height="16px"
5+
viewBox="0 0 24 24"
6+
width="16px"
7+
fill="currentColor"
8+
>
9+
<path d="M0 0h24v24H0V0z" fill="none" />
10+
<path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z" />
11+
</svg>
12+
)
13+
14+
export default ArrowForwardIcon
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const FilePlusIcon = () => (
2+
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
3+
<path
4+
d="M10.5 2H4.5C4.10218 2 3.72064 2.15804 3.43934 2.43934C3.15804 2.72064 3 3.10218 3 3.5V15.5C3 15.8978 3.15804 16.2794 3.43934 16.5607C3.72064 16.842 4.10218 17 4.5 17H13.5C13.8978 17 14.2794 16.842 14.5607 16.5607C14.842 16.2794 15 15.8978 15 15.5V6.5L10.5 2Z"
5+
stroke="currentColor"
6+
strokeWidth="1.5"
7+
strokeLinecap="round"
8+
strokeLinejoin="round"
9+
/>
10+
<path
11+
d="M10.5 2V6.5H15"
12+
stroke="currentColor"
13+
strokeWidth="1.5"
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
/>
17+
<path
18+
d="M9 14V9.5"
19+
stroke="currentColor"
20+
strokeWidth="1.5"
21+
strokeLinecap="round"
22+
strokeLinejoin="round"
23+
/>
24+
<path
25+
d="M6.75 11.75H11.25"
26+
stroke="currentColor"
27+
strokeWidth="1.5"
28+
strokeLinecap="round"
29+
strokeLinejoin="round"
30+
/>
31+
</svg>
32+
)
33+
34+
export default FilePlusIcon
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const PlusIcon = () => (
2+
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
3+
<path
4+
d="M9 3.75V14.25"
5+
stroke="currentColor"
6+
strokeWidth="2"
7+
strokeLinecap="round"
8+
strokeLinejoin="round"
9+
/>
10+
<path
11+
d="M3.75 9H14.25"
12+
stroke="currentColor"
13+
strokeWidth="2"
14+
strokeLinecap="round"
15+
strokeLinejoin="round"
16+
/>
17+
</svg>
18+
)
19+
20+
export default PlusIcon
Lines changed: 4 additions & 0 deletions
Loading

apps/xen-tg-app/src/components/Agent.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { FC } from 'react'
1+
import { FC, useMemo } from 'react'
22
// import UnlinkOutlineIcon from '../assets/unlink-outline'
3+
import ArrowForwardIcon from '@/assets/arrow-forward'
4+
import { Switch } from '@/components/ui/switch'
5+
import { API_URL } from '@/env'
36
import { useMutation, useQueryClient } from '@tanstack/react-query'
7+
import { useNavigate } from 'react-router'
48
import { TAgent } from '../types'
5-
import { API_URL } from '@/env'
6-
import { Switch } from '@/components/ui/switch'
79

810
const mutationFn = async ({
911
methodName,
@@ -40,6 +42,7 @@ type TAgentProps = {
4042
}
4143

4244
const Agent: FC<TAgentProps> = ({ capabilitiy }) => {
45+
const navigate = useNavigate()
4346
const queryClient = useQueryClient()
4447

4548
const handleToggleCapability = useMutation({
@@ -49,6 +52,10 @@ const Agent: FC<TAgentProps> = ({ capabilitiy }) => {
4952
},
5053
})
5154

55+
const action = useMemo(() => {
56+
if (capabilitiy.name === 'news-monitor') return () => navigate('/news-monitor')
57+
}, [capabilitiy, navigate])
58+
5259
const handleChangeStatus = () =>
5360
handleToggleCapability.mutate({
5461
methodName: capabilitiy.isEnabled ? 'disableCapability' : 'enableCapability',
@@ -66,14 +73,15 @@ const Agent: FC<TAgentProps> = ({ capabilitiy }) => {
6673
// })
6774
return (
6875
<div className="flex w-full items-center justify-between gap-3.5 rounded-[10px] bg-(--color-light-white-bg) p-2.5">
69-
<div className="flex flex-1 flex-col gap-0.5 overflow-hidden">
70-
<div className="flex py-0.25 text-[14px]/[100%] font-semibold wrap-anywhere">
71-
{capabilitiy.name}
76+
<button className="flex flex-1 flex-col gap-0.5 overflow-hidden" onClick={action}>
77+
<div className="flex items-center gap-2 py-0.25 text-left text-[14px]/[100%] font-semibold wrap-anywhere">
78+
{capabilitiy.title ?? capabilitiy.name}
79+
{action ? <ArrowForwardIcon /> : null}
7280
</div>
7381
<div className="flex py-0.25 text-[12px]/[100%] font-normal text-(--color-gray-text)">
7482
{capabilitiy.domain}
7583
</div>
76-
</div>
84+
</button>
7785

7886
<Switch onCheckedChange={handleChangeStatus} checked={capabilitiy.isEnabled} />
7987
{/* <button
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import PlusIcon from '@/assets/plus'
2+
import SyncIcon from '@/assets/sync'
3+
import { API_URL } from '@/env'
4+
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
5+
import { formatDistance } from 'date-fns'
6+
import { useEffect, useState } from 'react'
7+
import { TSubscription } from '../types'
8+
import Header from './Header'
9+
import Layout from './Layout'
10+
import { NewSubscription, Subscription } from './NewsSource'
11+
import Spinner from './Spinner'
12+
13+
const mutationFn = async ({
14+
methodName,
15+
params,
16+
}: {
17+
methodName: string
18+
params?: { [key: string]: string | number }
19+
}) => {
20+
if (!window.Telegram.WebApp.initData) {
21+
throw new Error('Telegram is not available')
22+
}
23+
const response = await fetch(API_URL, {
24+
method: 'POST',
25+
headers: {
26+
Authorization: `Bearer ${window.Telegram.WebApp.initData}`,
27+
'Content-Type': 'application/json',
28+
},
29+
body: JSON.stringify({
30+
jsonrpc: '2.0',
31+
method: methodName,
32+
params: params ?? {},
33+
id: 1,
34+
}),
35+
})
36+
if (!response.ok) {
37+
throw new Error('Network response was not ok')
38+
}
39+
const data = await response.json()
40+
return data.result
41+
}
42+
43+
const queryFn = (name: string, params?: { [key: string]: string | number }) => async () => {
44+
if (!window.Telegram.WebApp.initData) {
45+
throw new Error('Telegram is not available')
46+
}
47+
const response = await fetch(API_URL, {
48+
method: 'POST',
49+
headers: {
50+
Authorization: `Bearer ${window.Telegram.WebApp.initData}`,
51+
'Content-Type': 'application/json',
52+
},
53+
body: JSON.stringify({
54+
jsonrpc: '2.0',
55+
method: name,
56+
params: params ?? {},
57+
id: 1,
58+
}),
59+
})
60+
if (!response.ok) {
61+
throw new Error('Network response was not ok')
62+
}
63+
const data = await response.json()
64+
return data.result
65+
}
66+
67+
const NewsMonitor = () => {
68+
const queryClient = useQueryClient()
69+
const [showNewSubscriptionForm, setShowNewSubscriptionForm] = useState(false)
70+
71+
const { data: subscriptions, isPending } = useQuery<{ items: TSubscription[]; total: number }>({
72+
queryKey: ['subscriptions'],
73+
queryFn: queryFn('getSubscriptions', {
74+
offset: 0,
75+
limit: 10,
76+
}),
77+
})
78+
79+
const { data: nextScanOfSubscriptions, isPending: isPendingNextScanOfSubscriptions } = useQuery<{
80+
nextScanAt: string
81+
}>({
82+
queryKey: ['nextScanOfSubscriptions'],
83+
queryFn: queryFn('getNextScanOfSubscriptions'),
84+
})
85+
86+
const handleUpdateSubscription = useMutation({
87+
mutationFn,
88+
onSuccess: () => {
89+
queryClient.invalidateQueries({ queryKey: ['subscriptions'] })
90+
queryClient.invalidateQueries({ queryKey: ['nextScanOfSubscriptions'] })
91+
},
92+
})
93+
94+
const onUpdate = () => handleUpdateSubscription.mutate({ methodName: 'scanSubscriptions' })
95+
96+
useEffect(onUpdate, [])
97+
98+
return (
99+
<Layout>
100+
<Header />
101+
<div className="z-1 flex w-full flex-col items-center justify-between gap-2.5 rounded-xl border border-[#f8f9ff66] p-2.5 backdrop-blur-3xl backdrop-opacity-80">
102+
<div className="my-1.5 flex w-full items-center justify-between">
103+
<div className="flex flex-col items-start justify-start">
104+
<h1 className="text-center text-2xl font-bold">News Monitor</h1>
105+
<div className="flex items-center justify-center gap-1 text-[12px]/[150%] text-[#7A818B]">
106+
Next scan:{' '}
107+
<button
108+
className="flex items-center justify-center font-semibold text-(--my-primary)"
109+
onClick={onUpdate}
110+
disabled={isPendingNextScanOfSubscriptions || handleUpdateSubscription.isPending}
111+
>
112+
{nextScanOfSubscriptions?.nextScanAt
113+
? formatDistance(new Date(nextScanOfSubscriptions.nextScanAt), new Date(), {
114+
addSuffix: true,
115+
})
116+
: '-'}
117+
<div
118+
className={`${isPendingNextScanOfSubscriptions || handleUpdateSubscription.isPending ? 'animate-spin-back' : ''} flex cursor-pointer items-center justify-between p-1.5 transition`}
119+
>
120+
<SyncIcon />
121+
</div>
122+
</button>
123+
</div>
124+
</div>
125+
<button
126+
className="mr-3.5 flex cursor-pointer items-center justify-center p-1.5 text-[#7A818B] transition hover:not-disabled:text-(--color-main-text)"
127+
onClick={() => setShowNewSubscriptionForm(true)}
128+
disabled={showNewSubscriptionForm}
129+
>
130+
{isPending ? <Spinner /> : <PlusIcon />}
131+
</button>
132+
</div>
133+
{showNewSubscriptionForm ? (
134+
<NewSubscription onClose={() => setShowNewSubscriptionForm(false)} />
135+
) : null}
136+
{subscriptions?.items.map((newsSource) => (
137+
<Subscription key={newsSource.id} subscription={newsSource} />
138+
))}
139+
</div>
140+
</Layout>
141+
)
142+
}
143+
144+
export default NewsMonitor

0 commit comments

Comments
 (0)