11// codewit/client/src/pages/DemoForm.tsx
22import { useEffect , useMemo , useState } from "react" ;
3- import ReusableTable , { Column } from "../components/form/ReusableTable" ;
43import { toast } from "react-toastify" ;
5- import VideoSelect from "../components/form/VideoSelect" ;
6- import { topic_options } from "../components/form/TagSelect" ;
7- import { language_options , get_language_option } from "../components/form/LanguageSelect" ;
8- import CreateButton from "../components/form/CreateButton" ;
94import { DemoResponse , ExerciseResponse } from "@codewit/interfaces" ;
10- import { isFormValid } from "../utils/formValidationUtils" ;
11- import LoadingPage from "../components/loading/LoadingPage" ;
12- import { Modal , ModalBody , ModalFooter , ModalHeader } from "../components/ui/modal" ;
135import { useField , useForm } from "@tanstack/react-form" ;
14- import { Button , Label , TextInput } from "flowbite-react" ;
6+ import { Button , Label , TextInput , HelperText } from "flowbite-react" ;
157import CreatableSelect from "react-select/creatable" ;
16- import { cn , SelectStyles } from "../utils/styles" ;
178import Select from "react-select" ;
189import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
1910import axios from "axios" ;
20- import { ErrorView } from "../components/error/Error" ;
2111import {
2212 DndContext ,
2313 closestCenter ,
@@ -36,6 +26,18 @@ import {
3626} from "@dnd-kit/sortable" ;
3727import { Bars3Icon , TrashIcon } from "@heroicons/react/24/solid" ;
3828
29+ import { cn , SelectStyles } from "../utils/styles" ;
30+ import { ErrorView } from "../components/error/Error" ;
31+ import { isFormValid } from "../utils/formValidationUtils" ;
32+ import LoadingPage from "../components/loading/LoadingPage" ;
33+ import { Modal , ModalBody , ModalFooter , ModalHeader } from "../components/ui/modal" ;
34+ import VideoSelect from "../components/form/VideoSelect" ;
35+ import { topic_options } from "../components/form/TagSelect" ;
36+ import { language_options , get_language_option } from "../components/form/LanguageSelect" ;
37+ import CreateButton from "../components/form/CreateButton" ;
38+ import ReusableTable , { Column } from "../components/form/ReusableTable" ;
39+ import { VideoOption , use_yt_videos } from "../hooks/yt_videos" ;
40+
3941interface DemoForm {
4042 uid ?: number ,
4143 title : string ,
@@ -171,6 +173,10 @@ interface DemoFormProps {
171173}
172174
173175function DemoForm ( { view, demo, on_cancel, on_created, on_updated} : DemoFormProps ) {
176+ const {
177+ error : yt_video_error
178+ } = use_yt_videos ( ) ;
179+
174180 const send_demo = useMutation ( {
175181 mutationFn : async ( data : DemoForm ) => {
176182 let body = {
@@ -216,7 +222,8 @@ function DemoForm({view, demo, on_cancel, on_created, on_updated}: DemoFormProps
216222 let result = await send_demo . mutateAsync ( value ) ;
217223
218224 form . reset ( demo_form ( result . data ) ) ;
219- }
225+ } ,
226+ validators : { }
220227 } ) ;
221228
222229 useEffect ( ( ) => {
@@ -227,9 +234,7 @@ function DemoForm({view, demo, on_cancel, on_created, on_updated}: DemoFormProps
227234 form . reset ( ) ;
228235 } , [ view ] ) ;
229236
230- return < Modal show = { view } position = "center" size = "2xl" onClose = { ( ) => {
231- on_cancel ( ) ;
232- } } >
237+ return < Modal show = { view } position = "center" size = "2xl" onClose = { ( ) => on_cancel ( ) } >
233238 < ModalHeader >
234239 < form . Subscribe
235240 selector = { state => ( [ state . values . uid ] ) }
@@ -257,13 +262,73 @@ function DemoForm({view, demo, on_cancel, on_created, on_updated}: DemoFormProps
257262 />
258263 </ div > ;
259264 } } />
260- < form . Field name = "youtube_id" children = { ( field ) => {
261- return < VideoSelect required youtube_id = { field . state . value } onSelectVideo = { ( id , thumbnail ) => {
262- field . handleChange ( id ) ;
263- // have to use this since the id and thunbnail are tied to the same input
264- form . setFieldValue ( "youtube_thumbnail" , thumbnail ) ;
265- } } />
266- } } />
265+ { yt_video_error ?
266+ < >
267+ < form . Field name = "youtube_id" children = { ( field ) => {
268+ return < div className = "space-y-2" >
269+ < Label htmlFor = { field . name } >
270+ Youtube ID
271+ </ Label >
272+ < TextInput
273+ id = { field . name }
274+ name = { field . name }
275+ value = { field . state . value }
276+ onBlur = { field . handleBlur }
277+ onChange = { ev => field . handleChange ( ev . target . value ) }
278+ />
279+ < HelperText >
280+ Only the Youtube ID which is located in the URL of a video on the site.
281+ </ HelperText >
282+ </ div >
283+ } } />
284+ < form . Field
285+ name = "youtube_thumbnail"
286+ validators = { {
287+ onBlur : ( { value} ) => {
288+ try {
289+ let check = new URL ( value ) ;
290+
291+ return undefined ;
292+ } catch ( err ) {
293+ return "The thumbnail URL must be a valid absolute URL" ;
294+ }
295+ }
296+ } }
297+ children = { ( field ) => {
298+ let invalid_color = ! field . state . meta . isValid ? "failure" : undefined ;
299+
300+ return < div className = "space-y-2" >
301+ < Label htmlFor = { field . name } color = { invalid_color } >
302+ Youtube Thumbnail
303+ </ Label >
304+ < TextInput
305+ id = { field . name }
306+ name = { field . name }
307+ value = { field . state . value }
308+ onBlur = { field . handleBlur }
309+ onChange = { ev => field . handleChange ( ev . target . value ) }
310+ color = { invalid_color }
311+ />
312+ < HelperText >
313+ { field . state . meta . errors . length !== 0 ?
314+ field . state . meta . errors . join ( ", " ) + ". "
315+ :
316+ "The full url of the video thumbnail."
317+ }
318+ </ HelperText >
319+ </ div > ;
320+ } }
321+ />
322+ </ >
323+ :
324+ < form . Field name = "youtube_id" children = { ( field ) => {
325+ return < VideoSelect required youtube_id = { field . state . value } onSelectVideo = { ( id , thumbnail ) => {
326+ field . handleChange ( id ) ;
327+ // have to use this since the id and thunbnail are tied to the same input
328+ form . setFieldValue ( "youtube_thumbnail" , thumbnail ) ;
329+ } } />
330+ } } />
331+ }
267332 < form . Field name = "topic" children = { field => {
268333 return < div className = "space-y-2" >
269334 < Label htmlFor = { field . name } >
@@ -482,4 +547,4 @@ function SortableExerciseItem({uid, prompt, on_remove}: SortableExerciseItemProp
482547 < TrashIcon className = "h-6 w-6" />
483548 </ Button >
484549 </ div >
485- }
550+ }
0 commit comments