@learnpack/learnpack 5.0.53 → 5.0.57

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 (60) hide show
  1. package/README.md +30 -12
  2. package/lib/commands/publish.js +29 -7
  3. package/lib/commands/serve.d.ts +7 -0
  4. package/lib/commands/serve.js +149 -0
  5. package/lib/managers/server/routes.js +2 -0
  6. package/lib/utils/api.d.ts +4 -0
  7. package/lib/utils/api.js +21 -4
  8. package/lib/utils/cloudStorage.d.ts +8 -0
  9. package/lib/utils/cloudStorage.js +17 -0
  10. package/oclif.manifest.json +1 -1
  11. package/package.json +3 -1
  12. package/src/commands/publish.ts +68 -12
  13. package/src/commands/serve.ts +192 -0
  14. package/src/creator/README.md +54 -0
  15. package/src/creator/eslint.config.js +28 -0
  16. package/src/creator/index.html +13 -0
  17. package/src/creator/package-lock.json +4659 -0
  18. package/src/creator/package.json +41 -0
  19. package/src/creator/public/vite.svg +1 -0
  20. package/src/creator/src/App.css +42 -0
  21. package/src/creator/src/App.tsx +221 -0
  22. package/src/creator/src/assets/react.svg +1 -0
  23. package/src/creator/src/assets/svgs.tsx +88 -0
  24. package/src/creator/src/components/Loader.tsx +28 -0
  25. package/src/creator/src/components/Login.tsx +263 -0
  26. package/src/creator/src/components/SelectableCard.tsx +30 -0
  27. package/src/creator/src/components/StepWizard.tsx +77 -0
  28. package/src/creator/src/components/SyllabusEditor.tsx +431 -0
  29. package/src/creator/src/index.css +68 -0
  30. package/src/creator/src/main.tsx +19 -0
  31. package/src/creator/src/utils/configTypes.ts +122 -0
  32. package/src/creator/src/utils/constants.ts +2 -0
  33. package/src/creator/src/utils/lib.ts +36 -0
  34. package/src/creator/src/utils/rigo.ts +391 -0
  35. package/src/creator/src/utils/store.ts +78 -0
  36. package/src/creator/src/vite-env.d.ts +1 -0
  37. package/src/creator/tsconfig.app.json +26 -0
  38. package/src/creator/tsconfig.json +7 -0
  39. package/src/creator/tsconfig.node.json +24 -0
  40. package/src/creator/vite.config.ts +13 -0
  41. package/src/creatorDist/assets/index-D92OoEoU.js +23719 -0
  42. package/src/creatorDist/assets/index-tt9JBVY0.css +987 -0
  43. package/src/creatorDist/index.html +14 -0
  44. package/src/creatorDist/vite.svg +1 -0
  45. package/src/managers/server/routes.ts +3 -0
  46. package/src/ui/_app/app.css +1 -0
  47. package/src/ui/_app/app.js +3025 -0
  48. package/src/ui/_app/favicon.ico +0 -0
  49. package/src/ui/_app/index.html +109 -0
  50. package/src/ui/_app/index.html.backup +91 -0
  51. package/src/ui/_app/learnpack.svg +7 -0
  52. package/src/ui/_app/logo-192.png +0 -0
  53. package/src/ui/_app/logo-512.png +0 -0
  54. package/src/ui/_app/logo.png +0 -0
  55. package/src/ui/_app/manifest.webmanifest +21 -0
  56. package/src/ui/_app/sw.js +30 -0
  57. package/src/ui/app.tar.gz +0 -0
  58. package/src/utils/api.ts +24 -4
  59. package/src/utils/cloudStorage.ts +24 -0
  60. package/src/utils/creds.json +13 -0
