11// TODO: i18n
2+ import { useAutoAnimate } from "@formkit/auto-animate/react" ;
23import { useEffect } from "react" ;
34import { useFormContext } from "react-hook-form" ;
45
@@ -30,6 +31,7 @@ import {
3031 List ,
3132 ListLinkItem ,
3233 Tooltip ,
34+ ArrowButton ,
3335} from "@calcom/ui" ;
3436import {
3537 BarChart ,
@@ -83,6 +85,20 @@ export default function RoutingForms({
8385 const { hasPaidPlan } = useHasPaidPlan ( ) ;
8486 const routerQuery = useRouterQuery ( ) ;
8587 const hookForm = useFormContext < RoutingFormWithResponseCount > ( ) ;
88+ const utils = trpc . useContext ( ) ;
89+ const [ parent ] = useAutoAnimate < HTMLUListElement > ( ) ;
90+
91+ const mutation = trpc . viewer . routingFormOrder . useMutation ( {
92+ onError : async ( err ) => {
93+ console . error ( err . message ) ;
94+ await utils . viewer . appRoutingForms . forms . cancel ( ) ;
95+ await utils . viewer . appRoutingForms . invalidate ( ) ;
96+ } ,
97+ onSettled : ( ) => {
98+ utils . viewer . appRoutingForms . invalidate ( ) ;
99+ } ,
100+ } ) ;
101+
86102 useEffect ( ( ) => {
87103 hookForm . reset ( { } ) ;
88104 // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -128,6 +144,29 @@ export default function RoutingForms({
128144 } ,
129145 ] ;
130146
147+ async function moveRoutingForm ( index : number , increment : 1 | - 1 ) {
148+ const types = forms ?. map ( ( type ) => {
149+ return type . form ;
150+ } ) ;
151+
152+ if ( types ?. length ) {
153+ const newList = [ ...types ] ;
154+
155+ const type = types [ index ] ;
156+ const tmp = types [ index + increment ] ;
157+ if ( tmp ) {
158+ newList [ index ] = tmp ;
159+ newList [ index + increment ] = type ;
160+ }
161+
162+ await utils . viewer . appRoutingForms . forms . cancel ( ) ;
163+
164+ mutation . mutate ( {
165+ ids : newList ?. map ( ( type ) => type . id ) ,
166+ } ) ;
167+ }
168+ }
169+
131170 return (
132171 < LicenseRequired >
133172 < ShellMain
@@ -177,8 +216,8 @@ export default function RoutingForms({
177216 }
178217 SkeletonLoader = { SkeletonLoaderTeamList } >
179218 < div className = "bg-default mb-16 overflow-hidden" >
180- < List data-testid = "routing-forms-list" >
181- { forms ?. map ( ( { form, readOnly } ) => {
219+ < List data-testid = "routing-forms-list" ref = { parent } >
220+ { forms ?. map ( ( { form, readOnly } , index ) => {
182221 if ( ! form ) {
183222 return null ;
184223 }
@@ -187,116 +226,129 @@ export default function RoutingForms({
187226 form . routes = form . routes || [ ] ;
188227 const fields = form . fields || [ ] ;
189228 const userRoutes = form . routes . filter ( ( route ) => ! isFallbackRoute ( route ) ) ;
229+ const firstItem = forms [ 0 ] . form ;
230+ const lastItem = forms [ forms . length - 1 ] . form ;
231+
190232 return (
191- < ListLinkItem
192- key = { form . id }
193- href = { appUrl + "/form-edit/" + form . id }
194- heading = { form . name }
195- disabled = { readOnly }
196- subHeading = { description }
197- className = "space-x-2 rtl:space-x-reverse"
198- actions = {
199- < >
200- { form . team ?. name && (
201- < div className = "border-r-2 border-neutral-300" >
202- < Badge className = "ltr:mr-2 rtl:ml-2" variant = "gray" >
203- { form . team . name }
204- </ Badge >
205- </ div >
206- ) }
207- < FormAction
208- disabled = { readOnly }
209- className = "self-center"
210- action = "toggle"
211- routingForm = { form }
212- />
213- < ButtonGroup combined >
214- < Tooltip content = { t ( "preview" ) } >
215- < FormAction
216- action = "preview"
217- routingForm = { form }
218- target = "_blank"
219- StartIcon = { ExternalLink }
220- color = "secondary"
221- variant = "icon"
222- />
223- </ Tooltip >
224- < FormAction
225- routingForm = { form }
226- action = "copyLink"
227- color = "secondary"
228- variant = "icon"
229- StartIcon = { LinkIcon }
230- tooltip = { t ( "copy_link_to_form" ) }
231- />
233+ < div
234+ className = "group flex w-full max-w-full items-center justify-between overflow-hidden"
235+ key = { form . id } >
236+ { ! ( firstItem && firstItem . id === form . id ) && (
237+ < ArrowButton onClick = { ( ) => moveRoutingForm ( index , - 1 ) } arrowDirection = "up" />
238+ ) }
239+
240+ { ! ( lastItem && lastItem . id === form . id ) && (
241+ < ArrowButton onClick = { ( ) => moveRoutingForm ( index , 1 ) } arrowDirection = "down" />
242+ ) }
243+ < ListLinkItem
244+ href = { appUrl + "/form-edit/" + form . id }
245+ heading = { form . name }
246+ disabled = { readOnly }
247+ subHeading = { description }
248+ className = "space-x-2 rtl:space-x-reverse"
249+ actions = {
250+ < >
251+ { form . team ?. name && (
252+ < div className = "border-r-2 border-neutral-300" >
253+ < Badge className = "ltr:mr-2 rtl:ml-2" variant = "gray" >
254+ { form . team . name }
255+ </ Badge >
256+ </ div >
257+ ) }
232258 < FormAction
259+ disabled = { readOnly }
260+ className = "self-center"
261+ action = "toggle"
233262 routingForm = { form }
234- action = "embed"
235- color = "secondary"
236- variant = "icon"
237- StartIcon = { Code }
238- tooltip = { t ( "embed" ) }
239263 />
240- < FormActionsDropdown disabled = { readOnly } >
241- < FormAction
242- action = "edit"
243- routingForm = { form }
244- color = "minimal"
245- className = "!flex"
246- StartIcon = { Edit } >
247- { t ( "edit" ) }
248- </ FormAction >
264+ < ButtonGroup combined >
265+ < Tooltip content = { t ( "preview" ) } >
266+ < FormAction
267+ action = "preview"
268+ routingForm = { form }
269+ target = "_blank"
270+ StartIcon = { ExternalLink }
271+ color = "secondary"
272+ variant = "icon"
273+ />
274+ </ Tooltip >
249275 < FormAction
250- action = "download"
251276 routingForm = { form }
252- color = "minimal"
253- StartIcon = { Download } >
254- { t ( "download_responses" ) }
255- </ FormAction >
277+ action = "copyLink"
278+ color = "secondary"
279+ variant = "icon"
280+ StartIcon = { LinkIcon }
281+ tooltip = { t ( "copy_link_to_form" ) }
282+ />
256283 < FormAction
257- action = "duplicate"
258284 routingForm = { form }
259- color = "minimal"
260- className = "w-full"
261- StartIcon = { Copy } >
262- { t ( "duplicate" ) }
263- </ FormAction >
264- { typeformApp ?. isInstalled ? (
285+ action = "embed"
286+ color = "secondary"
287+ variant = "icon"
288+ StartIcon = { Code }
289+ tooltip = { t ( "embed" ) }
290+ />
291+ < FormActionsDropdown disabled = { readOnly } >
265292 < FormAction
266- data-testid = "copy-redirect-url "
293+ action = "edit "
267294 routingForm = { form }
268- action = "copyRedirectUrl"
269295 color = "minimal"
270- type = "button "
271- StartIcon = { LinkIcon } >
272- { t ( "Copy Typeform Redirect Url " ) }
296+ className = "!flex "
297+ StartIcon = { Edit } >
298+ { t ( "edit " ) }
273299 </ FormAction >
274- ) : null }
275- < FormAction
276- action = "_delete"
277- routingForm = { form }
278- color = "destructive"
279- className = "w-full"
280- StartIcon = { Trash } >
281- { t ( "delete" ) }
282- </ FormAction >
283- </ FormActionsDropdown >
284- </ ButtonGroup >
285- </ >
286- } >
287- < div className = "flex flex-wrap gap-1" >
288- < Badge variant = "gray" startIcon = { Menu } >
289- { fields . length } { fields . length === 1 ? "field" : "fields" }
290- </ Badge >
291- < Badge variant = "gray" startIcon = { GitMerge } >
292- { userRoutes . length } { userRoutes . length === 1 ? "route" : "routes" }
293- </ Badge >
294- < Badge variant = "gray" startIcon = { MessageCircle } >
295- { form . _count . responses } { " " }
296- { form . _count . responses === 1 ? "response" : "responses" }
297- </ Badge >
298- </ div >
299- </ ListLinkItem >
300+ < FormAction
301+ action = "download"
302+ routingForm = { form }
303+ color = "minimal"
304+ StartIcon = { Download } >
305+ { t ( "download_responses" ) }
306+ </ FormAction >
307+ < FormAction
308+ action = "duplicate"
309+ routingForm = { form }
310+ color = "minimal"
311+ className = "w-full"
312+ StartIcon = { Copy } >
313+ { t ( "duplicate" ) }
314+ </ FormAction >
315+ { typeformApp ?. isInstalled ? (
316+ < FormAction
317+ data-testid = "copy-redirect-url"
318+ routingForm = { form }
319+ action = "copyRedirectUrl"
320+ color = "minimal"
321+ type = "button"
322+ StartIcon = { LinkIcon } >
323+ { t ( "Copy Typeform Redirect Url" ) }
324+ </ FormAction >
325+ ) : null }
326+ < FormAction
327+ action = "_delete"
328+ routingForm = { form }
329+ color = "destructive"
330+ className = "w-full"
331+ StartIcon = { Trash } >
332+ { t ( "delete" ) }
333+ </ FormAction >
334+ </ FormActionsDropdown >
335+ </ ButtonGroup >
336+ </ >
337+ } >
338+ < div className = "flex flex-wrap gap-1" >
339+ < Badge variant = "gray" startIcon = { Menu } >
340+ { fields . length } { fields . length === 1 ? "field" : "fields" }
341+ </ Badge >
342+ < Badge variant = "gray" startIcon = { GitMerge } >
343+ { userRoutes . length } { userRoutes . length === 1 ? "route" : "routes" }
344+ </ Badge >
345+ < Badge variant = "gray" startIcon = { MessageCircle } >
346+ { form . _count . responses } { " " }
347+ { form . _count . responses === 1 ? "response" : "responses" }
348+ </ Badge >
349+ </ div >
350+ </ ListLinkItem >
351+ </ div >
300352 ) ;
301353 } ) }
302354 </ List >
0 commit comments