Skip to content

Commit 42b440c

Browse files
committed
feat: import subscriptions
1 parent 3878082 commit 42b440c

File tree

12 files changed

+183
-130
lines changed

12 files changed

+183
-130
lines changed

src/app/(protected)/network/GroupSection.tsx

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ const GroupContent: FC<{
4444
group: Group
4545
subscriptions: Subscription[]
4646
nodes: Node[]
47-
refetch: () => Promise<unknown>
48-
}> = ({ group, subscriptions, nodes, refetch }) => {
47+
}> = ({ group, subscriptions, nodes }) => {
4948
const { t } = useTranslation()
5049

5150
// subscriptions
@@ -104,8 +103,6 @@ const GroupContent: FC<{
104103
if (nodesToRemove.length > 0) {
105104
await groupDelNodesMutation.mutateAsync({ id: group.id, nodeIDs: nodesToRemove })
106105
}
107-
108-
await refetch()
109106
}}
110107
renderValue={(items) => (
111108
<div className="flex flex-wrap gap-2">
@@ -119,7 +116,6 @@ const GroupContent: FC<{
119116
classNames={{ base: 'max-w-full', content: 'truncate' }}
120117
onClose={async () => {
121118
await groupDelNodesMutation.mutateAsync({ id: group.id, nodeIDs: [nodeID] })
122-
await refetch()
123119
}}
124120
>
125121
{nodeName} {item.data?.subscription && `(${item.data.subscription})`}
@@ -162,8 +158,6 @@ const GroupContent: FC<{
162158
if (subscriptionsToRemove.length > 0) {
163159
await groupDelSubscriptionsMutation.mutateAsync({ id: group.id, subscriptionIDs: subscriptionsToRemove })
164160
}
165-
166-
await refetch()
167161
}}
168162
items={subscriptions}
169163
renderValue={(items) => (
@@ -176,7 +170,6 @@ const GroupContent: FC<{
176170
}}
177171
onClose={async () => {
178172
await groupDelSubscriptionsMutation.mutateAsync({ id: group.id, subscriptionIDs: [item.data!.id] })
179-
await refetch()
180173
}}
181174
>
182175
{item.data!.tag}
@@ -226,8 +219,7 @@ const GroupAccordion: FC<{
226219
group: Group
227220
subscriptions: Subscription[]
228221
nodes: Node[]
229-
refetch: () => Promise<unknown>
230-
}> = ({ group, refetch, subscriptions, nodes }) => {
222+
}> = ({ group, subscriptions, nodes }) => {
231223
const { t } = useTranslation()
232224

233225
const {
@@ -296,7 +288,6 @@ const GroupAccordion: FC<{
296288
await removeGroupsMutation.mutateAsync({
297289
groupIDs: [group.id]
298290
})
299-
await refetch()
300291

301292
onRemoveGroupClose()
302293
}}
@@ -306,7 +297,7 @@ const GroupAccordion: FC<{
306297
</div>
307298
}
308299
>
309-
<GroupContent group={group} subscriptions={subscriptions} nodes={nodes} refetch={refetch} />
300+
<GroupContent group={group} subscriptions={subscriptions} nodes={nodes} />
310301
</AccordionItem>
311302
</Accordion>
312303
)
@@ -354,7 +345,6 @@ export const GroupSection: FC<{ nodes: Node[]; subscriptions: Subscription[] }>
354345
policy: values.policy,
355346
policyParams: []
356347
})
357-
await groupsQuery.refetch()
358348

359349
onAddClose()
360350
})}
@@ -414,13 +404,7 @@ export const GroupSection: FC<{ nodes: Node[]; subscriptions: Subscription[] }>
414404

415405
{groupsQuery.data &&
416406
groupsQuery.data.groups.map((group) => (
417-
<GroupAccordion
418-
key={group.id}
419-
group={group}
420-
refetch={groupsQuery.refetch}
421-
nodes={nodes}
422-
subscriptions={subscriptions}
423-
/>
407+
<GroupAccordion key={group.id} group={group} nodes={nodes} subscriptions={subscriptions} />
424408
))}
425409
</div>
426410
)

