@learnpack/learnpack 5.0.68 → 5.0.70
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/README.md +13 -13
- package/lib/creatorDist/assets/{index-B01XTAAq.js → index-Chx6V3zd.js} +15989 -15785
- package/{src/creatorDist/assets/index-t6ma_gVm.css → lib/creatorDist/assets/index-Dqo9u2iR.css} +233 -35
- package/lib/creatorDist/index.html +2 -2
- package/lib/creatorDist/rigo-float.gif +0 -0
- package/lib/utils/api.js +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/public/rigo-float.gif +0 -0
- package/src/creator/src/App.tsx +202 -163
- package/src/creator/src/assets/svgs.tsx +41 -1
- package/src/creator/src/components/LessonItem.tsx +10 -3
- package/src/creator/src/components/Message.tsx +1 -1
- package/src/creator/src/components/StepWizard.tsx +32 -18
- package/src/creator/src/components/syllabus/ContentIndex.tsx +202 -0
- package/src/creator/src/components/syllabus/Sidebar.tsx +123 -0
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +134 -0
- package/src/creator/src/index.css +2 -6
- package/src/creator/src/main.tsx +1 -1
- package/src/creator/src/utils/store.ts +13 -4
- package/src/creatorDist/assets/{index-B01XTAAq.js → index-Chx6V3zd.js} +15989 -15785
- package/{lib/creatorDist/assets/index-t6ma_gVm.css → src/creatorDist/assets/index-Dqo9u2iR.css} +233 -35
- package/src/creatorDist/index.html +2 -2
- package/src/creatorDist/rigo-float.gif +0 -0
- package/src/utils/api.ts +2 -2
- package/src/creator/src/components/SyllabusEditor.tsx +0 -300
@@ -0,0 +1,202 @@
|
|
1
|
+
import { useEffect, useState, useRef } from "react"
|
2
|
+
import useStore, { Syllabus } from "../../utils/store"
|
3
|
+
import { Lesson, LessonItem } from "../LessonItem"
|
4
|
+
import { SVGS } from "../../assets/svgs"
|
5
|
+
import { TMessage } from "../Message"
|
6
|
+
|
7
|
+
const ContentIndexHeader = ({
|
8
|
+
messages,
|
9
|
+
syllabus,
|
10
|
+
}: {
|
11
|
+
messages: TMessage[]
|
12
|
+
syllabus: Syllabus
|
13
|
+
}) => {
|
14
|
+
return (
|
15
|
+
<div>
|
16
|
+
<h2 className="text-lg font-semibold">
|
17
|
+
{messages.filter((m) => m.type === "assistant" && m.content.length > 0)
|
18
|
+
.length === 0
|
19
|
+
? "I've created a detailed structure for your course."
|
20
|
+
: "I've updated the structure based on your feedback."}
|
21
|
+
</h2>
|
22
|
+
<p className="text-sm text-gray-600">
|
23
|
+
{messages.filter((m) => m.type === "assistant" && m.content.length > 0)
|
24
|
+
.length === 0
|
25
|
+
? `It includes a mix of reading, coding exercises, and quizzes. Give
|
26
|
+
it a look and let me know if it aligns with your expectations or if
|
27
|
+
there are any changes you'd like to make.`
|
28
|
+
: "Based on your input, here is the new syllabus, updates are highlighted in yellow"}
|
29
|
+
</p>
|
30
|
+
<h3 className="text-sm text-gray-600 mt-2 font-bold">
|
31
|
+
{syllabus.courseInfo.title}
|
32
|
+
</h3>
|
33
|
+
</div>
|
34
|
+
)
|
35
|
+
}
|
36
|
+
|
37
|
+
export const GenerateButton = ({
|
38
|
+
handleSubmit,
|
39
|
+
}: {
|
40
|
+
handleSubmit: () => void
|
41
|
+
}) => {
|
42
|
+
return (
|
43
|
+
<div className="flex justify-end mt-6">
|
44
|
+
<button
|
45
|
+
onClick={async () => {
|
46
|
+
handleSubmit()
|
47
|
+
}}
|
48
|
+
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
|
49
|
+
>
|
50
|
+
<span>I'm ready. Create the course for me!</span>
|
51
|
+
{SVGS.rigoSoftBlue}
|
52
|
+
</button>
|
53
|
+
</div>
|
54
|
+
)
|
55
|
+
}
|
56
|
+
export const ContentIndex = ({
|
57
|
+
prevLessons,
|
58
|
+
handleSubmit,
|
59
|
+
messages,
|
60
|
+
}: {
|
61
|
+
prevLessons?: Lesson[]
|
62
|
+
handleSubmit: () => void
|
63
|
+
messages: TMessage[]
|
64
|
+
}) => {
|
65
|
+
const syllabus = useStore((state) => state.syllabus)
|
66
|
+
const setSyllabus = useStore((state) => state.setSyllabus)
|
67
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
68
|
+
const [showScrollHint, setShowScrollHint] = useState(false)
|
69
|
+
|
70
|
+
const handleRemove = (lesson: Lesson) => {
|
71
|
+
setSyllabus({
|
72
|
+
...syllabus,
|
73
|
+
lessons: syllabus.lessons.filter(
|
74
|
+
(l) => l.id !== lesson.id && l.title !== lesson.title
|
75
|
+
),
|
76
|
+
})
|
77
|
+
}
|
78
|
+
|
79
|
+
const handleChange = (id: string, newTitle: string) => {
|
80
|
+
setSyllabus({
|
81
|
+
...syllabus,
|
82
|
+
lessons: syllabus.lessons.map((lesson) =>
|
83
|
+
lesson.id === id ? { ...lesson, title: newTitle } : lesson
|
84
|
+
),
|
85
|
+
})
|
86
|
+
}
|
87
|
+
|
88
|
+
const addLessonAfter = (index: number, id: string) => {
|
89
|
+
const newLesson: Lesson = {
|
90
|
+
id: (parseFloat(id) + 0.1).toFixed(1),
|
91
|
+
title: "Hello World",
|
92
|
+
type: "READ",
|
93
|
+
duration: 2,
|
94
|
+
description: "Hello World",
|
95
|
+
}
|
96
|
+
const updated = [...syllabus.lessons]
|
97
|
+
updated.splice(index + 1, 0, newLesson)
|
98
|
+
|
99
|
+
setSyllabus({
|
100
|
+
lessons: updated,
|
101
|
+
})
|
102
|
+
}
|
103
|
+
|
104
|
+
useEffect(() => {
|
105
|
+
const container = containerRef.current
|
106
|
+
|
107
|
+
const checkScroll = () => {
|
108
|
+
if (container) {
|
109
|
+
const nearBottom =
|
110
|
+
container.scrollHeight >
|
111
|
+
container.clientHeight + container.scrollTop + 100
|
112
|
+
|
113
|
+
setShowScrollHint(nearBottom)
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
checkScroll()
|
118
|
+
container?.addEventListener("scroll", checkScroll)
|
119
|
+
return () => container?.removeEventListener("scroll", checkScroll)
|
120
|
+
}, [syllabus.lessons])
|
121
|
+
|
122
|
+
const scrollToBottom = (target: "bottom" | "continue") => {
|
123
|
+
if (target === "continue") {
|
124
|
+
const container = containerRef.current
|
125
|
+
if (container) {
|
126
|
+
const scrollStep = container.clientHeight * 0.8
|
127
|
+
container.scrollBy({ top: scrollStep, behavior: "smooth" })
|
128
|
+
}
|
129
|
+
} else {
|
130
|
+
const container = containerRef.current
|
131
|
+
if (container) {
|
132
|
+
container.scrollTo({ top: container.scrollHeight, behavior: "smooth" })
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
return (
|
138
|
+
<div className="relative ">
|
139
|
+
<ContentIndexHeader messages={messages} syllabus={syllabus} />
|
140
|
+
<div
|
141
|
+
ref={containerRef}
|
142
|
+
className=" space-y-3 overflow-y-auto max-h-[70vh] pr-2 scrollbar-hide relative pb-5"
|
143
|
+
>
|
144
|
+
{syllabus.lessons.map((lesson, index) => (
|
145
|
+
<div key={lesson.id}>
|
146
|
+
<LessonItem
|
147
|
+
key={lesson.id + index + lesson.title}
|
148
|
+
lesson={lesson}
|
149
|
+
onChange={handleChange}
|
150
|
+
onRemove={() => handleRemove(lesson)}
|
151
|
+
isNew={Boolean(
|
152
|
+
prevLessons &&
|
153
|
+
prevLessons.length > 0 &&
|
154
|
+
!prevLessons.some(
|
155
|
+
(l) =>
|
156
|
+
l.id === lesson.id &&
|
157
|
+
l.title === lesson.title &&
|
158
|
+
l.type === lesson.type
|
159
|
+
)
|
160
|
+
)}
|
161
|
+
/>
|
162
|
+
<div className="relative h-6">
|
163
|
+
<div className="absolute left-1/2 -translate-x-1/2 -top-3">
|
164
|
+
<button
|
165
|
+
onClick={() => addLessonAfter(index, lesson.id)}
|
166
|
+
className="w-6 h-6 flex items-center justify-center bg-blue-100 text-blue-600 rounded hover:bg-blue-200 shadow-sm text-sm font-semibold cursor-pointer"
|
167
|
+
>
|
168
|
+
+
|
169
|
+
</button>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
))}
|
174
|
+
<GenerateButton handleSubmit={handleSubmit} />
|
175
|
+
</div>
|
176
|
+
|
177
|
+
{showScrollHint && (
|
178
|
+
<div className="pointer-events-none relative">
|
179
|
+
<div className="absolute bottom-0 left-0 w-full h-20 bg-gradient-to-t from-white to-transparent z-10" />
|
180
|
+
<div className="absolute bottom-3 left-0 w-full flex justify-center z-20">
|
181
|
+
<button
|
182
|
+
style={{ color: "#0084FF" }}
|
183
|
+
onClick={() => scrollToBottom("continue")}
|
184
|
+
className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
|
185
|
+
>
|
186
|
+
Continue scrolling
|
187
|
+
{SVGS.downArrow}
|
188
|
+
</button>
|
189
|
+
<button
|
190
|
+
style={{ color: "#0084FF" }}
|
191
|
+
onClick={() => scrollToBottom("bottom")}
|
192
|
+
className="px-4 py-1 bg-white text-sm rounded shadow hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
|
193
|
+
>
|
194
|
+
Scroll to bottom
|
195
|
+
{SVGS.bottom}
|
196
|
+
</button>
|
197
|
+
</div>
|
198
|
+
</div>
|
199
|
+
)}
|
200
|
+
</div>
|
201
|
+
)
|
202
|
+
}
|
@@ -0,0 +1,123 @@
|
|
1
|
+
import { useRef, useState } from "react"
|
2
|
+
import useStore from "../../utils/store"
|
3
|
+
import { TMessage } from "../Message"
|
4
|
+
import FileUploader from "../FileUploader"
|
5
|
+
import { SVGS } from "../../assets/svgs"
|
6
|
+
import { Message } from "../Message"
|
7
|
+
|
8
|
+
export const Sidebar = ({
|
9
|
+
messages,
|
10
|
+
sendPrompt,
|
11
|
+
handleSubmit,
|
12
|
+
}: {
|
13
|
+
messages: TMessage[]
|
14
|
+
sendPrompt: (prompt: string) => void
|
15
|
+
handleSubmit: () => void
|
16
|
+
}) => {
|
17
|
+
const inputRef = useRef<HTMLTextAreaElement>(null)
|
18
|
+
const syllabus = useStore((state) => state.syllabus)
|
19
|
+
const setSyllabus = useStore((state) => state.setSyllabus)
|
20
|
+
|
21
|
+
const [isOpen, setIsOpen] = useState(false)
|
22
|
+
|
23
|
+
return (
|
24
|
+
<>
|
25
|
+
{!isOpen && (
|
26
|
+
<button
|
27
|
+
className="fixed top-2 left-2 z-50 lg:hidden bg-white p-1 rounded shadow-md cursor-pointer"
|
28
|
+
onClick={() => setIsOpen(true)}
|
29
|
+
>
|
30
|
+
☰
|
31
|
+
</button>
|
32
|
+
)}
|
33
|
+
|
34
|
+
<div
|
35
|
+
className={`fixed z-40 top-0 left-0 h-full w-4/5 max-w-sm bg-learnpack-blue text-sm text-gray-700 border-r overflow-y-auto scrollbar-hide p-6 transition-transform duration-300 ease-in-out lg:relative lg:transform-none lg:w-1/3 ${
|
36
|
+
isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
37
|
+
}`}
|
38
|
+
>
|
39
|
+
{isOpen && (
|
40
|
+
<button
|
41
|
+
className="lg:hidden bg-white p-1 rounded shadow-md mb-4 cursor-pointer absolute top-2 left-2"
|
42
|
+
onClick={() => setIsOpen(false)}
|
43
|
+
>
|
44
|
+
✕
|
45
|
+
</button>
|
46
|
+
)}
|
47
|
+
{/* This should have the same width as the input area */}
|
48
|
+
<div className="space-y-2 mb-6 ">
|
49
|
+
<p className="w-full bg-white p-2 rounded">
|
50
|
+
If you're satisfied, type "OK" in the chat.
|
51
|
+
</p>
|
52
|
+
<p className="w-full bg-white p-2 rounded">
|
53
|
+
If not, use the chat to give more context.
|
54
|
+
</p>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<div className="space-y-2 pb-32 h-[70%] overflow-y-auto scrollbar-hide">
|
58
|
+
{messages.map((message, index) => (
|
59
|
+
<Message
|
60
|
+
key={index}
|
61
|
+
type={message.type}
|
62
|
+
content={message.content}
|
63
|
+
/>
|
64
|
+
))}
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div className="relative w-full rounded-md bg-white text-gray-700 h-24">
|
68
|
+
<textarea
|
69
|
+
ref={inputRef}
|
70
|
+
style={{ resize: "none" }}
|
71
|
+
className="w-full h-full p-2"
|
72
|
+
placeholder="How can Learnpack help you?"
|
73
|
+
autoFocus
|
74
|
+
onKeyUp={(e) => {
|
75
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
76
|
+
e.preventDefault()
|
77
|
+
sendPrompt(inputRef.current?.value || "")
|
78
|
+
inputRef.current!.value = ""
|
79
|
+
}
|
80
|
+
if (
|
81
|
+
e.key.toLowerCase() === "k" &&
|
82
|
+
inputRef.current?.value.toLowerCase().trim() === "ok"
|
83
|
+
) {
|
84
|
+
e.preventDefault()
|
85
|
+
handleSubmit()
|
86
|
+
}
|
87
|
+
}}
|
88
|
+
/>
|
89
|
+
<div className="absolute bottom-2 right-2 flex gap-1 items-center">
|
90
|
+
<div className="relative inline-block">
|
91
|
+
{syllabus.uploadedFiles?.length > 0 && (
|
92
|
+
<span
|
93
|
+
className="absolute -top-1 right-0 inline-flex items-center justify-center w-3 h-3 text-[10px] text-white bg-blue-200 rounded-full hover:bg-red-300 cursor-pointer"
|
94
|
+
title="Remove uploaded files"
|
95
|
+
onClick={() => {
|
96
|
+
setSyllabus({ ...syllabus, uploadedFiles: [] })
|
97
|
+
}}
|
98
|
+
>
|
99
|
+
{syllabus.uploadedFiles?.length}
|
100
|
+
</span>
|
101
|
+
)}
|
102
|
+
<FileUploader
|
103
|
+
onResult={(res) => {
|
104
|
+
setSyllabus({
|
105
|
+
...syllabus,
|
106
|
+
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
107
|
+
})
|
108
|
+
}}
|
109
|
+
/>
|
110
|
+
</div>
|
111
|
+
|
112
|
+
<button
|
113
|
+
className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
|
114
|
+
onClick={() => sendPrompt(inputRef.current?.value || "")}
|
115
|
+
>
|
116
|
+
{SVGS.send}
|
117
|
+
</button>
|
118
|
+
</div>
|
119
|
+
</div>
|
120
|
+
</div>
|
121
|
+
</>
|
122
|
+
)
|
123
|
+
}
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import React, { useRef, useState } from "react"
|
2
|
+
import { useShallow } from "zustand/react/shallow"
|
3
|
+
import useStore from "../../utils/store"
|
4
|
+
import { interactiveCreation } from "../../utils/rigo"
|
5
|
+
import {
|
6
|
+
parseLesson,
|
7
|
+
uploadFileToBucket,
|
8
|
+
useConsumableCall,
|
9
|
+
} from "../../utils/lib"
|
10
|
+
import {
|
11
|
+
createLearnJson,
|
12
|
+
processExercise,
|
13
|
+
slugify,
|
14
|
+
randomUUID,
|
15
|
+
} from "../../utils/creatorUtils"
|
16
|
+
|
17
|
+
import Loader from "../Loader"
|
18
|
+
import { TMessage } from "../Message"
|
19
|
+
import { Lesson } from "../LessonItem"
|
20
|
+
import { ConsumablesManager } from "../ConsumablesManager"
|
21
|
+
import toast from "react-hot-toast"
|
22
|
+
import { ContentIndex } from "./ContentIndex"
|
23
|
+
import { Sidebar } from "./Sidebar"
|
24
|
+
|
25
|
+
const SyllabusEditor: React.FC = () => {
|
26
|
+
const [messages, setMessages] = useState<TMessage[]>([])
|
27
|
+
const [isGenerating, setIsGenerating] = useState(false)
|
28
|
+
const prevLessons = useRef<Lesson[]>([])
|
29
|
+
const { syllabus, setSyllabus, auth } = useStore(
|
30
|
+
useShallow((state) => ({
|
31
|
+
syllabus: state.syllabus,
|
32
|
+
setSyllabus: state.setSyllabus,
|
33
|
+
auth: state.auth,
|
34
|
+
}))
|
35
|
+
)
|
36
|
+
|
37
|
+
const sendPrompt = async (prompt: string) => {
|
38
|
+
setMessages([
|
39
|
+
...messages,
|
40
|
+
{ type: "user", content: prompt },
|
41
|
+
{ type: "assistant", content: "" },
|
42
|
+
])
|
43
|
+
prevLessons.current = syllabus.lessons
|
44
|
+
const res = await interactiveCreation(auth.rigoToken, {
|
45
|
+
courseInfo: JSON.stringify(syllabus),
|
46
|
+
prevInteractions:
|
47
|
+
messages
|
48
|
+
.map((message) => `${message.type}: ${message.content}`)
|
49
|
+
.join("\n") + `\nUSER: ${prompt}`,
|
50
|
+
})
|
51
|
+
console.log(res, "RES")
|
52
|
+
const lessons: Lesson[] = res.parsed.listOfSteps.map((step: any) =>
|
53
|
+
parseLesson(step)
|
54
|
+
)
|
55
|
+
setSyllabus({
|
56
|
+
...syllabus,
|
57
|
+
lessons: lessons,
|
58
|
+
courseInfo: {
|
59
|
+
...syllabus.courseInfo,
|
60
|
+
title: res.parsed.title || syllabus.courseInfo.title,
|
61
|
+
},
|
62
|
+
})
|
63
|
+
setMessages((prev) => {
|
64
|
+
const newMessages = [...prev]
|
65
|
+
newMessages[newMessages.length - 1].content = res.parsed.aiMessage
|
66
|
+
return newMessages
|
67
|
+
})
|
68
|
+
}
|
69
|
+
|
70
|
+
const handleSubmit = async () => {
|
71
|
+
const success = await useConsumableCall(auth.bcToken, "ai-generation")
|
72
|
+
if (!success) {
|
73
|
+
toast.error("You don't have enough credits to generate a course!")
|
74
|
+
return
|
75
|
+
}
|
76
|
+
setIsGenerating(true)
|
77
|
+
|
78
|
+
const lessonsPromises = syllabus.lessons.map((lesson) =>
|
79
|
+
processExercise(
|
80
|
+
auth.rigoToken,
|
81
|
+
syllabus.lessons,
|
82
|
+
JSON.stringify(syllabus.courseInfo),
|
83
|
+
lesson,
|
84
|
+
"courses/" +
|
85
|
+
slugify(syllabus.courseInfo.title || randomUUID()) +
|
86
|
+
"/exercises"
|
87
|
+
)
|
88
|
+
)
|
89
|
+
await Promise.all(lessonsPromises)
|
90
|
+
|
91
|
+
const learnJson = createLearnJson(syllabus.courseInfo)
|
92
|
+
await uploadFileToBucket(
|
93
|
+
JSON.stringify(learnJson),
|
94
|
+
"courses/" +
|
95
|
+
slugify(syllabus.courseInfo.title || randomUUID()) +
|
96
|
+
"/learn.json"
|
97
|
+
)
|
98
|
+
setIsGenerating(false)
|
99
|
+
|
100
|
+
window.location.href = `/?slug=${slugify(
|
101
|
+
syllabus.courseInfo.title || "exercises"
|
102
|
+
)}&token=${auth.bcToken}`
|
103
|
+
}
|
104
|
+
|
105
|
+
return isGenerating ? (
|
106
|
+
<Loader
|
107
|
+
listeningTo="course-generation"
|
108
|
+
icon={<img src={"rigo-float.gif"} alt="rigo" className="w-20 h-20" />}
|
109
|
+
initialBuffer="🚀 Starting course generation..."
|
110
|
+
text="Learnpack is setting up your tutorial.
|
111
|
+
It may take a moment..."
|
112
|
+
/>
|
113
|
+
) : (
|
114
|
+
<div className="flex w-full bg-white rounded-md shadow-md overflow-hidden h-screen ">
|
115
|
+
<ConsumablesManager />
|
116
|
+
|
117
|
+
<Sidebar
|
118
|
+
messages={messages}
|
119
|
+
sendPrompt={sendPrompt}
|
120
|
+
handleSubmit={handleSubmit}
|
121
|
+
/>
|
122
|
+
|
123
|
+
<div className="w-full p-8 space-y-6">
|
124
|
+
<ContentIndex
|
125
|
+
prevLessons={prevLessons.current}
|
126
|
+
handleSubmit={handleSubmit}
|
127
|
+
messages={messages}
|
128
|
+
/>
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
)
|
132
|
+
}
|
133
|
+
|
134
|
+
export default SyllabusEditor
|
@@ -88,15 +88,12 @@ h1 {
|
|
88
88
|
.loader-icon {
|
89
89
|
width: 40px;
|
90
90
|
height: 40px;
|
91
|
-
|
92
|
-
border: 2px solid var(--loader-color);
|
93
|
-
border-top-color: transparent;
|
94
91
|
position: relative;
|
95
92
|
display: flex;
|
96
93
|
align-items: center;
|
97
94
|
justify-content: center;
|
98
95
|
}
|
99
|
-
.loader-icon::after {
|
96
|
+
/* .loader-icon::after {
|
100
97
|
content: "";
|
101
98
|
display: block;
|
102
99
|
width: 100%;
|
@@ -106,11 +103,10 @@ h1 {
|
|
106
103
|
height: 100%;
|
107
104
|
border: 2px solid var(--gray-text);
|
108
105
|
border-top: 2px solid var(--learnpack-blue);
|
109
|
-
/* background-color: red; */
|
110
106
|
|
111
107
|
border-radius: 50%;
|
112
108
|
animation: spin 2s linear infinite;
|
113
|
-
}
|
109
|
+
} */
|
114
110
|
|
115
111
|
@keyframes glowing {
|
116
112
|
0% {
|
package/src/creator/src/main.tsx
CHANGED
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client"
|
|
3
3
|
import { BrowserRouter, Route, Routes } from "react-router"
|
4
4
|
import "./index.css"
|
5
5
|
import App from "./App.tsx"
|
6
|
-
import SyllabusEditor from "./components/SyllabusEditor.tsx"
|
6
|
+
import SyllabusEditor from "./components/syllabus/SyllabusEditor.tsx"
|
7
7
|
import { Toaster } from "react-hot-toast"
|
8
8
|
|
9
9
|
createRoot(document.getElementById("root")!).render(
|
@@ -9,7 +9,8 @@ export type FormState = {
|
|
9
9
|
hasContentIndex: boolean
|
10
10
|
contentIndex: string
|
11
11
|
isCompleted: boolean
|
12
|
-
|
12
|
+
variables: string[]
|
13
|
+
currentStep: string
|
13
14
|
title?: string
|
14
15
|
}
|
15
16
|
|
@@ -18,7 +19,7 @@ type Auth = {
|
|
18
19
|
rigoToken: string
|
19
20
|
userId: string
|
20
21
|
}
|
21
|
-
type Syllabus = {
|
22
|
+
export type Syllabus = {
|
22
23
|
lessons: Lesson[]
|
23
24
|
courseInfo: FormState
|
24
25
|
uploadedFiles: {
|
@@ -57,7 +58,14 @@ const useStore = create<Store>()(
|
|
57
58
|
hasContentIndex: false,
|
58
59
|
contentIndex: "",
|
59
60
|
isCompleted: false,
|
60
|
-
currentStep:
|
61
|
+
currentStep: "description",
|
62
|
+
variables: [
|
63
|
+
"description",
|
64
|
+
"duration",
|
65
|
+
"login",
|
66
|
+
"targetAudience",
|
67
|
+
"hasContentIndex",
|
68
|
+
],
|
61
69
|
},
|
62
70
|
setFormState: (formState: Partial<FormState>) =>
|
63
71
|
set((state) => ({ formState: { ...state.formState, ...formState } })),
|
@@ -71,8 +79,9 @@ const useStore = create<Store>()(
|
|
71
79
|
hasContentIndex: false,
|
72
80
|
contentIndex: "",
|
73
81
|
isCompleted: false,
|
74
|
-
currentStep:
|
82
|
+
currentStep: "description",
|
75
83
|
title: "",
|
84
|
+
variables: [],
|
76
85
|
},
|
77
86
|
uploadedFiles: [],
|
78
87
|
},
|