@learnpack/learnpack 5.0.83 → 5.0.85
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-CwPh6b6M.js → index-D15TgYvM.js} +38846 -31631
- package/lib/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
- package/lib/creatorDist/index.html +2 -2
- package/lib/utils/api.js +0 -8
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/creator/package-lock.json +1229 -61
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +12 -6
- package/src/creator/src/components/FileCard.tsx +22 -0
- package/src/creator/src/components/LessonItem.tsx +20 -9
- package/src/creator/src/components/Login.tsx +6 -24
- package/src/creator/src/components/MarkdownRenderer.tsx +11 -0
- package/src/creator/src/components/Message.tsx +4 -2
- package/src/creator/src/components/syllabus/ContentIndex.tsx +45 -64
- package/src/creator/src/components/syllabus/Sidebar.tsx +21 -19
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +62 -33
- package/src/creator/src/index.css +40 -0
- package/src/creator/src/utils/creatorUtils.ts +5 -1
- package/src/creator/src/utils/lib.ts +20 -10
- package/src/creator/src/utils/store.ts +31 -22
- package/src/creatorDist/assets/{index-CwPh6b6M.js → index-D15TgYvM.js} +38846 -31631
- package/src/creatorDist/assets/{index-DSLkFVbA.css → index-ldEC0yWM.css} +125 -46
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +344 -344
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +0 -9
package/src/creator/package.json
CHANGED
package/src/creator/src/App.tsx
CHANGED
@@ -17,13 +17,12 @@ import FileUploader from "./components/FileUploader"
|
|
17
17
|
function App() {
|
18
18
|
const navigate = useNavigate()
|
19
19
|
|
20
|
-
const { formState, setFormState,
|
20
|
+
const { formState, setFormState, setAuth, push } = useStore(
|
21
21
|
useShallow((state) => ({
|
22
|
-
// auth: state.auth,
|
23
22
|
formState: state.formState,
|
24
23
|
setFormState: state.setFormState,
|
25
|
-
setSyllabus: state.setSyllabus,
|
26
24
|
setAuth: state.setAuth,
|
25
|
+
push: state.push,
|
27
26
|
}))
|
28
27
|
)
|
29
28
|
|
@@ -74,9 +73,10 @@ function App() {
|
|
74
73
|
prevInteractions: "",
|
75
74
|
})
|
76
75
|
const lessons = res.parsed.listOfSteps.map((lesson: any) => {
|
77
|
-
return parseLesson(lesson)
|
76
|
+
return parseLesson(lesson, [])
|
78
77
|
})
|
79
|
-
|
78
|
+
|
79
|
+
push({
|
80
80
|
lessons,
|
81
81
|
courseInfo: {
|
82
82
|
...formState,
|
@@ -266,7 +266,13 @@ function App() {
|
|
266
266
|
{formState.isCompleted ? (
|
267
267
|
<Loader
|
268
268
|
text="Learnpack is setting up your tutorial. It may take a moment..."
|
269
|
-
icon={
|
269
|
+
icon={
|
270
|
+
<img
|
271
|
+
src={"creator/rigo-float.gif"}
|
272
|
+
alt="rigo"
|
273
|
+
className="w-20 h-20"
|
274
|
+
/>
|
275
|
+
}
|
270
276
|
/>
|
271
277
|
) : (
|
272
278
|
<StepWizard
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { SVGS } from "../assets/svgs"
|
2
|
+
import { UploadedFile } from "../utils/store"
|
3
|
+
|
4
|
+
export const FileCard = ({
|
5
|
+
file,
|
6
|
+
handleRemove,
|
7
|
+
}: {
|
8
|
+
file: UploadedFile
|
9
|
+
handleRemove: () => void
|
10
|
+
}) => (
|
11
|
+
<div className="flex items-center justify-between bg-white shadow-sm p-2 rounded-lg w-[100px]">
|
12
|
+
<span title={file.name} className="text-xs text-gray-800 truncate">
|
13
|
+
{file.name}
|
14
|
+
</span>
|
15
|
+
<button
|
16
|
+
onClick={handleRemove}
|
17
|
+
className="p-1 text-red-500 hover:text-red-700 transition-colors cursor-pointer "
|
18
|
+
>
|
19
|
+
{SVGS.trash}
|
20
|
+
</button>
|
21
|
+
</div>
|
22
|
+
)
|
@@ -1,8 +1,9 @@
|
|
1
|
-
import { useState } from "react"
|
1
|
+
import { useState, useRef } from "react"
|
2
2
|
import { SVGS } from "../assets/svgs"
|
3
3
|
|
4
4
|
export interface Lesson {
|
5
5
|
id: string
|
6
|
+
uid: string
|
6
7
|
title: string
|
7
8
|
type: "READ" | "CODE" | "QUIZ"
|
8
9
|
description: string
|
@@ -12,8 +13,8 @@ export interface Lesson {
|
|
12
13
|
interface LessonItemProps {
|
13
14
|
lesson: Lesson
|
14
15
|
isNew: boolean
|
15
|
-
onChange: (
|
16
|
-
onRemove: (
|
16
|
+
onChange: (uid: string, newTitle: string) => void
|
17
|
+
onRemove: () => void
|
17
18
|
}
|
18
19
|
|
19
20
|
function cleanFloatString(input: string): string {
|
@@ -31,12 +32,13 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
31
32
|
onRemove,
|
32
33
|
isNew,
|
33
34
|
}) => {
|
35
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
34
36
|
const [isEditing, setIsEditing] = useState(false)
|
35
37
|
|
36
38
|
return (
|
37
39
|
<div
|
38
40
|
className={`flex items-center space-x-2 relative rounded-md p-3 ${
|
39
|
-
isNew ? "border border-learnpack-blue" : "border border-gray-200"
|
41
|
+
isNew ? "border border-learnpack-blue appear" : "border border-gray-200"
|
40
42
|
}`}
|
41
43
|
>
|
42
44
|
{isNew && <span className="red-ball"></span>}
|
@@ -47,9 +49,13 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
47
49
|
|
48
50
|
{isEditing ? (
|
49
51
|
<input
|
50
|
-
|
51
|
-
|
52
|
-
onBlur={() =>
|
52
|
+
defaultValue={lesson.title}
|
53
|
+
ref={inputRef}
|
54
|
+
onBlur={() => {
|
55
|
+
if (inputRef.current) {
|
56
|
+
onChange(lesson.uid, inputRef.current.value)
|
57
|
+
}
|
58
|
+
}}
|
53
59
|
autoFocus
|
54
60
|
className="flex-1 bg-white border border-gray-300 rounded-md px-2 py-1 text-sm"
|
55
61
|
/>
|
@@ -62,13 +68,18 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
62
68
|
</span>
|
63
69
|
|
64
70
|
<button
|
65
|
-
onClick={() =>
|
71
|
+
onClick={() => {
|
72
|
+
setIsEditing(!isEditing)
|
73
|
+
if (inputRef.current) {
|
74
|
+
onChange(lesson.uid, inputRef.current.value)
|
75
|
+
}
|
76
|
+
}}
|
66
77
|
className="text-gray-500 hover:text-blue-500 cursor-pointer "
|
67
78
|
>
|
68
79
|
{SVGS.pen}
|
69
80
|
</button>
|
70
81
|
<button
|
71
|
-
onClick={() => onRemove(
|
82
|
+
onClick={() => onRemove()}
|
72
83
|
className="text-red-500 hover:text-red-700 cursor-pointer"
|
73
84
|
>
|
74
85
|
{SVGS.trash}
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import {
|
1
|
+
import { useState } from "react"
|
2
2
|
import { BREATHECODE_HOST } from "../utils/constants"
|
3
3
|
import { SVGS } from "../assets/svgs"
|
4
4
|
import toast from "react-hot-toast"
|
5
5
|
import useStore from "../utils/store"
|
6
6
|
import { useShallow } from "zustand/react/shallow"
|
7
|
-
import {
|
7
|
+
import { login4Geeks } from "../utils/lib"
|
8
8
|
|
9
9
|
export default function Login({ onFinish }: { onFinish: () => void }) {
|
10
10
|
const [email, setEmail] = useState("")
|
@@ -58,24 +58,6 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
58
58
|
window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
|
59
59
|
}
|
60
60
|
|
61
|
-
useEffect(() => {
|
62
|
-
;(async () => {
|
63
|
-
const { token } = checkParams()
|
64
|
-
if (token) {
|
65
|
-
const user = await loginWithToken(token)
|
66
|
-
if (user) {
|
67
|
-
setAuth({
|
68
|
-
bcToken: token,
|
69
|
-
userId: user.id,
|
70
|
-
rigoToken: user.rigobot.key,
|
71
|
-
user,
|
72
|
-
})
|
73
|
-
onFinish()
|
74
|
-
}
|
75
|
-
}
|
76
|
-
})()
|
77
|
-
}, [])
|
78
|
-
|
79
61
|
return (
|
80
62
|
<div
|
81
63
|
className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000"
|
@@ -91,7 +73,7 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
91
73
|
</p>
|
92
74
|
<button
|
93
75
|
onClick={redirectGithub}
|
94
|
-
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4"
|
76
|
+
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
|
95
77
|
>
|
96
78
|
{SVGS.github} LOGIN WITH GITHUB
|
97
79
|
</button>
|
@@ -119,14 +101,14 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
119
101
|
<div className="flex gap-2 mt-4">
|
120
102
|
<button
|
121
103
|
type="submit"
|
122
|
-
className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold"
|
104
|
+
className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold cursor-pointer"
|
123
105
|
>
|
124
106
|
{isLoading ? "Logging in..." : "Log in"}
|
125
107
|
</button>
|
126
108
|
<button
|
127
109
|
type="button"
|
128
110
|
onClick={() => setShowForm(false)}
|
129
|
-
className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold"
|
111
|
+
className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold cursor-pointer"
|
130
112
|
>
|
131
113
|
Skip
|
132
114
|
</button>
|
@@ -144,7 +126,7 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
144
126
|
) : (
|
145
127
|
<button
|
146
128
|
onClick={() => setShowForm(true)}
|
147
|
-
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold"
|
129
|
+
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
|
148
130
|
>
|
149
131
|
Login with Email
|
150
132
|
</button>
|
@@ -2,6 +2,7 @@ import { RigoLoader } from "./RigoLoader"
|
|
2
2
|
|
3
3
|
import { SVGS } from "../assets/svgs"
|
4
4
|
import useStore from "../utils/store"
|
5
|
+
import { MarkdownRenderer } from "./MarkdownRenderer"
|
5
6
|
|
6
7
|
export type TMessage = {
|
7
8
|
type: "user" | "assistant"
|
@@ -14,7 +15,6 @@ export const Message: React.FC<TMessage> = ({ type, content }) => {
|
|
14
15
|
|
15
16
|
const isLoading = isAI && !content
|
16
17
|
|
17
|
-
console.log("user", user)
|
18
18
|
return isLoading ? (
|
19
19
|
<RigoLoader text="Thinking..." svg={<img src="rigo-float.gif" />} />
|
20
20
|
) : (
|
@@ -32,7 +32,9 @@ export const Message: React.FC<TMessage> = ({ type, content }) => {
|
|
32
32
|
) : (
|
33
33
|
<span className="mt-1">{SVGS.user}</span>
|
34
34
|
)}
|
35
|
-
<
|
35
|
+
<div className="w-full break-words overflow-x-auto ">
|
36
|
+
<MarkdownRenderer content={content} />
|
37
|
+
</div>
|
36
38
|
</div>
|
37
39
|
)
|
38
40
|
}
|
@@ -5,6 +5,8 @@ import { SVGS } from "../../assets/svgs"
|
|
5
5
|
import { TMessage } from "../Message"
|
6
6
|
import Loader from "../Loader"
|
7
7
|
import { motion, AnimatePresence } from "framer-motion"
|
8
|
+
import { randomUUID } from "../../utils/creatorUtils"
|
9
|
+
import toast from "react-hot-toast"
|
8
10
|
|
9
11
|
const ContentIndexHeader = ({
|
10
12
|
messages,
|
@@ -28,7 +30,7 @@ const ContentIndexHeader = ({
|
|
28
30
|
: "Based on your input, here is the new syllabus, updates are highlighted in yellow"
|
29
31
|
|
30
32
|
return (
|
31
|
-
<div className="mt-2">
|
33
|
+
<div className="mt-2 ">
|
32
34
|
<AnimatePresence mode="wait">
|
33
35
|
<motion.h2
|
34
36
|
key={headerText}
|
@@ -36,7 +38,7 @@ const ContentIndexHeader = ({
|
|
36
38
|
animate={{ opacity: 1, y: 0 }}
|
37
39
|
exit={{ opacity: 0, y: 10 }}
|
38
40
|
transition={{ duration: 0.2 }}
|
39
|
-
className="text-
|
41
|
+
className="text-xs font-semibold sm:text-base md:text-lg"
|
40
42
|
>
|
41
43
|
{headerText}
|
42
44
|
</motion.h2>
|
@@ -49,7 +51,7 @@ const ContentIndexHeader = ({
|
|
49
51
|
animate={{ opacity: 1 }}
|
50
52
|
exit={{ opacity: 0 }}
|
51
53
|
transition={{ duration: 0.2, delay: 0.1 }}
|
52
|
-
className="text-sm text-gray-600 mt-1 mb-5"
|
54
|
+
className="text-xs sm:text-sm text-gray-600 mt-1 mb-5"
|
53
55
|
>
|
54
56
|
{subText}
|
55
57
|
</motion.p>
|
@@ -58,33 +60,30 @@ const ContentIndexHeader = ({
|
|
58
60
|
)
|
59
61
|
}
|
60
62
|
|
61
|
-
export default ContentIndexHeader
|
62
|
-
|
63
63
|
export const GenerateButton = ({
|
64
64
|
handleSubmit,
|
65
|
-
handleUndo,
|
66
|
-
hasChanges,
|
67
65
|
}: {
|
68
66
|
handleSubmit: () => void
|
69
|
-
handleUndo: () => void
|
70
|
-
hasChanges: boolean
|
71
67
|
}) => {
|
68
|
+
const history = useStore((state) => state.history)
|
69
|
+
const undo = useStore((state) => state.undo)
|
72
70
|
return (
|
73
|
-
<div className="flex justify-end mt-6">
|
74
|
-
{
|
71
|
+
<div className="flex flex-col space-y-2 sm:flex-row sm:space-y-0 sm:space-x-4 justify-end mt-6">
|
72
|
+
{history.length > 1 && (
|
75
73
|
<button
|
76
|
-
|
77
|
-
|
74
|
+
onClick={() => {
|
75
|
+
undo()
|
76
|
+
toast.success("Changes reverted!")
|
77
|
+
}}
|
78
|
+
className="w-full sm:w-auto text-gray-500 bg-gray-200 rounded px-4 py-2 hover:bg-gray-300 flex items-center justify-center gap-2 cursor-pointer"
|
78
79
|
>
|
79
80
|
{SVGS.undo}
|
80
81
|
Revert changes
|
81
82
|
</button>
|
82
83
|
)}
|
83
84
|
<button
|
84
|
-
onClick={
|
85
|
-
|
86
|
-
}}
|
87
|
-
className="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 cursor-pointer flex items-center gap-2"
|
85
|
+
onClick={handleSubmit}
|
86
|
+
className="w-full sm:w-auto bg-blue-600 text-white rounded px-4 py-2 hover:bg-blue-700 flex items-center justify-center gap-2 cursor-pointer"
|
88
87
|
>
|
89
88
|
<span>I'm ready. Create the course for me!</span>
|
90
89
|
{SVGS.rigoSoftBlue}
|
@@ -93,52 +92,36 @@ export const GenerateButton = ({
|
|
93
92
|
)
|
94
93
|
}
|
95
94
|
|
96
|
-
function hasChanges(current: Lesson[], previous: Lesson[]): boolean {
|
97
|
-
return current.some(
|
98
|
-
(c) =>
|
99
|
-
!previous.some(
|
100
|
-
(p) =>
|
101
|
-
p.id === c.id &&
|
102
|
-
p.title === c.title &&
|
103
|
-
p.type === c.type &&
|
104
|
-
p.duration === c.duration &&
|
105
|
-
p.description === c.description
|
106
|
-
)
|
107
|
-
)
|
108
|
-
}
|
109
|
-
|
110
95
|
export const ContentIndex = ({
|
111
|
-
prevLessons,
|
112
96
|
handleSubmit,
|
113
97
|
messages,
|
114
98
|
isThinking,
|
115
|
-
handleUndo,
|
116
99
|
}: {
|
117
|
-
prevLessons?: Lesson[]
|
118
100
|
handleSubmit: () => void
|
119
101
|
messages: TMessage[]
|
120
102
|
isThinking: boolean
|
121
|
-
handleUndo: () => void
|
122
103
|
}) => {
|
123
|
-
const
|
124
|
-
const
|
104
|
+
const history = useStore((state) => state.history)
|
105
|
+
const push = useStore((state) => state.push)
|
125
106
|
const containerRef = useRef<HTMLDivElement>(null)
|
126
107
|
const [showScrollHint, setShowScrollHint] = useState(false)
|
127
108
|
|
109
|
+
const syllabus = history[history.length - 1]
|
110
|
+
|
111
|
+
const previousSyllabus = history[history.length - 2] || null
|
112
|
+
|
128
113
|
const handleRemove = (lesson: Lesson) => {
|
129
|
-
|
114
|
+
push({
|
130
115
|
...syllabus,
|
131
|
-
lessons: syllabus.lessons.filter(
|
132
|
-
(l) => l.id !== lesson.id && l.title !== lesson.title
|
133
|
-
),
|
116
|
+
lessons: syllabus.lessons.filter((l) => l.uid !== lesson.uid),
|
134
117
|
})
|
135
118
|
}
|
136
119
|
|
137
|
-
const handleChange = (
|
138
|
-
|
120
|
+
const handleChange = (uid: string, newTitle: string) => {
|
121
|
+
push({
|
139
122
|
...syllabus,
|
140
123
|
lessons: syllabus.lessons.map((lesson) =>
|
141
|
-
lesson.
|
124
|
+
lesson.uid === uid ? { ...lesson, title: newTitle } : lesson
|
142
125
|
),
|
143
126
|
})
|
144
127
|
}
|
@@ -147,14 +130,15 @@ export const ContentIndex = ({
|
|
147
130
|
const newLesson: Lesson = {
|
148
131
|
id: (parseFloat(id) + 0.1).toFixed(1),
|
149
132
|
title: "Hello World",
|
133
|
+
uid: randomUUID(),
|
150
134
|
type: "READ",
|
151
135
|
duration: 2,
|
152
136
|
description: "Hello World",
|
153
137
|
}
|
154
138
|
const updated = [...syllabus.lessons]
|
155
139
|
updated.splice(index + 1, 0, newLesson)
|
156
|
-
|
157
|
-
|
140
|
+
push({
|
141
|
+
...syllabus,
|
158
142
|
lessons: updated,
|
159
143
|
})
|
160
144
|
}
|
@@ -193,31 +177,30 @@ export const ContentIndex = ({
|
|
193
177
|
}
|
194
178
|
|
195
179
|
return (
|
196
|
-
<div className="relative ">
|
180
|
+
<div className="relative w-full p-2 sm:p-4 md:p-6 lg:p-8 space-y-6">
|
197
181
|
<ContentIndexHeader messages={messages} syllabus={syllabus} />
|
198
182
|
<div
|
199
183
|
ref={containerRef}
|
200
|
-
className="
|
184
|
+
className="space-y-3 overflow-y-auto max-h-[78vh] pr-2 scrollbar-hide relative pb-20"
|
201
185
|
>
|
202
186
|
{isThinking ? (
|
203
|
-
<Loader text="Thinking..." minheight="min-h-[
|
187
|
+
<Loader text="Thinking..." minheight="min-h-[69vh]" />
|
204
188
|
) : (
|
205
189
|
<>
|
206
190
|
{syllabus.lessons.map((lesson, index) => (
|
207
|
-
<div key={lesson.
|
191
|
+
<div key={lesson.uid + index}>
|
208
192
|
<LessonItem
|
209
|
-
key={lesson.
|
193
|
+
key={lesson.uid}
|
210
194
|
lesson={lesson}
|
211
195
|
onChange={handleChange}
|
212
196
|
onRemove={() => handleRemove(lesson)}
|
213
197
|
isNew={Boolean(
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
l.type === lesson.type
|
198
|
+
previousSyllabus &&
|
199
|
+
previousSyllabus &&
|
200
|
+
previousSyllabus.lessons &&
|
201
|
+
previousSyllabus.lessons.length > 0 &&
|
202
|
+
!previousSyllabus.lessons.some(
|
203
|
+
(l) => l.uid === lesson.uid
|
221
204
|
)
|
222
205
|
)}
|
223
206
|
/>
|
@@ -233,11 +216,9 @@ export const ContentIndex = ({
|
|
233
216
|
</div>
|
234
217
|
</div>
|
235
218
|
))}
|
236
|
-
|
237
|
-
handleSubmit={handleSubmit}
|
238
|
-
|
239
|
-
hasChanges={hasChanges(syllabus.lessons, prevLessons || [])}
|
240
|
-
/>
|
219
|
+
{syllabus.lessons.length > 0 && (
|
220
|
+
<GenerateButton handleSubmit={handleSubmit} />
|
221
|
+
)}
|
241
222
|
</>
|
242
223
|
)}
|
243
224
|
</div>
|
@@ -245,7 +226,7 @@ export const ContentIndex = ({
|
|
245
226
|
{showScrollHint && !isThinking && (
|
246
227
|
<div className="pointer-events-none relative">
|
247
228
|
<div className="absolute bottom-0 left-0 w-full h-60 bg-gradient-to-t from-white to-transparent z-10" />
|
248
|
-
<div className="absolute bottom-
|
229
|
+
<div className="absolute bottom-10 left-0 w-full flex justify-center z-20">
|
249
230
|
<button
|
250
231
|
style={{ color: "#0084FF" }}
|
251
232
|
onClick={() => scrollToBottom("bottom")}
|
@@ -4,6 +4,7 @@ import { TMessage } from "../Message"
|
|
4
4
|
import FileUploader from "../FileUploader"
|
5
5
|
import { SVGS } from "../../assets/svgs"
|
6
6
|
import { Message } from "../Message"
|
7
|
+
import { FileCard } from "../FileCard"
|
7
8
|
|
8
9
|
export const Sidebar = ({
|
9
10
|
messages,
|
@@ -15,8 +16,8 @@ export const Sidebar = ({
|
|
15
16
|
handleSubmit: () => void
|
16
17
|
}) => {
|
17
18
|
const inputRef = useRef<HTMLTextAreaElement>(null)
|
18
|
-
const
|
19
|
-
const
|
19
|
+
const uploadedFiles = useStore((state) => state.uploadedFiles)
|
20
|
+
const setUploadedFiles = useStore((state) => state.setUploadedFiles)
|
20
21
|
|
21
22
|
const [isOpen, setIsOpen] = useState(false)
|
22
23
|
|
@@ -32,7 +33,7 @@ export const Sidebar = ({
|
|
32
33
|
)}
|
33
34
|
|
34
35
|
<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
|
+
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 border-C8DBFC overflow-y-auto scrollbar-hide p-6 transition-transform duration-300 ease-in-out lg:relative lg:transform-none lg:w-1/3 ${
|
36
37
|
isOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
37
38
|
}`}
|
38
39
|
>
|
@@ -56,10 +57,25 @@ export const Sidebar = ({
|
|
56
57
|
</div>
|
57
58
|
|
58
59
|
<div className="relative w-full rounded-md bg-white text-gray-700 h-24">
|
60
|
+
{uploadedFiles?.length > 0 && (
|
61
|
+
<div className="absolute bottom-[100%] flex gap-2 w-full pb-1 rounded-md overflow-x-auto scrollbar-hide">
|
62
|
+
{uploadedFiles.map((file, index) => (
|
63
|
+
<FileCard
|
64
|
+
key={index}
|
65
|
+
file={file}
|
66
|
+
handleRemove={() => {
|
67
|
+
const newFiles = [...uploadedFiles]
|
68
|
+
newFiles.splice(index, 1)
|
69
|
+
setUploadedFiles(newFiles)
|
70
|
+
}}
|
71
|
+
/>
|
72
|
+
))}
|
73
|
+
</div>
|
74
|
+
)}
|
59
75
|
<textarea
|
60
76
|
ref={inputRef}
|
61
77
|
style={{ resize: "none" }}
|
62
|
-
className="w-full h-full p-2"
|
78
|
+
className="w-full h-full p-2 outline-blue rounded"
|
63
79
|
placeholder="How can Learnpack help you?"
|
64
80
|
autoFocus
|
65
81
|
onKeyUp={(e) => {
|
@@ -79,23 +95,9 @@ export const Sidebar = ({
|
|
79
95
|
/>
|
80
96
|
<div className="absolute bottom-2 right-2 flex gap-1 items-center">
|
81
97
|
<div className="relative inline-block">
|
82
|
-
{syllabus.uploadedFiles?.length > 0 && (
|
83
|
-
<span
|
84
|
-
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"
|
85
|
-
title="Remove uploaded files"
|
86
|
-
onClick={() => {
|
87
|
-
setSyllabus({ ...syllabus, uploadedFiles: [] })
|
88
|
-
}}
|
89
|
-
>
|
90
|
-
{syllabus.uploadedFiles?.length}
|
91
|
-
</span>
|
92
|
-
)}
|
93
98
|
<FileUploader
|
94
99
|
onResult={(res) => {
|
95
|
-
|
96
|
-
...syllabus,
|
97
|
-
uploadedFiles: [...syllabus.uploadedFiles, ...res],
|
98
|
-
})
|
100
|
+
setUploadedFiles([...(uploadedFiles || []), ...res])
|
99
101
|
}}
|
100
102
|
/>
|
101
103
|
</div>
|