@iblai/iblai-js 1.3.11 → 1.4.0

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.
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useRef, useEffect, useState, useCallback, useLayoutEffect, forwardRef, createElement as createElement$1, useMemo, createContext, useReducer, useImperativeHandle, useContext, useDebugValue, isValidElement, Children, PureComponent, cloneElement, Component } from 'react';
3
3
  import { LOCAL_STORAGE_KEYS, TimeTracker, getInitials, useTenantMetadata, isAlphaNumeric32, checkRbacPermission, getTimeAgo, formatRelativeTime as formatRelativeTime$2, ANONYMOUS_USERNAME, combineCSVData, redirectToAuthSpaJoinTenant, redirectToAuthSpa, getAuthSpaJoinUrl } from '@iblai/web-utils';
4
- import { useTimeTrackingMutation, useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useCreateGlobalMemoryMutation, useGetMemsearchConfigQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useGetNotificationsCountQuery, useLazyGetNotificationsQuery, useMarkAllAsReadMutation, useCreateNotificationPreviewMutation, useSendNotificationMutation, useGetMentorsQuery, useGetRbacPoliciesQuery, usePlatformUsersQuery, usePlatformUserGroupsQuery, useUpdateTemplateMutation, useGetTemplateDetailsQuery, useGetTemplatesQuery, useLazyGetTemplateDetailsQuery, useToggleTemplateMutation, useGetTopicsStatsQuery, useGetUsersStatsQuery, useGetSessionStatsQuery, useGetTopicsDetailsStatsQuery, useGetAccessTimeHeatmapQuery, useGetUserDetailsStatsQuery, useGetTranscriptsConversationHeadlineQuery, useGetAverageRatingQuery, useGetFinancialStatsQuery, useGetDetailedFinancialStatsQuery, useGetTranscriptsMessagesDetailsQuery, useGetTranscriptsMessagesQuery, useGetReportDetailQuery, useLazyGetDownloadReportFromURLQuery, useGetReportsQuery, useCreateReportMutation, useGetMentorPublicSettingsQuery, useGetContentAnalyticsQuery, useGetContentAnalyticsDetailsQuery, useGetRevenueQuery, useListPaywallsQuery, useListSubscribersQuery, useCreateCheckoutMutation } from '@iblai/data-layer';
4
+ import { useTimeTrackingMutation, useGetUserMetadataQuery, useGetUserMetadataEdxQuery, useUpdateUserMetadataMutation, useUpdateUserMetadataEdxMutation, useUploadProfileImageMutation, useResetPasswordMutation, useSelfRetireMutation, useCreateUserInstitutionMutation, useGetUserInstitutionsQuery, useCreateUserEducationMutation, useUpdateUserEducationMutation, useDeleteUserEducationMutation, useGetUserEducationQuery, useCreateUserCompanyMutation, useGetUserCompaniesQuery, useCreateUserExperienceMutation, useUpdateUserExperienceMutation, useDeleteUserExperienceMutation, useGetUserExperienceQuery, useGetUserResumeQuery, useCreateUserResumeMutation, useGetMySubscriptionsQuery, useGetItemSubscriptionQuery, useCancelSubscriptionMutation, useCreateGlobalMemoryMutation, useGetMemsearchConfigQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useGetNotificationsCountQuery, useLazyGetNotificationsQuery, useMarkAllAsReadMutation, useCreateNotificationPreviewMutation, useSendNotificationMutation, useGetMentorsQuery, useGetRbacPoliciesQuery, usePlatformUsersQuery, usePlatformUserGroupsQuery, useUpdateTemplateMutation, useGetTemplateDetailsQuery, useGetTemplatesQuery, useLazyGetTemplateDetailsQuery, useToggleTemplateMutation, useGetTopicsStatsQuery, useGetUsersStatsQuery, useGetSessionStatsQuery, useGetTopicsDetailsStatsQuery, useGetAccessTimeHeatmapQuery, useGetUserDetailsStatsQuery, useGetTranscriptsConversationHeadlineQuery, useGetAverageRatingQuery, useGetFinancialStatsQuery, useGetDetailedFinancialStatsQuery, useGetTranscriptsMessagesDetailsQuery, useGetTranscriptsMessagesQuery, useGetReportDetailQuery, useLazyGetDownloadReportFromURLQuery, useGetReportsQuery, useCreateReportMutation, useGetMentorPublicSettingsQuery, useGetContentAnalyticsQuery, useGetContentAnalyticsDetailsQuery, useGetRevenueQuery, useListPaywallsQuery, useListSubscribersQuery, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetUserEnrolledCoursesQuery, useLazyGetUserAssignedCoursesQuery, useLazyGetUserCredentialsQuery, useLazyGetOverTimeActivityQuery, useLazyGetCatalogSearchQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateOrUpdateUserReportedSkillMutation, useCreateOrUpdateUserDesiredSkillMutation, useLazyGetPathwayListQuery, useLazyGetUserAssignedPathwaysQuery, useLazyGetUserEnrolledPathwaysQuery, useLazyGetPathwayCompletionQuery, useLazyGetProgramListQuery, useLazyGetProgramCompletionQuery, useLazyGetUserEnrolledProgramsQuery, useLazyGetAssignedProgramsQuery, useLazyGetUserSkillsPointsQuery, useLazyGetUserReportedSkillsQuery, useLazyGetUserDesiredSkillsQuery, useLazyGetUserCatalogPathwaysQuery, useLazyGetPerLearnerInfoQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useCreateCheckoutMutation } from '@iblai/data-layer';
5
5
  import { toast, Toaster as Toaster$1 } from 'sonner';
6
6
  import { jsx, Fragment as Fragment$1, jsxs } from 'react/jsx-runtime';
7
7
  import * as ReactDOM from 'react-dom';
@@ -10,6 +10,7 @@ import { z as z$1 } from 'zod';
10
10
  import { NotificationSourceTypeEnum, StateEnum } from '@iblai/iblai-api';
11
11
  import { useSearchParams, useRouter, usePathname } from 'next/navigation';
12
12
  import { skipToken } from '@reduxjs/toolkit/query';
13
+ import Image$4 from 'next/image';
13
14
 
