@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.
- package/README.md +30 -12
- package/lib/commands/publish.js +29 -7
- package/lib/commands/serve.d.ts +7 -0
- package/lib/commands/serve.js +149 -0
- package/lib/managers/server/routes.js +2 -0
- package/lib/utils/api.d.ts +4 -0
- package/lib/utils/api.js +21 -4
- package/lib/utils/cloudStorage.d.ts +8 -0
- package/lib/utils/cloudStorage.js +17 -0
- package/oclif.manifest.json +1 -1
- package/package.json +3 -1
- package/src/commands/publish.ts +68 -12
- package/src/commands/serve.ts +192 -0
- package/src/creator/README.md +54 -0
- package/src/creator/eslint.config.js +28 -0
- package/src/creator/index.html +13 -0
- package/src/creator/package-lock.json +4659 -0
- package/src/creator/package.json +41 -0
- package/src/creator/public/vite.svg +1 -0
- package/src/creator/src/App.css +42 -0
- package/src/creator/src/App.tsx +221 -0
- package/src/creator/src/assets/react.svg +1 -0
- package/src/creator/src/assets/svgs.tsx +88 -0
- package/src/creator/src/components/Loader.tsx +28 -0
- package/src/creator/src/components/Login.tsx +263 -0
- package/src/creator/src/components/SelectableCard.tsx +30 -0
- package/src/creator/src/components/StepWizard.tsx +77 -0
- package/src/creator/src/components/SyllabusEditor.tsx +431 -0
- package/src/creator/src/index.css +68 -0
- package/src/creator/src/main.tsx +19 -0
- package/src/creator/src/utils/configTypes.ts +122 -0
- package/src/creator/src/utils/constants.ts +2 -0
- package/src/creator/src/utils/lib.ts +36 -0
- package/src/creator/src/utils/rigo.ts +391 -0
- package/src/creator/src/utils/store.ts +78 -0
- package/src/creator/src/vite-env.d.ts +1 -0
- package/src/creator/tsconfig.app.json +26 -0
- package/src/creator/tsconfig.json +7 -0
- package/src/creator/tsconfig.node.json +24 -0
- package/src/creator/vite.config.ts +13 -0
- package/src/creatorDist/assets/index-D92OoEoU.js +23719 -0
- package/src/creatorDist/assets/index-tt9JBVY0.css +987 -0
- package/src/creatorDist/index.html +14 -0
- package/src/creatorDist/vite.svg +1 -0
- package/src/managers/server/routes.ts +3 -0
- package/src/ui/_app/app.css +1 -0
- package/src/ui/_app/app.js +3025 -0
- package/src/ui/_app/favicon.ico +0 -0
- package/src/ui/_app/index.html +109 -0
- package/src/ui/_app/index.html.backup +91 -0
- package/src/ui/_app/learnpack.svg +7 -0
- package/src/ui/_app/logo-192.png +0 -0
- package/src/ui/_app/logo-512.png +0 -0
- package/src/ui/_app/logo.png +0 -0
- package/src/ui/_app/manifest.webmanifest +21 -0
- package/src/ui/_app/sw.js +30 -0
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +24 -4
- package/src/utils/cloudStorage.ts +24 -0
- 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
|