@learnpack/learnpack 5.0.172 → 5.0.178

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 (33) hide show
  1. package/README.md +13 -13
  2. package/lib/commands/serve.js +42 -2
  3. package/lib/creatorDist/assets/index-BvrB0WCf.js +32991 -0
  4. package/{src/creatorDist/assets/index-C_YTggyk.css → lib/creatorDist/assets/index-CrWESWmj.css} +43 -11
  5. package/lib/creatorDist/index.html +2 -2
  6. package/lib/utils/api.js +1 -0
  7. package/lib/utils/readDocuments.d.ts +0 -0
  8. package/lib/utils/readDocuments.js +1 -0
  9. package/oclif.manifest.json +1 -1
  10. package/package.json +3 -1
  11. package/src/commands/serve.ts +56 -2
  12. package/src/creator/src/App.tsx +51 -33
  13. package/src/creator/src/components/ConsumablesManager.tsx +1 -0
  14. package/src/creator/src/components/FileUploader.tsx +64 -52
  15. package/src/creator/src/components/Login.tsx +172 -82
  16. package/src/creator/src/components/ResumeCourseModal.tsx +38 -0
  17. package/src/creator/src/components/StepWizard.tsx +12 -10
  18. package/src/creator/src/components/TurnstileChallenge.tsx +2 -7
  19. package/src/creator/src/components/syllabus/ContentIndex.tsx +1 -0
  20. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +54 -20
  21. package/src/creator/src/utils/constants.ts +1 -0
  22. package/src/creator/src/utils/lib.ts +55 -0
  23. package/src/creator/src/utils/rigo.ts +11 -4
  24. package/src/creator/src/utils/store.ts +22 -1
  25. package/src/creatorDist/assets/index-BvrB0WCf.js +32991 -0
  26. package/{lib/creatorDist/assets/index-C_YTggyk.css → src/creatorDist/assets/index-CrWESWmj.css} +43 -11
  27. package/src/creatorDist/index.html +2 -2
  28. package/src/utils/api.ts +1 -0
  29. package/src/utils/readDocuments.ts +0 -0
  30. package/lib/creatorDist/assets/index-CCvMFC6N.js +0 -83701
  31. package/lib/creatorDist/assets/pdf.worker-DSVOJ9H9.js +0 -56037
  32. package/src/creatorDist/assets/index-CCvMFC6N.js +0 -83701
  33. package/src/creatorDist/assets/pdf.worker-DSVOJ9H9.js +0 -56037
@@ -1,12 +1,8 @@
1
1
  import React, { useRef, useState } from "react"
2
- import * as pdfjsLib from "pdfjs-dist"
3
- import mammoth from "mammoth"
4
2
  import { SVGS } from "../assets/svgs"
5
- import pdfWorker from "pdfjs-dist/build/pdf.worker?worker"
6
3
  import { ContentCard } from "./ContentCard"
7
4
  import useStore from "../utils/store"
8
-
9
- pdfjsLib.GlobalWorkerOptions.workerPort = new pdfWorker()
5
+ import toast from "react-hot-toast"
10
6
 
