sanity-plugin-media 2.1.0 → 2.1.1
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/lib/index.esm.js +1 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/package.json +4 -5
- package/src/components/DialogAssetEdit/index.tsx +95 -118
- package/src/components/DialogTagCreate/index.tsx +10 -21
- package/src/components/DialogTagEdit/index.tsx +25 -40
- package/src/components/FormFieldInputLabel/index.tsx +2 -3
- package/src/components/FormFieldInputTags/index.tsx +6 -5
- package/src/components/FormFieldInputText/index.tsx +3 -3
- package/src/components/FormFieldInputTextarea/index.tsx +1 -2
- package/src/components/SearchFacetTags/index.tsx +3 -3
- package/src/formSchema/index.ts +22 -0
- package/src/modules/tags/index.ts +2 -2
- package/src/types/index.ts +9 -6
- package/src/utils/getTagSelectOptions.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-media",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "This version of `sanity-plugin-media` is for Sanity Studio V3.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"watch": "pkg-utils watch"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@hookform/resolvers": "
|
|
57
|
+
"@hookform/resolvers": "^3.1.1",
|
|
58
58
|
"@reduxjs/toolkit": "^1.9.0",
|
|
59
59
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
60
60
|
"@sanity/ui": "^1.7.0",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"pluralize": "^8.0.0",
|
|
70
70
|
"react-dropzone": "^11.3.1",
|
|
71
71
|
"react-file-icon": "^1.1.0",
|
|
72
|
-
"react-hook-form": "^
|
|
72
|
+
"react-hook-form": "^7.45.1",
|
|
73
73
|
"react-redux": "^7.2.2",
|
|
74
74
|
"react-select": "^5.3.2",
|
|
75
75
|
"react-virtuoso": "^4.3.11",
|
|
76
76
|
"redux": "^4.2.0",
|
|
77
77
|
"redux-observable": "^2.0.0",
|
|
78
78
|
"rxjs": "^7.0.0",
|
|
79
|
-
"
|
|
79
|
+
"zod": "^3.21.4"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
82
|
"@commitlint/cli": "^17.2.0",
|
|
@@ -94,7 +94,6 @@
|
|
|
94
94
|
"@types/react-file-icon": "^1.0.1",
|
|
95
95
|
"@types/react-redux": "^7.1.24",
|
|
96
96
|
"@types/styled-components": "^5.1.7",
|
|
97
|
-
"@types/yup": "^0.29.14",
|
|
98
97
|
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
|
99
98
|
"@typescript-eslint/parser": "^5.42.0",
|
|
100
99
|
"conventional-changelog-conventionalcommits": "^5.0.0",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
2
|
import type {MutationEvent} from '@sanity/client'
|
|
3
3
|
import {Box, Button, Card, Flex, Stack, Tab, TabList, TabPanel, Text} from '@sanity/ui'
|
|
4
|
-
import {Asset, DialogAssetEditProps,
|
|
4
|
+
import {Asset, AssetFormData, DialogAssetEditProps, TagSelectOption} from '@types'
|
|
5
5
|
import groq from 'groq'
|
|
6
|
-
import React, {ReactNode, useEffect, useRef, useState} from 'react'
|
|
7
|
-
import {useForm} from 'react-hook-form'
|
|
6
|
+
import React, {ReactNode, useCallback, useEffect, useRef, useState} from 'react'
|
|
7
|
+
import {SubmitHandler, useForm} from 'react-hook-form'
|
|
8
8
|
import {useDispatch} from 'react-redux'
|
|
9
|
-
import
|
|
9
|
+
import {assetFormSchema} from '../../formSchema'
|
|
10
10
|
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
11
11
|
import useVersionedClient from '../../hooks/useVersionedClient'
|
|
12
12
|
import {assetsActions, selectAssetById} from '../../modules/assets'
|
|
@@ -31,12 +31,6 @@ type Props = {
|
|
|
31
31
|
dialog: DialogAssetEditProps
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
type FormData = yup.InferType<typeof formSchema>
|
|
35
|
-
|
|
36
|
-
const formSchema = yup.object().shape({
|
|
37
|
-
originalFilename: yup.string().trim().required('Filename cannot be empty')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
34
|
const DialogAssetEdit = (props: Props) => {
|
|
41
35
|
const {
|
|
42
36
|
children,
|
|
@@ -45,38 +39,34 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
45
39
|
|
|
46
40
|
const client = useVersionedClient()
|
|
47
41
|
|
|
48
|
-
// Redux
|
|
49
42
|
const dispatch = useDispatch()
|
|
50
43
|
const assetItem = useTypedSelector(state => selectAssetById(state, String(assetId))) // TODO: check casting
|
|
51
44
|
const tags = useTypedSelector(selectTags)
|
|
52
45
|
|
|
53
|
-
|
|
54
|
-
const isMounted = useRef(false)
|
|
46
|
+
const assetUpdatedPrev = useRef<string | null>(null)
|
|
55
47
|
|
|
56
|
-
//
|
|
57
|
-
// - Generate a snapshot of the current asset
|
|
48
|
+
// Generate a snapshot of the current asset
|
|
58
49
|
const [assetSnapshot, setAssetSnapshot] = useState(assetItem?.asset)
|
|
59
50
|
const [tabSection, setTabSection] = useState<'details' | 'references'>('details')
|
|
60
51
|
|
|
61
52
|
const currentAsset = assetItem ? assetItem?.asset : assetSnapshot
|
|
62
53
|
const allTagOptions = getTagSelectOptions(tags)
|
|
63
54
|
|
|
64
|
-
// Redux
|
|
65
55
|
const assetTagOptions = useTypedSelector(selectTagSelectOptions(currentAsset))
|
|
66
56
|
|
|
67
|
-
const generateDefaultValues = (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
57
|
+
const generateDefaultValues = useCallback(
|
|
58
|
+
(asset?: Asset): AssetFormData => {
|
|
59
|
+
return {
|
|
60
|
+
altText: asset?.altText || '',
|
|
61
|
+
description: asset?.description || '',
|
|
62
|
+
originalFilename: asset?.originalFilename || '',
|
|
63
|
+
opt: {media: {tags: assetTagOptions}},
|
|
64
|
+
title: asset?.title || ''
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
[assetTagOptions]
|
|
68
|
+
)
|
|
78
69
|
|
|
79
|
-
// react-hook-form
|
|
80
70
|
const {
|
|
81
71
|
control,
|
|
82
72
|
// Read the formState before render to subscribe the form state through Proxy
|
|
@@ -86,20 +76,19 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
86
76
|
register,
|
|
87
77
|
reset,
|
|
88
78
|
setValue
|
|
89
|
-
} = useForm({
|
|
79
|
+
} = useForm<AssetFormData>({
|
|
90
80
|
defaultValues: generateDefaultValues(assetItem?.asset),
|
|
91
81
|
mode: 'onChange',
|
|
92
|
-
resolver:
|
|
82
|
+
resolver: zodResolver(assetFormSchema)
|
|
93
83
|
})
|
|
94
84
|
|
|
95
85
|
const formUpdating = !assetItem || assetItem?.updating
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
const handleClose = () => {
|
|
87
|
+
const handleClose = useCallback(() => {
|
|
99
88
|
dispatch(dialogActions.remove({id}))
|
|
100
|
-
}
|
|
89
|
+
}, [dispatch, id])
|
|
101
90
|
|
|
102
|
-
const handleDelete = () => {
|
|
91
|
+
const handleDelete = useCallback(() => {
|
|
103
92
|
if (!assetItem?.asset) {
|
|
104
93
|
return
|
|
105
94
|
}
|
|
@@ -110,63 +99,64 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
110
99
|
closeDialogId: assetItem?.asset._id
|
|
111
100
|
})
|
|
112
101
|
)
|
|
113
|
-
}
|
|
102
|
+
}, [assetItem, dispatch])
|
|
114
103
|
|
|
115
|
-
const handleAssetUpdate = (update: MutationEvent) => {
|
|
104
|
+
const handleAssetUpdate = useCallback((update: MutationEvent) => {
|
|
116
105
|
const {result, transition} = update
|
|
117
|
-
|
|
118
106
|
if (result && transition === 'update') {
|
|
119
107
|
// Regenerate asset snapshot
|
|
120
108
|
setAssetSnapshot(result as Asset)
|
|
121
|
-
|
|
122
|
-
// Reset react-hook-form
|
|
123
|
-
reset(generateDefaultValues(result as Asset))
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const handleCreateTag = (tagName: string) => {
|
|
128
|
-
// Dispatch action to create new tag
|
|
129
|
-
dispatch(
|
|
130
|
-
tagsActions.createRequest({
|
|
131
|
-
assetId: currentAsset?._id,
|
|
132
|
-
name: tagName
|
|
133
|
-
})
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// - submit react-hook-form
|
|
138
|
-
const onSubmit = async (formData: FormData) => {
|
|
139
|
-
if (!assetItem?.asset) {
|
|
140
|
-
return
|
|
141
109
|
}
|
|
110
|
+
}, [])
|
|
142
111
|
|
|
143
|
-
|
|
112
|
+
const handleCreateTag = useCallback(
|
|
113
|
+
(tagName: string) => {
|
|
114
|
+
// Dispatch action to create new tag
|
|
115
|
+
dispatch(
|
|
116
|
+
tagsActions.createRequest({
|
|
117
|
+
assetId: currentAsset?._id,
|
|
118
|
+
name: tagName
|
|
119
|
+
})
|
|
120
|
+
)
|
|
121
|
+
},
|
|
122
|
+
[currentAsset?._id, dispatch]
|
|
123
|
+
)
|
|
144
124
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
125
|
+
// Submit react-hook-form
|
|
126
|
+
const onSubmit: SubmitHandler<AssetFormData> = useCallback(
|
|
127
|
+
formData => {
|
|
128
|
+
if (!assetItem?.asset) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const sanitizedFormData = sanitizeFormData(formData)
|
|
133
|
+
|
|
134
|
+
dispatch(
|
|
135
|
+
assetsActions.updateRequest({
|
|
136
|
+
asset: assetItem?.asset,
|
|
137
|
+
closeDialogId: assetItem?.asset._id,
|
|
138
|
+
formData: {
|
|
139
|
+
...sanitizedFormData,
|
|
140
|
+
// Map tags to sanity references
|
|
141
|
+
opt: {
|
|
142
|
+
media: {
|
|
143
|
+
...sanitizedFormData.opt.media,
|
|
144
|
+
tags:
|
|
145
|
+
sanitizedFormData.opt.media.tags?.map((tag: TagSelectOption) => ({
|
|
146
|
+
_ref: tag.value,
|
|
147
|
+
_type: 'reference',
|
|
148
|
+
_weak: true
|
|
149
|
+
})) || null
|
|
150
|
+
}
|
|
161
151
|
}
|
|
162
152
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
})
|
|
154
|
+
)
|
|
155
|
+
},
|
|
156
|
+
[assetItem?.asset, dispatch]
|
|
157
|
+
)
|
|
167
158
|
|
|
168
|
-
//
|
|
169
|
-
// - Listen for asset mutations and update snapshot
|
|
159
|
+
// Listen for asset mutations and update snapshot
|
|
170
160
|
useEffect(() => {
|
|
171
161
|
if (!assetItem?.asset) {
|
|
172
162
|
return undefined
|
|
@@ -180,49 +170,36 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
180
170
|
return () => {
|
|
181
171
|
subscriptionAsset?.unsubscribe()
|
|
182
172
|
}
|
|
183
|
-
}, [])
|
|
173
|
+
}, [assetItem?.asset, client, handleAssetUpdate])
|
|
184
174
|
|
|
185
|
-
//
|
|
186
|
-
useEffect(() => {
|
|
187
|
-
if (isMounted.current) {
|
|
188
|
-
reset(
|
|
189
|
-
{
|
|
190
|
-
opt: {
|
|
191
|
-
media: {tags: assetTagOptions}
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
errors: true,
|
|
196
|
-
dirtyFields: true,
|
|
197
|
-
isDirty: true
|
|
198
|
-
}
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Mark as mounted
|
|
203
|
-
isMounted.current = true
|
|
204
|
-
}, [currentTagLabels])
|
|
205
|
-
|
|
206
|
-
// - Update tags form field (react-select) when a new _inline_ tag has been created
|
|
175
|
+
// Update tags form field (react-select) when a new _inline_ tag has been created
|
|
207
176
|
useEffect(() => {
|
|
208
177
|
if (lastCreatedTag) {
|
|
209
|
-
const existingTags = (getValues('opt.media.tags') as
|
|
178
|
+
const existingTags = (getValues('opt.media.tags') as TagSelectOption[]) || []
|
|
210
179
|
const updatedTags = existingTags.concat([lastCreatedTag])
|
|
211
180
|
setValue('opt.media.tags', updatedTags, {shouldDirty: true})
|
|
212
181
|
}
|
|
213
|
-
}, [lastCreatedTag])
|
|
182
|
+
}, [getValues, lastCreatedTag, setValue])
|
|
214
183
|
|
|
215
|
-
//
|
|
184
|
+
// Update tags form field (react-select) when an _inline_ tag has been removed elsewhere
|
|
216
185
|
useEffect(() => {
|
|
217
186
|
if (lastRemovedTagIds) {
|
|
218
|
-
const existingTags = (getValues('opt.media.tags') as
|
|
187
|
+
const existingTags = (getValues('opt.media.tags') as TagSelectOption[]) || []
|
|
219
188
|
const updatedTags = existingTags.filter(tag => {
|
|
220
189
|
return !lastRemovedTagIds.includes(tag.value)
|
|
221
190
|
})
|
|
222
191
|
|
|
223
192
|
setValue('opt.media.tags', updatedTags, {shouldDirty: true})
|
|
224
193
|
}
|
|
225
|
-
}, [lastRemovedTagIds])
|
|
194
|
+
}, [getValues, lastRemovedTagIds, setValue])
|
|
195
|
+
|
|
196
|
+
// Reset react-hook-form local state on mount and every time the asset has been updated elsewhere
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (assetUpdatedPrev.current !== assetItem?.asset._updatedAt) {
|
|
199
|
+
reset(generateDefaultValues(assetItem?.asset))
|
|
200
|
+
}
|
|
201
|
+
assetUpdatedPrev.current = assetItem?.asset._updatedAt
|
|
202
|
+
}, [assetItem?.asset, generateDefaultValues, reset])
|
|
226
203
|
|
|
227
204
|
const Footer = () => (
|
|
228
205
|
<Box padding={3}>
|
|
@@ -306,7 +283,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
306
283
|
<FormFieldInputTags
|
|
307
284
|
control={control}
|
|
308
285
|
disabled={formUpdating}
|
|
309
|
-
error={errors?.opt?.media?.tags}
|
|
286
|
+
error={errors?.opt?.media?.tags?.message}
|
|
310
287
|
label="Tags"
|
|
311
288
|
name="opt.media.tags"
|
|
312
289
|
onCreateTag={handleCreateTag}
|
|
@@ -316,38 +293,38 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
316
293
|
/>
|
|
317
294
|
{/* Filename */}
|
|
318
295
|
<FormFieldInputText
|
|
296
|
+
{...register('originalFilename')}
|
|
319
297
|
disabled={formUpdating}
|
|
320
|
-
error={errors?.originalFilename}
|
|
298
|
+
error={errors?.originalFilename?.message}
|
|
321
299
|
label="Filename"
|
|
322
300
|
name="originalFilename"
|
|
323
|
-
ref={register}
|
|
324
301
|
value={currentAsset?.originalFilename}
|
|
325
302
|
/>
|
|
326
303
|
{/* Title */}
|
|
327
304
|
<FormFieldInputText
|
|
305
|
+
{...register('title')}
|
|
328
306
|
disabled={formUpdating}
|
|
329
|
-
error={errors?.title}
|
|
307
|
+
error={errors?.title?.message}
|
|
330
308
|
label="Title"
|
|
331
309
|
name="title"
|
|
332
|
-
ref={register}
|
|
333
310
|
value={currentAsset?.title}
|
|
334
311
|
/>
|
|
335
312
|
{/* Alt text */}
|
|
336
313
|
<FormFieldInputText
|
|
314
|
+
{...register('altText')}
|
|
337
315
|
disabled={formUpdating}
|
|
338
|
-
error={errors?.altText}
|
|
316
|
+
error={errors?.altText?.message}
|
|
339
317
|
label="Alt Text"
|
|
340
318
|
name="altText"
|
|
341
|
-
ref={register}
|
|
342
319
|
value={currentAsset?.altText}
|
|
343
320
|
/>
|
|
344
321
|
{/* Description */}
|
|
345
322
|
<FormFieldInputTextarea
|
|
323
|
+
{...register('description')}
|
|
346
324
|
disabled={formUpdating}
|
|
347
|
-
error={errors?.description}
|
|
325
|
+
error={errors?.description?.message}
|
|
348
326
|
label="Description"
|
|
349
327
|
name="description"
|
|
350
|
-
ref={register}
|
|
351
328
|
rows={3}
|
|
352
329
|
value={currentAsset?.description}
|
|
353
330
|
/>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
2
|
import {Box, Flex} from '@sanity/ui'
|
|
3
|
-
import {DialogTagCreateProps} from '@types'
|
|
3
|
+
import {DialogTagCreateProps, TagFormData} from '@types'
|
|
4
4
|
import React, {ReactNode, useEffect} from 'react'
|
|
5
|
-
import {useForm} from 'react-hook-form'
|
|
5
|
+
import {SubmitHandler, useForm} from 'react-hook-form'
|
|
6
6
|
import {useDispatch} from 'react-redux'
|
|
7
|
-
import
|
|
7
|
+
import {tagFormSchema} from '../../formSchema'
|
|
8
8
|
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
9
9
|
import {dialogActions} from '../../modules/dialog'
|
|
10
10
|
import {tagsActions} from '../../modules/tags'
|
|
@@ -18,62 +18,51 @@ type Props = {
|
|
|
18
18
|
dialog: DialogTagCreateProps
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
type FormData = yup.InferType<typeof formSchema>
|
|
22
|
-
|
|
23
|
-
const formSchema = yup.object().shape({
|
|
24
|
-
name: yup.string().required('Name cannot be empty')
|
|
25
|
-
})
|
|
26
|
-
|
|
27
21
|
const DialogTagCreate = (props: Props) => {
|
|
28
22
|
const {
|
|
29
23
|
children,
|
|
30
24
|
dialog: {id}
|
|
31
25
|
} = props
|
|
32
26
|
|
|
33
|
-
// Redux
|
|
34
27
|
const dispatch = useDispatch()
|
|
35
28
|
|
|
36
|
-
// State
|
|
37
29
|
const creating = useTypedSelector(state => state.tags.creating)
|
|
38
30
|
const creatingError = useTypedSelector(state => state.tags.creatingError)
|
|
39
31
|
|
|
40
|
-
// react-hook-form
|
|
41
32
|
const {
|
|
42
33
|
// Read the formState before render to subscribe the form state through Proxy
|
|
43
34
|
formState: {errors, isDirty, isValid},
|
|
44
35
|
handleSubmit,
|
|
45
36
|
register,
|
|
46
37
|
setError
|
|
47
|
-
} = useForm({
|
|
38
|
+
} = useForm<TagFormData>({
|
|
48
39
|
defaultValues: {
|
|
49
40
|
name: ''
|
|
50
41
|
},
|
|
51
42
|
mode: 'onChange',
|
|
52
|
-
resolver:
|
|
43
|
+
resolver: zodResolver(tagFormSchema)
|
|
53
44
|
})
|
|
54
45
|
|
|
55
46
|
const formUpdating = creating
|
|
56
47
|
|
|
57
|
-
// Callbacks
|
|
58
48
|
const handleClose = () => {
|
|
59
49
|
dispatch(dialogActions.clear())
|
|
60
50
|
}
|
|
61
51
|
|
|
62
52
|
// - submit react-hook-form
|
|
63
|
-
const onSubmit =
|
|
53
|
+
const onSubmit: SubmitHandler<TagFormData> = formData => {
|
|
64
54
|
const sanitizedFormData = sanitizeFormData(formData)
|
|
65
55
|
|
|
66
56
|
dispatch(tagsActions.createRequest({name: sanitizedFormData.name}))
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
// Effects
|
|
70
59
|
useEffect(() => {
|
|
71
60
|
if (creatingError) {
|
|
72
61
|
setError('name', {
|
|
73
62
|
message: creatingError?.message
|
|
74
63
|
})
|
|
75
64
|
}
|
|
76
|
-
}, [creatingError])
|
|
65
|
+
}, [creatingError, setError])
|
|
77
66
|
|
|
78
67
|
const Footer = () => (
|
|
79
68
|
<Box padding={3}>
|
|
@@ -97,11 +86,11 @@ const DialogTagCreate = (props: Props) => {
|
|
|
97
86
|
|
|
98
87
|
{/* Title */}
|
|
99
88
|
<FormFieldInputText
|
|
89
|
+
{...register('name')}
|
|
100
90
|
disabled={formUpdating}
|
|
101
|
-
error={errors?.name}
|
|
91
|
+
error={errors?.name?.message}
|
|
102
92
|
label="Name"
|
|
103
93
|
name="name"
|
|
104
|
-
ref={register}
|
|
105
94
|
/>
|
|
106
95
|
</Box>
|
|
107
96
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
2
|
import type {MutationEvent} from '@sanity/client'
|
|
3
3
|
import {Box, Button, Card, Flex, Text} from '@sanity/ui'
|
|
4
|
-
import {DialogTagEditProps, Tag} from '@types'
|
|
4
|
+
import {DialogTagEditProps, Tag, TagFormData} from '@types'
|
|
5
5
|
import groq from 'groq'
|
|
6
|
-
import React, {ReactNode, useEffect, useState} from 'react'
|
|
7
|
-
import {useForm} from 'react-hook-form'
|
|
6
|
+
import React, {ReactNode, useCallback, useEffect, useState} from 'react'
|
|
7
|
+
import {SubmitHandler, useForm} from 'react-hook-form'
|
|
8
8
|
import {useDispatch} from 'react-redux'
|
|
9
|
-
import
|
|
9
|
+
import {tagFormSchema} from '../../formSchema'
|
|
10
10
|
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
11
11
|
import useVersionedClient from '../../hooks/useVersionedClient'
|
|
12
12
|
import {dialogActions} from '../../modules/dialog'
|
|
@@ -21,12 +21,6 @@ type Props = {
|
|
|
21
21
|
dialog: DialogTagEditProps
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
type FormData = yup.InferType<typeof formSchema>
|
|
25
|
-
|
|
26
|
-
const formSchema = yup.object().shape({
|
|
27
|
-
name: yup.string().required('Name cannot be empty')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
24
|
const DialogTagEdit = (props: Props) => {
|
|
31
25
|
const {
|
|
32
26
|
children,
|
|
@@ -35,11 +29,9 @@ const DialogTagEdit = (props: Props) => {
|
|
|
35
29
|
|
|
36
30
|
const client = useVersionedClient()
|
|
37
31
|
|
|
38
|
-
// Redux
|
|
39
32
|
const dispatch = useDispatch()
|
|
40
33
|
const tagItem = useTypedSelector(state => selectTagById(state, String(tagId))) // TODO: double check string cast
|
|
41
34
|
|
|
42
|
-
// State
|
|
43
35
|
// - Generate a snapshot of the current tag
|
|
44
36
|
const [tagSnapshot, setTagSnapshot] = useState(tagItem?.tag)
|
|
45
37
|
|
|
@@ -48,7 +40,6 @@ const DialogTagEdit = (props: Props) => {
|
|
|
48
40
|
name: tag?.name?.current || ''
|
|
49
41
|
})
|
|
50
42
|
|
|
51
|
-
// react-hook-form
|
|
52
43
|
const {
|
|
53
44
|
// Read the formState before render to subscribe the form state through Proxy
|
|
54
45
|
formState: {errors, isDirty, isValid},
|
|
@@ -56,30 +47,24 @@ const DialogTagEdit = (props: Props) => {
|
|
|
56
47
|
register,
|
|
57
48
|
reset,
|
|
58
49
|
setError
|
|
59
|
-
} = useForm({
|
|
60
|
-
// defaultValues: {
|
|
61
|
-
// name: currentTag?.tag?.name?.current
|
|
62
|
-
// },
|
|
50
|
+
} = useForm<TagFormData>({
|
|
63
51
|
defaultValues: generateDefaultValues(tagItem?.tag),
|
|
64
52
|
mode: 'onChange',
|
|
65
|
-
resolver:
|
|
53
|
+
resolver: zodResolver(tagFormSchema)
|
|
66
54
|
})
|
|
67
55
|
|
|
68
56
|
const formUpdating = !tagItem || tagItem?.updating
|
|
69
57
|
|
|
70
|
-
// Callbacks
|
|
71
58
|
const handleClose = () => {
|
|
72
59
|
dispatch(dialogActions.remove({id}))
|
|
73
60
|
}
|
|
74
61
|
|
|
75
|
-
//
|
|
76
|
-
const onSubmit =
|
|
62
|
+
// Submit react-hook-form
|
|
63
|
+
const onSubmit: SubmitHandler<TagFormData> = formData => {
|
|
77
64
|
if (!tagItem?.tag) {
|
|
78
65
|
return
|
|
79
66
|
}
|
|
80
|
-
|
|
81
67
|
const sanitizedFormData = sanitizeFormData(formData)
|
|
82
|
-
|
|
83
68
|
dispatch(
|
|
84
69
|
tagsActions.updateRequest({
|
|
85
70
|
closeDialogId: tagItem?.tag?._id,
|
|
@@ -107,26 +92,26 @@ const DialogTagEdit = (props: Props) => {
|
|
|
107
92
|
)
|
|
108
93
|
}
|
|
109
94
|
|
|
110
|
-
const handleTagUpdate = (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
95
|
+
const handleTagUpdate = useCallback(
|
|
96
|
+
(update: MutationEvent) => {
|
|
97
|
+
const {result, transition} = update
|
|
98
|
+
if (result && transition === 'update') {
|
|
99
|
+
// Regenerate snapshot
|
|
100
|
+
setTagSnapshot(result as Tag)
|
|
101
|
+
// Reset react-hook-form
|
|
102
|
+
reset(generateDefaultValues(result as Tag))
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[reset]
|
|
106
|
+
)
|
|
121
107
|
|
|
122
|
-
// Effects
|
|
123
108
|
useEffect(() => {
|
|
124
109
|
if (tagItem?.error) {
|
|
125
110
|
setError('name', {
|
|
126
111
|
message: tagItem.error?.message
|
|
127
112
|
})
|
|
128
113
|
}
|
|
129
|
-
}, [tagItem
|
|
114
|
+
}, [setError, tagItem.error])
|
|
130
115
|
|
|
131
116
|
// - Listen for asset mutations and update snapshot
|
|
132
117
|
useEffect(() => {
|
|
@@ -142,7 +127,7 @@ const DialogTagEdit = (props: Props) => {
|
|
|
142
127
|
return () => {
|
|
143
128
|
subscriptionAsset?.unsubscribe()
|
|
144
129
|
}
|
|
145
|
-
}, [])
|
|
130
|
+
}, [client, handleTagUpdate, tagItem?.tag])
|
|
146
131
|
|
|
147
132
|
const Footer = () => (
|
|
148
133
|
<Box padding={3}>
|
|
@@ -188,11 +173,11 @@ const DialogTagEdit = (props: Props) => {
|
|
|
188
173
|
|
|
189
174
|
{/* Title */}
|
|
190
175
|
<FormFieldInputText
|
|
176
|
+
{...register('name')}
|
|
191
177
|
disabled={formUpdating}
|
|
192
|
-
error={errors?.name}
|
|
178
|
+
error={errors?.name?.message}
|
|
193
179
|
label="Name"
|
|
194
180
|
name="name"
|
|
195
|
-
ref={register}
|
|
196
181
|
/>
|
|
197
182
|
</Box>
|
|
198
183
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {ErrorOutlineIcon} from '@sanity/icons'
|
|
2
2
|
import {Box, Inline, Text, Tooltip} from '@sanity/ui'
|
|
3
3
|
import React from 'react'
|
|
4
|
-
import {FieldError} from 'react-hook-form'
|
|
5
4
|
import styled from 'styled-components'
|
|
6
5
|
|
|
7
6
|
type Props = {
|
|
8
7
|
description?: string
|
|
9
|
-
error?:
|
|
8
|
+
error?: string
|
|
10
9
|
label: string
|
|
11
10
|
name: string
|
|
12
11
|
}
|
|
@@ -37,7 +36,7 @@ const FormFieldInputLabel = (props: Props) => {
|
|
|
37
36
|
<Box padding={2}>
|
|
38
37
|
<Text muted size={1}>
|
|
39
38
|
<StyledErrorOutlineIcon style={{marginRight: '0.1em'}} />
|
|
40
|
-
{error
|
|
39
|
+
{error}
|
|
41
40
|
</Text>
|
|
42
41
|
</Box>
|
|
43
42
|
}
|