@learnpack/learnpack 5.0.298 → 5.0.301

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.
Files changed (84) hide show
  1. package/README.md +409 -409
  2. package/lib/commands/audit.js +15 -15
  3. package/lib/commands/breakToken.js +19 -19
  4. package/lib/commands/clean.js +3 -3
  5. package/lib/commands/logout.js +3 -3
  6. package/lib/commands/serve.js +32 -13
  7. package/lib/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
  8. package/lib/creatorDist/index.html +1 -1
  9. package/lib/managers/config/index.js +77 -77
  10. package/lib/utils/creatorUtilities.js +14 -14
  11. package/lib/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  12. package/lib/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  13. package/lib/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
  14. package/lib/utils/templates/scorm/config/index.html +209 -209
  15. package/lib/utils/templates/scorm/ims_xml.xsd +1 -1
  16. package/lib/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
  17. package/lib/utils/templates/scorm/imsmanifest.xml +38 -38
  18. package/lib/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -573
  19. package/package.json +1 -1
  20. package/src/commands/audit.ts +487 -487
  21. package/src/commands/breakToken.ts +67 -67
  22. package/src/commands/clean.ts +30 -30
  23. package/src/commands/logout.ts +38 -38
  24. package/src/commands/serve.ts +49 -27
  25. package/src/commands/start.ts +333 -333
  26. package/src/commands/translate.ts +123 -123
  27. package/src/creator/README.md +54 -54
  28. package/src/creator/package-lock.json +6621 -6621
  29. package/src/creator/package.json +55 -55
  30. package/src/creator/src/App.tsx +569 -569
  31. package/src/creator/src/components/FileUploader.tsx +302 -302
  32. package/src/creator/src/components/Icon.tsx +18 -18
  33. package/src/creator/src/components/LessonItem.tsx +152 -152
  34. package/src/creator/src/components/Login.tsx +259 -259
  35. package/src/creator/src/components/syllabus/ContentIndex.tsx +323 -323
  36. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +337 -337
  37. package/src/creator/src/i18n.ts +28 -28
  38. package/src/creator/src/locales/en.json +127 -127
  39. package/src/creator/src/locales/es.json +127 -127
  40. package/src/creator/src/utils/configTypes.ts +122 -122
  41. package/src/creator/src/utils/constants.ts +13 -13
  42. package/src/creator/src/utils/creatorUtils.ts +46 -46
  43. package/src/creator/src/utils/eventBus.ts +2 -2
  44. package/src/creator/src/utils/socket.ts +61 -61
  45. package/src/creator/src/utils/store.ts +222 -222
  46. package/src/creator/src/vite-env.d.ts +1 -1
  47. package/src/creator/vite.config.ts +13 -13
  48. package/src/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
  49. package/src/creatorDist/index.html +1 -1
  50. package/src/managers/config/defaults.ts +49 -49
  51. package/src/managers/config/exercise.ts +364 -364
  52. package/src/managers/config/index.ts +775 -775
  53. package/src/managers/file.ts +236 -236
  54. package/src/managers/server/routes.ts +554 -554
  55. package/src/managers/telemetry.ts +188 -188
  56. package/src/models/action.ts +13 -13
  57. package/src/models/config-manager.ts +28 -28
  58. package/src/models/config.ts +106 -106
  59. package/src/models/exercise-obj.ts +30 -30
  60. package/src/models/session.ts +39 -39
  61. package/src/models/socket.ts +61 -61
  62. package/src/models/status.ts +16 -16
  63. package/src/ui/_app/app.css +1 -1
  64. package/src/ui/_app/app.js +768 -768
  65. package/src/ui/_app/learnpack.svg +7 -7
  66. package/src/ui/_app/sw.js +59 -59
  67. package/src/ui/app.tar.gz +0 -0
  68. package/src/utils/BaseCommand.ts +56 -56
  69. package/src/utils/audit.ts +392 -392
  70. package/src/utils/checkNotInstalled.ts +267 -267
  71. package/src/utils/convertCreds.js +34 -34
  72. package/src/utils/creatorUtilities.ts +504 -504
  73. package/src/utils/export/README.md +178 -178
  74. package/src/utils/incrementVersion.js +74 -74
  75. package/src/utils/misc.ts +58 -58
  76. package/src/utils/sidebarGenerator.ts +195 -195
  77. package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  78. package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  79. package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
  80. package/src/utils/templates/scorm/config/index.html +209 -209
  81. package/src/utils/templates/scorm/ims_xml.xsd +1 -1
  82. package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
  83. package/src/utils/templates/scorm/imsmanifest.xml +38 -38
  84. package/src/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -573