14
15
  function useIframeMessageHandler({ handlers, allowedOrigins, defaultHandler, }) {
15
16
  const handlersRef = useRef(handlers);
@@ -49141,12 +49142,12 @@ const createLucideIcon = (iconName, iconNode) => {
49141
49142
  */
49142
49143
 
49143
49144
 
49144
- const __iconNode$1o = [
49145
+ const __iconNode$1p = [
49145
49146
  ["path", { d: "M17 12H7", key: "16if0g" }],
49146
49147
  ["path", { d: "M19 18H5", key: "18s9l3" }],
49147
49148
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
49148
49149
  ];
49149
- const AlignCenter = createLucideIcon("align-center", __iconNode$1o);
49150
+ const AlignCenter = createLucideIcon("align-center", __iconNode$1p);
49150
49151
 
49151
49152
  /**
49152
49153
  * @license lucide-react v0.507.0 - ISC
@@ -49156,12 +49157,12 @@ const AlignCenter = createLucideIcon("align-center", __iconNode$1o);
49156
49157
  */
49157
49158
 
49158
49159
 
49159
- const __iconNode$1n = [
49160
+ const __iconNode$1o = [
49160
49161
  ["path", { d: "M15 12H3", key: "6jk70r" }],
49161
49162
  ["path", { d: "M17 18H3", key: "1amg6g" }],
49162
49163
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
49163
49164
  ];
49164
- const AlignLeft = createLucideIcon("align-left", __iconNode$1n);
49165
+ const AlignLeft = createLucideIcon("align-left", __iconNode$1o);
49165
49166
 
49166
49167
  /**
49167
49168
  * @license lucide-react v0.507.0 - ISC
@@ -49171,12 +49172,12 @@ const AlignLeft = createLucideIcon("align-left", __iconNode$1n);
49171
49172
  */
49172
49173
 
49173
49174
 
49174
- const __iconNode$1m = [
49175
+ const __iconNode$1n = [
49175
49176
  ["path", { d: "M21 12H9", key: "dn1m92" }],
49176
49177
  ["path", { d: "M21 18H7", key: "1ygte8" }],
49177
49178
  ["path", { d: "M21 6H3", key: "1jwq7v" }]
49178
49179
  ];
49179
- const AlignRight = createLucideIcon("align-right", __iconNode$1m);
49180
+ const AlignRight = createLucideIcon("align-right", __iconNode$1n);
49180
49181
 
49181
49182
  /**
49182
49183
  * @license lucide-react v0.507.0 - ISC
@@ -49186,12 +49187,12 @@ const AlignRight = createLucideIcon("align-right", __iconNode$1m);
49186
49187
  */
49187
49188
 
49188
49189
 
49189
- const __iconNode$1l = [
49190
+ const __iconNode$1m = [
49190
49191
  ["rect", { width: "20", height: "5", x: "2", y: "3", rx: "1", key: "1wp1u1" }],
49191
49192
  ["path", { d: "M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8", key: "1s80jp" }],
49192
49193
  ["path", { d: "M10 12h4", key: "a56b0p" }]
49193
49194
  ];
49194
- const Archive = createLucideIcon("archive", __iconNode$1l);
49195
+ const Archive = createLucideIcon("archive", __iconNode$1m);
49195
49196
 
49196
49197
  /**
49197
49198
  * @license lucide-react v0.507.0 - ISC
@@ -49201,11 +49202,11 @@ const Archive = createLucideIcon("archive", __iconNode$1l);
49201
49202
  */
49202
49203
 
49203
49204
 
49204
- const __iconNode$1k = [
49205
+ const __iconNode$1l = [
49205
49206
  ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
49206
49207
  ["path", { d: "M19 12H5", key: "x3x0zl" }]
49207
49208
  ];
49208
- const ArrowLeft = createLucideIcon("arrow-left", __iconNode$1k);
49209
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$1l);
49209
49210
 
49210
49211
  /**
49211
49212
  * @license lucide-react v0.507.0 - ISC
@@ -49215,7 +49216,7 @@ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$1k);
49215
49216
  */
49216
49217
 
49217
49218
 
49218
- const __iconNode$1j = [
49219
+ const __iconNode$1k = [
49219
49220
  ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0", key: "vwvbt9" }],
49220
49221
  [
49221
49222
  "path",
@@ -49225,7 +49226,7 @@ const __iconNode$1j = [
49225
49226
  }
49226
49227
  ]
49227
49228
  ];
49228
- const Bell = createLucideIcon("bell", __iconNode$1j);
49229
+ const Bell = createLucideIcon("bell", __iconNode$1k);
49229
49230
 
49230
49231
  /**
49231
49232
  * @license lucide-react v0.507.0 - ISC
@@ -49235,13 +49236,13 @@ const Bell = createLucideIcon("bell", __iconNode$1j);
49235
49236
  */
49236
49237
 
49237
49238
 
49238
- const __iconNode$1i = [
49239
+ const __iconNode$1j = [
49239
49240
  [
49240
49241
  "path",
49241
49242
  { d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
49242
49243
  ]
49243
49244
  ];
49244
- const Bold$1 = createLucideIcon("bold", __iconNode$1i);
49245
+ const Bold$1 = createLucideIcon("bold", __iconNode$1j);
49245
49246
 
49246
49247
  /**
49247
49248
  * @license lucide-react v0.507.0 - ISC
@@ -49251,7 +49252,7 @@ const Bold$1 = createLucideIcon("bold", __iconNode$1i);
49251
49252
  */
49252
49253
 
49253
49254
 
49254
- const __iconNode$1h = [
49255
+ const __iconNode$1i = [
49255
49256
  ["path", { d: "M12 7v14", key: "1akyts" }],
49256
49257
  [
49257
49258
  "path",
@@ -49261,7 +49262,7 @@ const __iconNode$1h = [
49261
49262
  }
49262
49263
  ]
49263
49264
  ];
49264
- const BookOpen = createLucideIcon("book-open", __iconNode$1h);
49265
+ const BookOpen = createLucideIcon("book-open", __iconNode$1i);
49265
49266
 
49266
49267
  /**
49267
49268
  * @license lucide-react v0.507.0 - ISC
@@ -49271,7 +49272,7 @@ const BookOpen = createLucideIcon("book-open", __iconNode$1h);
49271
49272
  */
49272
49273
 
49273
49274
 
49274
- const __iconNode$1g = [
49275
+ const __iconNode$1h = [
49275
49276
  ["path", { d: "M12 8V4H8", key: "hb8ula" }],
49276
49277
  ["rect", { width: "16", height: "12", x: "4", y: "8", rx: "2", key: "enze0r" }],
49277
49278
  ["path", { d: "M2 14h2", key: "vft8re" }],
@@ -49279,7 +49280,7 @@ const __iconNode$1g = [
49279
49280
  ["path", { d: "M15 13v2", key: "1xurst" }],
49280
49281
  ["path", { d: "M9 13v2", key: "rq6x2g" }]
49281
49282
  ];
49282
- const Bot = createLucideIcon("bot", __iconNode$1g);
49283
+ const Bot = createLucideIcon("bot", __iconNode$1h);
49283
49284
 
49284
49285
  /**
49285
49286
  * @license lucide-react v0.507.0 - ISC
@@ -49289,11 +49290,11 @@ const Bot = createLucideIcon("bot", __iconNode$1g);
49289
49290
  */
49290
49291
 
49291
49292
 
49292
- const __iconNode$1f = [
49293
+ const __iconNode$1g = [
49293
49294
  ["path", { d: "M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16", key: "jecpp" }],
49294
49295
  ["rect", { width: "20", height: "14", x: "2", y: "6", rx: "2", key: "i6l2r4" }]
49295
49296
  ];
49296
- const Briefcase = createLucideIcon("briefcase", __iconNode$1f);
49297
+ const Briefcase = createLucideIcon("briefcase", __iconNode$1g);
49297
49298
 
49298
49299
  /**
49299
49300
  * @license lucide-react v0.507.0 - ISC
@@ -49303,13 +49304,13 @@ const Briefcase = createLucideIcon("briefcase", __iconNode$1f);
49303
49304
  */
49304
49305
 
49305
49306
 
49306
- const __iconNode$1e = [
49307
+ const __iconNode$1f = [
49307
49308
  ["path", { d: "M8 2v4", key: "1cmpym" }],
49308
49309
  ["path", { d: "M16 2v4", key: "4m81vk" }],
49309
49310
  ["rect", { width: "18", height: "18", x: "3", y: "4", rx: "2", key: "1hopcy" }],
49310
49311
  ["path", { d: "M3 10h18", key: "8toen8" }]
49311
49312
  ];
49312
- const Calendar$1 = createLucideIcon("calendar", __iconNode$1e);
49313
+ const Calendar$1 = createLucideIcon("calendar", __iconNode$1f);
49313
49314
 
49314
49315
  /**
49315
49316
  * @license lucide-react v0.507.0 - ISC
@@ -49319,11 +49320,11 @@ const Calendar$1 = createLucideIcon("calendar", __iconNode$1e);
49319
49320
  */
49320
49321
 
49321
49322
 
49322
- const __iconNode$1d = [
49323
+ const __iconNode$1e = [
49323
49324
  ["path", { d: "M18 6 7 17l-5-5", key: "116fxf" }],
49324
49325
  ["path", { d: "m22 10-7.5 7.5L13 16", key: "ke71qq" }]
49325
49326
  ];
49326
- const CheckCheck = createLucideIcon("check-check", __iconNode$1d);
49327
+ const CheckCheck = createLucideIcon("check-check", __iconNode$1e);
49327
49328
 
49328
49329
  /**
49329
49330
  * @license lucide-react v0.507.0 - ISC
@@ -49333,8 +49334,8 @@ const CheckCheck = createLucideIcon("check-check", __iconNode$1d);
49333
49334
  */
49334
49335
 
49335
49336
 
49336
- const __iconNode$1c = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
49337
- const Check = createLucideIcon("check", __iconNode$1c);
49337
+ const __iconNode$1d = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
49338
+ const Check = createLucideIcon("check", __iconNode$1d);
49338
49339
 
49339
49340
  /**
49340
49341
  * @license lucide-react v0.507.0 - ISC
@@ -49344,8 +49345,8 @@ const Check = createLucideIcon("check", __iconNode$1c);
49344
49345
  */
49345
49346
 
49346
49347
 
49347
- const __iconNode$1b = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
49348
- const ChevronDown = createLucideIcon("chevron-down", __iconNode$1b);
49348
+ const __iconNode$1c = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]];
49349
+ const ChevronDown = createLucideIcon("chevron-down", __iconNode$1c);
49349
49350
 
49350
49351
  /**
49351
49352
  * @license lucide-react v0.507.0 - ISC
@@ -49355,11 +49356,11 @@ const ChevronDown = createLucideIcon("chevron-down", __iconNode$1b);
49355
49356
  */
49356
49357
 
49357
49358
 
49358
- const __iconNode$1a = [
49359
+ const __iconNode$1b = [
49359
49360
  ["path", { d: "m17 18-6-6 6-6", key: "1yerx2" }],
49360
49361
  ["path", { d: "M7 6v12", key: "1p53r6" }]
49361
49362
  ];
49362
- const ChevronFirst = createLucideIcon("chevron-first", __iconNode$1a);
49363
+ const ChevronFirst = createLucideIcon("chevron-first", __iconNode$1b);
49363
49364
 
49364
49365
  /**
49365
49366
  * @license lucide-react v0.507.0 - ISC
@@ -49369,8 +49370,8 @@ const ChevronFirst = createLucideIcon("chevron-first", __iconNode$1a);
49369
49370
  */
49370
49371
 
49371
49372
 
49372
- const __iconNode$19 = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]];
49373
- const ChevronLeft = createLucideIcon("chevron-left", __iconNode$19);
49373
+ const __iconNode$1a = [["path", { d: "m15 18-6-6 6-6", key: "1wnfg3" }]];
49374
+ const ChevronLeft = createLucideIcon("chevron-left", __iconNode$1a);
49374
49375
 
49375
49376
  /**
49376
49377
  * @license lucide-react v0.507.0 - ISC
@@ -49380,11 +49381,11 @@ const ChevronLeft = createLucideIcon("chevron-left", __iconNode$19);
49380
49381
  */
49381
49382
 
49382
49383
 
49383
- const __iconNode$18 = [
49384
+ const __iconNode$19 = [
49384
49385
  ["path", { d: "m7 18 6-6-6-6", key: "lwmzdw" }],
49385
49386
  ["path", { d: "M17 6v12", key: "1o0aio" }]
49386
49387
  ];
49387
- const ChevronLast = createLucideIcon("chevron-last", __iconNode$18);
49388
+ const ChevronLast = createLucideIcon("chevron-last", __iconNode$19);
49388
49389
 
49389
49390
  /**
49390
49391
  * @license lucide-react v0.507.0 - ISC
@@ -49394,8 +49395,8 @@ const ChevronLast = createLucideIcon("chevron-last", __iconNode$18);
49394
49395
  */
49395
49396
 
49396
49397
 
49397
- const __iconNode$17 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
49398
- const ChevronRight = createLucideIcon("chevron-right", __iconNode$17);
49398
+ const __iconNode$18 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
49399
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$18);
49399
49400
 
49400
49401
  /**
49401
49402
  * @license lucide-react v0.507.0 - ISC
@@ -49405,8 +49406,8 @@ const ChevronRight = createLucideIcon("chevron-right", __iconNode$17);
49405
49406
  */
49406
49407
 
49407
49408
 
49408
- const __iconNode$16 = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]];
49409
- const ChevronUp = createLucideIcon("chevron-up", __iconNode$16);
49409
+ const __iconNode$17 = [["path", { d: "m18 15-6-6-6 6", key: "153udz" }]];
49410
+ const ChevronUp = createLucideIcon("chevron-up", __iconNode$17);
49410
49411
 
49411
49412
  /**
49412
49413
  * @license lucide-react v0.507.0 - ISC
@@ -49416,11 +49417,11 @@ const ChevronUp = createLucideIcon("chevron-up", __iconNode$16);
49416
49417
  */
49417
49418
 
49418
49419
 
49419
- const __iconNode$15 = [
49420
+ const __iconNode$16 = [
49420
49421
  ["path", { d: "m11 17-5-5 5-5", key: "13zhaf" }],
49421
49422
  ["path", { d: "m18 17-5-5 5-5", key: "h8a8et" }]
49422
49423
  ];
49423
- const ChevronsLeft = createLucideIcon("chevrons-left", __iconNode$15);
49424
+ const ChevronsLeft = createLucideIcon("chevrons-left", __iconNode$16);
49424
49425
 
49425
49426
  /**
49426
49427
  * @license lucide-react v0.507.0 - ISC
@@ -49430,11 +49431,26 @@ const ChevronsLeft = createLucideIcon("chevrons-left", __iconNode$15);
49430
49431
  */
49431
49432
 
49432
49433
 
49433
- const __iconNode$14 = [
49434
+ const __iconNode$15 = [
49434
49435
  ["path", { d: "m6 17 5-5-5-5", key: "xnjwq" }],
49435
49436
  ["path", { d: "m13 17 5-5-5-5", key: "17xmmf" }]
49436
49437
  ];
49437
- const ChevronsRight = createLucideIcon("chevrons-right", __iconNode$14);
49438
+ const ChevronsRight = createLucideIcon("chevrons-right", __iconNode$15);
49439
+
49440
+ /**
49441
+ * @license lucide-react v0.507.0 - ISC
49442
+ *
49443
+ * This source code is licensed under the ISC license.
49444
+ * See the LICENSE file in the root directory of this source tree.
49445
+ */
49446
+
49447
+
49448
+ const __iconNode$14 = [
49449
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
49450
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
49451
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
49452
+ ];
49453
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$14);
49438
49454
 
49439
49455
  /**
49440
49456
  * @license lucide-react v0.507.0 - ISC
@@ -82662,7 +82678,7 @@ TabsTrigger.displayName = Trigger$3.displayName;
82662
82678
  const TabsContent = React.forwardRef(({ className, ...props }, ref) => (jsx(Content$1, { ref: ref, className: cn("ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none", className), ...props })));
82663
82679
  TabsContent.displayName = Content$1.displayName;
82664
82680
 
82665
- const getUserName = () => {
82681
+ const getUserName$1 = () => {
82666
82682
  var _a;
82667
82683
  return (_a = JSON.parse(localStorage.getItem("userData"))) === null || _a === void 0 ? void 0 : _a.user_nicename;
82668
82684
  };
@@ -82829,7 +82845,7 @@ function UsersTab({ tenant, currentPage, itemsPerPage, onInviteSuccess }) {
82829
82845
  email,
82830
82846
  platform_key: tenant,
82831
82847
  redirect_to: window.location.origin + window.location.pathname,
82832
- source: getUserName(),
82848
+ source: getUserName$1(),
82833
82849
  active: true,
82834
82850
  },
82835
82851
  }).unwrap();
@@ -82909,7 +82925,7 @@ function UsersTab({ tenant, currentPage, itemsPerPage, onInviteSuccess }) {
82909
82925
  email: emailValue,
82910
82926
  platform_key: platformKey || tenant,
82911
82927
  redirect_to: window.location.origin + window.location.pathname,
82912
- source: getUserName(),
82928
+ source: getUserName$1(),
82913
82929
  active: true,
82914
82930
  ...(companyNameIndex !== -1 &&
82915
82931
  ((_c = row[companyNameIndex]) === null || _c === void 0 ? void 0 : _c.trim()) && {
@@ -83091,7 +83107,7 @@ function CoursesTab({ tenant, currentPage, itemsPerPage, hasManageUsersPermissio
83091
83107
  // Search query for courses
83092
83108
  const { data: coursesData, isLoading: isLoadingCourses, isFetching: isFetchingCourses, } = useGetPersonnalizedSearchQuery([
83093
83109
  {
83094
- username: getUserName(),
83110
+ username: getUserName$1(),
83095
83111
  content: ['courses'],
83096
83112
  query: debouncedCourseSearch || '',
83097
83113
  returnFacet: false,
@@ -83467,7 +83483,7 @@ function ProgramsTab({ tenant, currentPage, itemsPerPage, hasManageUsersPermissi
83467
83483
  // Search query for programs
83468
83484
  const { data: programsData, isLoading: isLoadingPrograms, isFetching: isFetchingPrograms, } = useGetPersonnalizedSearchQuery([
83469
83485
  {
83470
- username: getUserName(),
83486
+ username: getUserName$1(),
83471
83487
  content: ['programs'],
83472
83488
  query: debouncedProgramSearch || '',
83473
83489
  returnFacet: false,
@@ -122438,7 +122454,7 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
122438
122454
  }, [currentTemplateDetail, isHumanSupport, template]);
122439
122455
  useEffect(() => {
122440
122456
  try {
122441
- const storedUserName = getUserName();
122457
+ const storedUserName = getUserName$1();
122442
122458
  if (storedUserName) {
122443
122459
  setUsername(storedUserName);
122444
122460
  }
@@ -186823,6 +186839,2490 @@ function AnalyticsMonetizationStats({ tenantKey }) {
186823
186839
  setPaywallPage(paywallsData.previous_page), children: "Previous" }), jsxs("span", { className: "text-xs text-gray-500", children: ["Page ", paywallPage] }), jsx(Button$1, { variant: "outline", size: "sm", className: "h-8 text-xs", disabled: paywallsData.next_page === null, onClick: () => paywallsData.next_page !== null && setPaywallPage(paywallsData.next_page), children: "Next" })] })] }))] }) })] }));
186824
186840
  }
186825
186841
 
186842
+ const SkeletonMultiplier = ({ Skeleton, multiplier }) => {
186843
+ return (jsx(Fragment$1, { children: Array.from({ length: multiplier }).map((_, index) => (jsx(Skeleton, {}, index))) }));
186844
+ };
186845
+
186846
+ const SkeletonActivityStatBox = () => {
186847
+ return (jsxs("div", { className: "flex flex-col items-center justify-center rounded-lg border border-gray-100 bg-gray-50 p-3 transition-shadow hover:shadow-sm", children: [jsx("div", { className: "mb-1 h-6 w-12 animate-pulse rounded bg-gray-200" }), jsx("div", { className: "h-3 w-16 animate-pulse rounded bg-gray-200" })] }));
186848
+ };
186849
+
186850
+ function CourseCardSkeleton() {
186851
+ return (jsxs("div", { className: "relative flex h-full w-full flex-col overflow-hidden rounded-md border border-gray-200 bg-white shadow-sm", children: [jsx("div", { className: "animate-shimmer pointer-events-none absolute inset-0 z-10" }), jsx("div", { className: "skeleton-bg relative aspect-video w-full" }), jsxs("div", { className: "flex flex-1 flex-col justify-between p-4", children: [jsxs("div", { children: [jsx("div", { className: "skeleton-bg mb-2 h-4 w-24 rounded-sm" }), jsx("div", { className: "skeleton-bg mb-2 h-4 w-full rounded-sm" }), jsx("div", { className: "skeleton-bg h-4 w-3/4 rounded-sm" })] }), jsxs("div", { className: "mt-4 flex items-center justify-between", children: [jsx("div", { className: "skeleton-bg h-8 w-8 rounded-sm" }), jsx("div", { className: "skeleton-bg h-8 w-8 rounded-sm" })] })] })] }));
186852
+ }
186853
+
186854
+ const DefaultEmptyBox = ({ image = '/images/empty-data-icon.svg', message = 'No data available', imageSize = 40, bordered = true, className = '', }) => {
186855
+ return (jsx("div", { className: `border ${bordered ? 'border-gray-200' : 'border-none'} rounded-lg p-12 ${className}`, children: jsxs("div", { className: "flex flex-col items-center justify-center text-center", children: [jsx("div", { className: "mb-4 flex h-12 w-12 items-center justify-center", children: jsx("img", { src: image, alt: message, width: imageSize, height: imageSize, className: "h-10 w-10" }) }), jsx("p", { className: "text-gray-600", children: message })] }) }));
186856
+ };
186857
+
186858
+ const CredentialMiniBoxSkeleton = () => {
186859
+ return (jsxs("div", { className: "flex animate-pulse items-start rounded-lg border border-gray-200 bg-white p-4", children: [jsx("div", { className: "mr-4 h-12 w-12 flex-shrink-0 rounded-full bg-gray-200" }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "mb-2 h-4 w-3/4 rounded bg-gray-200" }), jsx("div", { className: "mb-2 h-3 w-1/2 rounded bg-gray-200" }), jsx("div", { className: "h-3 w-2/3 rounded bg-gray-200" })] })] }));
186860
+ };
186861
+
186862
+ const SkillBox = ({ skill, onSkillClick, showRating = true, iconSrc = '/images/empty-data-icon.svg', }) => {
186863
+ return (jsxs("div", { className: "flex cursor-pointer flex-col items-center rounded-lg border border-gray-200 p-6 transition-all hover:shadow-md", onClick: () => onSkillClick === null || onSkillClick === void 0 ? void 0 : onSkillClick(skill), children: [jsx("div", { className: "mb-2", children: jsx("img", { src: iconSrc, alt: "Skills icon", width: 32, height: 32, className: "h-8 w-8" }) }), jsx("p", { className: "mb-4 text-center text-sm text-gray-600", children: skill.name }), showRating && (jsxs("div", { className: "relative mb-4 h-20 w-20", children: [jsxs("svg", { className: "h-full w-full", viewBox: "0 0 100 100", children: [jsx("circle", { cx: "50", cy: "50", r: "40", fill: "none", stroke: "#E5E7EB", strokeWidth: "10" }), jsx("circle", { cx: "50", cy: "50", r: "40", fill: "none", stroke: "#F8B43A", strokeWidth: "10", strokeDasharray: "251.2", strokeDashoffset: (100 - ((skill.level || 1) / 5) * 100) * 2.512, transform: "rotate(-90 50 50)" })] }), jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsx("span", { className: "text-2xl font-bold text-gray-700", children: skill.level || 1 }) })] })), jsx(Star$1, { className: "h-6 w-6 fill-amber-500 text-amber-500" })] }));
186864
+ };
186865
+
186866
+ const SkeletonSkillBox = () => {
186867
+ return (jsxs("div", { className: "flex w-[200px] flex-shrink-0 animate-pulse flex-col items-center rounded-lg border border-gray-200 p-6", children: [jsx("div", { className: "mb-2", children: jsx("div", { className: "h-8 w-8 rounded-full bg-gray-200" }) }), jsx("div", { className: "mb-4 h-4 w-20 rounded bg-gray-200" }), jsxs("div", { className: "relative mb-4 h-20 w-20", children: [jsx("svg", { className: "h-full w-full", viewBox: "0 0 100 100", children: jsx("circle", { cx: "50", cy: "50", r: "40", fill: "none", stroke: "#E5E7EB", strokeWidth: "10" }) }), jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsx("div", { className: "h-8 w-8 rounded-full bg-gray-200" }) })] }), jsx(Star$1, { className: "h-6 w-6 text-gray-200" })] }));
186868
+ };
186869
+
186870
+ const RATING_DESCRIPTIONS = {
186871
+ 1: 'Basic understanding of fundamentals; requires significant guidance.',
186872
+ 2: 'Familiar with core concepts; can complete routine tasks with some supervision.',
186873
+ 3: 'Capable of managing varied tasks; understands nuances but may seek guidance for complexities.',
186874
+ 4: 'Proficient with advanced concepts; can work independently on complex tasks.',
186875
+ 5: 'Expert level mastery; can innovate and teach others in this domain.',
186876
+ };
186877
+ function SkillDetailModal({ skill, updatingSkill = false, deletingSkill = false, onClose, onRatingChange, onDeleteSkill, }) {
186878
+ const [originalRating] = useState(skill.rating || 1);
186879
+ const [tempRating, setTempRating] = useState(skill.rating || 1);
186880
+ const handleTempRatingChange = (rating) => {
186881
+ setTempRating(rating);
186882
+ };
186883
+ const handleConfirm = () => {
186884
+ if (updatingSkill)
186885
+ return;
186886
+ if (onRatingChange) {
186887
+ onRatingChange(tempRating);
186888
+ }
186889
+ };
186890
+ const handleCancel = () => {
186891
+ onClose();
186892
+ };
186893
+ return (jsx("div", { className: "bg-opacity-50 fixed inset-0 z-50 flex items-center justify-center bg-black p-4", children: jsxs("div", { className: "max-h-[85vh] w-full max-w-md overflow-y-auto rounded-lg bg-white", children: [jsxs("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gradient-to-r from-[var(--background-light)] to-[var(--primary-light)]/30 p-4", children: [jsxs("h3", { className: "text-lg font-medium text-[var(--text)]", children: ["Rate your expertise in \"", skill.name, "\""] }), jsx("button", { onClick: handleCancel, className: "rounded-full p-1 text-gray-400 hover:bg-[var(--primary-light)] hover:text-gray-500", "aria-label": "Close skill details", children: jsx(X, { className: "h-5 w-5" }) })] }), jsxs("div", { className: "max-h-[70vh] overflow-y-auto p-6", style: { scrollbarWidth: 'none', msOverflowStyle: 'none' }, children: [jsxs("div", { className: "mb-8", children: [jsxs("div", { className: "relative mb-10 pt-3", children: [jsx("div", { className: "h-2 rounded-full bg-gradient-to-r from-amber-100 to-amber-200" }), [1, 2, 3, 4, 5].map((rating) => {
186894
+ const position = ((rating - 1) / 4) * 100;
186895
+ return (jsx("div", { onClick: () => handleTempRatingChange(rating), className: `absolute top-3 -mt-1 -ml-2 h-4 w-4 cursor-pointer rounded-full border-2 transition-all duration-200 ${rating <= tempRating
186896
+ ? 'border-white bg-amber-500'
186897
+ : 'border-amber-200 bg-white'}`, style: { left: `${position}%` } }, rating));
186898
+ }), jsx("div", { className: "absolute top-3 left-0 h-2 rounded-full bg-gradient-to-r from-amber-400 to-amber-500 transition-all duration-200", style: {
186899
+ width: tempRating === 1 ? '0%' : `${((tempRating - 1) / 4) * 100}%`,
186900
+ } }), [1, 2, 3, 4, 5].map((rating) => {
186901
+ const position = ((rating - 1) / 4) * 100;
186902
+ return (jsx("div", { onClick: () => handleTempRatingChange(rating), className: "absolute top-7 -ml-2 w-4 cursor-pointer text-center transition-all duration-200", style: { left: `${position}%` }, children: jsx("span", { className: `text-sm font-medium ${rating === tempRating ? 'text-amber-600' : 'text-gray-500'}`, children: rating }) }, `label-${rating}`));
186903
+ }), jsx("div", { className: "absolute top-3 -mt-2 -ml-3 h-6 w-6 rounded-full border-2 border-white bg-amber-500 shadow-md transition-all duration-200", style: { left: `${((tempRating - 1) / 4) * 100}%` } }), jsx("input", { type: "range", min: 1, max: 5, step: 1, value: tempRating, onChange: (e) => handleTempRatingChange(Number.parseInt(e.target.value)), className: "absolute top-2 z-10 h-4 w-full cursor-pointer opacity-0", style: { margin: 0, padding: 0 }, "aria-label": "Skill rating" })] }), jsxs("div", { className: "mt-4 flex justify-between text-sm font-medium", children: [jsx("span", { className: "text-gray-600", children: "Beginner" }), jsxs("span", { className: "text-amber-600", children: ["Level ", tempRating] }), jsx("span", { className: "text-gray-600", children: "Expert" })] })] }), jsxs("div", { className: "mb-6 flex flex-col items-center", children: [jsxs("div", { className: "relative mb-4 h-20 w-20", children: [jsxs("svg", { className: "h-full w-full", viewBox: "0 0 100 100", children: [jsx("circle", { cx: "50", cy: "50", r: "40", fill: "none", stroke: "#E5E7EB", strokeWidth: "10" }), jsx("circle", { cx: "50", cy: "50", r: "40", fill: "none", stroke: "#F8B43A", strokeWidth: "10", strokeDasharray: "251.2", strokeDashoffset: `${(100 - ((tempRating || 1) / 5) * 100) * 2.512}`, transform: "rotate(-90 50 50)" })] }), jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: jsx("span", { className: "text-3xl font-bold text-gray-700", children: tempRating }) })] }), jsx("p", { className: "max-w-xs text-center text-sm text-gray-600", children: RATING_DESCRIPTIONS[tempRating] })] })] }), jsxs("div", { className: "flex justify-between border-t border-gray-200 bg-gradient-to-r from-[var(--background-light)] to-[var(--primary-light)]/30 p-4", children: [jsxs("div", { className: "flex gap-2", children: [jsx("button", { onClick: handleCancel, className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50", children: "Cancel" }), onDeleteSkill && (jsx("button", { onClick: onDeleteSkill, className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50", children: deletingSkill ? 'Deleting...' : 'Delete skill' }))] }), jsx("button", { onClick: handleConfirm, className: `rounded-md bg-gradient-to-r from-[var(--button-primary-gradient-from)] to-[var(--button-primary-gradient-to)] px-4 py-2 text-sm font-medium text-[var(--button-primary-text)] transition-all ${tempRating !== originalRating
186904
+ ? 'animate-pulse hover:opacity-[var(--button-primary-hover-opacity)]'
186905
+ : 'hover:opacity-[var(--button-primary-hover-opacity)]'}`, children: updatingSkill ? 'Updating...' : 'Confirm' })] })] }) }));
186906
+ }
186907
+
186908
+ const SkeletonPathwayBox = () => {
186909
+ return (jsxs("div", { className: "h-[200px] animate-pulse overflow-hidden rounded-lg border border-gray-200 bg-white", children: [jsx("div", { className: "h-32 bg-gray-200" }), jsxs("div", { className: "p-4", children: [jsx("div", { className: "mb-4 h-4 w-3/4 rounded bg-gray-200" }), jsx("div", { className: "mb-1 h-2 w-full rounded bg-gray-200" }), jsx("div", { className: "h-2 w-1/4 rounded bg-gray-200" })] })] }));
186910
+ };
186911
+
186912
+ /**
186913
+ * Skeleton placeholder for a single profile info card (Last Accessed,
186914
+ * Joined, Total Time Spent, Top Content). Ported from the skillsai SPA
186915
+ * (components/skeleton-profile-info-card.tsx).
186916
+ */
186917
+ const SkeletonProfileInfoCard = () => (jsxs("div", { className: "flex items-start rounded-sm border border-[var(--border)] p-4", children: [jsx(Skeleton, { className: "mr-4 h-12 w-12 rounded-sm" }), jsxs("div", { children: [jsx(Skeleton, { className: "mb-2 h-4 w-24" }), jsx(Skeleton, { className: "h-5 w-32" })] })] }));
186918
+
186919
+ /**
186920
+ * Browser-only helpers used by skills-profile hooks and components.
186921
+ *
186922
+ * These are sync localStorage accessors ported from the skillsai SPA. They
186923
+ * intentionally live here (not in @iblai/web-utils) to avoid colliding with
186924
+ * the existing async `getUserName(storageService)` helper in
186925
+ * packages/web-utils/src/providers/auth-provider.tsx, and because no other
186926
+ * package consumes them.
186927
+ */
186928
+ const LS_KEYS = {
186929
+ TENANT: 'tenant',
186930
+ CURRENT_TENANT: 'current_tenant',
186931
+ USER_DATA: 'userData',
186932
+ };
186933
+ const isJSON = (text) => {
186934
+ if (typeof text !== 'string')
186935
+ return false;
186936
+ try {
186937
+ JSON.parse(text);
186938
+ return true;
186939
+ }
186940
+ catch (_a) {
186941
+ return false;
186942
+ }
186943
+ };
186944
+ const readLocalStorage = (key) => {
186945
+ if (typeof window === 'undefined')
186946
+ return null;
186947
+ try {
186948
+ return window.localStorage.getItem(key);
186949
+ }
186950
+ catch (_a) {
186951
+ return null;
186952
+ }
186953
+ };
186954
+ /** Returns the current tenant slug from localStorage, or empty string. */
186955
+ function getTenant() {
186956
+ return readLocalStorage(LS_KEYS.TENANT) || '';
186957
+ }
186958
+ /** Returns the `org` field of the currently selected tenant, or null. */
186959
+ function getOrg() {
186960
+ var _a;
186961
+ const currentTenant = readLocalStorage(LS_KEYS.CURRENT_TENANT);
186962
+ return currentTenant && isJSON(currentTenant) ? (_a = JSON.parse(currentTenant)) === null || _a === void 0 ? void 0 : _a.org : null;
186963
+ }
186964
+ /** Returns the logged-in user's numeric user_id from localStorage, or null. */
186965
+ function getUserId() {
186966
+ var _a;
186967
+ const userData = readLocalStorage(LS_KEYS.USER_DATA);
186968
+ return userData && isJSON(userData) ? (_a = JSON.parse(userData)) === null || _a === void 0 ? void 0 : _a.user_id : null;
186969
+ }
186970
+ /** Returns the logged-in user's nicename (username) from localStorage, or null. */
186971
+ function getUserName() {
186972
+ var _a;
186973
+ const userData = readLocalStorage(LS_KEYS.USER_DATA);
186974
+ return userData && isJSON(userData) ? (_a = JSON.parse(userData)) === null || _a === void 0 ? void 0 : _a.user_nicename : null;
186975
+ }
186976
+ /**
186977
+ * Returns true if the current document is rendered inside an iframe. Safely
186978
+ * handles cross-origin access errors (throw → treated as embedded) and the
186979
+ * server-rendering case (no window → treated as embedded).
186980
+ */
186981
+ function inIframe() {
186982
+ try {
186983
+ return typeof window !== 'undefined' && window.self !== window.top;
186984
+ }
186985
+ catch (_a) {
186986
+ return true;
186987
+ }
186988
+ }
186989
+ /** Returns a path to a random bundled course placeholder image. */
186990
+ function getRandomCourseImage() {
186991
+ const courseImages = [
186992
+ '/images/courses/c1s.jpeg',
186993
+ '/images/courses/c2s.jpeg',
186994
+ '/images/courses/c3s.jpeg',
186995
+ '/images/courses/c4s.jpeg',
186996
+ '/images/courses/c5s.jpeg',
186997
+ '/images/courses/c6s.jpeg',
186998
+ '/images/courses/c7s.jpeg',
186999
+ '/images/courses/c8s.jpeg',
187000
+ ];
187001
+ return courseImages[Math.floor(Math.random() * courseImages.length)];
187002
+ }
187003
+ /**
187004
+ * Triggers the browser's print dialog scoped to a specific DOM element.
187005
+ * Injects a transient @media print stylesheet and cleans it up afterward.
187006
+ */
187007
+ function inBrowserPrint(layoutToPrint) {
187008
+ if (!layoutToPrint)
187009
+ return;
187010
+ layoutToPrint.classList.add('in-browser-printable');
187011
+ const style = document.createElement('style');
187012
+ style.innerHTML = `
187013
+ @media print {
187014
+ body * { visibility: hidden; }
187015
+ .in-browser-printable, .in-browser-printable * { visibility: visible; }
187016
+ .in-browser-printable {
187017
+ position: absolute;
187018
+ left: 0;
187019
+ top: 0;
187020
+ width: 100%;
187021
+ }
187022
+ }
187023
+ `;
187024
+ document.head.appendChild(style);
187025
+ window.print();
187026
+ document.head.removeChild(style);
187027
+ layoutToPrint.classList.remove('in-browser-printable');
187028
+ }
187029
+
187030
+ /**
187031
+ * Thin wrapper around useGetUserMetadataQuery that reads the username
187032
+ * from localStorage and skips the request when there is no logged-in
187033
+ * user.
187034
+ *
187035
+ * Ported from the skillsai SPA (hooks/users/use-usermetadata.tsx).
187036
+ */
187037
+ const useUserMetadata = () => {
187038
+ const username = getUserName();
187039
+ const { data: userMetaData, isLoading: userMetaDataLoading, isError: userMetaDataError, } = useGetUserMetadataQuery({ params: { username } }, { skip: !username });
187040
+ return { userMetaData, userMetaDataLoading, userMetaDataError };
187041
+ };
187042
+
187043
+ /**
187044
+ * Presentational avatar for the logged-in user. Pulls the user metadata
187045
+ * via the shared useUserMetadata hook and picks the best available
187046
+ * image source (uploaded profile image → Gravatar → initials fallback).
187047
+ *
187048
+ * Ported from the skillsai SPA (components/header/profile/user-avatar.tsx).
187049
+ * Note: `enableGravatarOnProfilePic` config is now passed in as a prop
187050
+ * rather than read from a skillsai-local config module.
187051
+ */
187052
+ const UserAvatar = ({ size = 32, containerClassName = '', enableGravatar = true, }) => {
187053
+ var _a, _b, _c, _d;
187054
+ const { userMetaData, userMetaDataLoading } = useUserMetadata();
187055
+ const data = userMetaData;
187056
+ const hasImage = !!((_a = data === null || data === void 0 ? void 0 : data.profile_image) === null || _a === void 0 ? void 0 : _a.has_image);
187057
+ return (jsx(Avatar, { className: containerClassName, children: userMetaDataLoading ? (jsx(Skeleton, { className: "h-8 w-8 rounded-full" })) : (jsxs(Fragment$1, { children: [hasImage ? (jsx(AvatarImage, { src: (_b = data === null || data === void 0 ? void 0 : data.profile_image) === null || _b === void 0 ? void 0 : _b.image_url_large, alt: (_c = data === null || data === void 0 ? void 0 : data.name) !== null && _c !== void 0 ? _c : '', className: "w-full" })) : enableGravatar ? (jsx(Gravatar$1, { className: "w-full", email: (_d = data === null || data === void 0 ? void 0 : data.email) !== null && _d !== void 0 ? _d : '', size: size })) : null, jsx(AvatarFallback, { className: cn(enableGravatar && !hasImage ? 'hidden' : ''), children: getInitials((data === null || data === void 0 ? void 0 : data.name) || (data === null || data === void 0 ? void 0 : data.username) || (data === null || data === void 0 ? void 0 : data.email) || '') })] })) }));
187058
+ };
187059
+
187060
+ /**
187061
+ * Presentational 7-day time-spent bar chart. Callers supply the data
187062
+ * (typically via useProfileTimeSpent) and a loading flag; this component
187063
+ * does no data fetching.
187064
+ *
187065
+ * Ported from the skillsai SPA (components/profile-time-chart.tsx),
187066
+ * refactored to be presentational.
187067
+ */
187068
+ const ProfileTimeChart = ({ data, loading = false }) => {
187069
+ const renderLegend = (props) => {
187070
+ var _a;
187071
+ const payload = (_a = props.payload) !== null && _a !== void 0 ? _a : [];
187072
+ return (jsx("div", { className: "flex w-full justify-center pt-0 pb-4", children: payload.map((entry, index) => (jsxs("div", { className: "mx-2 flex items-center", children: [jsx("div", { className: "mr-2 h-3 w-3 bg-amber-500 opacity-70" }), jsx("span", { className: "text-xs text-gray-600", children: entry.value })] }, `item-${index}`))) }));
187073
+ };
187074
+ return (jsx("div", { className: "mb-4 border-b border-gray-200 pb-2", children: loading ? (jsx("div", { "data-testid": "profile-time-chart-loading", className: "flex h-64 items-center justify-center", children: jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-amber-500 border-t-transparent" }) })) : (jsx("div", { className: "relative h-64", children: jsx(ResponsiveContainer, { width: "100%", height: "100%", children: jsxs(BarChart, { data: data, margin: { top: 10, right: 0, left: 0, bottom: 20 }, children: [jsx(CartesianGrid, { strokeDasharray: "3 3" }), jsx(XAxis, { dataKey: "date", axisLine: true, tickLine: true, tick: { fill: '#9CA3AF', fontSize: 12 }, height: 50 }), jsx(YAxis, { axisLine: true, tickLine: true, tick: { fill: '#9CA3AF', fontSize: 12 }, label: {
187075
+ value: 'Minutes',
187076
+ angle: -90,
187077
+ position: 'insideLeft',
187078
+ style: { textAnchor: 'middle', fill: '#9CA3AF', fontSize: 12 },
187079
+ }, domain: [0, 30], ticks: [0, 10, 20, 30, 40, 50] }), jsx(Legend, { content: renderLegend, verticalAlign: "top", height: 36 }), jsx(Bar, { dataKey: "minutes", name: "Minutes", barSize: 130, children: data === null || data === void 0 ? void 0 : data.map((_entry, index) => (jsx(Cell, { fill: "rgba(245, 158, 11, 0.7)" }, `cell-${index}`))) })] }) }) })) }));
187080
+ };
187081
+
187082
+ const skillLevels = [
187083
+ { name: 'Beginner', value: 0, description: 'basic knowledge' },
187084
+ { name: 'Novice', value: 100, description: 'limited experience' },
187085
+ { name: 'Intermediate', value: 200, description: 'practical application' },
187086
+ { name: 'Advanced', value: 300, description: 'applied theory' },
187087
+ { name: 'Expert', value: 400, description: 'recognized authority' },
187088
+ ];
187089
+ const generateCurveData = () => {
187090
+ const data = [];
187091
+ for (let i = 0; i <= 100; i += 5) {
187092
+ let value = i < 80 ? Math.pow(i, 1.5) * 3 : Math.pow(i, 1.8) * 1.5;
187093
+ value = Math.min(value, 500);
187094
+ let level = 'Beginner';
187095
+ let description = 'basic knowledge';
187096
+ if (i >= 80) {
187097
+ level = 'Expert';
187098
+ description = 'recognized authority';
187099
+ }
187100
+ else if (i >= 60) {
187101
+ level = 'Advanced';
187102
+ description = 'applied theory';
187103
+ }
187104
+ else if (i >= 40) {
187105
+ level = 'Intermediate';
187106
+ description = 'practical application';
187107
+ }
187108
+ else if (i >= 20) {
187109
+ level = 'Novice';
187110
+ description = 'limited experience';
187111
+ }
187112
+ data.push({ percentile: i, value, level, description });
187113
+ }
187114
+ return data;
187115
+ };
187116
+ /** @internal Exported for testing. */
187117
+ const CustomTooltip = ({ active, payload, label }) => {
187118
+ if (active && payload && payload.length) {
187119
+ return (jsxs("div", { className: "rounded-md border border-gray-200 bg-white p-3 shadow-sm", children: [jsx("p", { className: "text-sm font-medium", children: `Level: ${payload[0].payload.level}` }), jsx("p", { className: "text-xs text-gray-600", children: `Skill Points: ${Math.round(payload[0].value)}` }), jsx("p", { className: "text-xs text-gray-600", children: `Percentile: ${label}%` })] }));
187120
+ }
187121
+ return null;
187122
+ };
187123
+ /**
187124
+ * Presentational skill-leaderboard curve chart. Takes the user's raw
187125
+ * skill points as a prop and derives their percentile + level locally.
187126
+ * Ported verbatim from the skillsai SPA (components/skill-leaderboard-chart.tsx).
187127
+ */
187128
+ const SkillLeaderboardChart = ({ userSkillPoints = 0, }) => {
187129
+ const [data, setData] = React.useState([]);
187130
+ const [userPosition, setUserPosition] = React.useState(0);
187131
+ React.useEffect(() => {
187132
+ const skillPercentage = userSkillPoints / 500;
187133
+ if (Number.isNaN(skillPercentage)) {
187134
+ setUserPosition(0);
187135
+ }
187136
+ else if (skillPercentage > 1) {
187137
+ setUserPosition(100);
187138
+ }
187139
+ else {
187140
+ setUserPosition(skillPercentage * 100);
187141
+ }
187142
+ }, [userSkillPoints]);
187143
+ React.useEffect(() => {
187144
+ setData(generateCurveData());
187145
+ }, []);
187146
+ const getUserLevel = () => {
187147
+ if (userPosition >= 80)
187148
+ return 'Expert';
187149
+ if (userPosition >= 60)
187150
+ return 'Advanced';
187151
+ if (userPosition >= 40)
187152
+ return 'Intermediate';
187153
+ if (userPosition >= 20)
187154
+ return 'Novice';
187155
+ return 'Beginner';
187156
+ };
187157
+ const getUserPoints = () => {
187158
+ const userDataPoint = data.find((point) => point.percentile === userPosition) ||
187159
+ data.find((point) => point.percentile > userPosition) || {
187160
+ value: 0};
187161
+ return Math.round(userDataPoint.value);
187162
+ };
187163
+ return (jsxs(Fragment$1, { children: [jsx("div", { className: "mb-2 flex justify-between px-2", children: skillLevels.map((level) => (jsx("div", { className: "text-center", children: jsx("p", { className: "text-xs font-medium text-gray-700", children: level.name }) }, level.name))) }), jsx("div", { className: "h-[250px] w-full overflow-x-auto sm:h-[300px]", children: jsx("div", { className: "h-full min-w-[600px]", children: jsx(ResponsiveContainer, { width: "100%", height: "100%", children: jsxs(LineChart, { data: data, margin: { top: 20, right: 20, left: 20, bottom: 60 }, children: [jsx(CartesianGrid, { strokeDasharray: "3 3", vertical: false }), jsx(XAxis, { dataKey: "percentile", type: "number", domain: [0, 100], tickFormatter: (value) => `${value}%`, label: {
187164
+ value: 'Global Learner Percentile',
187165
+ position: 'bottom',
187166
+ offset: 0,
187167
+ dy: 30,
187168
+ } }), jsx(YAxis, { domain: [0, 500], label: {
187169
+ value: 'Skill Points',
187170
+ angle: -90,
187171
+ position: 'left',
187172
+ dx: -30,
187173
+ } }), jsx(Tooltip, { content: jsx(CustomTooltip, {}) }), jsx(ReferenceArea, { x1: 80, x2: 100, fill: "rgba(248, 180, 58, 0.15)", fillOpacity: 0.3 }), skillLevels.map((level, index) => (jsx(ReferenceLine, { x: index * 20, stroke: "rgba(0,0,0,0.1)", strokeDasharray: "3 3", label: {
187174
+ value: level.description,
187175
+ position: 'bottom',
187176
+ fill: '#6B7280',
187177
+ fontSize: 10,
187178
+ dy: 15,
187179
+ } }, level.name))), jsx(ReferenceLine, { x: userPosition, stroke: "#F8B43A", strokeWidth: 2, strokeDasharray: "3 3" }), jsx(Line, { type: "monotone", dataKey: "value", stroke: "#F8B43A", strokeWidth: 3, dot: false, activeDot: { r: 8, fill: '#F8B43A' } })] }) }) }) }), jsxs("div", { className: "mt-4 flex items-center justify-between", children: [jsxs("div", { children: [jsxs("p", { className: "text-sm text-gray-600", children: ["Your Level: ", jsx("span", { className: "font-medium text-amber-500", children: getUserLevel() })] }), jsxs("p", { className: "text-sm text-gray-600", children: ["Skill Points: ", jsx("span", { className: "font-medium", children: getUserPoints() })] })] }), jsxs("div", { className: "text-xs text-gray-500", children: ["Percentile: ", userPosition, "%"] })] })] }));
187180
+ };
187181
+
187182
+ /**
187183
+ * Skeleton row for the AddSkillDialog search results list. Ported from
187184
+ * the skillsai SPA (components/skeleton-add-skills-loading.tsx).
187185
+ */
187186
+ const SkeletonAddSkillsLoading = () => (jsxs("div", { className: "flex items-center rounded-md border border-gray-200 p-3", children: [jsx("div", { className: "flex-1", children: jsx("div", { className: "flex items-center", children: jsx("div", { className: "h-4 w-24 animate-pulse rounded bg-gray-200" }) }) }), jsx("div", { className: "h-6 w-6 animate-pulse rounded-full bg-gray-200" })] }));
187187
+
187188
+ const DIALOG_TITLES = {
187189
+ earned: 'Add Earned Skill',
187190
+ desired: 'Add Desired Skill',
187191
+ 'self-reported': 'Add Self-Reported Skill',
187192
+ };
187193
+ /**
187194
+ * Presentational "add skill" dialog with a debounced search input and
187195
+ * single-select list of skill candidates.
187196
+ *
187197
+ * Ported from the skillsai SPA (components/add-skill-dialog.tsx),
187198
+ * refactored to take data and handlers via props. Callers are expected
187199
+ * to wire the `onSearch` → `useProfileSkills().handleFetchAllSkills`
187200
+ * link and the `onAddSkill` → `handleSkillsUpdate` link themselves.
187201
+ */
187202
+ const AddSkillDialog = ({ open, onOpenChange, type, fetchedSkills, isFetchingSkills, isFetchingSkillsError, updatingSkill, onSearch, onAddSkill, searchDebounceMs = 500, }) => {
187203
+ const [searchQuery, setSearchQuery] = React.useState('');
187204
+ const [selectedSkill, setSelectedSkill] = React.useState(null);
187205
+ const searchInputRef = React.useRef(null);
187206
+ const debounceTimer = React.useRef(null);
187207
+ // Debounced search: reset timer on every keystroke.
187208
+ React.useEffect(() => {
187209
+ if (debounceTimer.current)
187210
+ clearTimeout(debounceTimer.current);
187211
+ debounceTimer.current = setTimeout(() => {
187212
+ onSearch(searchQuery);
187213
+ }, searchDebounceMs);
187214
+ return () => {
187215
+ if (debounceTimer.current)
187216
+ clearTimeout(debounceTimer.current);
187217
+ };
187218
+ // eslint-disable-next-line react-hooks/exhaustive-deps
187219
+ }, [searchQuery]);
187220
+ // Focus the search input when the dialog opens.
187221
+ React.useEffect(() => {
187222
+ if (open && searchInputRef.current) {
187223
+ const t = setTimeout(() => { var _a; return (_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 100);
187224
+ return () => clearTimeout(t);
187225
+ }
187226
+ }, [open]);
187227
+ // Reset local state when the dialog closes.
187228
+ React.useEffect(() => {
187229
+ if (!open) {
187230
+ setSearchQuery('');
187231
+ setSelectedSkill(null);
187232
+ }
187233
+ }, [open]);
187234
+ const handleAddSkill = () => {
187235
+ if (updatingSkill || !selectedSkill)
187236
+ return;
187237
+ onAddSkill(selectedSkill);
187238
+ };
187239
+ return (jsx(Dialog$1, { open: open, onOpenChange: onOpenChange, children: jsxs(DialogContent$1, { className: "overflow-hidden p-0 sm:max-w-md", children: [jsx(DialogHeader, { className: "border-b px-6 py-4", children: jsx("div", { className: "flex items-center justify-between", children: jsx(DialogTitle$1, { className: "text-lg font-medium text-gray-600", children: DIALOG_TITLES[type] }) }) }), jsxs("div", { className: "p-6", children: [jsxs("div", { className: "relative mb-4", children: [jsx("div", { className: "pointer-events-none absolute inset-y-0 left-3 flex items-center", children: jsx(Search, { className: "h-4 w-4 text-gray-400" }) }), jsx("input", { ref: searchInputRef, type: "text", placeholder: "Search skills...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "w-full rounded-md border border-gray-200 bg-gray-50 py-2 pr-4 pl-10 text-sm text-gray-700 focus:ring-1 focus:ring-amber-500 focus:outline-none" })] }), ((!isFetchingSkills && isFetchingSkillsError) ||
187240
+ (!isFetchingSkills && fetchedSkills.length === 0)) && (jsx(DefaultEmptyBox, { message: "No skills found" })), jsxs("div", { className: "max-h-[300px] space-y-2 overflow-y-auto pr-1", children: [isFetchingSkills && (jsx(SkeletonMultiplier, { multiplier: 6, Skeleton: SkeletonAddSkillsLoading })), !isFetchingSkills &&
187241
+ !isFetchingSkillsError &&
187242
+ fetchedSkills.map((row, index) => {
187243
+ const skill = row === null || row === void 0 ? void 0 : row.data;
187244
+ const isSelected = !!(skill === null || skill === void 0 ? void 0 : skill.skill_id) && (selectedSkill === null || selectedSkill === void 0 ? void 0 : selectedSkill.skill_id) === skill.skill_id;
187245
+ return (jsxs("div", { onClick: () => skill && setSelectedSkill(skill), className: `flex cursor-pointer items-center rounded-md border p-3 transition-colors ${isSelected
187246
+ ? 'border-amber-500 bg-amber-50'
187247
+ : 'border-gray-200 hover:bg-gray-50'}`, children: [jsx("div", { className: "flex-1", children: jsx("div", { className: "flex items-center", children: jsx("span", { className: "text-sm font-medium text-gray-700", children: skill === null || skill === void 0 ? void 0 : skill.name }) }) }), jsx("div", { className: `flex h-6 w-6 items-center justify-center rounded-full ${isSelected ? 'bg-amber-500 text-white' : 'bg-gray-100 text-gray-400'}`, children: isSelected ? jsx(Check, { className: "h-4 w-4" }) : jsx(Plus, { className: "h-4 w-4" }) })] }, `skill-${index}`));
187248
+ })] })] }), jsx("div", { className: "flex justify-end border-t p-4", children: jsx("button", { onClick: handleAddSkill, disabled: !selectedSkill, className: `rounded-md px-4 py-2 font-medium text-white transition-colors ${selectedSkill
187249
+ ? 'bg-gradient-to-r from-[var(--button-primary-gradient-from)] to-[var(--button-primary-gradient-to)] hover:opacity-[var(--button-primary-hover-opacity)]'
187250
+ : 'cursor-not-allowed bg-gray-300'}`, children: updatingSkill ? 'Submitting...' : 'Add Skill' }) })] }) }));
187251
+ };
187252
+
187253
+ /**
187254
+ * Skeleton row for the CreatePathwayModal content search list. Ported
187255
+ * from the skillsai SPA (components/skeleton-create-pathway-search-list.tsx).
187256
+ */
187257
+ const SkeletonCreatePathwaySearchList = () => (jsx("div", { className: "animate-pulse overflow-hidden rounded-lg border border-gray-200", children: jsxs("div", { className: "flex items-center bg-white p-3", children: [jsx("div", { className: "relative mr-3 h-12 w-12 flex-shrink-0 overflow-hidden rounded-md bg-gray-200", children: jsx("div", { className: "h-full w-full animate-pulse bg-gray-300" }) }), jsxs("div", { className: "flex-1", children: [jsx("div", { className: "mb-2 h-4 animate-pulse rounded bg-gray-200" }), jsx("div", { className: "h-3 w-3/4 animate-pulse rounded bg-gray-100" })] }), jsx("div", { className: "h-6 w-6 animate-pulse rounded-full bg-gray-200" })] }) }));
187258
+
187259
+ /**
187260
+ * Skeleton row for the EducationBox / ExperienceBox lists. Ported from
187261
+ * the skillsai SPA (components/profile/skeleton-education-box.tsx).
187262
+ */
187263
+ const SkeletonEducationBox = () => (jsxs("div", { children: [jsxs("div", { className: "flex items-center", children: [jsx(Skeleton, { className: "h-5 w-24" }), jsx(Skeleton, { className: "ml-2 h-4 w-4" })] }), jsx("div", { className: "mt-1", children: jsx(Skeleton, { className: "h-4 w-48" }) }), jsx("div", { className: "mt-2", children: jsx(Skeleton, { className: "h-4 w-32" }) })] }));
187264
+
187265
+ const FALLBACK_CREDENTIAL_IMG = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><circle cx="24" cy="24" r="22" fill="%23e5e7eb"/></svg>';
187266
+ const CredentialMiniBox = ({ credential, onClick = () => { }, minified = false, iconSize = 48, defaultImage = FALLBACK_CREDENTIAL_IMG, }) => {
187267
+ var _a, _b, _c, _d;
187268
+ return (jsxs("div", { className: "flex cursor-pointer items-start rounded-lg border border-gray-200 bg-white p-4 transition-shadow hover:shadow-md", onClick: onClick, children: [jsx("div", { className: "mr-4 h-12 w-12 flex-shrink-0 overflow-hidden rounded-full", children: jsx(Image$4, { src: ((_a = credential.credentialDetails) === null || _a === void 0 ? void 0 : _a.iconImage) || defaultImage, alt: ((_b = credential.credentialDetails) === null || _b === void 0 ? void 0 : _b.name) || 'Credential', width: iconSize, height: iconSize, className: "h-full w-full object-cover" }) }), jsxs("div", { children: [jsx("h3", { className: "text-sm font-medium text-gray-800", children: (_c = credential.credentialDetails) === null || _c === void 0 ? void 0 : _c.name }), !minified && jsx("p", { className: "text-xs text-gray-600", children: (_d = credential.course) === null || _d === void 0 ? void 0 : _d.name }), jsxs("p", { className: "mt-1 text-xs text-gray-500", children: ["Earned on: ", credential.issuedOn ? dayjs(credential.issuedOn).format('MMM D, YYYY') : '-'] })] })] }));
187269
+ };
187270
+
187271
+ /**
187272
+ * Presentational credentials grid for the public profile page. Caller
187273
+ * supplies the credentials list + loading flags (typically from the
187274
+ * useProfileCredentials hook).
187275
+ *
187276
+ * Ported from the skillsai SPA (components/profile/credential-box.tsx).
187277
+ */
187278
+ const CredentialBox = ({ credentials, isLoading = false, isError = false, }) => {
187279
+ const isEmpty = !isLoading && !isError && credentials.length === 0;
187280
+ return (jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-6", children: [jsx("h2", { className: "mb-4 text-lg font-medium text-gray-800", children: "Credentials" }), (isError || isEmpty) && (jsx(DefaultEmptyBox, { message: "No credentials found.", className: "w-full" })), jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [isLoading && jsx(SkeletonMultiplier, { Skeleton: CredentialMiniBoxSkeleton, multiplier: 8 }), !isLoading &&
187281
+ !isError &&
187282
+ credentials.length > 0 &&
187283
+ credentials.map((credential) => (jsx(CredentialMiniBox, { credential: credential }, credential.entityId)))] })] }));
187284
+ };
187285
+
187286
+ const formatYearRange$1 = (start, end) => {
187287
+ if (!start && !end)
187288
+ return '';
187289
+ const startYear = start ? dayjs(start).format('YYYY') : '';
187290
+ const endYear = end ? dayjs(end).format('YYYY') : 'Present';
187291
+ if (start && end && startYear === endYear)
187292
+ return endYear;
187293
+ return `${startYear} - ${endYear}`;
187294
+ };
187295
+ /**
187296
+ * Presentational education list for the public profile page. The
187297
+ * skillsai SPA also rendered an inline EducationDialog and
187298
+ * AddInstitutionDialog — both are caller-managed now (the caller can
187299
+ * open them in response to onEditEducation).
187300
+ *
187301
+ * Ported from the skillsai SPA (components/profile/education-box.tsx).
187302
+ */
187303
+ const EducationBox = ({ education = [], isLoading = false, isError = false, onEditEducation, }) => {
187304
+ const isEmpty = !isLoading && !isError && education.length === 0;
187305
+ return (jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-6", children: [jsx("h2", { className: "mb-4 text-lg font-medium text-gray-800", children: "Education" }), jsxs("div", { className: "space-y-8", children: [isLoading && jsx(SkeletonMultiplier, { multiplier: 4, Skeleton: SkeletonEducationBox }), (isError || isEmpty) && jsx(DefaultEmptyBox, { message: "No education found." }), !isLoading &&
187306
+ !isError &&
187307
+ education.map((entry, index) => {
187308
+ var _a;
187309
+ return (jsxs("div", { children: [jsxs("div", { className: "flex items-center", children: [jsx("h3", { className: "text-base font-medium text-amber-500", children: entry.degree }), onEditEducation && (jsx("button", { onClick: () => onEditEducation(entry), "aria-label": "Edit education entry", className: "ml-2 text-amber-500 transition-colors hover:text-amber-600", children: jsx(Pen, { className: "h-4 w-4" }) }))] }), jsxs("div", { className: "mt-1 text-gray-700", children: [(_a = entry === null || entry === void 0 ? void 0 : entry.institution) === null || _a === void 0 ? void 0 : _a.name, " | ", formatYearRange$1(entry.start_date, entry.end_date), entry.grade ? ` | Grade: ${entry.grade}` : ''] }), entry.description && jsx("p", { className: "mt-2 text-gray-600", children: entry.description })] }, index));
187310
+ })] })] }));
187311
+ };
187312
+
187313
+ const formatYearRange = (start, end) => {
187314
+ if (!start && !end)
187315
+ return '';
187316
+ const startYear = start ? dayjs(start).format('YYYY') : '';
187317
+ const endYear = end ? dayjs(end).format('YYYY') : 'Present';
187318
+ if (start && end && startYear === endYear)
187319
+ return endYear;
187320
+ return `${startYear} - ${endYear}`;
187321
+ };
187322
+ /**
187323
+ * Presentational work-experience list for the public profile page.
187324
+ *
187325
+ * Ported from the skillsai SPA (components/profile/experience-box.tsx).
187326
+ */
187327
+ const ExperienceBox = ({ experience = [], isLoading = false, isError = false, onEditExperience, }) => {
187328
+ const isEmpty = !isLoading && !isError && experience.length === 0;
187329
+ return (jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-6", children: [jsx("h2", { className: "mb-4 text-lg font-medium text-gray-800", children: "Work Experience" }), jsxs("div", { className: "space-y-8", children: [isLoading && jsx(SkeletonMultiplier, { multiplier: 4, Skeleton: SkeletonEducationBox }), (isError || isEmpty) && jsx(DefaultEmptyBox, { message: "No experience found." }), !isLoading &&
187330
+ !isError &&
187331
+ experience.map((entry, index) => {
187332
+ var _a;
187333
+ return (jsxs("div", { children: [jsxs("div", { className: "flex items-center", children: [jsx("h3", { className: "text-base font-medium text-amber-500", children: entry.title }), onEditExperience && (jsx("button", { onClick: () => onEditExperience(entry), "aria-label": "Edit experience entry", className: "ml-2 text-amber-500 transition-colors hover:text-amber-600", children: jsx(Pen, { className: "h-4 w-4" }) }))] }), jsxs("div", { className: "mt-1 text-gray-700", children: [(_a = entry.company) === null || _a === void 0 ? void 0 : _a.name, " | ", formatYearRange(entry.start_date, entry.end_date)] }), entry.description && jsx("p", { className: "mt-2 text-gray-600", children: entry.description })] }, index));
187334
+ })] })] }));
187335
+ };
187336
+
187337
+ const isEmpty$1 = (xs) => !xs || xs.length === 0;
187338
+ /**
187339
+ * Presentational skills list for the public profile page. Splits the
187340
+ * three skill buckets (earned, self-reported, desired) into their own
187341
+ * sections, each with its own loading skeleton and empty state.
187342
+ *
187343
+ * Ported from the skillsai SPA (components/profile/skills-box.tsx).
187344
+ */
187345
+ const SkillsBox = ({ earnedSkills, earnedSkillsLoading = false, earnedSkillsError = false, selfReportedSkills, selfReportedSkillsLoading = false, selfReportedSkillsError = false, desiredSkills, desiredSkillsLoading = false, desiredSkillsError = false, }) => {
187346
+ var _a, _b, _c;
187347
+ return (jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-6", children: [jsx("h2", { className: "mb-4 text-lg font-medium text-gray-800", children: "Skills" }), jsxs("div", { className: "mb-6", children: [jsx("h3", { className: "mb-4 text-base font-medium text-gray-700", children: "Earned" }), !earnedSkillsLoading && (earnedSkillsError || isEmpty$1(earnedSkills === null || earnedSkills === void 0 ? void 0 : earnedSkills.resources)) && (jsx(DefaultEmptyBox, { className: "w-full", message: "You don't have any earned skills yet." })), jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [earnedSkillsLoading && jsx(SkeletonMultiplier, { Skeleton: SkeletonSkillBox, multiplier: 6 }), !earnedSkillsLoading &&
187348
+ !earnedSkillsError &&
187349
+ ((_a = earnedSkills === null || earnedSkills === void 0 ? void 0 : earnedSkills.resources) === null || _a === void 0 ? void 0 : _a.map((skill, index) => (jsx(SkillBox, { onSkillClick: () => { }, skill: {
187350
+ name: skill.name || '',
187351
+ level: skill.points || 0,
187352
+ starred: false,
187353
+ } }, index))))] })] }), jsxs("div", { className: "mb-6", children: [jsx("h3", { className: "mb-4 text-base font-medium text-gray-700", children: "Self-Reported" }), !selfReportedSkillsLoading &&
187354
+ (selfReportedSkillsError || isEmpty$1(selfReportedSkills === null || selfReportedSkills === void 0 ? void 0 : selfReportedSkills.skills)) && (jsx(DefaultEmptyBox, { className: "w-full", message: "You don't have any self-reported skills yet." })), jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [selfReportedSkillsLoading && (jsx(SkeletonMultiplier, { Skeleton: SkeletonSkillBox, multiplier: 6 })), !selfReportedSkillsLoading &&
187355
+ !selfReportedSkillsError &&
187356
+ ((_b = selfReportedSkills === null || selfReportedSkills === void 0 ? void 0 : selfReportedSkills.skills) === null || _b === void 0 ? void 0 : _b.map((skill, index) => {
187357
+ var _a, _b;
187358
+ return (jsx(SkillBox, { skill: {
187359
+ name: skill.name || '',
187360
+ level: ((_b = (_a = selfReportedSkills === null || selfReportedSkills === void 0 ? void 0 : selfReportedSkills.data) === null || _a === void 0 ? void 0 : _a.level) === null || _b === void 0 ? void 0 : _b[index]) || 0,
187361
+ starred: true,
187362
+ } }, index));
187363
+ }))] })] }), jsxs("div", { children: [jsx("h3", { className: "mb-4 text-base font-medium text-gray-700", children: "Desired" }), !desiredSkillsLoading && (desiredSkillsError || isEmpty$1(desiredSkills === null || desiredSkills === void 0 ? void 0 : desiredSkills.skills)) && (jsx(DefaultEmptyBox, { className: "w-full", message: "You don't have any desired skills yet." })), jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: [desiredSkillsLoading && (jsx(SkeletonMultiplier, { Skeleton: SkeletonSkillBox, multiplier: 6 })), !desiredSkillsLoading &&
187364
+ !desiredSkillsError &&
187365
+ ((_c = desiredSkills === null || desiredSkills === void 0 ? void 0 : desiredSkills.skills) === null || _c === void 0 ? void 0 : _c.map((skill, index) => {
187366
+ var _a, _b;
187367
+ return (jsx(SkillBox, { showRating: false, skill: {
187368
+ name: skill.name || '',
187369
+ level: ((_b = (_a = desiredSkills === null || desiredSkills === void 0 ? void 0 : desiredSkills.data) === null || _a === void 0 ? void 0 : _a.level) === null || _b === void 0 ? void 0 : _b[index]) || 0,
187370
+ starred: true,
187371
+ } }, index));
187372
+ }))] })] })] }));
187373
+ };
187374
+
187375
+ try {
187376
+ __webpack_exports__GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${__webpack_exports__version}/build/pdf.worker.min.mjs`;
187377
+ }
187378
+ catch (_a) {
187379
+ // pdfjs may not be configurable in non-browser environments; ignore silently.
187380
+ }
187381
+ const ResumeBoxSkeleton = () => (jsx("div", { className: "h-[200px] w-full animate-pulse rounded-lg bg-gray-200" }));
187382
+ /**
187383
+ * Presentational PDF resume viewer with paging, zoom, and rotation
187384
+ * controls. Uses react-pdf, so it must run client-side.
187385
+ *
187386
+ * Ported from the skillsai SPA (components/profile/resume-box.tsx).
187387
+ */
187388
+ const ResumeBox = ({ resumeUrl, isLoading = false, isError = false, }) => {
187389
+ const [resumeLoadError, setResumeLoadError] = React.useState(false);
187390
+ const [numPages, setNumPages] = React.useState(null);
187391
+ const [pageNumber, setPageNumber] = React.useState(1);
187392
+ const [scale, setScale] = React.useState(1.0);
187393
+ const [rotation, setRotation] = React.useState(0);
187394
+ const onDocumentLoadSuccess = ({ numPages }) => {
187395
+ setNumPages(numPages);
187396
+ };
187397
+ const onDocumentLoadError = () => setResumeLoadError(true);
187398
+ const goToPrevPage = () => setPageNumber((p) => p - 1);
187399
+ const goToNextPage = () => setPageNumber((p) => p + 1);
187400
+ const zoomIn = () => setScale((s) => s + 0.2);
187401
+ const zoomOut = () => setScale((s) => (s > 0.4 ? s - 0.2 : s));
187402
+ const rotateLeft = () => setRotation((r) => r - 90);
187403
+ const rotateRight = () => setRotation((r) => r + 90);
187404
+ return (jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-6", children: [jsx("h2", { className: "mb-4 text-lg font-medium text-gray-800", children: "Resume" }), isLoading && jsx(ResumeBoxSkeleton, {}), ((!isLoading && isError) || (!isLoading && !isError && !resumeUrl)) && (jsx(DefaultEmptyBox, { message: "No resume found.", className: "w-full" })), !isLoading && !isError && resumeUrl && !resumeLoadError && (jsxs(Fragment$1, { children: [jsxs("div", { className: "mb-5 flex flex-col items-center justify-center rounded-lg bg-gray-100 p-2.5 md:flex-row", children: [jsxs("div", { className: "mb-2.5 flex items-center md:mr-5 md:mb-0", children: [jsx("button", { onClick: goToPrevPage, disabled: pageNumber <= 1, className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400 disabled:cursor-not-allowed disabled:bg-gray-300", children: "Previous Page" }), jsxs("span", { className: "mx-2.5 font-bold", children: ["Page ", pageNumber, " of ", numPages] }), jsx("button", { onClick: goToNextPage, disabled: pageNumber >= (numPages !== null && numPages !== void 0 ? numPages : 0), className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400 disabled:cursor-not-allowed disabled:bg-gray-300", children: "Next Page" })] }), jsxs("div", { className: "mb-2.5 flex items-center md:mr-5 md:mb-0", children: [jsx("button", { onClick: zoomOut, disabled: scale <= 0.4, "aria-label": "Zoom out", className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400 disabled:cursor-not-allowed disabled:bg-gray-300", children: "-" }), jsxs("span", { className: "mx-2.5 font-bold", children: ["Zoom: ", Math.round(scale * 100), "%"] }), jsx("button", { onClick: zoomIn, "aria-label": "Zoom in", className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400 disabled:cursor-not-allowed disabled:bg-gray-300", children: "+" })] }), jsxs("div", { className: "flex items-center", children: [jsx("button", { onClick: rotateLeft, className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400", children: "Rotate Left" }), jsx("button", { onClick: rotateRight, className: "mx-1 rounded border-none bg-amber-500 px-3 py-2 text-white transition-colors hover:bg-amber-400", children: "Rotate Right" })] })] }), jsx("div", { className: "flex min-h-[80vh] items-center justify-center", children: jsx(Document$1, { file: resumeUrl, onLoadSuccess: onDocumentLoadSuccess, onLoadError: onDocumentLoadError, loading: jsx(ResumeBoxSkeleton, {}), children: jsx(Page, { pageNumber: pageNumber, scale: scale, rotate: rotation }) }) })] })), resumeLoadError && jsx(DefaultEmptyBox, { message: "Error loading resume.", className: "w-full" })] }));
187405
+ };
187406
+
187407
+ /**
187408
+ * Wrapper around the course-metadata RTK endpoints that exposes
187409
+ * promise-returning fetch helpers for use inside effects/callbacks.
187410
+ *
187411
+ * Ported from the skillsai SPA (hooks/courses/use-course-metadata.ts).
187412
+ */
187413
+ const useCourseMetadata = () => {
187414
+ const [getCourseMetaData, { isLoading, isError }] = useLazyGetCourseMetaDataQuery();
187415
+ const [getCourseCompletionOutlines, { isLoading: isLoadingCompletionOutlines, isError: isErrorCompletionOutlines },] = useLazyGetCourseCompletionOutlinesQuery();
187416
+ const [getCourseEligibility] = useLazyGetCourseEligibilityQuery();
187417
+ const handleFetchCourseMetaData = async (courseKey) => {
187418
+ try {
187419
+ const { data: courseMetaData } = await getCourseMetaData({ courseKey }, true);
187420
+ return courseMetaData;
187421
+ }
187422
+ catch (_a) {
187423
+ return undefined;
187424
+ }
187425
+ };
187426
+ const handleFetchCourseEligibility = async (courseKey) => {
187427
+ try {
187428
+ const { data: courseEligibility } = await getCourseEligibility({ courseKey });
187429
+ return courseEligibility || null;
187430
+ }
187431
+ catch (_a) {
187432
+ return null;
187433
+ }
187434
+ };
187435
+ const handleFetchCourseCompletionOutlines = async (courseKey) => {
187436
+ try {
187437
+ const { data: courseCompletionOutlines } = await getCourseCompletionOutlines({ courseKey });
187438
+ return courseCompletionOutlines;
187439
+ }
187440
+ catch (_a) {
187441
+ return undefined;
187442
+ }
187443
+ };
187444
+ return {
187445
+ handleFetchCourseMetaData,
187446
+ handleFetchCourseCompletionOutlines,
187447
+ handleFetchCourseEligibility,
187448
+ isLoading,
187449
+ isError,
187450
+ isLoadingCompletionOutlines,
187451
+ isErrorCompletionOutlines,
187452
+ };
187453
+ };
187454
+
187455
+ /**
187456
+ * Fetches the current user's enrolled or assigned courses (paged), then
187457
+ * hydrates each row with course metadata from the LMS. Search can either
187458
+ * hit the API or filter client-side on the already-fetched page.
187459
+ *
187460
+ * Ported from the skillsai SPA (hooks/courses/use-user-courses.ts).
187461
+ */
187462
+ const useUserCourses = ({ limit = 8, search = '', page = 1, courseType = 'enrolled', useAPISearch = false, }) => {
187463
+ const { metadata } = useTenantMetadata({ org: getTenant() });
187464
+ const [getUserEnrolledCourses, { isLoading: isLoadingEnrolledCourses, isError: errorEnrolledCourses },] = useLazyGetUserEnrolledCoursesQuery();
187465
+ const [getUserAssignedCourses, { isLoading: isLoadingAssignedCourses, isError: errorAssignedCourses },] = useLazyGetUserAssignedCoursesQuery();
187466
+ const { handleFetchCourseMetaData } = useCourseMetadata();
187467
+ const [userCoursesWithMetaData, setUserCoursesWithMetaData] = useState([]);
187468
+ const [filteredCoursesWithMetaData, setFilteredCoursesWithMetaData] = useState([]);
187469
+ const [isLoadingCoursesMetaData, setIsLoadingCoursesMetaData] = useState(false);
187470
+ const [pagination, setPagination] = useState(null);
187471
+ const handleFetchAllCoursesMetaData = async (enrolledCourses) => {
187472
+ try {
187473
+ const coursesMetaData = await Promise.all(enrolledCourses.map((course) => handleFetchCourseMetaData(course.course_id)));
187474
+ const coursesWithMetaData = enrolledCourses.map((course, index) => ({
187475
+ ...course,
187476
+ name: course.course_name,
187477
+ edx_data: coursesMetaData[index],
187478
+ }));
187479
+ setUserCoursesWithMetaData(coursesWithMetaData);
187480
+ setFilteredCoursesWithMetaData(coursesWithMetaData);
187481
+ }
187482
+ catch (_a) {
187483
+ setUserCoursesWithMetaData([]);
187484
+ setFilteredCoursesWithMetaData([]);
187485
+ }
187486
+ finally {
187487
+ setIsLoadingCoursesMetaData(false);
187488
+ }
187489
+ };
187490
+ const handleFetchCourses = async () => {
187491
+ try {
187492
+ setUserCoursesWithMetaData([]);
187493
+ setFilteredCoursesWithMetaData([]);
187494
+ setIsLoadingCoursesMetaData(true);
187495
+ if (courseType === 'assigned') {
187496
+ const userId = getUserId();
187497
+ if (userId == null) {
187498
+ setIsLoadingCoursesMetaData(false);
187499
+ return;
187500
+ }
187501
+ const { data: assignedCourses } = await getUserAssignedCourses({
187502
+ user_id: userId,
187503
+ query: {
187504
+ page_size: limit,
187505
+ ...(search && { search }),
187506
+ ...(page && { page }),
187507
+ },
187508
+ }, true);
187509
+ if (Array.isArray(assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.results)) {
187510
+ setPagination({
187511
+ count: (assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.count) || 0,
187512
+ current_page: page,
187513
+ total_pages: Math.ceil(((assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.count) || 0) / limit),
187514
+ });
187515
+ await handleFetchAllCoursesMetaData(assignedCourses.results);
187516
+ }
187517
+ else {
187518
+ setIsLoadingCoursesMetaData(false);
187519
+ }
187520
+ }
187521
+ else {
187522
+ const username = getUserName();
187523
+ if (!username) {
187524
+ setIsLoadingCoursesMetaData(false);
187525
+ return;
187526
+ }
187527
+ const { data: enrolledCourses } = await getUserEnrolledCourses({
187528
+ username,
187529
+ query: {
187530
+ page_size: limit,
187531
+ ...(search && { search }),
187532
+ ...(page && { page }),
187533
+ platform_key: getTenant(),
187534
+ ...((metadata === null || metadata === void 0 ? void 0 : metadata.skills_include_community_courses) && { include_default_platform: 1 }),
187535
+ },
187536
+ }, true);
187537
+ if (Array.isArray(enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.results)) {
187538
+ setPagination({
187539
+ count: (enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.count) || 0,
187540
+ current_page: page,
187541
+ total_pages: Math.ceil(((enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.count) || 0) / limit),
187542
+ });
187543
+ await handleFetchAllCoursesMetaData(enrolledCourses.results);
187544
+ }
187545
+ else {
187546
+ setIsLoadingCoursesMetaData(false);
187547
+ }
187548
+ }
187549
+ }
187550
+ catch (_a) {
187551
+ setUserCoursesWithMetaData([]);
187552
+ setFilteredCoursesWithMetaData([]);
187553
+ setIsLoadingCoursesMetaData(false);
187554
+ }
187555
+ };
187556
+ const handleInPageSearch = () => {
187557
+ if (search.length > 2) {
187558
+ const filtered = userCoursesWithMetaData.filter((course) => course.name.toLowerCase().includes(search.toLowerCase()));
187559
+ setFilteredCoursesWithMetaData(filtered);
187560
+ }
187561
+ else {
187562
+ setFilteredCoursesWithMetaData(userCoursesWithMetaData);
187563
+ }
187564
+ };
187565
+ useEffect(() => {
187566
+ if (!useAPISearch) {
187567
+ handleInPageSearch();
187568
+ }
187569
+ else {
187570
+ handleFetchCourses();
187571
+ }
187572
+ }, [search, useAPISearch]);
187573
+ useEffect(() => {
187574
+ handleFetchCourses();
187575
+ }, [
187576
+ courseType,
187577
+ page,
187578
+ metadata === null || metadata === void 0 ? void 0 : metadata.skills_include_community_courses,
187579
+ ]);
187580
+ return {
187581
+ userCourses: filteredCoursesWithMetaData,
187582
+ isLoadingUserCourses: isLoadingEnrolledCourses || isLoadingAssignedCourses || isLoadingCoursesMetaData,
187583
+ errorUserCourses: errorEnrolledCourses || errorAssignedCourses,
187584
+ pagination,
187585
+ };
187586
+ };
187587
+
187588
+ /**
187589
+ * Fetches the logged-in user's credential assertions and maintains a
187590
+ * client-side filtered list based on a search string.
187591
+ *
187592
+ * Ported from the skillsai SPA (hooks/profile/use-profile-credentials.ts).
187593
+ */
187594
+ const useProfileCredentials = ({ search = '', maxCredentials, }) => {
187595
+ const [getUserCredentials, { isLoading, isError }] = useLazyGetUserCredentialsQuery();
187596
+ const [filteredCredentials, setFilteredCredentials] = useState([]);
187597
+ const [fetchedCredentials, setFetchedCredentials] = useState([]);
187598
+ const [credentialsLoading, setCredentialsLoading] = useState(false);
187599
+ const handleFetchCredentials = async () => {
187600
+ var _a, _b, _c;
187601
+ try {
187602
+ setCredentialsLoading(true);
187603
+ const response = await getUserCredentials({
187604
+ platformKey: getTenant(),
187605
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
187606
+ ...(maxCredentials && { pageSize: maxCredentials }),
187607
+ }, true);
187608
+ const data = ((_c = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : []);
187609
+ setFetchedCredentials(data);
187610
+ setFilteredCredentials(data);
187611
+ }
187612
+ catch (_d) {
187613
+ setFetchedCredentials([]);
187614
+ setFilteredCredentials([]);
187615
+ }
187616
+ finally {
187617
+ setCredentialsLoading(false);
187618
+ }
187619
+ };
187620
+ useEffect(() => {
187621
+ handleFetchCredentials();
187622
+ }, []);
187623
+ useEffect(() => {
187624
+ if (search.length > 2) {
187625
+ setFilteredCredentials(fetchedCredentials.filter((credential) => {
187626
+ var _a;
187627
+ return String(((_a = credential === null || credential === void 0 ? void 0 : credential.credentialDetails) === null || _a === void 0 ? void 0 : _a.name) || '')
187628
+ .toLowerCase()
187629
+ .includes(search.toLowerCase());
187630
+ }));
187631
+ }
187632
+ else {
187633
+ setFilteredCredentials(fetchedCredentials);
187634
+ }
187635
+ }, [search, fetchedCredentials]);
187636
+ return {
187637
+ filteredCredentials,
187638
+ fetchedCredentials,
187639
+ isLoading: isLoading || credentialsLoading,
187640
+ isError,
187641
+ };
187642
+ };
187643
+
187644
+ var duration$1 = {exports: {}};
187645
+
187646
+ (function (module, exports) {
187647
+ !function(t,s){module.exports=s();}(commonjsGlobal,(function(){var t,s,n=1e3,i=6e4,e=36e5,r=864e5,o=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,u=31536e6,d=2628e6,a=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/,h={years:u,months:d,days:r,hours:e,minutes:i,seconds:n,milliseconds:1,weeks:6048e5},c=function(t){return t instanceof g},f=function(t,s,n){return new g(t,n,s.$l)},m=function(t){return s.p(t)+"s"},l=function(t){return t<0},$=function(t){return l(t)?Math.ceil(t):Math.floor(t)},y=function(t){return Math.abs(t)},v=function(t,s){return t?l(t)?{negative:true,format:""+y(t)+s}:{negative:false,format:""+t+s}:{negative:false,format:""}},g=function(){function l(t,s,n){var i=this;if(this.$d={},this.$l=n,void 0===t&&(this.$ms=0,this.parseFromMilliseconds()),s)return f(t*h[m(s)],this);if("number"==typeof t)return this.$ms=t,this.parseFromMilliseconds(),this;if("object"==typeof t)return Object.keys(t).forEach((function(s){i.$d[m(s)]=t[s];})),this.calMilliseconds(),this;if("string"==typeof t){var e=t.match(a);if(e){var r=e.slice(2).map((function(t){return null!=t?Number(t):0}));return this.$d.years=r[0],this.$d.months=r[1],this.$d.weeks=r[2],this.$d.days=r[3],this.$d.hours=r[4],this.$d.minutes=r[5],this.$d.seconds=r[6],this.calMilliseconds(),this}}return this}var y=l.prototype;return y.calMilliseconds=function(){var t=this;this.$ms=Object.keys(this.$d).reduce((function(s,n){return s+(t.$d[n]||0)*h[n]}),0);},y.parseFromMilliseconds=function(){var t=this.$ms;this.$d.years=$(t/u),t%=u,this.$d.months=$(t/d),t%=d,this.$d.days=$(t/r),t%=r,this.$d.hours=$(t/e),t%=e,this.$d.minutes=$(t/i),t%=i,this.$d.seconds=$(t/n),t%=n,this.$d.milliseconds=t;},y.toISOString=function(){var t=v(this.$d.years,"Y"),s=v(this.$d.months,"M"),n=+this.$d.days||0;this.$d.weeks&&(n+=7*this.$d.weeks);var i=v(n,"D"),e=v(this.$d.hours,"H"),r=v(this.$d.minutes,"M"),o=this.$d.seconds||0;this.$d.milliseconds&&(o+=this.$d.milliseconds/1e3,o=Math.round(1e3*o)/1e3);var u=v(o,"S"),d=t.negative||s.negative||i.negative||e.negative||r.negative||u.negative,a=e.format||r.format||u.format?"T":"",h=(d?"-":"")+"P"+t.format+s.format+i.format+a+e.format+r.format+u.format;return "P"===h||"-P"===h?"P0D":h},y.toJSON=function(){return this.toISOString()},y.format=function(t){var n=t||"YYYY-MM-DDTHH:mm:ss",i={Y:this.$d.years,YY:s.s(this.$d.years,2,"0"),YYYY:s.s(this.$d.years,4,"0"),M:this.$d.months,MM:s.s(this.$d.months,2,"0"),D:this.$d.days,DD:s.s(this.$d.days,2,"0"),H:this.$d.hours,HH:s.s(this.$d.hours,2,"0"),m:this.$d.minutes,mm:s.s(this.$d.minutes,2,"0"),s:this.$d.seconds,ss:s.s(this.$d.seconds,2,"0"),SSS:s.s(this.$d.milliseconds,3,"0")};return n.replace(o,(function(t,s){return s||String(i[t])}))},y.as=function(t){return this.$ms/h[m(t)]},y.get=function(t){var s=this.$ms,n=m(t);return "milliseconds"===n?s%=1e3:s="weeks"===n?$(s/h[n]):this.$d[n],s||0},y.add=function(t,s,n){var i;return i=s?t*h[m(s)]:c(t)?t.$ms:f(t,this).$ms,f(this.$ms+i*(n?-1:1),this)},y.subtract=function(t,s){return this.add(t,s,true)},y.locale=function(t){var s=this.clone();return s.$l=t,s},y.clone=function(){return f(this.$ms,this)},y.humanize=function(s){return t().add(this.$ms,"ms").locale(this.$l).fromNow(!s)},y.valueOf=function(){return this.asMilliseconds()},y.milliseconds=function(){return this.get("milliseconds")},y.asMilliseconds=function(){return this.as("milliseconds")},y.seconds=function(){return this.get("seconds")},y.asSeconds=function(){return this.as("seconds")},y.minutes=function(){return this.get("minutes")},y.asMinutes=function(){return this.as("minutes")},y.hours=function(){return this.get("hours")},y.asHours=function(){return this.as("hours")},y.days=function(){return this.get("days")},y.asDays=function(){return this.as("days")},y.weeks=function(){return this.get("weeks")},y.asWeeks=function(){return this.as("weeks")},y.months=function(){return this.get("months")},y.asMonths=function(){return this.as("months")},y.years=function(){return this.get("years")},y.asYears=function(){return this.as("years")},l}(),p=function(t,s,n){return t.add(s.years()*n,"y").add(s.months()*n,"M").add(s.days()*n,"d").add(s.hours()*n,"h").add(s.minutes()*n,"m").add(s.seconds()*n,"s").add(s.milliseconds()*n,"ms")};return function(n,i,e){t=e,s=e().$utils(),e.duration=function(t,s){var n=e.locale();return f(t,{$l:n},s)},e.isDuration=c;var r=i.prototype.add,o=i.prototype.subtract;i.prototype.add=function(t,s){return c(t)?p(this,t,1):r.bind(this)(t,s)},i.prototype.subtract=function(t,s){return c(t)?p(this,t,-1):o.bind(this)(t,s)};}}));
187648
+ } (duration$1));
187649
+
187650
+ var durationExports = duration$1.exports;
187651
+ var duration = /*@__PURE__*/getDefaultExportFromCjs(durationExports);
187652
+
187653
+ dayjs.extend(duration);
187654
+ /**
187655
+ * Fetches the last 7 days of learning activity for the logged-in user
187656
+ * and returns it as a list of { date, minutes } rows ready for a chart.
187657
+ *
187658
+ * Ported from the skillsai SPA (hooks/profile/use-profile-timespent.ts).
187659
+ */
187660
+ const useProfileTimeSpent = () => {
187661
+ const [timeSpent, setTimeSpent] = useState([]);
187662
+ const [timeSpentLoading, setTimeSpentLoading] = useState(false);
187663
+ const [getOverTimeActivity] = useLazyGetOverTimeActivityQuery();
187664
+ const handleTimeSpentStats = async () => {
187665
+ var _a, _b;
187666
+ setTimeSpentLoading(true);
187667
+ try {
187668
+ const response = await getOverTimeActivity([
187669
+ {
187670
+ org: getTenant(),
187671
+ userId: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
187672
+ startDate: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
187673
+ endDate: dayjs().format('YYYY-MM-DD'),
187674
+ format: 'json',
187675
+ },
187676
+ ], true);
187677
+ const data = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.data;
187678
+ if (!data || Object.keys(data).length === 0) {
187679
+ throw new Error('no data');
187680
+ }
187681
+ setTimeSpent(Object.entries(data).map(([date, seconds]) => ({
187682
+ date: dayjs(date).format('ddd DD/MM/YY'),
187683
+ minutes: dayjs.duration(seconds, 'seconds').asMinutes(),
187684
+ })));
187685
+ }
187686
+ catch (_c) {
187687
+ setTimeSpent([]);
187688
+ }
187689
+ finally {
187690
+ setTimeSpentLoading(false);
187691
+ }
187692
+ };
187693
+ useEffect(() => {
187694
+ handleTimeSpentStats();
187695
+ }, []);
187696
+ return { timeSpent, timeSpentLoading };
187697
+ };
187698
+
187699
+ /**
187700
+ * Thin wrapper around the catalog-search RTK endpoint that exposes a
187701
+ * promise-returning helper for ad-hoc searches inside effects/callbacks.
187702
+ *
187703
+ * Ported from the skillsai SPA (hooks/search/use-catalog-search.ts).
187704
+ */
187705
+ const useCatalogSearch = () => {
187706
+ const [getCatalogSearch, { isLoading, isError }] = useLazyGetCatalogSearchQuery();
187707
+ const handleSearch = async (params) => {
187708
+ try {
187709
+ const response = await getCatalogSearch([{ ...params }], true);
187710
+ return response;
187711
+ }
187712
+ catch (error) {
187713
+ console.error(JSON.stringify(error));
187714
+ return null;
187715
+ }
187716
+ };
187717
+ return { handleSearch, isLoading, isError };
187718
+ };
187719
+
187720
+ /**
187721
+ * Aggregates the logged-in user's earned, self-reported, and desired
187722
+ * skills, plus mutations to create/update/delete self-reported and
187723
+ * desired skills. Also exposes a `handleFetchAllSkills` helper that
187724
+ * queries the catalog search endpoint for a skills list.
187725
+ *
187726
+ * Ported from the skillsai SPA (hooks/profile/use-profile-skills.ts).
187727
+ */
187728
+ const useProfileSkills = (showToast = true) => {
187729
+ const { data: earnedSkills, isLoading: earnedSkillsLoading, isError: earnedSkillsError, isSuccess: earnedSkillsSuccess, } = useGetUserEarnedSkillsQuery([
187730
+ { org: getTenant(), userId: getUserName() },
187731
+ ]);
187732
+ const { data: selfReportedSkills, isLoading: selfReportedSkillsLoading, isError: selfReportedSkillsError, isSuccess: selfReportedSkillsSuccess, } = useGetUserReportedSkillsQuery([
187733
+ { userId: getUserId(), username: getUserName() },
187734
+ ]);
187735
+ const { data: desiredSkills, isLoading: desiredSkillsLoading, isError: desiredSkillsError, isSuccess: desiredSkillsSuccess, } = useGetUserDesiredSkillsQuery([
187736
+ { userId: getUserId(), username: getUserName() },
187737
+ ]);
187738
+ const { handleSearch, isLoading: isFetchingSkills, isError: isFetchingSkillsError, } = useCatalogSearch();
187739
+ const [updatingSkill, setUpdatingSkill] = useState(false);
187740
+ const [deletingSkill, setDeletingSkill] = useState(false);
187741
+ const [fetchedSkills, setFetchedSkills] = useState([]);
187742
+ const handleFetchAllSkills = async (searchQuery) => {
187743
+ var _a, _b;
187744
+ try {
187745
+ const result = await handleSearch({
187746
+ content: ['skills'],
187747
+ tenant: getTenant(),
187748
+ ...(searchQuery && { query: searchQuery }),
187749
+ });
187750
+ if (!result) {
187751
+ throw new Error('Failed to fetch skills');
187752
+ }
187753
+ const items = (_b = (_a = result === null || result === void 0 ? void 0 : result.data) === null || _a === void 0 ? void 0 : _a.results) !== null && _b !== void 0 ? _b : [];
187754
+ setFetchedSkills(items);
187755
+ }
187756
+ catch (_c) {
187757
+ toast.error('Failed to fetch skills');
187758
+ setFetchedSkills([]);
187759
+ }
187760
+ };
187761
+ const [createOrUpdateUserReportedSkill, { isError: isCreatingOrUpdateUserReportedSkillError }] = useCreateOrUpdateUserReportedSkillMutation();
187762
+ const [createOrUpdateUserDesiredSkill, { isError: isCreatingOrUpdateUserDesiredSkillError }] = useCreateOrUpdateUserDesiredSkillMutation();
187763
+ const handleSkillsDeletion = async (skill, allSkills, callback) => {
187764
+ var _a, _b, _c, _d, _e, _f;
187765
+ setDeletingSkill(true);
187766
+ if (skill.type === 'self-reported') {
187767
+ const reportedSkillIndex = (_a = allSkills.selfReported) === null || _a === void 0 ? void 0 : _a.skills.findIndex((s) => s.name === skill.name);
187768
+ const updatedReportedLevel = (_b = allSkills.selfReported) === null || _b === void 0 ? void 0 : _b.data.level.filter((_, index) => index !== reportedSkillIndex);
187769
+ await createOrUpdateUserReportedSkill([
187770
+ {
187771
+ requestBody: {
187772
+ skills: ((_c = allSkills.selfReported) === null || _c === void 0 ? void 0 : _c.skills.filter((s) => s.name !== skill.name)) || [],
187773
+ data: { level: updatedReportedLevel },
187774
+ user_id: getUserId(),
187775
+ username: getUserName(),
187776
+ },
187777
+ },
187778
+ ]);
187779
+ if (isCreatingOrUpdateUserReportedSkillError) {
187780
+ toast.error('Failed to delete skill');
187781
+ return;
187782
+ }
187783
+ toast.success('Skill deleted successfully');
187784
+ setTimeout(() => {
187785
+ setDeletingSkill(false);
187786
+ callback === null || callback === void 0 ? void 0 : callback();
187787
+ }, 1000);
187788
+ }
187789
+ else if (skill.type === 'desired') {
187790
+ const desiredSkillIndex = (_d = allSkills.desired) === null || _d === void 0 ? void 0 : _d.skills.findIndex((s) => s.name === skill.name);
187791
+ const updatedDesiredLevel = (_e = allSkills.desired) === null || _e === void 0 ? void 0 : _e.data.level.filter((_, index) => index !== desiredSkillIndex);
187792
+ await createOrUpdateUserDesiredSkill([
187793
+ {
187794
+ requestBody: {
187795
+ skills: ((_f = allSkills.desired) === null || _f === void 0 ? void 0 : _f.skills.filter((s) => s.name !== skill.name)) || [],
187796
+ data: { level: updatedDesiredLevel },
187797
+ user_id: getUserId(),
187798
+ username: getUserName(),
187799
+ },
187800
+ },
187801
+ ]);
187802
+ if (isCreatingOrUpdateUserDesiredSkillError) {
187803
+ toast.error('Failed to delete skill');
187804
+ return;
187805
+ }
187806
+ toast.success('Skill deleted successfully');
187807
+ setTimeout(() => {
187808
+ setDeletingSkill(false);
187809
+ callback === null || callback === void 0 ? void 0 : callback();
187810
+ }, 1000);
187811
+ }
187812
+ };
187813
+ const handleSkillsUpdate = async (skill, allSkills, callback) => {
187814
+ var _a, _b, _c, _d, _e, _f, _g, _h;
187815
+ setUpdatingSkill(true);
187816
+ if (skill.type === 'self-reported') {
187817
+ const reportedSkillIndex = (_a = allSkills.selfReported) === null || _a === void 0 ? void 0 : _a.skills.findIndex((s) => s.name === skill.name);
187818
+ const updatedReportedLevel = [...((_c = (_b = allSkills.selfReported) === null || _b === void 0 ? void 0 : _b.data.level) !== null && _c !== void 0 ? _c : [])];
187819
+ if (reportedSkillIndex !== undefined &&
187820
+ reportedSkillIndex > -1 &&
187821
+ skill.level !== undefined) {
187822
+ updatedReportedLevel[reportedSkillIndex] = skill.level;
187823
+ }
187824
+ await createOrUpdateUserReportedSkill([
187825
+ {
187826
+ requestBody: {
187827
+ skills: ((_d = allSkills.selfReported) === null || _d === void 0 ? void 0 : _d.skills) || [],
187828
+ data: { level: updatedReportedLevel },
187829
+ user_id: getUserId(),
187830
+ username: getUserName(),
187831
+ },
187832
+ },
187833
+ ]);
187834
+ if (isCreatingOrUpdateUserReportedSkillError) {
187835
+ toast.error('Failed to update skill');
187836
+ return;
187837
+ }
187838
+ toast.success('Skill updated successfully');
187839
+ setTimeout(() => {
187840
+ setUpdatingSkill(false);
187841
+ callback === null || callback === void 0 ? void 0 : callback();
187842
+ }, 1000);
187843
+ }
187844
+ else if (skill.type === 'desired') {
187845
+ const desiredSkillIndex = (_e = allSkills.desired) === null || _e === void 0 ? void 0 : _e.skills.findIndex((s) => s.name === skill.name);
187846
+ const updatedDesiredLevel = [...((_g = (_f = allSkills.desired) === null || _f === void 0 ? void 0 : _f.data.level) !== null && _g !== void 0 ? _g : [])];
187847
+ if (desiredSkillIndex !== undefined && desiredSkillIndex > -1 && skill.level !== undefined) {
187848
+ updatedDesiredLevel[desiredSkillIndex] = skill.level;
187849
+ }
187850
+ await createOrUpdateUserDesiredSkill([
187851
+ {
187852
+ requestBody: {
187853
+ skills: ((_h = allSkills.desired) === null || _h === void 0 ? void 0 : _h.skills) || [],
187854
+ data: { level: updatedDesiredLevel },
187855
+ user_id: getUserId(),
187856
+ username: getUserName(),
187857
+ },
187858
+ },
187859
+ ]);
187860
+ if (isCreatingOrUpdateUserDesiredSkillError) {
187861
+ toast.error('Failed to update skill');
187862
+ return;
187863
+ }
187864
+ toast.success('Skill updated successfully');
187865
+ setTimeout(() => {
187866
+ setUpdatingSkill(false);
187867
+ callback === null || callback === void 0 ? void 0 : callback();
187868
+ }, 1000);
187869
+ }
187870
+ };
187871
+ const handleSkillsCreate = async (skills) => {
187872
+ try {
187873
+ await createOrUpdateUserReportedSkill([
187874
+ { requestBody: skills, userId: getUserId(), username: getUserName() },
187875
+ ]);
187876
+ if (isCreatingOrUpdateUserReportedSkillError) {
187877
+ throw new Error('Failed to create skills');
187878
+ }
187879
+ if (showToast) {
187880
+ toast.success('Skills created successfully');
187881
+ }
187882
+ return true;
187883
+ }
187884
+ catch (_a) {
187885
+ toast.error('Failed to create skills');
187886
+ return false;
187887
+ }
187888
+ };
187889
+ return {
187890
+ earnedSkills,
187891
+ earnedSkillsLoading,
187892
+ earnedSkillsError,
187893
+ earnedSkillsSuccess,
187894
+ selfReportedSkills,
187895
+ selfReportedSkillsLoading,
187896
+ selfReportedSkillsError,
187897
+ selfReportedSkillsSuccess,
187898
+ desiredSkills,
187899
+ desiredSkillsLoading,
187900
+ desiredSkillsError,
187901
+ desiredSkillsSuccess,
187902
+ handleSkillsDeletion,
187903
+ updatingSkill,
187904
+ deletingSkill,
187905
+ setUpdatingSkill,
187906
+ handleSkillsUpdate,
187907
+ handleSkillsCreate,
187908
+ handleFetchAllSkills,
187909
+ fetchedSkills,
187910
+ isFetchingSkills,
187911
+ isFetchingSkillsError,
187912
+ };
187913
+ };
187914
+
187915
+ /**
187916
+ * Fetches the logged-in user's catalog / assigned / enrolled pathways and
187917
+ * hydrates each with its completion stats. Supports client-side search
187918
+ * filtering on pathway name.
187919
+ *
187920
+ * Ported from the skillsai SPA (hooks/profile/use-profile-pathways.ts).
187921
+ */
187922
+ const useProfilePathways = ({ searchQuery, contentType = 'catalog', lmsUrl, }) => {
187923
+ const [isLoading, setIsLoading] = useState(false);
187924
+ const [getPathwayList, { isError: isCatalogPathwaysError }] = useLazyGetPathwayListQuery();
187925
+ const [getUserAssignedPathways, { isError: isAssignedPathwaysError }] = useLazyGetUserAssignedPathwaysQuery();
187926
+ const [getUserEnrolledPathways, { isError: isEnrolledPathwaysError }] = useLazyGetUserEnrolledPathwaysQuery();
187927
+ const [getPathwayCompletion] = useLazyGetPathwayCompletionQuery();
187928
+ const [pathways, setPathways] = useState([]);
187929
+ const [filteredPathways, setFilteredPathways] = useState([]);
187930
+ const [pathwayCompletions, setPathwayCompletions] = useState([]);
187931
+ const [pathwayCompletionsLoading, setPathwayCompletionsLoading] = useState(false);
187932
+ const [isError, setIsError] = useState(false);
187933
+ const handleFetchSinglePathwayEnrollmentStatus = async (pathway) => {
187934
+ try {
187935
+ const response = await getUserEnrolledPathways([
187936
+ { username: getUserName(), pathwayUuid: pathway.pathway_uuid || '' },
187937
+ ], true);
187938
+ const data = response.data;
187939
+ return (Array.isArray(data) &&
187940
+ data.findIndex((pre) => pre.active && pre.pathway_uuid === pathway.pathway_uuid) !== -1);
187941
+ }
187942
+ catch (_a) {
187943
+ return false;
187944
+ }
187945
+ };
187946
+ const handleFetchPathwayCompletions = async (list) => {
187947
+ setPathwayCompletionsLoading(true);
187948
+ try {
187949
+ const completions = await Promise.all(list.map(async (pathway) => {
187950
+ const response = await getPathwayCompletion([
187951
+ { pathwayUuid: pathway.pathway_uuid || '', username: getUserName() },
187952
+ ]);
187953
+ return (response.data || {});
187954
+ }));
187955
+ setPathwayCompletions(completions);
187956
+ }
187957
+ catch (error) {
187958
+ console.error(JSON.stringify(error));
187959
+ setPathwayCompletions([]);
187960
+ }
187961
+ finally {
187962
+ setPathwayCompletionsLoading(false);
187963
+ }
187964
+ };
187965
+ const handleFetchCatalogPathways = async () => {
187966
+ var _a;
187967
+ setIsLoading(true);
187968
+ const response = await getPathwayList([{ username: getUserName(), platformKey: getTenant() }], true);
187969
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
187970
+ const fetchedPathways = raw.map((result) => {
187971
+ const metadata = result.metadata;
187972
+ return {
187973
+ ...result,
187974
+ metadata: {
187975
+ ...metadata,
187976
+ course_image_asset_path: (metadata === null || metadata === void 0 ? void 0 : metadata.course_image_asset_path)
187977
+ ? (lmsUrl !== null && lmsUrl !== void 0 ? lmsUrl : '') + metadata.course_image_asset_path
187978
+ : getRandomCourseImage(),
187979
+ },
187980
+ };
187981
+ });
187982
+ setPathways(fetchedPathways);
187983
+ setFilteredPathways(fetchedPathways);
187984
+ handleFetchPathwayCompletions(fetchedPathways);
187985
+ setIsError(isCatalogPathwaysError);
187986
+ setIsLoading(false);
187987
+ };
187988
+ const handleFetchAssignedPathways = async () => {
187989
+ var _a, _b;
187990
+ setIsLoading(true);
187991
+ const userId = getUserId();
187992
+ const response = await getUserAssignedPathways({ user_id: userId }, true);
187993
+ const results = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.results) !== null && _b !== void 0 ? _b : []) || [];
187994
+ setPathways(results);
187995
+ setFilteredPathways(results);
187996
+ handleFetchPathwayCompletions(results);
187997
+ setIsError(isAssignedPathwaysError);
187998
+ setIsLoading(false);
187999
+ };
188000
+ const handleFetchEnrolledPathways = async () => {
188001
+ setIsLoading(true);
188002
+ const response = await getUserEnrolledPathways([{ username: getUserName() }], true);
188003
+ const data = response.data || [];
188004
+ setPathways(data);
188005
+ setFilteredPathways(data);
188006
+ handleFetchPathwayCompletions(data);
188007
+ setIsError(isEnrolledPathwaysError);
188008
+ setIsLoading(false);
188009
+ };
188010
+ useEffect(() => {
188011
+ setPathways([]);
188012
+ setFilteredPathways([]);
188013
+ switch (contentType) {
188014
+ case 'assigned':
188015
+ handleFetchAssignedPathways();
188016
+ break;
188017
+ case 'enrolled':
188018
+ handleFetchEnrolledPathways();
188019
+ break;
188020
+ default:
188021
+ handleFetchCatalogPathways();
188022
+ break;
188023
+ }
188024
+ }, [contentType]);
188025
+ useEffect(() => {
188026
+ if (String(searchQuery).length > 2) {
188027
+ setFilteredPathways(pathways.filter((pathway) => { var _a; return (_a = pathway.name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase()); }));
188028
+ }
188029
+ else {
188030
+ setFilteredPathways(pathways);
188031
+ }
188032
+ }, [searchQuery]);
188033
+ return {
188034
+ pathways,
188035
+ filteredPathways,
188036
+ isLoading,
188037
+ isError,
188038
+ setPathways,
188039
+ setFilteredPathways,
188040
+ pathwayCompletions,
188041
+ pathwayCompletionsLoading,
188042
+ handleFetchSinglePathwayEnrollmentStatus,
188043
+ };
188044
+ };
188045
+
188046
+ /**
188047
+ * Fetches the logged-in user's enrolled / assigned / catalog programs
188048
+ * and hydrates each with its completion stats. Supports client-side
188049
+ * search filtering on program name.
188050
+ *
188051
+ * Ported from the skillsai SPA (hooks/profile/use-profile-programs.ts).
188052
+ */
188053
+ const useProfilePrograms = ({ searchQuery, activeTab = 'enrolled', }) => {
188054
+ const [programs, setPrograms] = useState([]);
188055
+ const [filteredPrograms, setFilteredPrograms] = useState([]);
188056
+ const [isLoading, setIsLoading] = useState(false);
188057
+ const [isError, setIsError] = useState(false);
188058
+ const [programCompletions, setProgramCompletions] = useState([]);
188059
+ const [programCompletionsLoading, setProgramCompletionsLoading] = useState(false);
188060
+ const [getProgramList, { isError: isCatalogProgramsError }] = useLazyGetProgramListQuery();
188061
+ const [getProgramCompletion] = useLazyGetProgramCompletionQuery();
188062
+ const [getUserEnrolledPrograms, { isError: isEnrolledProgramsError }] = useLazyGetUserEnrolledProgramsQuery();
188063
+ const [getAssignedPrograms, { isError: isAssignedProgramsError }] = useLazyGetAssignedProgramsQuery();
188064
+ const withImage = (program) => {
188065
+ var _a;
188066
+ return ({
188067
+ ...program,
188068
+ metadata: {
188069
+ ...((_a = program.metadata) !== null && _a !== void 0 ? _a : {}),
188070
+ image: getRandomCourseImage(),
188071
+ },
188072
+ });
188073
+ };
188074
+ const handleFetchProgramCompletions = async (list) => {
188075
+ setProgramCompletionsLoading(true);
188076
+ try {
188077
+ const completions = await Promise.all(list.map(async (program) => {
188078
+ const response = await getProgramCompletion([
188079
+ { programKey: program.program_key || '', username: getUserName() },
188080
+ ], true);
188081
+ return (response.data || {});
188082
+ }));
188083
+ setProgramCompletions(completions);
188084
+ }
188085
+ catch (error) {
188086
+ console.error(JSON.stringify(error));
188087
+ setProgramCompletions([]);
188088
+ }
188089
+ finally {
188090
+ setProgramCompletionsLoading(false);
188091
+ }
188092
+ };
188093
+ const handleProgramEnrollmentFetch = async () => {
188094
+ var _a;
188095
+ setIsLoading(true);
188096
+ const response = await getUserEnrolledPrograms([{ userId: getUserId(), platformKey: getTenant() }], true);
188097
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
188098
+ const fetched = raw.map(withImage);
188099
+ setPrograms(fetched);
188100
+ setFilteredPrograms(fetched);
188101
+ handleFetchProgramCompletions(fetched);
188102
+ setIsError(isEnrolledProgramsError);
188103
+ setIsLoading(false);
188104
+ };
188105
+ const handleAssignedProgramsFetch = async () => {
188106
+ var _a, _b;
188107
+ setIsLoading(true);
188108
+ const response = await getAssignedPrograms({ user_id: getUserId() }, true);
188109
+ const raw = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.results) !== null && _b !== void 0 ? _b : []) || [];
188110
+ const fetched = raw.map(withImage);
188111
+ setPrograms(fetched);
188112
+ setFilteredPrograms(fetched);
188113
+ handleFetchProgramCompletions(fetched);
188114
+ setIsError(isAssignedProgramsError);
188115
+ setIsLoading(false);
188116
+ };
188117
+ const handleFetchCatalogPrograms = async () => {
188118
+ var _a;
188119
+ setIsLoading(true);
188120
+ const response = await getProgramList([{ org: getOrg(), username: getUserName() }], true);
188121
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
188122
+ const fetched = raw.map(withImage);
188123
+ setPrograms(fetched);
188124
+ setFilteredPrograms(fetched);
188125
+ handleFetchProgramCompletions(fetched);
188126
+ setIsError(isCatalogProgramsError);
188127
+ setIsLoading(false);
188128
+ };
188129
+ useEffect(() => {
188130
+ if (String(searchQuery).length > 2) {
188131
+ setFilteredPrograms(programs.filter((program) => { var _a; return (_a = program.name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase()); }));
188132
+ }
188133
+ else {
188134
+ setFilteredPrograms(programs);
188135
+ }
188136
+ }, [searchQuery]);
188137
+ useEffect(() => {
188138
+ switch (activeTab) {
188139
+ case 'assigned':
188140
+ handleAssignedProgramsFetch();
188141
+ break;
188142
+ case 'enrolled':
188143
+ handleProgramEnrollmentFetch();
188144
+ break;
188145
+ case 'catalog':
188146
+ handleFetchCatalogPrograms();
188147
+ break;
188148
+ }
188149
+ }, [activeTab]);
188150
+ return {
188151
+ programs,
188152
+ filteredPrograms,
188153
+ isLoading,
188154
+ isError,
188155
+ setFilteredPrograms,
188156
+ setPrograms,
188157
+ programCompletions,
188158
+ programCompletionsLoading,
188159
+ };
188160
+ };
188161
+
188162
+ const isEmpty = (value) => {
188163
+ if (value == null)
188164
+ return true;
188165
+ if (Array.isArray(value) || typeof value === 'string')
188166
+ return value.length === 0;
188167
+ if (typeof value === 'object')
188168
+ return Object.keys(value).length === 0;
188169
+ return false;
188170
+ };
188171
+ /**
188172
+ * Aggregates a fixed list of profile activity counters (points, skills,
188173
+ * credentials, courses, programs, pathways, resources, assessments,
188174
+ * videos) by fanning out to nine RTK lazy queries and updating each row
188175
+ * independently as its request resolves. Errors on any single fetch
188176
+ * collapse that stat to 0 without blocking the others.
188177
+ *
188178
+ * Ported from the skillsai SPA (hooks/profile/use-profile-activity-stats.ts).
188179
+ */
188180
+ const useProfileActivityStats = () => {
188181
+ const [getUserSkillsPoints, { isError: isErrorGetUserSkillsPoints }] = useLazyGetUserSkillsPointsQuery();
188182
+ const [getUserReportedSkills, { isError: isErrorGetUserReportedSkills }] = useLazyGetUserReportedSkillsQuery();
188183
+ const [getUserDesiredSkills, { isError: isErrorGetUserDesiredSkills }] = useLazyGetUserDesiredSkillsQuery();
188184
+ const [getUserCredentials, { isError: isErrorGetUserCredentials }] = useLazyGetUserCredentialsQuery();
188185
+ const [getUserEnrolledCourses, { isError: isErrorGetUserEnrolledCourses }] = useLazyGetUserEnrolledCoursesQuery();
188186
+ const [getUserEnrolledPrograms, { isError: isEnrolledProgramsError }] = useLazyGetUserEnrolledProgramsQuery();
188187
+ const [getUserCatalogPathways, { isError: isCatalogPathwaysError }] = useLazyGetUserCatalogPathwaysQuery();
188188
+ const [getPerLearnerInfo, { isError: isErrorgetPerLearnerInfo }] = useLazyGetPerLearnerInfoQuery();
188189
+ const [stats, setStats] = useState([
188190
+ { value: 0, label: 'Points', loading: true },
188191
+ { value: 3, label: 'Skills', loading: true },
188192
+ { value: 0, label: 'Credentials', loading: true },
188193
+ { value: 4, label: 'Courses', loading: true },
188194
+ { value: 0, label: 'Programs', loading: true },
188195
+ { value: 0, label: 'Pathways', loading: true },
188196
+ { value: 0, label: 'Resources', loading: true },
188197
+ { value: 0, label: 'Assessments', loading: true },
188198
+ { value: 0, label: 'Videos', loading: true },
188199
+ ]);
188200
+ const updateSingleStat = (stat) => {
188201
+ setStats((old) => old.map((s) => (s.label === stat.label ? stat : s)));
188202
+ };
188203
+ const handleSkillsPointActivityStats = async () => {
188204
+ var _a;
188205
+ const label = 'Points';
188206
+ try {
188207
+ const response = await getUserSkillsPoints([{ userId: getUserId(), username: getUserName() }], true);
188208
+ const skillPoints = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.skill_points;
188209
+ if (isErrorGetUserSkillsPoints || isEmpty(skillPoints))
188210
+ throw new Error();
188211
+ const total = Object.values(skillPoints || {}).reduce((sum, skill) => { var _a; return sum + ((_a = skill.total_points) !== null && _a !== void 0 ? _a : 0); }, 0);
188212
+ updateSingleStat({ value: total, label, loading: false });
188213
+ }
188214
+ catch (_b) {
188215
+ updateSingleStat({ value: 0, label, loading: false });
188216
+ }
188217
+ };
188218
+ const handleActivitySkillsStats = async () => {
188219
+ var _a, _b, _c, _d, _e, _f;
188220
+ const label = 'Skills';
188221
+ try {
188222
+ const reportedSkills = await getUserReportedSkills([{ userId: getUserId(), username: getUserName() }], true);
188223
+ if (isErrorGetUserDesiredSkills && isErrorGetUserReportedSkills)
188224
+ throw new Error();
188225
+ let skillsCount = 0;
188226
+ if (!isErrorGetUserReportedSkills) {
188227
+ skillsCount = ((_c = (_b = (_a = reportedSkills === null || reportedSkills === void 0 ? void 0 : reportedSkills.data) === null || _a === void 0 ? void 0 : _a.skills) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0);
188228
+ }
188229
+ const desiredSkills = await getUserDesiredSkills([{ userId: getUserId(), username: getUserName() }], true);
188230
+ if (!isErrorGetUserDesiredSkills) {
188231
+ skillsCount += ((_f = (_e = (_d = desiredSkills === null || desiredSkills === void 0 ? void 0 : desiredSkills.data) === null || _d === void 0 ? void 0 : _d.skills) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0);
188232
+ }
188233
+ updateSingleStat({ value: skillsCount, label, loading: false });
188234
+ }
188235
+ catch (_g) {
188236
+ updateSingleStat({ value: 0, label, loading: false });
188237
+ }
188238
+ };
188239
+ const handleCredentialsActivityStats = async () => {
188240
+ var _a, _b, _c;
188241
+ const label = 'Credentials';
188242
+ try {
188243
+ const response = await getUserCredentials({
188244
+ platformKey: getTenant(),
188245
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
188246
+ }, true);
188247
+ if (isErrorGetUserCredentials || isEmpty(response === null || response === void 0 ? void 0 : response.data))
188248
+ throw new Error();
188249
+ const data = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.data;
188250
+ updateSingleStat({ value: (_c = data === null || data === void 0 ? void 0 : data.length) !== null && _c !== void 0 ? _c : 0, label, loading: false });
188251
+ }
188252
+ catch (_d) {
188253
+ updateSingleStat({ value: 0, label, loading: false });
188254
+ }
188255
+ };
188256
+ const handleCoursesActivityStats = async () => {
188257
+ var _a, _b;
188258
+ const label = 'Courses';
188259
+ try {
188260
+ const username = getUserName();
188261
+ if (!username)
188262
+ throw new Error();
188263
+ const response = await getUserEnrolledCourses({ username }, true);
188264
+ if (isErrorGetUserEnrolledCourses || isEmpty(response.data))
188265
+ throw new Error();
188266
+ updateSingleStat({ value: (_b = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.count) !== null && _b !== void 0 ? _b : 0, label, loading: false });
188267
+ }
188268
+ catch (_c) {
188269
+ updateSingleStat({ value: 0, label, loading: false });
188270
+ }
188271
+ };
188272
+ const handleProgramsActivityStats = async () => {
188273
+ var _a;
188274
+ const label = 'Programs';
188275
+ try {
188276
+ const response = await getUserEnrolledPrograms({ username: getUserName(), platform_key: getTenant() }, true);
188277
+ if (isEnrolledProgramsError || isEmpty(response.data))
188278
+ throw new Error();
188279
+ const data = response === null || response === void 0 ? void 0 : response.data;
188280
+ updateSingleStat({ value: (_a = data === null || data === void 0 ? void 0 : data.length) !== null && _a !== void 0 ? _a : 0, label, loading: false });
188281
+ }
188282
+ catch (_b) {
188283
+ updateSingleStat({ value: 0, label, loading: false });
188284
+ }
188285
+ };
188286
+ const handlePathwaysActivityStats = async () => {
188287
+ var _a;
188288
+ const pathwaysLabel = 'Pathways';
188289
+ const resourcesLabel = 'Resources';
188290
+ try {
188291
+ const response = await getUserCatalogPathways({ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '', platform_key: getTenant() }, true);
188292
+ if (isCatalogPathwaysError || isEmpty(response.data))
188293
+ throw new Error();
188294
+ const data = response.data;
188295
+ const seen = new Set();
188296
+ const unique = data.filter((item) => {
188297
+ const dup = seen.has(item.id);
188298
+ seen.add(item.id);
188299
+ return !dup;
188300
+ });
188301
+ const totalResources = unique.reduce((sum, pathway) => { var _a, _b; return sum + ((_b = (_a = pathway.path) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); }, 0);
188302
+ updateSingleStat({ value: unique.length, label: pathwaysLabel, loading: false });
188303
+ updateSingleStat({ value: totalResources, label: resourcesLabel, loading: false });
188304
+ }
188305
+ catch (_b) {
188306
+ updateSingleStat({ value: 0, label: pathwaysLabel, loading: false });
188307
+ updateSingleStat({ value: 0, label: resourcesLabel, loading: false });
188308
+ }
188309
+ };
188310
+ const handlePerLearnerInfoStats = async () => {
188311
+ var _a, _b, _c;
188312
+ const assessmentsLabel = 'Assessments';
188313
+ const videosLabel = 'Videos';
188314
+ try {
188315
+ const response = await getPerLearnerInfo([
188316
+ {
188317
+ org: getTenant(),
188318
+ userId: getUserName(),
188319
+ format: 'json',
188320
+ includeMainPlatform: true,
188321
+ },
188322
+ ], true);
188323
+ if (isErrorgetPerLearnerInfo || isEmpty(response.data))
188324
+ throw new Error();
188325
+ const inner = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.data;
188326
+ updateSingleStat({
188327
+ value: (_b = inner === null || inner === void 0 ? void 0 : inner.total_assessments) !== null && _b !== void 0 ? _b : 0,
188328
+ label: assessmentsLabel,
188329
+ loading: false,
188330
+ });
188331
+ updateSingleStat({ value: (_c = inner === null || inner === void 0 ? void 0 : inner.total_videos) !== null && _c !== void 0 ? _c : 0, label: videosLabel, loading: false });
188332
+ }
188333
+ catch (_d) {
188334
+ updateSingleStat({ value: 0, label: assessmentsLabel, loading: false });
188335
+ updateSingleStat({ value: 0, label: videosLabel, loading: false });
188336
+ }
188337
+ };
188338
+ useEffect(() => {
188339
+ handleSkillsPointActivityStats();
188340
+ handleActivitySkillsStats();
188341
+ handleCredentialsActivityStats();
188342
+ handleCoursesActivityStats();
188343
+ handleProgramsActivityStats();
188344
+ handlePathwaysActivityStats();
188345
+ handlePerLearnerInfoStats();
188346
+ }, []);
188347
+ return { stats };
188348
+ };
188349
+
188350
+ const EdxIframeContext = createContext({
188351
+ iframeUrl: '',
188352
+ setIframeUrl: () => { },
188353
+ courseOutline: {},
188354
+ setActiveTab: () => { },
188355
+ activeTab: '',
188356
+ courseID: '',
188357
+ currentlyInExamSubsection: false,
188358
+ setCurrentlyInExamSubsection: () => { },
188359
+ examInfo: null,
188360
+ setExamInfo: () => { },
188361
+ refresher: null,
188362
+ setRefresher: () => { },
188363
+ });
188364
+
188365
+ const CourseOutlineContext = createContext({
188366
+ courseOutline: {},
188367
+ courseOutlineLoading: false,
188368
+ expandedModule: '',
188369
+ expandedLessons: [],
188370
+ selectLesson: () => { },
188371
+ toggleModule: () => { },
188372
+ toggleLesson: () => { },
188373
+ currentChapter: '',
188374
+ currentLesson: '',
188375
+ course: null,
188376
+ courseOutlineDrawerOpen: false,
188377
+ setCourseOutlineDrawerOpen: () => { },
188378
+ currentUnitID: null,
188379
+ refetchCourseOutline: () => { },
188380
+ });
188381
+
188382
+ /**
188383
+ * Pure utility helpers for the edX iframe course navigation.
188384
+ *
188385
+ * Extracted from skillsai's `hooks/courses/use-edx-iframe.ts` so they can be
188386
+ * unit tested without a React renderer and reused from both the hook and
188387
+ * (future) course-content components. Any function that used to read
188388
+ * `config.urls.lms() / mfe() / legacyLmsUrl()` now takes those URLs as
188389
+ * parameters — there is no package-level config coupling here.
188390
+ *
188391
+ * The async `getIframeURL` wrapper (which calls `useLazyGetEdxSSOTokenQuery`)
188392
+ * remains inside the hook.
188393
+ */
188394
+ /**
188395
+ * Walk the outline tree and return the id of the `sequential` parent block
188396
+ * whose children include the given vertical id, or null if not found.
188397
+ */
188398
+ function findSequentialParent(data, verticalId) {
188399
+ if (!data)
188400
+ return null;
188401
+ if (data.type === 'sequential' && data.children) {
188402
+ for (const child of data.children) {
188403
+ if (child.id === verticalId)
188404
+ return data.id;
188405
+ const foundId = findSequentialParent(child, verticalId);
188406
+ if (foundId)
188407
+ return foundId;
188408
+ }
188409
+ }
188410
+ else if (data.children) {
188411
+ for (const child of data.children) {
188412
+ const foundId = findSequentialParent(child, verticalId);
188413
+ if (foundId)
188414
+ return foundId;
188415
+ }
188416
+ }
188417
+ return null;
188418
+ }
188419
+ /**
188420
+ * Recursively collect every `vertical`-type block in the tree into a flat
188421
+ * `{id, display_name}[]` list, preserving document order.
188422
+ */
188423
+ function flattenVerticalBlocks(data) {
188424
+ if (!data || typeof data !== 'object')
188425
+ return [];
188426
+ if (Array.isArray(data)) {
188427
+ const result = [];
188428
+ for (const item of data) {
188429
+ result.push(...flattenVerticalBlocks(item));
188430
+ }
188431
+ return result;
188432
+ }
188433
+ if (data.type === 'vertical') {
188434
+ const block = {
188435
+ id: data.id,
188436
+ display_name: data.display_name,
188437
+ };
188438
+ return [block, ...flattenVerticalBlocks(data.children)];
188439
+ }
188440
+ return flattenVerticalBlocks(data.children);
188441
+ }
188442
+ /**
188443
+ * Walks `data.children[0].children[0].children[attempt]` up to `maxAttempts`
188444
+ * and returns the first non-empty unit. Falls back to the course's first
188445
+ * sub-sub block if the course has started. Returns null otherwise.
188446
+ */
188447
+ function getFirstAvailableUnit(data, maxAttempts = 2) {
188448
+ var _a, _b, _c, _d, _e, _f, _g, _h;
188449
+ if (!data)
188450
+ return null;
188451
+ try {
188452
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
188453
+ const element = (_e = (_d = (_c = (_b = (_a = data.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.children) === null || _e === void 0 ? void 0 : _e[attempt];
188454
+ if (element)
188455
+ return element;
188456
+ }
188457
+ }
188458
+ catch (_j) {
188459
+ // fallthrough to the tolerant branch below
188460
+ }
188461
+ try {
188462
+ if (data &&
188463
+ Object.prototype.hasOwnProperty.call(data, 'children') &&
188464
+ data.children &&
188465
+ data.children[0] &&
188466
+ Object.prototype.hasOwnProperty.call(data.children[0], 'children') &&
188467
+ (!data.start || new Date(data.start) < new Date())) {
188468
+ return (_h = (_g = (_f = data.children[0]) === null || _f === void 0 ? void 0 : _f.children) === null || _g === void 0 ? void 0 : _g[0]) !== null && _h !== void 0 ? _h : null;
188469
+ }
188470
+ }
188471
+ catch (_k) {
188472
+ return null;
188473
+ }
188474
+ return null;
188475
+ }
188476
+ /**
188477
+ * Find the deepest `resume_block: true` vertical node in the tree (last in
188478
+ * document order) or null if the course has never been started.
188479
+ */
188480
+ function findLastResumeBlock(courseData) {
188481
+ if (!courseData)
188482
+ return null;
188483
+ let lastResumeBlock = null;
188484
+ const traverse = (node) => {
188485
+ if (node.resume_block && node.type === 'vertical') {
188486
+ lastResumeBlock = node;
188487
+ }
188488
+ if (node.children && node.children.length > 0) {
188489
+ for (const child of node.children)
188490
+ traverse(child);
188491
+ }
188492
+ };
188493
+ traverse(courseData);
188494
+ return lastResumeBlock;
188495
+ }
188496
+ /**
188497
+ * Find a specific block by id inside the top-level `children` array and
188498
+ * return it together with the chain of child-indices that locate it.
188499
+ */
188500
+ function getParentBlockById(blocksArray, targetBlockId) {
188501
+ let foundIndices = [];
188502
+ const findParentBlock = (currentBlock, target, currentIndices) => {
188503
+ if (currentBlock.id === target) {
188504
+ foundIndices = currentIndices.slice();
188505
+ return currentBlock;
188506
+ }
188507
+ if (currentBlock.children) {
188508
+ for (let i = 0; i < currentBlock.children.length; i++) {
188509
+ const result = findParentBlock(currentBlock.children[i], target, [...currentIndices, i]);
188510
+ if (result)
188511
+ return result;
188512
+ }
188513
+ }
188514
+ return null;
188515
+ };
188516
+ for (let i = 0; i < blocksArray.length; i++) {
188517
+ const parentBlock = findParentBlock(blocksArray[i], targetBlockId, [i]);
188518
+ if (parentBlock)
188519
+ return { parentBlock, foundIndices };
188520
+ }
188521
+ return { parentBlock: null, foundIndices };
188522
+ }
188523
+ /**
188524
+ * Given a unit id and the course outline, return the id of the previous
188525
+ * vertical unit, or null if there is no previous unit.
188526
+ */
188527
+ function getPreviousUnitIframe(suppliedId, courseData) {
188528
+ const idList = flattenVerticalBlocks(courseData);
188529
+ const index = idList.findIndex((item) => item.id === suppliedId);
188530
+ if (index === -1 || index === 0)
188531
+ return null;
188532
+ return idList[index - 1].id;
188533
+ }
188534
+ /**
188535
+ * Given a unit id and the course outline, return the id of the next vertical
188536
+ * unit, or null if there is no next unit.
188537
+ */
188538
+ function getNextUnitIframe(suppliedId, courseData) {
188539
+ const idList = flattenVerticalBlocks(courseData);
188540
+ const index = idList.findIndex((item) => item.id === suppliedId);
188541
+ if (index === -1 || index === idList.length - 1)
188542
+ return null;
188543
+ return idList[index + 1].id;
188544
+ }
188545
+ /**
188546
+ * Given a sublesson (3rd-level) id, return the parent module + lesson objects
188547
+ * from a modules array. Returns `{module:{}, lesson:{}}` if not found — this
188548
+ * preserves the skillsai fallback shape so callers don't need to null-check.
188549
+ */
188550
+ function getParentsInfosFromSublessonId(modules, sublessonId) {
188551
+ try {
188552
+ for (const mod of modules) {
188553
+ if (!mod.children)
188554
+ continue;
188555
+ for (const lesson of mod.children) {
188556
+ if (!lesson.children)
188557
+ continue;
188558
+ for (const sublesson of lesson.children) {
188559
+ if (sublesson.id === sublessonId) {
188560
+ return { module: mod, lesson };
188561
+ }
188562
+ }
188563
+ }
188564
+ }
188565
+ throw new Error('Sublesson not found');
188566
+ }
188567
+ catch (_a) {
188568
+ return { module: {}, lesson: {} };
188569
+ }
188570
+ }
188571
+ /**
188572
+ * Append a Bookmarks tab to the given tabs list in place.
188573
+ * The caller supplies the LMS base URL — no package config dependency.
188574
+ */
188575
+ function addBookmarksTab(tabs, courseId, lmsUrl) {
188576
+ tabs.push({
188577
+ tab_id: 'bookmarks',
188578
+ title: 'Bookmarks',
188579
+ url: `${lmsUrl}/courses/${courseId}/bookmarks`,
188580
+ });
188581
+ }
188582
+ /**
188583
+ * Resolve which unit of the course outline should be loaded into the iframe.
188584
+ * 1. If the current URL has `?unit_id=…`, try to find that block.
188585
+ * 2. Otherwise, return the last resume block if the learner has progress.
188586
+ * 3. Otherwise, fall back to the first available unit.
188587
+ *
188588
+ * Priority order:
188589
+ * 1. Explicit `unitId` parameter (from React state — always up-to-date).
188590
+ * 2. `?unit_id=…` from the current URL (`window.location.href`).
188591
+ * 3. The last `resume_block` vertical in the outline.
188592
+ * 4. The first available unit.
188593
+ */
188594
+ function getUnitToIframe(courseOutlineData, unitId) {
188595
+ var _a, _b;
188596
+ // 1. Explicit unitId from caller (e.g. React searchParams / context)
188597
+ if (unitId) {
188598
+ return (_a = findVerticalById(courseOutlineData, unitId)) !== null && _a !== void 0 ? _a : getFirstAvailableUnit(courseOutlineData);
188599
+ }
188600
+ // 2. Fall back to reading from the URL (legacy path / initial load)
188601
+ if (typeof window !== 'undefined') {
188602
+ try {
188603
+ const courseUrl = new URL(window.location.href);
188604
+ const urlUnitId = courseUrl.searchParams.get('unit_id');
188605
+ if (urlUnitId) {
188606
+ return ((_b = findVerticalById(courseOutlineData, urlUnitId)) !== null && _b !== void 0 ? _b : getFirstAvailableUnit(courseOutlineData));
188607
+ }
188608
+ }
188609
+ catch (_c) {
188610
+ // invalid URL — fall through
188611
+ }
188612
+ }
188613
+ // 3. Resume block
188614
+ const lastResumeBlock = findLastResumeBlock(courseOutlineData);
188615
+ if (lastResumeBlock)
188616
+ return lastResumeBlock;
188617
+ // 4. First available
188618
+ return getFirstAvailableUnit(courseOutlineData);
188619
+ }
188620
+ /** Find a block by id within the top-level `children` of a course outline. */
188621
+ function findVerticalById(data, verticalId) {
188622
+ if (!data || !data.children)
188623
+ return null;
188624
+ const search = (nodes) => {
188625
+ for (const item of nodes) {
188626
+ if (item.id === verticalId)
188627
+ return item;
188628
+ if (item.children) {
188629
+ const result = search(item.children);
188630
+ if (result)
188631
+ return result;
188632
+ }
188633
+ }
188634
+ return null;
188635
+ };
188636
+ return search(data.children);
188637
+ }
188638
+
188639
+ /**
188640
+ * Hook exposing helpers for the edX iframe course navigation. Ported from
188641
+ * skillsai's `hooks/courses/use-edx-iframe.ts`, with two changes:
188642
+ *
188643
+ * 1. All pure utilities are imported from `../lib/edx-iframe-utils`.
188644
+ * 2. LMS / MFE / legacy LMS base URLs are passed in as hook args instead of
188645
+ * being read from the skillsai `config` module.
188646
+ */
188647
+ const useEdxIframe = ({ lmsUrl, mfeUrl, legacyLmsUrl }) => {
188648
+ const [getEdxSsoAuthToken] = useLazyGetEdxSSOTokenQuery();
188649
+ /**
188650
+ * Resolve the iframe URL for a given course + courseInfo value. The
188651
+ * `courseInfo` can be either the course outline object (pick a unit from
188652
+ * it) or an xblock id string (use as-is).
188653
+ *
188654
+ * `unitId` (optional) is the unit id from React state (e.g.
188655
+ * `searchParams.get('unit_id')` or `currentUnitID`). When provided it
188656
+ * takes precedence over reading `window.location.href`, which avoids a
188657
+ * race condition with Next.js async `router.push`.
188658
+ */
188659
+ async function getIframeURL(courseId, courseInfo, callback, unitId) {
188660
+ var _a;
188661
+ if (typeof courseInfo === 'object' ||
188662
+ (typeof courseInfo === 'string' && courseInfo.includes(':'))) {
188663
+ // Course outline case — resolve an actual vertical to load.
188664
+ const outline = typeof courseInfo === 'object' ? courseInfo : null;
188665
+ if (outline) {
188666
+ flattenVerticalBlocks(outline);
188667
+ const unit = getUnitToIframe(outline, unitId);
188668
+ await addIframeUrl(courseId, (_a = unit === null || unit === void 0 ? void 0 : unit.id) !== null && _a !== void 0 ? _a : '', callback);
188669
+ return;
188670
+ }
188671
+ await addIframeUrl(courseId, courseInfo, callback);
188672
+ }
188673
+ else {
188674
+ await addIframeUrl(courseId, courseInfo, callback);
188675
+ }
188676
+ }
188677
+ async function addIframeUrl(courseId, rawXblockId, callback) {
188678
+ var _a, _b;
188679
+ let url = '';
188680
+ const xblockID = rawXblockId;
188681
+ let baseLMSIframeURL = `${lmsUrl}/xblock/${xblockID}?show_title=0&show_bookmark_button=1&recheck_access=1&view=student_view`;
188682
+ try {
188683
+ switch (xblockID) {
188684
+ case 'forum':
188685
+ url = `${mfeUrl}/discussions/${courseId}/posts`;
188686
+ break;
188687
+ case 'notes':
188688
+ url = `${mfeUrl}/courses/${courseId}/edxnotes`;
188689
+ break;
188690
+ case 'progress':
188691
+ url = `${mfeUrl}/learning/course/${courseId}/progress/`;
188692
+ break;
188693
+ case 'dates':
188694
+ url = `${mfeUrl}/learning/course/${courseId}/dates/`;
188695
+ break;
188696
+ case 'bookmarks':
188697
+ url = `${legacyLmsUrl}/courses/${courseId}/bookmarks/`;
188698
+ break;
188699
+ case 'instructor':
188700
+ baseLMSIframeURL = `${lmsUrl}/courses/${courseId}/instructor`;
188701
+ // fallthrough — same SSO-wrap path as default
188702
+ // eslint-disable-next-line no-fallthrough
188703
+ default: {
188704
+ const { data: authSsoToken } = await getEdxSsoAuthToken({
188705
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
188706
+ redirectUrl: baseLMSIframeURL,
188707
+ });
188708
+ url = `${legacyLmsUrl}/ibl/ai/sso/backend/edx/iframe?sso_auth_token=${(_b = authSsoToken === null || authSsoToken === void 0 ? void 0 : authSsoToken.sso_auth_token) !== null && _b !== void 0 ? _b : ''}`;
188709
+ break;
188710
+ }
188711
+ }
188712
+ callback(url);
188713
+ }
188714
+ catch (_c) {
188715
+ callback(url);
188716
+ }
188717
+ }
188718
+ return {
188719
+ getIframeURL,
188720
+ getUnitToIframe,
188721
+ findSequentialParent,
188722
+ flattenVerticalBlocks,
188723
+ getFirstAvailableUnit,
188724
+ findLastResumeBlock,
188725
+ getParentBlockById,
188726
+ getPreviousUnitIframe,
188727
+ getNextUnitIframe,
188728
+ addBookmarksTab,
188729
+ getParentsInfosFromSublessonId,
188730
+ };
188731
+ };
188732
+
188733
+ const ChatContext = createContext({
188734
+ isOpen: false,
188735
+ setIsOpen: () => { },
188736
+ courseMentor: null,
188737
+ setCourseMentor: () => { },
188738
+ mentorSidebarHidden: false,
188739
+ setMentorSidebarHidden: () => { },
188740
+ });
188741
+ const useChatState = () => useContext(ChatContext);
188742
+ function ChatProvider({ children, initialOpen = false, initialCourseMentor = null, initialMentorSidebarHidden = false, }) {
188743
+ const [isOpen, setIsOpen] = useState(initialOpen);
188744
+ const [courseMentor, setCourseMentor] = useState(initialCourseMentor);
188745
+ const [mentorSidebarHidden, setMentorSidebarHidden] = useState(initialMentorSidebarHidden);
188746
+ const value = {
188747
+ isOpen,
188748
+ setIsOpen,
188749
+ courseMentor,
188750
+ setCourseMentor,
188751
+ mentorSidebarHidden,
188752
+ setMentorSidebarHidden,
188753
+ };
188754
+ return createElement$1(ChatContext.Provider, { value }, children);
188755
+ }
188756
+
188757
+ /**
188758
+ * Ported from skillsai (`hooks/courses/use-course-detail.ts`).
188759
+ *
188760
+ * Differences from the skillsai version:
188761
+ * - No `next/navigation`. Caller passes an `onNavigate(href, { external })`
188762
+ * callback which is invoked for lesson open / access-course / Stripe
188763
+ * checkout redirect. `external: true` means "open in a new tab / navigate
188764
+ * the full window" (as opposed to SPA push).
188765
+ * - No internal `sonner` toasts. Caller may provide `onError` / `onSuccess`
188766
+ * callbacks (both optional).
188767
+ * - No `config.*` reads. Caller passes `dmUrl` (used to build the Stripe
188768
+ * `success_url`) and `courseEligibilityEnabled` (the feature flag that
188769
+ * used to live in `config.settings.courseEligibilityEnabled()`).
188770
+ */
188771
+ const ACCESS_COURSE_LABEL = 'Access Course';
188772
+ const ENROLL_NOW_LABEL = 'Enroll Now';
188773
+ const REQUEST_ACCESS_LABEL = 'Request Access';
188774
+ const ENROLL_NOW_COURSE_STARTING_SOON_LABEL = 'Enroll Now - Course Starting Soon';
188775
+ const REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL = 'Request Access - Course Starting Soon';
188776
+ const INVITATION_ONLY_LABEL = 'Invitation Only';
188777
+ const BUY_NOW_LABEL = 'Buy Now';
188778
+ const useCourseDetail = ({ courseId, dmUrl, courseEligibilityEnabled = false, onNavigate, onError, onSuccess, }) => {
188779
+ const [createCourseEnrollment, { isError: isCourseEnrollmentError }] = useCreateCourseEnrollmentMutation();
188780
+ const [createStripeCheckoutSession] = useCreateStripeCheckoutSessionMutation();
188781
+ const [getCourseProgress, { isLoading: isCourseProgressLoading, isError: isCourseProgressError },] = useLazyGetCourseProgressQuery();
188782
+ const [getCourseCompletion, { isLoading: isCourseCompletionLoading, isError: isCourseCompletionError },] = useLazyGetCourseCompletionQuery();
188783
+ const { handleFetchCourseMetaData, handleFetchCourseCompletionOutlines, handleFetchCourseEligibility, } = useCourseMetadata();
188784
+ const [courseInfoLoadingState, setCourseInfoLoadingState] = useState('not-started');
188785
+ const [course, setCourse] = useState(null);
188786
+ const [courseOutline, setCourseOutline] = useState({});
188787
+ const [courseOutlineLoading, setCourseOutlineLoading] = useState(false);
188788
+ const [courseEligibilityLoading, setCourseEligibilityLoading] = useState(false);
188789
+ const [courseButtonActionLoading, setCourseButtonActionLoading] = useState(false);
188790
+ const [courseProgress, setCourseProgress] = useState(null);
188791
+ const [courseCompletion, setCourseCompletion] = useState(null);
188792
+ const [courseGradingPolicyActive, setCourseGradingPolicyActive] = useState(false);
188793
+ const navigateToCourseContent = (href) => {
188794
+ if (onNavigate) {
188795
+ onNavigate(href, { external: inIframe() });
188796
+ }
188797
+ };
188798
+ const handleRequestAccess = () => {
188799
+ // Intentional no-op — skillsai's original also had no behavior here.
188800
+ // Callers that need request-access wiring should listen to btn_label.
188801
+ };
188802
+ const handleSelfEnrollToCourse = () => {
188803
+ // Same as skillsai — placeholder. Self-enroll is distinguished from
188804
+ // normal enroll only by the label; callers can key off it if needed.
188805
+ };
188806
+ const handleAccessCourse = () => {
188807
+ navigateToCourseContent(`/course-content/${courseId}/course`);
188808
+ };
188809
+ const handleCreateCheckoutSession = async () => {
188810
+ setCourseButtonActionLoading(true);
188811
+ const currentTenant = getTenant();
188812
+ try {
188813
+ const checkoutSession = await createStripeCheckoutSession({
188814
+ sku: courseId,
188815
+ org: (course === null || course === void 0 ? void 0 : course.org) || '',
188816
+ tenant: (course === null || course === void 0 ? void 0 : course.platform_key) || currentTenant,
188817
+ username: getUserName() || '',
188818
+ mode: 'payment',
188819
+ cancel_url: typeof window !== 'undefined' ? window.location.href : '',
188820
+ success_url: `${dmUrl}/api/service/orgs/${course === null || course === void 0 ? void 0 : course.platform_key}/stripe/course-payment-callback/`,
188821
+ }).unwrap();
188822
+ if (!(checkoutSession === null || checkoutSession === void 0 ? void 0 : checkoutSession.redirect_to)) {
188823
+ throw new Error('Failed to create checkout session');
188824
+ }
188825
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess('Redirecting to checkout page...');
188826
+ onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(checkoutSession.redirect_to, { external: true });
188827
+ }
188828
+ catch (_a) {
188829
+ setCourseButtonActionLoading(false);
188830
+ onError === null || onError === void 0 ? void 0 : onError('Failed to create checkout session');
188831
+ }
188832
+ };
188833
+ const handleEnrollToCourse = async () => {
188834
+ setCourseButtonActionLoading(true);
188835
+ try {
188836
+ const response = await createCourseEnrollment({
188837
+ course_details: { course_id: courseId },
188838
+ });
188839
+ if (isCourseEnrollmentError || !response.data || !response.data.created) {
188840
+ throw new Error('Failed to enroll in course');
188841
+ }
188842
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess('Enrolled in course successfully');
188843
+ setCourseButtonActionLoading(false);
188844
+ handleAccessCourse();
188845
+ }
188846
+ catch (_a) {
188847
+ onError === null || onError === void 0 ? void 0 : onError('Failed to enroll in course.');
188848
+ setCourseButtonActionLoading(false);
188849
+ }
188850
+ };
188851
+ const [courseEligibility, setCourseEligibility] = useState({
188852
+ btn_label: ENROLL_NOW_LABEL,
188853
+ btn_action: handleEnrollToCourse,
188854
+ });
188855
+ const handleFetchCourseEligibilityInfo = async () => {
188856
+ setCourseEligibilityLoading(true);
188857
+ const eligibility = await handleFetchCourseEligibility(courseId);
188858
+ if (eligibility) {
188859
+ const enrollmentStarted = dayjs(course === null || course === void 0 ? void 0 : course.enrollment_start).diff(dayjs(), 'seconds') > 0;
188860
+ const isEnrolled = eligibility.is_enrolled;
188861
+ const canEnroll = eligibility.can_enroll;
188862
+ const isEligible = eligibility.is_eligible;
188863
+ const isNotMainTenant = getTenant() !== 'main';
188864
+ const invitationOnly = eligibility.invitation_only;
188865
+ const coursePrice = course === null || course === void 0 ? void 0 : course.course_price;
188866
+ if (courseEligibilityEnabled) {
188867
+ if (isNotMainTenant && isEnrolled && enrollmentStarted && isEligible) {
188868
+ setCourseEligibility({
188869
+ btn_label: ACCESS_COURSE_LABEL,
188870
+ btn_action: handleAccessCourse,
188871
+ });
188872
+ }
188873
+ else if (isNotMainTenant && enrollmentStarted && !isEligible) {
188874
+ setCourseEligibility({
188875
+ btn_label: REQUEST_ACCESS_LABEL,
188876
+ btn_action: handleRequestAccess,
188877
+ });
188878
+ }
188879
+ else if (!isNotMainTenant &&
188880
+ !enrollmentStarted &&
188881
+ isEligible &&
188882
+ canEnroll &&
188883
+ !isEnrolled) {
188884
+ setCourseEligibility({
188885
+ btn_label: ENROLL_NOW_COURSE_STARTING_SOON_LABEL,
188886
+ btn_action: handleSelfEnrollToCourse,
188887
+ });
188888
+ }
188889
+ else if (!isNotMainTenant &&
188890
+ !enrollmentStarted &&
188891
+ !isEligible &&
188892
+ (course === null || course === void 0 ? void 0 : course.platform_key) === 'main') {
188893
+ setCourseEligibility({
188894
+ btn_label: REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL,
188895
+ btn_action: handleRequestAccess,
188896
+ });
188897
+ }
188898
+ else if (isNotMainTenant && enrollmentStarted && isEligible && !isEnrolled && canEnroll) {
188899
+ setCourseEligibility({
188900
+ btn_label: ENROLL_NOW_LABEL,
188901
+ btn_action: handleSelfEnrollToCourse,
188902
+ });
188903
+ }
188904
+ }
188905
+ else {
188906
+ if (isEnrolled) {
188907
+ setCourseEligibility({
188908
+ btn_label: ACCESS_COURSE_LABEL,
188909
+ btn_action: handleAccessCourse,
188910
+ });
188911
+ }
188912
+ else if (invitationOnly) {
188913
+ setCourseEligibility({
188914
+ disabled: true,
188915
+ btn_label: INVITATION_ONLY_LABEL,
188916
+ btn_action: () => { },
188917
+ });
188918
+ }
188919
+ else if (coursePrice && coursePrice !== 'Free' && parseInt(coursePrice) !== 0) {
188920
+ setCourseEligibility({
188921
+ btn_label: BUY_NOW_LABEL,
188922
+ btn_action: handleCreateCheckoutSession,
188923
+ });
188924
+ }
188925
+ else {
188926
+ setCourseEligibility({
188927
+ btn_label: ENROLL_NOW_LABEL,
188928
+ btn_action: handleEnrollToCourse,
188929
+ });
188930
+ }
188931
+ }
188932
+ setCourseEligibilityLoading(false);
188933
+ }
188934
+ else {
188935
+ setCourseEligibility({
188936
+ btn_label: ENROLL_NOW_LABEL,
188937
+ btn_action: handleEnrollToCourse,
188938
+ });
188939
+ setCourseEligibilityLoading(false);
188940
+ }
188941
+ };
188942
+ const handleFetchCourseInfo = async () => {
188943
+ setCourseInfoLoadingState('loading');
188944
+ try {
188945
+ const courseMetaData = await handleFetchCourseMetaData(courseId);
188946
+ if (courseMetaData && Object.keys(courseMetaData).length > 0) {
188947
+ setCourse(courseMetaData);
188948
+ setCourseInfoLoadingState('successful');
188949
+ }
188950
+ else {
188951
+ setCourse(null);
188952
+ setCourseInfoLoadingState('failure');
188953
+ }
188954
+ }
188955
+ catch (_a) {
188956
+ setCourse(null);
188957
+ setCourseInfoLoadingState('failure');
188958
+ }
188959
+ };
188960
+ const handleFetchCourseSyllabus = async (setLoadingState = true) => {
188961
+ if (setLoadingState) {
188962
+ setCourseOutlineLoading(true);
188963
+ }
188964
+ const outlines = (await handleFetchCourseCompletionOutlines(courseId));
188965
+ if (outlines && Object.keys(outlines).length > 0) {
188966
+ setCourseOutline(outlines);
188967
+ }
188968
+ else {
188969
+ setCourseOutline({});
188970
+ }
188971
+ if (setLoadingState) {
188972
+ setCourseOutlineLoading(false);
188973
+ }
188974
+ };
188975
+ const handleOpenLesson = (lessonId, checkEligibility = false) => {
188976
+ if (lessonId &&
188977
+ (checkEligibility ? courseEligibility.btn_label === ACCESS_COURSE_LABEL : true)) {
188978
+ navigateToCourseContent(`/course-content/${courseId}/course?unit_id=${encodeURIComponent(lessonId)}`);
188979
+ }
188980
+ };
188981
+ const handleFetchCourseProgress = async () => {
188982
+ var _a;
188983
+ try {
188984
+ const progress = await getCourseProgress({ courseKey: courseId });
188985
+ if (isCourseProgressError) {
188986
+ throw new Error('Error fetching course progress');
188987
+ }
188988
+ setCourseProgress(progress.data || null);
188989
+ if (progress.data) {
188990
+ setCourseGradingPolicyActive(Array.isArray((_a = progress.data.grading_policy) === null || _a === void 0 ? void 0 : _a.assignment_policies) &&
188991
+ progress.data.grading_policy.assignment_policies.length > 0);
188992
+ }
188993
+ }
188994
+ catch (error) {
188995
+ setCourseProgress(null);
188996
+ console.error('Error fetching course progress:', error);
188997
+ }
188998
+ };
188999
+ const handleFetchCourseCompletion = async (userID) => {
189000
+ try {
189001
+ const completion = await getCourseCompletion({
189002
+ courseKey: encodeURIComponent(courseId),
189003
+ userID,
189004
+ });
189005
+ if (isCourseCompletionError) {
189006
+ throw new Error('Error fetching course completion');
189007
+ }
189008
+ setCourseCompletion(completion.data || null);
189009
+ }
189010
+ catch (error) {
189011
+ setCourseCompletion(null);
189012
+ console.error('Error fetching course completion:', error);
189013
+ }
189014
+ };
189015
+ return {
189016
+ // handlers
189017
+ handleRequestAccess,
189018
+ handleSelfEnrollToCourse,
189019
+ handleAccessCourse,
189020
+ handleCreateCheckoutSession,
189021
+ handleEnrollToCourse,
189022
+ handleFetchCourseEligibilityInfo,
189023
+ handleFetchCourseInfo,
189024
+ handleFetchCourseSyllabus,
189025
+ handleOpenLesson,
189026
+ handleFetchCourseProgress,
189027
+ handleFetchCourseCompletion,
189028
+ // state
189029
+ course,
189030
+ courseInfoLoadingState,
189031
+ courseOutline,
189032
+ courseEligibility,
189033
+ courseOutlineLoading,
189034
+ courseEligibilityLoading,
189035
+ courseButtonActionLoading,
189036
+ isCourseProgressLoading,
189037
+ isCourseCompletionLoading,
189038
+ courseProgress,
189039
+ courseCompletion,
189040
+ courseGradingPolicyActive,
189041
+ // labels (re-exported here for convenience; also exported as module
189042
+ // constants at the top of this file)
189043
+ ACCESS_COURSE_LABEL,
189044
+ ENROLL_NOW_LABEL,
189045
+ REQUEST_ACCESS_LABEL,
189046
+ ENROLL_NOW_COURSE_STARTING_SOON_LABEL,
189047
+ REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL,
189048
+ INVITATION_ONLY_LABEL,
189049
+ BUY_NOW_LABEL,
189050
+ };
189051
+ };
189052
+
189053
+ /**
189054
+ * Hierarchical course outline (chapters → lessons → sublessons) with
189055
+ * completion progress indicators and collapsible tree UI.
189056
+ *
189057
+ * Ported from skillsai (`components/course-outline.tsx`). Reads all state
189058
+ * from `CourseOutlineContext` — callers provide it by wrapping this
189059
+ * component in a `<CourseOutlineContext.Provider value={...} />` (or by
189060
+ * using the provided `CourseContentLayout`, which sets up the provider).
189061
+ */
189062
+ const MAX_CHECKMARK_POINT = 7;
189063
+ const getCompletionRatio = (node) => {
189064
+ if (!Array.isArray(node.children) || node.children.length === 0) {
189065
+ return node.complete ? 1 : 0;
189066
+ }
189067
+ const totalChildren = node.children.length;
189068
+ const completedScore = node.children.reduce((acc, child) => acc + getCompletionRatio(child), 0);
189069
+ return completedScore / totalChildren;
189070
+ };
189071
+ const getCompletionLevel = (node) => {
189072
+ return Math.round(getCompletionRatio(node) * MAX_CHECKMARK_POINT);
189073
+ };
189074
+ const CompletionIcon = ({ node }) => {
189075
+ const level = getCompletionLevel(node);
189076
+ const size = 16;
189077
+ const strokeWidth = 2;
189078
+ const radius = (size - strokeWidth) / 2;
189079
+ const circumference = 2 * Math.PI * radius;
189080
+ const progress = level / MAX_CHECKMARK_POINT;
189081
+ const dashOffset = circumference * (1 - progress);
189082
+ if (level === MAX_CHECKMARK_POINT) {
189083
+ return (jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "flex-shrink-0", children: [jsx("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "#f59e0b", stroke: "none" }), jsx("path", { d: "M5 8.5L7 10.5L11 6", fill: "none", stroke: "white", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })] }));
189084
+ }
189085
+ if (level === 0) {
189086
+ return (jsx("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "flex-shrink-0", children: jsx("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: "#d1d5db", strokeWidth: strokeWidth }) }));
189087
+ }
189088
+ return (jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, className: "flex-shrink-0", children: [jsx("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: "#e5e7eb", strokeWidth: strokeWidth }), jsx("circle", { cx: size / 2, cy: size / 2, r: radius, fill: "none", stroke: "#f59e0b", strokeWidth: strokeWidth, strokeDasharray: circumference, strokeDashoffset: dashOffset, strokeLinecap: "round", transform: `rotate(-90 ${size / 2} ${size / 2})` })] }));
189089
+ };
189090
+ const SkeletonRow = () => jsx("div", { className: "mb-2 h-6 w-full animate-pulse rounded bg-gray-200" });
189091
+ const CourseOutline = () => {
189092
+ const { courseOutline, courseOutlineLoading, expandedModule, expandedLessons, selectLesson, toggleModule, toggleLesson, currentChapter, currentLesson, } = useContext(CourseOutlineContext);
189093
+ return (jsx("div", { className: "h-full overflow-y-auto md:h-[calc(100%-35px)]", style: { scrollbarWidth: 'none' }, children: courseOutlineLoading ? (jsx("div", { className: "p-3", children: Array.from({ length: 8 }, (_v, i) => (jsx(SkeletonRow, {}, i))) })) : (Array.isArray(courseOutline === null || courseOutline === void 0 ? void 0 : courseOutline.children) &&
189094
+ courseOutline.children.map((module) => (jsxs("div", { className: "border-b border-gray-200", children: [jsxs("button", { type: "button", onClick: () => toggleModule(module.id), className: `flex w-full items-center justify-between p-3 text-left hover:bg-gray-50 ${expandedModule === module.id ? 'bg-gray-50' : ''}`, children: [jsx("span", { className: "text-sm font-medium text-gray-700", children: module.display_name }), jsx(ChevronRight, { className: `h-4 w-4 text-gray-500 transition-transform ${expandedModule === module.id ? 'rotate-90 transform' : ''}` })] }), expandedModule === module.id && module.children && (jsx("div", { className: "pr-2 pb-2 pl-6", children: module.children.map((lesson) => (jsxs("div", { children: [jsxs("button", { type: "button", onClick: () => toggleLesson(lesson.id), className: `mb-1 flex w-full items-center justify-between rounded-sm p-2 text-left text-sm ${currentChapter === lesson.id
189095
+ ? 'bg-amber-50 text-amber-700'
189096
+ : 'text-gray-600 hover:bg-gray-50'}`, children: [jsxs("div", { className: "flex items-center", children: [jsx("div", { className: "mr-2 flex-shrink-0", children: jsx(CompletionIcon, { node: lesson }) }), jsx("span", { children: lesson.display_name })] }), lesson.children && lesson.children.length > 0 && (jsx(ChevronRight, { className: `h-4 w-4 text-gray-500 transition-transform ${expandedLessons.includes(lesson.id) ? 'rotate-90 transform' : ''}` }))] }), lesson.children &&
189097
+ lesson.children.length > 0 &&
189098
+ expandedLessons.includes(lesson.id) && (jsx("div", { className: "pr-2 pb-2 pl-6", children: lesson.children.map((sublesson) => (jsxs("button", { type: "button", onClick: () => selectLesson(sublesson.id), className: `mb-1 flex w-full items-center rounded-sm p-2 text-left text-sm ${currentLesson === sublesson.id
189099
+ ? 'bg-amber-50 text-amber-700'
189100
+ : 'text-gray-600 hover:bg-gray-50'}`, children: [jsx("div", { className: "mr-2 flex-shrink-0", children: jsx(CompletionIcon, { node: sublesson }) }), jsx("span", { children: sublesson.display_name })] }, sublesson.id))) }))] }, lesson.id))) }))] }, module.id)))) }));
189101
+ };
189102
+
189103
+ function Sheet({ ...props }) {
189104
+ return jsx(Root$b, { "data-slot": "sheet", ...props });
189105
+ }
189106
+ function SheetPortal({ ...props }) {
189107
+ return jsx(Portal$6, { "data-slot": "sheet-portal", ...props });
189108
+ }
189109
+ function SheetOverlay({ className, ...props }) {
189110
+ return (jsx(Overlay$1, { "data-slot": "sheet-overlay", className: cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50', className), ...props }));
189111
+ }
189112
+ function SheetContent({ className, children, side = 'right', ...props }) {
189113
+ return (jsxs(SheetPortal, { children: [jsx(SheetOverlay, {}), jsxs(Content$3, { "data-slot": "sheet-content", className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500', side === 'right' &&
189114
+ 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm', side === 'left' &&
189115
+ 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm', side === 'top' &&
189116
+ 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b', side === 'bottom' &&
189117
+ 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t', className), ...props, children: [children, jsxs(Close$1, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none", children: [jsx(X, { className: "size-4" }), jsx("span", { className: "sr-only", children: "Close" })] })] })] }));
189118
+ }
189119
+ function SheetHeader({ className, ...props }) {
189120
+ return (jsx("div", { "data-slot": "sheet-header", className: cn('flex flex-col gap-1.5 p-4', className), ...props }));
189121
+ }
189122
+ function SheetTitle({ className, ...props }) {
189123
+ return (jsx(Title$1, { "data-slot": "sheet-title", className: cn('text-foreground font-semibold', className), ...props }));
189124
+ }
189125
+
189126
+ /**
189127
+ * Mobile drawer wrapping `<CourseOutline />` in a `Sheet` for responsive
189128
+ * course navigation. Ported from skillsai's `course-outline-drawer.tsx`.
189129
+ * All state comes from `CourseOutlineContext`.
189130
+ */
189131
+ const CourseOutlineDrawer = () => {
189132
+ const { course, courseOutlineDrawerOpen, setCourseOutlineDrawerOpen } = useContext(CourseOutlineContext);
189133
+ return (jsx(Sheet, { open: courseOutlineDrawerOpen, onOpenChange: setCourseOutlineDrawerOpen, children: jsxs(SheetContent, { side: "left", className: "flex w-72 flex-col p-0", children: [jsx(SheetHeader, { className: "border-b border-gray-200 p-4", children: jsx(SheetTitle, { className: "text-left font-semibold text-gray-800", children: course === null || course === void 0 ? void 0 : course.display_name }) }), jsx(CourseOutline, {})] }) }));
189134
+ };
189135
+
189136
+ /**
189137
+ * Timed exam overlay + countdown. Ported from skillsai
189138
+ * (`components/edx-iframe/timed-exam.tsx`). Reads all state from
189139
+ * `EdxIframeContext`; no internal toasts or routing.
189140
+ *
189141
+ * Errors from the RTK exam mutations are caught and logged via
189142
+ * `console.error` — same behavior as the skillsai original. Callers that
189143
+ * want user-facing error toasts can wrap the data-layer calls themselves
189144
+ * higher up, or intercept via a custom RTK middleware.
189145
+ */
189146
+ const TimedExam = () => {
189147
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
189148
+ const { examInfo, setExamInfo, setRefresher } = useContext(EdxIframeContext);
189149
+ const [isReadyToStart, setIsReadyToStart] = useState(false);
189150
+ const [timeRemaining, setTimeRemaining] = useState(0);
189151
+ const [showFullInstructions, setShowFullInstructions] = useState(false);
189152
+ const [showEndExamModal, setShowEndExamModal] = useState(false);
189153
+ const [updateExamAttempt, { isLoading: isSubmittingExam }] = useUpdateExamAttemptMutation();
189154
+ const [startExam, { isLoading: isStartingExam }] = useStartExamMutation();
189155
+ const [getExamInfo] = useLazyGetExamInfoQuery();
189156
+ // Initialize timer when exam is started
189157
+ useEffect(() => {
189158
+ var _a, _b, _c, _d;
189159
+ if (((_b = (_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.attempt) === null || _b === void 0 ? void 0 : _b.attempt_status) === 'started' &&
189160
+ ((_d = (_c = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _c === void 0 ? void 0 : _c.attempt) === null || _d === void 0 ? void 0 : _d.time_remaining_seconds)) {
189161
+ setTimeRemaining(Math.floor(examInfo.exam.attempt.time_remaining_seconds));
189162
+ }
189163
+ }, [examInfo]);
189164
+ const updateExamInfo = async () => {
189165
+ if (!examInfo)
189166
+ return;
189167
+ const updated = await getExamInfo({
189168
+ course_id: examInfo.exam.course_id,
189169
+ content_id: examInfo.exam.content_id,
189170
+ is_learning_mfe: true,
189171
+ }, false);
189172
+ setExamInfo((updated === null || updated === void 0 ? void 0 : updated.data) || null);
189173
+ };
189174
+ // Countdown timer
189175
+ useEffect(() => {
189176
+ var _a, _b;
189177
+ if (timeRemaining > 0 && ((_b = (_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.attempt) === null || _b === void 0 ? void 0 : _b.attempt_status) === 'started') {
189178
+ const timer = setInterval(() => {
189179
+ setTimeRemaining((prev) => {
189180
+ var _a;
189181
+ const newTime = prev > 0 ? prev - 1 : 0;
189182
+ if (newTime === 0 && ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id)) {
189183
+ updateExamAttempt({
189184
+ attemptID: examInfo.exam.attempt.attempt_id,
189185
+ action: 'submit',
189186
+ })
189187
+ .unwrap()
189188
+ .then(() => {
189189
+ updateExamInfo();
189190
+ setRefresher(new Date());
189191
+ })
189192
+ .catch((error) => {
189193
+ console.error('Failed to auto-submit exam:', error);
189194
+ });
189195
+ }
189196
+ return newTime;
189197
+ });
189198
+ }, 1000);
189199
+ return () => clearInterval(timer);
189200
+ }
189201
+ return undefined;
189202
+ }, [timeRemaining, (_b = (_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.attempt) === null || _b === void 0 ? void 0 : _b.attempt_status, (_d = (_c = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _c === void 0 ? void 0 : _c.attempt) === null || _d === void 0 ? void 0 : _d.attempt_id]);
189203
+ if (!examInfo) {
189204
+ return null;
189205
+ }
189206
+ const formatTimeRemaining = (seconds) => {
189207
+ const hours = Math.floor(seconds / 3600);
189208
+ const minutes = Math.floor((seconds % 3600) / 60);
189209
+ const secs = seconds % 60;
189210
+ if (hours > 0) {
189211
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
189212
+ }
189213
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
189214
+ };
189215
+ const { exam } = examInfo;
189216
+ const timeLimitHours = Math.floor(exam.time_limit_mins / 60);
189217
+ const timeLimitMinutes = exam.time_limit_mins % 60;
189218
+ const formatTimeLimit = () => {
189219
+ if (timeLimitHours > 0 && timeLimitMinutes > 0) {
189220
+ return `${timeLimitHours} hour${timeLimitHours > 1 ? 's' : ''} ${timeLimitMinutes} minute${timeLimitMinutes > 1 ? 's' : ''}`;
189221
+ }
189222
+ else if (timeLimitHours > 0) {
189223
+ return `${timeLimitHours} hour${timeLimitHours > 1 ? 's' : ''}`;
189224
+ }
189225
+ return `${timeLimitMinutes} minute${timeLimitMinutes > 1 ? 's' : ''}`;
189226
+ };
189227
+ const handleStartExam = async () => {
189228
+ var _a;
189229
+ try {
189230
+ setIsReadyToStart(true);
189231
+ if ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id) {
189232
+ const formData = new FormData();
189233
+ formData.append('exam_id', examInfo.exam.id.toString());
189234
+ formData.append('start_clock', 'true');
189235
+ await startExam(formData).unwrap();
189236
+ await updateExamInfo();
189237
+ }
189238
+ }
189239
+ catch (error) {
189240
+ console.error('Failed to start exam:', error);
189241
+ setIsReadyToStart(false);
189242
+ }
189243
+ };
189244
+ const handleEndExam = () => {
189245
+ setShowEndExamModal(true);
189246
+ };
189247
+ const handleConfirmEndExam = async () => {
189248
+ var _a;
189249
+ try {
189250
+ if ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id) {
189251
+ await updateExamAttempt({
189252
+ attemptID: examInfo.exam.attempt.attempt_id,
189253
+ action: 'submit',
189254
+ }).unwrap();
189255
+ setShowEndExamModal(false);
189256
+ await updateExamInfo();
189257
+ setRefresher(new Date());
189258
+ }
189259
+ }
189260
+ catch (error) {
189261
+ console.error('Failed to submit exam:', error);
189262
+ }
189263
+ };
189264
+ const handleCancelEndExam = () => {
189265
+ setShowEndExamModal(false);
189266
+ };
189267
+ if (((_f = (_e = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _e === void 0 ? void 0 : _e.attempt) === null || _f === void 0 ? void 0 : _f.attempt_status) === 'started') {
189268
+ const isLowTime = timeRemaining <= (examInfo.exam.attempt.low_threshold_sec || 14400);
189269
+ const isCriticalTime = timeRemaining <= (examInfo.exam.attempt.critically_low_threshold_sec || 3600);
189270
+ return (jsxs("div", { className: "mx-auto max-w-4xl px-6 pt-6", children: [jsxs("div", { className: `rounded-lg border p-4 ${isCriticalTime
189271
+ ? 'border-red-200 bg-red-50'
189272
+ : isLowTime
189273
+ ? 'border-yellow-200 bg-yellow-50'
189274
+ : 'border-blue-200 bg-blue-50'}`, children: [jsx("div", { className: "mb-4 text-sm text-gray-600", children: showFullInstructions ? (jsxs(Fragment$1, { children: ["You are taking \"", examInfo.exam.exam_name, "\" as a timed exam. The timer below shows the time remaining in the exam. To receive credit for problems, you must select \"Submit\" for each problem before you select \"End My Exam\".", ' ', jsx("button", { type: "button", className: "text-blue-600 underline hover:text-blue-800", onClick: () => setShowFullInstructions(false), children: "Show less" })] })) : (jsxs(Fragment$1, { children: ["You are taking \"", examInfo.exam.exam_name, "\" as a timed exam.", ' ', jsx("button", { type: "button", className: "text-blue-600 underline hover:text-blue-800", onClick: () => setShowFullInstructions(true), children: "Show more" })] })) }), jsxs("div", { className: "flex items-center gap-4", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Clock, { className: `h-5 w-5 ${isCriticalTime ? 'text-red-600' : isLowTime ? 'text-yellow-600' : 'text-blue-600'}` }), jsx("span", { className: `text-md font-mono font-semibold ${isCriticalTime ? 'text-red-700' : isLowTime ? 'text-yellow-700' : 'text-blue-700'}`, "aria-live": "polite", "aria-label": `Time remaining: ${formatTimeRemaining(timeRemaining)}`, children: formatTimeRemaining(timeRemaining) })] }), jsx("button", { type: "button", onClick: handleEndExam, className: "rounded-md border border-gray-300 bg-white px-4 py-2 font-medium text-gray-700 transition-colors hover:bg-gray-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none", "aria-describedby": "end-exam-warning", children: "End My Exam" }), jsx("div", { id: "end-exam-warning", className: "sr-only", children: "Warning: Ending the exam will submit all your answers and you cannot return to continue." })] })] }), showEndExamModal && (jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: jsxs("div", { className: "mx-4 w-full max-w-full rounded-lg bg-white p-6 shadow-xl md:max-w-[40%]", children: [jsx("h2", { className: "mb-4 text-lg font-semibold text-gray-900", children: "Are you sure that you want to submit your timed exam?" }), jsx("p", { className: "mb-3 text-gray-700", children: "Make sure that you have selected \"Submit\" for each problem before you submit your exam." }), jsx("p", { className: "mb-6 text-gray-700", children: "After you submit your exam, your exam will be graded." }), jsxs("div", { className: "flex gap-3", children: [jsx("button", { type: "button", onClick: handleConfirmEndExam, disabled: isSubmittingExam, className: "rounded bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50", children: isSubmittingExam ? 'Submitting...' : 'Yes, submit my timed exam.' }), jsx("button", { type: "button", onClick: handleCancelEndExam, className: "rounded px-4 py-2 font-medium text-blue-600 transition-colors hover:bg-blue-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none", children: "No, I want to continue working." })] })] }) }))] }));
189275
+ }
189276
+ if (((_h = (_g = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _g === void 0 ? void 0 : _g.attempt) === null || _h === void 0 ? void 0 : _h.attempt_status) === 'submitted') {
189277
+ return null;
189278
+ }
189279
+ const noActiveAttempt = !((_j = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _j === void 0 ? void 0 : _j.attempt) || Object.keys(examInfo.exam.attempt).length === 0;
189280
+ const noActiveAttemptTop = !(examInfo === null || examInfo === void 0 ? void 0 : examInfo.active_attempt) || Object.keys(examInfo.active_attempt).length === 0;
189281
+ if (noActiveAttempt || noActiveAttemptTop) {
189282
+ return (jsxs("div", { className: "sm:p-6", children: [jsx("div", { className: "mb-8 rounded-lg border border-blue-200 bg-blue-50 p-6", children: jsxs("div", { className: "flex items-start gap-3", children: [jsx(Clock, { className: "mt-1 h-6 w-6 flex-shrink-0 text-blue-600" }), jsxs("div", { className: "flex-1", children: [jsxs("h2", { className: "mb-3 text-xl font-semibold text-gray-900", children: [examInfo.exam.exam_name, " is a Timed Exam (", formatTimeLimit(), ")"] }), jsxs("p", { className: "mb-4 leading-relaxed text-gray-700", children: ["This exam has a time limit associated with it. To pass this exam, you must complete the problems in the time allowed. After you select I am ready to start this timed exam, you will have ", formatTimeLimit(), " to complete and submit the exam."] }), jsxs("button", { type: "button", onClick: handleStartExam, disabled: isStartingExam || isReadyToStart, className: "inline-flex items-center gap-2 rounded-md bg-blue-600 px-6 py-3 font-medium text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50", "aria-describedby": "exam-time-info", children: [jsx(Clock, { className: "hidden h-4 w-4 md:block" }), isStartingExam ? 'Starting exam...' : 'I am ready to start this timed exam.'] }), jsxs("div", { id: "exam-time-info", className: "sr-only", children: ["Starting this exam will begin a ", formatTimeLimit(), " timer that cannot be paused."] })] })] }) }), jsxs("div", { className: "mb-8", children: [jsx("h3", { className: "mb-4 text-lg font-medium text-gray-900", children: "Can I request additional time to complete my exam?" }), jsxs("div", { className: "flex items-start gap-3", children: [jsx(CircleAlert, { className: "mt-0.5 h-5 w-5 flex-shrink-0 text-gray-500" }), jsx("p", { className: "leading-relaxed text-gray-700", children: "If you have disabilities, you might be eligible for an additional time allowance on timed exams. Ask your course team for information about additional time allowances." })] })] })] }));
189283
+ }
189284
+ return null;
189285
+ };
189286
+
189287
+ /**
189288
+ * Prop-driven port of skillsai's `CourseAccessGuard`. Responsibilities
189289
+ * unchanged: show a spinner while the course is loading, surface not-found
189290
+ * / unauthorized states via callbacks, render children only on a clean
189291
+ * authorized load. Routing is now the caller's responsibility.
189292
+ *
189293
+ * Callers should stabilize `onUnauthorized` / `onNotFound` with `useCallback`
189294
+ * (or hoist them to module scope) to avoid spurious re-fires when the effect
189295
+ * re-runs.
189296
+ */
189297
+ const CourseAccessGuard = ({ course, courseInfoLoadingState, currentTenant, onUnauthorized, onNotFound, children, }) => {
189298
+ const isLoaded = courseInfoLoadingState === 'successful' || courseInfoLoadingState === 'failure';
189299
+ const isUnauthorizedTenant = isLoaded &&
189300
+ !!(course === null || course === void 0 ? void 0 : course.platform_key) &&
189301
+ course.platform_key !== currentTenant &&
189302
+ course.platform_key !== 'main';
189303
+ const isNotFound = courseInfoLoadingState === 'failure' && !course;
189304
+ useEffect(() => {
189305
+ if (isUnauthorizedTenant) {
189306
+ onUnauthorized === null || onUnauthorized === void 0 ? void 0 : onUnauthorized();
189307
+ }
189308
+ else if (isNotFound) {
189309
+ onNotFound === null || onNotFound === void 0 ? void 0 : onNotFound();
189310
+ }
189311
+ }, [isUnauthorizedTenant, isNotFound, onUnauthorized, onNotFound]);
189312
+ if (!isLoaded || isUnauthorizedTenant || isNotFound) {
189313
+ return (jsx("div", { className: "flex flex-1 items-center justify-center", children: jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-amber-500 border-t-transparent" }) }));
189314
+ }
189315
+ return jsx(Fragment$1, { children: children });
189316
+ };
189317
+
189318
+ /**
189319
+ * Loading skeleton shown while `CourseContentLayout` is hydrating.
189320
+ * Ported from skillsai (`app/course-content/[course_id]/loading.tsx`).
189321
+ */
189322
+ const CourseContentLoading = () => {
189323
+ return (jsxs("div", { className: "flex min-h-screen flex-col", children: [jsx("div", { className: "h-16 flex-shrink-0 border-b border-gray-200 bg-white md:h-20" }), jsxs("div", { className: "flex flex-1 flex-col md:flex-row", children: [jsxs("div", { className: "w-full border-r border-gray-200 md:w-72", children: [jsx("div", { className: "border-b border-gray-200 p-4", children: jsx(Skeleton, { className: "h-6 w-48" }) }), jsx("div", { className: "space-y-4 p-4", children: Array.from({ length: 6 }, (_v, i) => (jsxs("div", { className: "space-y-2", children: [jsx(Skeleton, { className: "h-5 w-full" }), i === 0 && (jsxs("div", { className: "space-y-2 pl-4", children: [jsx(Skeleton, { className: "h-4 w-5/6" }), jsx(Skeleton, { className: "h-4 w-4/6" })] }))] }, i))) })] }), jsxs("div", { className: "flex flex-1 flex-col", children: [jsxs("div", { className: "border-b border-gray-200", children: [jsx("div", { className: "flex p-4", children: Array.from({ length: 5 }, (_v, i) => (jsx(Skeleton, { className: "mx-2 h-4 w-16" }, i))) }), jsxs("div", { className: "flex items-center justify-between bg-gray-50 p-4", children: [jsxs("div", { className: "flex items-center", children: [jsx(Skeleton, { className: "mr-2 h-3 w-24" }), jsx(Skeleton, { className: "mx-1 h-3 w-3" }), jsx(Skeleton, { className: "mr-2 h-3 w-32" }), jsx(Skeleton, { className: "mx-1 h-3 w-3" }), jsx(Skeleton, { className: "h-3 w-16" })] }), jsxs("div", { className: "flex items-center", children: [jsx(Skeleton, { className: "mr-4 h-3 w-16" }), jsx(Skeleton, { className: "h-3 w-16" })] })] })] }), jsx("div", { className: "h-full bg-amber-50 p-6", children: jsxs("div", { className: "mx-auto max-w-4xl", children: [jsx(Skeleton, { className: "mb-4 h-8 w-64" }), jsx(Skeleton, { className: "mb-2 h-4 w-full" }), jsx(Skeleton, { className: "mb-6 h-4 w-5/6" }), jsx("div", { className: "mb-6 aspect-video rounded-md bg-gray-200" }), jsxs("div", { className: "mb-6 rounded-md border border-gray-200 bg-white p-4", children: [jsx(Skeleton, { className: "mb-2 h-5 w-40" }), jsx(Skeleton, { className: "mb-2 h-4 w-full" }), jsx(Skeleton, { className: "mb-2 h-4 w-full" }), jsx(Skeleton, { className: "h-4 w-3/4" })] }), jsxs("div", { className: "flex items-center justify-between", children: [jsx(Skeleton, { className: "h-10 w-32" }), jsx(Skeleton, { className: "h-10 w-32" })] })] }) })] })] })] }));
189324
+ };
189325
+
186826
189326
  /**
186827
189327
  * LoginButton Component
186828
189328
  *
@@ -188480,5 +190980,5 @@ var event = /*#__PURE__*/Object.freeze({
188480
190980
  listen: listen
188481
190981
  });
188482
190982
 
188483
- export { AccessTimeHeatmap, AlertsTab, AnalyticsCourseDetail, AnalyticsCourses, AnalyticsFinancialStats, AnalyticsLayout, AnalyticsMonetizationStats, AnalyticsOverview, AnalyticsProgramDetail, AnalyticsPrograms, AnalyticsReportDownload, AnalyticsReports, AnalyticsSettingsProvider, AnalyticsTopicsStats, AnalyticsTranscriptsStats, AnalyticsUsersStats, ChartCardWrapper, ChartFiltersProvider, CompanyDialog, ConnectorManagementDialog, CopyButtonIcon, CreateWorkflowModal, CustomDateRangePicker, DeleteWorkflowModal, EditAlertDialog, EducationDialog, EducationTab, EmptyStats, ExperienceDialog, ExperienceTab, GroupsFilterDropdown, InstitutionDialog, InviteUserDialog, InvitedUsersDialog, Loader, LocalLLMTab, LoginButton, Markdown, NotificationDisplay, NotificationDropdown, PaywallModal, Profile, ResumeTab, RichTextEditor, SearchableMultiSelect, SendNotificationDialog, SignupButton, Spinner, StatCard, TAURI_COMMANDS, TAURI_EVENTS, TenantSwitcher, TimeFilter, TimeTrackingProvider, ToolDialogs, TopBanner, Version, WorkflowSidebar, initialModelDownloadState, isLocalLLMEnabled, isTauriApp, components as markdownComponents, sanitizeCss, setLocalLLMEnabled, useAnalyticsSettings, useChartFilters, useCourses, useFinancial, useIframeMessageHandler, useLocalStorage, useModelDownload, useMonetization, useOverview, usePrograms, useReports, useTauri, useTimeTracking, useTopics, useTranscripts, useUsers };
190983
+ export { ACCESS_COURSE_LABEL, AccessTimeHeatmap, AddSkillDialog, AlertsTab, AnalyticsCourseDetail, AnalyticsCourses, AnalyticsFinancialStats, AnalyticsLayout, AnalyticsMonetizationStats, AnalyticsOverview, AnalyticsProgramDetail, AnalyticsPrograms, AnalyticsReportDownload, AnalyticsReports, AnalyticsSettingsProvider, AnalyticsTopicsStats, AnalyticsTranscriptsStats, AnalyticsUsersStats, BUY_NOW_LABEL, ChartCardWrapper, ChartFiltersProvider, ChatContext, ChatProvider, CompanyDialog, ConnectorManagementDialog, CopyButtonIcon, CourseAccessGuard, CourseCardSkeleton, CourseContentLoading, CourseOutline, CourseOutlineContext, CourseOutlineDrawer, CreateWorkflowModal, CredentialBox, CredentialMiniBoxSkeleton, CustomDateRangePicker, CustomTooltip, DefaultEmptyBox, DeleteWorkflowModal, ENROLL_NOW_COURSE_STARTING_SOON_LABEL, ENROLL_NOW_LABEL, EditAlertDialog, EducationBox, EducationDialog, EducationTab, EdxIframeContext, EmptyStats, ExperienceBox, ExperienceDialog, ExperienceTab, GroupsFilterDropdown, INVITATION_ONLY_LABEL, InstitutionDialog, InviteUserDialog, InvitedUsersDialog, Loader, LocalLLMTab, LoginButton, Markdown, NotificationDisplay, NotificationDropdown, PaywallModal, Profile, ProfileTimeChart, REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL, REQUEST_ACCESS_LABEL, ResumeBox, ResumeTab, RichTextEditor, SearchableMultiSelect, SendNotificationDialog, SignupButton, SkeletonActivityStatBox, SkeletonAddSkillsLoading, SkeletonCreatePathwaySearchList, SkeletonEducationBox, SkeletonMultiplier, SkeletonPathwayBox, SkeletonProfileInfoCard, SkeletonSkillBox, SkillBox, SkillDetailModal, SkillLeaderboardChart, SkillsBox, Spinner, StatCard, TAURI_COMMANDS, TAURI_EVENTS, TenantSwitcher, TimeFilter, TimeTrackingProvider, TimedExam, ToolDialogs, TopBanner, UserAvatar, Version, WorkflowSidebar, addBookmarksTab, findLastResumeBlock, findSequentialParent, flattenVerticalBlocks, getFirstAvailableUnit, getNextUnitIframe, getOrg, getParentBlockById, getParentsInfosFromSublessonId, getPreviousUnitIframe, getRandomCourseImage, getTenant, getUnitToIframe, getUserId, getUserName, inBrowserPrint, inIframe, initialModelDownloadState, isLocalLLMEnabled, isTauriApp, components as markdownComponents, sanitizeCss, setLocalLLMEnabled, useAnalyticsSettings, useCatalogSearch, useChartFilters, useChatState, useCourseDetail, useCourseMetadata, useCourses, useEdxIframe, useFinancial, useIframeMessageHandler, useLocalStorage, useModelDownload, useMonetization, useOverview, useProfileActivityStats, useProfileCredentials, useProfilePathways, useProfilePrograms, useProfileSkills, useProfileTimeSpent, usePrograms, useReports, useTauri, useTimeTracking, useTopics, useTranscripts, useUserCourses, useUserMetadata, useUsers };
188484
190984
  //# sourceMappingURL=index.esm.js.map