11import type { FormEvent } from 'react' ;
2- import { useCallback , useMemo } from 'react' ;
2+ import { useCallback } from 'react' ;
33import { usePostToSquad } from '../../../hooks' ;
44import { useMultipleSourcePost } from '../../../features/squads/hooks/useMultipleSourcePost' ;
55import { useToastNotification } from '../../../hooks/useToastNotification' ;
66import type {
77 CreatePostInMultipleSourcesArgs ,
88 ExternalLinkPreview ,
99} from '../../../graphql/posts' ;
10- import type { ApiErrorResult } from '../../../graphql/common' ;
11- import { DEFAULT_ERROR } from '../../../graphql/common' ;
1210import type { Squad } from '../../../graphql/sources' ;
1311import {
1412 POLL_OPTIONS_MIN ,
@@ -20,25 +18,22 @@ import {
2018import type { TextFormCover } from './TextForm' ;
2119import { isPreviewForComposerUrl } from './utils' ;
2220
23- const isApiErrorResult = ( error : unknown ) : error is ApiErrorResult =>
24- ! ! ( error as ApiErrorResult ) ?. response ?. errors ;
21+ const trimmedOptions = ( state : PollFormState ) : string [ ] =>
22+ state . options . map ( ( option ) => option . trim ( ) ) . filter ( Boolean ) ;
2523
2624const isTextValid = ( state : TextFormState ) : boolean =>
27- state . title . trim ( ) . length > 0 && state . body . trim ( ) . length > 0 ;
28-
29- const isLinkValid = ( preview ?: ExternalLinkPreview ) : boolean =>
30- Boolean ( preview ?. title && ( preview . url || preview . permalink ) ) ;
25+ ! ! state . title . trim ( ) && ! ! state . body . trim ( ) ;
3126
32- const isPollValid = ( state : PollFormState ) : boolean => {
33- if ( ! state . question . trim ( ) ) {
34- return false ;
35- }
36- const filled = state . options . map ( ( option ) => option . trim ( ) ) . filter ( Boolean ) ;
37- return filled . length >= POLL_OPTIONS_MIN ;
38- } ;
27+ const isLinkValid = (
28+ state : LinkFormState ,
29+ preview : ExternalLinkPreview | undefined ,
30+ ) : boolean =>
31+ ! ! preview ?. title &&
32+ ! ! ( preview . url || preview . permalink ) &&
33+ isPreviewForComposerUrl ( preview , state . url ) ;
3934
40- const filledPollOptions = ( state : PollFormState ) : string [ ] =>
41- state . options . map ( ( option ) => option . trim ( ) ) . filter ( Boolean ) ;
35+ const isPollValid = ( state : PollFormState ) : boolean =>
36+ ! ! state . question . trim ( ) && trimmedOptions ( state ) . length >= POLL_OPTIONS_MIN ;
4237
4338interface UseComposerSubmitProps {
4439 kind : ComposerKind ;
@@ -47,8 +42,8 @@ interface UseComposerSubmitProps {
4742 poll : PollFormState ;
4843 cover : TextFormCover | null ;
4944 primary : Squad | undefined ;
50- selected : Squad [ ] ;
5145 selectedIds : string [ ] ;
46+ isMulti : boolean ;
5247 initialPreview ?: ExternalLinkPreview ;
5348 onComplete : ( ) => void ;
5449}
@@ -69,18 +64,12 @@ export const useComposerSubmit = ({
6964 poll,
7065 cover,
7166 primary,
72- selected,
7367 selectedIds,
68+ isMulti,
7469 initialPreview,
7570 onComplete,
7671} : UseComposerSubmitProps ) : UseComposerSubmit => {
7772 const { displayToast } = useToastNotification ( ) ;
78- const handleError = useCallback (
79- ( error : ApiErrorResult ) => {
80- displayToast ( error . response ?. errors ?. [ 0 ] ?. message ?? DEFAULT_ERROR ) ;
81- } ,
82- [ displayToast ] ,
83- ) ;
8473 const {
8574 getLinkPreview,
8675 isLoadingPreview,
@@ -94,176 +83,135 @@ export const useComposerSubmit = ({
9483 onComplete,
9584 displayMutationErrors : true ,
9685 } ) ;
97-
9886 const { onCreate : createMulti , isPending : isMultiPending } =
99- useMultipleSourcePost ( { onSuccess : onComplete , onError : handleError } ) ;
87+ useMultipleSourcePost ( {
88+ onSuccess : onComplete ,
89+ onError : ( error ) =>
90+ displayToast ( error . response ?. errors ?. [ 0 ] ?. message ?? 'Failed to post' ) ,
91+ } ) ;
10092
10193 const fetchPreview = useCallback (
10294 ( url ?: string ) => {
10395 if ( ! url ) {
10496 return ;
10597 }
106- getLinkPreview ( url ) . catch ( ( ) => {
107- // surfaced via usePostToSquad toast
108- } ) ;
98+ // surfaced via usePostToSquad toast
99+ getLinkPreview ( url ) . catch ( ( ) => undefined ) ;
109100 } ,
110101 [ getLinkPreview ] ,
111102 ) ;
112103
113- const isMulti = selected . length > 1 ;
114104 const isInFlight = isPosting || isMultiPending ;
115105
116- const isSubmitDisabled = useMemo ( ( ) => {
106+ const getIsSubmitDisabled = ( ) : boolean => {
117107 if ( isInFlight || ! primary ) {
118108 return true ;
119109 }
120110 if ( kind === 'text' ) {
121111 return ! isTextValid ( text ) ;
122112 }
123113 if ( kind === 'link' ) {
124- return (
125- ! isLinkValid ( preview ) || ! isPreviewForComposerUrl ( preview , link . url )
126- ) ;
114+ return ! isLinkValid ( link , preview ) ;
127115 }
128116 return ! isPollValid ( poll ) ;
129- } , [ isInFlight , kind , link . url , poll , preview , primary , text ] ) ;
117+ } ;
130118
131- const submitMulti = useCallback ( async ( ) => {
132- if ( kind === 'text' ) {
119+ const submitText = async ( ) => {
120+ const payload = {
121+ title : text . title . trim ( ) ,
122+ content : text . body ,
123+ ...( cover ?. file ? { image : cover . file } : { } ) ,
124+ } ;
125+ if ( isMulti ) {
133126 await createMulti ( {
134127 sourceIds : selectedIds ,
135- title : text . title . trim ( ) ,
136- content : text . body ,
137- ...( cover ?. file ? { image : cover . file } : { } ) ,
128+ ...payload ,
138129 } as unknown as CreatePostInMultipleSourcesArgs ) ;
139130 return ;
140131 }
141- if ( kind === 'poll' ) {
132+ await onSubmitFreeformPost ( payload , primary as Squad ) ;
133+ } ;
134+
135+ const submitPoll = async ( ) => {
136+ const options = trimmedOptions ( poll ) ;
137+ const duration = poll . durationDays ;
138+ if ( isMulti ) {
142139 await createMulti ( {
143140 sourceIds : selectedIds ,
144141 title : poll . question . trim ( ) ,
145- options : filledPollOptions ( poll ) . map ( ( value , order ) => ( {
146- text : value ,
147- order,
148- } ) ) ,
149- ...( poll . durationDays != null ? { duration : poll . durationDays } : { } ) ,
142+ options : options . map ( ( value , order ) => ( { text : value , order } ) ) ,
143+ ...( duration != null ? { duration } : { } ) ,
150144 } as unknown as CreatePostInMultipleSourcesArgs ) ;
151145 return ;
152146 }
147+ await onSubmitPollPost (
148+ {
149+ title : poll . question . trim ( ) ,
150+ options,
151+ ...( duration != null ? { duration } : { } ) ,
152+ } ,
153+ primary as Squad ,
154+ ) ;
155+ } ;
156+
157+ const submitLink = async ( event : FormEvent < HTMLFormElement > ) => {
153158 if ( ! isPreviewForComposerUrl ( preview , link . url ) ) {
154159 displayToast ( 'Invalid link' ) ;
155160 return ;
156161 }
157-
158- const url = preview ?. finalUrl ?? preview ?. url ;
159- if ( ! url || ! preview ?. title ) {
162+ const commentary = link . commentary . trim ( ) ;
163+ if ( ! isMulti ) {
164+ await onSubmitPost ( event , primary as Squad , commentary ) ;
160165 return ;
161166 }
162- const commentary = link . commentary . trim ( ) ;
163- if ( preview . id ) {
164- await createMulti ( {
165- sourceIds : selectedIds ,
166- sharedPostId : preview . id ,
167- commentary,
168- } as unknown as CreatePostInMultipleSourcesArgs ) ;
167+ const url = preview ?. finalUrl ?? preview ?. url ;
168+ if ( ! url || ! preview ?. title ) {
169169 return ;
170170 }
171+ const sharedArgs = preview . id
172+ ? { sharedPostId : preview . id }
173+ : { externalLink : url , title : preview . title , imageUrl : preview . image } ;
171174 await createMulti ( {
172175 sourceIds : selectedIds ,
173- externalLink : url ,
174- title : preview . title ,
175- imageUrl : preview . image ,
176176 commentary,
177+ ...sharedArgs ,
177178 } as unknown as CreatePostInMultipleSourcesArgs ) ;
178- } , [
179- createMulti ,
180- cover ?. file ,
181- displayToast ,
182- kind ,
183- link . commentary ,
184- link . url ,
185- poll ,
186- preview ,
187- selectedIds ,
188- text ,
189- ] ) ;
179+ } ;
190180
191- const submitSingle = useCallback (
181+ const handleSubmit = useCallback (
192182 async ( event : FormEvent < HTMLFormElement > ) => {
193- if ( ! primary ) {
183+ event . preventDefault ( ) ;
184+ if ( getIsSubmitDisabled ( ) ) {
194185 return ;
195186 }
196187 if ( kind === 'text' ) {
197- await onSubmitFreeformPost (
198- {
199- title : text . title . trim ( ) ,
200- content : text . body ,
201- ...( cover ?. file ? { image : cover . file } : { } ) ,
202- } ,
203- primary ,
204- ) ;
188+ await submitText ( ) ;
205189 return ;
206190 }
207- if ( kind === 'link' ) {
208- if ( ! isPreviewForComposerUrl ( preview , link . url ) ) {
209- displayToast ( 'Invalid link' ) ;
210- return ;
211- }
212-
213- await onSubmitPost ( event , primary , link . commentary . trim ( ) ) ;
191+ if ( kind === 'poll' ) {
192+ await submitPoll ( ) ;
214193 return ;
215194 }
216- await onSubmitPollPost (
217- {
218- title : poll . question . trim ( ) ,
219- options : filledPollOptions ( poll ) ,
220- ...( poll . durationDays != null ? { duration : poll . durationDays } : { } ) ,
221- } ,
222- primary ,
223- ) ;
195+ await submitLink ( event ) ;
224196 } ,
197+ // eslint-disable-next-line react-hooks/exhaustive-deps
225198 [
226- cover ?. file ,
227- displayToast ,
228199 kind ,
229- link . commentary ,
230- link . url ,
231- onSubmitFreeformPost ,
232- onSubmitPollPost ,
233- onSubmitPost ,
234- poll ,
235- preview ,
200+ isMulti ,
236201 primary ,
202+ preview ,
237203 text ,
204+ link ,
205+ poll ,
206+ cover ,
207+ selectedIds ,
208+ isInFlight ,
238209 ] ,
239210 ) ;
240211
241- const handleSubmit = useCallback (
242- async ( event : FormEvent < HTMLFormElement > ) => {
243- event . preventDefault ( ) ;
244- if ( isSubmitDisabled ) {
245- return ;
246- }
247- try {
248- if ( isMulti ) {
249- await submitMulti ( ) ;
250- return ;
251- }
252- await submitSingle ( event ) ;
253- } catch ( error ) {
254- if ( isApiErrorResult ( error ) ) {
255- return ;
256- }
257-
258- throw error ;
259- }
260- } ,
261- [ isMulti , isSubmitDisabled , submitMulti , submitSingle ] ,
262- ) ;
263-
264212 return {
265213 handleSubmit,
266- isSubmitDisabled,
214+ isSubmitDisabled : getIsSubmitDisabled ( ) ,
267215 isInFlight,
268216 preview,
269217 isLoadingPreview,
0 commit comments