@@ -3,7 +3,8 @@ import { json, redirect } from "@remix-run/node";
3
3
import { Form , useActionData , useNavigation } from "@remix-run/react" ;
4
4
import { requireUserId } from "~/session.server" ;
5
5
import { createCourse } from "~/models/course.server" ;
6
- import React from 'react' ;
6
+ import React , { useState } from 'react' ;
7
+ import { DragDropContext , Droppable , Draggable } from 'react-beautiful-dnd' ;
7
8
8
9
export const loader = async ( { request } : LoaderFunctionArgs ) => {
9
10
await requireUserId ( request ) ;
@@ -15,62 +16,175 @@ export const action = async ({ request }: ActionFunctionArgs) => {
15
16
const formData = await request . formData ( ) ;
16
17
const title = formData . get ( "title" ) as string ;
17
18
const description = formData . get ( "description" ) as string | null ;
18
-
19
+ const structure = JSON . parse ( formData . get ( "structure" ) as string ) || [ ] ;
20
+ console . log ( 'Structure from form data:' , structure ) ;
21
+
19
22
if ( ! title ) {
20
23
return json ( { error : "Title is required" } , { status : 400 } ) ;
21
24
}
22
25
23
26
try {
24
- await createCourse ( { title, description : description || "" , userId } ) ;
27
+ console . log ( 'Structure being passed to createCourse:' , structure ) ;
28
+ console . log ( 'Structure being passed to createCourse:' , structure ) ;
29
+ await createCourse ( { title, description : description || "" , userId, structure } ) ;
25
30
return redirect ( "/courses" ) ;
26
31
} catch ( error ) {
27
32
return json ( { error : "Failed to create course" } , { status : 500 } ) ;
28
33
}
29
34
} ;
30
35
36
+ type CourseItem = {
37
+ id : string ;
38
+ content : string ;
39
+ type : 'topic' | 'lesson' | 'quiz' ;
40
+ } ;
41
+
31
42
export default function NewCoursePage ( ) {
32
43
const actionData = useActionData < typeof action > ( ) ;
33
44
const navigation = useNavigation ( ) ;
45
+ const [ courseTitle , setCourseTitle ] = useState ( '' ) ;
46
+ const [ courseDescription , setCourseDescription ] = useState ( '' ) ;
47
+ const [ courseItems , setCourseItems ] = useState < CourseItem [ ] > ( [ ] ) ;
48
+ const [ points , setPoints ] = useState ( 0 ) ; // Gamification: Points for actions
49
+
50
+ const addItem = ( type : CourseItem [ 'type' ] ) => {
51
+ const newItem : CourseItem = {
52
+ id : `item-${ Date . now ( ) } ` ,
53
+ content : `New ${ type . charAt ( 0 ) . toUpperCase ( ) + type . slice ( 1 ) } ` ,
54
+ type,
55
+ } ;
56
+ setCourseItems ( [ ...courseItems , newItem ] ) ;
57
+ setPoints ( points + 10 ) ; // Add points for creating new content
58
+ } ;
59
+
60
+ const onDragEnd = ( result : any ) => {
61
+ if ( ! result . destination ) return ;
62
+
63
+ const newItems = Array . from ( courseItems ) ;
64
+ const [ reorderedItem ] = newItems . splice ( result . source . index , 1 ) ;
65
+ newItems . splice ( result . destination . index , 0 , reorderedItem ) ;
66
+
67
+ setCourseItems ( newItems ) ;
68
+ setPoints ( points + 5 ) ; // Add points for reordering
69
+ } ;
34
70
35
71
return (
36
- < div className = "flex-1 p-6" >
37
- < h1 className = "text-2xl font-bold mb-4" > New Course</ h1 >
38
- < Form method = "post" className = "space-y-4 max-w-md" >
72
+ < div className = "flex-1 p-6 bg-gray-100 min-h-screen" >
73
+ < h1 className = "text-3xl font-bold mb-6 text-gray-800" > Create New Course</ h1 >
74
+ < Form method = "post" className = "space-y-6 max-w-2xl bg-white p-6 rounded-lg shadow-md" onSubmit = { ( ) => {
75
+ const formData = new FormData ( ) ;
76
+ formData . append ( 'title' , courseTitle ) ;
77
+ formData . append ( 'description' , courseDescription ) ;
78
+ formData . append ( 'structure' , JSON . stringify ( courseItems ) ) ;
79
+ } } >
39
80
< div >
40
- < label htmlFor = "title" className = "block text-sm font-medium text-gray-700" >
41
- Title
81
+ < label htmlFor = "title" className = "block text-lg font-medium text-gray-700 mb-2 " >
82
+ Course Title
42
83
</ label >
43
84
< input
44
85
type = "text"
45
86
id = "title"
46
87
name = "title"
47
88
required
48
- className = "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
49
- defaultValue = ""
89
+ value = { courseTitle }
90
+ onChange = { ( e ) => setCourseTitle ( e . target . value ) }
91
+ className = "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 p-2"
92
+ placeholder = "Enter course title"
50
93
/>
51
94
</ div >
52
95
53
96
< div >
54
- < label htmlFor = "description" className = "block text-sm font-medium text-gray-700" >
97
+ < label htmlFor = "description" className = "block text-lg font-medium text-gray-700 mb-2 " >
55
98
Description (Optional)
56
99
</ label >
57
100
< textarea
58
101
id = "description"
59
102
name = "description"
60
103
rows = { 4 }
61
- className = "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
62
- defaultValue = ""
104
+ value = { courseDescription }
105
+ onChange = { ( e ) => setCourseDescription ( e . target . value ) }
106
+ className = "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 p-2"
107
+ placeholder = "Enter course description"
63
108
/>
64
109
</ div >
65
110
111
+ < div className = "mb-6" >
112
+ < h2 className = "text-xl font-semibold text-gray-700 mb-4" > Course Structure</ h2 >
113
+ < div className = "space-y-4" >
114
+ < button
115
+ type = "button"
116
+ onClick = { ( ) => addItem ( 'topic' ) }
117
+ className = "bg-green-500 text-white py-2 px-4 rounded hover:bg-green-600"
118
+ >
119
+ Add Topic
120
+ </ button >
121
+ < button
122
+ type = "button"
123
+ onClick = { ( ) => addItem ( 'lesson' ) }
124
+ className = "bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 ml-2"
125
+ >
126
+ Add Lesson
127
+ </ button >
128
+ < button
129
+ type = "button"
130
+ onClick = { ( ) => addItem ( 'quiz' ) }
131
+ className = "bg-purple-500 text-white py-2 px-4 rounded hover:bg-purple-600 ml-2"
132
+ >
133
+ Add Quiz
134
+ </ button >
135
+ </ div >
136
+
137
+ < DragDropContext onDragEnd = { onDragEnd } >
138
+ < Droppable droppableId = "droppable" >
139
+ { ( provided ) => (
140
+ < div
141
+ { ...provided . droppableProps }
142
+ ref = { provided . innerRef }
143
+ className = "mt-4 space-y-4"
144
+ >
145
+ { courseItems . map ( ( item , index ) => (
146
+ < Draggable key = { item . id } draggableId = { item . id } index = { index } >
147
+ { ( provided ) => (
148
+ < div
149
+ ref = { provided . innerRef }
150
+ { ...provided . draggableProps }
151
+ { ...provided . dragHandleProps }
152
+ className = "p-4 bg-gray-200 rounded shadow-md flex justify-between items-center"
153
+ >
154
+ < span > { item . content } </ span >
155
+ < button
156
+ type = "button"
157
+ onClick = { ( ) => setCourseItems ( courseItems . filter ( i => i . id !== item . id ) ) }
158
+ className = "text-red-500 hover:text-red-700"
159
+ >
160
+ Remove
161
+ </ button >
162
+ </ div >
163
+ ) }
164
+ </ Draggable >
165
+ ) ) }
166
+ { provided . placeholder }
167
+ </ div >
168
+ ) }
169
+ </ Droppable >
170
+ </ DragDropContext >
171
+ </ div >
172
+
173
+ { /* Gamification Display */ }
174
+ < div className = "mt-6 p-4 bg-yellow-100 rounded-lg" >
175
+ < h3 className = "text-lg font-semibold text-gray-800" > Your Progress</ h3 >
176
+ < p className = "text-gray-700" > Points: { points } </ p >
177
+ { points >= 50 && < span className = "text-green-600" > Badge Unlocked: Course Creator!</ span > }
178
+ </ div >
179
+
66
180
{ actionData ?. error && (
67
181
< p className = "text-red-500 text-sm" > { actionData . error } </ p >
68
182
) }
69
183
70
184
< button
71
185
type = "submit"
72
186
disabled = { navigation . state === "submitting" }
73
- className = "w-full py-2 px-4 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
187
+ className = "w-full py-3 px-6 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition duration-300 "
74
188
>
75
189
{ navigation . state === "submitting" ? "Creating..." : "Create Course" }
76
190
</ button >
0 commit comments