sanity-plugin-workflow 1.0.0-beta.4 → 1.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-workflow",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "A demonstration of a custom content publishing workflow using Sanity.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -1,25 +1,20 @@
1
- // import {useState} from 'react'
2
- import {ArrowRightIcon, ArrowLeftIcon} from '@sanity/icons'
1
+ import {ArrowLeftIcon, ArrowRightIcon} from '@sanity/icons'
3
2
  import {useToast} from '@sanity/ui'
4
3
  import {useCurrentUser, useValidationStatus} from 'sanity'
5
4
  import {DocumentActionProps, useClient} from 'sanity'
6
5
 
7
- import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
8
6
  import {API_VERSION} from '../constants'
9
- import {State} from '../types'
10
7
  import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
8
+ import {useWorkflowMetadata} from '../hooks/useWorkflowMetadata'
9
+ import {State} from '../types'
11
10
 
11
+ // eslint-disable-next-line complexity
12
12
  export function UpdateWorkflow(
13
13
  props: DocumentActionProps,
14
14
  allStates: State[],
15
15
  actionState: State
16
16
  ) {
17
17
  const {id, type} = props
18
- const {validation, isValidating} = useValidationStatus(id, type)
19
- const hasValidationErrors =
20
- !isValidating &&
21
- validation?.length > 0 &&
22
- validation.find((v) => v.level === 'error')
23
18
 
24
19
  const user = useCurrentUser()
25
20
  const client = useClient({apiVersion: API_VERSION})
@@ -30,6 +25,13 @@ export function UpdateWorkflow(
30
25
  const {state: currentState} = data
31
26
  const {assignees = []} = data?.metadata ?? {}
32
27
 
28
+ const {validation, isValidating} = useValidationStatus(id, type)
29
+ const hasValidationErrors =
30
+ currentState?.requireValidation &&
31
+ !isValidating &&
32
+ validation?.length > 0 &&
33
+ validation.find((v) => v.level === 'error')
34
+
33
35
  if (error) {
34
36
  console.error(error)
35
37
  }
@@ -80,8 +82,6 @@ export function UpdateWorkflow(
80
82
  const DirectionIcon = direction === 'promote' ? ArrowRightIcon : ArrowLeftIcon
81
83
  const directionLabel = direction === 'promote' ? 'Promote' : 'Demote'
82
84
 
83
- let title = `${directionLabel} State to "${actionState.title}"`
84
-
85
85
  const userRoleCanUpdateState =
86
86
  user?.roles?.length && actionState?.roles?.length
87
87
  ? // If the Action state is limited to specific roles
@@ -93,10 +93,6 @@ export function UpdateWorkflow(
93
93
  : // No roles specified on the next state, so anyone can update
94
94
  actionState?.roles?.length !== 0
95
95
 
96
- if (!userRoleCanUpdateState) {
97
- title = `Your User role cannot ${directionLabel} State to "${actionState.title}"`
98
- }
99
-
100
96
  const actionStateIsAValidTransition =
101
97
  currentState?.id && currentState.transitions.length
102
98
  ? // If the Current State limits transitions to specific States
@@ -105,10 +101,6 @@ export function UpdateWorkflow(
105
101
  : // Otherwise this isn't a problem
106
102
  true
107
103
 
108
- if (!actionStateIsAValidTransition) {
109
- title = `You cannot ${directionLabel} State to "${actionState.title}" from "${currentState?.title}"`
110
- }
111
-
112
104
  const userAssignmentCanUpdateState = actionState.requireAssignment
113
105
  ? // If the Action State requires assigned users
114
106
  // Check the current user ID is in the assignees array
@@ -116,11 +108,17 @@ export function UpdateWorkflow(
116
108
  : // Otherwise this isn't a problem
117
109
  true
118
110
 
119
- if (!userAssignmentCanUpdateState) {
120
- title = `You must be assigned to the document to ${directionLabel} State to "${actionState.title}"`
121
- }
111
+ let title = `${directionLabel} State to "${actionState.title}"`
122
112
 
123
- if (hasValidationErrors) {
113
+ if (!userRoleCanUpdateState) {
114
+ title = `Your User role cannot ${directionLabel} State to "${actionState.title}"`
115
+ } else if (!actionStateIsAValidTransition) {
116
+ title = `You cannot ${directionLabel} State to "${actionState.title}" from "${currentState?.title}"`
117
+ } else if (!userAssignmentCanUpdateState) {
118
+ title = `You must be assigned to the document to ${directionLabel} State to "${actionState.title}"`
119
+ } else if (currentState?.requireValidation && isValidating) {
120
+ title = `Document is validating, cannot ${directionLabel} State to "${actionState.title}"`
121
+ } else if (hasValidationErrors) {
124
122
  title = `Document has validation errors, cannot ${directionLabel} State to "${actionState.title}"`
125
123
  }
126
124
 
@@ -129,7 +127,7 @@ export function UpdateWorkflow(
129
127
  disabled:
130
128
  loading ||
131
129
  error ||
132
- isValidating ||
130
+ (currentState?.requireValidation && isValidating) ||
133
131
  hasValidationErrors ||
134
132
  !currentState ||
135
133
  !userRoleCanUpdateState ||
@@ -0,0 +1,21 @@
1
+ import {useEffect} from 'react'
2
+ import {useValidationStatus, ValidationStatus} from 'sanity'
3
+
4
+ type ValidateProps = {
5
+ documentId: string
6
+ type: string
7
+ onChange: (validation: ValidationStatus) => void
8
+ }
9
+
10
+ // Document validation is siloed into its own component
11
+ // Because it's not performant to run on a lot of documents
12
+ export default function Validate(props: ValidateProps) {
13
+ const {documentId, type, onChange} = props
14
+ const {isValidating, validation = []} = useValidationStatus(documentId, type)
15
+
16
+ useEffect(() => {
17
+ onChange({isValidating, validation})
18
+ }, [onChange, isValidating, validation])
19
+
20
+ return null
21
+ }
@@ -1,8 +1,12 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  import {DragHandleIcon} from '@sanity/icons'
3
3
  import {Box, Card, CardTone, Flex, Stack, useTheme} from '@sanity/ui'
4
- import {useEffect, useMemo} from 'react'
5
- import {SchemaType, useSchema, useValidationStatus} from 'sanity'
4
+ import {useCallback, useEffect, useMemo, useState} from 'react'
5
+ import {
6
+ SchemaType,
7
+ useSchema,
8
+ ValidationStatus as ValidationStatusType,
9
+ } from 'sanity'
6
10
  import {Preview} from 'sanity'
7
11
 
8
12
  import {SanityDocumentWithMetadata, State, User} from '../../types'
@@ -11,6 +15,7 @@ import CompleteButton from './CompleteButton'
11
15
  import {DraftStatus} from './core/DraftStatus'
12
16
  import {PublishedStatus} from './core/PublishedStatus'
13
17
  import EditButton from './EditButton'
18
+ import Validate from './Validate'
14
19
  import {ValidationStatus} from './ValidationStatus'
15
20
 
16
21
  type DocumentCardProps = {
@@ -38,52 +43,25 @@ export function DocumentCard(props: DocumentCardProps) {
38
43
  } = props
39
44
  const {assignees = [], documentId} = item._metadata ?? {}
40
45
  const schema = useSchema()
41
-
42
- // Perform document operations after State changes
43
- // If State has changed and the document needs to be un/published
44
- // This functionality was deemed too dangerous / unexpected
45
- // Revisit with improved UX
46
- // const currentState = useMemo(
47
- // () => states.find((state) => state.id === item._metadata?.state),
48
- // [states, item]
49
- // )
50
- // const ops = useDocumentOperation(documentId ?? ``, item._type)
51
- // const toast = useToast()
52
-
53
- // useEffect(() => {
54
- // const isDraft = item._id.startsWith('drafts.')
55
-
56
- // if (isDraft && currentState?.operation === 'publish' && !item?._metadata?.optimistic) {
57
- // if (!ops.publish.disabled) {
58
- // ops.publish.execute()
59
- // toast.push({
60
- // title: 'Published Document',
61
- // description: documentId,
62
- // status: 'success',
63
- // })
64
- // }
65
- // } else if (
66
- // !isDraft &&
67
- // currentState?.operation === 'unpublish' &&
68
- // !item?._metadata?.optimistic
69
- // ) {
70
- // if (!ops.unpublish.disabled) {
71
- // ops.unpublish.execute()
72
- // toast.push({
73
- // title: 'Unpublished Document',
74
- // description: documentId,
75
- // status: 'success',
76
- // })
77
- // }
78
- // }
79
- // }, [currentState, documentId, item, ops, toast])
46
+ const state = states.find((s) => s.id === item._metadata?.state)
80
47
 
81
48
  const isDarkMode = useTheme().sanity.color.dark
82
49
  const defaultCardTone = isDarkMode ? `transparent` : `default`
83
- const {validation = [], isValidating} = useValidationStatus(
84
- documentId ?? ``,
85
- item._type
86
- )
50
+
51
+ // Validation only runs if the state requests it
52
+ // Because it's not performant to run it on many documents simultaneously
53
+ // So we fake it here, and maybe set it inside <Validate />
54
+ const [optimisticValidation, setOptimisticValidation] =
55
+ useState<ValidationStatusType>({
56
+ isValidating: state?.requireValidation ?? false,
57
+ validation: [],
58
+ })
59
+
60
+ const {isValidating, validation} = optimisticValidation
61
+
62
+ const handleValidation = useCallback((updates: ValidationStatusType) => {
63
+ setOptimisticValidation(updates)
64
+ }, [])
87
65
 
88
66
  const cardTone = useMemo(() => {
89
67
  let tone: CardTone = defaultCardTone
@@ -92,7 +70,7 @@ export function DocumentCard(props: DocumentCardProps) {
92
70
  if (!documentId) return tone
93
71
  if (isDragging) tone = `positive`
94
72
 
95
- if (!isValidating && validation.length > 0) {
73
+ if (state?.requireValidation && !isValidating && validation.length > 0) {
96
74
  if (validation.some((v) => v.level === 'error')) {
97
75
  tone = `critical`
98
76
  } else {
@@ -102,13 +80,14 @@ export function DocumentCard(props: DocumentCardProps) {
102
80
 
103
81
  return tone
104
82
  }, [
105
- isDarkMode,
106
- userRoleCanDrop,
107
83
  defaultCardTone,
84
+ userRoleCanDrop,
85
+ isDarkMode,
108
86
  documentId,
109
87
  isDragging,
110
- validation,
111
88
  isValidating,
89
+ validation,
90
+ state?.requireValidation,
112
91
  ])
113
92
 
114
93
  // Update validation status
@@ -136,63 +115,72 @@ export function DocumentCard(props: DocumentCardProps) {
136
115
  )
137
116
 
138
117
  return (
139
- <Box paddingBottom={3} paddingX={3}>
140
- <Card radius={2} shadow={isDragging ? 3 : 1} tone={cardTone}>
141
- <Stack>
142
- <Card
143
- borderBottom
144
- radius={2}
145
- padding={3}
146
- paddingLeft={2}
147
- tone={cardTone}
148
- style={{pointerEvents: 'none'}}
149
- >
150
- <Flex align="center" justify="space-between" gap={1}>
151
- <Box flex={1}>
152
- <Preview
153
- layout="default"
154
- value={item}
155
- schemaType={schema.get(item._type) as SchemaType}
118
+ <>
119
+ {state?.requireValidation ? (
120
+ <Validate
121
+ documentId={documentId}
122
+ type={item._type}
123
+ onChange={handleValidation}
124
+ />
125
+ ) : null}
126
+ <Box paddingBottom={3} paddingX={3}>
127
+ <Card radius={2} shadow={isDragging ? 3 : 1} tone={cardTone}>
128
+ <Stack>
129
+ <Card
130
+ borderBottom
131
+ radius={2}
132
+ padding={3}
133
+ paddingLeft={2}
134
+ tone={cardTone}
135
+ style={{pointerEvents: 'none'}}
136
+ >
137
+ <Flex align="center" justify="space-between" gap={1}>
138
+ <Box flex={1}>
139
+ <Preview
140
+ layout="default"
141
+ value={item}
142
+ schemaType={schema.get(item._type) as SchemaType}
143
+ />
144
+ </Box>
145
+ <Box style={{flexShrink: 0}}>
146
+ {hasError || isDragDisabled ? null : <DragHandleIcon />}
147
+ </Box>
148
+ </Flex>
149
+ </Card>
150
+
151
+ <Card padding={2} radius={2} tone="inherit">
152
+ <Flex align="center" justify="space-between" gap={3}>
153
+ <Box flex={1}>
154
+ {documentId && (
155
+ <UserDisplay
156
+ userList={userList}
157
+ assignees={assignees}
158
+ documentId={documentId}
159
+ disabled={!userRoleCanDrop}
160
+ />
161
+ )}
162
+ </Box>
163
+ {validation.length > 0 ? (
164
+ <ValidationStatus validation={validation} />
165
+ ) : null}
166
+ <DraftStatus document={item} />
167
+ <PublishedStatus document={item} />
168
+ <EditButton
169
+ id={item._id}
170
+ type={item._type}
171
+ disabled={!userRoleCanDrop}
156
172
  />
157
- </Box>
158
- <Box style={{flexShrink: 0}}>
159
- {hasError || isDragDisabled ? null : <DragHandleIcon />}
160
- </Box>
161
- </Flex>
162
- </Card>
163
-
164
- <Card padding={2} radius={2} tone="inherit">
165
- <Flex align="center" justify="space-between" gap={3}>
166
- <Box flex={1}>
167
- {documentId && (
168
- <UserDisplay
169
- userList={userList}
170
- assignees={assignees}
173
+ {isLastState ? (
174
+ <CompleteButton
171
175
  documentId={documentId}
172
176
  disabled={!userRoleCanDrop}
173
177
  />
174
- )}
175
- </Box>
176
- {validation.length > 0 ? (
177
- <ValidationStatus validation={validation} />
178
- ) : null}
179
- <DraftStatus document={item} />
180
- <PublishedStatus document={item} />
181
- <EditButton
182
- id={item._id}
183
- type={item._type}
184
- disabled={!userRoleCanDrop}
185
- />
186
- {isLastState ? (
187
- <CompleteButton
188
- documentId={documentId}
189
- disabled={!userRoleCanDrop}
190
- />
191
- ) : null}
192
- </Flex>
193
- </Card>
194
- </Stack>
195
- </Card>
196
- </Box>
178
+ ) : null}
179
+ </Flex>
180
+ </Card>
181
+ </Stack>
182
+ </Card>
183
+ </Box>
184
+ </>
197
185
  )
198
186
  }
@@ -51,7 +51,7 @@ export default function DocumentList(props: DocumentListProps) {
51
51
  getScrollElement: () => parentRef.current,
52
52
  getItemKey: (index) => dataFiltered[index]?._metadata?.documentId ?? index,
53
53
  estimateSize: () => 113,
54
- overscan: 5,
54
+ overscan: 10,
55
55
  })
56
56
 
57
57
  if (!data.length) {
@@ -1,21 +1,9 @@
1
- import {Flex, Card, Badge, BadgeTone} from '@sanity/ui'
2
1
  import {InfoOutlineIcon, UserIcon} from '@sanity/icons'
2
+ import {Badge, BadgeTone, Box, Card, Flex, Text} from '@sanity/ui'
3
3
  import styled, {css} from 'styled-components'
4
4
 
5
+ import {State} from '../../types'
5
6
  import {Status} from './Status'
6
- import {
7
- // Operation,
8
- State,
9
- } from '../../types'
10
-
11
- type StateTitleProps = {
12
- state: State
13
- requireAssignment: boolean
14
- userRoleCanDrop: boolean
15
- isDropDisabled: boolean
16
- draggingFrom: string
17
- // operation?: Operation
18
- }
19
7
 
20
8
  const StyledStickyCard = styled(Card)(
21
9
  () => css`
@@ -25,8 +13,24 @@ const StyledStickyCard = styled(Card)(
25
13
  `
26
14
  )
27
15
 
16
+ type StateTitleProps = {
17
+ state: State
18
+ requireAssignment: boolean
19
+ userRoleCanDrop: boolean
20
+ isDropDisabled: boolean
21
+ draggingFrom: string
22
+ documentCount: number
23
+ }
24
+
28
25
  export default function StateTitle(props: StateTitleProps) {
29
- const {state, requireAssignment, userRoleCanDrop, isDropDisabled, draggingFrom} = props
26
+ const {
27
+ state,
28
+ requireAssignment,
29
+ userRoleCanDrop,
30
+ isDropDisabled,
31
+ draggingFrom,
32
+ documentCount,
33
+ } = props
30
34
 
31
35
  let tone: BadgeTone = 'default'
32
36
  const isSource = draggingFrom === state.id
@@ -39,7 +43,11 @@ export default function StateTitle(props: StateTitleProps) {
39
43
  <StyledStickyCard paddingY={4} padding={3} tone="inherit">
40
44
  <Flex gap={3} align="center">
41
45
  <Badge
42
- mode={(draggingFrom && !isDropDisabled) || isSource ? 'default' : 'outline'}
46
+ mode={
47
+ (draggingFrom && !isDropDisabled) || isSource
48
+ ? 'default'
49
+ : 'outline'
50
+ }
43
51
  tone={tone}
44
52
  muted={!userRoleCanDrop || isDropDisabled}
45
53
  >
@@ -57,16 +65,13 @@ export default function StateTitle(props: StateTitleProps) {
57
65
  icon={UserIcon}
58
66
  />
59
67
  ) : null}
60
- {/* {operation ? (
61
- <Status
62
- text={
63
- operation === 'publish'
64
- ? `A document moved to this State will also publish the current Draft`
65
- : `A document moved to this State will also unpublish the current Published version`
66
- }
67
- icon={operation === 'publish' ? PublishIcon : UnpublishIcon}
68
- />
69
- ) : null} */}
68
+ <Box flex={1}>
69
+ {documentCount > 0 ? (
70
+ <Text weight="semibold" align="right" size={1}>
71
+ {documentCount}
72
+ </Text>
73
+ ) : null}
74
+ </Box>
70
75
  </Flex>
71
76
  </StyledStickyCard>
72
77
  )
@@ -8,13 +8,17 @@ import {API_VERSION} from '../constants'
8
8
  import {SanityDocumentWithMetadata, State} from '../types'
9
9
  import FloatingCard from './FloatingCard'
10
10
 
11
- type ValidatorsProps = {
11
+ type VerifyProps = {
12
12
  data: SanityDocumentWithMetadata[]
13
13
  userList: UserExtended[]
14
14
  states: State[]
15
15
  }
16
16
 
17
- export default function Validators({data, userList, states}: ValidatorsProps) {
17
+ // This component checks the validity of the data in the Kanban
18
+ // It will only render something it there is invalid date
19
+ // And will render buttons to fix the data
20
+ export default function Verify(props: VerifyProps) {
21
+ const {data, userList, states} = props
18
22
  const client = useClient({apiVersion: API_VERSION})
19
23
  const toast = useToast()
20
24
 
@@ -47,6 +51,18 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
47
51
  }, [] as string[])
48
52
  : []
49
53
 
54
+ const documentsWithDuplicatedOrderIds = data?.length
55
+ ? data.reduce((acc, cur) => {
56
+ const {documentId, orderRank} = cur._metadata ?? {}
57
+
58
+ return orderRank &&
59
+ data.filter((d) => d._metadata?.orderRank === orderRank).length > 1 &&
60
+ documentId
61
+ ? [...acc, documentId]
62
+ : acc
63
+ }, [] as string[])
64
+ : []
65
+
50
66
  // Updates metadata documents to a valid, existing state
51
67
  const correctDocuments = React.useCallback(
52
68
  async (ids: string[]) => {
@@ -204,6 +220,17 @@ export default function Validators({data, userList, states}: ValidatorsProps) {
204
220
  }
205
221
  />
206
222
  ) : null}
223
+ {documentsWithDuplicatedOrderIds.length > 0 ? (
224
+ <Button
225
+ tone="caution"
226
+ onClick={() => addOrderToDocuments(documentsWithDuplicatedOrderIds)}
227
+ text={
228
+ documentsWithDuplicatedOrderIds.length === 1
229
+ ? `Set Unique Order for 1 Document`
230
+ : `Set Unique Order for ${documentsWithDuplicatedOrderIds.length} Documents`
231
+ }
232
+ />
233
+ ) : null}
207
234
  {orphanedMetadataDocumentIds.length > 0 ? (
208
235
  <Button
209
236
  text="Cleanup orphaned metadata"
@@ -19,7 +19,7 @@ import {DocumentCard} from './DocumentCard'
19
19
  import DocumentList from './DocumentList'
20
20
  import Filters from './Filters'
21
21
  import StateTitle from './StateTitle'
22
- import Validators from './Validators'
22
+ import Verify from './Verify'
23
23
 
24
24
  type WorkflowToolProps = {
25
25
  tool: Tool<WorkflowConfig>
@@ -107,7 +107,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
107
107
  )
108
108
 
109
109
  const handleDragEnd = React.useCallback(
110
- (result: DropResult) => {
110
+ async (result: DropResult) => {
111
111
  // Reset undroppable states
112
112
  setUndroppableStates([])
113
113
  setDraggingFrom(``)
@@ -155,12 +155,12 @@ export default function WorkflowTool(props: WorkflowToolProps) {
155
155
  // Must be between two items
156
156
  const itemBefore = destinationStateItems[destination.index - 1]
157
157
  const itemBeforeRank = itemBefore?._metadata?.orderRank
158
- const itemBeforeRankParsed = itemBefore._metadata.orderRank
158
+ const itemBeforeRankParsed = itemBeforeRank
159
159
  ? LexoRank.parse(itemBeforeRank)
160
160
  : LexoRank.min()
161
161
  const itemAfter = destinationStateItems[destination.index]
162
162
  const itemAfterRank = itemAfter?._metadata?.orderRank
163
- const itemAfterRankParsed = itemAfter._metadata.orderRank
163
+ const itemAfterRankParsed = itemAfterRank
164
164
  ? LexoRank.parse(itemAfterRank)
165
165
  : LexoRank.max()
166
166
 
@@ -249,7 +249,8 @@ export default function WorkflowTool(props: WorkflowToolProps) {
249
249
 
250
250
  return (
251
251
  <Flex direction="column" height="fill" overflow="hidden">
252
- <Validators data={data} userList={userList} states={states} />
252
+ <Verify data={data} userList={userList} states={states} />
253
+
253
254
  <Filters
254
255
  uniqueAssignedUsers={uniqueAssignedUsers}
255
256
  selectedUserIds={selectedUserIds}
@@ -279,9 +280,16 @@ export default function WorkflowTool(props: WorkflowToolProps) {
279
280
  state={state}
280
281
  requireAssignment={state.requireAssignment ?? false}
281
282
  userRoleCanDrop={userRoleCanDrop}
282
- // operation={state.operation}
283
283
  isDropDisabled={isDropDisabled}
284
284
  draggingFrom={draggingFrom}
285
+ documentCount={
286
+ filterItemsAndSort(
287
+ data,
288
+ state.id,
289
+ selectedUserIds,
290
+ selectedSchemaTypes
291
+ ).length
292
+ }
285
293
  />
286
294
  <Box flex={1}>
287
295
  <Droppable
@@ -289,6 +297,7 @@ export default function WorkflowTool(props: WorkflowToolProps) {
289
297
  isDropDisabled={isDropDisabled}
290
298
  // props required for virtualization
291
299
  mode="virtual"
300
+ // TODO: Render this as a memo/callback
292
301
  renderClone={(provided, snapshot, rubric) => {
293
302
  const item = data.find(
294
303
  (doc) =>
@@ -24,8 +24,8 @@ export const DEFAULT_CONFIG: WorkflowConfig = {
24
24
  title: 'Approved',
25
25
  color: 'success',
26
26
  roles: ['administrator'],
27
- requireAssignment: true,
28
27
  transitions: ['changesRequested'],
28
+ requireAssignment: true,
29
29
  },
30
30
  ]),
31
31
  }
@@ -65,7 +65,7 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
65
65
  }, [data])
66
66
 
67
67
  const move = React.useCallback(
68
- (
68
+ async (
69
69
  draggedId: string,
70
70
  destination: DraggableLocation,
71
71
  states: State[],
@@ -122,11 +122,13 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
122
122
  const {_id, _type} = document
123
123
 
124
124
  // Metadata + useDocumentOperation always uses Published id
125
- const {_rev, documentId} = document._metadata || {}
125
+ const {documentId} = document._metadata || {}
126
126
 
127
- client
127
+ await client
128
128
  .patch(`workflow-metadata.${documentId}`)
129
- .ifRevisionId(_rev as string)
129
+ // Removed because it was effecting fast-updates between columns
130
+ // TODO: Prevent dragging while patching instead while keeping optimistic updates and revert when patch fails
131
+ // .ifRevisionId(document._metadata._rev)
130
132
  .set({state: newStateId, orderRank: newOrder})
131
133
  .commit()
132
134
  .then(() => {
@@ -135,12 +137,13 @@ export function useWorkflowDocuments(schemaTypes: string[]): WorkflowDocuments {
135
137
  status: 'success',
136
138
  })
137
139
  })
138
- .catch(() => {
140
+ .catch((err) => {
139
141
  // Revert optimistic update
140
142
  setLocalDocuments(currentLocalData)
141
143
 
142
144
  return toast.push({
143
145
  title: `Failed to move to "${newState?.title ?? newStateId}"`,
146
+ description: err.message,
144
147
  status: 'error',
145
148
  })
146
149
  })
@@ -1,14 +1,12 @@
1
1
  import {SanityDocumentLike} from 'sanity'
2
2
 
3
- // export type Operation = 'publish' | 'unpublish'
4
-
5
3
  export type State = {
6
4
  id: string
7
5
  transitions: string[]
8
6
  title: string
9
- // operation?: Operation
10
7
  roles?: string[]
11
8
  requireAssignment?: boolean
9
+ requireValidation?: boolean
12
10
  // From document badges
13
11
  color?: 'primary' | 'success' | 'warning' | 'danger'
14
12
  }