@learnpack/learnpack 5.0.71 → 5.0.72
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 +13 -13
- package/lib/commands/serve.js +48 -1
- package/lib/creatorDist/assets/{index-k_eF99Sf.css → index-BJ2JJzVC.css} +48 -12
- package/lib/creatorDist/assets/{index-Dm2fdeOs.js → index-CKBeex0S.js} +36853 -30578
- package/lib/creatorDist/index.html +2 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +57 -1
- package/src/creator/package-lock.json +49 -0
- package/src/creator/package.json +1 -0
- package/src/creator/src/App.tsx +27 -21
- package/src/creator/src/components/ConsumablesManager.tsx +12 -2
- package/src/creator/src/components/LessonItem.tsx +3 -2
- package/src/creator/src/components/Loader.tsx +5 -1
- package/src/creator/src/components/Login.tsx +46 -135
- package/src/creator/src/components/syllabus/ContentIndex.tsx +84 -46
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +55 -12
- package/src/creator/src/index.css +15 -0
- package/src/creator/src/utils/creatorUtils.ts +33 -3
- package/src/creator/src/utils/lib.ts +156 -2
- package/src/creator/src/utils/rigo.ts +3 -3
- package/src/creator/src/utils/store.ts +0 -1
- package/src/creatorDist/assets/{index-k_eF99Sf.css → index-BJ2JJzVC.css} +48 -12
- package/src/creatorDist/assets/{index-Dm2fdeOs.js → index-CKBeex0S.js} +36853 -30578
- package/src/creatorDist/index.html +2 -2
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +529 -529
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/creds.json +0 -13
@@ -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-
|
14
|
-
<link rel="stylesheet" crossorigin href="/creator/assets/index-
|
13
|
+
<script type="module" crossorigin src="/creator/assets/index-CKBeex0S.js"></script>
|
14
|
+
<link rel="stylesheet" crossorigin href="/creator/assets/index-BJ2JJzVC.css">
|
15
15
|
</head>
|
16
16
|
<body>
|
17
17
|
<div id="root"></div>
|
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"5.0.
|
1
|
+
{"version":"5.0.72","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.
|
4
|
+
"version": "5.0.72",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
package/src/commands/serve.ts
CHANGED
@@ -54,7 +54,7 @@ export default class ServeCommand extends SessionCommand {
|
|
54
54
|
})
|
55
55
|
|
56
56
|
const bucket = bucketStorage.bucket(
|
57
|
-
process.env.GCP_BUCKET_NAME || "learnpack-
|
57
|
+
process.env.GCP_BUCKET_NAME || "learnpack-packages"
|
58
58
|
)
|
59
59
|
|
60
60
|
async function listFilesWithPrefix(prefix: string) {
|
@@ -125,6 +125,40 @@ export default class ServeCommand extends SessionCommand {
|
|
125
125
|
stream.end(buffer)
|
126
126
|
})
|
127
127
|
|
128
|
+
app.post("/upload-image", express.json(), async (req, res) => {
|
129
|
+
const { image_url, destination } = req.body
|
130
|
+
if (!image_url || !destination) {
|
131
|
+
return res
|
132
|
+
.status(400)
|
133
|
+
.json({ error: "image_url and destination are required" })
|
134
|
+
}
|
135
|
+
|
136
|
+
try {
|
137
|
+
const response = await fetch(image_url)
|
138
|
+
if (!response.ok) {
|
139
|
+
return res
|
140
|
+
.status(400)
|
141
|
+
.json({ error: `Failed to download image: ${response.statusText}` })
|
142
|
+
}
|
143
|
+
|
144
|
+
const contentType =
|
145
|
+
response.headers.get("content-type") || "application/octet-stream"
|
146
|
+
const buffer = await response.arrayBuffer()
|
147
|
+
|
148
|
+
const file = bucket.file(destination)
|
149
|
+
await file.save(Buffer.from(buffer), {
|
150
|
+
resumable: false,
|
151
|
+
contentType,
|
152
|
+
})
|
153
|
+
|
154
|
+
console.log(`✅ Image uploaded to ${file.name}`)
|
155
|
+
res.json({ message: "Image uploaded successfully", path: file.name })
|
156
|
+
} catch (error) {
|
157
|
+
console.error("❌ upload-image error:", error)
|
158
|
+
res.status(500).json({ error: (error as Error).message })
|
159
|
+
}
|
160
|
+
})
|
161
|
+
|
128
162
|
app.get("/create", (req, res) => {
|
129
163
|
console.log("GET /create")
|
130
164
|
res.redirect("/creator/")
|
@@ -227,6 +261,28 @@ continue
|
|
227
261
|
res.send({ attributes, body })
|
228
262
|
})
|
229
263
|
|
264
|
+
app.get("/.learn/assets/:file", async (req, res) => {
|
265
|
+
console.log("GET /.learn/assets/:file", req.params.file)
|
266
|
+
const { file } = req.params
|
267
|
+
const courseSlug = req.query.slug
|
268
|
+
|
269
|
+
if (!courseSlug) {
|
270
|
+
return res.status(400).send("Course slug is required")
|
271
|
+
}
|
272
|
+
|
273
|
+
const filePath = `courses/${courseSlug}/.learn/assets/${file}`
|
274
|
+
const fileRef = bucket.file(filePath)
|
275
|
+
const [exists] = await fileRef.exists()
|
276
|
+
if (!exists) {
|
277
|
+
return res.status(404).send("File not found")
|
278
|
+
}
|
279
|
+
|
280
|
+
const fileStream = fileRef.createReadStream()
|
281
|
+
res.set("Content-Type", "application/octet-stream")
|
282
|
+
res.set("Content-Disposition", `attachment; filename="${file}"`)
|
283
|
+
fileStream.pipe(res)
|
284
|
+
})
|
285
|
+
|
230
286
|
app.put(
|
231
287
|
"/exercise/:slug/file/:fileName",
|
232
288
|
express.text(),
|
@@ -11,6 +11,7 @@
|
|
11
11
|
"@google-cloud/storage": "^7.16.0",
|
12
12
|
"@tailwindcss/vite": "^4.1.3",
|
13
13
|
"axios": "^1.8.4",
|
14
|
+
"framer-motion": "^12.9.2",
|
14
15
|
"front-matter": "^4.0.2",
|
15
16
|
"js-yaml": "^4.1.0",
|
16
17
|
"mammoth": "^1.9.0",
|
@@ -3012,6 +3013,33 @@
|
|
3012
3013
|
"url": "https://github.com/sponsors/rawify"
|
3013
3014
|
}
|
3014
3015
|
},
|
3016
|
+
"node_modules/framer-motion": {
|
3017
|
+
"version": "12.9.2",
|
3018
|
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.9.2.tgz",
|
3019
|
+
"integrity": "sha512-R0O3Jdqbfwywpm45obP+8sTgafmdEcUoShQTAV+rB5pi+Y1Px/FYL5qLLRe5tPtBdN1J4jos7M+xN2VV2oEAbQ==",
|
3020
|
+
"license": "MIT",
|
3021
|
+
"dependencies": {
|
3022
|
+
"motion-dom": "^12.9.1",
|
3023
|
+
"motion-utils": "^12.8.3",
|
3024
|
+
"tslib": "^2.4.0"
|
3025
|
+
},
|
3026
|
+
"peerDependencies": {
|
3027
|
+
"@emotion/is-prop-valid": "*",
|
3028
|
+
"react": "^18.0.0 || ^19.0.0",
|
3029
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
3030
|
+
},
|
3031
|
+
"peerDependenciesMeta": {
|
3032
|
+
"@emotion/is-prop-valid": {
|
3033
|
+
"optional": true
|
3034
|
+
},
|
3035
|
+
"react": {
|
3036
|
+
"optional": true
|
3037
|
+
},
|
3038
|
+
"react-dom": {
|
3039
|
+
"optional": true
|
3040
|
+
}
|
3041
|
+
}
|
3042
|
+
},
|
3015
3043
|
"node_modules/front-matter": {
|
3016
3044
|
"version": "4.0.2",
|
3017
3045
|
"resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz",
|
@@ -3983,6 +4011,21 @@
|
|
3983
4011
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
3984
4012
|
"license": "MIT"
|
3985
4013
|
},
|
4014
|
+
"node_modules/motion-dom": {
|
4015
|
+
"version": "12.9.1",
|
4016
|
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.9.1.tgz",
|
4017
|
+
"integrity": "sha512-xqXEwRLDYDTzOgXobSoWtytRtGlf7zdkRfFbrrdP7eojaGQZ5Go4OOKtgnx7uF8sAkfr1ZjMvbCJSCIT2h6fkQ==",
|
4018
|
+
"license": "MIT",
|
4019
|
+
"dependencies": {
|
4020
|
+
"motion-utils": "^12.8.3"
|
4021
|
+
}
|
4022
|
+
},
|
4023
|
+
"node_modules/motion-utils": {
|
4024
|
+
"version": "12.8.3",
|
4025
|
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.8.3.tgz",
|
4026
|
+
"integrity": "sha512-GYVauZEbca8/zOhEiYOY9/uJeedYQld6co/GJFKOy//0c/4lDqk0zB549sBYqqV2iMuX+uHrY1E5zd8A2L+1Lw==",
|
4027
|
+
"license": "MIT"
|
4028
|
+
},
|
3986
4029
|
"node_modules/ms": {
|
3987
4030
|
"version": "2.1.3",
|
3988
4031
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
@@ -4756,6 +4799,12 @@
|
|
4756
4799
|
"typescript": ">=4.8.4"
|
4757
4800
|
}
|
4758
4801
|
},
|
4802
|
+
"node_modules/tslib": {
|
4803
|
+
"version": "2.8.1",
|
4804
|
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
4805
|
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
4806
|
+
"license": "0BSD"
|
4807
|
+
},
|
4759
4808
|
"node_modules/turbo-stream": {
|
4760
4809
|
"version": "2.4.0",
|
4761
4810
|
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
package/src/creator/package.json
CHANGED
package/src/creator/src/App.tsx
CHANGED
@@ -3,11 +3,11 @@ import StepWizard from "./components/StepWizard"
|
|
3
3
|
import SelectableCard from "./components/SelectableCard"
|
4
4
|
import Loader from "./components/Loader"
|
5
5
|
import { useNavigate } from "react-router"
|
6
|
-
import Login, { loginWithToken } from "./components/Login"
|
7
6
|
import { useShallow } from "zustand/react/shallow"
|
8
7
|
import useStore from "./utils/store"
|
8
|
+
|
9
9
|
import { interactiveCreation } from "./utils/rigo"
|
10
|
-
import { checkParams, parseLesson } from "./utils/lib"
|
10
|
+
import { checkParams, loginWithToken, parseLesson } from "./utils/lib"
|
11
11
|
import FileUploader from "./components/FileUploader"
|
12
12
|
|
13
13
|
// const exampleContentIndex = `-Introduction to AI: Explain what is AI and its applications
|
@@ -17,9 +17,9 @@ import FileUploader from "./components/FileUploader"
|
|
17
17
|
function App() {
|
18
18
|
const navigate = useNavigate()
|
19
19
|
|
20
|
-
const {
|
20
|
+
const { formState, setFormState, setSyllabus, setAuth } = useStore(
|
21
21
|
useShallow((state) => ({
|
22
|
-
auth: state.auth,
|
22
|
+
// auth: state.auth,
|
23
23
|
formState: state.formState,
|
24
24
|
setFormState: state.setFormState,
|
25
25
|
setSyllabus: state.setSyllabus,
|
@@ -69,7 +69,7 @@ function App() {
|
|
69
69
|
}
|
70
70
|
|
71
71
|
const handleCreateTutorial = async () => {
|
72
|
-
const res = await interactiveCreation(
|
72
|
+
const res = await interactiveCreation({
|
73
73
|
courseInfo: JSON.stringify(formState),
|
74
74
|
prevInteractions: "",
|
75
75
|
})
|
@@ -114,21 +114,21 @@ function App() {
|
|
114
114
|
/>
|
115
115
|
),
|
116
116
|
},
|
117
|
-
{
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
},
|
117
|
+
// {
|
118
|
+
// title:
|
119
|
+
// "First you need to login with 4Geeks.com to use AI Generation tool for creators. ",
|
120
|
+
// slug: "login",
|
121
|
+
// isCompleted: false,
|
122
|
+
// content: (
|
123
|
+
// <Login
|
124
|
+
// onFinish={() => {
|
125
|
+
// setFormState({
|
126
|
+
// currentStep: "duration",
|
127
|
+
// })
|
128
|
+
// }}
|
129
|
+
// />
|
130
|
+
// ),
|
131
|
+
// },
|
132
132
|
{
|
133
133
|
title: "What is the estimated duration for this tutorial?",
|
134
134
|
slug: "duration",
|
@@ -266,7 +266,13 @@ function App() {
|
|
266
266
|
{formState.isCompleted ? (
|
267
267
|
<Loader
|
268
268
|
text="Learnpack is setting up your tutorial. It may take a moment..."
|
269
|
-
icon={
|
269
|
+
icon={
|
270
|
+
<img
|
271
|
+
src={"rigo-float.gif"}
|
272
|
+
alt="rigo"
|
273
|
+
className="w-20 h-20"
|
274
|
+
/>
|
275
|
+
}
|
270
276
|
/>
|
271
277
|
) : (
|
272
278
|
<StepWizard
|
@@ -4,6 +4,7 @@ import useStore from "../utils/store"
|
|
4
4
|
|
5
5
|
export const ConsumablesManager = () => {
|
6
6
|
const auth = useStore((state) => state.auth)
|
7
|
+
const setAuth = useStore((state) => state.setAuth)
|
7
8
|
const setConsumables = useStore((state) => state.setConsumables)
|
8
9
|
|
9
10
|
useEffect(() => {
|
@@ -13,8 +14,17 @@ export const ConsumablesManager = () => {
|
|
13
14
|
}, [auth])
|
14
15
|
|
15
16
|
const fetchConsumables = async () => {
|
16
|
-
|
17
|
-
|
17
|
+
try {
|
18
|
+
const consumables = await getConsumables(auth.bcToken)
|
19
|
+
setConsumables(parseConsumables(consumables.voids))
|
20
|
+
} catch (error) {
|
21
|
+
setAuth({
|
22
|
+
bcToken: "",
|
23
|
+
rigoToken: "",
|
24
|
+
userId: "",
|
25
|
+
user: null,
|
26
|
+
})
|
27
|
+
}
|
18
28
|
}
|
19
29
|
|
20
30
|
return <></>
|
@@ -35,10 +35,11 @@ export const LessonItem: React.FC<LessonItemProps> = ({
|
|
35
35
|
|
36
36
|
return (
|
37
37
|
<div
|
38
|
-
className={`flex items-center space-x-2
|
39
|
-
isNew ? "
|
38
|
+
className={`flex items-center space-x-2 relative rounded-md p-3 ${
|
39
|
+
isNew ? "border border-learnpack-blue" : "border border-gray-200"
|
40
40
|
}`}
|
41
41
|
>
|
42
|
+
{isNew && <span className="red-ball"></span>}
|
42
43
|
<span className="index-circle">{cleanFloatString(lesson.id)}</span>
|
43
44
|
<span className="text-gray-500 text-sm">
|
44
45
|
{lesson.type[0] + lesson.type.slice(1).toLowerCase()} ●
|
@@ -6,6 +6,7 @@ interface LoaderProps {
|
|
6
6
|
icon?: React.ReactNode
|
7
7
|
listeningTo?: string
|
8
8
|
initialBuffer?: string
|
9
|
+
minheight?: string
|
9
10
|
}
|
10
11
|
|
11
12
|
const Loader: React.FC<LoaderProps> = ({
|
@@ -13,6 +14,7 @@ const Loader: React.FC<LoaderProps> = ({
|
|
13
14
|
icon,
|
14
15
|
listeningTo,
|
15
16
|
initialBuffer,
|
17
|
+
minheight = "min-h-screen",
|
16
18
|
}) => {
|
17
19
|
const [buffer, setBuffer] = useState(initialBuffer || "")
|
18
20
|
const preRef = useRef<HTMLPreElement>(null)
|
@@ -40,7 +42,9 @@ const Loader: React.FC<LoaderProps> = ({
|
|
40
42
|
}, [buffer])
|
41
43
|
|
42
44
|
return (
|
43
|
-
<div
|
45
|
+
<div
|
46
|
+
className={`flex flex-col items-center justify-center bg-gray-50 text-center space-y-6 ${minheight}`}
|
47
|
+
>
|
44
48
|
<div className="text-blue-500 text-4xl animate-pulse">{icon}</div>
|
45
49
|
<p className="text-gray-800 text-lg whitespace-pre-line">{text}</p>
|
46
50
|
{!buffer && (
|
@@ -1,132 +1,45 @@
|
|
1
1
|
import { useEffect, useState } from "react"
|
2
|
-
import { BREATHECODE_HOST
|
2
|
+
import { BREATHECODE_HOST } from "../utils/constants"
|
3
3
|
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 { checkParams } from "../utils/lib"
|
8
|
-
|
9
|
-
type LoginInfo = {
|
10
|
-
email: string
|
11
|
-
password: string
|
12
|
-
}
|
13
|
-
|
14
|
-
const getRigobotJSON = async (breathecodeToken: string) => {
|
15
|
-
const rigoUrl = `${RIGOBOT_HOST}/v1/auth/me/token?breathecode_token=${breathecodeToken}`
|
16
|
-
const rigoResp = await fetch(rigoUrl)
|
17
|
-
if (!rigoResp.ok) {
|
18
|
-
throw new Error("Unable to obtain Rigobot token")
|
19
|
-
}
|
20
|
-
const rigobotJson = await rigoResp.json()
|
21
|
-
return rigobotJson
|
22
|
-
}
|
23
|
-
const validateUser = async (breathecodeToken: string) => {
|
24
|
-
const config = {
|
25
|
-
method: "GET",
|
26
|
-
headers: {
|
27
|
-
"Content-Type": "application/json",
|
28
|
-
Authorization: `Token ${breathecodeToken}`,
|
29
|
-
},
|
30
|
-
}
|
31
|
-
|
32
|
-
const res = await fetch(`${BREATHECODE_HOST}/v1/auth/user/me`, config)
|
33
|
-
if (!res.ok) {
|
34
|
-
console.log("ERROR", res)
|
35
|
-
return null
|
36
|
-
}
|
37
|
-
const json = await res.json()
|
38
|
-
|
39
|
-
if ("roles" in json) {
|
40
|
-
delete json.roles
|
41
|
-
}
|
42
|
-
if ("permissions" in json) {
|
43
|
-
delete json.permissions
|
44
|
-
}
|
45
|
-
if ("settings" in json) {
|
46
|
-
delete json.settings
|
47
|
-
}
|
48
|
-
|
49
|
-
return json
|
50
|
-
}
|
51
|
-
|
52
|
-
const login4Geeks = async (loginInfo: LoginInfo) => {
|
53
|
-
const url = `${BREATHECODE_HOST}/v1/auth/login/`
|
54
|
-
|
55
|
-
const res = await fetch(url, {
|
56
|
-
body: JSON.stringify(loginInfo),
|
57
|
-
method: "post",
|
58
|
-
headers: {
|
59
|
-
"Content-Type": "application/json",
|
60
|
-
},
|
61
|
-
})
|
62
|
-
|
63
|
-
if (!res.ok) {
|
64
|
-
throw Error("Unable to login with provided credentials")
|
65
|
-
}
|
66
|
-
|
67
|
-
const json = await res.json()
|
68
|
-
|
69
|
-
const rigoJson = await getRigobotJSON(json.token)
|
70
|
-
|
71
|
-
const user = await validateUser(json.token)
|
72
|
-
const returns = { ...json, rigobot: { ...rigoJson }, user }
|
73
|
-
|
74
|
-
return returns
|
75
|
-
}
|
76
|
-
|
77
|
-
export const loginWithToken = async (token: string) => {
|
78
|
-
const rigoJson = await getRigobotJSON(token)
|
79
|
-
|
80
|
-
const user = await validateUser(token)
|
81
|
-
|
82
|
-
const returns = { rigobot: { ...rigoJson }, ...user }
|
83
|
-
|
84
|
-
return returns
|
85
|
-
}
|
7
|
+
import { checkParams, login4Geeks, loginWithToken } from "../utils/lib"
|
86
8
|
|
87
9
|
export default function Login({ onFinish }: { onFinish: () => void }) {
|
88
10
|
const [email, setEmail] = useState("")
|
89
11
|
const [password, setPassword] = useState("")
|
90
12
|
const [isLoading, setIsLoading] = useState(false)
|
91
|
-
|
92
13
|
const [showForm, setShowForm] = useState(false)
|
93
14
|
|
94
15
|
const { setAuth } = useStore(
|
95
|
-
useShallow((state) => ({
|
96
|
-
setAuth: state.setAuth,
|
97
|
-
}))
|
16
|
+
useShallow((state) => ({ setAuth: state.setAuth }))
|
98
17
|
)
|
99
18
|
|
100
|
-
const login = async (e:
|
101
|
-
setIsLoading(true)
|
102
|
-
|
103
|
-
const tid = toast.loading("Logging in...")
|
19
|
+
const login = async (e: React.FormEvent) => {
|
104
20
|
e.preventDefault()
|
21
|
+
setIsLoading(true)
|
22
|
+
const tid = toast.loading("Logging in…")
|
105
23
|
|
106
24
|
if (!email || !password) {
|
107
|
-
// toast.error(t("please-fill-all-fields"))
|
108
25
|
setIsLoading(false)
|
26
|
+
toast.error("Please fill all fields", { id: tid })
|
109
27
|
return
|
110
28
|
}
|
111
29
|
|
112
|
-
const
|
113
|
-
|
114
|
-
password: password,
|
115
|
-
})
|
116
|
-
|
117
|
-
if (!isLoggedId) {
|
30
|
+
const resp = await login4Geeks({ email, password })
|
31
|
+
if (!resp) {
|
118
32
|
setIsLoading(false)
|
119
|
-
toast.error("
|
33
|
+
toast.error("Invalid credentials", { id: tid })
|
120
34
|
return
|
121
35
|
}
|
122
36
|
|
123
37
|
toast.success("Logged in successfully", { id: tid })
|
124
|
-
|
125
38
|
setAuth({
|
126
|
-
bcToken:
|
127
|
-
userId:
|
128
|
-
rigoToken:
|
129
|
-
user:
|
39
|
+
bcToken: resp.token,
|
40
|
+
userId: resp.user.id,
|
41
|
+
rigoToken: resp.rigobot.key,
|
42
|
+
user: resp.user,
|
130
43
|
})
|
131
44
|
setIsLoading(false)
|
132
45
|
onFinish()
|
@@ -137,49 +50,46 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
137
50
|
}
|
138
51
|
|
139
52
|
function getCurrentUrlWithQueryParams() {
|
140
|
-
// let currentUrl = window.location.origin + window.location.pathname
|
141
|
-
|
142
53
|
return window.location.href
|
143
54
|
}
|
144
55
|
|
145
56
|
const redirectGithub = () => {
|
146
|
-
|
147
|
-
|
148
|
-
window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${stringToBase64(
|
149
|
-
currentUrl
|
150
|
-
)}`
|
57
|
+
const url = stringToBase64(getCurrentUrlWithQueryParams())
|
58
|
+
window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
|
151
59
|
}
|
152
60
|
|
153
61
|
useEffect(() => {
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
user: user,
|
168
|
-
})
|
169
|
-
onFinish()
|
62
|
+
;(async () => {
|
63
|
+
const { token } = checkParams()
|
64
|
+
if (token) {
|
65
|
+
const user = await loginWithToken(token)
|
66
|
+
if (user) {
|
67
|
+
setAuth({
|
68
|
+
bcToken: token,
|
69
|
+
userId: user.id,
|
70
|
+
rigoToken: user.rigobot.key,
|
71
|
+
user,
|
72
|
+
})
|
73
|
+
onFinish()
|
74
|
+
}
|
170
75
|
}
|
171
|
-
}
|
172
|
-
}
|
76
|
+
})()
|
77
|
+
}, [])
|
173
78
|
|
174
79
|
return (
|
175
|
-
|
176
|
-
|
80
|
+
<div
|
81
|
+
className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000"
|
82
|
+
onClick={onFinish}
|
83
|
+
>
|
84
|
+
<div
|
85
|
+
className="bg-white p-8 rounded-xl shadow-md max-w-sm w-full"
|
86
|
+
onClick={(e) => e.stopPropagation()}
|
87
|
+
>
|
177
88
|
<button
|
178
89
|
onClick={redirectGithub}
|
179
|
-
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4
|
90
|
+
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4"
|
180
91
|
>
|
181
|
-
{SVGS.github}
|
182
|
-
LOGIN WITH GITHUB
|
92
|
+
{SVGS.github} LOGIN WITH GITHUB
|
183
93
|
</button>
|
184
94
|
|
185
95
|
<div className="flex items-center mb-4">
|
@@ -205,14 +115,14 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
205
115
|
<div className="flex gap-2 mt-4">
|
206
116
|
<button
|
207
117
|
type="submit"
|
208
|
-
className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold
|
118
|
+
className="flex-1 bg-sky-500 text-white py-2 rounded-md font-semibold"
|
209
119
|
>
|
210
120
|
{isLoading ? "Logging in..." : "Log in"}
|
211
121
|
</button>
|
212
122
|
<button
|
213
123
|
type="button"
|
214
124
|
onClick={() => setShowForm(false)}
|
215
|
-
className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold
|
125
|
+
className="flex-1 border border-sky-500 text-sky-600 py-2 rounded-md font-semibold"
|
216
126
|
>
|
217
127
|
Skip
|
218
128
|
</button>
|
@@ -230,11 +140,12 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
230
140
|
) : (
|
231
141
|
<button
|
232
142
|
onClick={() => setShowForm(true)}
|
233
|
-
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold
|
143
|
+
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold"
|
234
144
|
>
|
235
145
|
Login with Email
|
236
146
|
</button>
|
237
147
|
)}
|
148
|
+
|
238
149
|
<div className="flex justify-between items-center mt-4">
|
239
150
|
<div className="bg-blue-50 text-sm p-2 rounded text-left">
|
240
151
|
<p className="text-gray-700 m-0">You don't have an account?</p>
|
@@ -248,6 +159,6 @@ export default function Login({ onFinish }: { onFinish: () => void }) {
|
|
248
159
|
</div>
|
249
160
|
</div>
|
250
161
|
</div>
|
251
|
-
|
162
|
+
</div>
|
252
163
|
)
|
253
164
|
}
|