@learnpack/learnpack 5.0.261 → 5.0.265

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.
@@ -10,7 +10,7 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-BvHkfJm4.js"></script>
13
+ <script type="module" crossorigin src="/creator/assets/index-6e9E-1qG.js"></script>
14
14
  <link rel="stylesheet" crossorigin href="/creator/assets/index-DmpsXknz.css">
15
15
  </head>
16
16
  <body>
@@ -305,8 +305,7 @@ async function default_1(app, configObject, configManager) {
305
305
  const response = await (0, rigoActions_1.translateExercise)(rigoToken, {
306
306
  text_to_translate: readme.body,
307
307
  output_language: language,
308
- exercise_slug: slug,
309
- });
308
+ }, `${process.env.HOST}/webhooks/translate-exercise`);
310
309
  await (0, creatorUtilities_1.saveTranslatedReadme)(slug, response.parsed.output_language_code, response.parsed.translation);
311
310
  (0, sidebarGenerator_1.addExerciseToSidebar)(slug, response.parsed.output_language_code, response.parsed.translated_slug, configDirPath || "");
312
311
  console_1.default.success(`Translated ${slug} to ${language} successfully`);
@@ -11,10 +11,12 @@ export interface ParsedLink {
11
11
  name: string;
12
12
  text: string;
13
13
  }