11
7
  const allowedTypes = [
12
8
  "application/pdf",
@@ -21,53 +17,64 @@ export interface ParsedFile {
21
17
  }
22
18
 
23
19
  interface FileUploaderProps {
24
- // onResult: (files: ParsedFile[]) => void
25
20
  styledAs?: "button" | "card"
26
21
  }
27
22
 
28
23
  const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
24
+ const rigoToken = useStore((state) => state.auth.rigoToken)
29
25
  const inputRef = useRef<HTMLInputElement>(null)
30
26
  const uploadedFiles = useStore((state) => state.uploadedFiles)
31
27
  const setUploadedFiles = useStore((state) => state.setUploadedFiles)
32
28
  const [isDragging, setIsDragging] = useState(false)
33
- // const [parsedFiles, setParsedFiles] = useState<ParsedFile[]>([]) F
29
+ const [isLoading, setIsLoading] = useState(false)
34
30
 
35
- const extractText = async (file: File): Promise<ParsedFile> => {
31
+ const extractTextFromFile = async (file: File): Promise<ParsedFile> => {
36
32
  const { type, name } = file
37
33
 
38
- if (type === "application/pdf") {
39
- const arrayBuffer = await file.arrayBuffer()
40
- const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise
41
- let text = ""
42
-
43
- for (let i = 0; i < pdf.numPages; i++) {
44
- const page = await pdf.getPage(i + 1)
45
- const content = await page.getTextContent()
46
- text += content.items.map((item: any) => item.str).join(" ") + "\n"
47
- }
48
-
34
+ if (type === "text/plain" || type === "text/markdown") {
35
+ const text = await file.text()
49
36
  return { name, text }
50
37
  }
51
38
 
52
- if (
53
- type ===
54
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
55
- ) {
56
- const arrayBuffer = await file.arrayBuffer()
57
- const { value } = await mammoth.extractRawText({ arrayBuffer })
58
- return { name, text: value }
39
+ const formData = new FormData()
40
+ formData.append("file", file)
41
+
42
+ const loadingToast = toast.loading(`Processing ${file.name}...`)
43
+ try {
44
+ const res = await fetch("http://localhost:3000/read-document", {
45
+ method: "POST",
46
+ headers: {
47
+ "x-rigo-token": rigoToken,
48
+ },
49
+ body: formData,
50
+ })
51
+
52
+ if (!res.ok) throw new Error(`Failed to read ${file.name}`)
53
+ const data = await res.json()
54
+ console.log("DATA FROM BACKEND", data)
55
+ toast.success(`✅ ${file.name} processed`, { id: loadingToast })
56
+ return { name, text: data.content }
57
+ } catch (err: any) {
58
+ toast.error(`❌ ${file.name} failed: ${err.message}`, {
59
+ id: loadingToast,
60
+ })
61
+ return { name, text: "" }
59
62
  }
60
-
61
- const text = await file.text()
62
- return { name, text }
63
63
  }
64
64
 
65
65
  const parseFiles = async (files: FileList | File[]) => {
66
66
  const validFiles = Array.from(files).filter((file) =>
67
67
  allowedTypes.includes(file.type)
68
68
  )
69
- const parsed = await Promise.all(validFiles.map(extractText))
69
+ if (validFiles.length === 0) {
70
+ toast.error("No valid files selected")
71
+ return
72
+ }
73
+
74
+ setIsLoading(true)
75
+ const parsed = await Promise.all(validFiles.map(extractTextFromFile))
70
76
  setUploadedFiles([...uploadedFiles, ...parsed])
77
+ setIsLoading(false)
71
78
  }
72
79
 
73
80
  const handleInput = async (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -86,16 +93,16 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
86
93
  return (
87
94
  <div className="flex flex-col gap-2 w-full">
88
95
  {uploadedFiles.length > 0 && styledAs === "card" && (
89
- <div className="w-full flex flex-row gap-2 flex-wrap justify-center items-center ">
96
+ <div className="w-full flex flex-row gap-2 flex-wrap justify-center items-center">
90
97
  {uploadedFiles.map((file, idx) => (
91
98
  <div
92
99
  key={idx}
93
- className="p-3 rounded-md bg-white shadow-sm text-sm text-gray-800 text-left "
100
+ className="p-3 rounded-md bg-white shadow-sm text-sm text-gray-800 text-left"
94
101
  title={file.name}
95
102
  >
96
103
  <strong>{file.name.slice(0, 20)}...</strong>
97
104
  <button
98
- className="text-gray-600 mt-1 float-right cursor-pointer"
105
+ className="text-gray-600 mt-1 float-right cursor-pointer"
99
106
  onClick={() =>
100
107
  setUploadedFiles(uploadedFiles.filter((_, i) => i !== idx))
101
108
  }
@@ -106,6 +113,7 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
106
113
  ))}
107
114
  </div>
108
115
  )}
116
+
109
117
  {styledAs === "button" && (
110
118
  <div className="flex items-center justify-end gap-2 w-100">
111
119
  <button
@@ -113,30 +121,34 @@ const FileUploader: React.FC<FileUploaderProps> = ({ styledAs = "button" }) => {
113
121
  className="cursor-pointer blue-on-hover flex items-center justify-center w-6 h-6"
114
122
  onClick={() => inputRef.current?.click()}
115
123
  >
116
- {SVGS.clip}
124
+ {isLoading ? (
125
+ <div className="loader w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
126
+ ) : (
127
+ SVGS.clip
128
+ )}
117
129
  </button>
118
130
  </div>
119
131
  )}
120
132
 
121
133
  {styledAs === "card" && (
122
- <>
123
- <ContentCard
124
- description={
125
- isDragging
126
- ? "Drop it here"
127
- : "Upload a PDF or DOCX file or drag it here"
128
- }
129
- icon={isDragging ? SVGS.clip : SVGS.pdf}
130
- onClick={() => inputRef.current?.click()}
131
- onDragOver={(e) => {
132
- e.preventDefault()
133
- setIsDragging(true)
134
- }}
135
- onDragLeave={() => setIsDragging(false)}
136
- onDrop={handleDrop}
137
- className={isDragging ? "border-blue-600 bg-blue-50" : ""}
138
- />
139
- </>
134
+ <ContentCard
135
+ description={
136
+ isDragging
137
+ ? "Drop it here"
138
+ : isLoading
139
+ ? "Processing..."
140
+ : "Upload a PDF or DOCX file or drag it here"
141
+ }
142
+ icon={isDragging ? SVGS.clip : SVGS.pdf}
143
+ onClick={() => inputRef.current?.click()}
144
+ onDragOver={(e) => {
145
+ e.preventDefault()
146
+ setIsDragging(true)
147
+ }}
148
+ onDragLeave={() => setIsDragging(false)}
149
+ onDrop={handleDrop}
150
+ className={isDragging ? "border-blue-600 bg-blue-50" : ""}
151
+ />
140
152
  )}
141
153
 
142
154
  <input
@@ -4,61 +4,88 @@ import { SVGS } from "../assets/svgs"
4
4
  import toast from "react-hot-toast"
5
5
  import useStore from "../utils/store"
6
6
  import { useShallow } from "zustand/react/shallow"
7
- import { login4Geeks } from "../utils/lib"
7
+ import { login4Geeks, registerUserWithFormData } from "../utils/lib"
8
8
 
9
9
  export default function Login({ onFinish }: { onFinish: () => void }) {
10
+ // Login states
10
11
  const [email, setEmail] = useState("")
11
12
  const [password, setPassword] = useState("")
12
13
  const [isLoading, setIsLoading] = useState(false)
13
14
  const [showForm, setShowForm] = useState(false)
14
- const planToRedirect = useStore((state) => state.planToRedirect)
15
-
15
+ // Signup states
16
+ const [showSignup, setShowSignup] = useState(false)
17
+ const [signupData, setSignupData] = useState({
18
+ firstName: "",
19
+ lastName: "",
20
+ email: "",
21
+ })
22
+ // const planToRedirect = useStore((state) => state.planToRedirect)
16
23
  const { setAuth } = useStore(
17
24
  useShallow((state) => ({ setAuth: state.setAuth }))
18
25
  )
19
26
 
20
- const login = async (e: React.FormEvent) => {
27
+ // Login handler
28
+ const login = async (e: React.FormEvent<HTMLFormElement>) => {
21
29
  e.preventDefault()
22
30
  setIsLoading(true)
23
31
  const tid = toast.loading("Logging in…")
24
-
25
32
  if (!email || !password) {
26
33
  setIsLoading(false)
27
34
  toast.error("Please fill all fields", { id: tid })
28
35
  return
29
36
  }
30
-
31
37
  const resp = await login4Geeks({ email, password })
32
38
  if (!resp) {
33
39
  setIsLoading(false)
34
40
  toast.error("Invalid credentials", { id: tid })
35
41
  return
36
42
  }
37
-
38
43
  toast.success("Logged in successfully", { id: tid })
39
44
  setAuth({
40
45
  bcToken: resp.token,
41
46
  userId: resp.user.id,
42
47
  rigoToken: resp.rigobot.key,
43
48
  user: resp.user,
49
+ publicToken: "",
44
50
  })
45
51
  setIsLoading(false)
46
52
  onFinish()
47
53
  }
48
54
 
55
+ // Signup handler
56
+ const handleSignup = async (e: React.FormEvent<HTMLFormElement>) => {
57
+ e.preventDefault()
58
+ setIsLoading(true)
59
+ const tid = toast.loading("Creating account…")
60
+ const { firstName, lastName, email } = signupData
61
+ if (!firstName || !lastName || !email) {
62
+ setIsLoading(false)
63
+ toast.error("Please fill all fields", { id: tid })
64
+ return
65
+ }
66
+ try {
67
+ await registerUserWithFormData(firstName, lastName, email)
68
+ toast.success("Account created! Check your email.", { id: tid })
69
+ setShowSignup(false)
70
+ setShowForm(true)
71
+ setEmail(email)
72
+ } catch (err) {
73
+ toast.error("Registration failed. Try again.", { id: tid })
74
+ } finally {
75
+ setIsLoading(false)
76
+ }
77
+ }
78
+
49
79
  function stringToBase64(str: string) {
50
80
  return btoa(unescape(encodeURIComponent(str)))
51
81
  }
52
-
53
82
  function getCurrentUrlWithQueryParams() {
54
83
  return window.location.href
55
84
  }
56
-
57
85
  const redirectGithub = () => {
58
86
  const url = stringToBase64(getCurrentUrlWithQueryParams())
59
87
  window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
60
88
  }
61
-
62
89
  const redirectGoogle = () => {
63
90
  const url = stringToBase64(getCurrentUrlWithQueryParams())
64
91
  window.location.href = `${BREATHECODE_HOST}/v1/auth/google?url=${url}`
@@ -73,89 +100,152 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
73
100
  className="bg-white p-8 rounded-xl shadow-md max-w-sm w-full"
74
101
  onClick={(e) => e.stopPropagation()}
75
102
  >
76
- <p className="mb-4 text-center text-gray-700">
77
- You need to have a 4Geeks account with a creator plan to create a
78
- tutorial.
79
- </p>
80
- <button
81
- onClick={redirectGithub}
82
- className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
83
- >
84
- {SVGS.github} LOGIN WITH GITHUB
85
- </button>
86
- <button
87
- onClick={redirectGoogle}
88
- className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
89
- >
90
- {SVGS.google} LOGIN WITH GOOGLE
91
- </button>
92
-
93
- <div className="flex items-center mb-4">
94
- <hr className="flex-grow border-gray-300" />
95
- <span className="mx-2 text-gray-400 text-sm">or</span>
96
- <hr className="flex-grow border-gray-300" />
97
- </div>
98
-
99
- {showForm ? (
100
- <form className="space-y-3" onSubmit={login}>
101
- <input
102
- type="email"
103
- placeholder="Email"
104
- className="w-full border border-gray-300 px-3 py-2 rounded-md"
105
- onChange={(e) => setEmail(e.target.value)}
106
- />
107
- <input
108
- type="password"
109
- placeholder="Password"
110
- className="w-full border border-gray-300 px-3 py-2 rounded-md"
111
- onChange={(e) => setPassword(e.target.value)}
112
- />
113
- <div className="flex gap-2 mt-4">
103
+ {showSignup ? (
104
+ <>
105
+ <h2 className="mb-4 text-xl font-semibold text-center">
106
+ Create your account
107
+ </h2>
108
+ <form className="space-y-3" onSubmit={handleSignup}>
109
+ <input
110
+ type="text"
111
+ placeholder="First name"
112
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
113
+ value={signupData.firstName}
114
+ onChange={(e) =>
115
+ setSignupData({ ...signupData, firstName: e.target.value })
116
+ }
117
+ />
118
+ <input
119
+ type="text"
120
+ placeholder="Last name"
121
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
122
+ value={signupData.lastName}
123
+ onChange={(e) =>
124
+ setSignupData({ ...signupData, lastName: e.target.value })
125
+ }
126
+ />
127
+ <input
128
+ type="email"
129
+ placeholder="Email"
130
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
131
+ value={signupData.email}
132
+ onChange={(e) =>
133
+ setSignupData({ ...signupData, email: e.target.value })
134
+ }
135
+ />
114
136
  <button
115
137
  type="submit"
116
- className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold cursor-pointer"
138
+ className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
139
+ disabled={isLoading}
117
140
  >
118
- {isLoading ? "Logging in..." : "Log in"}
141
+ {isLoading ? "Creating..." : "Sign up"}
119
142
  </button>
143
+ </form>
144
+ <div className="mt-4 text-sm text-center">
145
+ Already have an account?{" "}
120
146
  <button
121
- type="button"
122
- onClick={() => setShowForm(false)}
123
- className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold cursor-pointer"
147
+ className="text-blue-600 font-medium"
148
+ onClick={() => {
149
+ setShowSignup(false)
150
+ setShowForm(true)
151
+ }}
124
152
  >
125
- Skip
153
+ Log in here
126
154
  </button>
127
155
  </div>
128
- <div className="text-sm text-gray-600 mt-2">
129
- Forgot your password?{" "}
130
- <a
131
- href={`${BREATHECODE_HOST}/v1/auth/password/reset?url=${getCurrentUrlWithQueryParams()}`}
132
- className="text-sky-600 font-medium"
156
+ </>
157
+ ) : (
158
+ <>
159
+ <p className="mb-4 text-center text-gray-700">
160
+ You need to have a 4Geeks account with a creator plan to create a
161
+ tutorial.
162
+ </p>
163
+ <button
164
+ onClick={redirectGithub}
165
+ className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
166
+ >
167
+ {SVGS.github} LOGIN WITH GITHUB
168
+ </button>
169
+ <button
170
+ onClick={redirectGoogle}
171
+ className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
172
+ >
173
+ {SVGS.google} LOGIN WITH GOOGLE
174
+ </button>
175
+ <div className="flex items-center mb-4">
176
+ <hr className="flex-grow border-gray-300" />
177
+ <span className="mx-2 text-gray-400 text-sm">or</span>
178
+ <hr className="flex-grow border-gray-300" />
179
+ </div>
180
+ {showForm ? (
181
+ <form className="space-y-3" onSubmit={login}>
182
+ <input
183
+ type="email"
184
+ placeholder="Email"
185
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
186
+ value={email}
187
+ onChange={(e) => setEmail(e.target.value)}
188
+ />
189
+ <input
190
+ type="password"
191
+ placeholder="Password"
192
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
193
+ value={password}
194
+ onChange={(e) => setPassword(e.target.value)}
195
+ />
196
+ <div className="flex gap-2 mt-4">
197
+ <button
198
+ type="submit"
199
+ className="flex-1 bg-blue-500 text-white py-2 rounded-md font-semibold cursor-pointer"
200
+ >
201
+ {isLoading ? "Logging in..." : "Log in"}
202
+ </button>
203
+ <button
204
+ type="button"
205
+ onClick={() => setShowForm(false)}
206
+ className="flex-1 border border-blue-500 text-blue-600 py-2 rounded-md font-semibold cursor-pointer"
207
+ >
208
+ Skip
209
+ </button>
210
+ </div>
211
+ <div className="text-sm text-gray-600 mt-2">
212
+ Forgot your password?{" "}
213
+ <a
214
+ href={`${BREATHECODE_HOST}/v1/auth/password/reset?url=${getCurrentUrlWithQueryParams()}`}
215
+ className="text-blue-600 font-medium"
216
+ >
217
+ Recover it here.
218
+ </a>
219
+ </div>
220
+ </form>
221
+ ) : (
222
+ <button
223
+ onClick={() => setShowForm(true)}
224
+ className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
133
225
  >
134
- Recover it here.
135
- </a>
226
+ Login with Email
227
+ </button>
228
+ )}
229
+ <div className="flex justify-between items-center mt-4">
230
+ <div className="bg-blue-50 text-sm p-2 rounded text-left">
231
+ <p className="text-gray-700 m-0">You don't have an account?</p>
232
+ {/* <button
233
+ className="text-blue-600 font-medium cursor-pointer"
234
+ onClick={() => setShowSignup(true)}
235
+ >
236
+ Register here.
237
+ </button> */}
238
+ <a
239
+ href={`https://www.learnpack.co/register`}
240
+ target="_blank"
241
+ className="text-blue-600 font-medium"
242
+ >
243
+ Register here.
244
+ </a>
245
+ </div>
136
246
  </div>
137
- </form>
138
- ) : (
139
- <button
140
- onClick={() => setShowForm(true)}
141
- className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
142
- >
143
- Login with Email
144
- </button>
247
+ </>
145
248
  )}
146
-
147
- <div className="flex justify-between items-center mt-4">
148
- <div className="bg-blue-50 text-sm p-2 rounded text-left">
149
- <p className="text-gray-700 m-0">You don't have an account?</p>
150
- <a
151
- href={`https://4geeks.com/checkout?plan=${planToRedirect}`}
152
- target="_blank"
153
- className="text-blue-600 font-medium"
154
- >
155
- Register here.
156
- </a>
157
- </div>
158
- </div>
159
249
  </div>
160
250
  </div>
161
251
  )
@@ -0,0 +1,38 @@
1
+ interface ResumeCourseModalProps {
2
+ onContinue: () => void
3
+ onStartOver: () => void
4
+ }
5
+
6
+ export default function ResumeCourseModal({
7
+ onContinue,
8
+ onStartOver,
9
+ }: ResumeCourseModalProps) {
10
+ return (
11
+ <div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
12
+ <div className="bg-white shadow-xl rounded-lg p-8 max-w-md w-full animate-fade-in">
13
+ <h2 className="text-xl font-bold text-gray-800 mb-3 text-center">
14
+ You have an unfinished course
15
+ </h2>
16
+ <p className="text-gray-600 mb-6 text-center">
17
+ You didn't finish creating your previous course.
18
+ <br />
19
+ What would you like to do?
20
+ </p>
21
+ <div className="flex flex-col gap-4">
22
+ <button
23
+ onClick={onContinue}
24
+ className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-md font-semibold transition cursor-pointer"
25
+ >
26
+ Continue previous course
27
+ </button>
28
+ <button
29
+ onClick={onStartOver}
30
+ className="w-full border border-blue-600 text-blue-700 py-2 rounded-md font-semibold hover:bg-blue-50 transition cursor-pointer"
31
+ >
32
+ Erase previous answers and start a new course
33
+ </button>
34
+ </div>
35
+ </div>
36
+ </div>
37
+ )
38
+ }
@@ -17,6 +17,7 @@ type Props = {
17
17
  formState: any
18
18
  setFormState: (formState: any) => void
19
19
  onFinish: () => void
20
+ hideLastButton?: boolean
20
21
  }
21
22
 
22
23
  const StepWizard: React.FC<Props> = ({
@@ -24,6 +25,7 @@ const StepWizard: React.FC<Props> = ({
24
25
  formState,
25
26
  setFormState,
26
27
  onFinish,
28
+ hideLastButton = false,
27
29
  }) => {
28
30
  const currentStep = formState.currentStep
29
31
  const index = steps.findIndex((step) => step.slug === currentStep)
@@ -48,8 +50,6 @@ const StepWizard: React.FC<Props> = ({
48
50
  setFormState({ ...formState, currentStep: steps[index - 1].slug })
49
51
  }
50
52
 
51
- console.log(index, totalSteps, steps)
52
-
53
53
  return (
54
54
  <div className="min-h-screen flex flex-col items-center justify-center text-center px-4">
55
55
  <div className=" rounded-xl p-8 w-full max-w-xl">
@@ -99,14 +99,16 @@ const StepWizard: React.FC<Props> = ({
99
99
  >
100
100
  ⬅ Back
101
101
  </button>
102
- <button
103
- onClick={index === totalSteps - 1 ? onFinish : goNext}
104
- className={`text-sm text-blue-600 hover:text-blue-800 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed p-2 rounded-md ${
105
- index === totalSteps - 1 ? "bg-blue-600 text-white" : ""
106
- }`}
107
- >
108
- {index === totalSteps - 1 ? "Finish 🚀" : "Next ➡"}
109
- </button>
102
+ {!(hideLastButton && index === totalSteps - 1) && (
103
+ <button
104
+ onClick={index === totalSteps - 1 ? onFinish : goNext}
105
+ className={`text-sm text-blue-600 hover:text-blue-800 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed p-2 rounded-md ${
106
+ index === totalSteps - 1 ? "bg-blue-600 text-white" : ""
107
+ }`}
108
+ >
109
+ {index === totalSteps - 1 ? "Finish 🚀" : "Next ➡"}
110
+ </button>
111
+ )}
110
112
  </div>
111
113
  </div>
112
114
  </div>
@@ -1,6 +1,6 @@
1
1
  // components/TurnstileChallenge.tsx
2
2
  import { useEffect, useRef, useState } from "react"
3
- import toast from "react-hot-toast"
3
+ // import toast from "react-hot-toast"
4
4
 
5
5
  interface TurnstileChallengeProps {
6
6
  siteKey: string
@@ -34,8 +34,6 @@ const TurnstileChallenge = ({
34
34
  const id = window.turnstile.render(`#${containerId}`, {
35
35
  sitekey: siteKey,
36
36
  callback: (token: string) => {
37
- console.log("token to send", token)
38
-
39
37
  onSuccess(token)
40
38
  },
41
39
  "error-callback": () => {
@@ -43,7 +41,7 @@ const TurnstileChallenge = ({
43
41
  },
44
42
  "refresh-callback": () => {
45
43
  console.log("refresh callback CALLEd")
46
- toast.error("Refresh callback called")
44
+ // toast.error("Refresh callback called")
47
45
  },
48
46
  })
49
47
 
@@ -61,11 +59,8 @@ const TurnstileChallenge = ({
61
59
  return () => clearInterval(interval)
62
60
  }, [siteKey, autoStart, onSuccess, onError])
63
61
 
64
-
65
62
  return (
66
-
67
63
  <div className="">
68
-
69
64
  <div id={containerId} data-refresh-timeout="auto" />
70
65
  {!ready && <p className="">...</p>}
71
66
  {/* <button onClick={reset}>RESET</button> */}
@@ -126,6 +126,7 @@ export const GenerateButton = ({
126
126
  bcToken: "",
127
127
  userId: "",
128
128
  user: null,
129
+ publicToken: "",
129
130
  })
130
131
  openLogin()
131
132
  }}