@learnpack/learnpack 5.0.146 → 5.0.150

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.
@@ -98,6 +98,8 @@
98
98
  --text-2xl--line-height: calc(2 / 1.5);
99
99
  --text-4xl: 2.25rem;
100
100
  --text-4xl--line-height: calc(2.5 / 2.25);
101
+ --text-7xl: 4.5rem;
102
+ --text-7xl--line-height: 1;
101
103
  --font-weight-medium: 500;
102
104
  --font-weight-semibold: 600;
103
105
  --font-weight-bold: 700;
@@ -393,9 +395,6 @@
393
395
  .top-2 {
394
396
  top: calc(var(--spacing) * 2);
395
397
  }
396
- .right-0 {
397
- right: calc(var(--spacing) * 0);
398
- }
399
398
  .right-2 {
400
399
  right: calc(var(--spacing) * 2);
401
400
  }
@@ -426,11 +425,11 @@
426
425
  .z-10 {
427
426
  z-index: 10;
428
427
  }
429
- .z-20 {
430
- z-index: 20;
428
+ .z-30 {
429
+ z-index: 30;
431
430
  }
432
- .z-40 {
433
- z-index: 40;
431
+ .z-31 {
432
+ z-index: 31;
434
433
  }
435
434
  .z-50 {
436
435
  z-index: 50;
@@ -496,6 +495,12 @@
496
495
  .mt-6 {
497
496
  margin-top: calc(var(--spacing) * 6);
498
497
  }
498
+ .mt-10 {
499
+ margin-top: calc(var(--spacing) * 10);
500
+ }
501
+ .mt-14 {
502
+ margin-top: calc(var(--spacing) * 14);
503
+ }
499
504
  .mr-1 {
500
505
  margin-right: calc(var(--spacing) * 1);
501
506
  }
@@ -517,18 +522,12 @@
517
522
  .ml-auto {
518
523
  margin-left: auto;
519
524
  }
520
- .block {
521
- display: block;
522
- }
523
525
  .flex {
524
526
  display: flex;
525
527
  }
526
528
  .hidden {
527
529
  display: none;
528
530
  }
529
- .inline {
530
- display: inline;
531
- }
532
531
  .inline-block {
533
532
  display: inline-block;
534
533
  }
@@ -613,6 +612,9 @@
613
612
  .w-\[100px\] {
614
613
  width: 100px;
615
614
  }
615
+ .w-\[280px\] {
616
+ width: 280px;
617
+ }
616
618
  .w-full {
617
619
  width: 100%;
618
620
  }
@@ -707,9 +709,6 @@
707
709
  .gap-3 {
708
710
  gap: calc(var(--spacing) * 3);
709
711
  }
710
- .gap-4 {
711
- gap: calc(var(--spacing) * 4);
712
- }
713
712
  :where(.space-y-2 > :not(:last-child)) {
714
713
  --tw-space-y-reverse: 0;
715
714
  margin-block-start: calc(
@@ -899,6 +898,9 @@
899
898
  .p-8 {
900
899
  padding: calc(var(--spacing) * 8);
901
900
  }
901
+ .p-10 {
902
+ padding: calc(var(--spacing) * 10);
903
+ }
902
904
  .px-2 {
903
905
  padding-inline: calc(var(--spacing) * 2);
904
906
  }
@@ -908,6 +910,9 @@
908
910
  .px-4 {
909
911
  padding-inline: calc(var(--spacing) * 4);
910
912
  }
913
+ .px-20 {
914
+ padding-inline: calc(var(--spacing) * 20);
915
+ }
911
916
  .py-1 {
912
917
  padding-block: calc(var(--spacing) * 1);
913
918
  }
@@ -943,6 +948,10 @@
943
948
  font-size: var(--text-4xl);
944
949
  line-height: var(--tw-leading, var(--text-4xl--line-height));
945
950
  }
951
+ .text-7xl {
952
+ font-size: var(--text-7xl);
953
+ line-height: var(--tw-leading, var(--text-7xl--line-height));
954
+ }
946
955
  .text-lg {
947
956
  font-size: var(--text-lg);
948
957
  line-height: var(--tw-leading, var(--text-lg--line-height));
@@ -1257,10 +1266,6 @@ body {
1257
1266
  min-height: 100vh;
1258
1267
  margin: 0;
1259
1268
  }
1260
- h1 {
1261
- font-size: 3.2em;
1262
- line-height: 1.1;
1263
- }
1264
1269
  .bg-learnpack-blue {
1265
1270
  background-color: var(--soft-blue);
1266
1271
  }
@@ -10,8 +10,8 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-CzMCewx6.js"></script>
14
- <link rel="stylesheet" crossorigin href="/creator/assets/index-DSOj0E0h.css">
13
+ <script type="module" crossorigin src="/creator/assets/index-CkzbY9Qa.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/creator/assets/index-CztA582_.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":"5.0.146","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
1
+ {"version":"5.0.150","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false}},"args":[]},"breakToken":{"id":"breakToken","description":"Break the token","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"strict":{"name":"strict","type":"boolean","char":"s","description":"strict mode","allowNo":false},"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"serve":{"id":"serve","description":"Runs a small server to build tutorials","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]},"translate":{"id":"translate","description":"List all the lessons, the user is able of select many of them to translate to the given languages","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"yes":{"name":"yes","type":"boolean","char":"y","description":"Skip all prompts and initialize an empty project","allowNo":false}},"args":[]}}}
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.146",
4
+ "version": "5.0.150",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -45,6 +45,11 @@ dotenv.config()
45
45
  export const createLearnJson = (courseInfo: FormState) => {
46
46
  console.log("courseInfo to create learn json", courseInfo)
47
47
 
48
+ const expectedPreviewUrl = `https://${slugify(
49
+ courseInfo.title as string
50
+ )}.learn-pack.com/preview.png`
51
+ console.log("Preview url in generated learn.json", expectedPreviewUrl)
52
+
48
53
  const learnJson = {
49
54
  slug: slugify(courseInfo.title as string),
50
55
  title: {
@@ -59,7 +64,7 @@ export const createLearnJson = (courseInfo: FormState) => {
59
64
  telemetry: {
60
65
  batch: "https://breathecode.herokuapp.com/v1/assignment/me/telemetry",
61
66
  },
62
- preview: "preview.png",
67
+ preview: expectedPreviewUrl,
63
68
  }
64
69
  return learnJson
65
70
  }
@@ -292,7 +297,7 @@ const fixPreviewUrl = (slug: string, previewUrl: string) => {
292
297
  }
293
298
 
294
299
  const expectedUrl = `https://${slug}.learn-pack.com/preview.png`
295
- console.log("EXPECTED URL", expectedUrl)
300
+ console.log("Preview url fixed!", expectedUrl)
296
301
  return expectedUrl
297
302
  }
298
303
 
@@ -473,8 +478,13 @@ export default class ServeCommand extends SessionCommand {
473
478
 
474
479
  app.get("/config", async (req, res) => {
475
480
  const courseSlug = req.query.slug as string
481
+ // GEt the x-rigo-token
482
+ // const rigoToken = req.header("x-rigo-token")
483
+
476
484
  if (!courseSlug) {
477
- return res.status(400).json({ error: "Course slug required" })
485
+ return res
486
+ .status(400)
487
+ .json({ error: "Course slug and rigo token required" })
478
488
  }
479
489
 
480
490
  try {
@@ -878,15 +888,14 @@ export default class ServeCommand extends SessionCommand {
878
888
  const { slug } = req.params
879
889
  const rigoToken = req.header("x-rigo-token")
880
890
  const bcToken = req.header("x-breathecode-token")
881
- const { academyId, categoryId } = req.body
891
+ // const { academyId, categoryId } = req.body
882
892
 
883
- if (!rigoToken || !bcToken || !academyId || !categoryId) {
893
+ if (!rigoToken || !bcToken) {
884
894
  return res
885
895
  .status(400)
886
896
  .json({ error: "Faltan tokens o academy/category" })
887
897
  }
888
898
 
889
- // 2) Leer y construir config.json vía buildConfig
890
899
  const { config, exercises }: ConfigResponse = await buildConfig(
891
900
  bucket,
892
901
  slug
@@ -6,13 +6,14 @@ import { useNavigate } from "react-router"
6
6
  import { useShallow } from "zustand/react/shallow"
7
7
  import useStore from "./utils/store"
8
8
 
9
- import { interactiveCreation } from "./utils/rigo"
9
+ import { interactiveCreation, isHuman } from "./utils/rigo"
10
10
  import { checkParams, loginWithToken, parseLesson } from "./utils/lib"
11
11
 
12
12
  import { Uploader } from "./components/Uploader"
13
13
  import toast from "react-hot-toast"
14
14
  import { ParamsChecker } from "./components/ParamsChecker"
15
15
  import { RIGO_FLOAT_GIT } from "./utils/constants"
16
+ import TurnstileChallenge from "./components/TurnstileChallenge"
16
17
  // import TurnstileChallenge from "./components/TurnstileChallenge"
17
18
 
18
19
  function App() {
@@ -126,6 +127,7 @@ function App() {
126
127
  courseInfo: {
127
128
  ...formState,
128
129
  title: res.parsed.title,
130
+ description: res.parsed.description,
129
131
  },
130
132
  })
131
133
  navigate("/creator/syllabus")
@@ -212,7 +214,7 @@ function App() {
212
214
  <>
213
215
  <div className="flex flex-col md:flex-row gap-2 justify-center">
214
216
  <SelectableCard
215
- title="Yes"
217
+ title="📄 Yes"
216
218
  onClick={() => {
217
219
  setFormState({
218
220
  hasContentIndex: true,
@@ -224,20 +226,12 @@ function App() {
224
226
  selected={false}
225
227
  />
226
228
  <SelectableCard
227
- title="No, help me create one"
229
+ title="🛠️ No, help me create one"
228
230
  onClick={() => {
229
- if (!formState.contentIndex && !formState.description) {
230
- toast.error(
231
- "Please provide at least a description for your course!"
232
- )
233
- setFormState({
234
- currentStep: "description",
235
- })
236
- return
237
- }
238
231
  setFormState({
239
232
  hasContentIndex: false,
240
- isCompleted: true,
233
+ currentStep: "verifyHuman",
234
+ // isCompleted: true,
241
235
  })
242
236
  }}
243
237
  selected={false}
@@ -257,12 +251,35 @@ function App() {
257
251
  onFinish={(text) => {
258
252
  setFormState({
259
253
  contentIndex: text,
260
- isCompleted: true,
254
+ currentStep: "verifyHuman",
255
+ // isCompleted: true,
261
256
  })
262
257
  }}
263
258
  />
264
259
  ),
265
260
  },
261
+ {
262
+ title: "Please verify you are a human",
263
+ slug: "verifyHuman",
264
+ isCompleted: false,
265
+ content: (
266
+ <TurnstileChallenge
267
+ siteKey={"0x4AAAAAABeKMBYYinMU4Ib0"}
268
+ onSuccess={async (token) => {
269
+ const _isHuman = await isHuman(token)
270
+ if (_isHuman) {
271
+ toast.success("You are a human! 👌🏻")
272
+ setFormState({ isCompleted: true })
273
+ } else {
274
+ toast.error("You are not a human! 🤖")
275
+ setFormState({
276
+ currentStep: "hasContentIndex",
277
+ })
278
+ }
279
+ }}
280
+ />
281
+ ),
282
+ },
266
283
  ]
267
284
 
268
285
  return steps.filter(
@@ -274,8 +291,6 @@ function App() {
274
291
  return (
275
292
  <>
276
293
  <ParamsChecker />
277
- {/* <div id="turnstile-container" style={{ display: "none" }} /> */}
278
-
279
294
  {formState.isCompleted ? (
280
295
  <Loader
281
296
  text="Learnpack is setting up your tutorial. It may take a moment..."
@@ -16,6 +16,7 @@ export const ConsumablesManager = () => {
16
16
  const fetchConsumables = async () => {
17
17
  try {
18
18
  const consumables = await getConsumables(auth.bcToken)
19
+ console.log("CONSUMABLES", parseConsumables(consumables.voids))
19
20
  setConsumables(parseConsumables(consumables.voids))
20
21
  } catch (error) {
21
22
  setAuth({
@@ -1,21 +1,42 @@
1
- export const ContentCard = ({
1
+ import React from "react"
2
+
3
+ interface ContentCardProps {
4
+ description: string
5
+ icon: React.ReactNode
6
+ className?: string
7
+ children?: React.ReactNode
8
+ onClick?: () => void
9
+ onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void
10
+ onDrop?: (e: React.DragEvent<HTMLDivElement>) => void
11
+ onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void
12
+ onDragLeave?: (e: React.DragEvent<HTMLDivElement>) => void
13
+ }
14
+
15
+ export const ContentCard: React.FC<ContentCardProps> = ({
2
16
  description,
3
17
  icon,
18
+ className = "",
19
+ children,
4
20
  onClick,
5
- }: {
6
- description: string
7
- icon: React.ReactNode
8
- onClick: () => void
21
+ onDragOver,
22
+ onDrop,
23
+ onDragEnter,
24
+ onDragLeave,
9
25
  }) => {
10
26
  return (
11
27
  <div
12
- className="flex flex-col items-center justify-center gap-2 bg-white card p-2 rounded-lg shadow-md min-w-[250px] w-full h-[150px] cursor-pointer"
28
+ className={`flex flex-col items-center justify-center gap-2 bg-white card p-4 rounded-lg shadow-md min-w-[250px] w-full h-[150px] cursor-pointer transition border border-gray-200 hover:shadow-lg ${className}`}
13
29
  onClick={onClick}
30
+ onDragOver={onDragOver}
31
+ onDrop={onDrop}
32
+ onDragEnter={onDragEnter}
33
+ onDragLeave={onDragLeave}
14
34
  >
15
35
  <div className="text-blue-500 flex justify-center items-center">
16
36
  {icon}
17
37
  </div>
18
- <span className="text-sm text-center">{description}</span>
38
+ <span className="text-sm text-center text-gray-700">{description}</span>
39
+ {children}
19
40
  </div>
20
41
  )
21
42
  }
@@ -1,4 +1,4 @@
1
- import React, { useRef } from "react"
1
+ import React, { useRef, useState } from "react"
2
2
  import * as pdfjsLib from "pdfjs-dist"
3
3
  import mammoth from "mammoth"
4
4
  import { SVGS } from "../assets/svgs"
@@ -29,6 +29,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({
29
29
  styledAs = "button",
30
30
  }) => {
31
31
  const inputRef = useRef<HTMLInputElement>(null)
32
+ const [isDragging, setIsDragging] = useState(false)
32
33
 
33
34
  const extractText = async (file: File): Promise<ParsedFile> => {
34
35
  const { type, name } = file
@@ -60,42 +61,68 @@ const FileUploader: React.FC<FileUploaderProps> = ({
60
61
  return { name, text }
61
62
  }
62
63
 
63
- const handleFiles = async (e: React.ChangeEvent<HTMLInputElement>) => {
64
- const inputFiles = e.target.files
65
- if (!inputFiles) return
66
-
67
- const files = Array.from(inputFiles).filter((file) =>
64
+ const parseFiles = async (files: FileList | File[]) => {
65
+ const validFiles = Array.from(files).filter((file) =>
68
66
  allowedTypes.includes(file.type)
69
67
  )
70
- const parsed = await Promise.all(files.map(extractText))
68
+ const parsed = await Promise.all(validFiles.map(extractText))
69
+ console.log(parsed, "parsed files")
71
70
  onResult(parsed)
72
71
  }
73
72
 
73
+ const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
74
+ if (!e.target.files) return
75
+ parseFiles(e.target.files)
76
+ }
77
+
78
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
79
+ e.preventDefault()
80
+ setIsDragging(false)
81
+ if (e.dataTransfer.files.length > 0) {
82
+ parseFiles(e.dataTransfer.files)
83
+ }
84
+ }
85
+
74
86
  return (
75
87
  <>
76
88
  {styledAs === "button" && (
77
- <button
78
- type="button"
79
- className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
80
- onClick={() => inputRef.current?.click()}
89
+ <div
90
+ className="flex items-center justify-center"
91
+ onDragOver={() => setIsDragging(true)}
92
+ onDragLeave={() => setIsDragging(false)}
93
+ onDrop={handleDrop}
81
94
  >
82
- {SVGS.clip}
83
- </button>
95
+ <button
96
+ type="button"
97
+ className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
98
+ onClick={() => inputRef.current?.click()}
99
+ >
100
+ {SVGS.clip}
101
+ </button>
102
+ </div>
84
103
  )}
85
104
  {styledAs === "card" && (
86
105
  <ContentCard
87
- description="Upload a PDF"
88
- icon={SVGS.pdf}
106
+ description={
107
+ isDragging ? "Drop it here" : "Upload a PDF or drag it here"
108
+ }
109
+ icon={isDragging ? SVGS.clip : SVGS.pdf}
89
110
  onClick={() => inputRef.current?.click()}
111
+ onDragOver={(e) => {
112
+ e.preventDefault()
113
+ setIsDragging(true)
114
+ }}
115
+ onDragLeave={() => setIsDragging(false)}
116
+ onDrop={handleDrop}
117
+ className={isDragging ? "border-blue-600 bg-blue-50" : ""}
90
118
  />
91
119
  )}
92
-
93
120
  <input
94
121
  ref={inputRef}
95
122
  type="file"
96
123
  multiple
97
124
  accept=".pdf,.docx,.txt,.md"
98
- onChange={handleFiles}
125
+ onChange={handleInput}
99
126
  style={{ display: "none" }}
100
127
  />
101
128
  </>
@@ -46,7 +46,7 @@ const Loader: React.FC<LoaderProps> = ({
46
46
  className={`flex flex-col items-center justify-center bg-gray-50 text-center space-y-6 ${minheight}`}
47
47
  >
48
48
  <div className="text-blue-500 text-4xl animate-pulse">{icon}</div>
49
- <p className="text-gray-800 text-lg whitespace-pre-line">{text}</p>
49
+ <p className="text-gray-800 text-lg whitespace-pre-line px-4">{text}</p>
50
50
  {!buffer && (
51
51
  <div className="flex space-x-2 mt-2">
52
52
  {[0, 1, 2, 3].map((i) => (
@@ -34,10 +34,10 @@ const PreviewGenerator: React.FC = () => {
34
34
  html2canvas(previewElement, {
35
35
  useCORS: true,
36
36
  }).then(async (canvas) => {
37
- // const anchor = document.createElement("a")
38
- // anchor.href = canvas.toDataURL("image/png")
39
- // anchor.download = "preview.png"
40
- // anchor.click()
37
+ const anchor = document.createElement("a")
38
+ anchor.href = canvas.toDataURL("image/png")
39
+ anchor.download = "preview.png"
40
+ anchor.click()
41
41
 
42
42
  const imageUrl = canvas.toDataURL("image/png")
43
43
 
@@ -55,7 +55,7 @@ const PreviewGenerator: React.FC = () => {
55
55
  }, [])
56
56
 
57
57
  return (
58
- <div className="fixed">
58
+ <div className="fixed bg-white z-50 border-2 border-gray-200 ">
59
59
  <div
60
60
  id="preview"
61
61
  style={{
@@ -64,18 +64,20 @@ const PreviewGenerator: React.FC = () => {
64
64
  background: "white",
65
65
  }}
66
66
  >
67
- <div className="bg-learnpack-blue p-4 rounded-md" />
68
- <div className="px-4 -mt-5">
69
- <h1 className="text-2xl font-bold">{syllabus.courseInfo?.title}</h1>
70
- <p className="mt-5 text-sm">{syllabus.courseInfo?.description}</p>
71
- <div className="flex items-center gap-2 mt-5">
67
+ <div className="bg-learnpack-blue p-10 rounded-md" />
68
+ <div className="px-20 -mt-5">
69
+ <h1 className="text-7xl font-bold mt-14">
70
+ {syllabus.courseInfo?.title}
71
+ </h1>
72
+ {/* <p className="mt-5 text-sm">{syllabus.courseInfo?.description}</p> */}
73
+ <div className="flex items-center gap-2 mt-10">
72
74
  <img
73
75
  src={proxify(auth.user?.profile?.avatar_url || "")}
74
76
  alt="Profile"
75
77
  className="w-10 h-10 rounded-full mt-3"
76
78
  />
77
79
  <div>
78
- <p className=" text-sm font-bold">
80
+ <p className=" text-sm font-bold ">
79
81
  Author: {auth.user?.first_name} {auth.user?.last_name}
80
82
  </p>
81
83
  <small className=" text-sm">
@@ -61,18 +61,11 @@ const TurnstileChallenge = ({
61
61
  return () => clearInterval(interval)
62
62
  }, [siteKey, autoStart, onSuccess, onError])
63
63
 
64
- // const reset = () => {
65
- // if (widgetId.current && window.turnstile) {
66
- // window.turnstile.reset(widgetId.current)
67
- // }
68
- // }
69
64
 
70
65
  return (
71
- // <div className="flex flex-col gap-4 bg-white w-full fixed bottom-0 left-0 right-0 p-4 h-full items-center justify-center">
66
+
72
67
  <div className="">
73
- {/* <h3 className="text-center text-2xl font-bold">
74
- We must verify you are human
75
- </h3> */}
68
+
76
69
  <div id={containerId} data-refresh-timeout="auto" />
77
70
  {!ready && <p className="">...</p>}
78
71
  {/* <button onClick={reset}>RESET</button> */}
@@ -244,11 +244,11 @@ export const ContentIndex = ({
244
244
  {showScrollHint && !isThinking && (
245
245
  <div className="pointer-events-none relative">
246
246
  <div className="absolute bottom-0 left-0 w-full h-60 bg-gradient-to-t from-white to-transparent z-10" />
247
- <div className="absolute bottom-10 left-0 w-full flex justify-center z-20">
247
+ <div className="absolute bottom-10 left-0 w-full flex justify-center z-31">
248
248
  <button
249
249
  style={{ color: "#0084FF" }}
250
250
  onClick={() => scrollToBottom("bottom")}
251
- className="px-4 py-1 bg-white text-sm rounded hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2"
251
+ className="px-4 py-1 bg-white text-sm rounded hover:bg-blue-50 cursor-pointer pointer-events-auto font-bold flex items-center gap-2 "
252
252
  >
253
253
  Continue scrolling
254
254
  {SVGS.downArrow}