src/app/(protected)/network/NodeSection.tsx

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const CheckNodeQRCodeButton: FC<{ name: string; link: string }> = ({ name, link
5050
)
5151
}
5252

53-
const RemoveNodeButton: FC<{ id: string; name: string; refetch: () => Promise<unknown> }> = ({ id, name, refetch }) => {
53+
const RemoveNodeButton: FC<{ id: string; name: string }> = ({ id, name }) => {
5454
const { t } = useTranslation()
5555

5656
const {
@@ -78,7 +78,6 @@ const RemoveNodeButton: FC<{ id: string; name: string; refetch: () => Promise<un
7878
isSubmitting={removeNodesMutation.isPending}
7979
onConfirm={async () => {
8080
await removeNodesMutation.mutateAsync({ nodeIDs: [id] })
81-
await refetch()
8281

8382
onRemoveClose()
8483
}}
@@ -91,9 +90,8 @@ const RemoveNodeButton: FC<{ id: string; name: string; refetch: () => Promise<un
9190

9291
const NodeTable: FC<{
9392
nodes: Node[]
94-
refetch: () => Promise<unknown>
9593
isLoading?: boolean
96-
}> = ({ nodes, refetch, isLoading }) => {
94+
}> = ({ nodes, isLoading }) => {
9795
const { t } = useTranslation()
9896

9997
const nodesTableColumns = useMemo(
@@ -106,27 +104,24 @@ const NodeTable: FC<{
106104
[t]
107105
)
108106

109-
const renderCell = useCallback(
110-
(item: Node, columnKey: Key) => {
111-
switch (columnKey) {
112-
case 'name':
113-
return item.tag || item.name
114-
115-
case 'action':
116-
return (
117-
<div className="flex items-center gap-2">
118-
<CheckNodeQRCodeButton name={item.tag || item.name} link={item.link} />
119-
120-
<RemoveNodeButton id={item.id} name={item.tag || item.name} refetch={refetch} />
121-
</div>
122-
)
123-
124-
default:
125-
return getKeyValue(item, columnKey)
126-
}
127-
},
128-
[refetch]
129-
)
107+
const renderCell = useCallback((item: Node, columnKey: Key) => {
108+
switch (columnKey) {
109+
case 'name':
110+
return item.tag || item.name
111+
112+
case 'action':
113+
return (
114+
<div className="flex items-center gap-2">
115+
<CheckNodeQRCodeButton name={item.tag || item.name} link={item.link} />
116+
117+
<RemoveNodeButton id={item.id} name={item.tag || item.name} />
118+
</div>
119+
)
120+
121+
default:
122+
return getKeyValue(item, columnKey)
123+
}
124+
}, [])
130125

131126
return (
132127
<Table isCompact aria-label="nodes">
@@ -143,11 +138,7 @@ const NodeTable: FC<{
143138
)
144139
}
145140

146-
export const NodeSection: FC<{ nodes: Node[]; refetch: () => Promise<unknown>; isLoading?: boolean }> = ({
147-
nodes,
148-
refetch,
149-
isLoading
150-
}) => {
141+
export const NodeSection: FC<{ nodes: Node[]; isLoading?: boolean }> = ({ nodes, isLoading }) => {
151142
const { t } = useTranslation()
152143

153144
return (
@@ -159,7 +150,7 @@ export const NodeSection: FC<{ nodes: Node[]; refetch: () => Promise<unknown>; i
159150
</Button>
160151
</div>
161152

162-
<NodeTable nodes={nodes} refetch={refetch} isLoading={isLoading} />
153+
<NodeTable nodes={nodes} isLoading={isLoading} />
163154
</div>
164155
)
165156
}

src/app/(protected)/network/SubscriptionSection.tsx

Lines changed: 112 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { zodResolver } from '@hookform/resolvers/zod'
12
import {
23
Accordion,
34
AccordionItem,
45
getKeyValue,
6+
Input,
57
ModalBody,
68
ModalContent,
79
ModalHeader,
@@ -15,11 +17,18 @@ import {
1517
} from '@nextui-org/react'
1618
import { IconPlus, IconRefresh, IconTrash } from '@tabler/icons-react'
1719
import dayjs from 'dayjs'
18-
import { FC, Fragment, useMemo } from 'react'
20+
import { FC, Fragment, useEffect, useMemo } from 'react'
21+
import { Controller, FormProvider, useFieldArray, useForm } from 'react-hook-form'
1922
import { useTranslation } from 'react-i18next'
20-
import { useRemoveSubscriptionsMutation, useUpdateSubscriptionsMutation } from '~/apis/mutation'
23+
import { z } from 'zod'
24+
import {
25+
useImportSubscriptionsMutation,
26+
useRemoveSubscriptionsMutation,
27+
useUpdateSubscriptionsMutation
28+
} from '~/apis/mutation'
2129
import { Button } from '~/components/Button'
22-
import { Modal, ModalConfirmFormFooter } from '~/components/Modal'
30+
import { Modal, ModalConfirmFormFooter, ModalSubmitFormFooter } from '~/components/Modal'
31+
import { subscriptionFormDefault, subscriptionFormSchema } from '~/schemas/subscription'
2332
import { Node, Subscription } from './typings'
2433

2534
const SubscriptionNodeTable: FC<{
@@ -52,30 +61,124 @@ const SubscriptionNodeTable: FC<{
5261
)
5362
}
5463

55-
export const SubscriptionSection: FC<{ subscriptions: Subscription[]; refetch: () => Promise<unknown> }> = ({
56-
subscriptions,
57-
refetch
58-
}) => {
64+
const ImportSubscriptionInputList: FC<{ name: string }> = ({ name }) => {
65+
const { t } = useTranslation()
66+
const { fields, append, remove } = useFieldArray({ name })
67+
68+
return (
69+
<div className="flex flex-col gap-2">
70+
{fields.map((item, index) => (
71+
<div key={item.id} className="flex items-start gap-2">
72+
<Controller
73+
name={`${name}.${index}.tag`}
74+
render={({ field, fieldState }) => (
75+
<Input
76+
className="w-1/3"
77+
label={t('form.fields.tag')}
78+
placeholder={t('form.fields.tag')}
79+
errorMessage={fieldState.error?.message}
80+
isRequired
81+
{...field}
82+
/>
83+
)}
84+
/>
85+
86+
<Controller
87+
name={`${name}.${index}.link`}
88+
render={({ field, fieldState }) => (
89+
<Input
90+
label={t('form.fields.link')}
91+
placeholder={t('form.fields.link')}
92+
errorMessage={fieldState.error?.message}
93+
isRequired
94+
{...field}
95+
/>
96+
)}
97+
/>
98+
99+
<Button color="danger" isIconOnly onPress={() => remove(index)}>
100+
<IconTrash />
101+
</Button>
102+
</div>
103+
))}
104+
105+
<div className="self-end">
106+
<Button color="primary" onPress={() => append({ tag: '', link: '' })} isIconOnly>
107+
<IconPlus />
108+
</Button>
109+
</div>
110+
</div>
111+
)
112+
}
113+
114+
export const SubscriptionSection: FC<{ subscriptions: Subscription[] }> = ({ subscriptions }) => {
59115
const { t } = useTranslation()
60116

117+
const {
118+
isOpen: isImportSubscriptionOpen,
119+
onOpen: onImportSubscriptionOpen,
120+
onClose: onImportSubscriptionClose,
121+
onOpenChange: onImportSubscriptionOpenChange
122+
} = useDisclosure()
123+
124+
const importSubscriptionForm = useForm<z.infer<typeof subscriptionFormSchema>>({
125+
shouldFocusError: true,
126+
resolver: zodResolver(subscriptionFormSchema),
127+
defaultValues: subscriptionFormDefault
128+
})
129+
61130
const {
62131
isOpen: isRemoveSubscriptionOpen,
63132
onOpen: onRemoveSubscriptionOpen,
64133
onClose: onRemoveSubscriptionClose,
65134
onOpenChange: onRemoveSubscriptionOpenChange
66135
} = useDisclosure()
67136

137+
const importSubscriptionsMutation = useImportSubscriptionsMutation()
68138
const removeSubscriptionsMutation = useRemoveSubscriptionsMutation()
69139
const updateSubscriptionsMutation = useUpdateSubscriptionsMutation()
70140

141+
useEffect(() => {
142+
const timer = setTimeout(() => {
143+
if (!isImportSubscriptionOpen) importSubscriptionForm.reset()
144+
}, 150)
145+
146+
return () => timer && clearTimeout(timer)
147+
}, [importSubscriptionForm, isImportSubscriptionOpen])
148+
71149
return (
72150
<div className="flex flex-col gap-4">
73151
<div className="flex items-center justify-between">
74152
<h3 className="text-xl font-bold">{t('primitives.subscription')}</h3>
75153

76-
<Button color="primary" isIconOnly>
154+
<Button color="primary" isIconOnly onPress={onImportSubscriptionOpen}>
77155
<IconPlus />
78156
</Button>
157+
158+
<Modal isOpen={isImportSubscriptionOpen} onOpenChange={onImportSubscriptionOpenChange}>
159+
<FormProvider {...importSubscriptionForm}>
160+
<form
161+
onSubmit={importSubscriptionForm.handleSubmit(async (values) => {
162+
await importSubscriptionsMutation.mutateAsync(values.subscriptions)
163+
164+
onImportSubscriptionClose()
165+
})}
166+
>
167+
<ModalContent>
168+
<ModalHeader>{t('primitives.subscription')}</ModalHeader>
169+
170+
<ModalBody>
171+
<ImportSubscriptionInputList name="subscriptions" />
172+
</ModalBody>
173+
174+
<ModalSubmitFormFooter
175+
reset={importSubscriptionForm.reset}
176+
isSubmitting={importSubscriptionsMutation.isPending}
177+
/>
178+
</ModalContent>
179+
</form>
180+
</FormProvider>
181+
</Modal>
79182
</div>
80183

81184
<Accordion selectionMode="multiple" variant="shadow" isCompact>
@@ -91,11 +194,7 @@ export const SubscriptionSection: FC<{ subscriptions: Subscription[]; refetch: (
91194
as="div"
92195
isIconOnly
93196
isLoading={updateSubscriptionsMutation.isPending}
94-
onPress={() =>
95-
updateSubscriptionsMutation.mutate({
96-
subscriptionIDs: [subscription.id]
97-
})
98-
}
197+
onPress={() => updateSubscriptionsMutation.mutate({ subscriptionIDs: [subscription.id] })}
99198
>
100199
<IconRefresh />
101200
</Button>
@@ -117,7 +216,6 @@ export const SubscriptionSection: FC<{ subscriptions: Subscription[]; refetch: (
117216
isSubmitting={removeSubscriptionsMutation.isPending}
118217
onConfirm={async () => {
119218
await removeSubscriptionsMutation.mutateAsync({ subscriptionIDs: [subscription.id] })
120-
await refetch()
121219

122220
onRemoveSubscriptionClose()
123221
}}

src/app/(protected)/network/page.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,9 @@ export default function NetworkPage() {
2121
subscriptions={subscriptionsQuery.data?.subscriptions || []}
2222
/>
2323

24-
<NodeSection
25-
nodes={nodesQuery.data?.nodes.edges || []}
26-
refetch={nodesQuery.refetch}
27-
isLoading={nodesQuery.isLoading}
28-
/>
24+
<NodeSection nodes={nodesQuery.data?.nodes.edges || []} isLoading={nodesQuery.isLoading} />
2925

30-
<SubscriptionSection
31-
subscriptions={subscriptionsQuery.data?.subscriptions || []}
32-
refetch={subscriptionsQuery.refetch}
33-
/>
26+
<SubscriptionSection subscriptions={subscriptionsQuery.data?.subscriptions || []} />
3427
</div>
3528
</ResourcePage>
3629
)

0 commit comments

Comments
 (0)