@@ -1,569 +1,569 @@
1
- import { useEffect, useState } from "react"
2
- import StepWizard from "./components/StepWizard"
3
- import SelectableCard from "./components/SelectableCard"
4
- import Loader from "./components/Loader"
5
- import { useNavigate } from "react-router"
6
- import { useShallow } from "zustand/react/shallow"
7
- import useStore, { TDifficulty } from "./utils/store"
8
-
9
- import { publicInteractiveCreation, isHuman } from "./utils/rigo"
10
- import {
11
- checkParams,
12
- isValidRigoToken,
13
- loginWithToken,
14
- parseLesson,
15
- fixTitleLength,
16
- getTechnologies,
17
- detectLanguage,
18
- isValidPublicToken,
19
- } from "./utils/lib"
20
-
21
- import { Uploader } from "./components/Uploader"
22
- import toast from "react-hot-toast"
23
- import { ParamsChecker } from "./components/ParamsChecker"
24
- import { DEV_MODE, RIGO_FLOAT_GIF } from "./utils/constants"
25
- import TurnstileChallenge from "./components/TurnstileChallenge"
26
- // import TurnstileChallenge from "./components/TurnstileChallenge"
27
- import ResumeCourseModal from "./components/ResumeCourseModal"
28
- import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
29
- import { useTranslation } from "react-i18next"
30
- import NotificationListener from "./components/NotificationListener"
31
- import { slugify } from "./utils/creatorUtils"
32
- import TurnstileModal from "./components/TurnstileModal"
33
- import { TMessage } from "./components/Message"
34
-
35
- function App() {
36
- const navigate = useNavigate()
37
- const { t, i18n } = useTranslation()
38
-
39
- const {
40
- formState,
41
- setFormState,
42
- setAuth,
43
- push,
44
- cleanHistory,
45
- setPlanToRedirect,
46
- history,
47
- uploadedFiles,
48
- auth,
49
- resetFormState,
50
- cleanAll,
51
- setMessages,
52
- technologies,
53
- setTechnologies,
54
- } = useStore(
55
- useShallow((state) => ({
56
- formState: state.formState,
57
- setFormState: state.setFormState,
58
- setAuth: state.setAuth,
59
- technologies: state.technologies,
60
- setTechnologies: state.setTechnologies,
61
- push: state.push,
62
- history: state.history,
63
- cleanHistory: state.cleanHistory,
64
- setPlanToRedirect: state.setPlanToRedirect,
65
- uploadedFiles: state.uploadedFiles,
66
- auth: state.auth,
67
- resetFormState: state.resetFormState,
68
- cleanAll: state.cleanAll,
69
- setMessages: state.setMessages,
70
- }))
71
- )
72
-
73
- const [notificationId, setNotificationId] = useState<string>("")
74
- const [showTurnstileModal, setShowTurnstileModal] = useState(false)
75
-
76
- useEffect(() => {
77
- if (formState.isCompleted) {
78
- handleCreateTutorial()
79
- }
80
- }, [formState.isCompleted])
81
-
82
- useEffect(() => {
83
- verifyToken()
84
- checkQueryParams()
85
- checkTechs()
86
- }, [])
87
-
88
- const verifyToken = async () => {
89
- const { token } = checkParams(["token"])
90
- if (token) {
91
- const user = await loginWithToken(token)
92
- setAuth({
93
- bcToken: token,
94
- userId: user.id,
95
- rigoToken: user.rigobot.key,
96
- user: user,
97
- publicToken: "",
98
- })
99
- }
100
- }
101
-
102
- const checkQueryParams = () => {
103
- const {
104
- description,
105
- duration,
106
- plan,
107
- purpose,
108
- language,
109
- new: newParam,
110
- difficulty,
111
- } = checkParams([
112
- "description",
113
- "duration",
114
- "plan",
115
- "purpose",
116
- "language",
117
- "new",
118
- "difficulty",
119
- ])
120
-
121
- if (newParam && newParam.toLowerCase().trim() === "true") {
122
- cleanAll()
123
- }
124
-
125
- if (description) {
126
- setFormState({
127
- description: description,
128
- })
129
- }
130
- if (duration && !isNaN(parseInt(duration))) {
131
- if (["15", "30", "60"].includes(duration)) {
132
- const durationInt = parseInt(duration)
133
- console.log("duration", durationInt)
134
- setFormState({
135
- duration: durationInt,
136
- })
137
- } else {
138
- console.log("Invalid duration received in params", duration)
139
- }
140
- }
141
-
142
- if (plan) {
143
- setPlanToRedirect(plan)
144
- } else {
145
- console.debug("No plan received in params")
146
- }
147
-
148
- if (language && language.length === 2) {
149
- setFormState({
150
- language: language,
151
- })
152
- }
153
-
154
- if (purpose && purpose.length > 0 && possiblePurposes.includes(purpose)) {
155
- setFormState({
156
- purpose: purpose,
157
- })
158
- }
159
-
160
- if (
161
- difficulty &&
162
- ["easy", "beginner", "intermediate", "hard"].includes(difficulty)
163
- ) {
164
- setFormState({
165
- difficulty: difficulty as TDifficulty,
166
- })
167
- }
168
-
169
- if (description && purpose) {
170
- setFormState({
171
- currentStep: "duration",
172
- })
173
- }
174
- if (description && !purpose) {
175
- setFormState({
176
- currentStep: "purpose",
177
- })
178
- }
179
- }
180
-
181
- const handleCreateTutorial = async () => {
182
- try {
183
- let isAuthenticated = false
184
- let tokenToUse = ""
185
- let tokenType = ""
186
- if (auth.rigoToken) {
187
- console.log("auth.rigoToken", auth.rigoToken)
188
- const isRigoTokenValid = await isValidRigoToken(auth.rigoToken)
189
- if (isRigoTokenValid) {
190
- tokenToUse = auth.rigoToken
191
- isAuthenticated = true
192
- tokenType = "rigo"
193
- } else {
194
- setAuth({
195
- ...auth,
196
- rigoToken: "",
197
- bcToken: "",
198
- userId: "",
199
- user: null,
200
- })
201
- }
202
- }
203
-
204
- if (auth.publicToken && !isAuthenticated) {
205
- const isPublicTokenValid = await isValidPublicToken(auth.publicToken)
206
- if (isPublicTokenValid) {
207
- tokenToUse = auth.publicToken
208
- isAuthenticated = true
209
- tokenType = "public"
210
- }
211
- }
212
- if (!isAuthenticated) {
213
- setShowTurnstileModal(true)
214
- return
215
- }
216
-
217
- let techs = technologies.filter((t) => t.lang === formState.language)
218
-
219
- if (techs.length === 0) {
220
- techs = technologies.filter((t) => t.lang === "en")
221
- }
222
-
223
- const res = await publicInteractiveCreation(
224
- {
225
- courseInfo: `${JSON.stringify(
226
- formState
227
- )}. The following technologies are available, choose up to 3 from the following list: <techs>${techs
228
- .map((t) => t.slug)
229
- .join(", ")}</techs>`,
230
- prevInteractions: "USER: " + formState.description,
231
- },
232
- tokenToUse,
233
- formState.purpose || "learnpack-lesson-writer",
234
- tokenType === "rigo" ? false : true
235
- )
236
- console.log("RES", res)
237
-
238
- setNotificationId(res.notificationId)
239
- } catch (error) {
240
- console.error(error, "ERROR CREATING TUTORIAL")
241
- toast.error("Something went wrong. Please try again.")
242
- setFormState({
243
- isCompleted: false,
244
- currentStep: "hasContentIndex",
245
- })
246
- }
247
- }
248
-
249
- const checkTechs = async () => {
250
- if (technologies.length === 0) {
251
- const technologies = await getTechnologies()
252
- console.log("TECHNOLOGIES", technologies)
253
- setTechnologies(technologies)
254
- }
255
- }
256
-
257
- const buildSteps = () => {
258
- const steps = [
259
- {
260
- title: t("stepWizard.description"),
261
- slug: "description",
262
- isCompleted: formState.description.length > 0,
263
- required: true,
264
- validator: (value: string) => {
265
- const lang = detectLanguage(value)
266
-
267
- if (lang) {
268
- i18n.changeLanguage(lang)
269
- }
270
-
271
- return value.length > 0
272
- },
273
- content: (
274
- <textarea
275
- required
276
- placeholder={t("stepWizard.descriptionPlaceholder")}
277
- className="w-full h-24 border-2 border-gray-300 rounded-md p-2 bg-white"
278
- value={formState.description}
279
- onChange={(e) => {
280
- setFormState({
281
- description: e.target.value,
282
- })
283
- }}
284
- />
285
- ),
286
- },
287
- {
288
- title: t("stepWizard.purpose"),
289
- slug: "purpose",
290
- isCompleted: formState?.purpose?.length > 0,
291
- required: true,
292
- content: (
293
- <PurposeSelector
294
- onFinish={(purpose) => {
295
- setFormState({
296
- purpose: purpose,
297
- currentStep: "duration",
298
- })
299
- }}
300
- />
301
- ),
302
- },
303
- {
304
- title: t("stepWizard.duration"),
305
- slug: "duration",
306
- isCompleted: formState.duration > 0,
307
- required: true,
308
- content: (
309
- <div className="flex flex-col md:flex-row gap-2">
310
- <SelectableCard
311
- title={t("stepWizard.durationCard.15")}
312
- // subtitle="This is a tutorial that will take 15 minutes to complete"
313
- onClick={() => {
314
- setFormState({
315
- duration: 15,
316
- currentStep: "verifyHuman",
317
- })
318
- }}
319
- selected={formState.duration === 15}
320
- />
321
- <SelectableCard
322
- title={t("stepWizard.durationCard.30")}
323
- // subtitle="This is a tutorial that will take 30 minutes to complete"
324
- onClick={() => {
325
- setFormState({
326
- duration: 30,
327
- currentStep: "verifyHuman",
328
- })
329
- }}
330
- selected={formState.duration === 30}
331
- />
332
- <SelectableCard
333
- title={t("stepWizard.durationCard.60")}
334
- // subtitle="This is a tutorial that will take 1 hour to complete"
335
- onClick={() => {
336
- setFormState({
337
- duration: 60,
338
- currentStep: "verifyHuman",
339
- })
340
- }}
341
- selected={formState.duration === 60}
342
- />
343
- </div>
344
- ),
345
- },
346
- {
347
- title: t("stepWizard.verifyHuman"),
348
- slug: "verifyHuman",
349
- isCompleted: false,
350
- required: true,
351
- content: (
352
- <TurnstileChallenge
353
- siteKey={
354
- DEV_MODE ? "0x4AAAAAABeKMBYYinMU4Ib0" : "0x4AAAAAABeZ9tjEevGBsJFU"
355
- }
356
- onSuccess={async (token) => {
357
- const { human, message, token: jwtToken } = await isHuman(token)
358
- if (human) {
359
- toast.success(t("stepWizard.humanSuccess"))
360
-
361
- console.log("JWT TOKEN received", jwtToken)
362
- setAuth({
363
- ...auth,
364
- publicToken: jwtToken,
365
- })
366
- setFormState({
367
- currentStep: "hasContentIndex",
368
- })
369
- } else {
370
- toast.error(message)
371
- setFormState({
372
- currentStep: "duration",
373
- })
374
- }
375
- }}
376
- onError={() => {
377
- toast.error(t("turnstileModal.error"), {
378
- duration: 10000,
379
- })
380
- setFormState({
381
- currentStep: "duration",
382
- })
383
- }}
384
- />
385
- ),
386
- },
387
- {
388
- title: t("stepWizard.hasContentIndex"),
389
- slug: "hasContentIndex",
390
- isCompleted: false,
391
- content: (
392
- <>
393
- <div className="flex flex-col md:flex-row gap-2 justify-center">
394
- <SelectableCard
395
- title={t("stepWizard.hasContentIndexCard.no")}
396
- onClick={() => {
397
- setFormState({
398
- hasContentIndex: false,
399
- variables: [
400
- ...formState.variables.filter(
401
- (v) => v !== "contentIndex"
402
- ),
403
- ],
404
- isCompleted: true,
405
- })
406
- }}
407
- // selected={formState.hasContentIndex === false}
408
- />
409
- <SelectableCard
410
- title={t("stepWizard.hasContentIndexCard.yes")}
411
- onClick={() => {
412
- setFormState({
413
- hasContentIndex: true,
414
- currentStep: "contentIndex",
415
- variables: [...formState.variables, "contentIndex"],
416
- })
417
- }}
418
- // selected={formState.hasContentIndex === true}
419
- />
420
- </div>
421
- </>
422
- ),
423
- },
424
- {
425
- title: t("stepWizard.contentIndex"),
426
- slug: "contentIndex",
427
- helpText: t("stepWizard.contentIndexHelpText"),
428
- isCompleted:
429
- formState.contentIndex.length > 0 || uploadedFiles.length > 0,
430
- required: true,
431
- content: (
432
- <Uploader
433
- onFinish={(text) => {
434
- setFormState({
435
- contentIndex: text,
436
- isCompleted: true,
437
- })
438
- }}
439
- />
440
- ),
441
- },
442
- ]
443
-
444
- return steps.filter(
445
- (step) =>
446
- formState.variables.includes(step.slug) ||
447
- step.slug === formState.currentStep
448
- )
449
- }
450
-
451
- return (
452
- <>
453
- <ParamsChecker />
454
- {showTurnstileModal && (
455
- <TurnstileModal
456
- onClose={() => {
457
- setShowTurnstileModal(false)
458
- handleCreateTutorial()
459
- }}
460
- onError={() => {
461
- toast.error(t("turnstileModal.error"), {
462
- duration: 10000,
463
- })
464
- setShowTurnstileModal(false)
465
- setFormState({
466
- currentStep: "purpose",
467
- })
468
- }}
469
- />
470
- )}
471
- {formState.isCompleted && history.length === 0 ? (
472
- <>
473
- <Loader
474
- text={t("loader.text")}
475
- icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
476
- />
477
- {notificationId && (
478
- <NotificationListener
479
- onNotification={(res) => {
480
- console.log("Async response", res)
481
- const lessons = res.parsed.listOfSteps.map((lesson: any) => {
482
- return parseLesson(lesson, [])
483
- })
484
-
485
- push({
486
- lessons,
487
- courseInfo: {
488
- ...formState,
489
- title: res.parsed.title,
490
- slug: slugify(fixTitleLength(res.parsed.title)),
491
- description: res.parsed.description,
492
- language:
493
- res.parsed.languageCode || formState.language || "en",
494
- technologies:
495
- res.parsed.technologies.length > 0
496
- ? res.parsed.technologies
497
- : ["education", "quizzes"],
498
- },
499
- })
500
-
501
- if (res.parsed.languageCode) {
502
- i18n.changeLanguage(res.parsed.languageCode)
503
- }
504
-
505
- let initialMessages: TMessage[] = [
506
- {
507
- type: "user",
508
- content: formState.description,
509
- },
510
- {
511
- type: "assistant",
512
- content: res.parsed.aiMessage,
513
- },
514
- ]
515
-
516
- if (lessons.length > 0) {
517
- initialMessages.push({
518
- type: "assistant",
519
- content: t("contentIndex.okMessage"),
520
- })
521
- initialMessages.push({
522
- type: "assistant",
523
- content: t("contentIndex.instructionsMessage"),
524
- })
525
- }
526
-
527
- setMessages(initialMessages)
528
- navigate("/creator/syllabus")
529
- setFormState({
530
- isCompleted: false,
531
- currentStep: "description",
532
- })
533
- }}
534
- notificationId={notificationId}
535
- />
536
- )}
537
- </>
538
- ) : (
539
- <>
540
- {history.length > 0 && (
541
- <ResumeCourseModal
542
- onContinue={() => {
543
- navigate("/creator/syllabus")
544
- }}
545
- onStartOver={() => {
546
- resetFormState()
547
- cleanAll()
548
- cleanHistory()
549
- }}
550
- />
551
- )}
552
- <StepWizard
553
- hideLastButton={true}
554
- formState={formState}
555
- steps={buildSteps()}
556
- setFormState={setFormState}
557
- onFinish={() => {
558
- setFormState({
559
- isCompleted: true,
560
- })
561
- }}
562
- />
563
- </>
564
- )}
565
- </>
566
- )
567
- }
568
-
569
- export default App
1
+ import { useEffect, useState } from "react"
2
+ import StepWizard from "./components/StepWizard"
3
+ import SelectableCard from "./components/SelectableCard"
4
+ import Loader from "./components/Loader"
5
+ import { useNavigate } from "react-router"
6
+ import { useShallow } from "zustand/react/shallow"
7
+ import useStore, { TDifficulty } from "./utils/store"
8
+
9
+ import { publicInteractiveCreation, isHuman } from "./utils/rigo"
10
+ import {
11
+ checkParams,
12
+ isValidRigoToken,
13
+ loginWithToken,
14
+ parseLesson,
15
+ fixTitleLength,
16
+ getTechnologies,
17
+ detectLanguage,
18
+ isValidPublicToken,
19
+ } from "./utils/lib"
20
+
21
+ import { Uploader } from "./components/Uploader"
22
+ import toast from "react-hot-toast"
23
+ import { ParamsChecker } from "./components/ParamsChecker"
24
+ import { DEV_MODE, RIGO_FLOAT_GIF } from "./utils/constants"
25
+ import TurnstileChallenge from "./components/TurnstileChallenge"
26
+ // import TurnstileChallenge from "./components/TurnstileChallenge"
27
+ import ResumeCourseModal from "./components/ResumeCourseModal"
28
+ import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
29
+ import { useTranslation } from "react-i18next"
30
+ import NotificationListener from "./components/NotificationListener"
31
+ import { slugify } from "./utils/creatorUtils"
32
+ import TurnstileModal from "./components/TurnstileModal"
33
+ import { TMessage } from "./components/Message"
34
+
35
+ function App() {
36
+ const navigate = useNavigate()
37
+ const { t, i18n } = useTranslation()
38
+
39
+ const {
40
+ formState,
41
+ setFormState,
42
+ setAuth,
43
+ push,
44
+ cleanHistory,
45
+ setPlanToRedirect,
46
+ history,
47
+ uploadedFiles,
48
+ auth,
49
+ resetFormState,
50
+ cleanAll,
51
+ setMessages,
52
+ technologies,
53
+ setTechnologies,
54
+ } = useStore(
55
+ useShallow((state) => ({
56
+ formState: state.formState,
57
+ setFormState: state.setFormState,
58
+ setAuth: state.setAuth,
59
+ technologies: state.technologies,
60
+ setTechnologies: state.setTechnologies,
61
+ push: state.push,
62
+ history: state.history,
63
+ cleanHistory: state.cleanHistory,
64
+ setPlanToRedirect: state.setPlanToRedirect,
65
+ uploadedFiles: state.uploadedFiles,
66
+ auth: state.auth,
67
+ resetFormState: state.resetFormState,
68
+ cleanAll: state.cleanAll,
69
+ setMessages: state.setMessages,
70
+ }))
71
+ )
72
+
73
+ const [notificationId, setNotificationId] = useState<string>("")
74
+ const [showTurnstileModal, setShowTurnstileModal] = useState(false)
75
+
76
+ useEffect(() => {
77
+ if (formState.isCompleted) {
78
+ handleCreateTutorial()
79
+ }
80
+ }, [formState.isCompleted])
81
+
82
+ useEffect(() => {
83
+ verifyToken()
84
+ checkQueryParams()
85
+ checkTechs()
86
+ }, [])
87
+
88
+ const verifyToken = async () => {
89
+ const { token } = checkParams(["token"])
90
+ if (token) {
91
+ const user = await loginWithToken(token)
92
+ setAuth({
93
+ bcToken: token,
94
+ userId: user.id,
95
+ rigoToken: user.rigobot.key,
96
+ user: user,
97
+ publicToken: "",
98
+ })
99
+ }
100
+ }
101
+
102
+ const checkQueryParams = () => {
103
+ const {
104
+ description,
105
+ duration,
106
+ plan,
107
+ purpose,
108
+ language,
109
+ new: newParam,
110
+ difficulty,
111
+ } = checkParams([
112
+ "description",
113
+ "duration",
114
+ "plan",
115
+ "purpose",
116
+ "language",
117
+ "new",
118
+ "difficulty",
119
+ ])
120
+
121
+ if (newParam && newParam.toLowerCase().trim() === "true") {
122
+ cleanAll()
123
+ }
124
+
125
+ if (description) {
126
+ setFormState({
127
+ description: description,
128
+ })
129
+ }
130
+ if (duration && !isNaN(parseInt(duration))) {
131
+ if (["15", "30", "60"].includes(duration)) {
132
+ const durationInt = parseInt(duration)
133
+ console.log("duration", durationInt)
134
+ setFormState({
135
+ duration: durationInt,
136
+ })
137
+ } else {
138
+ console.log("Invalid duration received in params", duration)
139
+ }
140
+ }
141
+
142
+ if (plan) {
143
+ setPlanToRedirect(plan)
144
+ } else {
145
+ console.debug("No plan received in params")
146
+ }
147
+
148
+ if (language && language.length === 2) {
149
+ setFormState({
150
+ language: language,
151
+ })
152
+ }
153
+
154
+ if (purpose && purpose.length > 0 && possiblePurposes.includes(purpose)) {
155
+ setFormState({
156
+ purpose: purpose,
157
+ })
158
+ }
159
+
160
+ if (
161
+ difficulty &&
162
+ ["easy", "beginner", "intermediate", "hard"].includes(difficulty)
163
+ ) {
164
+ setFormState({
165
+ difficulty: difficulty as TDifficulty,
166
+ })
167
+ }
168
+
169
+ if (description && purpose) {
170
+ setFormState({
171
+ currentStep: "duration",
172
+ })
173
+ }
174
+ if (description && !purpose) {
175
+ setFormState({
176
+ currentStep: "purpose",
177
+ })
178
+ }
179
+ }
180
+
181
+ const handleCreateTutorial = async () => {
182
+ try {
183
+ let isAuthenticated = false
184
+ let tokenToUse = ""
185
+ let tokenType = ""
186
+ if (auth.rigoToken) {
187
+ console.log("auth.rigoToken", auth.rigoToken)
188
+ const isRigoTokenValid = await isValidRigoToken(auth.rigoToken)
189
+ if (isRigoTokenValid) {
190
+ tokenToUse = auth.rigoToken
191
+ isAuthenticated = true
192
+ tokenType = "rigo"
193
+ } else {
194
+ setAuth({
195
+ ...auth,
196
+ rigoToken: "",
197
+ bcToken: "",
198
+ userId: "",
199
+ user: null,
200
+ })
201
+ }
202
+ }
203
+
204
+ if (auth.publicToken && !isAuthenticated) {
205
+ const isPublicTokenValid = await isValidPublicToken(auth.publicToken)
206
+ if (isPublicTokenValid) {
207
+ tokenToUse = auth.publicToken
208
+ isAuthenticated = true
209
+ tokenType = "public"
210
+ }
211
+ }
212
+ if (!isAuthenticated) {
213
+ setShowTurnstileModal(true)
214
+ return
215
+ }
216
+
217
+ let techs = technologies.filter((t) => t.lang === formState.language)
218
+
219
+ if (techs.length === 0) {
220
+ techs = technologies.filter((t) => t.lang === "en")
221
+ }
222
+
223
+ const res = await publicInteractiveCreation(
224
+ {
225
+ courseInfo: `${JSON.stringify(
226
+ formState
227
+ )}. The following technologies are available, choose up to 3 from the following list: <techs>${techs
228
+ .map((t) => t.slug)
229
+ .join(", ")}</techs>`,
230
+ prevInteractions: "USER: " + formState.description,
231
+ },
232
+ tokenToUse,
233
+ formState.purpose || "learnpack-lesson-writer",
234
+ tokenType === "rigo" ? false : true
235
+ )
236
+ console.log("RES", res)
237
+
238
+ setNotificationId(res.notificationId)
239
+ } catch (error) {
240
+ console.error(error, "ERROR CREATING TUTORIAL")
241
+ toast.error("Something went wrong. Please try again.")
242
+ setFormState({
243
+ isCompleted: false,
244
+ currentStep: "hasContentIndex",
245
+ })
246
+ }
247
+ }
248
+
249
+ const checkTechs = async () => {
250
+ if (technologies.length === 0) {
251
+ const technologies = await getTechnologies()
252
+ console.log("TECHNOLOGIES", technologies)
253
+ setTechnologies(technologies)
254
+ }
255
+ }
256
+
257
+ const buildSteps = () => {
258
+ const steps = [
259
+ {
260
+ title: t("stepWizard.description"),
261
+ slug: "description",
262
+ isCompleted: formState.description.length > 0,
263
+ required: true,
264
+ validator: (value: string) => {
265
+ const lang = detectLanguage(value)
266
+
267
+ if (lang) {
268
+ i18n.changeLanguage(lang)
269
+ }
270
+
271
+ return value.length > 0
272
+ },
273
+ content: (
274
+ <textarea
275
+ required
276
+ placeholder={t("stepWizard.descriptionPlaceholder")}
277
+ className="w-full h-24 border-2 border-gray-300 rounded-md p-2 bg-white"
278
+ value={formState.description}
279
+ onChange={(e) => {
280
+ setFormState({
281
+ description: e.target.value,
282
+ })
283
+ }}
284
+ />
285
+ ),
286
+ },
287
+ {
288
+ title: t("stepWizard.purpose"),
289
+ slug: "purpose",
290
+ isCompleted: formState?.purpose?.length > 0,
291
+ required: true,
292
+ content: (
293
+ <PurposeSelector
294
+ onFinish={(purpose) => {
295
+ setFormState({
296
+ purpose: purpose,
297
+ currentStep: "duration",
298
+ })
299
+ }}
300
+ />
301
+ ),
302
+ },
303
+ {
304
+ title: t("stepWizard.duration"),
305
+ slug: "duration",
306
+ isCompleted: formState.duration > 0,
307
+ required: true,
308
+ content: (
309
+ <div className="flex flex-col md:flex-row gap-2">
310
+ <SelectableCard
311
+ title={t("stepWizard.durationCard.15")}
312
+ // subtitle="This is a tutorial that will take 15 minutes to complete"
313
+ onClick={() => {
314
+ setFormState({
315
+ duration: 15,
316
+ currentStep: "verifyHuman",
317
+ })
318
+ }}
319
+ selected={formState.duration === 15}
320
+ />
321
+ <SelectableCard
322
+ title={t("stepWizard.durationCard.30")}
323
+ // subtitle="This is a tutorial that will take 30 minutes to complete"
324
+ onClick={() => {
325
+ setFormState({
326
+ duration: 30,
327
+ currentStep: "verifyHuman",
328
+ })
329
+ }}
330
+ selected={formState.duration === 30}
331
+ />
332
+ <SelectableCard
333
+ title={t("stepWizard.durationCard.60")}
334
+ // subtitle="This is a tutorial that will take 1 hour to complete"
335
+ onClick={() => {
336
+ setFormState({
337
+ duration: 60,
338
+ currentStep: "verifyHuman",
339
+ })
340
+ }}
341
+ selected={formState.duration === 60}
342
+ />
343
+ </div>
344
+ ),
345
+ },
346
+ {
347
+ title: t("stepWizard.verifyHuman"),
348
+ slug: "verifyHuman",
349
+ isCompleted: false,
350
+ required: true,
351
+ content: (
352
+ <TurnstileChallenge
353
+ siteKey={
354
+ DEV_MODE ? "0x4AAAAAABeKMBYYinMU4Ib0" : "0x4AAAAAABeZ9tjEevGBsJFU"
355
+ }
356
+ onSuccess={async (token) => {
357
+ const { human, message, token: jwtToken } = await isHuman(token)
358
+ if (human) {
359
+ toast.success(t("stepWizard.humanSuccess"))
360
+
361
+ console.log("JWT TOKEN received", jwtToken)
362
+ setAuth({
363
+ ...auth,
364
+ publicToken: jwtToken,
365
+ })
366
+ setFormState({
367
+ currentStep: "hasContentIndex",
368
+ })
369
+ } else {
370
+ toast.error(message)
371
+ setFormState({
372
+ currentStep: "duration",
373
+ })
374
+ }
375
+ }}
376
+ onError={() => {
377
+ toast.error(t("turnstileModal.error"), {
378
+ duration: 10000,
379
+ })
380
+ setFormState({
381
+ currentStep: "duration",
382
+ })
383
+ }}
384
+ />
385
+ ),
386
+ },
387
+ {
388
+ title: t("stepWizard.hasContentIndex"),
389
+ slug: "hasContentIndex",
390
+ isCompleted: false,
391
+ content: (
392
+ <>
393
+ <div className="flex flex-col md:flex-row gap-2 justify-center">
394
+ <SelectableCard
395
+ title={t("stepWizard.hasContentIndexCard.no")}
396
+ onClick={() => {
397
+ setFormState({
398
+ hasContentIndex: false,
399
+ variables: [
400
+ ...formState.variables.filter(
401
+ (v) => v !== "contentIndex"
402
+ ),
403
+ ],
404
+ isCompleted: true,
405
+ })
406
+ }}
407
+ // selected={formState.hasContentIndex === false}
408
+ />
409
+ <SelectableCard
410
+ title={t("stepWizard.hasContentIndexCard.yes")}
411
+ onClick={() => {
412
+ setFormState({
413
+ hasContentIndex: true,
414
+ currentStep: "contentIndex",
415
+ variables: [...formState.variables, "contentIndex"],
416
+ })
417
+ }}
418
+ // selected={formState.hasContentIndex === true}
419
+ />
420
+ </div>
421
+ </>
422
+ ),
423
+ },
424
+ {
425
+ title: t("stepWizard.contentIndex"),
426
+ slug: "contentIndex",
427
+ helpText: t("stepWizard.contentIndexHelpText"),
428
+ isCompleted:
429
+ formState.contentIndex.length > 0 || uploadedFiles.length > 0,
430
+ required: true,
431
+ content: (
432
+ <Uploader
433
+ onFinish={(text) => {
434
+ setFormState({
435
+ contentIndex: text,
436
+ isCompleted: true,
437
+ })
438
+ }}
439
+ />
440
+ ),
441
+ },
442
+ ]
443
+
444
+ return steps.filter(
445
+ (step) =>
446
+ formState.variables.includes(step.slug) ||
447
+ step.slug === formState.currentStep
448
+ )
449
+ }
450
+
451
+ return (
452
+ <>
453
+ <ParamsChecker />
454
+ {showTurnstileModal && (
455
+ <TurnstileModal
456
+ onClose={() => {
457
+ setShowTurnstileModal(false)
458
+ handleCreateTutorial()
459
+ }}
460
+ onError={() => {
461
+ toast.error(t("turnstileModal.error"), {
462
+ duration: 10000,
463
+ })
464
+ setShowTurnstileModal(false)
465
+ setFormState({
466
+ currentStep: "purpose",
467
+ })
468
+ }}
469
+ />
470
+ )}
471
+ {formState.isCompleted && history.length === 0 ? (
472
+ <>
473
+ <Loader
474
+ text={t("loader.text")}
475
+ icon={<img src={RIGO_FLOAT_GIF} alt="rigo" className="w-20 h-20" />}
476
+ />
477
+ {notificationId && (
478
+ <NotificationListener
479
+ onNotification={(res) => {
480
+ console.log("Async response", res)
481
+ const lessons = res.parsed.listOfSteps.map((lesson: any) => {
482
+ return parseLesson(lesson, [])
483
+ })
484
+
485
+ push({
486
+ lessons,
487
+ courseInfo: {
488
+ ...formState,
489
+ title: res.parsed.title,
490
+ slug: slugify(fixTitleLength(res.parsed.title)),
491
+ description: res.parsed.description,
492
+ language:
493
+ res.parsed.languageCode || formState.language || "en",
494
+ technologies:
495
+ res.parsed.technologies.length > 0
496
+ ? res.parsed.technologies
497
+ : ["education", "quizzes"],
498
+ },
499
+ })
500
+
501
+ if (res.parsed.languageCode) {
502
+ i18n.changeLanguage(res.parsed.languageCode)
503
+ }
504
+
505
+ let initialMessages: TMessage[] = [
506
+ {
507
+ type: "user",
508
+ content: formState.description,
509
+ },
510
+ {
511
+ type: "assistant",
512
+ content: res.parsed.aiMessage,
513
+ },
514
+ ]
515
+
516
+ if (lessons.length > 0) {
517
+ initialMessages.push({
518
+ type: "assistant",
519
+ content: t("contentIndex.okMessage"),
520
+ })
521
+ initialMessages.push({
522
+ type: "assistant",
523
+ content: t("contentIndex.instructionsMessage"),
524
+ })
525
+ }
526
+
527
+ setMessages(initialMessages)
528
+ navigate("/creator/syllabus")
529
+ setFormState({
530
+ isCompleted: false,
531
+ currentStep: "description",
532
+ })
533
+ }}
534
+ notificationId={notificationId}
535
+ />
536
+ )}
537
+ </>
538
+ ) : (
539
+ <>
540
+ {history.length > 0 && (
541
+ <ResumeCourseModal
542
+ onContinue={() => {
543
+ navigate("/creator/syllabus")
544
+ }}
545
+ onStartOver={() => {
546
+ resetFormState()
547
+ cleanAll()
548
+ cleanHistory()
549
+ }}
550
+ />
551
+ )}
552
+ <StepWizard
553
+ hideLastButton={true}
554
+ formState={formState}
555
+ steps={buildSteps()}
556
+ setFormState={setFormState}
557
+ onFinish={() => {
558
+ setFormState({
559
+ isCompleted: true,
560
+ })
561
+ }}
562
+ />
563
+ </>
564
+ )}
565
+ </>
566
+ )
567
+ }
568
+
569
+ export default App