@iblai/iblai-js 1.3.12 → 1.4.2

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, useGetMemsearchStatusQuery, 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
@@ -49585,17 +49601,11 @@ const Database = createLucideIcon("database", __iconNode$W);
49585
49601
 
49586
49602
 
49587
49603
  const __iconNode$V = [
49588
- [
49589
- "path",
49590
- {
49591
- d: "M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z",
49592
- key: "1yo7s0"
49593
- }
49594
- ],
49595
- ["path", { d: "m12 9 6 6", key: "anjzzh" }],
49596
- ["path", { d: "m18 9-6 6", key: "1fp51s" }]
49604
+ ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
49605
+ ["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
49606
+ ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
49597
49607
  ];
49598
- const Delete$1 = createLucideIcon("delete", __iconNode$V);
49608
+ const Download = createLucideIcon("download", __iconNode$V);
49599
49609
 
49600
49610
  /**
49601
49611
  * @license lucide-react v0.507.0 - ISC
@@ -49606,11 +49616,11 @@ const Delete$1 = createLucideIcon("delete", __iconNode$V);
49606
49616
 
49607
49617
 
49608
49618
  const __iconNode$U = [
49609
- ["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
49610
- ["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
49611
- ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
49619
+ ["circle", { cx: "12", cy: "12", r: "1", key: "41hilf" }],
49620
+ ["circle", { cx: "19", cy: "12", r: "1", key: "1wjl8i" }],
49621
+ ["circle", { cx: "5", cy: "12", r: "1", key: "1pcz8c" }]
49612
49622
  ];
49613
- const Download = createLucideIcon("download", __iconNode$U);
49623
+ const Ellipsis = createLucideIcon("ellipsis", __iconNode$U);
49614
49624
 
49615
49625
  /**
49616
49626
  * @license lucide-react v0.507.0 - ISC
@@ -82016,12 +82026,82 @@ function AddMemoryDialog({ open, onOpenChange, org, username }) {
82016
82026
  }, children: jsx(DialogContent$1, { className: "p-0 overflow-hidden max-w-lg", children: jsxs("form", { onSubmit: handleSubmit, children: [jsx("div", { className: "flex items-center justify-between px-6 py-4 border-b", children: jsx(DialogTitle$1, { className: "text-lg font-semibold text-gray-900", children: "Add Memory" }) }), jsx(DialogDescription$1, { className: "sr-only", children: "Add a new memory for the AI to remember about you" }), jsx("div", { className: "p-6 space-y-4", children: jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "memory-content", children: "What should the AI remember?" }), jsx(Textarea, { id: "memory-content", placeholder: "e.g., I prefer learning through practical examples rather than theory.", value: content, onChange: (e) => setContent(e.target.value), rows: 4, className: "w-full bg-gray-50 border-gray-200 focus:ring-blue-500 focus:border-blue-500 text-sm resize-none", disabled: isLoading }), content.length > 0 && content.trim().length < 10 && (jsx("p", { className: "text-sm text-red-500", children: "Must be at least 10 characters" }))] }) }), jsxs("div", { className: "flex justify-end gap-3 border-t px-6 py-4", children: [jsx(Button$1, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isLoading, children: "Cancel" }), jsx(Button$1, { className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] hover:opacity-90 text-white", type: "submit", disabled: isLoading || content.trim().length < 10, children: isLoading ? 'Saving...' : 'Save Memory' })] })] }) }) }));
82017
82027
  }
82018
82028
 
82029
+ const Pagination = ({ className, ...props }) => (jsx("nav", { role: "navigation", "aria-label": "pagination", className: cn("mx-auto flex w-full justify-center", className), ...props }));
82030
+ Pagination.displayName = "Pagination";
82031
+ const PaginationContent = React.forwardRef(({ className, ...props }, ref) => (jsx("ul", { ref: ref, className: cn("flex flex-row items-center gap-1", className), ...props })));
82032
+ PaginationContent.displayName = "PaginationContent";
82033
+ const PaginationItem = React.forwardRef(({ className, ...props }, ref) => (jsx("li", { ref: ref, className: cn("", className), ...props })));
82034
+ PaginationItem.displayName = "PaginationItem";
82035
+ const PaginationLink = ({ className, isActive, size = "icon", ...props }) => (jsx("a", { "aria-current": isActive ? "page" : undefined, className: cn(buttonVariants({
82036
+ variant: isActive ? "outline" : "ghost",
82037
+ size,
82038
+ }), className), ...props }));
82039
+ PaginationLink.displayName = "PaginationLink";
82040
+ const PaginationPrevious = ({ className, ...props }) => (jsxs(PaginationLink, { "aria-label": "Go to previous page", size: "default", className: cn("gap-1 pl-2.5", className), ...props, children: [jsx(ChevronLeft, { className: "h-4 w-4" }), jsx("span", { children: "Previous" })] }));
82041
+ PaginationPrevious.displayName = "PaginationPrevious";
82042
+ const PaginationNext = ({ className, ...props }) => (jsxs(PaginationLink, { "aria-label": "Go to next page", size: "default", className: cn("gap-1 pr-2.5", className), ...props, children: [jsx("span", { children: "Next" }), jsx(ChevronRight, { className: "h-4 w-4" })] }));
82043
+ PaginationNext.displayName = "PaginationNext";
82044
+ const PaginationEllipsis = ({ className, ...props }) => (jsxs("span", { "aria-hidden": true, className: cn("flex h-9 w-9 items-center justify-center", className), ...props, children: [jsx(Ellipsis, { className: "h-4 w-4" }), jsx("span", { className: "sr-only", children: "More pages" })] }));
82045
+ PaginationEllipsis.displayName = "PaginationEllipsis";
82046
+
82047
+ const IblPagination = ({ currentPage, totalPages, onPageChange, disabled = false, disableNumberedButtons = false, }) => {
82048
+ const getPageNumbers = () => {
82049
+ const pages = [];
82050
+ pages.push(1);
82051
+ let startPage = Math.max(2, currentPage - 1);
82052
+ let endPage = Math.min(totalPages - 1, currentPage + 1);
82053
+ if (currentPage <= 2) {
82054
+ endPage = Math.min(totalPages - 1, 4);
82055
+ }
82056
+ else if (currentPage >= totalPages - 1) {
82057
+ startPage = Math.max(2, totalPages - 3);
82058
+ }
82059
+ if (startPage > 2) {
82060
+ pages.push('ellipsis-start');
82061
+ }
82062
+ for (let i = startPage; i <= endPage; i++) {
82063
+ if (i !== 1 && i !== totalPages) {
82064
+ pages.push(i);
82065
+ }
82066
+ }
82067
+ if (endPage < totalPages - 1) {
82068
+ pages.push('ellipsis-end');
82069
+ }
82070
+ if (totalPages > 1) {
82071
+ pages.push(totalPages);
82072
+ }
82073
+ return pages;
82074
+ };
82075
+ if (totalPages <= 1) {
82076
+ return null;
82077
+ }
82078
+ return (jsx(Pagination, { children: jsxs(PaginationContent, { children: [jsx(PaginationItem, { children: jsx(PaginationPrevious, { size: "sm", onClick: (e) => {
82079
+ e.preventDefault();
82080
+ if (!disabled && currentPage > 1) {
82081
+ onPageChange(currentPage - 1);
82082
+ }
82083
+ }, "aria-disabled": currentPage === 1 || disabled, className: cn('cursor-pointer', (currentPage === 1 || disabled) && 'pointer-events-none opacity-50') }) }), !disableNumberedButtons &&
82084
+ getPageNumbers().map((page, index) => (jsx(PaginationItem, { children: page === 'ellipsis-start' || page === 'ellipsis-end' ? (jsx(PaginationEllipsis, {})) : (jsx(PaginationLink, { size: "sm", onClick: (e) => {
82085
+ e.preventDefault();
82086
+ if (!disabled && typeof page === 'number') {
82087
+ onPageChange(page);
82088
+ }
82089
+ }, isActive: page === currentPage, className: cn('cursor-pointer', disabled && 'pointer-events-none opacity-50'), children: page })) }, index))), jsx(PaginationItem, { children: jsx(PaginationNext, { size: "sm", onClick: (e) => {
82090
+ e.preventDefault();
82091
+ if (!disabled && currentPage < totalPages) {
82092
+ onPageChange(currentPage + 1);
82093
+ }
82094
+ }, "aria-disabled": currentPage === totalPages || disabled, className: cn('cursor-pointer', (currentPage === totalPages || disabled) && 'pointer-events-none opacity-50') }) })] }) }));
82095
+ };
82096
+
82097
+ const MEMORIES_PAGE_SIZE = 25;
82019
82098
  function MemoryTab({ org, username }) {
82020
82099
  var _a, _b, _c;
82021
82100
  const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
82022
82101
  const [deletingId, setDeletingId] = useState(null);
82023
- // Platform memsearch config
82024
- const { data: memsearchConfig, isLoading: isLoadingConfig, isError: isConfigError, } = useGetMemsearchConfigQuery({
82102
+ const [currentPage, setCurrentPage] = useState(1);
82103
+ // Platform memsearch status (available to students and admins)
82104
+ const { data: memsearchStatus, isLoading: isLoadingConfig, isError: isConfigError, } = useGetMemsearchStatusQuery({
82025
82105
  org,
82026
82106
  userId: username,
82027
82107
  });
@@ -82035,8 +82115,9 @@ function MemoryTab({ org, username }) {
82035
82115
  const { data: memoriesData, isLoading: isLoadingMemories } = useGetGlobalMemoriesQuery({
82036
82116
  org,
82037
82117
  userId: username,
82038
- params: { page_size: 25 },
82118
+ params: { page: currentPage, page_size: MEMORIES_PAGE_SIZE },
82039
82119
  });
82120
+ const totalPages = (memoriesData === null || memoriesData === void 0 ? void 0 : memoriesData.count) ? Math.ceil(memoriesData.count / MEMORIES_PAGE_SIZE) : 0;
82040
82121
  const [deleteMemory] = useDeleteGlobalMemoryMutation();
82041
82122
  const handleToggleSetting = async (key, checked) => {
82042
82123
  try {
@@ -82072,10 +82153,10 @@ function MemoryTab({ org, username }) {
82072
82153
  return (jsxs("div", { className: "max-w-2xl space-y-6", children: [jsx(Skeleton, { className: "h-20 w-full rounded-lg" }), jsx(Skeleton, { className: "h-20 w-full rounded-lg" }), jsx(Skeleton, { className: "h-40 w-full rounded-lg" })] }));
82073
82154
  }
82074
82155
  // Hide memory UI when platform has disabled memsearch (skip check if API errored)
82075
- if (!isConfigError && memsearchConfig && !memsearchConfig.enable_memsearch) {
82156
+ if (!isConfigError && memsearchStatus && !memsearchStatus.enable_memsearch) {
82076
82157
  return (jsx("div", { className: "max-w-2xl", children: jsx("div", { className: "rounded-lg border px-6 py-8 text-center", style: { borderColor: 'oklch(.922 0 0)' }, children: jsx("p", { className: "text-sm text-gray-500", children: "The memory feature is not enabled for this platform. Contact your administrator to enable it." }) }) }));
82077
82158
  }
82078
- return (jsxs("div", { className: "max-w-2xl space-y-8", children: [jsxs("div", { className: "space-y-4", children: [jsx("h4", { className: "text-sm font-semibold text-gray-700 dark:text-gray-300 tracking-wide", children: "Memory & Personalization" }), jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-5", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex-1 mr-4", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Allow AI to learn from our conversations" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "When enabled, the AI will remember useful information from your chats to provide better assistance." })] }), jsx(Switch, { checked: (_a = settings === null || settings === void 0 ? void 0 : settings.auto_capture_enabled) !== null && _a !== void 0 ? _a : false, onCheckedChange: (checked) => handleToggleSetting('auto_capture_enabled', checked), disabled: isUpdatingSettings, "aria-label": `Auto memory capture ${(settings === null || settings === void 0 ? void 0 : settings.auto_capture_enabled) ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500 flex-shrink-0" })] }), jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-5", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex-1 mr-4", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Use my saved information in responses" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "When enabled, the AI will use what it knows about you to personalize responses." })] }), jsx(Switch, { checked: (_b = settings === null || settings === void 0 ? void 0 : settings.use_memory_in_responses) !== null && _b !== void 0 ? _b : false, onCheckedChange: (checked) => handleToggleSetting('use_memory_in_responses', checked), disabled: isUpdatingSettings, "aria-label": `Use memory in responses ${(settings === null || settings === void 0 ? void 0 : settings.use_memory_in_responses) ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500 flex-shrink-0" })] })] }), jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx("h4", { className: "text-sm font-semibold text-gray-700 dark:text-gray-300 tracking-wide", children: "My Memories" }), jsxs(Button$1, { variant: "outline", size: "sm", onClick: () => setIsAddDialogOpen(true), className: "text-xs", children: [jsx(Plus, { className: "h-3 w-3 mr-1" }), "Add Memory"] })] }), isLoadingMemories ? (jsx("div", { className: "space-y-3", children: [1, 2, 3].map((i) => (jsx(Skeleton, { className: "h-16 w-full rounded-lg" }, i))) })) : ((_c = memoriesData === null || memoriesData === void 0 ? void 0 : memoriesData.results) === null || _c === void 0 ? void 0 : _c.length) ? (jsx("div", { className: "space-y-3", children: memoriesData.results.map((memory) => (jsxs("div", { className: "group flex items-start gap-3 rounded-lg border px-4 py-3", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsx("div", { className: "flex-shrink-0 mt-0.5", children: memory.is_auto_generated ? (jsx(Bot, { className: "h-4 w-4 text-blue-500", "aria-label": "Auto-generated memory" })) : (jsx(User, { className: "h-4 w-4 text-gray-400", "aria-label": "Manually added memory" })) }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("p", { className: "text-sm text-gray-800 dark:text-gray-200", children: memory.content }), jsxs("p", { className: "text-xs text-gray-400 mt-1", children: [new Date(memory.created_at).toLocaleDateString(), memory.is_auto_generated && (jsx("span", { className: "ml-2 inline-flex items-center rounded-full bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-600", children: "auto" }))] })] }), jsx("button", { type: "button", onClick: () => handleDeleteMemory(memory.id), disabled: deletingId === memory.id, className: "flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-100 text-gray-400 hover:text-gray-600 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-300", "aria-label": `Delete memory: ${memory.content.substring(0, 30)}`, children: deletingId === memory.id ? (jsx("div", { className: "w-4 h-4 border-2 border-gray-500 border-t-transparent rounded-full animate-spin" })) : (jsx(Delete$1, { className: "h-4 w-4" })) })] }, memory.id))) })) : (jsx("div", { className: "rounded-lg border px-6 py-8 text-center", style: { borderColor: 'oklch(.922 0 0)' }, children: jsx("p", { className: "text-sm text-gray-500", children: "No memories yet. The AI will start remembering information from your conversations, or you can add memories manually." }) })), memoriesData && memoriesData.count > 25 && (jsxs("p", { className: "text-xs text-gray-400 text-center", children: ["Showing 25 of ", memoriesData.count, " memories"] }))] }), jsx(AddMemoryDialog, { open: isAddDialogOpen, onOpenChange: setIsAddDialogOpen, org: org, username: username })] }));
82159
+ return (jsxs("div", { className: "max-w-2xl space-y-8", children: [jsxs("div", { className: "space-y-4", children: [jsx("h4", { className: "text-sm font-semibold text-gray-700 dark:text-gray-300 tracking-wide", children: "Memory & Personalization" }), jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-5", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex-1 mr-4", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Allow AI to learn from our conversations" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "When enabled, the AI will remember useful information from your chats to provide better assistance." })] }), jsx(Switch, { checked: (_a = settings === null || settings === void 0 ? void 0 : settings.auto_capture_enabled) !== null && _a !== void 0 ? _a : false, onCheckedChange: (checked) => handleToggleSetting('auto_capture_enabled', checked), disabled: isUpdatingSettings, "aria-label": `Auto memory capture ${(settings === null || settings === void 0 ? void 0 : settings.auto_capture_enabled) ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500 flex-shrink-0" })] }), jsxs("div", { className: "flex items-center justify-between rounded-lg border px-6 py-5", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsxs("div", { className: "flex-1 mr-4", children: [jsx("span", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: "Use my saved information in responses" }), jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "When enabled, the AI will use what it knows about you to personalize responses." })] }), jsx(Switch, { checked: (_b = settings === null || settings === void 0 ? void 0 : settings.use_memory_in_responses) !== null && _b !== void 0 ? _b : false, onCheckedChange: (checked) => handleToggleSetting('use_memory_in_responses', checked), disabled: isUpdatingSettings, "aria-label": `Use memory in responses ${(settings === null || settings === void 0 ? void 0 : settings.use_memory_in_responses) ? 'enabled' : 'disabled'}`, className: "cursor-pointer data-[state=checked]:bg-blue-500 flex-shrink-0" })] })] }), jsxs("div", { className: "space-y-4", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx("h4", { className: "text-sm font-semibold text-gray-700 dark:text-gray-300 tracking-wide", children: "My Memories" }), jsxs(Button$1, { variant: "outline", size: "sm", onClick: () => setIsAddDialogOpen(true), className: "text-xs", children: [jsx(Plus, { className: "h-3 w-3 mr-1" }), "Add Memory"] })] }), isLoadingMemories ? (jsx("div", { className: "space-y-3", children: [1, 2, 3].map((i) => (jsx(Skeleton, { className: "h-16 w-full rounded-lg" }, i))) })) : ((_c = memoriesData === null || memoriesData === void 0 ? void 0 : memoriesData.results) === null || _c === void 0 ? void 0 : _c.length) ? (jsx("div", { className: "space-y-3", children: memoriesData.results.map((memory) => (jsxs("div", { className: "group flex items-start gap-3 rounded-lg border px-4 py-3", style: { borderColor: 'oklch(.922 0 0)' }, children: [jsx("div", { className: "flex-shrink-0 mt-0.5", children: memory.is_auto_generated ? (jsx(Bot, { className: "h-4 w-4 text-blue-500", "aria-label": "Auto-generated memory" })) : (jsx(User, { className: "h-4 w-4 text-gray-400", "aria-label": "Manually added memory" })) }), jsxs("div", { className: "flex-1 min-w-0", children: [jsx("p", { className: "text-sm text-gray-800 dark:text-gray-200", children: memory.content }), jsxs("p", { className: "text-xs text-gray-400 mt-1", children: [new Date(memory.created_at).toLocaleDateString(), memory.is_auto_generated && (jsx("span", { className: "ml-2 inline-flex items-center rounded-full bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-600", children: "auto" }))] })] }), jsx("button", { type: "button", onClick: () => handleDeleteMemory(memory.id), disabled: deletingId === memory.id, className: "flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-100 text-gray-400 hover:text-gray-600 focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-300", "aria-label": `Delete memory: ${memory.content.substring(0, 30)}`, children: deletingId === memory.id ? (jsx("div", { className: "w-4 h-4 border-2 border-gray-500 border-t-transparent rounded-full animate-spin" })) : (jsx(Trash2, { className: "h-4 w-4" })) })] }, memory.id))) })) : (jsx("div", { className: "rounded-lg border px-6 py-8 text-center", style: { borderColor: 'oklch(.922 0 0)' }, children: jsx("p", { className: "text-sm text-gray-500", children: "No memories yet. The AI will start remembering information from your conversations, or you can add memories manually." }) })), totalPages > 1 && (jsx(IblPagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: setCurrentPage, disabled: isLoadingMemories }))] }), jsx(AddMemoryDialog, { open: isAddDialogOpen, onOpenChange: setIsAddDialogOpen, org: org, username: username })] }));
82079
82160
  }
82080
82161
 
82081
82162
  const renderLucideIcon = (Icon) => function RenderedIcon(props) {
@@ -82089,17 +82170,17 @@ function Profile({ tenant, username, onClose, customization = {}, isAdmin = fals
82089
82170
  isUsingFoundry: localLLMProps === null || localLLMProps === void 0 ? void 0 : localLLMProps.isUsingFoundry,
82090
82171
  hasFoundryStatus: 'foundryStatus' in (localLLMProps || {}),
82091
82172
  });
82092
- const { data: memsearchConfig, isError: isMemsearchConfigError, isLoading: isMemsearchConfigLoading, error: memsearchConfigError, } = useGetMemsearchConfigQuery({ org: tenant, userId: username }, { skip: !enableMemoryTab });
82093
- console.log('[Profile] memsearch config debug:', {
82173
+ const { data: memsearchStatus, isError: isMemsearchStatusError, isLoading: isMemsearchStatusLoading, error: memsearchStatusError, } = useGetMemsearchStatusQuery({ org: tenant, userId: username }, { skip: !enableMemoryTab });
82174
+ console.log('[Profile] memsearch status debug:', {
82094
82175
  enableMemoryTab,
82095
- isMemsearchConfigLoading,
82096
- isMemsearchConfigError,
82097
- memsearchConfigError,
82098
- memsearchConfig,
82099
- enable_memsearch: memsearchConfig === null || memsearchConfig === void 0 ? void 0 : memsearchConfig.enable_memsearch,
82100
- });
82101
- // Show memory tab if enabled for this platform AND memsearch is enabled (or config API is unavailable)
82102
- const isMemoryEnabled = enableMemoryTab && (isMemsearchConfigError || ((_a = memsearchConfig === null || memsearchConfig === void 0 ? void 0 : memsearchConfig.enable_memsearch) !== null && _a !== void 0 ? _a : false));
82176
+ isMemsearchStatusLoading,
82177
+ isMemsearchStatusError,
82178
+ memsearchStatusError,
82179
+ memsearchStatus,
82180
+ enable_memsearch: memsearchStatus === null || memsearchStatus === void 0 ? void 0 : memsearchStatus.enable_memsearch,
82181
+ });
82182
+ // Show memory tab if enabled for this platform AND memsearch is enabled (or status API is unavailable)
82183
+ const isMemoryEnabled = enableMemoryTab && (isMemsearchStatusError || ((_a = memsearchStatus === null || memsearchStatus === void 0 ? void 0 : memsearchStatus.enable_memsearch) !== null && _a !== void 0 ? _a : false));
82103
82184
  const baseTabs = [
82104
82185
  { id: 'basic', label: 'Basic', renderIcon: renderLucideIcon(User) },
82105
82186
  { id: 'social', label: 'Social', renderIcon: renderLucideIcon(Globe) },
@@ -82662,7 +82743,7 @@ TabsTrigger.displayName = Trigger$3.displayName;
82662
82743
  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
82744
  TabsContent.displayName = Content$1.displayName;
82664
82745
 
82665
- const getUserName = () => {
82746
+ const getUserName$1 = () => {
82666
82747
  var _a;
82667
82748
  return (_a = JSON.parse(localStorage.getItem("userData"))) === null || _a === void 0 ? void 0 : _a.user_nicename;
82668
82749
  };
@@ -82829,7 +82910,7 @@ function UsersTab({ tenant, currentPage, itemsPerPage, onInviteSuccess }) {
82829
82910
  email,
82830
82911
  platform_key: tenant,
82831
82912
  redirect_to: window.location.origin + window.location.pathname,
82832
- source: getUserName(),
82913
+ source: getUserName$1(),
82833
82914
  active: true,
82834
82915
  },
82835
82916
  }).unwrap();
@@ -82909,7 +82990,7 @@ function UsersTab({ tenant, currentPage, itemsPerPage, onInviteSuccess }) {
82909
82990
  email: emailValue,
82910
82991
  platform_key: platformKey || tenant,
82911
82992
  redirect_to: window.location.origin + window.location.pathname,
82912
- source: getUserName(),
82993
+ source: getUserName$1(),
82913
82994
  active: true,
82914
82995
  ...(companyNameIndex !== -1 &&
82915
82996
  ((_c = row[companyNameIndex]) === null || _c === void 0 ? void 0 : _c.trim()) && {
@@ -83091,7 +83172,7 @@ function CoursesTab({ tenant, currentPage, itemsPerPage, hasManageUsersPermissio
83091
83172
  // Search query for courses
83092
83173
  const { data: coursesData, isLoading: isLoadingCourses, isFetching: isFetchingCourses, } = useGetPersonnalizedSearchQuery([
83093
83174
  {
83094
- username: getUserName(),
83175
+ username: getUserName$1(),
83095
83176
  content: ['courses'],
83096
83177
  query: debouncedCourseSearch || '',
83097
83178
  returnFacet: false,
@@ -83467,7 +83548,7 @@ function ProgramsTab({ tenant, currentPage, itemsPerPage, hasManageUsersPermissi
83467
83548
  // Search query for programs
83468
83549
  const { data: programsData, isLoading: isLoadingPrograms, isFetching: isFetchingPrograms, } = useGetPersonnalizedSearchQuery([
83469
83550
  {
83470
- username: getUserName(),
83551
+ username: getUserName$1(),
83471
83552
  content: ['programs'],
83472
83553
  query: debouncedProgramSearch || '',
83473
83554
  returnFacet: false,
@@ -122438,7 +122519,7 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
122438
122519
  }, [currentTemplateDetail, isHumanSupport, template]);
122439
122520
  useEffect(() => {
122440
122521
  try {
122441
- const storedUserName = getUserName();
122522
+ const storedUserName = getUserName$1();
122442
122523
  if (storedUserName) {
122443
122524
  setUsername(storedUserName);
122444
122525
  }
@@ -186823,6 +186904,2490 @@ function AnalyticsMonetizationStats({ tenantKey }) {
186823
186904
  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
186905
  }
186825
186906
 
186907
+ const SkeletonMultiplier = ({ Skeleton, multiplier }) => {
186908
+ return (jsx(Fragment$1, { children: Array.from({ length: multiplier }).map((_, index) => (jsx(Skeleton, {}, index))) }));
186909
+ };
186910
+
186911
+ const SkeletonActivityStatBox = () => {
186912
+ 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" })] }));
186913
+ };
186914
+
186915
+ function CourseCardSkeleton() {
186916
+ 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" })] })] })] }));
186917
+ }
186918
+
186919
+ const DefaultEmptyBox = ({ image = '/images/empty-data-icon.svg', message = 'No data available', imageSize = 40, bordered = true, className = '', }) => {
186920
+ 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 })] }) }));
186921
+ };
186922
+
186923
+ const CredentialMiniBoxSkeleton = () => {
186924
+ 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" })] })] }));
186925
+ };
186926
+
186927
+ const SkillBox = ({ skill, onSkillClick, showRating = true, iconSrc = '/images/empty-data-icon.svg', }) => {
186928
+ 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" })] }));
186929
+ };
186930
+
186931
+ const SkeletonSkillBox = () => {
186932
+ 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" })] }));
186933
+ };
186934
+
186935
+ const RATING_DESCRIPTIONS = {
186936
+ 1: 'Basic understanding of fundamentals; requires significant guidance.',
186937
+ 2: 'Familiar with core concepts; can complete routine tasks with some supervision.',
186938
+ 3: 'Capable of managing varied tasks; understands nuances but may seek guidance for complexities.',
186939
+ 4: 'Proficient with advanced concepts; can work independently on complex tasks.',
186940
+ 5: 'Expert level mastery; can innovate and teach others in this domain.',
186941
+ };
186942
+ function SkillDetailModal({ skill, updatingSkill = false, deletingSkill = false, onClose, onRatingChange, onDeleteSkill, }) {
186943
+ const [originalRating] = useState(skill.rating || 1);
186944
+ const [tempRating, setTempRating] = useState(skill.rating || 1);
186945
+ const handleTempRatingChange = (rating) => {
186946
+ setTempRating(rating);
186947
+ };
186948
+ const handleConfirm = () => {
186949
+ if (updatingSkill)
186950
+ return;
186951
+ if (onRatingChange) {
186952
+ onRatingChange(tempRating);
186953
+ }
186954
+ };
186955
+ const handleCancel = () => {
186956
+ onClose();
186957
+ };
186958
+ 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) => {
186959
+ const position = ((rating - 1) / 4) * 100;
186960
+ 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
186961
+ ? 'border-white bg-amber-500'
186962
+ : 'border-amber-200 bg-white'}`, style: { left: `${position}%` } }, rating));
186963
+ }), 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: {
186964
+ width: tempRating === 1 ? '0%' : `${((tempRating - 1) / 4) * 100}%`,
186965
+ } }), [1, 2, 3, 4, 5].map((rating) => {
186966
+ const position = ((rating - 1) / 4) * 100;
186967
+ 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}`));
186968
+ }), 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
186969
+ ? 'animate-pulse hover:opacity-[var(--button-primary-hover-opacity)]'
186970
+ : 'hover:opacity-[var(--button-primary-hover-opacity)]'}`, children: updatingSkill ? 'Updating...' : 'Confirm' })] })] }) }));
186971
+ }
186972
+
186973
+ const SkeletonPathwayBox = () => {
186974
+ 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" })] })] }));
186975
+ };
186976
+
186977
+ /**
186978
+ * Skeleton placeholder for a single profile info card (Last Accessed,
186979
+ * Joined, Total Time Spent, Top Content). Ported from the skillsai SPA
186980
+ * (components/skeleton-profile-info-card.tsx).
186981
+ */
186982
+ 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" })] })] }));
186983
+
186984
+ /**
186985
+ * Browser-only helpers used by skills-profile hooks and components.
186986
+ *
186987
+ * These are sync localStorage accessors ported from the skillsai SPA. They
186988
+ * intentionally live here (not in @iblai/web-utils) to avoid colliding with
186989
+ * the existing async `getUserName(storageService)` helper in
186990
+ * packages/web-utils/src/providers/auth-provider.tsx, and because no other
186991
+ * package consumes them.
186992
+ */
186993
+ const LS_KEYS = {
186994
+ TENANT: 'tenant',
186995
+ CURRENT_TENANT: 'current_tenant',
186996
+ USER_DATA: 'userData',
186997
+ };
186998
+ const isJSON = (text) => {
186999
+ if (typeof text !== 'string')
187000
+ return false;
187001
+ try {
187002
+ JSON.parse(text);
187003
+ return true;
187004
+ }
187005
+ catch (_a) {
187006
+ return false;
187007
+ }
187008
+ };
187009
+ const readLocalStorage = (key) => {
187010
+ if (typeof window === 'undefined')
187011
+ return null;
187012
+ try {
187013
+ return window.localStorage.getItem(key);
187014
+ }
187015
+ catch (_a) {
187016
+ return null;
187017
+ }
187018
+ };
187019
+ /** Returns the current tenant slug from localStorage, or empty string. */
187020
+ function getTenant() {
187021
+ return readLocalStorage(LS_KEYS.TENANT) || '';
187022
+ }
187023
+ /** Returns the `org` field of the currently selected tenant, or null. */
187024
+ function getOrg() {
187025
+ var _a;
187026
+ const currentTenant = readLocalStorage(LS_KEYS.CURRENT_TENANT);
187027
+ return currentTenant && isJSON(currentTenant) ? (_a = JSON.parse(currentTenant)) === null || _a === void 0 ? void 0 : _a.org : null;
187028
+ }
187029
+ /** Returns the logged-in user's numeric user_id from localStorage, or null. */
187030
+ function getUserId() {
187031
+ var _a;
187032
+ const userData = readLocalStorage(LS_KEYS.USER_DATA);
187033
+ return userData && isJSON(userData) ? (_a = JSON.parse(userData)) === null || _a === void 0 ? void 0 : _a.user_id : null;
187034
+ }
187035
+ /** Returns the logged-in user's nicename (username) from localStorage, or null. */
187036
+ function getUserName() {
187037
+ var _a;
187038
+ const userData = readLocalStorage(LS_KEYS.USER_DATA);
187039
+ return userData && isJSON(userData) ? (_a = JSON.parse(userData)) === null || _a === void 0 ? void 0 : _a.user_nicename : null;
187040
+ }
187041
+ /**
187042
+ * Returns true if the current document is rendered inside an iframe. Safely
187043
+ * handles cross-origin access errors (throw → treated as embedded) and the
187044
+ * server-rendering case (no window → treated as embedded).
187045
+ */
187046
+ function inIframe() {
187047
+ try {
187048
+ return typeof window !== 'undefined' && window.self !== window.top;
187049
+ }
187050
+ catch (_a) {
187051
+ return true;
187052
+ }
187053
+ }
187054
+ /** Returns a path to a random bundled course placeholder image. */
187055
+ function getRandomCourseImage() {
187056
+ const courseImages = [
187057
+ '/images/courses/c1s.jpeg',
187058
+ '/images/courses/c2s.jpeg',
187059
+ '/images/courses/c3s.jpeg',
187060
+ '/images/courses/c4s.jpeg',
187061
+ '/images/courses/c5s.jpeg',
187062
+ '/images/courses/c6s.jpeg',
187063
+ '/images/courses/c7s.jpeg',
187064
+ '/images/courses/c8s.jpeg',
187065
+ ];
187066
+ return courseImages[Math.floor(Math.random() * courseImages.length)];
187067
+ }
187068
+ /**
187069
+ * Triggers the browser's print dialog scoped to a specific DOM element.
187070
+ * Injects a transient @media print stylesheet and cleans it up afterward.
187071
+ */
187072
+ function inBrowserPrint(layoutToPrint) {
187073
+ if (!layoutToPrint)
187074
+ return;
187075
+ layoutToPrint.classList.add('in-browser-printable');
187076
+ const style = document.createElement('style');
187077
+ style.innerHTML = `
187078
+ @media print {
187079
+ body * { visibility: hidden; }
187080
+ .in-browser-printable, .in-browser-printable * { visibility: visible; }
187081
+ .in-browser-printable {
187082
+ position: absolute;
187083
+ left: 0;
187084
+ top: 0;
187085
+ width: 100%;
187086
+ }
187087
+ }
187088
+ `;
187089
+ document.head.appendChild(style);
187090
+ window.print();
187091
+ document.head.removeChild(style);
187092
+ layoutToPrint.classList.remove('in-browser-printable');
187093
+ }
187094
+
187095
+ /**
187096
+ * Thin wrapper around useGetUserMetadataQuery that reads the username
187097
+ * from localStorage and skips the request when there is no logged-in
187098
+ * user.
187099
+ *
187100
+ * Ported from the skillsai SPA (hooks/users/use-usermetadata.tsx).
187101
+ */
187102
+ const useUserMetadata = () => {
187103
+ const username = getUserName();
187104
+ const { data: userMetaData, isLoading: userMetaDataLoading, isError: userMetaDataError, } = useGetUserMetadataQuery({ params: { username } }, { skip: !username });
187105
+ return { userMetaData, userMetaDataLoading, userMetaDataError };
187106
+ };
187107
+
187108
+ /**
187109
+ * Presentational avatar for the logged-in user. Pulls the user metadata
187110
+ * via the shared useUserMetadata hook and picks the best available
187111
+ * image source (uploaded profile image → Gravatar → initials fallback).
187112
+ *
187113
+ * Ported from the skillsai SPA (components/header/profile/user-avatar.tsx).
187114
+ * Note: `enableGravatarOnProfilePic` config is now passed in as a prop
187115
+ * rather than read from a skillsai-local config module.
187116
+ */
187117
+ const UserAvatar = ({ size = 32, containerClassName = '', enableGravatar = true, }) => {
187118
+ var _a, _b, _c, _d;
187119
+ const { userMetaData, userMetaDataLoading } = useUserMetadata();
187120
+ const data = userMetaData;
187121
+ const hasImage = !!((_a = data === null || data === void 0 ? void 0 : data.profile_image) === null || _a === void 0 ? void 0 : _a.has_image);
187122
+ 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) || '') })] })) }));
187123
+ };
187124
+
187125
+ /**
187126
+ * Presentational 7-day time-spent bar chart. Callers supply the data
187127
+ * (typically via useProfileTimeSpent) and a loading flag; this component
187128
+ * does no data fetching.
187129
+ *
187130
+ * Ported from the skillsai SPA (components/profile-time-chart.tsx),
187131
+ * refactored to be presentational.
187132
+ */
187133
+ const ProfileTimeChart = ({ data, loading = false }) => {
187134
+ const renderLegend = (props) => {
187135
+ var _a;
187136
+ const payload = (_a = props.payload) !== null && _a !== void 0 ? _a : [];
187137
+ 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}`))) }));
187138
+ };
187139
+ 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: {
187140
+ value: 'Minutes',
187141
+ angle: -90,
187142
+ position: 'insideLeft',
187143
+ style: { textAnchor: 'middle', fill: '#9CA3AF', fontSize: 12 },
187144
+ }, 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}`))) })] }) }) })) }));
187145
+ };
187146
+
187147
+ const skillLevels = [
187148
+ { name: 'Beginner', value: 0, description: 'basic knowledge' },
187149
+ { name: 'Novice', value: 100, description: 'limited experience' },
187150
+ { name: 'Intermediate', value: 200, description: 'practical application' },
187151
+ { name: 'Advanced', value: 300, description: 'applied theory' },
187152
+ { name: 'Expert', value: 400, description: 'recognized authority' },
187153
+ ];
187154
+ const generateCurveData = () => {
187155
+ const data = [];
187156
+ for (let i = 0; i <= 100; i += 5) {
187157
+ let value = i < 80 ? Math.pow(i, 1.5) * 3 : Math.pow(i, 1.8) * 1.5;
187158
+ value = Math.min(value, 500);
187159
+ let level = 'Beginner';
187160
+ let description = 'basic knowledge';
187161
+ if (i >= 80) {
187162
+ level = 'Expert';
187163
+ description = 'recognized authority';
187164
+ }
187165
+ else if (i >= 60) {
187166
+ level = 'Advanced';
187167
+ description = 'applied theory';
187168
+ }
187169
+ else if (i >= 40) {
187170
+ level = 'Intermediate';
187171
+ description = 'practical application';
187172
+ }
187173
+ else if (i >= 20) {
187174
+ level = 'Novice';
187175
+ description = 'limited experience';
187176
+ }
187177
+ data.push({ percentile: i, value, level, description });
187178
+ }
187179
+ return data;
187180
+ };
187181
+ /** @internal Exported for testing. */
187182
+ const CustomTooltip = ({ active, payload, label }) => {
187183
+ if (active && payload && payload.length) {
187184
+ 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}%` })] }));
187185
+ }
187186
+ return null;
187187
+ };
187188
+ /**
187189
+ * Presentational skill-leaderboard curve chart. Takes the user's raw
187190
+ * skill points as a prop and derives their percentile + level locally.
187191
+ * Ported verbatim from the skillsai SPA (components/skill-leaderboard-chart.tsx).
187192
+ */
187193
+ const SkillLeaderboardChart = ({ userSkillPoints = 0, }) => {
187194
+ const [data, setData] = React.useState([]);
187195
+ const [userPosition, setUserPosition] = React.useState(0);
187196
+ React.useEffect(() => {
187197
+ const skillPercentage = userSkillPoints / 500;
187198
+ if (Number.isNaN(skillPercentage)) {
187199
+ setUserPosition(0);
187200
+ }
187201
+ else if (skillPercentage > 1) {
187202
+ setUserPosition(100);
187203
+ }
187204
+ else {
187205
+ setUserPosition(skillPercentage * 100);
187206
+ }
187207
+ }, [userSkillPoints]);
187208
+ React.useEffect(() => {
187209
+ setData(generateCurveData());
187210
+ }, []);
187211
+ const getUserLevel = () => {
187212
+ if (userPosition >= 80)
187213
+ return 'Expert';
187214
+ if (userPosition >= 60)
187215
+ return 'Advanced';
187216
+ if (userPosition >= 40)
187217
+ return 'Intermediate';
187218
+ if (userPosition >= 20)
187219
+ return 'Novice';
187220
+ return 'Beginner';
187221
+ };
187222
+ const getUserPoints = () => {
187223
+ const userDataPoint = data.find((point) => point.percentile === userPosition) ||
187224
+ data.find((point) => point.percentile > userPosition) || {
187225
+ value: 0};
187226
+ return Math.round(userDataPoint.value);
187227
+ };
187228
+ 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: {
187229
+ value: 'Global Learner Percentile',
187230
+ position: 'bottom',
187231
+ offset: 0,
187232
+ dy: 30,
187233
+ } }), jsx(YAxis, { domain: [0, 500], label: {
187234
+ value: 'Skill Points',
187235
+ angle: -90,
187236
+ position: 'left',
187237
+ dx: -30,
187238
+ } }), 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: {
187239
+ value: level.description,
187240
+ position: 'bottom',
187241
+ fill: '#6B7280',
187242
+ fontSize: 10,
187243
+ dy: 15,
187244
+ } }, 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, "%"] })] })] }));
187245
+ };
187246
+
187247
+ /**
187248
+ * Skeleton row for the AddSkillDialog search results list. Ported from
187249
+ * the skillsai SPA (components/skeleton-add-skills-loading.tsx).
187250
+ */
187251
+ 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" })] }));
187252
+
187253
+ const DIALOG_TITLES = {
187254
+ earned: 'Add Earned Skill',
187255
+ desired: 'Add Desired Skill',
187256
+ 'self-reported': 'Add Self-Reported Skill',
187257
+ };
187258
+ /**
187259
+ * Presentational "add skill" dialog with a debounced search input and
187260
+ * single-select list of skill candidates.
187261
+ *
187262
+ * Ported from the skillsai SPA (components/add-skill-dialog.tsx),
187263
+ * refactored to take data and handlers via props. Callers are expected
187264
+ * to wire the `onSearch` → `useProfileSkills().handleFetchAllSkills`
187265
+ * link and the `onAddSkill` → `handleSkillsUpdate` link themselves.
187266
+ */
187267
+ const AddSkillDialog = ({ open, onOpenChange, type, fetchedSkills, isFetchingSkills, isFetchingSkillsError, updatingSkill, onSearch, onAddSkill, searchDebounceMs = 500, }) => {
187268
+ const [searchQuery, setSearchQuery] = React.useState('');
187269
+ const [selectedSkill, setSelectedSkill] = React.useState(null);
187270
+ const searchInputRef = React.useRef(null);
187271
+ const debounceTimer = React.useRef(null);
187272
+ // Debounced search: reset timer on every keystroke.
187273
+ React.useEffect(() => {
187274
+ if (debounceTimer.current)
187275
+ clearTimeout(debounceTimer.current);
187276
+ debounceTimer.current = setTimeout(() => {
187277
+ onSearch(searchQuery);
187278
+ }, searchDebounceMs);
187279
+ return () => {
187280
+ if (debounceTimer.current)
187281
+ clearTimeout(debounceTimer.current);
187282
+ };
187283
+ // eslint-disable-next-line react-hooks/exhaustive-deps
187284
+ }, [searchQuery]);
187285
+ // Focus the search input when the dialog opens.
187286
+ React.useEffect(() => {
187287
+ if (open && searchInputRef.current) {
187288
+ const t = setTimeout(() => { var _a; return (_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 100);
187289
+ return () => clearTimeout(t);
187290
+ }
187291
+ }, [open]);
187292
+ // Reset local state when the dialog closes.
187293
+ React.useEffect(() => {
187294
+ if (!open) {
187295
+ setSearchQuery('');
187296
+ setSelectedSkill(null);
187297
+ }
187298
+ }, [open]);
187299
+ const handleAddSkill = () => {
187300
+ if (updatingSkill || !selectedSkill)
187301
+ return;
187302
+ onAddSkill(selectedSkill);
187303
+ };
187304
+ 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) ||
187305
+ (!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 &&
187306
+ !isFetchingSkillsError &&
187307
+ fetchedSkills.map((row, index) => {
187308
+ const skill = row === null || row === void 0 ? void 0 : row.data;
187309
+ 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;
187310
+ return (jsxs("div", { onClick: () => skill && setSelectedSkill(skill), className: `flex cursor-pointer items-center rounded-md border p-3 transition-colors ${isSelected
187311
+ ? 'border-amber-500 bg-amber-50'
187312
+ : '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}`));
187313
+ })] })] }), 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
187314
+ ? 'bg-gradient-to-r from-[var(--button-primary-gradient-from)] to-[var(--button-primary-gradient-to)] hover:opacity-[var(--button-primary-hover-opacity)]'
187315
+ : 'cursor-not-allowed bg-gray-300'}`, children: updatingSkill ? 'Submitting...' : 'Add Skill' }) })] }) }));
187316
+ };
187317
+
187318
+ /**
187319
+ * Skeleton row for the CreatePathwayModal content search list. Ported
187320
+ * from the skillsai SPA (components/skeleton-create-pathway-search-list.tsx).
187321
+ */
187322
+ 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" })] }) }));
187323
+
187324
+ /**
187325
+ * Skeleton row for the EducationBox / ExperienceBox lists. Ported from
187326
+ * the skillsai SPA (components/profile/skeleton-education-box.tsx).
187327
+ */
187328
+ 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" }) })] }));
187329
+
187330
+ 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>';
187331
+ const CredentialMiniBox = ({ credential, onClick = () => { }, minified = false, iconSize = 48, defaultImage = FALLBACK_CREDENTIAL_IMG, }) => {
187332
+ var _a, _b, _c, _d;
187333
+ 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') : '-'] })] })] }));
187334
+ };
187335
+
187336
+ /**
187337
+ * Presentational credentials grid for the public profile page. Caller
187338
+ * supplies the credentials list + loading flags (typically from the
187339
+ * useProfileCredentials hook).
187340
+ *
187341
+ * Ported from the skillsai SPA (components/profile/credential-box.tsx).
187342
+ */
187343
+ const CredentialBox = ({ credentials, isLoading = false, isError = false, }) => {
187344
+ const isEmpty = !isLoading && !isError && credentials.length === 0;
187345
+ 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 &&
187346
+ !isError &&
187347
+ credentials.length > 0 &&
187348
+ credentials.map((credential) => (jsx(CredentialMiniBox, { credential: credential }, credential.entityId)))] })] }));
187349
+ };
187350
+
187351
+ const formatYearRange$1 = (start, end) => {
187352
+ if (!start && !end)
187353
+ return '';
187354
+ const startYear = start ? dayjs(start).format('YYYY') : '';
187355
+ const endYear = end ? dayjs(end).format('YYYY') : 'Present';
187356
+ if (start && end && startYear === endYear)
187357
+ return endYear;
187358
+ return `${startYear} - ${endYear}`;
187359
+ };
187360
+ /**
187361
+ * Presentational education list for the public profile page. The
187362
+ * skillsai SPA also rendered an inline EducationDialog and
187363
+ * AddInstitutionDialog — both are caller-managed now (the caller can
187364
+ * open them in response to onEditEducation).
187365
+ *
187366
+ * Ported from the skillsai SPA (components/profile/education-box.tsx).
187367
+ */
187368
+ const EducationBox = ({ education = [], isLoading = false, isError = false, onEditEducation, }) => {
187369
+ const isEmpty = !isLoading && !isError && education.length === 0;
187370
+ 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 &&
187371
+ !isError &&
187372
+ education.map((entry, index) => {
187373
+ var _a;
187374
+ 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));
187375
+ })] })] }));
187376
+ };
187377
+
187378
+ const formatYearRange = (start, end) => {
187379
+ if (!start && !end)
187380
+ return '';
187381
+ const startYear = start ? dayjs(start).format('YYYY') : '';
187382
+ const endYear = end ? dayjs(end).format('YYYY') : 'Present';
187383
+ if (start && end && startYear === endYear)
187384
+ return endYear;
187385
+ return `${startYear} - ${endYear}`;
187386
+ };
187387
+ /**
187388
+ * Presentational work-experience list for the public profile page.
187389
+ *
187390
+ * Ported from the skillsai SPA (components/profile/experience-box.tsx).
187391
+ */
187392
+ const ExperienceBox = ({ experience = [], isLoading = false, isError = false, onEditExperience, }) => {
187393
+ const isEmpty = !isLoading && !isError && experience.length === 0;
187394
+ 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 &&
187395
+ !isError &&
187396
+ experience.map((entry, index) => {
187397
+ var _a;
187398
+ 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));
187399
+ })] })] }));
187400
+ };
187401
+
187402
+ const isEmpty$1 = (xs) => !xs || xs.length === 0;
187403
+ /**
187404
+ * Presentational skills list for the public profile page. Splits the
187405
+ * three skill buckets (earned, self-reported, desired) into their own
187406
+ * sections, each with its own loading skeleton and empty state.
187407
+ *
187408
+ * Ported from the skillsai SPA (components/profile/skills-box.tsx).
187409
+ */
187410
+ const SkillsBox = ({ earnedSkills, earnedSkillsLoading = false, earnedSkillsError = false, selfReportedSkills, selfReportedSkillsLoading = false, selfReportedSkillsError = false, desiredSkills, desiredSkillsLoading = false, desiredSkillsError = false, }) => {
187411
+ var _a, _b, _c;
187412
+ 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 &&
187413
+ !earnedSkillsError &&
187414
+ ((_a = earnedSkills === null || earnedSkills === void 0 ? void 0 : earnedSkills.resources) === null || _a === void 0 ? void 0 : _a.map((skill, index) => (jsx(SkillBox, { onSkillClick: () => { }, skill: {
187415
+ name: skill.name || '',
187416
+ level: skill.points || 0,
187417
+ starred: false,
187418
+ } }, index))))] })] }), jsxs("div", { className: "mb-6", children: [jsx("h3", { className: "mb-4 text-base font-medium text-gray-700", children: "Self-Reported" }), !selfReportedSkillsLoading &&
187419
+ (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 &&
187420
+ !selfReportedSkillsError &&
187421
+ ((_b = selfReportedSkills === null || selfReportedSkills === void 0 ? void 0 : selfReportedSkills.skills) === null || _b === void 0 ? void 0 : _b.map((skill, index) => {
187422
+ var _a, _b;
187423
+ return (jsx(SkillBox, { skill: {
187424
+ name: skill.name || '',
187425
+ 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,
187426
+ starred: true,
187427
+ } }, index));
187428
+ }))] })] }), 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 &&
187429
+ !desiredSkillsError &&
187430
+ ((_c = desiredSkills === null || desiredSkills === void 0 ? void 0 : desiredSkills.skills) === null || _c === void 0 ? void 0 : _c.map((skill, index) => {
187431
+ var _a, _b;
187432
+ return (jsx(SkillBox, { showRating: false, skill: {
187433
+ name: skill.name || '',
187434
+ 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,
187435
+ starred: true,
187436
+ } }, index));
187437
+ }))] })] })] }));
187438
+ };
187439
+
187440
+ try {
187441
+ __webpack_exports__GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${__webpack_exports__version}/build/pdf.worker.min.mjs`;
187442
+ }
187443
+ catch (_a) {
187444
+ // pdfjs may not be configurable in non-browser environments; ignore silently.
187445
+ }
187446
+ const ResumeBoxSkeleton = () => (jsx("div", { className: "h-[200px] w-full animate-pulse rounded-lg bg-gray-200" }));
187447
+ /**
187448
+ * Presentational PDF resume viewer with paging, zoom, and rotation
187449
+ * controls. Uses react-pdf, so it must run client-side.
187450
+ *
187451
+ * Ported from the skillsai SPA (components/profile/resume-box.tsx).
187452
+ */
187453
+ const ResumeBox = ({ resumeUrl, isLoading = false, isError = false, }) => {
187454
+ const [resumeLoadError, setResumeLoadError] = React.useState(false);
187455
+ const [numPages, setNumPages] = React.useState(null);
187456
+ const [pageNumber, setPageNumber] = React.useState(1);
187457
+ const [scale, setScale] = React.useState(1.0);
187458
+ const [rotation, setRotation] = React.useState(0);
187459
+ const onDocumentLoadSuccess = ({ numPages }) => {
187460
+ setNumPages(numPages);
187461
+ };
187462
+ const onDocumentLoadError = () => setResumeLoadError(true);
187463
+ const goToPrevPage = () => setPageNumber((p) => p - 1);
187464
+ const goToNextPage = () => setPageNumber((p) => p + 1);
187465
+ const zoomIn = () => setScale((s) => s + 0.2);
187466
+ const zoomOut = () => setScale((s) => (s > 0.4 ? s - 0.2 : s));
187467
+ const rotateLeft = () => setRotation((r) => r - 90);
187468
+ const rotateRight = () => setRotation((r) => r + 90);
187469
+ 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" })] }));
187470
+ };
187471
+
187472
+ /**
187473
+ * Wrapper around the course-metadata RTK endpoints that exposes
187474
+ * promise-returning fetch helpers for use inside effects/callbacks.
187475
+ *
187476
+ * Ported from the skillsai SPA (hooks/courses/use-course-metadata.ts).
187477
+ */
187478
+ const useCourseMetadata = () => {
187479
+ const [getCourseMetaData, { isLoading, isError }] = useLazyGetCourseMetaDataQuery();
187480
+ const [getCourseCompletionOutlines, { isLoading: isLoadingCompletionOutlines, isError: isErrorCompletionOutlines },] = useLazyGetCourseCompletionOutlinesQuery();
187481
+ const [getCourseEligibility] = useLazyGetCourseEligibilityQuery();
187482
+ const handleFetchCourseMetaData = async (courseKey) => {
187483
+ try {
187484
+ const { data: courseMetaData } = await getCourseMetaData({ courseKey }, true);
187485
+ return courseMetaData;
187486
+ }
187487
+ catch (_a) {
187488
+ return undefined;
187489
+ }
187490
+ };
187491
+ const handleFetchCourseEligibility = async (courseKey) => {
187492
+ try {
187493
+ const { data: courseEligibility } = await getCourseEligibility({ courseKey });
187494
+ return courseEligibility || null;
187495
+ }
187496
+ catch (_a) {
187497
+ return null;
187498
+ }
187499
+ };
187500
+ const handleFetchCourseCompletionOutlines = async (courseKey) => {
187501
+ try {
187502
+ const { data: courseCompletionOutlines } = await getCourseCompletionOutlines({ courseKey });
187503
+ return courseCompletionOutlines;
187504
+ }
187505
+ catch (_a) {
187506
+ return undefined;
187507
+ }
187508
+ };
187509
+ return {
187510
+ handleFetchCourseMetaData,
187511
+ handleFetchCourseCompletionOutlines,
187512
+ handleFetchCourseEligibility,
187513
+ isLoading,
187514
+ isError,
187515
+ isLoadingCompletionOutlines,
187516
+ isErrorCompletionOutlines,
187517
+ };
187518
+ };
187519
+
187520
+ /**
187521
+ * Fetches the current user's enrolled or assigned courses (paged), then
187522
+ * hydrates each row with course metadata from the LMS. Search can either
187523
+ * hit the API or filter client-side on the already-fetched page.
187524
+ *
187525
+ * Ported from the skillsai SPA (hooks/courses/use-user-courses.ts).
187526
+ */
187527
+ const useUserCourses = ({ limit = 8, search = '', page = 1, courseType = 'enrolled', useAPISearch = false, }) => {
187528
+ const { metadata } = useTenantMetadata({ org: getTenant() });
187529
+ const [getUserEnrolledCourses, { isLoading: isLoadingEnrolledCourses, isError: errorEnrolledCourses },] = useLazyGetUserEnrolledCoursesQuery();
187530
+ const [getUserAssignedCourses, { isLoading: isLoadingAssignedCourses, isError: errorAssignedCourses },] = useLazyGetUserAssignedCoursesQuery();
187531
+ const { handleFetchCourseMetaData } = useCourseMetadata();
187532
+ const [userCoursesWithMetaData, setUserCoursesWithMetaData] = useState([]);
187533
+ const [filteredCoursesWithMetaData, setFilteredCoursesWithMetaData] = useState([]);
187534
+ const [isLoadingCoursesMetaData, setIsLoadingCoursesMetaData] = useState(false);
187535
+ const [pagination, setPagination] = useState(null);
187536
+ const handleFetchAllCoursesMetaData = async (enrolledCourses) => {
187537
+ try {
187538
+ const coursesMetaData = await Promise.all(enrolledCourses.map((course) => handleFetchCourseMetaData(course.course_id)));
187539
+ const coursesWithMetaData = enrolledCourses.map((course, index) => ({
187540
+ ...course,
187541
+ name: course.course_name,
187542
+ edx_data: coursesMetaData[index],
187543
+ }));
187544
+ setUserCoursesWithMetaData(coursesWithMetaData);
187545
+ setFilteredCoursesWithMetaData(coursesWithMetaData);
187546
+ }
187547
+ catch (_a) {
187548
+ setUserCoursesWithMetaData([]);
187549
+ setFilteredCoursesWithMetaData([]);
187550
+ }
187551
+ finally {
187552
+ setIsLoadingCoursesMetaData(false);
187553
+ }
187554
+ };
187555
+ const handleFetchCourses = async () => {
187556
+ try {
187557
+ setUserCoursesWithMetaData([]);
187558
+ setFilteredCoursesWithMetaData([]);
187559
+ setIsLoadingCoursesMetaData(true);
187560
+ if (courseType === 'assigned') {
187561
+ const userId = getUserId();
187562
+ if (userId == null) {
187563
+ setIsLoadingCoursesMetaData(false);
187564
+ return;
187565
+ }
187566
+ const { data: assignedCourses } = await getUserAssignedCourses({
187567
+ user_id: userId,
187568
+ query: {
187569
+ page_size: limit,
187570
+ ...(search && { search }),
187571
+ ...(page && { page }),
187572
+ },
187573
+ }, true);
187574
+ if (Array.isArray(assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.results)) {
187575
+ setPagination({
187576
+ count: (assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.count) || 0,
187577
+ current_page: page,
187578
+ total_pages: Math.ceil(((assignedCourses === null || assignedCourses === void 0 ? void 0 : assignedCourses.count) || 0) / limit),
187579
+ });
187580
+ await handleFetchAllCoursesMetaData(assignedCourses.results);
187581
+ }
187582
+ else {
187583
+ setIsLoadingCoursesMetaData(false);
187584
+ }
187585
+ }
187586
+ else {
187587
+ const username = getUserName();
187588
+ if (!username) {
187589
+ setIsLoadingCoursesMetaData(false);
187590
+ return;
187591
+ }
187592
+ const { data: enrolledCourses } = await getUserEnrolledCourses({
187593
+ username,
187594
+ query: {
187595
+ page_size: limit,
187596
+ ...(search && { search }),
187597
+ ...(page && { page }),
187598
+ platform_key: getTenant(),
187599
+ ...((metadata === null || metadata === void 0 ? void 0 : metadata.skills_include_community_courses) && { include_default_platform: 1 }),
187600
+ },
187601
+ }, true);
187602
+ if (Array.isArray(enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.results)) {
187603
+ setPagination({
187604
+ count: (enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.count) || 0,
187605
+ current_page: page,
187606
+ total_pages: Math.ceil(((enrolledCourses === null || enrolledCourses === void 0 ? void 0 : enrolledCourses.count) || 0) / limit),
187607
+ });
187608
+ await handleFetchAllCoursesMetaData(enrolledCourses.results);
187609
+ }
187610
+ else {
187611
+ setIsLoadingCoursesMetaData(false);
187612
+ }
187613
+ }
187614
+ }
187615
+ catch (_a) {
187616
+ setUserCoursesWithMetaData([]);
187617
+ setFilteredCoursesWithMetaData([]);
187618
+ setIsLoadingCoursesMetaData(false);
187619
+ }
187620
+ };
187621
+ const handleInPageSearch = () => {
187622
+ if (search.length > 2) {
187623
+ const filtered = userCoursesWithMetaData.filter((course) => course.name.toLowerCase().includes(search.toLowerCase()));
187624
+ setFilteredCoursesWithMetaData(filtered);
187625
+ }
187626
+ else {
187627
+ setFilteredCoursesWithMetaData(userCoursesWithMetaData);
187628
+ }
187629
+ };
187630
+ useEffect(() => {
187631
+ if (!useAPISearch) {
187632
+ handleInPageSearch();
187633
+ }
187634
+ else {
187635
+ handleFetchCourses();
187636
+ }
187637
+ }, [search, useAPISearch]);
187638
+ useEffect(() => {
187639
+ handleFetchCourses();
187640
+ }, [
187641
+ courseType,
187642
+ page,
187643
+ metadata === null || metadata === void 0 ? void 0 : metadata.skills_include_community_courses,
187644
+ ]);
187645
+ return {
187646
+ userCourses: filteredCoursesWithMetaData,
187647
+ isLoadingUserCourses: isLoadingEnrolledCourses || isLoadingAssignedCourses || isLoadingCoursesMetaData,
187648
+ errorUserCourses: errorEnrolledCourses || errorAssignedCourses,
187649
+ pagination,
187650
+ };
187651
+ };
187652
+
187653
+ /**
187654
+ * Fetches the logged-in user's credential assertions and maintains a
187655
+ * client-side filtered list based on a search string.
187656
+ *
187657
+ * Ported from the skillsai SPA (hooks/profile/use-profile-credentials.ts).
187658
+ */
187659
+ const useProfileCredentials = ({ search = '', maxCredentials, }) => {
187660
+ const [getUserCredentials, { isLoading, isError }] = useLazyGetUserCredentialsQuery();
187661
+ const [filteredCredentials, setFilteredCredentials] = useState([]);
187662
+ const [fetchedCredentials, setFetchedCredentials] = useState([]);
187663
+ const [credentialsLoading, setCredentialsLoading] = useState(false);
187664
+ const handleFetchCredentials = async () => {
187665
+ var _a, _b, _c;
187666
+ try {
187667
+ setCredentialsLoading(true);
187668
+ const response = await getUserCredentials({
187669
+ platformKey: getTenant(),
187670
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
187671
+ ...(maxCredentials && { pageSize: maxCredentials }),
187672
+ }, true);
187673
+ 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 : []);
187674
+ setFetchedCredentials(data);
187675
+ setFilteredCredentials(data);
187676
+ }
187677
+ catch (_d) {
187678
+ setFetchedCredentials([]);
187679
+ setFilteredCredentials([]);
187680
+ }
187681
+ finally {
187682
+ setCredentialsLoading(false);
187683
+ }
187684
+ };
187685
+ useEffect(() => {
187686
+ handleFetchCredentials();
187687
+ }, []);
187688
+ useEffect(() => {
187689
+ if (search.length > 2) {
187690
+ setFilteredCredentials(fetchedCredentials.filter((credential) => {
187691
+ var _a;
187692
+ return String(((_a = credential === null || credential === void 0 ? void 0 : credential.credentialDetails) === null || _a === void 0 ? void 0 : _a.name) || '')
187693
+ .toLowerCase()
187694
+ .includes(search.toLowerCase());
187695
+ }));
187696
+ }
187697
+ else {
187698
+ setFilteredCredentials(fetchedCredentials);
187699
+ }
187700
+ }, [search, fetchedCredentials]);
187701
+ return {
187702
+ filteredCredentials,
187703
+ fetchedCredentials,
187704
+ isLoading: isLoading || credentialsLoading,
187705
+ isError,
187706
+ };
187707
+ };
187708
+
187709
+ var duration$1 = {exports: {}};
187710
+
187711
+ (function (module, exports) {
187712
+ !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)};}}));
187713
+ } (duration$1));
187714
+
187715
+ var durationExports = duration$1.exports;
187716
+ var duration = /*@__PURE__*/getDefaultExportFromCjs(durationExports);
187717
+
187718
+ dayjs.extend(duration);
187719
+ /**
187720
+ * Fetches the last 7 days of learning activity for the logged-in user
187721
+ * and returns it as a list of { date, minutes } rows ready for a chart.
187722
+ *
187723
+ * Ported from the skillsai SPA (hooks/profile/use-profile-timespent.ts).
187724
+ */
187725
+ const useProfileTimeSpent = () => {
187726
+ const [timeSpent, setTimeSpent] = useState([]);
187727
+ const [timeSpentLoading, setTimeSpentLoading] = useState(false);
187728
+ const [getOverTimeActivity] = useLazyGetOverTimeActivityQuery();
187729
+ const handleTimeSpentStats = async () => {
187730
+ var _a, _b;
187731
+ setTimeSpentLoading(true);
187732
+ try {
187733
+ const response = await getOverTimeActivity([
187734
+ {
187735
+ org: getTenant(),
187736
+ userId: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
187737
+ startDate: dayjs().subtract(6, 'day').format('YYYY-MM-DD'),
187738
+ endDate: dayjs().format('YYYY-MM-DD'),
187739
+ format: 'json',
187740
+ },
187741
+ ], true);
187742
+ const data = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.data;
187743
+ if (!data || Object.keys(data).length === 0) {
187744
+ throw new Error('no data');
187745
+ }
187746
+ setTimeSpent(Object.entries(data).map(([date, seconds]) => ({
187747
+ date: dayjs(date).format('ddd DD/MM/YY'),
187748
+ minutes: dayjs.duration(seconds, 'seconds').asMinutes(),
187749
+ })));
187750
+ }
187751
+ catch (_c) {
187752
+ setTimeSpent([]);
187753
+ }
187754
+ finally {
187755
+ setTimeSpentLoading(false);
187756
+ }
187757
+ };
187758
+ useEffect(() => {
187759
+ handleTimeSpentStats();
187760
+ }, []);
187761
+ return { timeSpent, timeSpentLoading };
187762
+ };
187763
+
187764
+ /**
187765
+ * Thin wrapper around the catalog-search RTK endpoint that exposes a
187766
+ * promise-returning helper for ad-hoc searches inside effects/callbacks.
187767
+ *
187768
+ * Ported from the skillsai SPA (hooks/search/use-catalog-search.ts).
187769
+ */
187770
+ const useCatalogSearch = () => {
187771
+ const [getCatalogSearch, { isLoading, isError }] = useLazyGetCatalogSearchQuery();
187772
+ const handleSearch = async (params) => {
187773
+ try {
187774
+ const response = await getCatalogSearch([{ ...params }], true);
187775
+ return response;
187776
+ }
187777
+ catch (error) {
187778
+ console.error(JSON.stringify(error));
187779
+ return null;
187780
+ }
187781
+ };
187782
+ return { handleSearch, isLoading, isError };
187783
+ };
187784
+
187785
+ /**
187786
+ * Aggregates the logged-in user's earned, self-reported, and desired
187787
+ * skills, plus mutations to create/update/delete self-reported and
187788
+ * desired skills. Also exposes a `handleFetchAllSkills` helper that
187789
+ * queries the catalog search endpoint for a skills list.
187790
+ *
187791
+ * Ported from the skillsai SPA (hooks/profile/use-profile-skills.ts).
187792
+ */
187793
+ const useProfileSkills = (showToast = true) => {
187794
+ const { data: earnedSkills, isLoading: earnedSkillsLoading, isError: earnedSkillsError, isSuccess: earnedSkillsSuccess, } = useGetUserEarnedSkillsQuery([
187795
+ { org: getTenant(), userId: getUserName() },
187796
+ ]);
187797
+ const { data: selfReportedSkills, isLoading: selfReportedSkillsLoading, isError: selfReportedSkillsError, isSuccess: selfReportedSkillsSuccess, } = useGetUserReportedSkillsQuery([
187798
+ { userId: getUserId(), username: getUserName() },
187799
+ ]);
187800
+ const { data: desiredSkills, isLoading: desiredSkillsLoading, isError: desiredSkillsError, isSuccess: desiredSkillsSuccess, } = useGetUserDesiredSkillsQuery([
187801
+ { userId: getUserId(), username: getUserName() },
187802
+ ]);
187803
+ const { handleSearch, isLoading: isFetchingSkills, isError: isFetchingSkillsError, } = useCatalogSearch();
187804
+ const [updatingSkill, setUpdatingSkill] = useState(false);
187805
+ const [deletingSkill, setDeletingSkill] = useState(false);
187806
+ const [fetchedSkills, setFetchedSkills] = useState([]);
187807
+ const handleFetchAllSkills = async (searchQuery) => {
187808
+ var _a, _b;
187809
+ try {
187810
+ const result = await handleSearch({
187811
+ content: ['skills'],
187812
+ tenant: getTenant(),
187813
+ ...(searchQuery && { query: searchQuery }),
187814
+ });
187815
+ if (!result) {
187816
+ throw new Error('Failed to fetch skills');
187817
+ }
187818
+ 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 : [];
187819
+ setFetchedSkills(items);
187820
+ }
187821
+ catch (_c) {
187822
+ toast.error('Failed to fetch skills');
187823
+ setFetchedSkills([]);
187824
+ }
187825
+ };
187826
+ const [createOrUpdateUserReportedSkill, { isError: isCreatingOrUpdateUserReportedSkillError }] = useCreateOrUpdateUserReportedSkillMutation();
187827
+ const [createOrUpdateUserDesiredSkill, { isError: isCreatingOrUpdateUserDesiredSkillError }] = useCreateOrUpdateUserDesiredSkillMutation();
187828
+ const handleSkillsDeletion = async (skill, allSkills, callback) => {
187829
+ var _a, _b, _c, _d, _e, _f;
187830
+ setDeletingSkill(true);
187831
+ if (skill.type === 'self-reported') {
187832
+ const reportedSkillIndex = (_a = allSkills.selfReported) === null || _a === void 0 ? void 0 : _a.skills.findIndex((s) => s.name === skill.name);
187833
+ const updatedReportedLevel = (_b = allSkills.selfReported) === null || _b === void 0 ? void 0 : _b.data.level.filter((_, index) => index !== reportedSkillIndex);
187834
+ await createOrUpdateUserReportedSkill([
187835
+ {
187836
+ requestBody: {
187837
+ skills: ((_c = allSkills.selfReported) === null || _c === void 0 ? void 0 : _c.skills.filter((s) => s.name !== skill.name)) || [],
187838
+ data: { level: updatedReportedLevel },
187839
+ user_id: getUserId(),
187840
+ username: getUserName(),
187841
+ },
187842
+ },
187843
+ ]);
187844
+ if (isCreatingOrUpdateUserReportedSkillError) {
187845
+ toast.error('Failed to delete skill');
187846
+ return;
187847
+ }
187848
+ toast.success('Skill deleted successfully');
187849
+ setTimeout(() => {
187850
+ setDeletingSkill(false);
187851
+ callback === null || callback === void 0 ? void 0 : callback();
187852
+ }, 1000);
187853
+ }
187854
+ else if (skill.type === 'desired') {
187855
+ const desiredSkillIndex = (_d = allSkills.desired) === null || _d === void 0 ? void 0 : _d.skills.findIndex((s) => s.name === skill.name);
187856
+ const updatedDesiredLevel = (_e = allSkills.desired) === null || _e === void 0 ? void 0 : _e.data.level.filter((_, index) => index !== desiredSkillIndex);
187857
+ await createOrUpdateUserDesiredSkill([
187858
+ {
187859
+ requestBody: {
187860
+ skills: ((_f = allSkills.desired) === null || _f === void 0 ? void 0 : _f.skills.filter((s) => s.name !== skill.name)) || [],
187861
+ data: { level: updatedDesiredLevel },
187862
+ user_id: getUserId(),
187863
+ username: getUserName(),
187864
+ },
187865
+ },
187866
+ ]);
187867
+ if (isCreatingOrUpdateUserDesiredSkillError) {
187868
+ toast.error('Failed to delete skill');
187869
+ return;
187870
+ }
187871
+ toast.success('Skill deleted successfully');
187872
+ setTimeout(() => {
187873
+ setDeletingSkill(false);
187874
+ callback === null || callback === void 0 ? void 0 : callback();
187875
+ }, 1000);
187876
+ }
187877
+ };
187878
+ const handleSkillsUpdate = async (skill, allSkills, callback) => {
187879
+ var _a, _b, _c, _d, _e, _f, _g, _h;
187880
+ setUpdatingSkill(true);
187881
+ if (skill.type === 'self-reported') {
187882
+ const reportedSkillIndex = (_a = allSkills.selfReported) === null || _a === void 0 ? void 0 : _a.skills.findIndex((s) => s.name === skill.name);
187883
+ const updatedReportedLevel = [...((_c = (_b = allSkills.selfReported) === null || _b === void 0 ? void 0 : _b.data.level) !== null && _c !== void 0 ? _c : [])];
187884
+ if (reportedSkillIndex !== undefined &&
187885
+ reportedSkillIndex > -1 &&
187886
+ skill.level !== undefined) {
187887
+ updatedReportedLevel[reportedSkillIndex] = skill.level;
187888
+ }
187889
+ await createOrUpdateUserReportedSkill([
187890
+ {
187891
+ requestBody: {
187892
+ skills: ((_d = allSkills.selfReported) === null || _d === void 0 ? void 0 : _d.skills) || [],
187893
+ data: { level: updatedReportedLevel },
187894
+ user_id: getUserId(),
187895
+ username: getUserName(),
187896
+ },
187897
+ },
187898
+ ]);
187899
+ if (isCreatingOrUpdateUserReportedSkillError) {
187900
+ toast.error('Failed to update skill');
187901
+ return;
187902
+ }
187903
+ toast.success('Skill updated successfully');
187904
+ setTimeout(() => {
187905
+ setUpdatingSkill(false);
187906
+ callback === null || callback === void 0 ? void 0 : callback();
187907
+ }, 1000);
187908
+ }
187909
+ else if (skill.type === 'desired') {
187910
+ const desiredSkillIndex = (_e = allSkills.desired) === null || _e === void 0 ? void 0 : _e.skills.findIndex((s) => s.name === skill.name);
187911
+ const updatedDesiredLevel = [...((_g = (_f = allSkills.desired) === null || _f === void 0 ? void 0 : _f.data.level) !== null && _g !== void 0 ? _g : [])];
187912
+ if (desiredSkillIndex !== undefined && desiredSkillIndex > -1 && skill.level !== undefined) {
187913
+ updatedDesiredLevel[desiredSkillIndex] = skill.level;
187914
+ }
187915
+ await createOrUpdateUserDesiredSkill([
187916
+ {
187917
+ requestBody: {
187918
+ skills: ((_h = allSkills.desired) === null || _h === void 0 ? void 0 : _h.skills) || [],
187919
+ data: { level: updatedDesiredLevel },
187920
+ user_id: getUserId(),
187921
+ username: getUserName(),
187922
+ },
187923
+ },
187924
+ ]);
187925
+ if (isCreatingOrUpdateUserDesiredSkillError) {
187926
+ toast.error('Failed to update skill');
187927
+ return;
187928
+ }
187929
+ toast.success('Skill updated successfully');
187930
+ setTimeout(() => {
187931
+ setUpdatingSkill(false);
187932
+ callback === null || callback === void 0 ? void 0 : callback();
187933
+ }, 1000);
187934
+ }
187935
+ };
187936
+ const handleSkillsCreate = async (skills) => {
187937
+ try {
187938
+ await createOrUpdateUserReportedSkill([
187939
+ { requestBody: skills, userId: getUserId(), username: getUserName() },
187940
+ ]);
187941
+ if (isCreatingOrUpdateUserReportedSkillError) {
187942
+ throw new Error('Failed to create skills');
187943
+ }
187944
+ if (showToast) {
187945
+ toast.success('Skills created successfully');
187946
+ }
187947
+ return true;
187948
+ }
187949
+ catch (_a) {
187950
+ toast.error('Failed to create skills');
187951
+ return false;
187952
+ }
187953
+ };
187954
+ return {
187955
+ earnedSkills,
187956
+ earnedSkillsLoading,
187957
+ earnedSkillsError,
187958
+ earnedSkillsSuccess,
187959
+ selfReportedSkills,
187960
+ selfReportedSkillsLoading,
187961
+ selfReportedSkillsError,
187962
+ selfReportedSkillsSuccess,
187963
+ desiredSkills,
187964
+ desiredSkillsLoading,
187965
+ desiredSkillsError,
187966
+ desiredSkillsSuccess,
187967
+ handleSkillsDeletion,
187968
+ updatingSkill,
187969
+ deletingSkill,
187970
+ setUpdatingSkill,
187971
+ handleSkillsUpdate,
187972
+ handleSkillsCreate,
187973
+ handleFetchAllSkills,
187974
+ fetchedSkills,
187975
+ isFetchingSkills,
187976
+ isFetchingSkillsError,
187977
+ };
187978
+ };
187979
+
187980
+ /**
187981
+ * Fetches the logged-in user's catalog / assigned / enrolled pathways and
187982
+ * hydrates each with its completion stats. Supports client-side search
187983
+ * filtering on pathway name.
187984
+ *
187985
+ * Ported from the skillsai SPA (hooks/profile/use-profile-pathways.ts).
187986
+ */
187987
+ const useProfilePathways = ({ searchQuery, contentType = 'catalog', lmsUrl, }) => {
187988
+ const [isLoading, setIsLoading] = useState(false);
187989
+ const [getPathwayList, { isError: isCatalogPathwaysError }] = useLazyGetPathwayListQuery();
187990
+ const [getUserAssignedPathways, { isError: isAssignedPathwaysError }] = useLazyGetUserAssignedPathwaysQuery();
187991
+ const [getUserEnrolledPathways, { isError: isEnrolledPathwaysError }] = useLazyGetUserEnrolledPathwaysQuery();
187992
+ const [getPathwayCompletion] = useLazyGetPathwayCompletionQuery();
187993
+ const [pathways, setPathways] = useState([]);
187994
+ const [filteredPathways, setFilteredPathways] = useState([]);
187995
+ const [pathwayCompletions, setPathwayCompletions] = useState([]);
187996
+ const [pathwayCompletionsLoading, setPathwayCompletionsLoading] = useState(false);
187997
+ const [isError, setIsError] = useState(false);
187998
+ const handleFetchSinglePathwayEnrollmentStatus = async (pathway) => {
187999
+ try {
188000
+ const response = await getUserEnrolledPathways([
188001
+ { username: getUserName(), pathwayUuid: pathway.pathway_uuid || '' },
188002
+ ], true);
188003
+ const data = response.data;
188004
+ return (Array.isArray(data) &&
188005
+ data.findIndex((pre) => pre.active && pre.pathway_uuid === pathway.pathway_uuid) !== -1);
188006
+ }
188007
+ catch (_a) {
188008
+ return false;
188009
+ }
188010
+ };
188011
+ const handleFetchPathwayCompletions = async (list) => {
188012
+ setPathwayCompletionsLoading(true);
188013
+ try {
188014
+ const completions = await Promise.all(list.map(async (pathway) => {
188015
+ const response = await getPathwayCompletion([
188016
+ { pathwayUuid: pathway.pathway_uuid || '', username: getUserName() },
188017
+ ]);
188018
+ return (response.data || {});
188019
+ }));
188020
+ setPathwayCompletions(completions);
188021
+ }
188022
+ catch (error) {
188023
+ console.error(JSON.stringify(error));
188024
+ setPathwayCompletions([]);
188025
+ }
188026
+ finally {
188027
+ setPathwayCompletionsLoading(false);
188028
+ }
188029
+ };
188030
+ const handleFetchCatalogPathways = async () => {
188031
+ var _a;
188032
+ setIsLoading(true);
188033
+ const response = await getPathwayList([{ username: getUserName(), platformKey: getTenant() }], true);
188034
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
188035
+ const fetchedPathways = raw.map((result) => {
188036
+ const metadata = result.metadata;
188037
+ return {
188038
+ ...result,
188039
+ metadata: {
188040
+ ...metadata,
188041
+ course_image_asset_path: (metadata === null || metadata === void 0 ? void 0 : metadata.course_image_asset_path)
188042
+ ? (lmsUrl !== null && lmsUrl !== void 0 ? lmsUrl : '') + metadata.course_image_asset_path
188043
+ : getRandomCourseImage(),
188044
+ },
188045
+ };
188046
+ });
188047
+ setPathways(fetchedPathways);
188048
+ setFilteredPathways(fetchedPathways);
188049
+ handleFetchPathwayCompletions(fetchedPathways);
188050
+ setIsError(isCatalogPathwaysError);
188051
+ setIsLoading(false);
188052
+ };
188053
+ const handleFetchAssignedPathways = async () => {
188054
+ var _a, _b;
188055
+ setIsLoading(true);
188056
+ const userId = getUserId();
188057
+ const response = await getUserAssignedPathways({ user_id: userId }, true);
188058
+ const results = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.results) !== null && _b !== void 0 ? _b : []) || [];
188059
+ setPathways(results);
188060
+ setFilteredPathways(results);
188061
+ handleFetchPathwayCompletions(results);
188062
+ setIsError(isAssignedPathwaysError);
188063
+ setIsLoading(false);
188064
+ };
188065
+ const handleFetchEnrolledPathways = async () => {
188066
+ setIsLoading(true);
188067
+ const response = await getUserEnrolledPathways([{ username: getUserName() }], true);
188068
+ const data = response.data || [];
188069
+ setPathways(data);
188070
+ setFilteredPathways(data);
188071
+ handleFetchPathwayCompletions(data);
188072
+ setIsError(isEnrolledPathwaysError);
188073
+ setIsLoading(false);
188074
+ };
188075
+ useEffect(() => {
188076
+ setPathways([]);
188077
+ setFilteredPathways([]);
188078
+ switch (contentType) {
188079
+ case 'assigned':
188080
+ handleFetchAssignedPathways();
188081
+ break;
188082
+ case 'enrolled':
188083
+ handleFetchEnrolledPathways();
188084
+ break;
188085
+ default:
188086
+ handleFetchCatalogPathways();
188087
+ break;
188088
+ }
188089
+ }, [contentType]);
188090
+ useEffect(() => {
188091
+ if (String(searchQuery).length > 2) {
188092
+ setFilteredPathways(pathways.filter((pathway) => { var _a; return (_a = pathway.name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase()); }));
188093
+ }
188094
+ else {
188095
+ setFilteredPathways(pathways);
188096
+ }
188097
+ }, [searchQuery]);
188098
+ return {
188099
+ pathways,
188100
+ filteredPathways,
188101
+ isLoading,
188102
+ isError,
188103
+ setPathways,
188104
+ setFilteredPathways,
188105
+ pathwayCompletions,
188106
+ pathwayCompletionsLoading,
188107
+ handleFetchSinglePathwayEnrollmentStatus,
188108
+ };
188109
+ };
188110
+
188111
+ /**
188112
+ * Fetches the logged-in user's enrolled / assigned / catalog programs
188113
+ * and hydrates each with its completion stats. Supports client-side
188114
+ * search filtering on program name.
188115
+ *
188116
+ * Ported from the skillsai SPA (hooks/profile/use-profile-programs.ts).
188117
+ */
188118
+ const useProfilePrograms = ({ searchQuery, activeTab = 'enrolled', }) => {
188119
+ const [programs, setPrograms] = useState([]);
188120
+ const [filteredPrograms, setFilteredPrograms] = useState([]);
188121
+ const [isLoading, setIsLoading] = useState(false);
188122
+ const [isError, setIsError] = useState(false);
188123
+ const [programCompletions, setProgramCompletions] = useState([]);
188124
+ const [programCompletionsLoading, setProgramCompletionsLoading] = useState(false);
188125
+ const [getProgramList, { isError: isCatalogProgramsError }] = useLazyGetProgramListQuery();
188126
+ const [getProgramCompletion] = useLazyGetProgramCompletionQuery();
188127
+ const [getUserEnrolledPrograms, { isError: isEnrolledProgramsError }] = useLazyGetUserEnrolledProgramsQuery();
188128
+ const [getAssignedPrograms, { isError: isAssignedProgramsError }] = useLazyGetAssignedProgramsQuery();
188129
+ const withImage = (program) => {
188130
+ var _a;
188131
+ return ({
188132
+ ...program,
188133
+ metadata: {
188134
+ ...((_a = program.metadata) !== null && _a !== void 0 ? _a : {}),
188135
+ image: getRandomCourseImage(),
188136
+ },
188137
+ });
188138
+ };
188139
+ const handleFetchProgramCompletions = async (list) => {
188140
+ setProgramCompletionsLoading(true);
188141
+ try {
188142
+ const completions = await Promise.all(list.map(async (program) => {
188143
+ const response = await getProgramCompletion([
188144
+ { programKey: program.program_key || '', username: getUserName() },
188145
+ ], true);
188146
+ return (response.data || {});
188147
+ }));
188148
+ setProgramCompletions(completions);
188149
+ }
188150
+ catch (error) {
188151
+ console.error(JSON.stringify(error));
188152
+ setProgramCompletions([]);
188153
+ }
188154
+ finally {
188155
+ setProgramCompletionsLoading(false);
188156
+ }
188157
+ };
188158
+ const handleProgramEnrollmentFetch = async () => {
188159
+ var _a;
188160
+ setIsLoading(true);
188161
+ const response = await getUserEnrolledPrograms([{ userId: getUserId(), platformKey: getTenant() }], true);
188162
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
188163
+ const fetched = raw.map(withImage);
188164
+ setPrograms(fetched);
188165
+ setFilteredPrograms(fetched);
188166
+ handleFetchProgramCompletions(fetched);
188167
+ setIsError(isEnrolledProgramsError);
188168
+ setIsLoading(false);
188169
+ };
188170
+ const handleAssignedProgramsFetch = async () => {
188171
+ var _a, _b;
188172
+ setIsLoading(true);
188173
+ const response = await getAssignedPrograms({ user_id: getUserId() }, true);
188174
+ const raw = ((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.results) !== null && _b !== void 0 ? _b : []) || [];
188175
+ const fetched = raw.map(withImage);
188176
+ setPrograms(fetched);
188177
+ setFilteredPrograms(fetched);
188178
+ handleFetchProgramCompletions(fetched);
188179
+ setIsError(isAssignedProgramsError);
188180
+ setIsLoading(false);
188181
+ };
188182
+ const handleFetchCatalogPrograms = async () => {
188183
+ var _a;
188184
+ setIsLoading(true);
188185
+ const response = await getProgramList([{ org: getOrg(), username: getUserName() }], true);
188186
+ const raw = (_a = response.data) !== null && _a !== void 0 ? _a : [];
188187
+ const fetched = raw.map(withImage);
188188
+ setPrograms(fetched);
188189
+ setFilteredPrograms(fetched);
188190
+ handleFetchProgramCompletions(fetched);
188191
+ setIsError(isCatalogProgramsError);
188192
+ setIsLoading(false);
188193
+ };
188194
+ useEffect(() => {
188195
+ if (String(searchQuery).length > 2) {
188196
+ setFilteredPrograms(programs.filter((program) => { var _a; return (_a = program.name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(searchQuery.toLowerCase()); }));
188197
+ }
188198
+ else {
188199
+ setFilteredPrograms(programs);
188200
+ }
188201
+ }, [searchQuery]);
188202
+ useEffect(() => {
188203
+ switch (activeTab) {
188204
+ case 'assigned':
188205
+ handleAssignedProgramsFetch();
188206
+ break;
188207
+ case 'enrolled':
188208
+ handleProgramEnrollmentFetch();
188209
+ break;
188210
+ case 'catalog':
188211
+ handleFetchCatalogPrograms();
188212
+ break;
188213
+ }
188214
+ }, [activeTab]);
188215
+ return {
188216
+ programs,
188217
+ filteredPrograms,
188218
+ isLoading,
188219
+ isError,
188220
+ setFilteredPrograms,
188221
+ setPrograms,
188222
+ programCompletions,
188223
+ programCompletionsLoading,
188224
+ };
188225
+ };
188226
+
188227
+ const isEmpty = (value) => {
188228
+ if (value == null)
188229
+ return true;
188230
+ if (Array.isArray(value) || typeof value === 'string')
188231
+ return value.length === 0;
188232
+ if (typeof value === 'object')
188233
+ return Object.keys(value).length === 0;
188234
+ return false;
188235
+ };
188236
+ /**
188237
+ * Aggregates a fixed list of profile activity counters (points, skills,
188238
+ * credentials, courses, programs, pathways, resources, assessments,
188239
+ * videos) by fanning out to nine RTK lazy queries and updating each row
188240
+ * independently as its request resolves. Errors on any single fetch
188241
+ * collapse that stat to 0 without blocking the others.
188242
+ *
188243
+ * Ported from the skillsai SPA (hooks/profile/use-profile-activity-stats.ts).
188244
+ */
188245
+ const useProfileActivityStats = () => {
188246
+ const [getUserSkillsPoints, { isError: isErrorGetUserSkillsPoints }] = useLazyGetUserSkillsPointsQuery();
188247
+ const [getUserReportedSkills, { isError: isErrorGetUserReportedSkills }] = useLazyGetUserReportedSkillsQuery();
188248
+ const [getUserDesiredSkills, { isError: isErrorGetUserDesiredSkills }] = useLazyGetUserDesiredSkillsQuery();
188249
+ const [getUserCredentials, { isError: isErrorGetUserCredentials }] = useLazyGetUserCredentialsQuery();
188250
+ const [getUserEnrolledCourses, { isError: isErrorGetUserEnrolledCourses }] = useLazyGetUserEnrolledCoursesQuery();
188251
+ const [getUserEnrolledPrograms, { isError: isEnrolledProgramsError }] = useLazyGetUserEnrolledProgramsQuery();
188252
+ const [getUserCatalogPathways, { isError: isCatalogPathwaysError }] = useLazyGetUserCatalogPathwaysQuery();
188253
+ const [getPerLearnerInfo, { isError: isErrorgetPerLearnerInfo }] = useLazyGetPerLearnerInfoQuery();
188254
+ const [stats, setStats] = useState([
188255
+ { value: 0, label: 'Points', loading: true },
188256
+ { value: 3, label: 'Skills', loading: true },
188257
+ { value: 0, label: 'Credentials', loading: true },
188258
+ { value: 4, label: 'Courses', loading: true },
188259
+ { value: 0, label: 'Programs', loading: true },
188260
+ { value: 0, label: 'Pathways', loading: true },
188261
+ { value: 0, label: 'Resources', loading: true },
188262
+ { value: 0, label: 'Assessments', loading: true },
188263
+ { value: 0, label: 'Videos', loading: true },
188264
+ ]);
188265
+ const updateSingleStat = (stat) => {
188266
+ setStats((old) => old.map((s) => (s.label === stat.label ? stat : s)));
188267
+ };
188268
+ const handleSkillsPointActivityStats = async () => {
188269
+ var _a;
188270
+ const label = 'Points';
188271
+ try {
188272
+ const response = await getUserSkillsPoints([{ userId: getUserId(), username: getUserName() }], true);
188273
+ const skillPoints = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.skill_points;
188274
+ if (isErrorGetUserSkillsPoints || isEmpty(skillPoints))
188275
+ throw new Error();
188276
+ const total = Object.values(skillPoints || {}).reduce((sum, skill) => { var _a; return sum + ((_a = skill.total_points) !== null && _a !== void 0 ? _a : 0); }, 0);
188277
+ updateSingleStat({ value: total, label, loading: false });
188278
+ }
188279
+ catch (_b) {
188280
+ updateSingleStat({ value: 0, label, loading: false });
188281
+ }
188282
+ };
188283
+ const handleActivitySkillsStats = async () => {
188284
+ var _a, _b, _c, _d, _e, _f;
188285
+ const label = 'Skills';
188286
+ try {
188287
+ const reportedSkills = await getUserReportedSkills([{ userId: getUserId(), username: getUserName() }], true);
188288
+ if (isErrorGetUserDesiredSkills && isErrorGetUserReportedSkills)
188289
+ throw new Error();
188290
+ let skillsCount = 0;
188291
+ if (!isErrorGetUserReportedSkills) {
188292
+ 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);
188293
+ }
188294
+ const desiredSkills = await getUserDesiredSkills([{ userId: getUserId(), username: getUserName() }], true);
188295
+ if (!isErrorGetUserDesiredSkills) {
188296
+ 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);
188297
+ }
188298
+ updateSingleStat({ value: skillsCount, label, loading: false });
188299
+ }
188300
+ catch (_g) {
188301
+ updateSingleStat({ value: 0, label, loading: false });
188302
+ }
188303
+ };
188304
+ const handleCredentialsActivityStats = async () => {
188305
+ var _a, _b, _c;
188306
+ const label = 'Credentials';
188307
+ try {
188308
+ const response = await getUserCredentials({
188309
+ platformKey: getTenant(),
188310
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
188311
+ }, true);
188312
+ if (isErrorGetUserCredentials || isEmpty(response === null || response === void 0 ? void 0 : response.data))
188313
+ throw new Error();
188314
+ const data = (_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.data;
188315
+ updateSingleStat({ value: (_c = data === null || data === void 0 ? void 0 : data.length) !== null && _c !== void 0 ? _c : 0, label, loading: false });
188316
+ }
188317
+ catch (_d) {
188318
+ updateSingleStat({ value: 0, label, loading: false });
188319
+ }
188320
+ };
188321
+ const handleCoursesActivityStats = async () => {
188322
+ var _a, _b;
188323
+ const label = 'Courses';
188324
+ try {
188325
+ const username = getUserName();
188326
+ if (!username)
188327
+ throw new Error();
188328
+ const response = await getUserEnrolledCourses({ username }, true);
188329
+ if (isErrorGetUserEnrolledCourses || isEmpty(response.data))
188330
+ throw new Error();
188331
+ 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 });
188332
+ }
188333
+ catch (_c) {
188334
+ updateSingleStat({ value: 0, label, loading: false });
188335
+ }
188336
+ };
188337
+ const handleProgramsActivityStats = async () => {
188338
+ var _a;
188339
+ const label = 'Programs';
188340
+ try {
188341
+ const response = await getUserEnrolledPrograms({ username: getUserName(), platform_key: getTenant() }, true);
188342
+ if (isEnrolledProgramsError || isEmpty(response.data))
188343
+ throw new Error();
188344
+ const data = response === null || response === void 0 ? void 0 : response.data;
188345
+ updateSingleStat({ value: (_a = data === null || data === void 0 ? void 0 : data.length) !== null && _a !== void 0 ? _a : 0, label, loading: false });
188346
+ }
188347
+ catch (_b) {
188348
+ updateSingleStat({ value: 0, label, loading: false });
188349
+ }
188350
+ };
188351
+ const handlePathwaysActivityStats = async () => {
188352
+ var _a;
188353
+ const pathwaysLabel = 'Pathways';
188354
+ const resourcesLabel = 'Resources';
188355
+ try {
188356
+ const response = await getUserCatalogPathways({ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '', platform_key: getTenant() }, true);
188357
+ if (isCatalogPathwaysError || isEmpty(response.data))
188358
+ throw new Error();
188359
+ const data = response.data;
188360
+ const seen = new Set();
188361
+ const unique = data.filter((item) => {
188362
+ const dup = seen.has(item.id);
188363
+ seen.add(item.id);
188364
+ return !dup;
188365
+ });
188366
+ 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);
188367
+ updateSingleStat({ value: unique.length, label: pathwaysLabel, loading: false });
188368
+ updateSingleStat({ value: totalResources, label: resourcesLabel, loading: false });
188369
+ }
188370
+ catch (_b) {
188371
+ updateSingleStat({ value: 0, label: pathwaysLabel, loading: false });
188372
+ updateSingleStat({ value: 0, label: resourcesLabel, loading: false });
188373
+ }
188374
+ };
188375
+ const handlePerLearnerInfoStats = async () => {
188376
+ var _a, _b, _c;
188377
+ const assessmentsLabel = 'Assessments';
188378
+ const videosLabel = 'Videos';
188379
+ try {
188380
+ const response = await getPerLearnerInfo([
188381
+ {
188382
+ org: getTenant(),
188383
+ userId: getUserName(),
188384
+ format: 'json',
188385
+ includeMainPlatform: true,
188386
+ },
188387
+ ], true);
188388
+ if (isErrorgetPerLearnerInfo || isEmpty(response.data))
188389
+ throw new Error();
188390
+ const inner = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.data;
188391
+ updateSingleStat({
188392
+ value: (_b = inner === null || inner === void 0 ? void 0 : inner.total_assessments) !== null && _b !== void 0 ? _b : 0,
188393
+ label: assessmentsLabel,
188394
+ loading: false,
188395
+ });
188396
+ updateSingleStat({ value: (_c = inner === null || inner === void 0 ? void 0 : inner.total_videos) !== null && _c !== void 0 ? _c : 0, label: videosLabel, loading: false });
188397
+ }
188398
+ catch (_d) {
188399
+ updateSingleStat({ value: 0, label: assessmentsLabel, loading: false });
188400
+ updateSingleStat({ value: 0, label: videosLabel, loading: false });
188401
+ }
188402
+ };
188403
+ useEffect(() => {
188404
+ handleSkillsPointActivityStats();
188405
+ handleActivitySkillsStats();
188406
+ handleCredentialsActivityStats();
188407
+ handleCoursesActivityStats();
188408
+ handleProgramsActivityStats();
188409
+ handlePathwaysActivityStats();
188410
+ handlePerLearnerInfoStats();
188411
+ }, []);
188412
+ return { stats };
188413
+ };
188414
+
188415
+ const EdxIframeContext = createContext({
188416
+ iframeUrl: '',
188417
+ setIframeUrl: () => { },
188418
+ courseOutline: {},
188419
+ setActiveTab: () => { },
188420
+ activeTab: '',
188421
+ courseID: '',
188422
+ currentlyInExamSubsection: false,
188423
+ setCurrentlyInExamSubsection: () => { },
188424
+ examInfo: null,
188425
+ setExamInfo: () => { },
188426
+ refresher: null,
188427
+ setRefresher: () => { },
188428
+ });
188429
+
188430
+ const CourseOutlineContext = createContext({
188431
+ courseOutline: {},
188432
+ courseOutlineLoading: false,
188433
+ expandedModule: '',
188434
+ expandedLessons: [],
188435
+ selectLesson: () => { },
188436
+ toggleModule: () => { },
188437
+ toggleLesson: () => { },
188438
+ currentChapter: '',
188439
+ currentLesson: '',
188440
+ course: null,
188441
+ courseOutlineDrawerOpen: false,
188442
+ setCourseOutlineDrawerOpen: () => { },
188443
+ currentUnitID: null,
188444
+ refetchCourseOutline: () => { },
188445
+ });
188446
+
188447
+ /**
188448
+ * Pure utility helpers for the edX iframe course navigation.
188449
+ *
188450
+ * Extracted from skillsai's `hooks/courses/use-edx-iframe.ts` so they can be
188451
+ * unit tested without a React renderer and reused from both the hook and
188452
+ * (future) course-content components. Any function that used to read
188453
+ * `config.urls.lms() / mfe() / legacyLmsUrl()` now takes those URLs as
188454
+ * parameters — there is no package-level config coupling here.
188455
+ *
188456
+ * The async `getIframeURL` wrapper (which calls `useLazyGetEdxSSOTokenQuery`)
188457
+ * remains inside the hook.
188458
+ */
188459
+ /**
188460
+ * Walk the outline tree and return the id of the `sequential` parent block
188461
+ * whose children include the given vertical id, or null if not found.
188462
+ */
188463
+ function findSequentialParent(data, verticalId) {
188464
+ if (!data)
188465
+ return null;
188466
+ if (data.type === 'sequential' && data.children) {
188467
+ for (const child of data.children) {
188468
+ if (child.id === verticalId)
188469
+ return data.id;
188470
+ const foundId = findSequentialParent(child, verticalId);
188471
+ if (foundId)
188472
+ return foundId;
188473
+ }
188474
+ }
188475
+ else if (data.children) {
188476
+ for (const child of data.children) {
188477
+ const foundId = findSequentialParent(child, verticalId);
188478
+ if (foundId)
188479
+ return foundId;
188480
+ }
188481
+ }
188482
+ return null;
188483
+ }
188484
+ /**
188485
+ * Recursively collect every `vertical`-type block in the tree into a flat
188486
+ * `{id, display_name}[]` list, preserving document order.
188487
+ */
188488
+ function flattenVerticalBlocks(data) {
188489
+ if (!data || typeof data !== 'object')
188490
+ return [];
188491
+ if (Array.isArray(data)) {
188492
+ const result = [];
188493
+ for (const item of data) {
188494
+ result.push(...flattenVerticalBlocks(item));
188495
+ }
188496
+ return result;
188497
+ }
188498
+ if (data.type === 'vertical') {
188499
+ const block = {
188500
+ id: data.id,
188501
+ display_name: data.display_name,
188502
+ };
188503
+ return [block, ...flattenVerticalBlocks(data.children)];
188504
+ }
188505
+ return flattenVerticalBlocks(data.children);
188506
+ }
188507
+ /**
188508
+ * Walks `data.children[0].children[0].children[attempt]` up to `maxAttempts`
188509
+ * and returns the first non-empty unit. Falls back to the course's first
188510
+ * sub-sub block if the course has started. Returns null otherwise.
188511
+ */
188512
+ function getFirstAvailableUnit(data, maxAttempts = 2) {
188513
+ var _a, _b, _c, _d, _e, _f, _g, _h;
188514
+ if (!data)
188515
+ return null;
188516
+ try {
188517
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
188518
+ 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];
188519
+ if (element)
188520
+ return element;
188521
+ }
188522
+ }
188523
+ catch (_j) {
188524
+ // fallthrough to the tolerant branch below
188525
+ }
188526
+ try {
188527
+ if (data &&
188528
+ Object.prototype.hasOwnProperty.call(data, 'children') &&
188529
+ data.children &&
188530
+ data.children[0] &&
188531
+ Object.prototype.hasOwnProperty.call(data.children[0], 'children') &&
188532
+ (!data.start || new Date(data.start) < new Date())) {
188533
+ 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;
188534
+ }
188535
+ }
188536
+ catch (_k) {
188537
+ return null;
188538
+ }
188539
+ return null;
188540
+ }
188541
+ /**
188542
+ * Find the deepest `resume_block: true` vertical node in the tree (last in
188543
+ * document order) or null if the course has never been started.
188544
+ */
188545
+ function findLastResumeBlock(courseData) {
188546
+ if (!courseData)
188547
+ return null;
188548
+ let lastResumeBlock = null;
188549
+ const traverse = (node) => {
188550
+ if (node.resume_block && node.type === 'vertical') {
188551
+ lastResumeBlock = node;
188552
+ }
188553
+ if (node.children && node.children.length > 0) {
188554
+ for (const child of node.children)
188555
+ traverse(child);
188556
+ }
188557
+ };
188558
+ traverse(courseData);
188559
+ return lastResumeBlock;
188560
+ }
188561
+ /**
188562
+ * Find a specific block by id inside the top-level `children` array and
188563
+ * return it together with the chain of child-indices that locate it.
188564
+ */
188565
+ function getParentBlockById(blocksArray, targetBlockId) {
188566
+ let foundIndices = [];
188567
+ const findParentBlock = (currentBlock, target, currentIndices) => {
188568
+ if (currentBlock.id === target) {
188569
+ foundIndices = currentIndices.slice();
188570
+ return currentBlock;
188571
+ }
188572
+ if (currentBlock.children) {
188573
+ for (let i = 0; i < currentBlock.children.length; i++) {
188574
+ const result = findParentBlock(currentBlock.children[i], target, [...currentIndices, i]);
188575
+ if (result)
188576
+ return result;
188577
+ }
188578
+ }
188579
+ return null;
188580
+ };
188581
+ for (let i = 0; i < blocksArray.length; i++) {
188582
+ const parentBlock = findParentBlock(blocksArray[i], targetBlockId, [i]);
188583
+ if (parentBlock)
188584
+ return { parentBlock, foundIndices };
188585
+ }
188586
+ return { parentBlock: null, foundIndices };
188587
+ }
188588
+ /**
188589
+ * Given a unit id and the course outline, return the id of the previous
188590
+ * vertical unit, or null if there is no previous unit.
188591
+ */
188592
+ function getPreviousUnitIframe(suppliedId, courseData) {
188593
+ const idList = flattenVerticalBlocks(courseData);
188594
+ const index = idList.findIndex((item) => item.id === suppliedId);
188595
+ if (index === -1 || index === 0)
188596
+ return null;
188597
+ return idList[index - 1].id;
188598
+ }
188599
+ /**
188600
+ * Given a unit id and the course outline, return the id of the next vertical
188601
+ * unit, or null if there is no next unit.
188602
+ */
188603
+ function getNextUnitIframe(suppliedId, courseData) {
188604
+ const idList = flattenVerticalBlocks(courseData);
188605
+ const index = idList.findIndex((item) => item.id === suppliedId);
188606
+ if (index === -1 || index === idList.length - 1)
188607
+ return null;
188608
+ return idList[index + 1].id;
188609
+ }
188610
+ /**
188611
+ * Given a sublesson (3rd-level) id, return the parent module + lesson objects
188612
+ * from a modules array. Returns `{module:{}, lesson:{}}` if not found — this
188613
+ * preserves the skillsai fallback shape so callers don't need to null-check.
188614
+ */
188615
+ function getParentsInfosFromSublessonId(modules, sublessonId) {
188616
+ try {
188617
+ for (const mod of modules) {
188618
+ if (!mod.children)
188619
+ continue;
188620
+ for (const lesson of mod.children) {
188621
+ if (!lesson.children)
188622
+ continue;
188623
+ for (const sublesson of lesson.children) {
188624
+ if (sublesson.id === sublessonId) {
188625
+ return { module: mod, lesson };
188626
+ }
188627
+ }
188628
+ }
188629
+ }
188630
+ throw new Error('Sublesson not found');
188631
+ }
188632
+ catch (_a) {
188633
+ return { module: {}, lesson: {} };
188634
+ }
188635
+ }
188636
+ /**
188637
+ * Append a Bookmarks tab to the given tabs list in place.
188638
+ * The caller supplies the LMS base URL — no package config dependency.
188639
+ */
188640
+ function addBookmarksTab(tabs, courseId, lmsUrl) {
188641
+ tabs.push({
188642
+ tab_id: 'bookmarks',
188643
+ title: 'Bookmarks',
188644
+ url: `${lmsUrl}/courses/${courseId}/bookmarks`,
188645
+ });
188646
+ }
188647
+ /**
188648
+ * Resolve which unit of the course outline should be loaded into the iframe.
188649
+ * 1. If the current URL has `?unit_id=…`, try to find that block.
188650
+ * 2. Otherwise, return the last resume block if the learner has progress.
188651
+ * 3. Otherwise, fall back to the first available unit.
188652
+ *
188653
+ * Priority order:
188654
+ * 1. Explicit `unitId` parameter (from React state — always up-to-date).
188655
+ * 2. `?unit_id=…` from the current URL (`window.location.href`).
188656
+ * 3. The last `resume_block` vertical in the outline.
188657
+ * 4. The first available unit.
188658
+ */
188659
+ function getUnitToIframe(courseOutlineData, unitId) {
188660
+ var _a, _b;
188661
+ // 1. Explicit unitId from caller (e.g. React searchParams / context)
188662
+ if (unitId) {
188663
+ return (_a = findVerticalById(courseOutlineData, unitId)) !== null && _a !== void 0 ? _a : getFirstAvailableUnit(courseOutlineData);
188664
+ }
188665
+ // 2. Fall back to reading from the URL (legacy path / initial load)
188666
+ if (typeof window !== 'undefined') {
188667
+ try {
188668
+ const courseUrl = new URL(window.location.href);
188669
+ const urlUnitId = courseUrl.searchParams.get('unit_id');
188670
+ if (urlUnitId) {
188671
+ return ((_b = findVerticalById(courseOutlineData, urlUnitId)) !== null && _b !== void 0 ? _b : getFirstAvailableUnit(courseOutlineData));
188672
+ }
188673
+ }
188674
+ catch (_c) {
188675
+ // invalid URL — fall through
188676
+ }
188677
+ }
188678
+ // 3. Resume block
188679
+ const lastResumeBlock = findLastResumeBlock(courseOutlineData);
188680
+ if (lastResumeBlock)
188681
+ return lastResumeBlock;
188682
+ // 4. First available
188683
+ return getFirstAvailableUnit(courseOutlineData);
188684
+ }
188685
+ /** Find a block by id within the top-level `children` of a course outline. */
188686
+ function findVerticalById(data, verticalId) {
188687
+ if (!data || !data.children)
188688
+ return null;
188689
+ const search = (nodes) => {
188690
+ for (const item of nodes) {
188691
+ if (item.id === verticalId)
188692
+ return item;
188693
+ if (item.children) {
188694
+ const result = search(item.children);
188695
+ if (result)
188696
+ return result;
188697
+ }
188698
+ }
188699
+ return null;
188700
+ };
188701
+ return search(data.children);
188702
+ }
188703
+
188704
+ /**
188705
+ * Hook exposing helpers for the edX iframe course navigation. Ported from
188706
+ * skillsai's `hooks/courses/use-edx-iframe.ts`, with two changes:
188707
+ *
188708
+ * 1. All pure utilities are imported from `../lib/edx-iframe-utils`.
188709
+ * 2. LMS / MFE / legacy LMS base URLs are passed in as hook args instead of
188710
+ * being read from the skillsai `config` module.
188711
+ */
188712
+ const useEdxIframe = ({ lmsUrl, mfeUrl, legacyLmsUrl }) => {
188713
+ const [getEdxSsoAuthToken] = useLazyGetEdxSSOTokenQuery();
188714
+ /**
188715
+ * Resolve the iframe URL for a given course + courseInfo value. The
188716
+ * `courseInfo` can be either the course outline object (pick a unit from
188717
+ * it) or an xblock id string (use as-is).
188718
+ *
188719
+ * `unitId` (optional) is the unit id from React state (e.g.
188720
+ * `searchParams.get('unit_id')` or `currentUnitID`). When provided it
188721
+ * takes precedence over reading `window.location.href`, which avoids a
188722
+ * race condition with Next.js async `router.push`.
188723
+ */
188724
+ async function getIframeURL(courseId, courseInfo, callback, unitId) {
188725
+ var _a;
188726
+ if (typeof courseInfo === 'object' ||
188727
+ (typeof courseInfo === 'string' && courseInfo.includes(':'))) {
188728
+ // Course outline case — resolve an actual vertical to load.
188729
+ const outline = typeof courseInfo === 'object' ? courseInfo : null;
188730
+ if (outline) {
188731
+ flattenVerticalBlocks(outline);
188732
+ const unit = getUnitToIframe(outline, unitId);
188733
+ await addIframeUrl(courseId, (_a = unit === null || unit === void 0 ? void 0 : unit.id) !== null && _a !== void 0 ? _a : '', callback);
188734
+ return;
188735
+ }
188736
+ await addIframeUrl(courseId, courseInfo, callback);
188737
+ }
188738
+ else {
188739
+ await addIframeUrl(courseId, courseInfo, callback);
188740
+ }
188741
+ }
188742
+ async function addIframeUrl(courseId, rawXblockId, callback) {
188743
+ var _a, _b;
188744
+ let url = '';
188745
+ const xblockID = rawXblockId;
188746
+ let baseLMSIframeURL = `${lmsUrl}/xblock/${xblockID}?show_title=0&show_bookmark_button=1&recheck_access=1&view=student_view`;
188747
+ try {
188748
+ switch (xblockID) {
188749
+ case 'forum':
188750
+ url = `${mfeUrl}/discussions/${courseId}/posts`;
188751
+ break;
188752
+ case 'notes':
188753
+ url = `${mfeUrl}/courses/${courseId}/edxnotes`;
188754
+ break;
188755
+ case 'progress':
188756
+ url = `${mfeUrl}/learning/course/${courseId}/progress/`;
188757
+ break;
188758
+ case 'dates':
188759
+ url = `${mfeUrl}/learning/course/${courseId}/dates/`;
188760
+ break;
188761
+ case 'bookmarks':
188762
+ url = `${legacyLmsUrl}/courses/${courseId}/bookmarks/`;
188763
+ break;
188764
+ case 'instructor':
188765
+ baseLMSIframeURL = `${lmsUrl}/courses/${courseId}/instructor`;
188766
+ // fallthrough — same SSO-wrap path as default
188767
+ // eslint-disable-next-line no-fallthrough
188768
+ default: {
188769
+ const { data: authSsoToken } = await getEdxSsoAuthToken({
188770
+ username: (_a = getUserName()) !== null && _a !== void 0 ? _a : '',
188771
+ redirectUrl: baseLMSIframeURL,
188772
+ });
188773
+ 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 : ''}`;
188774
+ break;
188775
+ }
188776
+ }
188777
+ callback(url);
188778
+ }
188779
+ catch (_c) {
188780
+ callback(url);
188781
+ }
188782
+ }
188783
+ return {
188784
+ getIframeURL,
188785
+ getUnitToIframe,
188786
+ findSequentialParent,
188787
+ flattenVerticalBlocks,
188788
+ getFirstAvailableUnit,
188789
+ findLastResumeBlock,
188790
+ getParentBlockById,
188791
+ getPreviousUnitIframe,
188792
+ getNextUnitIframe,
188793
+ addBookmarksTab,
188794
+ getParentsInfosFromSublessonId,
188795
+ };
188796
+ };
188797
+
188798
+ const ChatContext = createContext({
188799
+ isOpen: false,
188800
+ setIsOpen: () => { },
188801
+ courseMentor: null,
188802
+ setCourseMentor: () => { },
188803
+ mentorSidebarHidden: false,
188804
+ setMentorSidebarHidden: () => { },
188805
+ });
188806
+ const useChatState = () => useContext(ChatContext);
188807
+ function ChatProvider({ children, initialOpen = false, initialCourseMentor = null, initialMentorSidebarHidden = false, }) {
188808
+ const [isOpen, setIsOpen] = useState(initialOpen);
188809
+ const [courseMentor, setCourseMentor] = useState(initialCourseMentor);
188810
+ const [mentorSidebarHidden, setMentorSidebarHidden] = useState(initialMentorSidebarHidden);
188811
+ const value = {
188812
+ isOpen,
188813
+ setIsOpen,
188814
+ courseMentor,
188815
+ setCourseMentor,
188816
+ mentorSidebarHidden,
188817
+ setMentorSidebarHidden,
188818
+ };
188819
+ return createElement$1(ChatContext.Provider, { value }, children);
188820
+ }
188821
+
188822
+ /**
188823
+ * Ported from skillsai (`hooks/courses/use-course-detail.ts`).
188824
+ *
188825
+ * Differences from the skillsai version:
188826
+ * - No `next/navigation`. Caller passes an `onNavigate(href, { external })`
188827
+ * callback which is invoked for lesson open / access-course / Stripe
188828
+ * checkout redirect. `external: true` means "open in a new tab / navigate
188829
+ * the full window" (as opposed to SPA push).
188830
+ * - No internal `sonner` toasts. Caller may provide `onError` / `onSuccess`
188831
+ * callbacks (both optional).
188832
+ * - No `config.*` reads. Caller passes `dmUrl` (used to build the Stripe
188833
+ * `success_url`) and `courseEligibilityEnabled` (the feature flag that
188834
+ * used to live in `config.settings.courseEligibilityEnabled()`).
188835
+ */
188836
+ const ACCESS_COURSE_LABEL = 'Access Course';
188837
+ const ENROLL_NOW_LABEL = 'Enroll Now';
188838
+ const REQUEST_ACCESS_LABEL = 'Request Access';
188839
+ const ENROLL_NOW_COURSE_STARTING_SOON_LABEL = 'Enroll Now - Course Starting Soon';
188840
+ const REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL = 'Request Access - Course Starting Soon';
188841
+ const INVITATION_ONLY_LABEL = 'Invitation Only';
188842
+ const BUY_NOW_LABEL = 'Buy Now';
188843
+ const useCourseDetail = ({ courseId, dmUrl, courseEligibilityEnabled = false, onNavigate, onError, onSuccess, }) => {
188844
+ const [createCourseEnrollment, { isError: isCourseEnrollmentError }] = useCreateCourseEnrollmentMutation();
188845
+ const [createStripeCheckoutSession] = useCreateStripeCheckoutSessionMutation();
188846
+ const [getCourseProgress, { isLoading: isCourseProgressLoading, isError: isCourseProgressError },] = useLazyGetCourseProgressQuery();
188847
+ const [getCourseCompletion, { isLoading: isCourseCompletionLoading, isError: isCourseCompletionError },] = useLazyGetCourseCompletionQuery();
188848
+ const { handleFetchCourseMetaData, handleFetchCourseCompletionOutlines, handleFetchCourseEligibility, } = useCourseMetadata();
188849
+ const [courseInfoLoadingState, setCourseInfoLoadingState] = useState('not-started');
188850
+ const [course, setCourse] = useState(null);
188851
+ const [courseOutline, setCourseOutline] = useState({});
188852
+ const [courseOutlineLoading, setCourseOutlineLoading] = useState(false);
188853
+ const [courseEligibilityLoading, setCourseEligibilityLoading] = useState(false);
188854
+ const [courseButtonActionLoading, setCourseButtonActionLoading] = useState(false);
188855
+ const [courseProgress, setCourseProgress] = useState(null);
188856
+ const [courseCompletion, setCourseCompletion] = useState(null);
188857
+ const [courseGradingPolicyActive, setCourseGradingPolicyActive] = useState(false);
188858
+ const navigateToCourseContent = (href) => {
188859
+ if (onNavigate) {
188860
+ onNavigate(href, { external: inIframe() });
188861
+ }
188862
+ };
188863
+ const handleRequestAccess = () => {
188864
+ // Intentional no-op — skillsai's original also had no behavior here.
188865
+ // Callers that need request-access wiring should listen to btn_label.
188866
+ };
188867
+ const handleSelfEnrollToCourse = () => {
188868
+ // Same as skillsai — placeholder. Self-enroll is distinguished from
188869
+ // normal enroll only by the label; callers can key off it if needed.
188870
+ };
188871
+ const handleAccessCourse = () => {
188872
+ navigateToCourseContent(`/course-content/${courseId}/course`);
188873
+ };
188874
+ const handleCreateCheckoutSession = async () => {
188875
+ setCourseButtonActionLoading(true);
188876
+ const currentTenant = getTenant();
188877
+ try {
188878
+ const checkoutSession = await createStripeCheckoutSession({
188879
+ sku: courseId,
188880
+ org: (course === null || course === void 0 ? void 0 : course.org) || '',
188881
+ tenant: (course === null || course === void 0 ? void 0 : course.platform_key) || currentTenant,
188882
+ username: getUserName() || '',
188883
+ mode: 'payment',
188884
+ cancel_url: typeof window !== 'undefined' ? window.location.href : '',
188885
+ success_url: `${dmUrl}/api/service/orgs/${course === null || course === void 0 ? void 0 : course.platform_key}/stripe/course-payment-callback/`,
188886
+ }).unwrap();
188887
+ if (!(checkoutSession === null || checkoutSession === void 0 ? void 0 : checkoutSession.redirect_to)) {
188888
+ throw new Error('Failed to create checkout session');
188889
+ }
188890
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess('Redirecting to checkout page...');
188891
+ onNavigate === null || onNavigate === void 0 ? void 0 : onNavigate(checkoutSession.redirect_to, { external: true });
188892
+ }
188893
+ catch (_a) {
188894
+ setCourseButtonActionLoading(false);
188895
+ onError === null || onError === void 0 ? void 0 : onError('Failed to create checkout session');
188896
+ }
188897
+ };
188898
+ const handleEnrollToCourse = async () => {
188899
+ setCourseButtonActionLoading(true);
188900
+ try {
188901
+ const response = await createCourseEnrollment({
188902
+ course_details: { course_id: courseId },
188903
+ });
188904
+ if (isCourseEnrollmentError || !response.data || !response.data.created) {
188905
+ throw new Error('Failed to enroll in course');
188906
+ }
188907
+ onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess('Enrolled in course successfully');
188908
+ setCourseButtonActionLoading(false);
188909
+ handleAccessCourse();
188910
+ }
188911
+ catch (_a) {
188912
+ onError === null || onError === void 0 ? void 0 : onError('Failed to enroll in course.');
188913
+ setCourseButtonActionLoading(false);
188914
+ }
188915
+ };
188916
+ const [courseEligibility, setCourseEligibility] = useState({
188917
+ btn_label: ENROLL_NOW_LABEL,
188918
+ btn_action: handleEnrollToCourse,
188919
+ });
188920
+ const handleFetchCourseEligibilityInfo = async () => {
188921
+ setCourseEligibilityLoading(true);
188922
+ const eligibility = await handleFetchCourseEligibility(courseId);
188923
+ if (eligibility) {
188924
+ const enrollmentStarted = dayjs(course === null || course === void 0 ? void 0 : course.enrollment_start).diff(dayjs(), 'seconds') > 0;
188925
+ const isEnrolled = eligibility.is_enrolled;
188926
+ const canEnroll = eligibility.can_enroll;
188927
+ const isEligible = eligibility.is_eligible;
188928
+ const isNotMainTenant = getTenant() !== 'main';
188929
+ const invitationOnly = eligibility.invitation_only;
188930
+ const coursePrice = course === null || course === void 0 ? void 0 : course.course_price;
188931
+ if (courseEligibilityEnabled) {
188932
+ if (isNotMainTenant && isEnrolled && enrollmentStarted && isEligible) {
188933
+ setCourseEligibility({
188934
+ btn_label: ACCESS_COURSE_LABEL,
188935
+ btn_action: handleAccessCourse,
188936
+ });
188937
+ }
188938
+ else if (isNotMainTenant && enrollmentStarted && !isEligible) {
188939
+ setCourseEligibility({
188940
+ btn_label: REQUEST_ACCESS_LABEL,
188941
+ btn_action: handleRequestAccess,
188942
+ });
188943
+ }
188944
+ else if (!isNotMainTenant &&
188945
+ !enrollmentStarted &&
188946
+ isEligible &&
188947
+ canEnroll &&
188948
+ !isEnrolled) {
188949
+ setCourseEligibility({
188950
+ btn_label: ENROLL_NOW_COURSE_STARTING_SOON_LABEL,
188951
+ btn_action: handleSelfEnrollToCourse,
188952
+ });
188953
+ }
188954
+ else if (!isNotMainTenant &&
188955
+ !enrollmentStarted &&
188956
+ !isEligible &&
188957
+ (course === null || course === void 0 ? void 0 : course.platform_key) === 'main') {
188958
+ setCourseEligibility({
188959
+ btn_label: REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL,
188960
+ btn_action: handleRequestAccess,
188961
+ });
188962
+ }
188963
+ else if (isNotMainTenant && enrollmentStarted && isEligible && !isEnrolled && canEnroll) {
188964
+ setCourseEligibility({
188965
+ btn_label: ENROLL_NOW_LABEL,
188966
+ btn_action: handleSelfEnrollToCourse,
188967
+ });
188968
+ }
188969
+ }
188970
+ else {
188971
+ if (isEnrolled) {
188972
+ setCourseEligibility({
188973
+ btn_label: ACCESS_COURSE_LABEL,
188974
+ btn_action: handleAccessCourse,
188975
+ });
188976
+ }
188977
+ else if (invitationOnly) {
188978
+ setCourseEligibility({
188979
+ disabled: true,
188980
+ btn_label: INVITATION_ONLY_LABEL,
188981
+ btn_action: () => { },
188982
+ });
188983
+ }
188984
+ else if (coursePrice && coursePrice !== 'Free' && parseInt(coursePrice) !== 0) {
188985
+ setCourseEligibility({
188986
+ btn_label: BUY_NOW_LABEL,
188987
+ btn_action: handleCreateCheckoutSession,
188988
+ });
188989
+ }
188990
+ else {
188991
+ setCourseEligibility({
188992
+ btn_label: ENROLL_NOW_LABEL,
188993
+ btn_action: handleEnrollToCourse,
188994
+ });
188995
+ }
188996
+ }
188997
+ setCourseEligibilityLoading(false);
188998
+ }
188999
+ else {
189000
+ setCourseEligibility({
189001
+ btn_label: ENROLL_NOW_LABEL,
189002
+ btn_action: handleEnrollToCourse,
189003
+ });
189004
+ setCourseEligibilityLoading(false);
189005
+ }
189006
+ };
189007
+ const handleFetchCourseInfo = async () => {
189008
+ setCourseInfoLoadingState('loading');
189009
+ try {
189010
+ const courseMetaData = await handleFetchCourseMetaData(courseId);
189011
+ if (courseMetaData && Object.keys(courseMetaData).length > 0) {
189012
+ setCourse(courseMetaData);
189013
+ setCourseInfoLoadingState('successful');
189014
+ }
189015
+ else {
189016
+ setCourse(null);
189017
+ setCourseInfoLoadingState('failure');
189018
+ }
189019
+ }
189020
+ catch (_a) {
189021
+ setCourse(null);
189022
+ setCourseInfoLoadingState('failure');
189023
+ }
189024
+ };
189025
+ const handleFetchCourseSyllabus = async (setLoadingState = true) => {
189026
+ if (setLoadingState) {
189027
+ setCourseOutlineLoading(true);
189028
+ }
189029
+ const outlines = (await handleFetchCourseCompletionOutlines(courseId));
189030
+ if (outlines && Object.keys(outlines).length > 0) {
189031
+ setCourseOutline(outlines);
189032
+ }
189033
+ else {
189034
+ setCourseOutline({});
189035
+ }
189036
+ if (setLoadingState) {
189037
+ setCourseOutlineLoading(false);
189038
+ }
189039
+ };
189040
+ const handleOpenLesson = (lessonId, checkEligibility = false) => {
189041
+ if (lessonId &&
189042
+ (checkEligibility ? courseEligibility.btn_label === ACCESS_COURSE_LABEL : true)) {
189043
+ navigateToCourseContent(`/course-content/${courseId}/course?unit_id=${encodeURIComponent(lessonId)}`);
189044
+ }
189045
+ };
189046
+ const handleFetchCourseProgress = async () => {
189047
+ var _a;
189048
+ try {
189049
+ const progress = await getCourseProgress({ courseKey: courseId });
189050
+ if (isCourseProgressError) {
189051
+ throw new Error('Error fetching course progress');
189052
+ }
189053
+ setCourseProgress(progress.data || null);
189054
+ if (progress.data) {
189055
+ setCourseGradingPolicyActive(Array.isArray((_a = progress.data.grading_policy) === null || _a === void 0 ? void 0 : _a.assignment_policies) &&
189056
+ progress.data.grading_policy.assignment_policies.length > 0);
189057
+ }
189058
+ }
189059
+ catch (error) {
189060
+ setCourseProgress(null);
189061
+ console.error('Error fetching course progress:', error);
189062
+ }
189063
+ };
189064
+ const handleFetchCourseCompletion = async (userID) => {
189065
+ try {
189066
+ const completion = await getCourseCompletion({
189067
+ courseKey: encodeURIComponent(courseId),
189068
+ userID,
189069
+ });
189070
+ if (isCourseCompletionError) {
189071
+ throw new Error('Error fetching course completion');
189072
+ }
189073
+ setCourseCompletion(completion.data || null);
189074
+ }
189075
+ catch (error) {
189076
+ setCourseCompletion(null);
189077
+ console.error('Error fetching course completion:', error);
189078
+ }
189079
+ };
189080
+ return {
189081
+ // handlers
189082
+ handleRequestAccess,
189083
+ handleSelfEnrollToCourse,
189084
+ handleAccessCourse,
189085
+ handleCreateCheckoutSession,
189086
+ handleEnrollToCourse,
189087
+ handleFetchCourseEligibilityInfo,
189088
+ handleFetchCourseInfo,
189089
+ handleFetchCourseSyllabus,
189090
+ handleOpenLesson,
189091
+ handleFetchCourseProgress,
189092
+ handleFetchCourseCompletion,
189093
+ // state
189094
+ course,
189095
+ courseInfoLoadingState,
189096
+ courseOutline,
189097
+ courseEligibility,
189098
+ courseOutlineLoading,
189099
+ courseEligibilityLoading,
189100
+ courseButtonActionLoading,
189101
+ isCourseProgressLoading,
189102
+ isCourseCompletionLoading,
189103
+ courseProgress,
189104
+ courseCompletion,
189105
+ courseGradingPolicyActive,
189106
+ // labels (re-exported here for convenience; also exported as module
189107
+ // constants at the top of this file)
189108
+ ACCESS_COURSE_LABEL,
189109
+ ENROLL_NOW_LABEL,
189110
+ REQUEST_ACCESS_LABEL,
189111
+ ENROLL_NOW_COURSE_STARTING_SOON_LABEL,
189112
+ REQUEST_ACCESS_COURSE_STARTING_SOON_LABEL,
189113
+ INVITATION_ONLY_LABEL,
189114
+ BUY_NOW_LABEL,
189115
+ };
189116
+ };
189117
+
189118
+ /**
189119
+ * Hierarchical course outline (chapters → lessons → sublessons) with
189120
+ * completion progress indicators and collapsible tree UI.
189121
+ *
189122
+ * Ported from skillsai (`components/course-outline.tsx`). Reads all state
189123
+ * from `CourseOutlineContext` — callers provide it by wrapping this
189124
+ * component in a `<CourseOutlineContext.Provider value={...} />` (or by
189125
+ * using the provided `CourseContentLayout`, which sets up the provider).
189126
+ */
189127
+ const MAX_CHECKMARK_POINT = 7;
189128
+ const getCompletionRatio = (node) => {
189129
+ if (!Array.isArray(node.children) || node.children.length === 0) {
189130
+ return node.complete ? 1 : 0;
189131
+ }
189132
+ const totalChildren = node.children.length;
189133
+ const completedScore = node.children.reduce((acc, child) => acc + getCompletionRatio(child), 0);
189134
+ return completedScore / totalChildren;
189135
+ };
189136
+ const getCompletionLevel = (node) => {
189137
+ return Math.round(getCompletionRatio(node) * MAX_CHECKMARK_POINT);
189138
+ };
189139
+ const CompletionIcon = ({ node }) => {
189140
+ const level = getCompletionLevel(node);
189141
+ const size = 16;
189142
+ const strokeWidth = 2;
189143
+ const radius = (size - strokeWidth) / 2;
189144
+ const circumference = 2 * Math.PI * radius;
189145
+ const progress = level / MAX_CHECKMARK_POINT;
189146
+ const dashOffset = circumference * (1 - progress);
189147
+ if (level === MAX_CHECKMARK_POINT) {
189148
+ 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" })] }));
189149
+ }
189150
+ if (level === 0) {
189151
+ 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 }) }));
189152
+ }
189153
+ 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})` })] }));
189154
+ };
189155
+ const SkeletonRow = () => jsx("div", { className: "mb-2 h-6 w-full animate-pulse rounded bg-gray-200" });
189156
+ const CourseOutline = () => {
189157
+ const { courseOutline, courseOutlineLoading, expandedModule, expandedLessons, selectLesson, toggleModule, toggleLesson, currentChapter, currentLesson, } = useContext(CourseOutlineContext);
189158
+ 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) &&
189159
+ 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
189160
+ ? 'bg-amber-50 text-amber-700'
189161
+ : '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 &&
189162
+ lesson.children.length > 0 &&
189163
+ 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
189164
+ ? 'bg-amber-50 text-amber-700'
189165
+ : '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)))) }));
189166
+ };
189167
+
189168
+ function Sheet({ ...props }) {
189169
+ return jsx(Root$b, { "data-slot": "sheet", ...props });
189170
+ }
189171
+ function SheetPortal({ ...props }) {
189172
+ return jsx(Portal$6, { "data-slot": "sheet-portal", ...props });
189173
+ }
189174
+ function SheetOverlay({ className, ...props }) {
189175
+ 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 }));
189176
+ }
189177
+ function SheetContent({ className, children, side = 'right', ...props }) {
189178
+ 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' &&
189179
+ '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' &&
189180
+ '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' &&
189181
+ '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' &&
189182
+ '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" })] })] })] }));
189183
+ }
189184
+ function SheetHeader({ className, ...props }) {
189185
+ return (jsx("div", { "data-slot": "sheet-header", className: cn('flex flex-col gap-1.5 p-4', className), ...props }));
189186
+ }
189187
+ function SheetTitle({ className, ...props }) {
189188
+ return (jsx(Title$1, { "data-slot": "sheet-title", className: cn('text-foreground font-semibold', className), ...props }));
189189
+ }
189190
+
189191
+ /**
189192
+ * Mobile drawer wrapping `<CourseOutline />` in a `Sheet` for responsive
189193
+ * course navigation. Ported from skillsai's `course-outline-drawer.tsx`.
189194
+ * All state comes from `CourseOutlineContext`.
189195
+ */
189196
+ const CourseOutlineDrawer = () => {
189197
+ const { course, courseOutlineDrawerOpen, setCourseOutlineDrawerOpen } = useContext(CourseOutlineContext);
189198
+ 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, {})] }) }));
189199
+ };
189200
+
189201
+ /**
189202
+ * Timed exam overlay + countdown. Ported from skillsai
189203
+ * (`components/edx-iframe/timed-exam.tsx`). Reads all state from
189204
+ * `EdxIframeContext`; no internal toasts or routing.
189205
+ *
189206
+ * Errors from the RTK exam mutations are caught and logged via
189207
+ * `console.error` — same behavior as the skillsai original. Callers that
189208
+ * want user-facing error toasts can wrap the data-layer calls themselves
189209
+ * higher up, or intercept via a custom RTK middleware.
189210
+ */
189211
+ const TimedExam = () => {
189212
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
189213
+ const { examInfo, setExamInfo, setRefresher } = useContext(EdxIframeContext);
189214
+ const [isReadyToStart, setIsReadyToStart] = useState(false);
189215
+ const [timeRemaining, setTimeRemaining] = useState(0);
189216
+ const [showFullInstructions, setShowFullInstructions] = useState(false);
189217
+ const [showEndExamModal, setShowEndExamModal] = useState(false);
189218
+ const [updateExamAttempt, { isLoading: isSubmittingExam }] = useUpdateExamAttemptMutation();
189219
+ const [startExam, { isLoading: isStartingExam }] = useStartExamMutation();
189220
+ const [getExamInfo] = useLazyGetExamInfoQuery();
189221
+ // Initialize timer when exam is started
189222
+ useEffect(() => {
189223
+ var _a, _b, _c, _d;
189224
+ 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' &&
189225
+ ((_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)) {
189226
+ setTimeRemaining(Math.floor(examInfo.exam.attempt.time_remaining_seconds));
189227
+ }
189228
+ }, [examInfo]);
189229
+ const updateExamInfo = async () => {
189230
+ if (!examInfo)
189231
+ return;
189232
+ const updated = await getExamInfo({
189233
+ course_id: examInfo.exam.course_id,
189234
+ content_id: examInfo.exam.content_id,
189235
+ is_learning_mfe: true,
189236
+ }, false);
189237
+ setExamInfo((updated === null || updated === void 0 ? void 0 : updated.data) || null);
189238
+ };
189239
+ // Countdown timer
189240
+ useEffect(() => {
189241
+ var _a, _b;
189242
+ 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') {
189243
+ const timer = setInterval(() => {
189244
+ setTimeRemaining((prev) => {
189245
+ var _a;
189246
+ const newTime = prev > 0 ? prev - 1 : 0;
189247
+ if (newTime === 0 && ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id)) {
189248
+ updateExamAttempt({
189249
+ attemptID: examInfo.exam.attempt.attempt_id,
189250
+ action: 'submit',
189251
+ })
189252
+ .unwrap()
189253
+ .then(() => {
189254
+ updateExamInfo();
189255
+ setRefresher(new Date());
189256
+ })
189257
+ .catch((error) => {
189258
+ console.error('Failed to auto-submit exam:', error);
189259
+ });
189260
+ }
189261
+ return newTime;
189262
+ });
189263
+ }, 1000);
189264
+ return () => clearInterval(timer);
189265
+ }
189266
+ return undefined;
189267
+ }, [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]);
189268
+ if (!examInfo) {
189269
+ return null;
189270
+ }
189271
+ const formatTimeRemaining = (seconds) => {
189272
+ const hours = Math.floor(seconds / 3600);
189273
+ const minutes = Math.floor((seconds % 3600) / 60);
189274
+ const secs = seconds % 60;
189275
+ if (hours > 0) {
189276
+ return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
189277
+ }
189278
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
189279
+ };
189280
+ const { exam } = examInfo;
189281
+ const timeLimitHours = Math.floor(exam.time_limit_mins / 60);
189282
+ const timeLimitMinutes = exam.time_limit_mins % 60;
189283
+ const formatTimeLimit = () => {
189284
+ if (timeLimitHours > 0 && timeLimitMinutes > 0) {
189285
+ return `${timeLimitHours} hour${timeLimitHours > 1 ? 's' : ''} ${timeLimitMinutes} minute${timeLimitMinutes > 1 ? 's' : ''}`;
189286
+ }
189287
+ else if (timeLimitHours > 0) {
189288
+ return `${timeLimitHours} hour${timeLimitHours > 1 ? 's' : ''}`;
189289
+ }
189290
+ return `${timeLimitMinutes} minute${timeLimitMinutes > 1 ? 's' : ''}`;
189291
+ };
189292
+ const handleStartExam = async () => {
189293
+ var _a;
189294
+ try {
189295
+ setIsReadyToStart(true);
189296
+ if ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id) {
189297
+ const formData = new FormData();
189298
+ formData.append('exam_id', examInfo.exam.id.toString());
189299
+ formData.append('start_clock', 'true');
189300
+ await startExam(formData).unwrap();
189301
+ await updateExamInfo();
189302
+ }
189303
+ }
189304
+ catch (error) {
189305
+ console.error('Failed to start exam:', error);
189306
+ setIsReadyToStart(false);
189307
+ }
189308
+ };
189309
+ const handleEndExam = () => {
189310
+ setShowEndExamModal(true);
189311
+ };
189312
+ const handleConfirmEndExam = async () => {
189313
+ var _a;
189314
+ try {
189315
+ if ((_a = examInfo === null || examInfo === void 0 ? void 0 : examInfo.exam) === null || _a === void 0 ? void 0 : _a.id) {
189316
+ await updateExamAttempt({
189317
+ attemptID: examInfo.exam.attempt.attempt_id,
189318
+ action: 'submit',
189319
+ }).unwrap();
189320
+ setShowEndExamModal(false);
189321
+ await updateExamInfo();
189322
+ setRefresher(new Date());
189323
+ }
189324
+ }
189325
+ catch (error) {
189326
+ console.error('Failed to submit exam:', error);
189327
+ }
189328
+ };
189329
+ const handleCancelEndExam = () => {
189330
+ setShowEndExamModal(false);
189331
+ };
189332
+ 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') {
189333
+ const isLowTime = timeRemaining <= (examInfo.exam.attempt.low_threshold_sec || 14400);
189334
+ const isCriticalTime = timeRemaining <= (examInfo.exam.attempt.critically_low_threshold_sec || 3600);
189335
+ return (jsxs("div", { className: "mx-auto max-w-4xl px-6 pt-6", children: [jsxs("div", { className: `rounded-lg border p-4 ${isCriticalTime
189336
+ ? 'border-red-200 bg-red-50'
189337
+ : isLowTime
189338
+ ? 'border-yellow-200 bg-yellow-50'
189339
+ : '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." })] })] }) }))] }));
189340
+ }
189341
+ 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') {
189342
+ return null;
189343
+ }
189344
+ 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;
189345
+ const noActiveAttemptTop = !(examInfo === null || examInfo === void 0 ? void 0 : examInfo.active_attempt) || Object.keys(examInfo.active_attempt).length === 0;
189346
+ if (noActiveAttempt || noActiveAttemptTop) {
189347
+ 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." })] })] })] }));
189348
+ }
189349
+ return null;
189350
+ };
189351
+
189352
+ /**
189353
+ * Prop-driven port of skillsai's `CourseAccessGuard`. Responsibilities
189354
+ * unchanged: show a spinner while the course is loading, surface not-found
189355
+ * / unauthorized states via callbacks, render children only on a clean
189356
+ * authorized load. Routing is now the caller's responsibility.
189357
+ *
189358
+ * Callers should stabilize `onUnauthorized` / `onNotFound` with `useCallback`
189359
+ * (or hoist them to module scope) to avoid spurious re-fires when the effect
189360
+ * re-runs.
189361
+ */
189362
+ const CourseAccessGuard = ({ course, courseInfoLoadingState, currentTenant, onUnauthorized, onNotFound, children, }) => {
189363
+ const isLoaded = courseInfoLoadingState === 'successful' || courseInfoLoadingState === 'failure';
189364
+ const isUnauthorizedTenant = isLoaded &&
189365
+ !!(course === null || course === void 0 ? void 0 : course.platform_key) &&
189366
+ course.platform_key !== currentTenant &&
189367
+ course.platform_key !== 'main';
189368
+ const isNotFound = courseInfoLoadingState === 'failure' && !course;
189369
+ useEffect(() => {
189370
+ if (isUnauthorizedTenant) {
189371
+ onUnauthorized === null || onUnauthorized === void 0 ? void 0 : onUnauthorized();
189372
+ }
189373
+ else if (isNotFound) {
189374
+ onNotFound === null || onNotFound === void 0 ? void 0 : onNotFound();
189375
+ }
189376
+ }, [isUnauthorizedTenant, isNotFound, onUnauthorized, onNotFound]);
189377
+ if (!isLoaded || isUnauthorizedTenant || isNotFound) {
189378
+ 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" }) }));
189379
+ }
189380
+ return jsx(Fragment$1, { children: children });
189381
+ };
189382
+
189383
+ /**
189384
+ * Loading skeleton shown while `CourseContentLayout` is hydrating.
189385
+ * Ported from skillsai (`app/course-content/[course_id]/loading.tsx`).
189386
+ */
189387
+ const CourseContentLoading = () => {
189388
+ 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" })] })] }) })] })] })] }));
189389
+ };
189390
+
186826
189391
  /**
186827
189392
  * LoginButton Component
186828
189393
  *
@@ -188480,5 +191045,5 @@ var event = /*#__PURE__*/Object.freeze({
188480
191045
  listen: listen
188481
191046
  });
188482
191047
 
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 };
191048
+ 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
191049
  //# sourceMappingURL=index.esm.js.map