5
5
AccordionTrigger ,
6
6
} from "@follow/components/ui/accordion/index.js"
7
7
import { Button } from "@follow/components/ui/button/index.js"
8
- import { Card , CardContent , CardHeader } from "@follow/components/ui/card/index.jsx"
9
8
import {
10
9
Form ,
11
10
FormControl ,
@@ -14,27 +13,34 @@ import {
14
13
FormMessage ,
15
14
} from "@follow/components/ui/form/index.jsx"
16
15
import { Input } from "@follow/components/ui/input/index.js"
17
- import { cn } from "@follow/utils/utils "
16
+ import type { BizRespose } from "@follow/models "
18
17
import { zodResolver } from "@hookform/resolvers/zod"
19
18
import { useMutation } from "@tanstack/react-query"
20
- import { Fragment } from "react/jsx-runtime "
19
+ import { Fragment } from "react"
21
20
import { useForm } from "react-hook-form"
22
21
import { Trans , useTranslation } from "react-i18next"
23
22
import { z } from "zod"
24
23
25
24
import { DropZone } from "~/components/ui/drop-zone"
26
25
import { Media } from "~/components/ui/media"
26
+ import { useModalStack } from "~/components/ui/modal/stacked/hooks"
27
27
import { apiFetch } from "~/lib/api-fetch"
28
28
import { toastFetchError } from "~/lib/error-parser"
29
- import { Queries } from "~/queries"
30
29
31
- import { FollowSummary } from "../feed/feed-summary"
30
+ import { OpmlSelectionModal } from "./OpmlSelectionModal"
31
+ import type { ParsedOpmlData } from "./types"
32
32
33
- type FeedResponseList = {
34
- id : string
35
- url : string
36
- title : string | null
37
- } [ ]
33
+ const parseOpmlFile = async ( file : File ) : Promise < ParsedOpmlData > => {
34
+ const formData = new FormData ( )
35
+ formData . append ( "file" , file )
36
+
37
+ const data = await apiFetch < BizRespose < ParsedOpmlData > > ( "/subscriptions/parse-opml" , {
38
+ method : "POST" ,
39
+ body : formData ,
40
+ } )
41
+
42
+ return data . data
43
+ }
38
44
39
45
const formSchema = z . object ( {
40
46
file : z
@@ -47,64 +53,32 @@ const formSchema = z.object({
47
53
} ) ,
48
54
} )
49
55
50
- const NumberDisplay = ( { value } ) => < span className = "font-bold text-zinc-800" > { value ?? 0 } </ span >
51
-
52
- const list : {
53
- key : string
54
- title : I18nKeys
55
- className : string
56
- } [ ] = [
57
- {
58
- key : "parsedErrorItems" ,
59
- title : "discover.import.parsedErrorItems" ,
60
- className : "text-red-500" ,
61
- } ,
62
- {
63
- key : "successfulItems" ,
64
- title : "discover.import.successfulItems" ,
65
- className : "text-green-500" ,
66
- } ,
67
- {
68
- key : "conflictItems" ,
69
- title : "discover.import.conflictItems" ,
70
- className : "text-yellow-500" ,
71
- } ,
72
- ]
73
-
74
56
export function DiscoverImport ( ) {
75
57
const form = useForm < z . infer < typeof formSchema > > ( {
76
58
resolver : zodResolver ( formSchema ) ,
77
59
} )
78
60
79
- const mutation = useMutation ( {
80
- mutationFn : async ( file : File ) => {
81
- const formData = new FormData ( )
82
- formData . append ( "file" , file )
83
- // FIXME: if post data is form data, hono hc not support this.
61
+ const { present } = useModalStack ( )
84
62
85
- const { data } = await apiFetch < {
86
- data : {
87
- successfulItems : FeedResponseList
88
- conflictItems : FeedResponseList
89
- parsedErrorItems : FeedResponseList
90
- }
91
- } > ( "/subscriptions/import" , {
92
- method : "POST" ,
93
- body : formData ,
94
- } )
95
-
96
- return data
97
- } ,
98
- onSuccess : ( ) => {
99
- Queries . subscription . all ( ) . invalidateRoot ( )
100
- } ,
63
+ const parseOpmlMutation = useMutation ( {
64
+ mutationFn : parseOpmlFile ,
101
65
async onError ( err ) {
102
66
toastFetchError ( err )
103
67
} ,
104
68
} )
105
69
106
70
function onSubmit ( values : z . infer < typeof formSchema > ) {
107
- mutation . mutate ( values . file )
71
+ parseOpmlMutation . mutate ( values . file , {
72
+ onSuccess : ( parsedData ) => {
73
+ present ( {
74
+ title : t ( "discover.import.preview_opml_content" ) ,
75
+ content : ( ) => < OpmlSelectionModal file = { values . file } parsedData = { parsedData } /> ,
76
+ clickOutsideToDismiss : false ,
77
+ modalClassName : "max-w-2xl w-full h-[80vh]" ,
78
+ modalContentClassName : "flex flex-col h-full" ,
79
+ } )
80
+ } ,
81
+ } )
108
82
}
109
83
110
84
const { t } = useTranslation ( )
@@ -180,7 +154,7 @@ export function DiscoverImport() {
180
154
</ AccordionItem >
181
155
< AccordionItem value = "other" className = "border-b-0" >
182
156
< AccordionTrigger className = "justify-normal gap-2 hover:no-underline" >
183
- < i className = "i-mgc-rss-cute-fi - ml-[0.14rem] size-6 text-orange-500" />
157
+ < i className = "i-mgc-rss-cute-fi ml-[- 0.14rem] size-6 text-orange-500" />
184
158
{ t ( "discover.import.opml_step1_other" ) }
185
159
</ AccordionTrigger >
186
160
< AccordionContent className = "flex flex-col gap-1" >
@@ -230,47 +204,13 @@ export function DiscoverImport() {
230
204
< Button
231
205
type = "submit"
232
206
disabled = { ! form . formState . dirtyFields . file }
233
- isLoading = { mutation . isPending }
207
+ isLoading = { parseOpmlMutation . isPending }
234
208
>
235
209
{ t ( "words.import" ) }
236
210
</ Button >
237
211
</ div >
238
212
</ form >
239
213
</ Form >
240
- { mutation . isSuccess && (
241
- < div className = "mt-8 w-full max-w-lg" >
242
- < Card >
243
- < CardHeader className = "block text-zinc-500" >
244
- < Trans
245
- ns = "app"
246
- i18nKey = "discover.import.result"
247
- components = { {
248
- SuccessfulNum : < NumberDisplay value = { mutation . data ?. successfulItems . length } /> ,
249
- ConflictNum : < NumberDisplay value = { mutation . data ?. conflictItems . length } /> ,
250
- ErrorNum : < NumberDisplay value = { mutation . data ?. parsedErrorItems . length } /> ,
251
- } }
252
- />
253
- </ CardHeader >
254
- < CardContent className = "space-y-6" >
255
- { list . map ( ( item ) => (
256
- < div key = { item . key } >
257
- < div className = { cn ( "mb-4 text-lg font-medium" , item . className ) } >
258
- { t ( item . title ) }
259
- </ div >
260
- < div className = "space-y-4" >
261
- { ! mutation . data ?. [ item . key ] . length && (
262
- < div className = "text-zinc-500" > { t ( "discover.import.noItems" ) } </ div >
263
- ) }
264
- { mutation . data ?. [ item . key ] . map ( ( feed ) => (
265
- < FollowSummary className = "max-w-[462px]" key = { feed . id } feed = { feed } />
266
- ) ) }
267
- </ div >
268
- </ div >
269
- ) ) }
270
- </ CardContent >
271
- </ Card >
272
- </ div >
273
- ) }
274
214
</ div >
275
215
)
276
216
}
0 commit comments