@@ -0,0 +1,263 @@
1
+ import { useEffect, useState } from "react"
2
+ import { BREATHECODE_HOST, RIGOBOT_HOST } from "../utils/constants"
3
+ import { SVGS } from "../assets/svgs"
4
+ import toast from "react-hot-toast"
5
+ import useStore from "../utils/store"
6
+ import { useShallow } from "zustand/react/shallow"
7
+
8
+ type LoginInfo = {
9
+ email: string
10
+ password: string
11
+ }
12
+
13
+ const checkParams = () => {
14
+ const urlParams = new URLSearchParams(window.location.search)
15
+ const token = urlParams.get("token")
16
+
17
+ return { token }
18
+ }
19
+
20
+ const getRigobotJSON = async (breathecodeToken: string) => {
21
+ const rigoUrl = `${RIGOBOT_HOST}/v1/auth/me/token?breathecode_token=${breathecodeToken}`
22
+ const rigoResp = await fetch(rigoUrl)
23
+ if (!rigoResp.ok) {
24
+ throw new Error("Unable to obtain Rigobot token")
25
+ }
26
+ const rigobotJson = await rigoResp.json()
27
+ return rigobotJson
28
+ }
29
+ const validateUser = async (breathecodeToken: string) => {
30
+ const config = {
31
+ method: "GET",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ Authorization: `Token ${breathecodeToken}`,
35
+ },
36
+ }
37
+
38
+ const res = await fetch(`${BREATHECODE_HOST}/v1/auth/user/me`, config)
39
+ if (!res.ok) {
40
+ console.log("ERROR", res)
41
+ return null
42
+ }
43
+ const json = await res.json()
44
+
45
+ if ("roles" in json) {
46
+ delete json.roles
47
+ }
48
+ if ("permissions" in json) {
49
+ delete json.permissions
50
+ }
51
+ if ("settings" in json) {
52
+ delete json.settings
53
+ }
54
+
55
+ return json
56
+ }
57
+
58
+ const login4Geeks = async (loginInfo: LoginInfo) => {
59
+ const url = `${BREATHECODE_HOST}/v1/auth/login/`
60
+
61
+ const res = await fetch(url, {
62
+ body: JSON.stringify(loginInfo),
63
+ method: "post",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ },
67
+ })
68
+
69
+ if (!res.ok) {
70
+ throw Error("Unable to login with provided credentials")
71
+ }
72
+
73
+ const json = await res.json()
74
+
75
+ const rigoJson = await getRigobotJSON(json.token)
76
+
77
+ const user = await validateUser(json.token)
78
+ const returns = { ...json, rigobot: { ...rigoJson }, user }
79
+
80
+ return returns
81
+ }
82
+
83
+ const loginWithToken = async (token: string) => {
84
+ const rigoJson = await getRigobotJSON(token)
85
+
86
+ const user = await validateUser(token)
87
+
88
+ const returns = { rigobot: { ...rigoJson }, ...user }
89
+
90
+ return returns
91
+ }
92
+
93
+ export default function Login({ onFinish }: { onFinish: () => void }) {
94
+ const [email, setEmail] = useState("")
95
+ const [password, setPassword] = useState("")
96
+ const [isLoading, setIsLoading] = useState(false)
97
+
98
+ const [showForm, setShowForm] = useState(false)
99
+
100
+ const { setAuth } = useStore(
101
+ useShallow((state) => ({
102
+ setAuth: state.setAuth,
103
+ }))
104
+ )
105
+
106
+ const login = async (e: any) => {
107
+ setIsLoading(true)
108
+
109
+ const tid = toast.loading("Logging in...")
110
+ e.preventDefault()
111
+
112
+ if (!email || !password) {
113
+ // toast.error(t("please-fill-all-fields"))
114
+ setIsLoading(false)
115
+ return
116
+ }
117
+
118
+ const isLoggedId = await login4Geeks({
119
+ email: email,
120
+ password: password,
121
+ })
122
+
123
+ if (!isLoggedId) {
124
+ setIsLoading(false)
125
+ toast.error("Unable to login with provided credentials", { id: tid })
126
+ return
127
+ }
128
+
129
+ toast.success("Logged in successfully", { id: tid })
130
+
131
+ setAuth({
132
+ bcToken: isLoggedId.token,
133
+ userId: isLoggedId.user.id,
134
+ rigoToken: isLoggedId.rigobot.key,
135
+ })
136
+ setIsLoading(false)
137
+ onFinish()
138
+ }
139
+
140
+ function stringToBase64(str: string) {
141
+ return btoa(unescape(encodeURIComponent(str)))
142
+ }
143
+
144
+ function getCurrentUrlWithQueryParams() {
145
+ // let currentUrl = window.location.origin + window.location.pathname
146
+
147
+ return window.location.href
148
+ }
149
+
150
+ const redirectGithub = () => {
151
+ let currentUrl = getCurrentUrlWithQueryParams()
152
+
153
+ window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${stringToBase64(
154
+ currentUrl
155
+ )}`
156
+ }
157
+
158
+ useEffect(() => {
159
+ verifySession()
160
+ }, [])
161
+
162
+ const verifySession = async () => {
163
+ const { token } = checkParams()
164
+ if (token) {
165
+ const user = await loginWithToken(token)
166
+
167
+ if (user) {
168
+ setAuth({
169
+ bcToken: token,
170
+ userId: user.id,
171
+ rigoToken: user.rigobot.key,
172
+ })
173
+ onFinish()
174
+ }
175
+ }
176
+ }
177
+
178
+ return (
179
+ <>
180
+ <div className="max-w-sm mx-auto mt-10 bg-white p-8 rounded-xl shadow-md text-center">
181
+ <div className="flex justify-between items-center mb-4">
182
+ <h2 className="text-xl font-semibold">Login</h2>
183
+ <div className="bg-blue-50 text-sm p-2 rounded text-left">
184
+ <p className="text-gray-700 m-0">You don't have an account?</p>
185
+ <a
186
+ href="https://4geeks.com/pricing?plan=basic"
187
+ className="text-blue-600 font-medium"
188
+ >
189
+ Register here.
190
+ </a>
191
+ </div>
192
+ </div>
193
+
194
+ <p className="text-gray-600 mb-6">
195
+ Log in to 4Geeks to get performance statistics, access to our AI
196
+ mentor, and many other benefits
197
+ </p>
198
+
199
+ <button
200
+ onClick={redirectGithub}
201
+ className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4"
202
+ >
203
+ {SVGS.github}
204
+ LOGIN WITH GITHUB
205
+ </button>
206
+
207
+ <div className="flex items-center mb-4">
208
+ <hr className="flex-grow border-gray-300" />
209
+ <span className="mx-2 text-gray-400 text-sm">or</span>
210
+ <hr className="flex-grow border-gray-300" />
211
+ </div>
212
+
213
+ {showForm ? (
214
+ <form className="space-y-3" onSubmit={login}>
215
+ <input
216
+ type="email"
217
+ placeholder="Email"
218
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
219
+ onChange={(e) => setEmail(e.target.value)}
220
+ />
221
+ <input
222
+ type="password"
223
+ placeholder="Password"
224
+ className="w-full border border-gray-300 px-3 py-2 rounded-md"
225
+ onChange={(e) => setPassword(e.target.value)}
226
+ />
227
+ <div className="flex gap-2 mt-4">
228
+ <button
229
+ type="submit"
230
+ className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold"
231
+ >
232
+ {isLoading ? "Logging in..." : "Log in"}
233
+ </button>
234
+ <button
235
+ type="button"
236
+ onClick={() => setShowForm(false)}
237
+ className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold"
238
+ >
239
+ Skip
240
+ </button>
241
+ </div>
242
+ <div className="text-sm text-gray-600 mt-2">
243
+ Forgot your password?{" "}
244
+ <a
245
+ href={`${BREATHECODE_HOST}/v1/auth/password/reset?url=${getCurrentUrlWithQueryParams()}`}
246
+ className="text-sky-600 font-medium"
247
+ >
248
+ Recover it here.
249
+ </a>
250
+ </div>
251
+ </form>
252
+ ) : (
253
+ <button
254
+ onClick={() => setShowForm(true)}
255
+ className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold"
256
+ >
257
+ Login with Email
258
+ </button>
259
+ )}
260
+ </div>
261
+ </>
262
+ )
263
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react"
2
+
3
+ interface SelectableCardProps {
4
+ title: string
5
+ subtitle?: string
6
+ selected?: boolean
7
+ onClick: () => void
8
+ }
9
+
10
+ const SelectableCard: React.FC<SelectableCardProps> = ({
11
+ title,
12
+ selected = false,
13
+ subtitle = "",
14
+ onClick,
15
+ }) => {
16
+ return (
17
+ <div
18
+ className={`cursor-pointer bg-white rounded-lg shadow-md p-6 text-center transition-all
19
+ hover:shadow-lg border-2 ${
20
+ selected ? "border-blue-500" : "border-transparent"
21
+ }`}
22
+ onClick={onClick}
23
+ >
24
+ <p className="text-md font-medium">{title}</p>
25
+ {subtitle && <p className="text-sm text-gray-600">{subtitle}</p>}
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default SelectableCard
@@ -0,0 +1,77 @@
1
+ import React, { useEffect, useState } from "react"
2
+
3
+ export type Step = {
4
+ title: string
5
+ subtitle?: string
6
+ content: React.ReactNode
7
+ }
8
+
9
+ type Props = {
10
+ steps: Step[]
11
+ initialStep?: number
12
+ }
13
+
14
+ const StepWizard: React.FC<Props> = ({ steps, initialStep = 0 }) => {
15
+ const [currentStep, setCurrentStep] = useState(initialStep)
16
+ const totalSteps = steps.length
17
+
18
+ const goNext = () => {
19
+ if (currentStep < totalSteps - 1) setCurrentStep((s) => s + 1)
20
+ }
21
+
22
+ const goBack = () => {
23
+ if (currentStep > 0) setCurrentStep((s) => s - 1)
24
+ }
25
+
26
+ useEffect(() => {
27
+ setCurrentStep(initialStep)
28
+ }, [initialStep])
29
+
30
+ return (
31
+ <div className="min-h-screen flex flex-col items-center justify-center text-center px-4">
32
+ <div className=" rounded-xl p-8 w-full max-w-xl">
33
+ <h5 className="text-sm text-blue-400 uppercase mb-2">
34
+ {steps[currentStep].subtitle || "Setting up your tutorial"}
35
+ </h5>
36
+ <h2 className="text-lg font-medium mb-6">
37
+ <span className="text-blue-600 mr-1">{currentStep + 1}.</span>
38
+ {steps[currentStep].title}
39
+ </h2>
40
+
41
+ <div className="mb-6">{steps[currentStep].content}</div>
42
+
43
+ {/* Dot Indicators */}
44
+ <div className="flex justify-center mb-4 space-x-2">
45
+ {steps.map((_, i) => (
46
+ <span
47
+ key={i}
48
+ className={`h-2 w-2 rounded-full ${
49
+ i === currentStep ? "bg-blue-600" : "bg-gray-300"
50
+ }`}
51
+ />
52
+ ))}
53
+ </div>
54
+
55
+ {/* Navigation */}
56
+ <div className="flex justify-between">
57
+ <button
58
+ onClick={goBack}
59
+ disabled={currentStep === 0}
60
+ className="text-sm text-gray-500 hover:text-gray-900 disabled:opacity-40"
61
+ >
62
+ ⬅ Back
63
+ </button>
64
+ <button
65
+ onClick={goNext}
66
+ disabled={currentStep === totalSteps - 1}
67
+ className="text-sm text-blue-600 hover:text-blue-800 disabled:opacity-40"
68
+ >
69
+ Next ➡
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ )
75
+ }
76
+
77
+ export default StepWizard