@learnpack/learnpack 5.0.297 → 5.0.300
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 +409 -409
- package/lib/commands/audit.js +15 -15
- package/lib/commands/breakToken.js +19 -19
- package/lib/commands/clean.js +3 -3
- package/lib/commands/logout.js +3 -3
- package/lib/commands/serve.js +32 -11
- package/lib/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
- package/lib/creatorDist/index.html +1 -1
- package/lib/managers/config/index.js +77 -77
- package/lib/utils/creatorUtilities.js +14 -14
- package/lib/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
- package/lib/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
- package/lib/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
- package/lib/utils/templates/scorm/config/index.html +209 -209
- package/lib/utils/templates/scorm/ims_xml.xsd +1 -1
- package/lib/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
- package/lib/utils/templates/scorm/imsmanifest.xml +38 -38
- package/lib/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -573
- package/package.json +1 -1
- package/src/commands/audit.ts +487 -487
- package/src/commands/breakToken.ts +67 -67
- package/src/commands/clean.ts +30 -30
- package/src/commands/logout.ts +38 -38
- package/src/commands/serve.ts +49 -26
- package/src/commands/start.ts +333 -333
- package/src/commands/translate.ts +123 -123
- package/src/creator/README.md +54 -54
- package/src/creator/package-lock.json +6621 -6621
- package/src/creator/package.json +55 -55
- package/src/creator/src/App.tsx +569 -569
- package/src/creator/src/components/FileUploader.tsx +302 -302
- package/src/creator/src/components/Icon.tsx +18 -18
- package/src/creator/src/components/LessonItem.tsx +152 -152
- package/src/creator/src/components/Login.tsx +259 -259
- package/src/creator/src/components/syllabus/ContentIndex.tsx +323 -323
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +337 -337
- package/src/creator/src/i18n.ts +28 -28
- package/src/creator/src/locales/en.json +127 -127
- package/src/creator/src/locales/es.json +127 -127
- package/src/creator/src/utils/configTypes.ts +122 -122
- package/src/creator/src/utils/constants.ts +13 -13
- package/src/creator/src/utils/creatorUtils.ts +46 -46
- package/src/creator/src/utils/eventBus.ts +2 -2
- package/src/creator/src/utils/socket.ts +61 -61
- package/src/creator/src/utils/store.ts +222 -222
- package/src/creator/src/vite-env.d.ts +1 -1
- package/src/creator/vite.config.ts +13 -13
- package/src/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
- package/src/creatorDist/index.html +1 -1
- package/src/managers/config/defaults.ts +49 -49
- package/src/managers/config/exercise.ts +364 -364
- package/src/managers/config/index.ts +775 -775
- package/src/managers/file.ts +236 -236
- package/src/managers/server/routes.ts +554 -554
- package/src/managers/telemetry.ts +188 -188
- package/src/models/action.ts +13 -13
- package/src/models/config-manager.ts +28 -28
- package/src/models/config.ts +106 -106
- package/src/models/exercise-obj.ts +30 -30
- package/src/models/session.ts +39 -39
- package/src/models/socket.ts +61 -61
- package/src/models/status.ts +16 -16
- package/src/utils/BaseCommand.ts +56 -56
- package/src/utils/audit.ts +392 -392
- package/src/utils/checkNotInstalled.ts +267 -267
- package/src/utils/convertCreds.js +34 -34
- package/src/utils/creatorUtilities.ts +504 -504
- package/src/utils/export/README.md +178 -178
- package/src/utils/incrementVersion.js +74 -74
- package/src/utils/misc.ts +58 -58
- package/src/utils/sidebarGenerator.ts +195 -195
- package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
- package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
- package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
- package/src/utils/templates/scorm/config/index.html +209 -209
- package/src/utils/templates/scorm/ims_xml.xsd +1 -1
- package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
- package/src/utils/templates/scorm/imsmanifest.xml +38 -38
- package/src/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -573
@@ -1,259 +1,259 @@
|
|
1
|
-
import { useState } from "react"
|
2
|
-
import { BREATHECODE_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
|
-
import { login4Geeks, registerUserWithFormData } from "../utils/lib"
|
8
|
-
import { useTranslation } from "react-i18next"
|
9
|
-
|
10
|
-
export default function Login({ onFinish }: { onFinish: () => void }) {
|
11
|
-
// Login states
|
12
|
-
const { t } = useTranslation()
|
13
|
-
|
14
|
-
const [email, setEmail] = useState("")
|
15
|
-
const [password, setPassword] = useState("")
|
16
|
-
const [isLoading, setIsLoading] = useState(false)
|
17
|
-
const [showForm, setShowForm] = useState(false)
|
18
|
-
// Signup states
|
19
|
-
const [showSignup, setShowSignup] = useState(false)
|
20
|
-
const [signupData, setSignupData] = useState({
|
21
|
-
firstName: "",
|
22
|
-
lastName: "",
|
23
|
-
email: "",
|
24
|
-
})
|
25
|
-
// const planToRedirect = useStore((state) => state.planToRedirect)
|
26
|
-
const { setAuth } = useStore(
|
27
|
-
useShallow((state) => ({ setAuth: state.setAuth }))
|
28
|
-
)
|
29
|
-
|
30
|
-
// Login handler
|
31
|
-
const login = async (e: React.FormEvent<HTMLFormElement>) => {
|
32
|
-
e.preventDefault()
|
33
|
-
setIsLoading(true)
|
34
|
-
const tid = toast.loading(t("login.loggingIn"))
|
35
|
-
try {
|
36
|
-
if (!email || !password) {
|
37
|
-
setIsLoading(false)
|
38
|
-
toast.error(t("login.pleaseFillAllFields"), { id: tid })
|
39
|
-
return
|
40
|
-
}
|
41
|
-
const resp = await login4Geeks({ email, password })
|
42
|
-
if (!resp) {
|
43
|
-
setIsLoading(false)
|
44
|
-
toast.error(t("login.invalidCredentials"), { id: tid })
|
45
|
-
return
|
46
|
-
}
|
47
|
-
toast.success(t("login.loggedInSuccessfully"), { id: tid })
|
48
|
-
setAuth({
|
49
|
-
bcToken: resp.token,
|
50
|
-
userId: resp.user.id,
|
51
|
-
rigoToken: resp.rigobot.key,
|
52
|
-
user: resp.user,
|
53
|
-
publicToken: "",
|
54
|
-
})
|
55
|
-
setIsLoading(false)
|
56
|
-
onFinish()
|
57
|
-
} catch (error) {
|
58
|
-
console.error(error)
|
59
|
-
toast.error("Invalid credentials", { id: tid })
|
60
|
-
setIsLoading(false)
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
// Signup handler
|
65
|
-
const handleSignup = async (e: React.FormEvent<HTMLFormElement>) => {
|
66
|
-
e.preventDefault()
|
67
|
-
setIsLoading(true)
|
68
|
-
const tid = toast.loading(t("login.creatingAccount"))
|
69
|
-
const { firstName, lastName, email } = signupData
|
70
|
-
if (!firstName || !lastName || !email) {
|
71
|
-
setIsLoading(false)
|
72
|
-
toast.error(t("login.pleaseFillAllFields"), { id: tid })
|
73
|
-
return
|
74
|
-
}
|
75
|
-
try {
|
76
|
-
await registerUserWithFormData(firstName, lastName, email)
|
77
|
-
toast.success(t("login.accountCreated"), { id: tid })
|
78
|
-
setShowSignup(false)
|
79
|
-
setShowForm(true)
|
80
|
-
setEmail(email)
|
81
|
-
} catch (err) {
|
82
|
-
toast.error(t("login.registrationFailed"), { id: tid })
|
83
|
-
} finally {
|
84
|
-
setIsLoading(false)
|
85
|
-
}
|
86
|
-
}
|
87
|
-
|
88
|
-
function stringToBase64(str: string) {
|
89
|
-
return btoa(unescape(encodeURIComponent(str)))
|
90
|
-
}
|
91
|
-
function getCurrentUrlWithQueryParams() {
|
92
|
-
return window.location.href
|
93
|
-
}
|
94
|
-
const redirectGithub = () => {
|
95
|
-
const url = stringToBase64(getCurrentUrlWithQueryParams())
|
96
|
-
window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
|
97
|
-
}
|
98
|
-
const redirectGoogle = () => {
|
99
|
-
const url = stringToBase64(getCurrentUrlWithQueryParams())
|
100
|
-
window.location.href = `${BREATHECODE_HOST}/v1/auth/google?url=${url}`
|
101
|
-
}
|
102
|
-
|
103
|
-
return (
|
104
|
-
<div
|
105
|
-
className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000"
|
106
|
-
onClick={onFinish}
|
107
|
-
>
|
108
|
-
<div
|
109
|
-
className="bg-white p-8 rounded-xl shadow-md max-w-sm w-full"
|
110
|
-
onClick={(e) => e.stopPropagation()}
|
111
|
-
>
|
112
|
-
{showSignup ? (
|
113
|
-
<>
|
114
|
-
<h2 className="mb-4 text-xl font-semibold text-center">
|
115
|
-
{t("login.createYourAccount")}
|
116
|
-
</h2>
|
117
|
-
<form className="space-y-3" onSubmit={handleSignup}>
|
118
|
-
<input
|
119
|
-
type="text"
|
120
|
-
placeholder={t("login.firstName")}
|
121
|
-
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
122
|
-
value={signupData.firstName}
|
123
|
-
onChange={(e) =>
|
124
|
-
setSignupData({ ...signupData, firstName: e.target.value })
|
125
|
-
}
|
126
|
-
/>
|
127
|
-
<input
|
128
|
-
type="text"
|
129
|
-
placeholder={t("login.lastName")}
|
130
|
-
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
131
|
-
value={signupData.lastName}
|
132
|
-
onChange={(e) =>
|
133
|
-
setSignupData({ ...signupData, lastName: e.target.value })
|
134
|
-
}
|
135
|
-
/>
|
136
|
-
<input
|
137
|
-
type="email"
|
138
|
-
placeholder="Email"
|
139
|
-
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
140
|
-
value={signupData.email}
|
141
|
-
onChange={(e) =>
|
142
|
-
setSignupData({ ...signupData, email: e.target.value })
|
143
|
-
}
|
144
|
-
/>
|
145
|
-
<button
|
146
|
-
type="submit"
|
147
|
-
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
|
148
|
-
disabled={isLoading}
|
149
|
-
>
|
150
|
-
{isLoading ? t("login.creating") : t("login.signUp")}
|
151
|
-
</button>
|
152
|
-
</form>
|
153
|
-
<div className="mt-4 text-sm text-center">
|
154
|
-
{t("login.alreadyHaveAnAccount")}
|
155
|
-
<button
|
156
|
-
className="text-blue-600 font-medium"
|
157
|
-
onClick={() => {
|
158
|
-
setShowSignup(false)
|
159
|
-
setShowForm(true)
|
160
|
-
}}
|
161
|
-
>
|
162
|
-
{t("login.logInHere")}
|
163
|
-
</button>
|
164
|
-
</div>
|
165
|
-
</>
|
166
|
-
) : (
|
167
|
-
<>
|
168
|
-
<p className="mb-4 text-center text-gray-700">
|
169
|
-
{t("login.youNeedToHaveAnAccount")}
|
170
|
-
</p>
|
171
|
-
<button
|
172
|
-
onClick={redirectGithub}
|
173
|
-
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
|
174
|
-
>
|
175
|
-
{SVGS.github} {t("login.loginWithGithub")}
|
176
|
-
</button>
|
177
|
-
<button
|
178
|
-
onClick={redirectGoogle}
|
179
|
-
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
|
180
|
-
>
|
181
|
-
{SVGS.google} {t("login.loginWithGoogle")}
|
182
|
-
</button>
|
183
|
-
<div className="flex items-center mb-4">
|
184
|
-
<hr className="flex-grow border-gray-300" />
|
185
|
-
<span className="mx-2 text-gray-400 text-sm">
|
186
|
-
{t("login.or")}
|
187
|
-
</span>
|
188
|
-
<hr className="flex-grow border-gray-300" />
|
189
|
-
</div>
|
190
|
-
{showForm ? (
|
191
|
-
<form className="space-y-3" onSubmit={login}>
|
192
|
-
<input
|
193
|
-
type="email"
|
194
|
-
placeholder="Email"
|
195
|
-
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
196
|
-
value={email}
|
197
|
-
onChange={(e) => setEmail(e.target.value)}
|
198
|
-
/>
|
199
|
-
<input
|
200
|
-
type="password"
|
201
|
-
placeholder={t("login.password")}
|
202
|
-
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
203
|
-
value={password}
|
204
|
-
onChange={(e) => setPassword(e.target.value)}
|
205
|
-
/>
|
206
|
-
<div className="flex gap-2 mt-4">
|
207
|
-
<button
|
208
|
-
type="submit"
|
209
|
-
className="flex-1 bg-blue-500 text-white py-2 rounded-md font-semibold cursor-pointer"
|
210
|
-
>
|
211
|
-
{isLoading ? t("login.loggingIn") : t("login.logIn")}
|
212
|
-
</button>
|
213
|
-
<button
|
214
|
-
type="button"
|
215
|
-
onClick={() => setShowForm(false)}
|
216
|
-
className="flex-1 border border-blue-500 text-blue-600 py-2 rounded-md font-semibold cursor-pointer"
|
217
|
-
>
|
218
|
-
{t("login.skip")}
|
219
|
-
</button>
|
220
|
-
</div>
|
221
|
-
<div className="text-sm text-gray-600 mt-2">
|
222
|
-
{t("login.forgotPassword")}
|
223
|
-
<a
|
224
|
-
href={`${BREATHECODE_HOST}/v1/auth/password/reset?url=${getCurrentUrlWithQueryParams()}`}
|
225
|
-
className="text-blue-600 font-medium"
|
226
|
-
>
|
227
|
-
{t("login.recoverItHere")}
|
228
|
-
</a>
|
229
|
-
</div>
|
230
|
-
</form>
|
231
|
-
) : (
|
232
|
-
<button
|
233
|
-
onClick={() => setShowForm(true)}
|
234
|
-
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
|
235
|
-
>
|
236
|
-
{t("login.loginWithEmail")}
|
237
|
-
</button>
|
238
|
-
)}
|
239
|
-
<div className="flex justify-between items-center mt-4">
|
240
|
-
<div className="bg-blue-50 text-sm p-2 rounded text-left">
|
241
|
-
<p className="text-gray-700 m-0">
|
242
|
-
{t("login.youDontHaveAnAccount")}
|
243
|
-
</p>
|
244
|
-
<a
|
245
|
-
href={`https://www.learnpack.co/register?callback=${encodeURIComponent(window.location.href)}`}
|
246
|
-
target="_blank"
|
247
|
-
rel="noopener noreferrer"
|
248
|
-
className="text-blue-600 font-medium"
|
249
|
-
>
|
250
|
-
{t("login.registerHere")}
|
251
|
-
</a>
|
252
|
-
</div>
|
253
|
-
</div>
|
254
|
-
</>
|
255
|
-
)}
|
256
|
-
</div>
|
257
|
-
</div>
|
258
|
-
)
|
259
|
-
}
|
1
|
+
import { useState } from "react"
|
2
|
+
import { BREATHECODE_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
|
+
import { login4Geeks, registerUserWithFormData } from "../utils/lib"
|
8
|
+
import { useTranslation } from "react-i18next"
|
9
|
+
|
10
|
+
export default function Login({ onFinish }: { onFinish: () => void }) {
|
11
|
+
// Login states
|
12
|
+
const { t } = useTranslation()
|
13
|
+
|
14
|
+
const [email, setEmail] = useState("")
|
15
|
+
const [password, setPassword] = useState("")
|
16
|
+
const [isLoading, setIsLoading] = useState(false)
|
17
|
+
const [showForm, setShowForm] = useState(false)
|
18
|
+
// Signup states
|
19
|
+
const [showSignup, setShowSignup] = useState(false)
|
20
|
+
const [signupData, setSignupData] = useState({
|
21
|
+
firstName: "",
|
22
|
+
lastName: "",
|
23
|
+
email: "",
|
24
|
+
})
|
25
|
+
// const planToRedirect = useStore((state) => state.planToRedirect)
|
26
|
+
const { setAuth } = useStore(
|
27
|
+
useShallow((state) => ({ setAuth: state.setAuth }))
|
28
|
+
)
|
29
|
+
|
30
|
+
// Login handler
|
31
|
+
const login = async (e: React.FormEvent<HTMLFormElement>) => {
|
32
|
+
e.preventDefault()
|
33
|
+
setIsLoading(true)
|
34
|
+
const tid = toast.loading(t("login.loggingIn"))
|
35
|
+
try {
|
36
|
+
if (!email || !password) {
|
37
|
+
setIsLoading(false)
|
38
|
+
toast.error(t("login.pleaseFillAllFields"), { id: tid })
|
39
|
+
return
|
40
|
+
}
|
41
|
+
const resp = await login4Geeks({ email, password })
|
42
|
+
if (!resp) {
|
43
|
+
setIsLoading(false)
|
44
|
+
toast.error(t("login.invalidCredentials"), { id: tid })
|
45
|
+
return
|
46
|
+
}
|
47
|
+
toast.success(t("login.loggedInSuccessfully"), { id: tid })
|
48
|
+
setAuth({
|
49
|
+
bcToken: resp.token,
|
50
|
+
userId: resp.user.id,
|
51
|
+
rigoToken: resp.rigobot.key,
|
52
|
+
user: resp.user,
|
53
|
+
publicToken: "",
|
54
|
+
})
|
55
|
+
setIsLoading(false)
|
56
|
+
onFinish()
|
57
|
+
} catch (error) {
|
58
|
+
console.error(error)
|
59
|
+
toast.error("Invalid credentials", { id: tid })
|
60
|
+
setIsLoading(false)
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
// Signup handler
|
65
|
+
const handleSignup = async (e: React.FormEvent<HTMLFormElement>) => {
|
66
|
+
e.preventDefault()
|
67
|
+
setIsLoading(true)
|
68
|
+
const tid = toast.loading(t("login.creatingAccount"))
|
69
|
+
const { firstName, lastName, email } = signupData
|
70
|
+
if (!firstName || !lastName || !email) {
|
71
|
+
setIsLoading(false)
|
72
|
+
toast.error(t("login.pleaseFillAllFields"), { id: tid })
|
73
|
+
return
|
74
|
+
}
|
75
|
+
try {
|
76
|
+
await registerUserWithFormData(firstName, lastName, email)
|
77
|
+
toast.success(t("login.accountCreated"), { id: tid })
|
78
|
+
setShowSignup(false)
|
79
|
+
setShowForm(true)
|
80
|
+
setEmail(email)
|
81
|
+
} catch (err) {
|
82
|
+
toast.error(t("login.registrationFailed"), { id: tid })
|
83
|
+
} finally {
|
84
|
+
setIsLoading(false)
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
function stringToBase64(str: string) {
|
89
|
+
return btoa(unescape(encodeURIComponent(str)))
|
90
|
+
}
|
91
|
+
function getCurrentUrlWithQueryParams() {
|
92
|
+
return window.location.href
|
93
|
+
}
|
94
|
+
const redirectGithub = () => {
|
95
|
+
const url = stringToBase64(getCurrentUrlWithQueryParams())
|
96
|
+
window.location.href = `${BREATHECODE_HOST}/v1/auth/github/?url=${url}`
|
97
|
+
}
|
98
|
+
const redirectGoogle = () => {
|
99
|
+
const url = stringToBase64(getCurrentUrlWithQueryParams())
|
100
|
+
window.location.href = `${BREATHECODE_HOST}/v1/auth/google?url=${url}`
|
101
|
+
}
|
102
|
+
|
103
|
+
return (
|
104
|
+
<div
|
105
|
+
className="fixed inset-0 bg-black/50 flex items-center justify-center z-1000"
|
106
|
+
onClick={onFinish}
|
107
|
+
>
|
108
|
+
<div
|
109
|
+
className="bg-white p-8 rounded-xl shadow-md max-w-sm w-full"
|
110
|
+
onClick={(e) => e.stopPropagation()}
|
111
|
+
>
|
112
|
+
{showSignup ? (
|
113
|
+
<>
|
114
|
+
<h2 className="mb-4 text-xl font-semibold text-center">
|
115
|
+
{t("login.createYourAccount")}
|
116
|
+
</h2>
|
117
|
+
<form className="space-y-3" onSubmit={handleSignup}>
|
118
|
+
<input
|
119
|
+
type="text"
|
120
|
+
placeholder={t("login.firstName")}
|
121
|
+
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
122
|
+
value={signupData.firstName}
|
123
|
+
onChange={(e) =>
|
124
|
+
setSignupData({ ...signupData, firstName: e.target.value })
|
125
|
+
}
|
126
|
+
/>
|
127
|
+
<input
|
128
|
+
type="text"
|
129
|
+
placeholder={t("login.lastName")}
|
130
|
+
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
131
|
+
value={signupData.lastName}
|
132
|
+
onChange={(e) =>
|
133
|
+
setSignupData({ ...signupData, lastName: e.target.value })
|
134
|
+
}
|
135
|
+
/>
|
136
|
+
<input
|
137
|
+
type="email"
|
138
|
+
placeholder="Email"
|
139
|
+
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
140
|
+
value={signupData.email}
|
141
|
+
onChange={(e) =>
|
142
|
+
setSignupData({ ...signupData, email: e.target.value })
|
143
|
+
}
|
144
|
+
/>
|
145
|
+
<button
|
146
|
+
type="submit"
|
147
|
+
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
|
148
|
+
disabled={isLoading}
|
149
|
+
>
|
150
|
+
{isLoading ? t("login.creating") : t("login.signUp")}
|
151
|
+
</button>
|
152
|
+
</form>
|
153
|
+
<div className="mt-4 text-sm text-center">
|
154
|
+
{t("login.alreadyHaveAnAccount")}
|
155
|
+
<button
|
156
|
+
className="text-blue-600 font-medium"
|
157
|
+
onClick={() => {
|
158
|
+
setShowSignup(false)
|
159
|
+
setShowForm(true)
|
160
|
+
}}
|
161
|
+
>
|
162
|
+
{t("login.logInHere")}
|
163
|
+
</button>
|
164
|
+
</div>
|
165
|
+
</>
|
166
|
+
) : (
|
167
|
+
<>
|
168
|
+
<p className="mb-4 text-center text-gray-700">
|
169
|
+
{t("login.youNeedToHaveAnAccount")}
|
170
|
+
</p>
|
171
|
+
<button
|
172
|
+
onClick={redirectGithub}
|
173
|
+
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
|
174
|
+
>
|
175
|
+
{SVGS.github} {t("login.loginWithGithub")}
|
176
|
+
</button>
|
177
|
+
<button
|
178
|
+
onClick={redirectGoogle}
|
179
|
+
className="w-full border border-gray-300 py-2 rounded-md font-semibold flex items-center justify-center gap-2 mb-4 cursor-pointer"
|
180
|
+
>
|
181
|
+
{SVGS.google} {t("login.loginWithGoogle")}
|
182
|
+
</button>
|
183
|
+
<div className="flex items-center mb-4">
|
184
|
+
<hr className="flex-grow border-gray-300" />
|
185
|
+
<span className="mx-2 text-gray-400 text-sm">
|
186
|
+
{t("login.or")}
|
187
|
+
</span>
|
188
|
+
<hr className="flex-grow border-gray-300" />
|
189
|
+
</div>
|
190
|
+
{showForm ? (
|
191
|
+
<form className="space-y-3" onSubmit={login}>
|
192
|
+
<input
|
193
|
+
type="email"
|
194
|
+
placeholder="Email"
|
195
|
+
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
196
|
+
value={email}
|
197
|
+
onChange={(e) => setEmail(e.target.value)}
|
198
|
+
/>
|
199
|
+
<input
|
200
|
+
type="password"
|
201
|
+
placeholder={t("login.password")}
|
202
|
+
className="w-full border border-gray-300 px-3 py-2 rounded-md"
|
203
|
+
value={password}
|
204
|
+
onChange={(e) => setPassword(e.target.value)}
|
205
|
+
/>
|
206
|
+
<div className="flex gap-2 mt-4">
|
207
|
+
<button
|
208
|
+
type="submit"
|
209
|
+
className="flex-1 bg-blue-500 text-white py-2 rounded-md font-semibold cursor-pointer"
|
210
|
+
>
|
211
|
+
{isLoading ? t("login.loggingIn") : t("login.logIn")}
|
212
|
+
</button>
|
213
|
+
<button
|
214
|
+
type="button"
|
215
|
+
onClick={() => setShowForm(false)}
|
216
|
+
className="flex-1 border border-blue-500 text-blue-600 py-2 rounded-md font-semibold cursor-pointer"
|
217
|
+
>
|
218
|
+
{t("login.skip")}
|
219
|
+
</button>
|
220
|
+
</div>
|
221
|
+
<div className="text-sm text-gray-600 mt-2">
|
222
|
+
{t("login.forgotPassword")}
|
223
|
+
<a
|
224
|
+
href={`${BREATHECODE_HOST}/v1/auth/password/reset?url=${getCurrentUrlWithQueryParams()}`}
|
225
|
+
className="text-blue-600 font-medium"
|
226
|
+
>
|
227
|
+
{t("login.recoverItHere")}
|
228
|
+
</a>
|
229
|
+
</div>
|
230
|
+
</form>
|
231
|
+
) : (
|
232
|
+
<button
|
233
|
+
onClick={() => setShowForm(true)}
|
234
|
+
className="w-full bg-blue-600 text-white py-2 rounded-md font-semibold cursor-pointer"
|
235
|
+
>
|
236
|
+
{t("login.loginWithEmail")}
|
237
|
+
</button>
|
238
|
+
)}
|
239
|
+
<div className="flex justify-between items-center mt-4">
|
240
|
+
<div className="bg-blue-50 text-sm p-2 rounded text-left">
|
241
|
+
<p className="text-gray-700 m-0">
|
242
|
+
{t("login.youDontHaveAnAccount")}
|
243
|
+
</p>
|
244
|
+
<a
|
245
|
+
href={`https://www.learnpack.co/register?callback=${encodeURIComponent(window.location.href)}`}
|
246
|
+
target="_blank"
|
247
|
+
rel="noopener noreferrer"
|
248
|
+
className="text-blue-600 font-medium"
|
249
|
+
>
|
250
|
+
{t("login.registerHere")}
|
251
|
+
</a>
|
252
|
+
</div>
|
253
|
+
</div>
|
254
|
+
</>
|
255
|
+
)}
|
256
|
+
</div>
|
257
|
+
</div>
|
258
|
+
)
|
259
|
+
}
|