@learnpack/learnpack 5.0.298 → 5.0.301

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 (84) hide show
  1. package/README.md +409 -409
  2. package/lib/commands/audit.js +15 -15
  3. package/lib/commands/breakToken.js +19 -19
  4. package/lib/commands/clean.js +3 -3
  5. package/lib/commands/logout.js +3 -3
  6. package/lib/commands/serve.js +32 -13
  7. package/lib/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
  8. package/lib/creatorDist/index.html +1 -1
  9. package/lib/managers/config/index.js +77 -77
  10. package/lib/utils/creatorUtilities.js +14 -14
  11. package/lib/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  12. package/lib/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  13. package/lib/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
  14. package/lib/utils/templates/scorm/config/index.html +209 -209
  15. package/lib/utils/templates/scorm/ims_xml.xsd +1 -1
  16. package/lib/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
  17. package/lib/utils/templates/scorm/imsmanifest.xml +38 -38
  18. package/lib/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -573
  19. package/package.json +1 -1
  20. package/src/commands/audit.ts +487 -487
  21. package/src/commands/breakToken.ts +67 -67
  22. package/src/commands/clean.ts +30 -30
  23. package/src/commands/logout.ts +38 -38
  24. package/src/commands/serve.ts +49 -27
  25. package/src/commands/start.ts +333 -333
  26. package/src/commands/translate.ts +123 -123
  27. package/src/creator/README.md +54 -54
  28. package/src/creator/package-lock.json +6621 -6621
  29. package/src/creator/package.json +55 -55
  30. package/src/creator/src/App.tsx +569 -569
  31. package/src/creator/src/components/FileUploader.tsx +302 -302
  32. package/src/creator/src/components/Icon.tsx +18 -18
  33. package/src/creator/src/components/LessonItem.tsx +152 -152
  34. package/src/creator/src/components/Login.tsx +259 -259
  35. package/src/creator/src/components/syllabus/ContentIndex.tsx +323 -323
  36. package/src/creator/src/components/syllabus/SyllabusEditor.tsx +337 -337
  37. package/src/creator/src/i18n.ts +28 -28
  38. package/src/creator/src/locales/en.json +127 -127
  39. package/src/creator/src/locales/es.json +127 -127
  40. package/src/creator/src/utils/configTypes.ts +122 -122
  41. package/src/creator/src/utils/constants.ts +13 -13
  42. package/src/creator/src/utils/creatorUtils.ts +46 -46
  43. package/src/creator/src/utils/eventBus.ts +2 -2
  44. package/src/creator/src/utils/socket.ts +61 -61
  45. package/src/creator/src/utils/store.ts +222 -222
  46. package/src/creator/src/vite-env.d.ts +1 -1
  47. package/src/creator/vite.config.ts +13 -13
  48. package/src/creatorDist/assets/{index-D25zkBaN.js → index-DoYRptnk.js} +11875 -11992
  49. package/src/creatorDist/index.html +1 -1
  50. package/src/managers/config/defaults.ts +49 -49
  51. package/src/managers/config/exercise.ts +364 -364
  52. package/src/managers/config/index.ts +775 -775
  53. package/src/managers/file.ts +236 -236
  54. package/src/managers/server/routes.ts +554 -554
  55. package/src/managers/telemetry.ts +188 -188
  56. package/src/models/action.ts +13 -13
  57. package/src/models/config-manager.ts +28 -28
  58. package/src/models/config.ts +106 -106
  59. package/src/models/exercise-obj.ts +30 -30
  60. package/src/models/session.ts +39 -39
  61. package/src/models/socket.ts +61 -61
  62. package/src/models/status.ts +16 -16
  63. package/src/ui/_app/app.css +1 -1
  64. package/src/ui/_app/app.js +768 -768
  65. package/src/ui/_app/learnpack.svg +7 -7
  66. package/src/ui/_app/sw.js +59 -59
  67. package/src/ui/app.tar.gz +0 -0
  68. package/src/utils/BaseCommand.ts +56 -56
  69. package/src/utils/audit.ts +392 -392
  70. package/src/utils/checkNotInstalled.ts +267 -267
  71. package/src/utils/convertCreds.js +34 -34
  72. package/src/utils/creatorUtilities.ts +504 -504
  73. package/src/utils/export/README.md +178 -178
  74. package/src/utils/incrementVersion.js +74 -74
  75. package/src/utils/misc.ts +58 -58
  76. package/src/utils/sidebarGenerator.ts +195 -195
  77. package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  78. package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  79. package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -110
  80. package/src/utils/templates/scorm/config/index.html +209 -209
  81. package/src/utils/templates/scorm/ims_xml.xsd +1 -1
  82. package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -345
  83. package/src/utils/templates/scorm/imsmanifest.xml +38 -38
  84. 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
+ }