@learnpack/learnpack 5.0.238 → 5.0.244

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.
@@ -1,6 +1,5 @@
1
1
  import { flags } from "@oclif/command"
2
2
 
3
- // import { readDocument } from "../utils/readDocuments"
4
3
  import { YoutubeTranscript } from "youtube-transcript"
5
4
  import * as express from "express"
6
5
  import * as cors from "cors"
@@ -30,6 +29,8 @@ import {
30
29
  // makeReadmeReadable,
31
30
  generateImage,
32
31
  isPackageAuthor,
32
+ createStructuredPreviewReadme,
33
+ translateCourseMetadata,
33
34
  } from "../utils/rigoActions"
34
35
  import * as dotenv from "dotenv"
35
36
  import {
@@ -64,15 +65,13 @@ function findLast<T>(
64
65
  export const createLearnJson = (courseInfo: FormState) => {
65
66
  // console.log("courseInfo to create learn json", courseInfo)
66
67
 
67
- const expectedPreviewUrl = `https://${slugify(
68
- courseInfo.title as string
69
- )}.learn-pack.com/preview.png`
68
+ const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`
70
69
  console.log("Preview url in generated learn.json", expectedPreviewUrl)
71
70
 
72
71
  const language = courseInfo.language || "en"
73
72
 
74
73
  const learnJson = {
75
- slug: slugify(courseInfo.title as string),
74
+ slug: courseInfo.slug,
76
75
  title:
77
76
  language === "en" || language === "us" ?
78
77
  {
@@ -105,27 +104,6 @@ const uploadFileToBucket = async (
105
104
  await fileRef.save(Buffer.from(file, "utf8"))
106
105
  }
107
106
 
108
- const uploadImageToBucket = async (
109
- bucket: Bucket,
110
- url: string,
111
- path: string
112
- ) => {
113
- const response = await fetch(url)
114
- if (!response.ok) {
115
- throw new Error(`Failed to download image: ${response.statusText}`)
116
- }
117
-
118
- const contentType =
119
- response.headers.get("content-type") || "application/octet-stream"
120
- const buffer = await response.arrayBuffer()
121
-
122
- const fileRef = bucket.file(path)
123
- await fileRef.save(Buffer.from(buffer), {
124
- resumable: false,
125
- contentType,
126
- })
127
- }
128
-
129
107
  const PARAMS = {
130
108
  expected_grade_level: "8",
131
109
  max_fkgl: 10,
@@ -135,25 +113,23 @@ const PARAMS = {
135
113
  }
136
114
 
137
115
  export const processImage = async (
138
- bucket: Bucket,
139
- tutorialDir: string,
140
116
  url: string,
141
117
  description: string,
142
- rigoToken: string
118
+ rigoToken: string,
119
+ courseSlug: string
143
120
  ) => {
144
121
  try {
145
122
  // TODO: MAKE THIS ASYNC
146
123
  const filename = getFilenameFromUrl(url)
147
124
 
148
- console.log("🖼️ IMAGE FILENAME", filename)
149
- const imagePath = `${tutorialDir}/.learn/assets/${filename}`
150
-
151
- console.log("🖼️ Generating image", imagePath)
125
+ const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/images/${filename}`
152
126
 
153
- const res = await generateImage(rigoToken, { prompt: description })
154
- await uploadImageToBucket(bucket, res.image_url, imagePath)
127
+ await generateImage(rigoToken, {
128
+ prompt: description,
129
+ callbackUrl: webhookUrl,
130
+ })
155
131
 
156
- console.log("✅ Image", imagePath, "generated successfully!")
132
+ // console.log("✅ Image", imagePath, "generated successfully!")
157
133
  return true
158
134
  } catch {
159
135
  return false
@@ -201,7 +177,6 @@ const uploadInitialReadme = async (
201
177
  }
202
178
 
203
179
  const cleanFormState = (formState: FormState) => {
204
- // keysToDelete: description, technologies, purpose
205
180
  const {
206
181
  description,
207
182
  technologies,
@@ -217,6 +192,51 @@ const cleanFormState = (formState: FormState) => {
217
192
  return rest
218
193
  }
219
194
 
195
+ const createMultiLangAsset = async (
196
+ bucket: Bucket,
197
+ rigoToken: string,
198
+ bcToken: string,
199
+ courseSlug: string,
200
+ courseJson: any,
201
+ deployUrl: string
202
+ ) => {
203
+ const availableLangs = Object.keys(courseJson.title)
204
+ console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
205
+
206
+ const all_translations: string[] = []
207
+
208
+ for (const lang of availableLangs) {
209
+ // eslint-disable-next-line no-await-in-loop
210
+ const indexReadme = await bucket.file(
211
+ `courses/${courseSlug}/README.${
212
+ lang === "us" || lang === "en" ? "md" : `${lang}.md`
213
+ }`
214
+ )
215
+ // eslint-disable-next-line no-await-in-loop
216
+ const [indexReadmeContent] = await indexReadme.download()
217
+ const indexReadmeString = indexReadmeContent.toString()
218
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
219
+
220
+ // eslint-disable-next-line no-await-in-loop
221
+ const asset = await handleAssetCreation(
222
+ { token: bcToken, rigobotToken: rigoToken },
223
+ courseJson,
224
+ lang,
225
+ deployUrl,
226
+ b64IndexReadme,
227
+ all_translations
228
+ )
229
+
230
+ all_translations.push(asset.slug)
231
+ }
232
+ // const languageCodes = new Set(languageCodes)
233
+ // const asset = await api.createAsset(rigoToken, {
234
+ // slug: courseSlug,
235
+ // title: courseJson.title,
236
+ // description: courseJson.description,
237
+ // })
238
+ }
239
+
220
240
  async function startExerciseGeneration(
221
241
  bucket: Bucket,
222
242
  rigoToken: string,
@@ -253,6 +273,27 @@ async function startExerciseGeneration(
253
273
  )
254
274
  }
255
275
 
276
+ async function createInitialReadme(
277
+ tutorialInfo: string,
278
+ tutorialSlug: string,
279
+ rigoToken: string
280
+ ) {
281
+ const webhookUrl = `${process.env.HOST}/webhooks/${tutorialSlug}/initial-readme-processor`
282
+ console.log("Creating initial readme", webhookUrl)
283
+ try {
284
+ const res = await createStructuredPreviewReadme(
285
+ rigoToken,
286
+ {
287
+ tutorial_info: tutorialInfo,
288
+ },
289
+ webhookUrl
290
+ )
291
+ console.log("Initial readme created", res)
292
+ } catch (error) {
293
+ console.error("Error creating initial readme", error)
294
+ }
295
+ }
296
+
256
297
  const fixPreviewUrl = (slug: string, previewUrl: string) => {
257
298
  if (!previewUrl) {
258
299
  return null
@@ -482,6 +523,27 @@ export default class ServeCommand extends SessionCommand {
482
523
  res.json({ id, status: "SUCCESS" })
483
524
  })
484
525
 
526
+ app.post(
527
+ "/webhooks/:courseSlug/initial-readme-processor",
528
+ async (req, res) => {
529
+ const { courseSlug } = req.params
530
+ const body = req.body
531
+
532
+ // Save the file as courses/courseSlug/README.md
533
+ const filePath = `courses/${courseSlug}/README.${
534
+ body.parsed.language_code === "us" ||
535
+ body.parsed.language_code === "en" ?
536
+ "md" :
537
+ `${body.parsed.language_code}.md`
538
+ }`
539
+ console.log("Saving initial readme to", filePath)
540
+ await uploadFileToBucket(bucket, body.parsed.content, filePath)
541
+ console.log("Initial readme saved to", filePath)
542
+
543
+ res.json({ status: "SUCCESS" })
544
+ }
545
+ )
546
+
485
547
  app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
486
548
  const { courseSlug, imageId } = req.params
487
549
  const body = req.body
@@ -629,13 +691,7 @@ export default class ServeCommand extends SessionCommand {
629
691
  })
630
692
  for (const image of imagesArray) {
631
693
  // eslint-disable-next-line no-await-in-loop
632
- await processImage(
633
- bucket,
634
- `courses/${courseSlug}`,
635
- image.url,
636
- image.alt,
637
- rigoToken
638
- )
694
+ await processImage(image.url, image.alt, rigoToken, courseSlug)
639
695
  }
640
696
  }
641
697
 
@@ -863,25 +919,34 @@ export default class ServeCommand extends SessionCommand {
863
919
 
864
920
  app.post("/actions/translate", express.json(), async (req, res) => {
865
921
  console.log("POST /actions/translate")
866
- const { exerciseSlugs, languages, rigoToken } = req.body
922
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body
867
923
  const query = req.query
868
924
  const courseSlug = query.slug
869
925
 
870
- console.log("EXERCISE SLUGS", exerciseSlugs)
871
- console.log("LANGUAGES", languages)
872
- console.log("RIGO TOKEN", rigoToken)
873
-
874
926
  if (!rigoToken) {
875
927
  return res.status(400).json({ error: "RigoToken not found" })
876
928
  }
877
929
 
878
930
  const languagesToTranslate: string[] = languages.split(",")
879
931
 
932
+ const course = await bucket
933
+ .file(`courses/${courseSlug}/learn.json`)
934
+ .download()
935
+
936
+ const courseJson = JSON.parse(course.toString())
937
+ const languageCodes = new Set()
938
+
880
939
  try {
881
940
  await Promise.all(
882
941
  exerciseSlugs.map(async (slug: string) => {
883
- const readmePath = `courses/${courseSlug}/exercises/${slug}/README.md`
942
+ const readmePath = `courses/${courseSlug}/exercises/${slug}/README${
943
+ currentLanguage === "us" || currentLanguage === "en" ?
944
+ "" :
945
+ `.${currentLanguage}`
946
+ }.md`
947
+
884
948
  const readme = await bucket.file(readmePath).download()
949
+
885
950
  await Promise.all(
886
951
  languagesToTranslate.map(async (language: string) => {
887
952
  const response = await translateExercise(rigoToken, {
@@ -891,14 +956,98 @@ export default class ServeCommand extends SessionCommand {
891
956
  })
892
957
 
893
958
  const translatedReadme = await bucket.file(
894
- `courses/${courseSlug}/exercises/${slug}/README.${response.parsed.output_language_code}.md`
959
+ `courses/${courseSlug}/exercises/${slug}/README${
960
+ response.parsed.output_language_code === "us" ||
961
+ response.parsed.output_language_code === "en" ?
962
+ "" :
963
+ `.${response.parsed.output_language_code}`
964
+ }.md`
895
965
  )
896
966
  await translatedReadme.save(response.parsed.translation)
967
+
968
+ languageCodes.add(response.parsed.output_language_code)
897
969
  })
898
970
  )
899
971
  })
900
972
  )
901
973
 
974
+ if (languageCodes.has("en")) {
975
+ languageCodes.delete("en")
976
+ languageCodes.add("us")
977
+ }
978
+
979
+ const currentLanguages = Object.keys(courseJson.title)
980
+ for (const languageCode of currentLanguages) {
981
+ if (languageCodes.has(languageCode)) {
982
+ languageCodes.delete(languageCode)
983
+ }
984
+ }
985
+
986
+ if ([...languageCodes].length > 0) {
987
+ console.log("TRANSLATING COURSE METADATA", languageCodes)
988
+
989
+ const translatedCourseMetadata = await Promise.all(
990
+ [...languageCodes].map(async languageCode => {
991
+ const result = await translateCourseMetadata(rigoToken, {
992
+ title: courseJson.title[currentLanguage],
993
+ description: courseJson.description[currentLanguage],
994
+ destination_lang_code: languageCode as string,
995
+ })
996
+
997
+ return {
998
+ languageCode,
999
+ title: result.parsed.title,
1000
+ description: result.parsed.description,
1001
+ }
1002
+ })
1003
+ )
1004
+
1005
+ for (const metadata of translatedCourseMetadata) {
1006
+ courseJson.title[metadata.languageCode as string] = metadata.title
1007
+ courseJson.description[metadata.languageCode as string] =
1008
+ metadata.description
1009
+ }
1010
+
1011
+ await uploadFileToBucket(
1012
+ bucket,
1013
+ JSON.stringify(courseJson),
1014
+ `courses/${courseSlug}/learn.json`
1015
+ )
1016
+
1017
+ const previewReadme = await bucket.file(
1018
+ `courses/${courseSlug}/README${
1019
+ currentLanguage === "us" || currentLanguage === "en" ?
1020
+ "" :
1021
+ `.${currentLanguage}`
1022
+ }.md`
1023
+ )
1024
+ const [previewReadmeContent] = await previewReadme.download()
1025
+ const previewReadmeContentString = previewReadmeContent.toString()
1026
+
1027
+ await Promise.all(
1028
+ [...languageCodes].map(async languageCode => {
1029
+ const translatedPreviewReadme = await translateExercise(
1030
+ rigoToken,
1031
+ {
1032
+ text_to_translate: previewReadmeContentString,
1033
+ output_language: languageCode as string,
1034
+ exercise_slug: "preview-readme",
1035
+ }
1036
+ )
1037
+
1038
+ await bucket
1039
+ .file(
1040
+ `courses/${courseSlug}/README${
1041
+ languageCode === "us" || languageCode === "en" ?
1042
+ "" :
1043
+ `.${languageCode}`
1044
+ }.md`
1045
+ )
1046
+ .save(translatedPreviewReadme.parsed.translation)
1047
+ })
1048
+ )
1049
+ }
1050
+
902
1051
  return res.status(200).json({ message: "Translated exercises" })
903
1052
  } catch (error) {
904
1053
  console.log(error, "ERROR")
@@ -907,6 +1056,8 @@ export default class ServeCommand extends SessionCommand {
907
1056
  })
908
1057
 
909
1058
  app.delete("/exercise/:slug/delete", async (req, res) => {
1059
+ console.log("DELETE /exercise/:slug/delete")
1060
+
910
1061
  const { slug } = req.params
911
1062
  const query = req.query
912
1063
  const courseSlug = query.slug
@@ -960,13 +1111,17 @@ export default class ServeCommand extends SessionCommand {
960
1111
  res.json(sidebar)
961
1112
  } catch (error: any) {
962
1113
  if (error.code === 404) {
963
- console.log("SIDEBAR FILE NOT FOUND", courseSlug)
964
-
965
1114
  const { exercises } = await buildConfig(bucket, courseSlug)
966
1115
 
967
1116
  const exerciseSlugsArray = exercises.map(exercise => exercise.slug)
968
1117
  const sidebar = await createInitialSidebar(exerciseSlugsArray)
969
1118
 
1119
+ await uploadFileToBucket(
1120
+ bucket,
1121
+ JSON.stringify(sidebar),
1122
+ `courses/${courseSlug}/.learn/sidebar.json`
1123
+ )
1124
+
970
1125
  if (rigoToken) {
971
1126
  const { fixedSidebar } = await checkAndFixSidebarPure(
972
1127
  sidebar,
@@ -981,20 +1136,9 @@ export default class ServeCommand extends SessionCommand {
981
1136
  )
982
1137
  }
983
1138
 
984
- await uploadFileToBucket(
985
- bucket,
986
- JSON.stringify(sidebar),
987
- `courses/${courseSlug}/.learn/sidebar.json`
988
- )
989
1139
  return res.status(200).json(fixedSidebar)
990
1140
  }
991
1141
 
992
- await uploadFileToBucket(
993
- bucket,
994
- JSON.stringify(sidebar),
995
- `courses/${courseSlug}/.learn/sidebar.json`
996
- )
997
-
998
1142
  return res.status(200).json(sidebar)
999
1143
  }
1000
1144
 
@@ -1003,35 +1147,6 @@ export default class ServeCommand extends SessionCommand {
1003
1147
  }
1004
1148
  })
1005
1149
 
1006
- // app.get("/test/:slug", (req, res) => {
1007
- // emitToCourse(req.params.slug, "course-creation", {
1008
- // lesson: "000-welcome-to-bird-domestication",
1009
- // status: "generating",
1010
- // log: "Hello",
1011
- // })
1012
- // emitToCourse(req.params.slug, "course-creation", {
1013
- // lesson: "000-welcome-to-bird-domestication",
1014
- // status: "generating",
1015
- // log: "Hello",
1016
- // })
1017
- // emitToCourse(req.params.slug, "course-creation", {
1018
- // lesson: "000-welcome-to-bird-domestication",
1019
- // status: "generating",
1020
- // log: "Hello broder",
1021
- // })
1022
- // emitToCourse(req.params.slug, "course-creation", {
1023
- // lesson: "000-welcome-to-bird-domestication",
1024
- // status: "done",
1025
- // log: "Hello broder",
1026
- // })
1027
- // emitToCourse(req.params.slug, "course-creation", {
1028
- // lesson: "other",
1029
- // status: "generating",
1030
- // log: "Hello",
1031
- // })
1032
- // res.send({ message: "Course creation started" })
1033
- // })
1034
-
1035
1150
  app.get("/technologies", async (req, res) => {
1036
1151
  console.log("GET /technologies")
1037
1152
 
@@ -1071,7 +1186,7 @@ export default class ServeCommand extends SessionCommand {
1071
1186
  return res.status(400).json({ error: "Missing tokens" })
1072
1187
  }
1073
1188
 
1074
- const courseSlug = slugify(syllabus.courseInfo.title)
1189
+ const courseSlug = syllabus.courseInfo.slug
1075
1190
 
1076
1191
  const tutorialDir = `courses/${courseSlug}`
1077
1192
  const learnJson = createLearnJson(syllabus.courseInfo)
@@ -1161,9 +1276,15 @@ export default class ServeCommand extends SessionCommand {
1161
1276
  lastResult
1162
1277
  )
1163
1278
 
1279
+ await createInitialReadme(
1280
+ JSON.stringify(syllabus.courseInfo),
1281
+ courseSlug,
1282
+ rigoToken
1283
+ )
1284
+
1164
1285
  return res.json({
1165
1286
  message: "Course created",
1166
- slug: slugify(syllabus.courseInfo.title),
1287
+ slug: syllabus.courseInfo.slug,
1167
1288
  })
1168
1289
  })
1169
1290
 
@@ -1331,12 +1452,6 @@ export default class ServeCommand extends SessionCommand {
1331
1452
  selectedLang = availableLangs[0]
1332
1453
  }
1333
1454
 
1334
- console.log(config.description, "CONFIG DESCRIPTION")
1335
- // console.log(availableLangs, "AVAILABLE LANGs")
1336
- // console.log(selectedLang, "SELECTED LANG")
1337
- // console.log(title, "TITLE")
1338
-
1339
- // 5) Inyectar placeholders en index.html
1340
1455
  const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8")
1341
1456
  const idxHtml = idxTpl
1342
1457
  .replace(/{{title}}/g, title)
@@ -1403,20 +1518,22 @@ export default class ServeCommand extends SessionCommand {
1403
1518
  {
1404
1519
  headers: {
1405
1520
  ...form.getHeaders(),
1406
- Authorization: `Token ${rigoToken}`,
1521
+ Authorization: `Token ${rigoToken.trim()}`,
1407
1522
  },
1408
1523
  }
1409
1524
  )
1410
1525
 
1411
- await handleAssetCreation(
1412
- { token: bcToken, rigobotToken: rigoToken },
1526
+ await createMultiLangAsset(
1527
+ bucket,
1528
+ rigoToken,
1529
+ bcToken,
1530
+ slug,
1413
1531
  fullConfig.config,
1414
- selectedLang,
1415
1532
  rigoRes.data.url
1416
1533
  )
1417
1534
 
1418
1535
  rimraf.sync(tmpRoot)
1419
-
1536
+ console.log("RigoRes", rigoRes.data)
1420
1537
  return res.json({ url: rigoRes.data.url })
1421
1538
  })
1422
1539
 
@@ -26,6 +26,7 @@ import ResumeCourseModal from "./components/ResumeCourseModal"
26
26
  import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
27
27
  import { useTranslation } from "react-i18next"
28
28
  import NotificationListener from "./components/NotificationListener"
29
+ import { slugify } from "./utils/creatorUtils"
29
30
 
30
31
  function App() {
31
32
  const navigate = useNavigate()
@@ -410,7 +411,8 @@ function App() {
410
411
  lessons,
411
412
  courseInfo: {
412
413
  ...formState,
413
- title: fixTitleLength(res.parsed.title),
414
+ title: res.parsed.title,
415
+ slug: slugify(fixTitleLength(res.parsed.title)),
414
416
  description: res.parsed.description,
415
417
  language:
416
418
  res.parsed.languageCode || formState.language || "en",
@@ -13,7 +13,7 @@ const NotificationListener: React.FC<NotificationListenerProps> = ({
13
13
  onNotification,
14
14
  }) => {
15
15
  useEffect(() => {
16
- console.log("NOTIFICATION ID listening", notificationId)
16
+
17
17
  if (!notificationId) return
18
18
 
19
19
  socketClient.connect()
@@ -13,7 +13,6 @@ import {
13
13
  // reWriteTitle,
14
14
  useConsumableCall,
15
15
  isValidRigoToken,
16
- fixTitleLength,
17
16
  } from "../../utils/lib"
18
17
 
19
18
  import Loader from "../Loader"
@@ -96,25 +95,23 @@ const SyllabusEditor: React.FC = () => {
96
95
  return
97
96
  }
98
97
 
99
- let slug = slugify(syllabus.courseInfo.title)
100
- let newTitle = syllabus.courseInfo.title
101
- let isAvailable = await isSlugAvailable(slug)
98
+ let newSlug = syllabus.courseInfo.slug
99
+ let isAvailable = await isSlugAvailable(syllabus.courseInfo.slug)
102
100
 
103
101
  while (!isAvailable) {
104
- if (newTitle.length > 50) {
105
- newTitle = newTitle.slice(0, 44)
102
+ if (newSlug.length > 50) {
103
+ newSlug = newSlug.slice(0, 44)
106
104
  }
107
- newTitle = newTitle + "-" + randomUUID()
108
- newTitle = newTitle.slice(0, 50)
109
- slug = slugify(newTitle)
110
- isAvailable = await isSlugAvailable(slug)
105
+ newSlug = newSlug + "-" + randomUUID()
106
+ newSlug = newSlug.slice(0, 50)
107
+ isAvailable = await isSlugAvailable(newSlug)
111
108
  }
112
109
 
113
110
  push({
114
111
  ...syllabus,
115
112
  courseInfo: {
116
113
  ...syllabus.courseInfo,
117
- title: newTitle,
114
+ slug: newSlug,
118
115
  },
119
116
  })
120
117
  }
@@ -230,6 +227,8 @@ const SyllabusEditor: React.FC = () => {
230
227
 
231
228
  if (!syllabus) return null
232
229
 
230
+ console.log(syllabus.courseInfo)
231
+
233
232
  return isGenerating ? (
234
233
  <>
235
234
  <Loader
@@ -253,8 +252,7 @@ It may take a moment..."
253
252
  lessons: lessons,
254
253
  courseInfo: {
255
254
  ...syllabus.courseInfo,
256
- title:
257
- fixTitleLength(res.parsed.title) || syllabus.courseInfo.title,
255
+ title: res.parsed.title || syllabus.courseInfo.title,
258
256
  description:
259
257
  res.parsed.description || syllabus.courseInfo.description,
260
258
  language:
@@ -15,12 +15,14 @@ export const publicInteractiveCreation = async (
15
15
  publicRequest: boolean = true
16
16
  ): Promise<any | null> => {
17
17
  try {
18
- const randomUID = randomUUID(10)
18
+ const randomUID = randomUUID(15)
19
19
  const webhookUrl = `${
20
20
  DEV_MODE
21
21
  ? "https://9cw5zmww-3000.use2.devtunnels.ms"
22
22
  : window.location.origin
23
+ // "https://9cw5zmww-3000.use2.devtunnels.ms"
23
24
  }/notifications/${randomUID}`
25
+
24
26
  const response = await axios.post(
25
27
  `${RIGOBOT_HOST}/v1/prompting${
26
28
  publicRequest ? "/public" : ""
@@ -42,7 +44,7 @@ export const publicInteractiveCreation = async (
42
44
  return { res: response.data, notificationId: randomUID }
43
45
  } catch (error: unknown) {
44
46
  const err = error as AxiosError
45
- console.log("error", err)
47
+ console.log("error trying to create course", err)
46
48
 
47
49
  if (err.response?.status === 403) {
48
50
  toast.error("You've reached the limit. Please log in to continue.")
@@ -11,6 +11,7 @@ export type FormState = {
11
11
  hasContentIndex: boolean
12
12
  contentIndex: string
13
13
  purpose: string
14
+ slug: string
14
15
  language?: string
15
16
  isCompleted: boolean
16
17
  variables: string[]
@@ -85,6 +86,7 @@ const useStore = create<Store>()(
85
86
  formState: {
86
87
  description: "",
87
88
  duration: 0,
89
+ slug: "",
88
90
  hasContentIndex: false,
89
91
  contentIndex: "",
90
92
  purpose: "",
@@ -119,6 +121,7 @@ const useStore = create<Store>()(
119
121
  resetFormState: () =>
120
122
  set({
121
123
  formState: {
124
+ slug: "",
122
125
  currentStep: "description",
123
126
  description: "",
124
127
  duration: 0,
@@ -169,7 +172,7 @@ const useStore = create<Store>()(
169
172
  formState: {
170
173
  description: "",
171
174
  duration: 0,
172
-
175
+ slug: "",
173
176
  language: "en",
174
177
  technologies: [],
175
178
  hasContentIndex: false,