14
+ export type TDifficulty = "easy" | "beginner" | "intermediate" | "hard";
14
15
  export type FormState = {
15
16
  description: string;
16
17
  duration: number;
17
18
  hasContentIndex: boolean;
19
+ difficulty: TDifficulty;
18
20
  contentIndex: string;
19
21
  language?: string;
20
22
  technologies?: string[];
@@ -19,9 +19,8 @@ export declare function downloadImage(imageUrl: string, savePath: string): Promi
19
19
  type TTranslateInputs = {
20
20
  text_to_translate: string;
21
21
  output_language: string;
22
- exercise_slug: string;
23
22
  };
24
- export declare const translateExercise: (token: string, inputs: TTranslateInputs) => Promise<any>;
23
+ export declare const translateExercise: (token: string, inputs: TTranslateInputs, webhookUrl: string) => Promise<any>;
25
24
  type TGenerateCourseIntroductionInputs = {
26
25
  course_title: string;
27
26
  lessons_context: string;
@@ -63,7 +62,7 @@ export declare const createStructuredPreviewReadme: (token: string, inputs: TCre
63
62
  export declare const translateCourseMetadata: (token: string, inputs: {
64
63
  title: string;
65
64
  description: string;
66
- destination_lang_code: string;
65
+ new_languages: string;
67
66
  }) => Promise<any>;
68
67
  export declare function createPreviewReadme(tutorialDir: string, packageInfo: PackageInfo, rigoToken: string, readmeContents: string[]): Promise<void>;
69
68
  type TReduceReadmeInputs = {
@@ -88,4 +87,8 @@ export declare const isPackageAuthor: (token: string, packageSlug: string) => Pr
88
87
  isAuthor: boolean;
89
88
  status: number;
90
89
  }>;
90
+ type TGetLanguageCodesInputs = {
91
+ raw_languages: string;
92
+ };
93
+ export declare const getLanguageCodes: (token: string, inputs: TGetLanguageCodesInputs) => Promise<any>;
91
94
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.translateCourseMetadata = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
3
+ exports.getLanguageCodes = exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.translateCourseMetadata = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
4
4
  exports.downloadImage = downloadImage;
5
5
  exports.createPreviewReadme = createPreviewReadme;
6
6
  exports.makeReadmeReadable = makeReadmeReadable;
@@ -79,11 +79,12 @@ async function downloadImage(imageUrl, savePath) {
79
79
  const buffer = Buffer.from(response.data, "binary");
80
80
  await (0, promises_1.writeFile)(savePath, buffer);
81
81
  }
82
- const translateExercise = async (token, inputs) => {
83
- const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/159/`, {
82
+ const translateExercise = async (token, inputs, webhookUrl) => {
83
+ const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/translate-asset-markdown/`, {
84
84
  inputs: inputs,
85
85
  include_purpose_objective: false,
86
- execute_async: false,
86
+ execute_async: true,
87
+ webhook_url: webhookUrl,
87
88
  }, {
88
89
  headers: {
89
90
  "Content-Type": "application/json",
@@ -272,3 +273,13 @@ const isPackageAuthor = async (token, packageSlug) => {
272
273
  }
273
274
  };
274
275
  exports.isPackageAuthor = isPackageAuthor;
276
+ const getLanguageCodes = async (token, inputs) => {
277
+ const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/get-language-codes/`, { inputs, include_purpose_objective: false, execute_async: false }, {
278
+ headers: {
279
+ "Content-Type": "application/json",
280
+ Authorization: "Token " + token,
281
+ },
282
+ });
283
+ return response.data;
284
+ };
285
+ exports.getLanguageCodes = getLanguageCodes;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.261",
4
+ "version": "5.0.265",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -31,6 +31,7 @@ import {
31
31
  isPackageAuthor,
32
32
  createStructuredPreviewReadme,
33
33
  translateCourseMetadata,
34
+ getLanguageCodes,
34
35
  } from "../utils/rigoActions"
35
36
  import * as dotenv from "dotenv"
36
37
  import {
@@ -519,11 +520,25 @@ export default class ServeCommand extends SessionCommand {
519
520
  const { courseSlug } = req.params
520
521
  const body = req.body
521
522
 
522
- // Save the file as courses/courseSlug/README.md
523
+ console.log("RECEIVING INITIAL README WEBHOOK", body)
524
+
525
+ const langCode =
526
+ body.parsed.language_code || body.parsed.output_language_code || ""
527
+
528
+ const content = body.parsed.content || body.parsed.translation || ""
529
+
530
+ if (!content || !langCode) {
531
+ console.log("No content or language code to save", body)
532
+ return res.status(400).json({
533
+ status: "ERROR",
534
+ message: "No content to save",
535
+ })
536
+ }
537
+
523
538
  const filePath = `courses/${courseSlug}/README${getReadmeExtension(
524
- body.parsed.language_code
539
+ langCode
525
540
  )}`
526
- await uploadFileToBucket(bucket, body.parsed.content, filePath)
541
+ await uploadFileToBucket(bucket, content, filePath)
527
542
 
528
543
  res.json({ status: "SUCCESS" })
529
544
  }
@@ -707,6 +722,25 @@ export default class ServeCommand extends SessionCommand {
707
722
  }
708
723
  )
709
724
 
725
+ // The following endpoint is used to store an incoming translation where it supposed to be
726
+ app.post(
727
+ "/webhooks/:courseSlug/:exSlug/save-translation",
728
+ async (req, res) => {
729
+ const { courseSlug, exSlug } = req.params
730
+ const body = req.body
731
+
732
+ console.log("RECEIVING TRANSLATION WEBHOOK", body)
733
+
734
+ const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${getReadmeExtension(
735
+ body.parsed.output_language_code
736
+ )}`
737
+
738
+ await uploadFileToBucket(bucket, body.parsed.translation, readmePath)
739
+
740
+ res.json({ status: "SUCCESS" })
741
+ }
742
+ )
743
+
710
744
  app.get("/check-preview-image/:slug", async (req, res) => {
711
745
  const { slug } = req.params
712
746
  const file = bucket.file(`courses/${slug}/preview.png`)
@@ -948,13 +982,10 @@ export default class ServeCommand extends SessionCommand {
948
982
  }
949
983
 
950
984
  const languagesToTranslate: string[] = languages.split(",")
951
-
952
- const course = await bucket
953
- .file(`courses/${courseSlug}/learn.json`)
954
- .download()
955
-
956
- const courseJson = JSON.parse(course.toString())
957
- const languageCodes = new Set()
985
+ const languageCodesRes = await getLanguageCodes(rigoToken, {
986
+ raw_languages: languagesToTranslate.join(","),
987
+ })
988
+ const languageCodes = languageCodesRes.parsed.language_codes
958
989
 
959
990
  try {
960
991
  await Promise.all(
@@ -962,59 +993,68 @@ export default class ServeCommand extends SessionCommand {
962
993
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
963
994
  currentLanguage
964
995
  )}`
965
-
966
996
  const readme = await bucket.file(readmePath).download()
967
997
 
968
998
  await Promise.all(
969
- languagesToTranslate.map(async (language: string) => {
970
- const response = await translateExercise(rigoToken, {
971
- text_to_translate: readme.toString(),
972
- output_language: language,
973
- exercise_slug: slug,
974
- })
975
-
976
- const translatedReadme = await bucket.file(
977
- `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
978
- response.parsed.output_language_code
979
- )}`
999
+ languageCodes.map(async (language: string) => {
1000
+ // verify if the translation already exists
1001
+ const translationPath = `courses/${courseSlug}/exercises/${slug}/README${getReadmeExtension(
1002
+ language
1003
+ )}`
1004
+
1005
+ const [exists] = await bucket.file(translationPath).exists()
1006
+ if (exists) {
1007
+ console.log(
1008
+ `Translation in ${language} already exists for exercise ${slug}`
1009
+ )
1010
+ return
1011
+ }
1012
+
1013
+ await translateExercise(
1014
+ rigoToken,
1015
+ {
1016
+ text_to_translate: readme.toString(),
1017
+ output_language: language,
1018
+ },
1019
+ `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`
980
1020
  )
981
- await translatedReadme.save(response.parsed.translation)
982
-
983
- languageCodes.add(response.parsed.output_language_code)
984
1021
  })
985
1022
  )
986
1023
  })
987
1024
  )
988
1025
 
989
- const currentLanguages = Object.keys(courseJson.title)
990
- for (const languageCode of currentLanguages) {
991
- if (languageCodes.has(languageCode)) {
992
- languageCodes.delete(languageCode)
1026
+ const course = await bucket
1027
+ .file(`courses/${courseSlug}/learn.json`)
1028
+ .download()
1029
+
1030
+ const courseJson = JSON.parse(course.toString())
1031
+
1032
+ const neededLanguages = new Set<string>()
1033
+
1034
+ let currentLanguages = Object.keys(courseJson.title)
1035
+
1036
+ for (const languageCode of languageCodes) {
1037
+ if (!currentLanguages.includes(languageCode)) {
1038
+ neededLanguages.add(languageCode)
993
1039
  }
994
1040
  }
995
1041
 
996
- if ([...languageCodes].length > 0) {
997
- const translatedCourseMetadata = await Promise.all(
998
- [...languageCodes].map(async languageCode => {
999
- const result = await translateCourseMetadata(rigoToken, {
1000
- title: courseJson.title[currentLanguage],
1001
- description: courseJson.description[currentLanguage],
1002
- destination_lang_code: languageCode as string,
1003
- })
1042
+ const neededLanguagesList: string[] = [...neededLanguages]
1043
+ .map((lang: string) => lang.toLowerCase())
1044
+ .sort()
1004
1045
 
1005
- return {
1006
- languageCode,
1007
- title: result.parsed.title,
1008
- description: result.parsed.description,
1009
- }
1010
- })
1011
- )
1046
+ if (neededLanguagesList.length > 0) {
1047
+ const result = await translateCourseMetadata(rigoToken, {
1048
+ title: JSON.stringify(courseJson.title),
1049
+ description: JSON.stringify(courseJson.description),
1050
+ new_languages: neededLanguagesList.join(","),
1051
+ })
1012
1052
 
1013
- for (const metadata of translatedCourseMetadata) {
1014
- courseJson.title[metadata.languageCode as string] = metadata.title
1015
- courseJson.description[metadata.languageCode as string] =
1016
- metadata.description
1017
- }
1053
+ const translateTitle = JSON.parse(result.parsed.title)
1054
+ const translateDescription = JSON.parse(result.parsed.description)
1055
+
1056
+ courseJson.title = translateTitle
1057
+ courseJson.description = translateDescription
1018
1058
 
1019
1059
  await uploadFileToBucket(
1020
1060
  bucket,
@@ -1022,34 +1062,45 @@ export default class ServeCommand extends SessionCommand {
1022
1062
  `courses/${courseSlug}/learn.json`
1023
1063
  )
1024
1064
 
1065
+ currentLanguages = Object.keys(courseJson.title)
1066
+ }
1067
+
1068
+ // Check that all the READMEs exists
1069
+
1070
+ const missingReadmeTranslations = []
1071
+ let firstAvailable = ""
1072
+ for (const languageCode of currentLanguages) {
1073
+ // eslint-disable-next-line no-await-in-loop
1025
1074
  const previewReadme = await bucket.file(
1026
- `courses/${courseSlug}/README${getReadmeExtension(currentLanguage)}`
1075
+ `courses/${courseSlug}/README${getReadmeExtension(languageCode)}`
1027
1076
  )
1028
- const [previewReadmeContent] = await previewReadme.download()
1029
- const previewReadmeContentString = previewReadmeContent.toString()
1030
-
1031
- await Promise.all(
1032
- [...languageCodes].map(async languageCode => {
1033
- const translatedPreviewReadme = await translateExercise(
1034
- rigoToken,
1035
- {
1036
- text_to_translate: previewReadmeContentString,
1037
- output_language: languageCode as string,
1038
- exercise_slug: "preview-readme",
1039
- }
1040
- )
1041
1077
 
1042
- await bucket
1043
- .file(
1044
- `courses/${courseSlug}/README${getReadmeExtension(
1045
- languageCode as string
1046
- )}`
1047
- )
1048
- .save(translatedPreviewReadme.parsed.translation)
1049
- })
1050
- )
1078
+ // eslint-disable-next-line no-await-in-loop
1079
+ const [exists] = await previewReadme.exists()
1080
+ if (!exists) {
1081
+ missingReadmeTranslations.push(languageCode)
1082
+ } else {
1083
+ // eslint-disable-next-line no-await-in-loop
1084
+ const [previewReadmeContent] = await previewReadme.download()
1085
+ const previewReadmeContentString = previewReadmeContent.toString()
1086
+ firstAvailable = previewReadmeContentString
1087
+ }
1051
1088
  }
1052
1089
 
1090
+ await Promise.all(
1091
+ missingReadmeTranslations.map(async languageCode => {
1092
+
1093
+ await translateExercise(
1094
+ rigoToken,
1095
+ {
1096
+ text_to_translate: firstAvailable,
1097
+ output_language: languageCode as string,
1098
+ },
1099
+ `${process.env.HOST}/webhooks/${courseSlug}/initial-readme-processor`
1100
+ )
1101
+ })
1102
+ )
1103
+
1053
1104
  return res.status(200).json({ message: "Translated exercises" })
1054
1105
  } catch (error) {
1055
1106
  console.log(error, "ERROR")
@@ -96,11 +96,14 @@ export default class BuildCommand extends SessionCommand {
96
96
  .split(",")
97
97
  .map(async (language: string) => {
98
98
  const readme = await getReadmeForExercise(exercise)
99
- const response = await translateExercise(rigoToken, {
100
- text_to_translate: readme,
101
- output_language: language,
102
- exercise_slug: exercise,
103
- })
99
+ const response = await translateExercise(
100
+ rigoToken,
101
+ {
102
+ text_to_translate: readme,
103
+ output_language: language,
104
+ },
105
+ `${process.env.HOST}/webhooks/translate-exercise`
106
+ )
104
107
 
105
108
  await saveTranslatedReadme(
106
109
  exercise,
@@ -4,7 +4,7 @@ import SelectableCard from "./components/SelectableCard"
4
4
  import Loader from "./components/Loader"
5
5
  import { useNavigate } from "react-router"
6
6
  import { useShallow } from "zustand/react/shallow"
7
- import useStore from "./utils/store"
7
+ import useStore, { TDifficulty } from "./utils/store"
8
8
 
9
9
  import { publicInteractiveCreation, isHuman } from "./utils/rigo"
10
10
  import {
@@ -106,6 +106,7 @@ function App() {
106
106
  purpose,
107
107
  language,
108
108
  new: newParam,
109
+ difficulty,
109
110
  } = checkParams([
110
111
  "description",
111
112
  "duration",
@@ -113,6 +114,7 @@ function App() {
113
114
  "purpose",
114
115
  "language",
115
116
  "new",
117
+ "difficulty",
116
118
  ])
117
119
 
118
120
  if (newParam && newParam.toLowerCase().trim() === "true") {
@@ -155,6 +157,15 @@ function App() {
155
157
  })
156
158
  }
157
159
 
160
+ if (
161
+ difficulty &&
162
+ ["easy", "beginner", "intermediate", "hard"].includes(difficulty)
163
+ ) {
164
+ setFormState({
165
+ difficulty: difficulty as TDifficulty,
166
+ })
167
+ }
168
+
158
169
  if (description && duration && purpose) {
159
170
  setFormState({
160
171
  currentStep: "hasContentIndex",
@@ -363,11 +374,14 @@ function App() {
363
374
  })
364
375
  }
365
376
  }}
366
- // onError={() => {
367
- // setFormState({
368
- // currentStep: "purpose",
369
- // })
370
- // }}
377
+ onError={() => {
378
+ toast.error(t("turnstileModal.error"), {
379
+ duration: 10000,
380
+ })
381
+ setFormState({
382
+ currentStep: "purpose",
383
+ })
384
+ }}
371
385
  />
372
386
  ),
373
387
  },
@@ -444,6 +458,15 @@ function App() {
444
458
  setShowTurnstileModal(false)
445
459
  handleCreateTutorial()
446
460
  }}
461
+ onError={() => {
462
+ toast.error(t("turnstileModal.error"), {
463
+ duration: 10000,
464
+ })
465
+ setShowTurnstileModal(false)
466
+ setFormState({
467
+ currentStep: "purpose",
468
+ })
469
+ }}
447
470
  />
448
471
  )}
449
472
  {formState.isCompleted && history.length === 0 ? (
@@ -36,17 +36,17 @@ export const PurposeSelector: React.FC<PurposeSelectorProps> = ({
36
36
  // description: t("purposeSelector.learnpack-lesson-writer.description"),
37
37
  // },
38
38
  {
39
- slug: "homework-and-exam-preparation-aid",
40
- label: t("purposeSelector.homework-and-exam-preparation-aid.label"),
39
+ slug: "skill-building-facilitator",
40
+ label: t("purposeSelector.skill-building-facilitator.label"),
41
41
  description: t(
42
- "purposeSelector.homework-and-exam-preparation-aid.description"
42
+ "purposeSelector.skill-building-facilitator.description"
43
43
  ),
44
44
  },
45
45
  {
46
- slug: "skill-building-facilitator",
47
- label: t("purposeSelector.skill-building-facilitator.label"),
46
+ slug: "homework-and-exam-preparation-aid",
47
+ label: t("purposeSelector.homework-and-exam-preparation-aid.label"),
48
48
  description: t(
49
- "purposeSelector.skill-building-facilitator.description"
49
+ "purposeSelector.homework-and-exam-preparation-aid.description"
50
50
  ),
51
51
  },
52
52
  {
@@ -5,32 +5,47 @@ import { toast } from "react-hot-toast"
5
5
  import useStore from "../utils/store"
6
6
  import { useTranslation } from "react-i18next"
7
7
 
8
- export default function TurnstileModal({ onClose }: { onClose: () => void }) {
8
+ export default function TurnstileModal({
9
+ onClose,
10
+ onError,
11
+ }: {
12
+ onClose: () => void
13
+ onError: () => void
14
+ }) {
9
15
  const auth = useStore((state) => state.auth)
10
16
  const setAuth = useStore((state) => state.setAuth)
11
17
  const { t } = useTranslation()
12
18
 
13
19
  return (
14
20
  <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000">
15
- <TurnstileChallenge
16
- siteKey={
17
- DEV_MODE ? "0x4AAAAAABeKMBYYinMU4Ib0" : "0x4AAAAAABeZ9tjEevGBsJFU"
18
- }
19
- onSuccess={async (token) => {
20
- const { human, message, token: jwtToken } = await isHuman(token)
21
- if (human) {
22
- toast.success(t("stepWizard.humanSuccess"))
23
- setAuth({
24
- ...auth,
25
- publicToken: jwtToken,
26
- })
27
- onClose()
28
- } else {
29
- toast.error(message)
30
- onClose()
21
+ <div className="bg-white p-4 rounded-lg max-w-md w-full flex flex-col items-center justify-center gap-4">
22
+ <h2 className="text-xl font-semibold text-center">
23
+ {t("turnstileModal.title")}
24
+ </h2>
25
+ <TurnstileChallenge
26
+ siteKey={
27
+ DEV_MODE ? "0x4AAAAAABeKMBYYinMU4Ib0" : "0x4AAAAAABeZ9tjEevGBsJFU"
31
28
  }
32
- }}
33
- />
29
+ onSuccess={async (token) => {
30
+ const { human, message, token: jwtToken } = await isHuman(token)
31
+ if (human) {
32
+ toast.success(t("stepWizard.humanSuccess"))
33
+ setAuth({
34
+ ...auth,
35
+ publicToken: jwtToken,
36
+ })
37
+ onClose()
38
+ } else {
39
+ console.error(message, "turnstileModal error")
40
+ onError()
41
+ }
42
+ }}
43
+ onError={() => {
44
+ console.error("turnstileModal error")
45
+ onError()
46
+ }}
47
+ />
48
+ </div>
34
49
  </div>
35
50
  )
36
51
  }
@@ -115,5 +115,9 @@
115
115
  "loginWithEmail": "Login with Email",
116
116
  "youDontHaveAnAccount": "You don't have an account?",
117
117
  "registerHere": "Register here."
118
+ },
119
+ "turnstileModal": {
120
+ "title": "We need to verify you are a human, wait a moment...",
121
+ "error": "Error verifying you are a human, please try again, if the problem persists, use a different browser."
118
122
  }
119
123
  }
@@ -115,5 +115,9 @@
115
115
  "loginWithEmail": "Iniciar sesión con Email",
116
116
  "youDontHaveAnAccount": "¿No tienes una cuenta?",
117
117
  "registerHere": "Regístrate aquí."
118
+ },
119
+ "turnstileModal": {
120
+ "title": "Necesitamos verificar que eres un humano, espera un momento...",
121
+ "error": "Error al verificar que eres un humano, por favor intenta de nuevo, si el problema persiste, usa un navegador diferente."
118
122
  }
119
123
  }
@@ -4,6 +4,7 @@ import { Lesson } from "../components/LessonItem"
4
4
  import { ParsedFile } from "../components/FileUploader"
5
5
  import { TMessage } from "../components/Message"
6
6
  // import { ParsedLink } from "../components/LinkUploader"
7
+ export type TDifficulty = "easy" | "beginner" | "intermediate" | "hard"
7
8
 
8
9
  export type FormState = {
9
10
  description: string
@@ -11,6 +12,7 @@ export type FormState = {
11
12
  hasContentIndex: boolean
12
13
  contentIndex: string
13
14
  purpose: string
15
+ difficulty: TDifficulty
14
16
  slug: string
15
17
  language?: string
16
18
  isCompleted: boolean
@@ -92,6 +94,7 @@ const useStore = create<Store>()(
92
94
  purpose: "",
93
95
  language: "en",
94
96
  technologies: [],
97
+ difficulty: "beginner",
95
98
  // sources: [],
96
99
  isCompleted: false,
97
100
  currentStep: "description",
@@ -126,6 +129,7 @@ const useStore = create<Store>()(
126
129
  description: "",
127
130
  duration: 0,
128
131
  language: "en",
132
+ difficulty: "beginner",
129
133
  technologies: [],
130
134
  hasContentIndex: false,
131
135
  contentIndex: "",
@@ -173,6 +177,7 @@ const useStore = create<Store>()(
173
177
  description: "",
174
178
  duration: 0,
175
179
  slug: "",
180
+ difficulty: "beginner",
176
181
  language: "en",
177
182
  technologies: [],
178
183
  hasContentIndex: false,