@iblai/iblai-js 2.4.2 → 2.4.4

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.
@@ -3,7 +3,7 @@ import React__default, { useRef, useEffect, useState, useCallback, useLayoutEffe
3
3
  import { useDispatch, useSelector } from 'react-redux';
4
4
  import { selectSessionId, selectActiveChatMessages, useCachedSessionId, chatActions, LOCAL_STORAGE_KEYS, TimeTracker, getInitials, useTenantMetadata, useCurrentTenant, selectRbacPermissions, isPureWatcher, WithPermissions, canWriteField, useStripeUpgrade, openExternalUrl, CHAT_AREA_SIZE, isAlphaNumeric32, checkRbacPermission, getTimeAgo, REQUIRED_ACTIONS_FOR_GROUPS, getVisibleAnalyticsTabs, formatRelativeTime as formatRelativeTime$2, ANONYMOUS_USERNAME, combineCSVData, WATCHER_HIDDEN_REPORTS, canViewPlatformAuditLogs, redirectToAuthSpaJoinTenant, redirectToAuthSpa, getAuthSpaJoinUrl, useOS, selectNumberOfActiveChatMessages, useUsername, selectStreaming, isLoggedIn, TOOLS, hasRemoteAiConfig, isSafariBrowser, useShowAttachment, useShowVoiceCall, useShowVoiceRecorder, useMentorSettings, selectShowingSharedChat, useShowFreeTrialDialog, useEmbedMode, chatInputSliceSelectors, useResponsive, useAccessingPublicRoute, useVisitingTenant, useModelFileUploadCapabilities, useChatFileUpload, useVoiceChat, selectAttachedFiles, MENTOR_CHAT_DOCUMENTS_EXTENSIONS, chatInputSliceActions, removeFile, WithFormPermissions, MENTOR_VISIBILITY } from '@iblai/web-utils';
5
5
  import * as dataLayer from '@iblai/data-layer';
6
- import { useCreateSessionIdMutation, 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, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useGetAllMentorMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, useGetAccessibleUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacActionsDefinitionQuery, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, useDeleteApiKeyMutation, useGetApiKeyQuery, useGetApiKeyFieldPermissionsQuery, useUpdateApiKeyMutation, useGetApiKeysQuery, unwrapApiTokenList, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useCreateStripeCustomerPortalMutation, useSetPlatformConfigurationsMutation, useGetPlatformConfigurationsQuery, useUpdatePlatformMembershipMutation, useGetPlatformMembershipQuery, useGetCustomDomainsQuery, useCreateCustomDomainMutation, useDeleteCustomDomainMutation, useGetStudentMentorCreationStatusQuery, useSetStudentMentorCreationStatusMutation, coreApiSlice, recommendationPromptTypeEnum, useGetRecommendedPromptsListQuery, useCreateRecommendedPromptMutation, useUpdateRecommendedPromptMutation, useDeleteRecommendedPromptMutation, useLazyGetPublicPlatformImageAssetFileUrlQuery, useUpdateTenantMetadataMutation, useCreatePlatformImageAssetMutation, useGetProviderConfigQuery, useCreateProviderConfigMutation, useDeleteProviderConfigMutation, useGetExternalMappingQuery, useGetCredentialsListQuery, useCreateExternalMappingMutation, useDeleteExternalMappingMutation, useGetMemsearchConfigQuery, useUpdateMemsearchConfigMutation, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useGetAiSearchMentorsQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetNotificationsCountQuery, useLazyGetNotificationsQuery, useMarkAllAsReadMutation, useCreateNotificationPreviewMutation, useSendNotificationMutation, useGetMentorsQuery, useUpdateTemplateMutation, useGetTemplateDetailsQuery, useGetTemplatesQuery, useLazyGetTemplateDetailsQuery, useToggleTemplateMutation, useGetTopicsStatsQuery, useGetUsersStatsQuery, useGetSessionStatsQuery, useGetTopicsDetailsStatsQuery, useGetAccessTimeHeatmapQuery, useGetUserDetailsStatsQuery, useGetTranscriptsConversationHeadlineQuery, useGetAverageRatingQuery, useGetFinancialStatsQuery, useGetDetailedFinancialStatsQuery, useGetTranscriptsMessagesDetailsQuery, useGetTranscriptsMessagesQuery, useGetReportDetailQuery, useLazyGetDownloadReportFromURLQuery, useGetMentorPublicSettingsQuery, useGetReportsQuery, useCreateReportMutation, useGetContentAnalyticsQuery, useGetContentAnalyticsDetailsQuery, useGetRevenueQuery, useListSubscribersQuery, useGetAuditLogsQuery, useGetPlatformAuditLogsQuery, PLATFORM_AUDIT_EVENT_TYPES, PLATFORM_AUDIT_ACTION_CODES, useGetMentorMemoriesListQuery, useGetMentorMemoriesQuery, useGetMemoryCategoriesAdminQuery, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetUserEnrolledCoursesQuery, useLazyGetUserAssignedCoursesQuery, useLazyGetUserCredentialsQuery, useLazyGetOverTimeActivityQuery, useLazyGetCatalogSearchQuery, useCreateOrUpdateUserReportedSkillMutation, useCreateOrUpdateUserDesiredSkillMutation, useLazyGetPathwayListQuery, useLazyGetUserAssignedPathwaysQuery, useLazyGetUserEnrolledPathwaysQuery, useLazyGetPathwayCompletionQuery, useLazyGetProgramListQuery, useLazyGetProgramCompletionQuery, useLazyGetUserEnrolledProgramsQuery, useLazyGetAssignedProgramsQuery, useLazyGetUserSkillsPointsQuery, useLazyGetUserReportedSkillsQuery, useLazyGetUserDesiredSkillsQuery, useLazyGetUserCatalogPathwaysQuery, useLazyGetPerLearnerInfoQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useLazyGetPersonnalizedSearchQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetClawMentorConfigQuery, useGetClawInstancesQuery, useGetAgentConfigQuery, useUpdateClawMentorConfigMutation, useCreateClawMentorConfigMutation, useDeleteClawMentorConfigMutation, usePushClawConfigMutation, useCreateClawInstanceMutation, useDeleteClawInstanceMutation, useUpdateAgentConfigMutation, useHealthCheckClawInstanceMutation, useTestConnectivityClawInstanceMutation, useUpdateClawInstanceMutation, useGetAgentSkillResourcesQuery, useCreateAgentSkillResourceMutation, useUpdateAgentSkillResourceMutation, useUploadAgentSkillResourceAssetMutation, useDeleteAgentSkillResourceMutation, useGetAgentSkillsQuery, useGetMentorSkillAssignmentsQuery, useCreateMentorSkillAssignmentMutation, useUpdateMentorSkillAssignmentMutation, useDeleteMentorSkillAssignmentMutation, useCreateAgentSkillMutation, useUpdateAgentSkillMutation, useDeleteAgentSkillMutation, resolveEffectiveAgentSkills, filterSlashSkills, useGetMentorSkillAssignmentsInfiniteQuery, MENTOR_SKILL_ASSIGNMENTS_PAGE_SIZE, useCreateCheckoutMutation, useGetUserProjectsQuery, useUpdateUserProjectMutation, useGetTrainingDocumentsQuery, useGetVectorDocumentsQuery, useDeleteMentorMemoryMutation, useUpdateMentorMemoryMutation, useCreateMentorMemoryMutation, useLazyGetConnectedServiceAuthUrlQuery, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useGetMentorCategoriesQuery, useCreateMentorMutation, useUpdateUserPlatformMetadataMutation, useGetApplicationStatusQuery, useListOwnApplicationsQuery, useGetApplicationFormQuery, useWithdrawApplicationMutation, useRenewApplicationMutation, useCreateApplicationDraftMutation, useSubmitApplicationMutation, useUpdateApplicationMutation, useUploadApplicationAttachmentMutation, useDeleteApplicationAttachmentMutation, useGetPromptCategoriesQuery, useCreatePromptMutation, useDeletePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation } from '@iblai/data-layer';
6
+ import { useCreateSessionIdMutation, 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, useGetLearnerDetailsQuery, useGetUserEnrolledCoursesQuery, useGetUserEnrolledProgramsQuery, useGetUserCatalogPathwaysQuery, useGetUsersAsAssertionsQuery, useGetUserEarnedSkillsQuery, useGetUserReportedSkillsQuery, useGetUserDesiredSkillsQuery, useCreateGlobalMemoryMutation, useGetMemsearchStatusQuery, useGetUserMemorySettingsQuery, useUpdateUserMemorySettingsMutation, useGetGlobalMemoriesQuery, useGetAllMentorMemoriesQuery, useDeleteGlobalMemoryMutation, useInviteUserMutation, usePlatformInvitationsQuery, useCreateCatalogInvitationCourseBulkMutation, useGetCatalogInvitationsCourseQuery, useLazyPlatformUsersQuery, useLazyPlatformUserGroupsQuery, useGetPersonnalizedSearchQuery, useCreateCatalogInvitationProgramBulkMutation, useGetCatalogInvitationsProgramQuery, useUpdateUserRoleMutation, useUpdateUserStatusMutation, useUpdatePlatformUserRoleWithPoliciesMutation, usePlatformUsersQuery, useGetAccessibleUsersQuery, isPoliciesResponse, platformApiSlice, featureTags, useLazyGetRbacTeamsAccessListQuery, useCreateRbacTeamsAccessMutation, useGetRbacGroupsQuery, usePlatformUserGroupsQuery, useCreateRbacGroupMutation, useUpdateRbacGroupMutation, useDeleteRbacGroupMutation, useCreatePlatformUserGroupMutation, useUpdatePlatformUserGroupMutation, useDeletePlatformUserGroupMutation, useGetRbacGroupDetailsQuery, useGetPlatformUserGroupDetailsQuery, useGetRbacPermissionsMutation, useGetRbacRolesQuery, useCreateRbacRoleMutation, useUpdateRbacRoleMutation, useDeleteRbacRoleMutation, useGetRbacRoleDetailsQuery, useGetRbacActionsDefinitionQuery, useGetRbacResourceDiscoveryQuery, useGetRbacResourceListQuery, useGetRbacPoliciesQuery, useCreateRbacPolicyMutation, useUpdateRbacPolicyMutation, useDeleteRbacPolicyMutation, useGetRbacPolicyDetailsQuery, useGetWatchedGroupsQuery, useCreateWatchedGroupMutation, useUpdateWatchedGroupMutation, useDeleteWatchedGroupMutation, useAddWatchedUserMutation, useRemoveWatchedUserMutation, useAddWatcherMutation, useUpdateWatcherMutation, useDeleteWatcherMutation, useGetWatchedUsersQuery, useGetWatchersQuery, WATCHER_NOTIFICATION_EVENTS, WATCHER_NOTIFICATION_EVENT_LABELS, useGetApplicationFormsQuery, useGetApplicationFormDetailQuery, useCreateApplicationFormMutation, useUpdateApplicationFormMutation, useGetApplicationAdminDetailQuery, useInviteApplicationCandidateMutation, useLazyGetApplicationAttachmentFileQuery, useCreateApplicationWaiverMutation, useDeleteApplicationWaiverMutation, useSubmitApplicationOverrideMutation, useUpdateApplicationPlacementMutation, useDeleteApplicationCourseAssignmentMutation, useCreateApplicationNoteMutation, useUpdateApplicationNoteMutation, useDeleteApplicationNoteMutation, useTransitionApplicationMutation, useLinkApplicationCandidateMutation, useCreateApplicationPlacementsMutation, useGetCustomMentorsQuery, useCreateApplicationCourseAssignmentsMutation, useGetApplicationBlocksQuery, useDeleteApplicationBlockMutation, useGetApplicationsPipelineStatsQuery, useGetApplicationsPipelineQuery, useDeleteApiKeyMutation, useGetApiKeyQuery, useGetApiKeyFieldPermissionsQuery, useUpdateApiKeyMutation, useGetApiKeysQuery, unwrapApiTokenList, useCreateApiKeyMutation, useCreateLLMCredentialMutation, useGetCredentialsSchemaQuery, useGetMaskedLLMCredentialsQuery, useGetLlmsQuery, useDeleteIntegrationCredentialMutation, useDeleteCredentialMutation, useCreateIntegrationCredentialMutation, useGetIntegrationCredentialsSchemaQuery, useGetMaskedIntegrationCredentialsQuery, useGetAccountBillingInfoQuery, useUpdateAutoRechargeInfoMutation, useTriggerAutoRechargeMutation, useCreateStripeCustomerPortalMutation, useSetPlatformConfigurationsMutation, useGetPlatformConfigurationsQuery, useUpdatePlatformMembershipMutation, useGetPlatformMembershipQuery, useGetCustomDomainsQuery, useCreateCustomDomainMutation, useDeleteCustomDomainMutation, useGetStudentMentorCreationStatusQuery, useSetStudentMentorCreationStatusMutation, coreApiSlice, recommendationPromptTypeEnum, useGetRecommendedPromptsListQuery, useCreateRecommendedPromptMutation, useUpdateRecommendedPromptMutation, useDeleteRecommendedPromptMutation, useLazyGetPublicPlatformImageAssetFileUrlQuery, useUpdateTenantMetadataMutation, useCreatePlatformImageAssetMutation, useGetProviderConfigQuery, useCreateProviderConfigMutation, useDeleteProviderConfigMutation, useGetExternalMappingQuery, useGetCredentialsListQuery, useCreateExternalMappingMutation, useDeleteExternalMappingMutation, useGetMemsearchConfigQuery, useUpdateMemsearchConfigMutation, useGetStripeConnectStatusQuery, useStartStripeConnectOnboardingMutation, useLazyGetStripeConnectDashboardQuery, useGetAiSearchMentorsQuery, useListPaywallsQuery, useListPricesQuery, useCreatePriceMutation, useUpdatePriceMutation, useDeletePriceMutation, useGetPaywallConfigQuery, useEnablePaywallMutation, useUpdatePaywallMutation, useGetNotificationsCountQuery, useLazyGetNotificationsQuery, useMarkAllAsReadMutation, useCreateNotificationPreviewMutation, useSendNotificationMutation, useGetMentorsQuery, USER_PROGRESS_DATA_POINTS, useResetTemplateMutation, useSendTestTemplateEmailMutation, useUpdateTemplateMutation, useGetTemplateDetailsQuery, USER_PROGRESS_SUMMARY_TYPE, useGetTemplatesQuery, useLazyGetTemplateDetailsQuery, useToggleTemplateMutation, useGetTopicsStatsQuery, useGetUsersStatsQuery, useGetSessionStatsQuery, useGetTopicsDetailsStatsQuery, useGetAccessTimeHeatmapQuery, useGetUserDetailsStatsQuery, useGetTranscriptsConversationHeadlineQuery, useGetAverageRatingQuery, useGetFinancialStatsQuery, useGetDetailedFinancialStatsQuery, useGetTranscriptsMessagesDetailsQuery, useGetTranscriptsMessagesQuery, useGetReportDetailQuery, useLazyGetDownloadReportFromURLQuery, useGetMentorPublicSettingsQuery, useGetReportsQuery, useCreateReportMutation, useGetContentAnalyticsQuery, useGetContentAnalyticsDetailsQuery, useGetRevenueQuery, useListSubscribersQuery, useGetAuditLogsQuery, useGetPlatformAuditLogsQuery, PLATFORM_AUDIT_EVENT_TYPES, PLATFORM_AUDIT_ACTION_CODES, useGetMentorMemoriesListQuery, useGetMentorMemoriesQuery, useGetMemoryCategoriesAdminQuery, useLazyGetCourseMetaDataQuery, useLazyGetCourseCompletionOutlinesQuery, useLazyGetCourseEligibilityQuery, useLazyGetUserEnrolledCoursesQuery, useLazyGetUserAssignedCoursesQuery, useLazyGetUserCredentialsQuery, useLazyGetOverTimeActivityQuery, useLazyGetCatalogSearchQuery, useCreateOrUpdateUserReportedSkillMutation, useCreateOrUpdateUserDesiredSkillMutation, useLazyGetPathwayListQuery, useLazyGetUserAssignedPathwaysQuery, useLazyGetUserEnrolledPathwaysQuery, useLazyGetPathwayCompletionQuery, useLazyGetProgramListQuery, useLazyGetProgramCompletionQuery, useLazyGetUserEnrolledProgramsQuery, useLazyGetAssignedProgramsQuery, useLazyGetUserSkillsPointsQuery, useLazyGetUserReportedSkillsQuery, useLazyGetUserDesiredSkillsQuery, useLazyGetUserCatalogPathwaysQuery, useLazyGetPerLearnerInfoQuery, useLazyGetEdxSSOTokenQuery, useCreateCourseEnrollmentMutation, useCreateStripeCheckoutSessionMutation, useLazyGetCourseProgressQuery, useLazyGetCourseCompletionQuery, useLazyGetPersonnalizedSearchQuery, useUpdateExamAttemptMutation, useStartExamMutation, useLazyGetExamInfoQuery, useGetClawMentorConfigQuery, useGetClawInstancesQuery, useGetAgentConfigQuery, useUpdateClawMentorConfigMutation, useCreateClawMentorConfigMutation, useDeleteClawMentorConfigMutation, usePushClawConfigMutation, useCreateClawInstanceMutation, useDeleteClawInstanceMutation, useUpdateAgentConfigMutation, useHealthCheckClawInstanceMutation, useTestConnectivityClawInstanceMutation, useUpdateClawInstanceMutation, useGetAgentSkillResourcesQuery, useCreateAgentSkillResourceMutation, useUpdateAgentSkillResourceMutation, useUploadAgentSkillResourceAssetMutation, useDeleteAgentSkillResourceMutation, useGetAgentSkillsQuery, useGetMentorSkillAssignmentsQuery, useCreateMentorSkillAssignmentMutation, useUpdateMentorSkillAssignmentMutation, useDeleteMentorSkillAssignmentMutation, useCreateAgentSkillMutation, useUpdateAgentSkillMutation, useDeleteAgentSkillMutation, resolveEffectiveAgentSkills, filterSlashSkills, useGetMentorSkillAssignmentsInfiniteQuery, MENTOR_SKILL_ASSIGNMENTS_PAGE_SIZE, useCreateCheckoutMutation, useGetUserProjectsQuery, useUpdateUserProjectMutation, useGetTrainingDocumentsQuery, useGetVectorDocumentsQuery, useDeleteMentorMemoryMutation, useUpdateMentorMemoryMutation, useCreateMentorMemoryMutation, useLazyGetConnectedServiceAuthUrlQuery, useCreateUserProjectMutation, useDeleteUserProjectMutation, useGetUserProjectDetailsQuery, useEditTrainingDocumentMutation, useAddTrainingDocumentMutation, useLazyGetCredentialsQuery, useGetMentorSettingsQuery, useEditMentorMutation, useGetMentorCategoriesQuery, useCreateMentorMutation, useUpdateUserPlatformMetadataMutation, useGetApplicationStatusQuery, useListOwnApplicationsQuery, useGetApplicationFormQuery, useWithdrawApplicationMutation, useRenewApplicationMutation, useCreateApplicationDraftMutation, useSubmitApplicationMutation, useUpdateApplicationMutation, useUploadApplicationAttachmentMutation, useDeleteApplicationAttachmentMutation, useGetPromptCategoriesQuery, useCreatePromptMutation, useDeletePromptMutation, useGetPromptsSearchQuery, useUpdatePromptMutation, useDeleteTrainingDocumentMutation, useGetTrainingDocumentRetrainScheduleQuery, useCreateTrainingDocumentRetrainScheduleMutation } from '@iblai/data-layer';
7
7
  import { toast } from 'sonner';
8
8
  import { jsx, Fragment as Fragment$1, jsxs } from 'react/jsx-runtime';
9
9
  import * as ReactDOM from 'react-dom';
@@ -61892,6 +61892,92 @@ var componentsProactiveSettingsSection$3 = {
61892
61892
  editAgentPromptAriaLabel: "Edit agent prompt",
61893
61893
  periodicAgentsHelp: "Optional. Select agents to include in the proactive outreach rotation."
61894
61894
  };
61895
+ var componentsTemplateActionsSection$3 = {
61896
+ title: "Test & Reset",
61897
+ description: "Preview the template in your inbox, or discard this platform's customizations.",
61898
+ testEmailLabel: "Send a test email",
61899
+ testEmailHelper: "Sends one rendering of this template to the address you enter.",
61900
+ testEmailPlaceholder: "you@example.com",
61901
+ sendTest: "Send test email",
61902
+ sendingTest: "Sending...",
61903
+ testSuccessTitle: "Test email sent",
61904
+ testSuccessDescription: "Sent to {email}.",
61905
+ testErrorTitle: "Could not send test email",
61906
+ testErrorDescription: "Please check the address and try again.",
61907
+ resetLabel: "Reset to default",
61908
+ resetHelper: "Discards this platform's copy and restores the default template.",
61909
+ reset: "Reset to default",
61910
+ resetting: "Resetting...",
61911
+ resetConfirmTitle: "Reset this template?",
61912
+ resetConfirmDescription: "This platform's customizations will be discarded and the default template restored. This cannot be undone.",
61913
+ resetConfirmCancel: "Cancel",
61914
+ resetConfirmAction: "Reset template",
61915
+ resetSuccessTitle: "Template reset",
61916
+ resetSuccessDescription: "The platform default has been restored.",
61917
+ resetErrorTitle: "Could not reset template",
61918
+ resetErrorDescription: "Please try again."
61919
+ };
61920
+ var componentsTemplateVariablesHelper$3 = {
61921
+ title: "Variables you can use",
61922
+ copyHint: "Click a variable to copy it, then paste it into the subject or body.",
61923
+ insertHint: "Click a variable to insert it.",
61924
+ copiedToast: "Copied {token} to your clipboard.",
61925
+ copyUnavailableToast: "Copy {token} manually — clipboard access is unavailable.",
61926
+ copyAriaLabel: "Copy the {name} variable",
61927
+ insertAriaLabel: "Insert the {name} variable"
61928
+ };
61929
+ var componentsUserProgressSettingsSection$3 = {
61930
+ scheduleTitle: "Schedule & Reporting Period",
61931
+ scheduleDescription: "Choose how often the summary is sent and how far back it looks.",
61932
+ frequencyLabel: "Frequency",
61933
+ selectFrequencyAriaLabel: "Select frequency",
61934
+ selectFrequencyPlaceholder: "Select frequency",
61935
+ daily: "Daily",
61936
+ weekly: "Weekly",
61937
+ monthly: "Monthly",
61938
+ custom: "Custom",
61939
+ cadenceDaily: "Sends every day at the time below.",
61940
+ cadenceWeekly: "Sends every Monday at the time below.",
61941
+ cadenceMonthly: "Sends on the 1st of each month at the time below.",
61942
+ cadenceCustom: "Sends every {days} days at the time below.",
61943
+ cadenceCustomUnset: "Set a custom interval to choose how often the summary is sent.",
61944
+ executionTimeLabel: "Send Time",
61945
+ executionTimeRequired: "Specify the time the summary should be sent.",
61946
+ timezoneLabel: "Timezone",
61947
+ selectTimezoneAriaLabel: "Select timezone",
61948
+ selectTimezonePlaceholder: "Select timezone",
61949
+ useLocalTimezone: "Use local timezone ({timezone})",
61950
+ customIntervalLabel: "Custom Interval (days)",
61951
+ customIntervalRequired: "Set how many days should pass between sends.",
61952
+ dayRangeError: "Enter a whole number of days between 1 and 365.",
61953
+ lookbackLabel: "Reporting Window (days)",
61954
+ lookbackHelp: "How far back each summary looks. This is separate from how often the email is sent — a weekly email can still report on the last 30 days.",
61955
+ lookbackRequired: "Set the reporting window in days.",
61956
+ dataPointsLabel: "Included Data Points",
61957
+ dataPointsHelp: "The email only shows sections that are enabled and have data for that learner.",
61958
+ dataPointCoursesProgressed: "Courses progressed",
61959
+ dataPointCoursesCompleted: "Courses completed",
61960
+ dataPointCredentialsEarned: "Credentials earned",
61961
+ dataPointTimeSpent: "Time spent",
61962
+ dataPointActiveDays: "Active days",
61963
+ dataPointMentorsAccessed: "AI agent activity",
61964
+ noDataPointsWarning: "No data points selected — the summary will have no content to show."
61965
+ };
61966
+ var componentsWatcherEmailSection$3 = {
61967
+ title: "Watcher Email",
61968
+ description: "Sent to a learner's watchers, such as a parent or manager.",
61969
+ fallbackNotice: "Watchers currently receive the learner email. Customize it to send them a different message.",
61970
+ customize: "Customize watcher email",
61971
+ useLearnerEmail: "Use the learner email",
61972
+ subjectLabel: "Watcher Email Subject",
61973
+ subjectHelper: "Supports the same variables as the learner email, plus learner_name.",
61974
+ subjectFallbackHelper: "Leave empty and watchers receive the learner email subject.",
61975
+ subjectPlaceholder: "Enter the watcher email subject...",
61976
+ bodyLabel: "Watcher Email Body",
61977
+ bodyHelper: "Supports the same variables as the learner email, plus learner_name.",
61978
+ bodyFallbackHelper: "Leave empty and watchers receive the learner email body.",
61979
+ bodyPlaceholder: "Enter the watcher email body..."
61980
+ };
61895
61981
  var courseContentAccessiblePaginate$3 = {
61896
61982
  pagination: "Pagination"
61897
61983
  };
@@ -63094,7 +63180,14 @@ var notificationsEditAlertDialog$3 = {
63094
63180
  toastErrorDescription: "Failed to update template. Please try again.",
63095
63181
  cancel: "Cancel",
63096
63182
  saving: "Saving...",
63097
- saveChanges: "Save Changes"
63183
+ saveChanges: "Save Changes",
63184
+ learnerEmailSubjectLabel: "Learner Email Subject",
63185
+ learnerEmailSubjectHelper: "Required. Supports template variables — see the list below the body.",
63186
+ learnerEmailBodyLabel: "Learner Email Body",
63187
+ learnerEmailBodyHelper: "Required. Greet with \"Hey,\" rather than a username — usernames are often auto-generated.",
63188
+ noChangesToast: "No changes to save.",
63189
+ toastValidationTitle: "Check the highlighted fields",
63190
+ toastValidationDescription: "Some values were rejected. Review the fields marked in red."
63098
63191
  };
63099
63192
  var notificationsNotificationDisplay$3 = {
63100
63193
  notificationsAriaLabel: "Notifications",
@@ -66244,6 +66337,10 @@ var en = {
66244
66337
  componentsPolicyAssignmentSection: componentsPolicyAssignmentSection$3,
66245
66338
  componentsPolicyConfigDialog: componentsPolicyConfigDialog$3,
66246
66339
  componentsProactiveSettingsSection: componentsProactiveSettingsSection$3,
66340
+ componentsTemplateActionsSection: componentsTemplateActionsSection$3,
66341
+ componentsTemplateVariablesHelper: componentsTemplateVariablesHelper$3,
66342
+ componentsUserProgressSettingsSection: componentsUserProgressSettingsSection$3,
66343
+ componentsWatcherEmailSection: componentsWatcherEmailSection$3,
66247
66344
  courseContentAccessiblePaginate: courseContentAccessiblePaginate$3,
66248
66345
  courseContentCourseContentLayout: courseContentCourseContentLayout$3,
66249
66346
  courseContentDiscoverFacetsFilter: courseContentDiscoverFacetsFilter$3,
@@ -68531,6 +68628,92 @@ var componentsProactiveSettingsSection$2 = {
68531
68628
  editAgentPromptAriaLabel: "Modifier le prompt de l'agent",
68532
68629
  periodicAgentsHelp: "Facultatif. Sélectionnez les agents à inclure dans la rotation de contact proactif."
68533
68630
  };
68631
+ var componentsTemplateActionsSection$2 = {
68632
+ title: "Tester et réinitialiser",
68633
+ description: "Prévisualisez le modèle dans votre boîte de réception ou annulez les personnalisations de cette plateforme.",
68634
+ testEmailLabel: "Envoyer un e-mail de test",
68635
+ testEmailHelper: "Envoie un rendu de ce modèle à l'adresse que vous saisissez.",
68636
+ testEmailPlaceholder: "vous@exemple.com",
68637
+ sendTest: "Envoyer l'e-mail de test",
68638
+ sendingTest: "Envoi...",
68639
+ testSuccessTitle: "E-mail de test envoyé",
68640
+ testSuccessDescription: "Envoyé à {email}.",
68641
+ testErrorTitle: "Impossible d'envoyer l'e-mail de test",
68642
+ testErrorDescription: "Vérifiez l'adresse et réessayez.",
68643
+ resetLabel: "Réinitialiser par défaut",
68644
+ resetHelper: "Supprime la copie de cette plateforme et restaure le modèle par défaut.",
68645
+ reset: "Réinitialiser par défaut",
68646
+ resetting: "Réinitialisation...",
68647
+ resetConfirmTitle: "Réinitialiser ce modèle ?",
68648
+ resetConfirmDescription: "Les personnalisations de cette plateforme seront supprimées et le modèle par défaut restauré. Cette action est irréversible.",
68649
+ resetConfirmCancel: "Annuler",
68650
+ resetConfirmAction: "Réinitialiser le modèle",
68651
+ resetSuccessTitle: "Modèle réinitialisé",
68652
+ resetSuccessDescription: "Le modèle par défaut de la plateforme a été restauré.",
68653
+ resetErrorTitle: "Impossible de réinitialiser le modèle",
68654
+ resetErrorDescription: "Veuillez réessayer."
68655
+ };
68656
+ var componentsTemplateVariablesHelper$2 = {
68657
+ title: "Variables disponibles",
68658
+ copyHint: "Cliquez sur une variable pour la copier, puis collez-la dans l’objet ou le corps.",
68659
+ insertHint: "Cliquez sur une variable pour l’insérer.",
68660
+ copiedToast: "{token} copié dans le presse-papiers.",
68661
+ copyUnavailableToast: "Copiez {token} manuellement : le presse-papiers est indisponible.",
68662
+ copyAriaLabel: "Copier la variable {name}",
68663
+ insertAriaLabel: "Insérer la variable {name}"
68664
+ };
68665
+ var componentsUserProgressSettingsSection$2 = {
68666
+ scheduleTitle: "Planification et période de rapport",
68667
+ scheduleDescription: "Choisissez la fréquence d’envoi du résumé et la période qu’il couvre.",
68668
+ frequencyLabel: "Fréquence",
68669
+ selectFrequencyAriaLabel: "Sélectionner la fréquence",
68670
+ selectFrequencyPlaceholder: "Sélectionner la fréquence",
68671
+ daily: "Quotidienne",
68672
+ weekly: "Hebdomadaire",
68673
+ monthly: "Mensuelle",
68674
+ custom: "Personnalisée",
68675
+ cadenceDaily: "Envoyé chaque jour à l’heure indiquée ci-dessous.",
68676
+ cadenceWeekly: "Envoyé chaque lundi à l’heure indiquée ci-dessous.",
68677
+ cadenceMonthly: "Envoyé le 1er de chaque mois à l’heure indiquée ci-dessous.",
68678
+ cadenceCustom: "Envoyé tous les {days} jours à l’heure indiquée ci-dessous.",
68679
+ cadenceCustomUnset: "Définissez un intervalle personnalisé pour choisir la fréquence d’envoi du résumé.",
68680
+ executionTimeLabel: "Heure d’envoi",
68681
+ executionTimeRequired: "Indiquez l’heure à laquelle le résumé doit être envoyé.",
68682
+ timezoneLabel: "Fuseau horaire",
68683
+ selectTimezoneAriaLabel: "Sélectionner le fuseau horaire",
68684
+ selectTimezonePlaceholder: "Sélectionner le fuseau horaire",
68685
+ useLocalTimezone: "Utiliser le fuseau horaire local ({timezone})",
68686
+ customIntervalLabel: "Intervalle personnalisé (jours)",
68687
+ customIntervalRequired: "Indiquez le nombre de jours entre deux envois.",
68688
+ dayRangeError: "Saisissez un nombre entier de jours compris entre 1 et 365.",
68689
+ lookbackLabel: "Période de rapport (jours)",
68690
+ lookbackHelp: "La période couverte par chaque résumé. Elle est indépendante de la fréquence d’envoi : un e-mail hebdomadaire peut couvrir les 30 derniers jours.",
68691
+ lookbackRequired: "Indiquez la période de rapport en jours.",
68692
+ dataPointsLabel: "Données incluses",
68693
+ dataPointsHelp: "L’e-mail n’affiche que les sections activées et pour lesquelles l’apprenant a des données.",
68694
+ dataPointCoursesProgressed: "Cours en progression",
68695
+ dataPointCoursesCompleted: "Cours terminés",
68696
+ dataPointCredentialsEarned: "Certifications obtenues",
68697
+ dataPointTimeSpent: "Temps passé",
68698
+ dataPointActiveDays: "Jours actifs",
68699
+ dataPointMentorsAccessed: "Activité des agents IA",
68700
+ noDataPointsWarning: "Aucune donnée sélectionnée : le résumé n’aura aucun contenu à afficher."
68701
+ };
68702
+ var componentsWatcherEmailSection$2 = {
68703
+ title: "E-mail des observateurs",
68704
+ description: "Envoyé aux observateurs de l’apprenant, par exemple un parent ou un responsable.",
68705
+ fallbackNotice: "Les observateurs reçoivent actuellement l’e-mail de l’apprenant. Personnalisez-le pour leur envoyer un message différent.",
68706
+ customize: "Personnaliser l’e-mail des observateurs",
68707
+ useLearnerEmail: "Utiliser l’e-mail de l’apprenant",
68708
+ subjectLabel: "Objet de l’e-mail des observateurs",
68709
+ subjectHelper: "Prend en charge les mêmes variables que l’e-mail de l’apprenant, plus learner_name.",
68710
+ subjectFallbackHelper: "Laissez vide et les observateurs recevront l’objet de l’e-mail de l’apprenant.",
68711
+ subjectPlaceholder: "Saisissez l’objet de l’e-mail des observateurs...",
68712
+ bodyLabel: "Corps de l’e-mail des observateurs",
68713
+ bodyHelper: "Prend en charge les mêmes variables que l’e-mail de l’apprenant, plus learner_name.",
68714
+ bodyFallbackHelper: "Laissez vide et les observateurs recevront le corps de l’e-mail de l’apprenant.",
68715
+ bodyPlaceholder: "Saisissez le corps de l’e-mail des observateurs..."
68716
+ };
68534
68717
  var courseContentAccessiblePaginate$2 = {
68535
68718
  pagination: "Pagination"
68536
68719
  };
@@ -69733,7 +69916,14 @@ var notificationsEditAlertDialog$2 = {
69733
69916
  toastErrorDescription: "Échec de la mise à jour du modèle. Veuillez réessayer.",
69734
69917
  cancel: "Annuler",
69735
69918
  saving: "Enregistrement...",
69736
- saveChanges: "Enregistrer les modifications"
69919
+ saveChanges: "Enregistrer les modifications",
69920
+ learnerEmailSubjectLabel: "Objet de l’e-mail de l’apprenant",
69921
+ learnerEmailSubjectHelper: "Obligatoire. Prend en charge les variables de modèle : voir la liste sous le corps.",
69922
+ learnerEmailBodyLabel: "Corps de l’e-mail de l’apprenant",
69923
+ learnerEmailBodyHelper: "Obligatoire. Utilisez « Bonjour, » plutôt que le nom d’utilisateur : ceux-ci sont souvent générés automatiquement.",
69924
+ noChangesToast: "Aucune modification à enregistrer.",
69925
+ toastValidationTitle: "Vérifiez les champs signalés",
69926
+ toastValidationDescription: "Certaines valeurs ont été rejetées. Vérifiez les champs marqués en rouge."
69737
69927
  };
69738
69928
  var notificationsNotificationDisplay$2 = {
69739
69929
  notificationsAriaLabel: "Notifications",
@@ -72718,6 +72908,10 @@ var fr = {
72718
72908
  componentsPolicyAssignmentSection: componentsPolicyAssignmentSection$2,
72719
72909
  componentsPolicyConfigDialog: componentsPolicyConfigDialog$2,
72720
72910
  componentsProactiveSettingsSection: componentsProactiveSettingsSection$2,
72911
+ componentsTemplateActionsSection: componentsTemplateActionsSection$2,
72912
+ componentsTemplateVariablesHelper: componentsTemplateVariablesHelper$2,
72913
+ componentsUserProgressSettingsSection: componentsUserProgressSettingsSection$2,
72914
+ componentsWatcherEmailSection: componentsWatcherEmailSection$2,
72721
72915
  courseContentAccessiblePaginate: courseContentAccessiblePaginate$2,
72722
72916
  courseContentCourseContentLayout: courseContentCourseContentLayout$2,
72723
72917
  courseContentDiscoverFacetsFilter: courseContentDiscoverFacetsFilter$2,
@@ -75004,6 +75198,92 @@ var componentsProactiveSettingsSection$1 = {
75004
75198
  editAgentPromptAriaLabel: "Editar prompt del agente",
75005
75199
  periodicAgentsHelp: "Opcional. Seleccione los agentes a incluir en la rotación de contacto proactivo."
75006
75200
  };
75201
+ var componentsTemplateActionsSection$1 = {
75202
+ title: "Probar y restablecer",
75203
+ description: "Previsualice la plantilla en su bandeja de entrada o descarte las personalizaciones de esta plataforma.",
75204
+ testEmailLabel: "Enviar un correo de prueba",
75205
+ testEmailHelper: "Envía una versión de esta plantilla a la dirección que indique.",
75206
+ testEmailPlaceholder: "usted@ejemplo.com",
75207
+ sendTest: "Enviar correo de prueba",
75208
+ sendingTest: "Enviando...",
75209
+ testSuccessTitle: "Correo de prueba enviado",
75210
+ testSuccessDescription: "Enviado a {email}.",
75211
+ testErrorTitle: "No se pudo enviar el correo de prueba",
75212
+ testErrorDescription: "Verifique la dirección e inténtelo de nuevo.",
75213
+ resetLabel: "Restablecer al valor predeterminado",
75214
+ resetHelper: "Descarta la copia de esta plataforma y restaura la plantilla predeterminada.",
75215
+ reset: "Restablecer al valor predeterminado",
75216
+ resetting: "Restableciendo...",
75217
+ resetConfirmTitle: "¿Restablecer esta plantilla?",
75218
+ resetConfirmDescription: "Las personalizaciones de esta plataforma se descartarán y se restaurará la plantilla predeterminada. Esta acción no se puede deshacer.",
75219
+ resetConfirmCancel: "Cancelar",
75220
+ resetConfirmAction: "Restablecer plantilla",
75221
+ resetSuccessTitle: "Plantilla restablecida",
75222
+ resetSuccessDescription: "Se restauró el valor predeterminado de la plataforma.",
75223
+ resetErrorTitle: "No se pudo restablecer la plantilla",
75224
+ resetErrorDescription: "Inténtelo de nuevo."
75225
+ };
75226
+ var componentsTemplateVariablesHelper$1 = {
75227
+ title: "Variables que puede usar",
75228
+ copyHint: "Haga clic en una variable para copiarla y péguela en el asunto o el cuerpo.",
75229
+ insertHint: "Haga clic en una variable para insertarla.",
75230
+ copiedToast: "Se copió {token} al portapapeles.",
75231
+ copyUnavailableToast: "Copie {token} manualmente: el portapapeles no está disponible.",
75232
+ copyAriaLabel: "Copiar la variable {name}",
75233
+ insertAriaLabel: "Insertar la variable {name}"
75234
+ };
75235
+ var componentsUserProgressSettingsSection$1 = {
75236
+ scheduleTitle: "Programación y período del informe",
75237
+ scheduleDescription: "Elija con qué frecuencia se envía el resumen y qué período abarca.",
75238
+ frequencyLabel: "Frecuencia",
75239
+ selectFrequencyAriaLabel: "Seleccionar frecuencia",
75240
+ selectFrequencyPlaceholder: "Seleccionar frecuencia",
75241
+ daily: "Diaria",
75242
+ weekly: "Semanal",
75243
+ monthly: "Mensual",
75244
+ custom: "Personalizada",
75245
+ cadenceDaily: "Se envía todos los días a la hora indicada abajo.",
75246
+ cadenceWeekly: "Se envía todos los lunes a la hora indicada abajo.",
75247
+ cadenceMonthly: "Se envía el día 1 de cada mes a la hora indicada abajo.",
75248
+ cadenceCustom: "Se envía cada {days} días a la hora indicada abajo.",
75249
+ cadenceCustomUnset: "Defina un intervalo personalizado para elegir con qué frecuencia se envía el resumen.",
75250
+ executionTimeLabel: "Hora de envío",
75251
+ executionTimeRequired: "Indique la hora a la que debe enviarse el resumen.",
75252
+ timezoneLabel: "Zona horaria",
75253
+ selectTimezoneAriaLabel: "Seleccionar zona horaria",
75254
+ selectTimezonePlaceholder: "Seleccionar zona horaria",
75255
+ useLocalTimezone: "Usar la zona horaria local ({timezone})",
75256
+ customIntervalLabel: "Intervalo personalizado (días)",
75257
+ customIntervalRequired: "Indique cuántos días deben pasar entre envíos.",
75258
+ dayRangeError: "Introduzca un número entero de días entre 1 y 365.",
75259
+ lookbackLabel: "Período del informe (días)",
75260
+ lookbackHelp: "Cuánto tiempo hacia atrás abarca cada resumen. Es independiente de la frecuencia de envío: un correo semanal puede informar sobre los últimos 30 días.",
75261
+ lookbackRequired: "Indique el período del informe en días.",
75262
+ dataPointsLabel: "Datos incluidos",
75263
+ dataPointsHelp: "El correo solo muestra las secciones que están activadas y tienen datos para ese estudiante.",
75264
+ dataPointCoursesProgressed: "Cursos en progreso",
75265
+ dataPointCoursesCompleted: "Cursos completados",
75266
+ dataPointCredentialsEarned: "Credenciales obtenidas",
75267
+ dataPointTimeSpent: "Tiempo dedicado",
75268
+ dataPointActiveDays: "Días activos",
75269
+ dataPointMentorsAccessed: "Actividad de agentes de IA",
75270
+ noDataPointsWarning: "No se seleccionó ningún dato: el resumen no tendrá contenido que mostrar."
75271
+ };
75272
+ var componentsWatcherEmailSection$1 = {
75273
+ title: "Correo para observadores",
75274
+ description: "Se envía a los observadores del estudiante, como un padre, una madre o un supervisor.",
75275
+ fallbackNotice: "Actualmente los observadores reciben el correo del estudiante. Personalícelo para enviarles un mensaje distinto.",
75276
+ customize: "Personalizar el correo para observadores",
75277
+ useLearnerEmail: "Usar el correo del estudiante",
75278
+ subjectLabel: "Asunto del correo para observadores",
75279
+ subjectHelper: "Admite las mismas variables que el correo del estudiante, más learner_name.",
75280
+ subjectFallbackHelper: "Si lo deja vacío, los observadores reciben el asunto del correo del estudiante.",
75281
+ subjectPlaceholder: "Escriba el asunto del correo para observadores...",
75282
+ bodyLabel: "Cuerpo del correo para observadores",
75283
+ bodyHelper: "Admite las mismas variables que el correo del estudiante, más learner_name.",
75284
+ bodyFallbackHelper: "Si lo deja vacío, los observadores reciben el cuerpo del correo del estudiante.",
75285
+ bodyPlaceholder: "Escriba el cuerpo del correo para observadores..."
75286
+ };
75007
75287
  var courseContentAccessiblePaginate$1 = {
75008
75288
  pagination: "Paginación"
75009
75289
  };
@@ -76206,7 +76486,14 @@ var notificationsEditAlertDialog$1 = {
76206
76486
  toastErrorDescription: "No se pudo actualizar la plantilla. Por favor, inténtelo de nuevo.",
76207
76487
  cancel: "Cancelar",
76208
76488
  saving: "Guardando...",
76209
- saveChanges: "Guardar cambios"
76489
+ saveChanges: "Guardar cambios",
76490
+ learnerEmailSubjectLabel: "Asunto del correo del estudiante",
76491
+ learnerEmailSubjectHelper: "Obligatorio. Admite variables de plantilla: consulte la lista debajo del cuerpo.",
76492
+ learnerEmailBodyLabel: "Cuerpo del correo del estudiante",
76493
+ learnerEmailBodyHelper: "Obligatorio. Salude con «Hola,» en lugar de usar el nombre de usuario: suelen generarse automáticamente.",
76494
+ noChangesToast: "No hay cambios que guardar.",
76495
+ toastValidationTitle: "Revise los campos resaltados",
76496
+ toastValidationDescription: "Se rechazaron algunos valores. Revise los campos marcados en rojo."
76210
76497
  };
76211
76498
  var notificationsNotificationDisplay$1 = {
76212
76499
  notificationsAriaLabel: "Notificaciones",
@@ -79191,6 +79478,10 @@ var es = {
79191
79478
  componentsPolicyAssignmentSection: componentsPolicyAssignmentSection$1,
79192
79479
  componentsPolicyConfigDialog: componentsPolicyConfigDialog$1,
79193
79480
  componentsProactiveSettingsSection: componentsProactiveSettingsSection$1,
79481
+ componentsTemplateActionsSection: componentsTemplateActionsSection$1,
79482
+ componentsTemplateVariablesHelper: componentsTemplateVariablesHelper$1,
79483
+ componentsUserProgressSettingsSection: componentsUserProgressSettingsSection$1,
79484
+ componentsWatcherEmailSection: componentsWatcherEmailSection$1,
79194
79485
  courseContentAccessiblePaginate: courseContentAccessiblePaginate$1,
79195
79486
  courseContentCourseContentLayout: courseContentCourseContentLayout$1,
79196
79487
  courseContentDiscoverFacetsFilter: courseContentDiscoverFacetsFilter$1,
@@ -81477,6 +81768,92 @@ var componentsProactiveSettingsSection = {
81477
81768
  editAgentPromptAriaLabel: "编辑代理提示",
81478
81769
  periodicAgentsHelp: "可选。选择要包含在主动外联轮换中的代理。"
81479
81770
  };
81771
+ var componentsTemplateActionsSection = {
81772
+ title: "测试与重置",
81773
+ description: "在您的收件箱中预览模板,或放弃本平台的自定义内容。",
81774
+ testEmailLabel: "发送测试邮件",
81775
+ testEmailHelper: "将此模板的一次渲染结果发送至您输入的地址。",
81776
+ testEmailPlaceholder: "you@example.com",
81777
+ sendTest: "发送测试邮件",
81778
+ sendingTest: "发送中...",
81779
+ testSuccessTitle: "测试邮件已发送",
81780
+ testSuccessDescription: "已发送至 {email}。",
81781
+ testErrorTitle: "无法发送测试邮件",
81782
+ testErrorDescription: "请检查地址后重试。",
81783
+ resetLabel: "重置为默认值",
81784
+ resetHelper: "放弃本平台的副本并恢复默认模板。",
81785
+ reset: "重置为默认值",
81786
+ resetting: "重置中...",
81787
+ resetConfirmTitle: "要重置此模板吗?",
81788
+ resetConfirmDescription: "本平台的自定义内容将被放弃并恢复默认模板。此操作无法撤销。",
81789
+ resetConfirmCancel: "取消",
81790
+ resetConfirmAction: "重置模板",
81791
+ resetSuccessTitle: "模板已重置",
81792
+ resetSuccessDescription: "已恢复平台默认设置。",
81793
+ resetErrorTitle: "无法重置模板",
81794
+ resetErrorDescription: "请重试。"
81795
+ };
81796
+ var componentsTemplateVariablesHelper = {
81797
+ title: "可用变量",
81798
+ copyHint: "点击变量即可复制,然后粘贴到主题或正文中。",
81799
+ insertHint: "点击变量即可插入。",
81800
+ copiedToast: "已将 {token} 复制到剪贴板。",
81801
+ copyUnavailableToast: "请手动复制 {token}:剪贴板不可用。",
81802
+ copyAriaLabel: "复制变量 {name}",
81803
+ insertAriaLabel: "插入变量 {name}"
81804
+ };
81805
+ var componentsUserProgressSettingsSection = {
81806
+ scheduleTitle: "发送计划与统计周期",
81807
+ scheduleDescription: "选择摘要的发送频率及其涵盖的时间范围。",
81808
+ frequencyLabel: "频率",
81809
+ selectFrequencyAriaLabel: "选择频率",
81810
+ selectFrequencyPlaceholder: "选择频率",
81811
+ daily: "每天",
81812
+ weekly: "每周",
81813
+ monthly: "每月",
81814
+ custom: "自定义",
81815
+ cadenceDaily: "每天在下方设定的时间发送。",
81816
+ cadenceWeekly: "每周一在下方设定的时间发送。",
81817
+ cadenceMonthly: "每月 1 日在下方设定的时间发送。",
81818
+ cadenceCustom: "每 {days} 天在下方设定的时间发送。",
81819
+ cadenceCustomUnset: "设置自定义间隔以确定摘要的发送频率。",
81820
+ executionTimeLabel: "发送时间",
81821
+ executionTimeRequired: "请指定摘要的发送时间。",
81822
+ timezoneLabel: "时区",
81823
+ selectTimezoneAriaLabel: "选择时区",
81824
+ selectTimezonePlaceholder: "选择时区",
81825
+ useLocalTimezone: "使用本地时区({timezone})",
81826
+ customIntervalLabel: "自定义间隔(天)",
81827
+ customIntervalRequired: "请设置两次发送之间相隔的天数。",
81828
+ dayRangeError: "请输入 1 到 365 之间的整数天数。",
81829
+ lookbackLabel: "统计周期(天)",
81830
+ lookbackHelp: "每份摘要回溯统计的时间范围。它与发送频率相互独立——每周发送的邮件也可以统计最近 30 天的数据。",
81831
+ lookbackRequired: "请设置以天为单位的统计周期。",
81832
+ dataPointsLabel: "包含的数据项",
81833
+ dataPointsHelp: "邮件仅显示已启用且该学习者有相应数据的板块。",
81834
+ dataPointCoursesProgressed: "课程进展",
81835
+ dataPointCoursesCompleted: "已完成课程",
81836
+ dataPointCredentialsEarned: "已获得证书",
81837
+ dataPointTimeSpent: "学习时长",
81838
+ dataPointActiveDays: "活跃天数",
81839
+ dataPointMentorsAccessed: "AI 代理活动",
81840
+ noDataPointsWarning: "未选择任何数据项——摘要将没有内容可显示。"
81841
+ };
81842
+ var componentsWatcherEmailSection = {
81843
+ title: "关注者邮件",
81844
+ description: "发送给学习者的关注者,例如家长或主管。",
81845
+ fallbackNotice: "关注者目前收到的是学习者邮件。自定义后可向他们发送不同的内容。",
81846
+ customize: "自定义关注者邮件",
81847
+ useLearnerEmail: "使用学习者邮件",
81848
+ subjectLabel: "关注者邮件主题",
81849
+ subjectHelper: "支持与学习者邮件相同的变量,另加 learner_name。",
81850
+ subjectFallbackHelper: "留空则关注者收到学习者邮件的主题。",
81851
+ subjectPlaceholder: "请输入关注者邮件主题...",
81852
+ bodyLabel: "关注者邮件正文",
81853
+ bodyHelper: "支持与学习者邮件相同的变量,另加 learner_name。",
81854
+ bodyFallbackHelper: "留空则关注者收到学习者邮件的正文。",
81855
+ bodyPlaceholder: "请输入关注者邮件正文..."
81856
+ };
81480
81857
  var courseContentAccessiblePaginate = {
81481
81858
  pagination: "分页"
81482
81859
  };
@@ -82679,7 +83056,14 @@ var notificationsEditAlertDialog = {
82679
83056
  toastErrorDescription: "更新模板失败,请重试。",
82680
83057
  cancel: "取消",
82681
83058
  saving: "保存中...",
82682
- saveChanges: "保存更改"
83059
+ saveChanges: "保存更改",
83060
+ learnerEmailSubjectLabel: "学习者邮件主题",
83061
+ learnerEmailSubjectHelper: "必填。支持模板变量——请参阅正文下方的列表。",
83062
+ learnerEmailBodyLabel: "学习者邮件正文",
83063
+ learnerEmailBodyHelper: "必填。请使用“您好,”问候,而不要使用用户名——用户名通常是自动生成的。",
83064
+ noChangesToast: "没有需要保存的更改。",
83065
+ toastValidationTitle: "请检查高亮显示的字段",
83066
+ toastValidationDescription: "部分值被拒绝。请检查标记为红色的字段。"
82683
83067
  };
82684
83068
  var notificationsNotificationDisplay = {
82685
83069
  notificationsAriaLabel: "通知",
@@ -85664,6 +86048,10 @@ var zh = {
85664
86048
  componentsPolicyAssignmentSection: componentsPolicyAssignmentSection,
85665
86049
  componentsPolicyConfigDialog: componentsPolicyConfigDialog,
85666
86050
  componentsProactiveSettingsSection: componentsProactiveSettingsSection,
86051
+ componentsTemplateActionsSection: componentsTemplateActionsSection,
86052
+ componentsTemplateVariablesHelper: componentsTemplateVariablesHelper,
86053
+ componentsUserProgressSettingsSection: componentsUserProgressSettingsSection,
86054
+ componentsWatcherEmailSection: componentsWatcherEmailSection,
85667
86055
  courseContentAccessiblePaginate: courseContentAccessiblePaginate,
85668
86056
  courseContentCourseContentLayout: courseContentCourseContentLayout,
85669
86057
  courseContentDiscoverFacetsFilter: courseContentDiscoverFacetsFilter,
@@ -186339,6 +186727,548 @@ function HumanSupportSection({ platformKey, isSaving, recipientMode, onRecipient
186339
186727
  return (jsxs("div", { className: "space-y-4 border-t border-gray-200 pt-4", children: [jsxs("div", { className: "space-y-1", children: [jsx("h3", { className: "text-sm font-semibold text-gray-900", children: t('componentsHumanSupportSection.heading') }), jsx("p", { className: "text-xs text-gray-500", children: t('componentsHumanSupportSection.headingDescription') })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "recipient-mode", className: "text-sm font-medium text-gray-700", children: t('componentsHumanSupportSection.recipientModeLabel') }), jsxs(Select$1, { value: recipientMode, onValueChange: (value) => onRecipientModeChange(value), disabled: isSaving, children: [jsx(SelectTrigger, { id: "recipient-mode", className: "w-full", children: jsx(SelectValue, { placeholder: t('componentsHumanSupportSection.recipientModePlaceholder') }) }), jsx(SelectContent, { children: RECIPIENT_MODE_OPTIONS.map((option) => (jsx(SelectItem, { value: option.value, children: t(option.labelKey) }, option.value))) })] }), jsx("p", { id: "recipient-mode-help", className: "text-xs text-gray-500", children: t('componentsHumanSupportSection.recipientModeHelp') })] }), isCustomMode && (jsxs("div", { className: "space-y-4 pl-4 border-l-2 border-gray-200", children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { className: "text-sm font-medium text-gray-700", htmlFor: "custom-users", children: t('componentsHumanSupportSection.usersLabel') }), jsx(SearchableMultiSelect, { items: userList, selectedItems: selectedUsers, onSelectionChange: handleUserSelectionChange, searchQuery: userSearch, onSearchChange: setUserSearch, isLoading: isUsersLoading, placeholder: t('componentsHumanSupportSection.usersSearchPlaceholder'), getItemValue: (user) => String(user.user_id), getItemLabel: (user) => user.name || user.username || user.email || String(user.user_id), inputId: "custom-users", ariaDescribedBy: "custom-users-help", className: "max-w-full" }), jsx("p", { id: "custom-users-help", className: "text-xs text-gray-500", children: t('componentsHumanSupportSection.usersHelp') })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { className: "text-sm font-medium text-gray-700", htmlFor: "custom-groups", children: t('componentsHumanSupportSection.groupsLabel') }), jsx(SearchableMultiSelect, { items: groupList, selectedItems: selectedGroups, onSelectionChange: handleGroupSelectionChange, searchQuery: groupSearch, onSearchChange: setGroupSearch, isLoading: isGroupsLoading, placeholder: t('componentsHumanSupportSection.groupsSearchPlaceholder'), getItemValue: (group) => String(group.id), getItemLabel: (group) => group.name, inputId: "custom-groups", ariaDescribedBy: "custom-groups-help", className: "max-w-full" }), jsx("p", { id: "custom-groups-help", className: "text-xs text-gray-500", children: t('componentsHumanSupportSection.groupsHelp') })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { className: "text-sm font-medium text-gray-700", htmlFor: "custom-policies", children: t('componentsHumanSupportSection.policiesLabel') }), jsx(SearchableMultiSelect, { items: policyList, selectedItems: selectedPolicies, onSelectionChange: handlePolicySelectionChange, searchQuery: policySearch, onSearchChange: setPolicySearch, isLoading: isPoliciesLoading, placeholder: t('componentsHumanSupportSection.policiesSearchPlaceholder'), getItemValue: (policy) => String(policy.id), getItemLabel: (policy) => policy.name, inputId: "custom-policies", ariaDescribedBy: "custom-policies-help", className: "max-w-full" }), jsx("p", { id: "custom-policies-help", className: "text-xs text-gray-500", children: t('componentsHumanSupportSection.policiesHelp') })] })] }))] }));
186340
186728
  }
186341
186729
 
186730
+ /**
186731
+ * Timezone option list for schedule pickers.
186732
+ *
186733
+ * `Intl.supportedValuesOf` is the source of truth where available; the fallback
186734
+ * covers the older runtimes that lack it, and any value already stored on the
186735
+ * template is always included so an existing selection is never dropped.
186736
+ */
186737
+ const FALLBACK_TIMEZONES = [
186738
+ 'UTC',
186739
+ 'Africa/Cairo',
186740
+ 'Africa/Johannesburg',
186741
+ 'Africa/Lagos',
186742
+ 'Africa/Nairobi',
186743
+ 'America/Anchorage',
186744
+ 'America/Argentina/Buenos_Aires',
186745
+ 'America/Bogota',
186746
+ 'America/Chicago',
186747
+ 'America/Denver',
186748
+ 'America/Halifax',
186749
+ 'America/Lima',
186750
+ 'America/Los_Angeles',
186751
+ 'America/Mexico_City',
186752
+ 'America/New_York',
186753
+ 'America/Phoenix',
186754
+ 'America/Sao_Paulo',
186755
+ 'America/Toronto',
186756
+ 'America/Vancouver',
186757
+ 'Asia/Bangkok',
186758
+ 'Asia/Dubai',
186759
+ 'Asia/Hong_Kong',
186760
+ 'Asia/Jakarta',
186761
+ 'Asia/Jerusalem',
186762
+ 'Asia/Kolkata',
186763
+ 'Asia/Karachi',
186764
+ 'Asia/Manila',
186765
+ 'Asia/Riyadh',
186766
+ 'Asia/Seoul',
186767
+ 'Asia/Shanghai',
186768
+ 'Asia/Singapore',
186769
+ 'Asia/Tokyo',
186770
+ 'Australia/Adelaide',
186771
+ 'Australia/Brisbane',
186772
+ 'Australia/Melbourne',
186773
+ 'Australia/Perth',
186774
+ 'Australia/Sydney',
186775
+ 'Europe/Amsterdam',
186776
+ 'Europe/Athens',
186777
+ 'Europe/Berlin',
186778
+ 'Europe/Brussels',
186779
+ 'Europe/Dublin',
186780
+ 'Europe/Istanbul',
186781
+ 'Europe/Lisbon',
186782
+ 'Europe/London',
186783
+ 'Europe/Madrid',
186784
+ 'Europe/Moscow',
186785
+ 'Europe/Paris',
186786
+ 'Europe/Rome',
186787
+ 'Europe/Stockholm',
186788
+ 'Europe/Warsaw',
186789
+ 'Europe/Zurich',
186790
+ 'Pacific/Auckland',
186791
+ 'Pacific/Honolulu',
186792
+ ];
186793
+ /**
186794
+ * Returns the sorted IANA timezone names to offer, guaranteeing that every
186795
+ * value in `ensure` appears in the list.
186796
+ */
186797
+ const getTimezoneOptions = (...ensure) => {
186798
+ let zones = [];
186799
+ const supportedValuesOf = Intl.supportedValuesOf;
186800
+ if (typeof supportedValuesOf === 'function') {
186801
+ try {
186802
+ zones = supportedValuesOf('timeZone');
186803
+ }
186804
+ catch (_a) {
186805
+ zones = [];
186806
+ }
186807
+ }
186808
+ const options = new Set(zones.length ? zones : FALLBACK_TIMEZONES);
186809
+ for (const zone of ensure) {
186810
+ if (zone) {
186811
+ options.add(zone);
186812
+ }
186813
+ }
186814
+ return Array.from(options).sort((a, b) => a.localeCompare(b));
186815
+ };
186816
+
186817
+ const DAY_BOUNDS = { min: 1, max: 365 };
186818
+ const DATA_POINT_LABEL_KEYS = {
186819
+ courses_progressed: 'dataPointCoursesProgressed',
186820
+ courses_completed: 'dataPointCoursesCompleted',
186821
+ credentials_earned: 'dataPointCredentialsEarned',
186822
+ time_spent: 'dataPointTimeSpent',
186823
+ active_days: 'dataPointActiveDays',
186824
+ mentors_accessed: 'dataPointMentorsAccessed',
186825
+ };
186826
+ const isDayCountInRange = (value) => {
186827
+ const parsed = Number(value);
186828
+ return Number.isInteger(parsed) && parsed >= DAY_BOUNDS.min && parsed <= DAY_BOUNDS.max;
186829
+ };
186830
+ /**
186831
+ * Client-side rules for the schedule fields, shared by the section (which
186832
+ * renders the messages) and the parent dialog (which gates saving on it).
186833
+ */
186834
+ const validateUserProgressSettings = ({ frequency, executionTime, customIntervalDays, lookbackDays, }) => {
186835
+ const errors = {};
186836
+ if (!executionTime.trim()) {
186837
+ errors.executionTime = 'required';
186838
+ }
186839
+ if (frequency === 'CUSTOM') {
186840
+ if (!customIntervalDays.trim()) {
186841
+ errors.customIntervalDays = 'required';
186842
+ }
186843
+ else if (!isDayCountInRange(customIntervalDays)) {
186844
+ errors.customIntervalDays = 'range';
186845
+ }
186846
+ }
186847
+ if (!lookbackDays.trim()) {
186848
+ errors.lookbackDays = 'required';
186849
+ }
186850
+ else if (!isDayCountInRange(lookbackDays)) {
186851
+ errors.lookbackDays = 'range';
186852
+ }
186853
+ return errors;
186854
+ };
186855
+ /**
186856
+ * Schedule, reporting window and data-point configuration for the User Progress
186857
+ * Summary template.
186858
+ *
186859
+ * Deliberately renders no day-of-week / day-of-month picker: `WEEKLY` always
186860
+ * sends on Monday and `MONTHLY` on day 1, so only the time, timezone and (for
186861
+ * `CUSTOM`) the interval are adjustable.
186862
+ */
186863
+ function UserProgressSettingsSection({ isSaving, userLocalTimezone, frequency, onFrequencyChange, executionTime, onExecutionTimeChange, timezone, onTimezoneChange, customIntervalDays, onCustomIntervalDaysChange, lookbackDays, onLookbackDaysChange, enabledDataPoints, onEnabledDataPointsChange, serverErrors, }) {
186864
+ var _a, _b, _c, _d, _e, _f;
186865
+ const t = useT();
186866
+ const executionTimeErrorId = 'user-progress-execution-time-error';
186867
+ const customIntervalErrorId = 'user-progress-custom-interval-error';
186868
+ const lookbackErrorId = 'user-progress-lookback-error';
186869
+ const lookbackHelpId = 'user-progress-lookback-help';
186870
+ const cadenceNoticeId = 'user-progress-cadence-notice';
186871
+ const dataPointsHelpId = 'user-progress-data-points-help';
186872
+ const timezoneOptions = useMemo(() => getTimezoneOptions(timezone, userLocalTimezone), [timezone, userLocalTimezone]);
186873
+ const errors = validateUserProgressSettings({
186874
+ frequency,
186875
+ executionTime,
186876
+ customIntervalDays,
186877
+ lookbackDays,
186878
+ });
186879
+ const executionTimeError = (_a = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_execution_time) !== null && _a !== void 0 ? _a : (errors.executionTime ? t('componentsUserProgressSettingsSection.executionTimeRequired') : '');
186880
+ const customIntervalError = (_b = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_custom_interval_days) !== null && _b !== void 0 ? _b : (errors.customIntervalDays === 'required'
186881
+ ? t('componentsUserProgressSettingsSection.customIntervalRequired')
186882
+ : errors.customIntervalDays === 'range'
186883
+ ? t('componentsUserProgressSettingsSection.dayRangeError')
186884
+ : '');
186885
+ const lookbackError = (_c = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_lookback_days) !== null && _c !== void 0 ? _c : (errors.lookbackDays === 'required'
186886
+ ? t('componentsUserProgressSettingsSection.lookbackRequired')
186887
+ : errors.lookbackDays === 'range'
186888
+ ? t('componentsUserProgressSettingsSection.dayRangeError')
186889
+ : '');
186890
+ const frequencyError = (_d = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_frequency) !== null && _d !== void 0 ? _d : '';
186891
+ const timezoneError = (_e = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_timezone) !== null && _e !== void 0 ? _e : '';
186892
+ const dataPointsError = (_f = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.user_progress_enabled_data_points) !== null && _f !== void 0 ? _f : '';
186893
+ const showUseLocalTimezone = timezone !== userLocalTimezone;
186894
+ const cadenceNotice = (() => {
186895
+ switch (frequency) {
186896
+ case 'DAILY':
186897
+ return t('componentsUserProgressSettingsSection.cadenceDaily');
186898
+ case 'WEEKLY':
186899
+ return t('componentsUserProgressSettingsSection.cadenceWeekly');
186900
+ case 'MONTHLY':
186901
+ return t('componentsUserProgressSettingsSection.cadenceMonthly');
186902
+ case 'CUSTOM':
186903
+ return customIntervalDays && !errors.customIntervalDays
186904
+ ? t('componentsUserProgressSettingsSection.cadenceCustom', { days: customIntervalDays })
186905
+ : t('componentsUserProgressSettingsSection.cadenceCustomUnset');
186906
+ default:
186907
+ return '';
186908
+ }
186909
+ })();
186910
+ const handleDataPointToggle = (dataPoint, checked) => {
186911
+ if (checked) {
186912
+ if (enabledDataPoints.includes(dataPoint)) {
186913
+ return;
186914
+ }
186915
+ // Keep the canonical order so the payload is stable across toggles.
186916
+ onEnabledDataPointsChange(USER_PROGRESS_DATA_POINTS.filter((key) => key === dataPoint || enabledDataPoints.includes(key)));
186917
+ return;
186918
+ }
186919
+ onEnabledDataPointsChange(enabledDataPoints.filter((key) => key !== dataPoint));
186920
+ };
186921
+ return (jsxs("div", { className: "space-y-4 border-t border-gray-200 pt-4", "data-testid": "user-progress-settings-section", children: [jsxs("div", { className: "space-y-1", children: [jsx("h3", { className: "text-sm font-semibold text-gray-900", children: t('componentsUserProgressSettingsSection.scheduleTitle') }), jsx("p", { className: "text-xs text-gray-500", children: t('componentsUserProgressSettingsSection.scheduleDescription') })] }), jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "user-progress-frequency", className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.frequencyLabel') }), jsxs(Select$1, { value: frequency, onValueChange: (value) => onFrequencyChange(value), disabled: isSaving, children: [jsx(SelectTrigger, { id: "user-progress-frequency", "aria-label": t('componentsUserProgressSettingsSection.selectFrequencyAriaLabel'), "aria-describedby": cadenceNoticeId, children: jsx(SelectValue, { placeholder: t('componentsUserProgressSettingsSection.selectFrequencyPlaceholder') }) }), jsxs(SelectContent, { children: [jsx(SelectItem, { value: "DAILY", children: t('componentsUserProgressSettingsSection.daily') }), jsx(SelectItem, { value: "WEEKLY", children: t('componentsUserProgressSettingsSection.weekly') }), jsx(SelectItem, { value: "MONTHLY", children: t('componentsUserProgressSettingsSection.monthly') }), jsx(SelectItem, { value: "CUSTOM", children: t('componentsUserProgressSettingsSection.custom') })] })] }), jsx("p", { id: cadenceNoticeId, className: "text-xs text-gray-500", children: cadenceNotice }), frequencyError && (jsx("p", { className: "text-sm text-red-600", role: "alert", children: frequencyError }))] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "user-progress-execution-time", className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.executionTimeLabel') }), jsx(Input, { id: "user-progress-execution-time", type: "time", value: executionTime, onChange: (event) => onExecutionTimeChange(event.target.value), disabled: isSaving, required: true, "aria-required": "true", "aria-invalid": Boolean(executionTimeError), "aria-describedby": executionTimeError ? executionTimeErrorId : undefined }), executionTimeError && (jsx("p", { id: executionTimeErrorId, className: "text-sm text-red-600", role: "alert", children: executionTimeError }))] })] }), jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "user-progress-timezone", className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.timezoneLabel') }), jsxs(Select$1, { value: timezone, onValueChange: onTimezoneChange, disabled: isSaving, children: [jsx(SelectTrigger, { id: "user-progress-timezone", "aria-label": t('componentsUserProgressSettingsSection.selectTimezoneAriaLabel'), children: jsx(SelectValue, { placeholder: t('componentsUserProgressSettingsSection.selectTimezonePlaceholder') }) }), jsx(SelectContent, { children: timezoneOptions.map((zone) => (jsx(SelectItem, { value: zone, children: zone }, zone))) })] }), showUseLocalTimezone && (jsx(Button$1, { type: "button", variant: "link", size: "sm", className: "px-0", onClick: () => onTimezoneChange(userLocalTimezone), disabled: isSaving, children: t('componentsUserProgressSettingsSection.useLocalTimezone', {
186922
+ timezone: userLocalTimezone,
186923
+ }) })), timezoneError && (jsx("p", { className: "text-sm text-red-600", role: "alert", children: timezoneError }))] }), frequency === 'CUSTOM' && (jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "user-progress-custom-interval", className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.customIntervalLabel') }), jsx(Input, { id: "user-progress-custom-interval", type: "number", inputMode: "numeric", min: DAY_BOUNDS.min, max: DAY_BOUNDS.max, step: 1, value: customIntervalDays, onChange: (event) => onCustomIntervalDaysChange(event.target.value), disabled: isSaving, required: true, "aria-required": "true", "aria-invalid": Boolean(customIntervalError), "aria-describedby": customIntervalError ? customIntervalErrorId : undefined }), customIntervalError && (jsx("p", { id: customIntervalErrorId, className: "text-sm text-red-600", role: "alert", children: customIntervalError }))] }))] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "user-progress-lookback", className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.lookbackLabel') }), jsx("p", { id: lookbackHelpId, className: "text-xs text-gray-500", children: t('componentsUserProgressSettingsSection.lookbackHelp') }), jsx(Input, { id: "user-progress-lookback", type: "number", inputMode: "numeric", min: DAY_BOUNDS.min, max: DAY_BOUNDS.max, step: 1, value: lookbackDays, onChange: (event) => onLookbackDaysChange(event.target.value), disabled: isSaving, required: true, "aria-required": "true", "aria-invalid": Boolean(lookbackError), "aria-describedby": lookbackError ? `${lookbackHelpId} ${lookbackErrorId}` : lookbackHelpId, className: "md:max-w-[12rem]" }), lookbackError && (jsx("p", { id: lookbackErrorId, className: "text-sm text-red-600", role: "alert", children: lookbackError }))] }), jsxs("fieldset", { className: "space-y-2", disabled: isSaving, children: [jsx("legend", { className: "text-sm font-medium text-gray-700", children: t('componentsUserProgressSettingsSection.dataPointsLabel') }), jsx("p", { id: dataPointsHelpId, className: "text-xs text-gray-500", children: t('componentsUserProgressSettingsSection.dataPointsHelp') }), jsx("div", { className: "grid gap-2 sm:grid-cols-2", children: USER_PROGRESS_DATA_POINTS.map((dataPoint) => {
186924
+ const inputId = `user-progress-data-point-${dataPoint}`;
186925
+ return (jsxs("div", { className: "flex items-center gap-2", children: [jsx(Checkbox, { id: inputId, checked: enabledDataPoints.includes(dataPoint), onCheckedChange: (checked) => handleDataPointToggle(dataPoint, checked === true), disabled: isSaving, "aria-describedby": dataPointsHelpId }), jsx(Label$2, { htmlFor: inputId, className: "text-sm font-normal text-gray-700", children: t(`componentsUserProgressSettingsSection.${DATA_POINT_LABEL_KEYS[dataPoint]}`) })] }, dataPoint));
186926
+ }) }), !enabledDataPoints.length && (jsx("p", { className: "text-xs text-amber-700", role: "status", children: t('componentsUserProgressSettingsSection.noDataPointsWarning') })), dataPointsError && (jsx("p", { className: "text-sm text-red-600", role: "alert", children: dataPointsError }))] })] }));
186927
+ }
186928
+
186929
+ /**
186930
+ * Coerces the read-only `available_context` / `watcher_available_context` map
186931
+ * into `{ variable: description }` entries.
186932
+ *
186933
+ * The API documents an object, but some deployments serialize it as a JSON
186934
+ * string, so both are accepted. Anything else yields an empty list, which hides
186935
+ * the helper rather than rendering a broken one.
186936
+ */
186937
+ const normalizeTemplateContext = (context) => {
186938
+ if (!context) {
186939
+ return [];
186940
+ }
186941
+ let source = context;
186942
+ if (typeof source === 'string') {
186943
+ try {
186944
+ source = JSON.parse(source);
186945
+ }
186946
+ catch (_a) {
186947
+ return [];
186948
+ }
186949
+ }
186950
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
186951
+ return [];
186952
+ }
186953
+ return Object.entries(source).map(([name, description]) => [
186954
+ name,
186955
+ typeof description === 'string' ? description : '',
186956
+ ]);
186957
+ };
186958
+ /** Renders `name` as the Django template token an admin would paste. */
186959
+ const toTemplateToken = (name) => `{{ ${name} }}`;
186960
+ /**
186961
+ * Lists the template variables the API says are available, so the UI never
186962
+ * maintains a static copy of the list.
186963
+ *
186964
+ * Renders nothing when the context is empty or unparseable.
186965
+ */
186966
+ function TemplateVariablesHelper({ context, onInsert, className, }) {
186967
+ const t = useT();
186968
+ const entries = useMemo(() => normalizeTemplateContext(context), [context]);
186969
+ if (!entries.length) {
186970
+ return null;
186971
+ }
186972
+ const handleActivate = async (name) => {
186973
+ const token = toTemplateToken(name);
186974
+ if (onInsert) {
186975
+ onInsert(token);
186976
+ return;
186977
+ }
186978
+ try {
186979
+ await navigator.clipboard.writeText(token);
186980
+ toast.success(t('componentsTemplateVariablesHelper.copiedToast', { token }));
186981
+ }
186982
+ catch (_a) {
186983
+ // Clipboard access can be denied or unavailable (insecure context); the
186984
+ // token is still readable on screen, so this is not worth an error toast.
186985
+ toast.message(t('componentsTemplateVariablesHelper.copyUnavailableToast', { token }));
186986
+ }
186987
+ };
186988
+ return (jsxs("div", { className: `rounded-md border border-gray-200 bg-gray-50 p-3 ${className !== null && className !== void 0 ? className : ''}`, "data-testid": "template-variables-helper", children: [jsx("p", { className: "text-xs font-medium text-gray-700", children: t('componentsTemplateVariablesHelper.title') }), jsx("p", { className: "text-xs text-gray-500", children: onInsert
186989
+ ? t('componentsTemplateVariablesHelper.insertHint')
186990
+ : t('componentsTemplateVariablesHelper.copyHint') }), jsx("dl", { className: "mt-2 grid gap-x-4 gap-y-1 sm:grid-cols-2", children: entries.map(([name, description]) => (jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [jsx("dt", { children: jsx("button", { type: "button", onClick: () => void handleActivate(name), className: "rounded bg-white px-1.5 py-0.5 font-mono text-xs text-blue-700 ring-1 ring-inset ring-gray-200 hover:bg-blue-50 focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:outline-none", "aria-label": onInsert
186991
+ ? t('componentsTemplateVariablesHelper.insertAriaLabel', { name })
186992
+ : t('componentsTemplateVariablesHelper.copyAriaLabel', { name }), children: toTemplateToken(name) }) }), description && jsx("dd", { className: "text-xs text-gray-500", children: description })] }, name))) })] }));
186993
+ }
186994
+
186995
+ /**
186996
+ * Editor for the watcher variant of the email — the copy sent to a learner's
186997
+ * watchers (a parent or manager).
186998
+ *
186999
+ * Both fields are independently nullable: leaving one empty falls back to the
187000
+ * matching learner field, and "use the learner email" clears both.
187001
+ */
187002
+ function WatcherEmailSection({ isSaving, isCustomized, onCustomizedChange, subject, onSubjectChange, body, onBodyChange, context, serverErrors, }) {
187003
+ var _a, _b;
187004
+ const t = useT();
187005
+ const subjectHelperId = 'watcher-email-subject-helper';
187006
+ const subjectErrorId = 'watcher-email-subject-error';
187007
+ const bodyLabelId = 'watcher-email-body-label';
187008
+ const bodyHelperId = 'watcher-email-body-helper';
187009
+ const bodyErrorId = 'watcher-email-body-error';
187010
+ const subjectError = (_a = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.watcher_email_subject) !== null && _a !== void 0 ? _a : '';
187011
+ const bodyError = (_b = serverErrors === null || serverErrors === void 0 ? void 0 : serverErrors.watcher_email_html_template) !== null && _b !== void 0 ? _b : '';
187012
+ return (jsxs("div", { className: "space-y-4 border-t border-gray-200 pt-4", "data-testid": "watcher-email-section", children: [jsxs("div", { className: "flex flex-wrap items-start justify-between gap-2", children: [jsxs("div", { className: "space-y-1", children: [jsx("h3", { className: "text-sm font-semibold text-gray-900", children: t('componentsWatcherEmailSection.title') }), jsx("p", { className: "text-xs text-gray-500", children: t('componentsWatcherEmailSection.description') })] }), isCustomized && (jsx(Button$1, { type: "button", variant: "outline", size: "sm", onClick: () => onCustomizedChange(false), disabled: isSaving, children: t('componentsWatcherEmailSection.useLearnerEmail') }))] }), !isCustomized ? (jsxs("div", { className: "rounded-md border border-blue-100 bg-blue-50 p-3", children: [jsx("p", { className: "text-xs text-blue-900", role: "status", children: t('componentsWatcherEmailSection.fallbackNotice') }), jsx(Button$1, { type: "button", variant: "outline", size: "sm", className: "mt-2 bg-white", onClick: () => onCustomizedChange(true), disabled: isSaving, "data-testid": "watcher-email-customize", children: t('componentsWatcherEmailSection.customize') })] })) : (jsxs(Fragment$1, { children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "watcher-email-subject", className: "text-sm font-medium text-gray-700", children: t('componentsWatcherEmailSection.subjectLabel') }), jsx("p", { id: subjectHelperId, className: "text-xs text-gray-500", children: subject.trim()
187013
+ ? t('componentsWatcherEmailSection.subjectHelper')
187014
+ : t('componentsWatcherEmailSection.subjectFallbackHelper') }), jsx(Input, { id: "watcher-email-subject", value: subject, onChange: (event) => onSubjectChange(event.target.value), placeholder: t('componentsWatcherEmailSection.subjectPlaceholder'), disabled: isSaving, "aria-invalid": Boolean(subjectError), "aria-describedby": subjectError ? `${subjectHelperId} ${subjectErrorId}` : subjectHelperId }), subjectError && (jsx("p", { id: subjectErrorId, className: "text-sm text-red-600", role: "alert", children: subjectError }))] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "watcher-email-body", id: bodyLabelId, className: "text-sm font-medium text-gray-700", children: t('componentsWatcherEmailSection.bodyLabel') }), jsx("p", { id: bodyHelperId, className: "text-xs text-gray-500", children: body.trim()
187015
+ ? t('componentsWatcherEmailSection.bodyHelper')
187016
+ : t('componentsWatcherEmailSection.bodyFallbackHelper') }), jsx(RichTextEditor, { value: body, onChange: onBodyChange, placeholder: t('componentsWatcherEmailSection.bodyPlaceholder'), exportFormat: "html", disabled: isSaving, id: "watcher-email-body", ariaLabelledBy: bodyLabelId, ariaDescribedBy: bodyError ? `${bodyHelperId} ${bodyErrorId}` : bodyHelperId, ariaInvalid: Boolean(bodyError) }), bodyError && (jsx("p", { id: bodyErrorId, className: "text-sm text-red-600", role: "alert", children: bodyError }))] }), jsx(TemplateVariablesHelper, { context: context })] }))] }));
187017
+ }
187018
+
187019
+ var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
187020
+ // @__NO_SIDE_EFFECTS__
187021
+ function createSlottable(ownerName) {
187022
+ const Slottable2 = ({ children }) => {
187023
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
187024
+ };
187025
+ Slottable2.displayName = `${ownerName}.Slottable`;
187026
+ Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
187027
+ return Slottable2;
187028
+ }
187029
+
187030
+ var ROOT_NAME = "AlertDialog";
187031
+ var [createAlertDialogContext] = createContextScope$1(ROOT_NAME, [
187032
+ createDialogScope
187033
+ ]);
187034
+ var useDialogScope = createDialogScope();
187035
+ var AlertDialog$1 = (props) => {
187036
+ const { __scopeAlertDialog, ...alertDialogProps } = props;
187037
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187038
+ return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
187039
+ };
187040
+ AlertDialog$1.displayName = ROOT_NAME;
187041
+ var TRIGGER_NAME = "AlertDialogTrigger";
187042
+ var AlertDialogTrigger = React.forwardRef(
187043
+ (props, forwardedRef) => {
187044
+ const { __scopeAlertDialog, ...triggerProps } = props;
187045
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187046
+ return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
187047
+ }
187048
+ );
187049
+ AlertDialogTrigger.displayName = TRIGGER_NAME;
187050
+ var PORTAL_NAME = "AlertDialogPortal";
187051
+ var AlertDialogPortal$1 = (props) => {
187052
+ const { __scopeAlertDialog, ...portalProps } = props;
187053
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187054
+ return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
187055
+ };
187056
+ AlertDialogPortal$1.displayName = PORTAL_NAME;
187057
+ var OVERLAY_NAME = "AlertDialogOverlay";
187058
+ var AlertDialogOverlay$1 = React.forwardRef(
187059
+ (props, forwardedRef) => {
187060
+ const { __scopeAlertDialog, ...overlayProps } = props;
187061
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187062
+ return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
187063
+ }
187064
+ );
187065
+ AlertDialogOverlay$1.displayName = OVERLAY_NAME;
187066
+ var CONTENT_NAME = "AlertDialogContent";
187067
+ var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME);
187068
+ var Slottable = createSlottable("AlertDialogContent");
187069
+ var AlertDialogContent$1 = React.forwardRef(
187070
+ (props, forwardedRef) => {
187071
+ const { __scopeAlertDialog, children, ...contentProps } = props;
187072
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187073
+ const contentRef = React.useRef(null);
187074
+ const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
187075
+ const cancelRef = React.useRef(null);
187076
+ return /* @__PURE__ */ jsx(
187077
+ DialogPrimitive.WarningProvider,
187078
+ {
187079
+ contentName: CONTENT_NAME,
187080
+ titleName: TITLE_NAME,
187081
+ docsSlug: "alert-dialog",
187082
+ children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
187083
+ DialogPrimitive.Content,
187084
+ {
187085
+ role: "alertdialog",
187086
+ ...dialogScope,
187087
+ ...contentProps,
187088
+ ref: composedRefs,
187089
+ onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
187090
+ event.preventDefault();
187091
+ cancelRef.current?.focus({ preventScroll: true });
187092
+ }),
187093
+ onPointerDownOutside: (event) => event.preventDefault(),
187094
+ onInteractOutside: (event) => event.preventDefault(),
187095
+ children: [
187096
+ /* @__PURE__ */ jsx(Slottable, { children }),
187097
+ /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
187098
+ ]
187099
+ }
187100
+ ) })
187101
+ }
187102
+ );
187103
+ }
187104
+ );
187105
+ AlertDialogContent$1.displayName = CONTENT_NAME;
187106
+ var TITLE_NAME = "AlertDialogTitle";
187107
+ var AlertDialogTitle$1 = React.forwardRef(
187108
+ (props, forwardedRef) => {
187109
+ const { __scopeAlertDialog, ...titleProps } = props;
187110
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187111
+ return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
187112
+ }
187113
+ );
187114
+ AlertDialogTitle$1.displayName = TITLE_NAME;
187115
+ var DESCRIPTION_NAME = "AlertDialogDescription";
187116
+ var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
187117
+ const { __scopeAlertDialog, ...descriptionProps } = props;
187118
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187119
+ return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
187120
+ });
187121
+ AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
187122
+ var ACTION_NAME = "AlertDialogAction";
187123
+ var AlertDialogAction$1 = React.forwardRef(
187124
+ (props, forwardedRef) => {
187125
+ const { __scopeAlertDialog, ...actionProps } = props;
187126
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187127
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
187128
+ }
187129
+ );
187130
+ AlertDialogAction$1.displayName = ACTION_NAME;
187131
+ var CANCEL_NAME = "AlertDialogCancel";
187132
+ var AlertDialogCancel$1 = React.forwardRef(
187133
+ (props, forwardedRef) => {
187134
+ const { __scopeAlertDialog, ...cancelProps } = props;
187135
+ const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
187136
+ const dialogScope = useDialogScope(__scopeAlertDialog);
187137
+ const ref = useComposedRefs$1(forwardedRef, cancelRef);
187138
+ return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
187139
+ }
187140
+ );
187141
+ AlertDialogCancel$1.displayName = CANCEL_NAME;
187142
+ var DescriptionWarning = ({ contentRef }) => {
187143
+ const MESSAGE = `\`${CONTENT_NAME}\` requires a description for the component to be accessible for screen reader users.
187144
+
187145
+ You can add a description to the \`${CONTENT_NAME}\` by passing a \`${DESCRIPTION_NAME}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
187146
+
187147
+ Alternatively, you can use your own component as a description by assigning it an \`id\` and passing the same value to the \`aria-describedby\` prop in \`${CONTENT_NAME}\`. If the description is confusing or duplicative for sighted users, you can use the \`@radix-ui/react-visually-hidden\` primitive as a wrapper around your description component.
187148
+
187149
+ For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
187150
+ React.useEffect(() => {
187151
+ const hasDescription = document.getElementById(
187152
+ contentRef.current?.getAttribute("aria-describedby")
187153
+ );
187154
+ if (!hasDescription) console.warn(MESSAGE);
187155
+ }, [MESSAGE, contentRef]);
187156
+ return null;
187157
+ };
187158
+ var Root2 = AlertDialog$1;
187159
+ var Portal2 = AlertDialogPortal$1;
187160
+ var Overlay2 = AlertDialogOverlay$1;
187161
+ var Content2 = AlertDialogContent$1;
187162
+ var Action = AlertDialogAction$1;
187163
+ var Cancel = AlertDialogCancel$1;
187164
+ var Title2 = AlertDialogTitle$1;
187165
+ var Description2 = AlertDialogDescription$1;
187166
+
187167
+ const AlertDialog = Root2;
187168
+ const AlertDialogPortal = Portal2;
187169
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (jsx(Overlay2, { 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/80', className), ...props, ref: ref })));
187170
+ AlertDialogOverlay.displayName = Overlay2.displayName;
187171
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (jsxs(AlertDialogPortal, { children: [jsx(AlertDialogOverlay, {}), jsx(Content2, { ref: ref, className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg', className), ...props })] })));
187172
+ AlertDialogContent.displayName = Content2.displayName;
187173
+ const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
187174
+ AlertDialogHeader.displayName = 'AlertDialogHeader';
187175
+ const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
187176
+ AlertDialogFooter.displayName = 'AlertDialogFooter';
187177
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
187178
+ AlertDialogTitle.displayName = Title2.displayName;
187179
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
187180
+ AlertDialogDescription.displayName = Description2.displayName;
187181
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
187182
+ AlertDialogAction.displayName = Action.displayName;
187183
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
187184
+ AlertDialogCancel.displayName = Cancel.displayName;
187185
+
187186
+ /** Rough client-side check; the server remains the authority. */
187187
+ const isProbableEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
187188
+ /**
187189
+ * "Reset to default" and "Send test email" actions for a notification template.
187190
+ *
187191
+ * Reset discards the platform-specific copy, so it is confirmed before firing.
187192
+ */
187193
+ function TemplateActionsSection({ platformKey, notificationType, isSaving, onResetComplete, }) {
187194
+ const t = useT();
187195
+ const [testEmail, setTestEmail] = useState('');
187196
+ const [isResetConfirmOpen, setIsResetConfirmOpen] = useState(false);
187197
+ const [resetTemplate, { isLoading: isResetting }] = useResetTemplateMutation();
187198
+ const [sendTestEmail, { isLoading: isSendingTest }] = useSendTestTemplateEmailMutation();
187199
+ const testEmailHelperId = 'template-test-email-helper';
187200
+ const isTestEmailValid = isProbableEmail(testEmail);
187201
+ const isBusy = isSaving || isResetting || isSendingTest;
187202
+ const handleReset = async () => {
187203
+ try {
187204
+ await resetTemplate({ platformKey, notificationType }).unwrap();
187205
+ toast.success(t('componentsTemplateActionsSection.resetSuccessTitle'), {
187206
+ description: t('componentsTemplateActionsSection.resetSuccessDescription'),
187207
+ });
187208
+ onResetComplete === null || onResetComplete === void 0 ? void 0 : onResetComplete();
187209
+ }
187210
+ catch (error) {
187211
+ console.error('Error resetting notification template:', error);
187212
+ toast.error(t('componentsTemplateActionsSection.resetErrorTitle'), {
187213
+ description: t('componentsTemplateActionsSection.resetErrorDescription'),
187214
+ });
187215
+ }
187216
+ finally {
187217
+ setIsResetConfirmOpen(false);
187218
+ }
187219
+ };
187220
+ // Reachable only with a valid address: the button is disabled otherwise.
187221
+ const handleSendTest = async () => {
187222
+ try {
187223
+ await sendTestEmail({
187224
+ platformKey,
187225
+ notificationType,
187226
+ testEmail: testEmail.trim(),
187227
+ }).unwrap();
187228
+ toast.success(t('componentsTemplateActionsSection.testSuccessTitle'), {
187229
+ description: t('componentsTemplateActionsSection.testSuccessDescription', {
187230
+ email: testEmail.trim(),
187231
+ }),
187232
+ });
187233
+ }
187234
+ catch (error) {
187235
+ console.error('Error sending test notification email:', error);
187236
+ toast.error(t('componentsTemplateActionsSection.testErrorTitle'), {
187237
+ description: t('componentsTemplateActionsSection.testErrorDescription'),
187238
+ });
187239
+ }
187240
+ };
187241
+ return (jsxs("div", { className: "space-y-4 border-t border-gray-200 pt-4", "data-testid": "template-actions-section", children: [jsxs("div", { className: "space-y-1", children: [jsx("h3", { className: "text-sm font-semibold text-gray-900", children: t('componentsTemplateActionsSection.title') }), jsx("p", { className: "text-xs text-gray-500", children: t('componentsTemplateActionsSection.description') })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "template-test-email", className: "text-sm font-medium text-gray-700", children: t('componentsTemplateActionsSection.testEmailLabel') }), jsx("p", { id: testEmailHelperId, className: "text-xs text-gray-500", children: t('componentsTemplateActionsSection.testEmailHelper') }), jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [jsx(Input, { id: "template-test-email", type: "email", value: testEmail, onChange: (event) => setTestEmail(event.target.value), placeholder: t('componentsTemplateActionsSection.testEmailPlaceholder'), disabled: isBusy, "aria-describedby": testEmailHelperId, className: "sm:max-w-xs" }), jsx(Button$1, { type: "button", variant: "outline", onClick: () => void handleSendTest(), disabled: isBusy || !isTestEmailValid, "data-testid": "template-send-test-email", children: isSendingTest
187242
+ ? t('componentsTemplateActionsSection.sendingTest')
187243
+ : t('componentsTemplateActionsSection.sendTest') })] })] }), jsxs("div", { className: "space-y-2", children: [jsx("p", { className: "text-sm font-medium text-gray-700", children: t('componentsTemplateActionsSection.resetLabel') }), jsx("p", { className: "text-xs text-gray-500", children: t('componentsTemplateActionsSection.resetHelper') }), jsx(Button$1, { type: "button", variant: "outline", className: "text-red-600 hover:text-red-700", onClick: () => setIsResetConfirmOpen(true), disabled: isBusy, "data-testid": "template-reset-to-default", children: isResetting
187244
+ ? t('componentsTemplateActionsSection.resetting')
187245
+ : t('componentsTemplateActionsSection.reset') })] }), jsx(AlertDialog, { open: isResetConfirmOpen, onOpenChange: setIsResetConfirmOpen, children: jsxs(AlertDialogContent, { children: [jsxs(AlertDialogHeader, { children: [jsx(AlertDialogTitle, { children: t('componentsTemplateActionsSection.resetConfirmTitle') }), jsx(AlertDialogDescription, { children: t('componentsTemplateActionsSection.resetConfirmDescription') })] }), jsxs(AlertDialogFooter, { children: [jsx(AlertDialogCancel, { disabled: isResetting, children: t('componentsTemplateActionsSection.resetConfirmCancel') }), jsx(AlertDialogAction, { onClick: (event) => {
187246
+ // Keep the dialog mounted until the request settles.
187247
+ event.preventDefault();
187248
+ void handleReset();
187249
+ }, disabled: isResetting, className: "bg-red-600 hover:bg-red-700", children: t('componentsTemplateActionsSection.resetConfirmAction') })] })] }) })] }));
187250
+ }
187251
+
187252
+ /**
187253
+ * Removes HTML tags from `value`.
187254
+ *
187255
+ * A single pass of `/<[^>]*>/g` is unsafe: stripping the inner match of nested
187256
+ * markup such as `<<div>script>` re-forms a live `<script>` tag in the output.
187257
+ * Replacing until the string stops changing guarantees no tag can be
187258
+ * reconstructed by the removal itself.
187259
+ */
187260
+ const stripHtmlTags = (value) => {
187261
+ let previous = value;
187262
+ let current = value.replace(/<[^>]*>/g, '');
187263
+ while (current !== previous) {
187264
+ previous = current;
187265
+ current = current.replace(/<[^>]*>/g, '');
187266
+ }
187267
+ return current;
187268
+ };
187269
+
187270
+ const DEFAULT_TIMEZONE = 'UTC';
187271
+ /** Normalizes an API time value to the `"HH:MM"` an `<input type="time">` wants. */
186342
187272
  const toTimeString = (value) => {
186343
187273
  if (!value) {
186344
187274
  return '';
@@ -186352,7 +187282,6 @@ const toTimeString = (value) => {
186352
187282
  }
186353
187283
  return date.toISOString().slice(11, 16);
186354
187284
  };
186355
- const DEFAULT_TIMEZONE = 'UTC';
186356
187285
  const isValidTimeZone$1 = (value) => {
186357
187286
  if (!value) {
186358
187287
  return false;
@@ -186365,6 +187294,10 @@ const isValidTimeZone$1 = (value) => {
186365
187294
  return false;
186366
187295
  }
186367
187296
  };
187297
+ /**
187298
+ * Returns the first valid IANA zone among `candidates`, falling back to the
187299
+ * viewer's own zone and finally to UTC.
187300
+ */
186368
187301
  const resolveTimezone = (...candidates) => {
186369
187302
  for (const candidate of candidates) {
186370
187303
  if (isValidTimeZone$1(candidate)) {
@@ -186377,6 +187310,144 @@ const resolveTimezone = (...candidates) => {
186377
187310
  }
186378
187311
  return DEFAULT_TIMEZONE;
186379
187312
  };
187313
+ /** First candidate that is neither `undefined` nor `null`. */
187314
+ const firstDefined = (...candidates) => {
187315
+ for (const candidate of candidates) {
187316
+ if (candidate !== undefined && candidate !== null) {
187317
+ return candidate;
187318
+ }
187319
+ }
187320
+ return undefined;
187321
+ };
187322
+
187323
+ const DEFAULT_USER_PROGRESS_LOOKBACK_DAYS = '7';
187324
+ const DEFAULT_USER_PROGRESS_FREQUENCY = 'WEEKLY';
187325
+ /**
187326
+ * Pulls a progress config out of `metadata`, which arrives as an object on
187327
+ * current backends and as a JSON string on older ones.
187328
+ */
187329
+ const readMetadataConfig = (metadata) => {
187330
+ var _a;
187331
+ let source = metadata;
187332
+ if (typeof source === 'string') {
187333
+ try {
187334
+ source = JSON.parse(source);
187335
+ }
187336
+ catch (_b) {
187337
+ return undefined;
187338
+ }
187339
+ }
187340
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
187341
+ return undefined;
187342
+ }
187343
+ const container = source;
187344
+ const config = (_a = container.user_progress_config) !== null && _a !== void 0 ? _a : container.courses_progress_config;
187345
+ return config && typeof config === 'object'
187346
+ ? config
187347
+ : undefined;
187348
+ };
187349
+ const toDayString = (value) => typeof value === 'number' && Number.isFinite(value) ? String(value) : '';
187350
+ /**
187351
+ * Reads the schedule, reporting window and data points out of a template
187352
+ * detail response.
187353
+ *
187354
+ * The same settings can arrive in five places, so every field is resolved
187355
+ * through this precedence:
187356
+ *
187357
+ * 1. flat `user_progress_*`
187358
+ * 2. nested `user_progress_config`
187359
+ * 3. flat `courses_progress_*` (deprecated)
187360
+ * 4. nested `courses_progress_config` (deprecated — what backends return today)
187361
+ * 5. `metadata.courses_progress_config`
187362
+ *
187363
+ * Resolving per field rather than picking one source means a partially
187364
+ * populated `user_progress_config` cannot mask a fully populated legacy one.
187365
+ */
187366
+ const readUserProgressSettings = (detail) => {
187367
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
187368
+ const template = detail !== null && detail !== void 0 ? detail : {};
187369
+ const newConfig = template.user_progress_config;
187370
+ const legacyConfig = (_a = template.courses_progress_config) !== null && _a !== void 0 ? _a : undefined;
187371
+ const metadataConfig = readMetadataConfig(template.metadata);
187372
+ const frequency = firstDefined(template.user_progress_frequency, (_b = newConfig === null || newConfig === void 0 ? void 0 : newConfig.schedule) === null || _b === void 0 ? void 0 : _b.frequency, template.courses_progress_frequency, (_c = legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.schedule) === null || _c === void 0 ? void 0 : _c.frequency, (_d = metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.schedule) === null || _d === void 0 ? void 0 : _d.frequency);
187373
+ const executionTime = firstDefined(template.user_progress_execution_time, (_e = newConfig === null || newConfig === void 0 ? void 0 : newConfig.schedule) === null || _e === void 0 ? void 0 : _e.execution_time, template.courses_progress_execution_time, (_f = legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.schedule) === null || _f === void 0 ? void 0 : _f.execution_time, (_g = metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.schedule) === null || _g === void 0 ? void 0 : _g.execution_time);
187374
+ const timezone = firstDefined(template.user_progress_timezone, (_h = newConfig === null || newConfig === void 0 ? void 0 : newConfig.schedule) === null || _h === void 0 ? void 0 : _h.timezone, template.courses_progress_timezone, (_j = legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.schedule) === null || _j === void 0 ? void 0 : _j.timezone, (_k = metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.schedule) === null || _k === void 0 ? void 0 : _k.timezone);
187375
+ const customIntervalDays = firstDefined(template.user_progress_custom_interval_days, (_l = newConfig === null || newConfig === void 0 ? void 0 : newConfig.schedule) === null || _l === void 0 ? void 0 : _l.custom_interval_days, template.courses_progress_custom_interval_days, (_m = legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.schedule) === null || _m === void 0 ? void 0 : _m.custom_interval_days, (_o = metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.schedule) === null || _o === void 0 ? void 0 : _o.custom_interval_days);
187376
+ const lookbackDays = firstDefined(template.user_progress_lookback_days, newConfig === null || newConfig === void 0 ? void 0 : newConfig.lookback_days, template.courses_progress_lookback_days, legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.lookback_days, metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.lookback_days);
187377
+ const enabledDataPoints = firstDefined(template.user_progress_enabled_data_points, newConfig === null || newConfig === void 0 ? void 0 : newConfig.enabled_data_points, template.courses_progress_enabled_data_points, legacyConfig === null || legacyConfig === void 0 ? void 0 : legacyConfig.enabled_data_points, metadataConfig === null || metadataConfig === void 0 ? void 0 : metadataConfig.enabled_data_points);
187378
+ const selected = Array.isArray(enabledDataPoints) ? enabledDataPoints : [];
187379
+ return {
187380
+ frequency: frequency !== null && frequency !== void 0 ? frequency : DEFAULT_USER_PROGRESS_FREQUENCY,
187381
+ executionTime: toTimeString(executionTime),
187382
+ timezone: resolveTimezone(timezone),
187383
+ customIntervalDays: toDayString(customIntervalDays),
187384
+ lookbackDays: toDayString(lookbackDays) || DEFAULT_USER_PROGRESS_LOOKBACK_DAYS,
187385
+ // Drop unknown keys and normalize to the canonical order so the save diff
187386
+ // is order-insensitive.
187387
+ enabledDataPoints: USER_PROGRESS_DATA_POINTS.filter((dataPoint) => selected.includes(dataPoint)),
187388
+ };
187389
+ };
187390
+ /**
187391
+ * `true` when the template has a watcher variant.
187392
+ *
187393
+ * `watcher_available_context` is the documented signal, but backends that
187394
+ * predate it still mark the variant with an `audience:watcher` tag.
187395
+ */
187396
+ const hasWatcherVariant = (detail, watcherContextEntryCount) => {
187397
+ var _a;
187398
+ if (watcherContextEntryCount > 0) {
187399
+ return true;
187400
+ }
187401
+ return Boolean((_a = detail === null || detail === void 0 ? void 0 : detail.tags) === null || _a === void 0 ? void 0 : _a.includes('audience:watcher'));
187402
+ };
187403
+
187404
+ const GENERAL_ERROR_KEYS = ['detail', 'non_field_errors', 'error', 'message'];
187405
+ const flattenMessage = (value) => {
187406
+ if (typeof value === 'string') {
187407
+ return value;
187408
+ }
187409
+ if (Array.isArray(value)) {
187410
+ return value.map(flattenMessage).filter(Boolean).join(' ');
187411
+ }
187412
+ if (value && typeof value === 'object') {
187413
+ return Object.values(value)
187414
+ .map(flattenMessage)
187415
+ .filter(Boolean)
187416
+ .join(' ');
187417
+ }
187418
+ return '';
187419
+ };
187420
+ /**
187421
+ * Maps a DRF `400` response onto per-field messages.
187422
+ *
187423
+ * `iblFetchBaseQuery` rejects with `{ status, data }`, where `data` is the DRF
187424
+ * body — usually `{ field: ["message"] }`, sometimes `{ detail: "message" }`.
187425
+ * Anything that doesn't match those shapes yields empty results so the caller
187426
+ * falls back to a generic toast.
187427
+ */
187428
+ const parseTemplateFieldErrors = (error) => {
187429
+ const fieldErrors = {};
187430
+ let generalError;
187431
+ const data = error === null || error === void 0 ? void 0 : error.data;
187432
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
187433
+ return { fieldErrors, generalError };
187434
+ }
187435
+ for (const [key, value] of Object.entries(data)) {
187436
+ const message = flattenMessage(value);
187437
+ if (!message) {
187438
+ continue;
187439
+ }
187440
+ if (GENERAL_ERROR_KEYS.includes(key)) {
187441
+ generalError = generalError ? `${generalError} ${message}` : message;
187442
+ continue;
187443
+ }
187444
+ fieldErrors[key] = message;
187445
+ }
187446
+ return { fieldErrors, generalError };
187447
+ };
187448
+ /** `true` when the request failed validation rather than erroring some other way. */
187449
+ const isValidationError = (error) => (error === null || error === void 0 ? void 0 : error.status) === 400;
187450
+
186380
187451
  const pickFirstNonEmpty = (...candidates) => {
186381
187452
  for (const candidate of candidates) {
186382
187453
  if (typeof candidate === 'string' && candidate.trim()) {
@@ -186385,8 +187456,23 @@ const pickFirstNonEmpty = (...candidates) => {
186385
187456
  }
186386
187457
  return '';
186387
187458
  };
187459
+ /**
187460
+ * `true` when rich-text HTML carries actual content. An emptied editor still
187461
+ * emits markup such as `<p><br></p>`, which must be treated as "cleared" so the
187462
+ * watcher fields can revert to their learner fallback.
187463
+ */
187464
+ const hasHtmlContent = (value) => {
187465
+ if (!value) {
187466
+ return false;
187467
+ }
187468
+ if (/<img\b/i.test(value)) {
187469
+ return true;
187470
+ }
187471
+ return stripHtmlTags(value).replace(/&nbsp;/gi, ' ').trim().length > 0;
187472
+ };
187473
+ const areDataPointsEqual = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
186388
187474
  function EditAlertDialog({ open, onOpenChange, template, platformKey, templateType, onSave, }) {
186389
- var _a;
187475
+ var _a, _b, _c, _d, _e;
186390
187476
  const t = useT();
186391
187477
  const [messageTitle, setMessageTitle] = useState('');
186392
187478
  const [messageBody, setMessageBody] = useState('');
@@ -186403,14 +187489,29 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186403
187489
  const [policyNotifyOnRemoval, setPolicyNotifyOnRemoval] = useState(true);
186404
187490
  const [humanSupportRecipientMode, setHumanSupportRecipientMode] = useState('platform_admins_and_mentor_owner');
186405
187491
  const [humanSupportCustomRecipients, setHumanSupportCustomRecipients] = useState([]);
187492
+ // User Progress Summary settings
187493
+ const [userProgressFrequency, setUserProgressFrequency] = useState(DEFAULT_USER_PROGRESS_FREQUENCY);
187494
+ const [userProgressExecutionTime, setUserProgressExecutionTime] = useState('');
187495
+ const [userProgressTimezone, setUserProgressTimezone] = useState(() => resolveTimezone());
187496
+ const [userProgressCustomIntervalDays, setUserProgressCustomIntervalDays] = useState('');
187497
+ const [userProgressLookbackDays, setUserProgressLookbackDays] = useState(DEFAULT_USER_PROGRESS_LOOKBACK_DAYS);
187498
+ const [userProgressDataPoints, setUserProgressDataPoints] = useState([]);
187499
+ const [watcherEmailSubject, setWatcherEmailSubject] = useState('');
187500
+ const [watcherEmailBody, setWatcherEmailBody] = useState('');
187501
+ const [isWatcherEmailCustomized, setIsWatcherEmailCustomized] = useState(false);
187502
+ /** Inline `400` messages keyed by API field name. */
187503
+ const [fieldErrors, setFieldErrors] = useState({});
186406
187504
  const dialogDescriptionId = 'edit-alert-dialog-description';
186407
187505
  const messageTitleHelperId = 'edit-alert-message-title-helper';
187506
+ const messageTitleErrorId = 'edit-alert-message-title-error';
186408
187507
  const messageBodyLabelId = 'edit-alert-message-body-label';
186409
187508
  const messageBodyHelperId = 'edit-alert-message-body-helper';
187509
+ const messageBodyErrorId = 'edit-alert-message-body-error';
186410
187510
  const notificationType = (_a = templateType !== null && templateType !== void 0 ? templateType : template === null || template === void 0 ? void 0 : template.type) !== null && _a !== void 0 ? _a : null;
186411
187511
  const isProactive = notificationType === 'PROACTIVE_LEARNER_NOTIFICATION';
186412
187512
  const isPolicyAssignment = notificationType === 'POLICY_ASSIGNMENT';
186413
187513
  const isHumanSupport = notificationType === 'HUMAN_SUPPORT_NOTIFICATION';
187514
+ const isUserProgress = notificationType === USER_PROGRESS_SUMMARY_TYPE;
186414
187515
  const [updateTemplate, { isLoading: isSaving }] = useUpdateTemplateMutation();
186415
187516
  const shouldFetchTemplateDetails = Boolean(open && platformKey && notificationType);
186416
187517
  const userLocalTimezone = useMemo(() => resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone), []);
@@ -186494,6 +187595,38 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186494
187595
  customRecipients: (_b = humanSupportConfig === null || humanSupportConfig === void 0 ? void 0 : humanSupportConfig.custom_recipients) !== null && _b !== void 0 ? _b : [],
186495
187596
  };
186496
187597
  }, [currentTemplateDetail, isHumanSupport, template]);
187598
+ /**
187599
+ * Seeded values for the learner email. Doubles as the baseline the save diff
187600
+ * compares against, so an untouched field is never PATCHed.
187601
+ */
187602
+ const learnerEmailSeed = useMemo(() => ({
187603
+ subject: pickFirstNonEmpty(currentTemplateDetail.email_subject, currentTemplateDetail.message_title, template === null || template === void 0 ? void 0 : template.email_subject, template === null || template === void 0 ? void 0 : template.message_title),
187604
+ body: pickFirstNonEmpty(currentTemplateDetail.email_html_template, currentTemplateDetail.message_body, template === null || template === void 0 ? void 0 : template.email_html_template),
187605
+ }), [currentTemplateDetail, template]);
187606
+ /**
187607
+ * Schedule / period / data points. The settings can arrive under several
187608
+ * different keys depending on the backend build, so the resolver walks them
187609
+ * all — see `readUserProgressSettings`.
187610
+ */
187611
+ const userProgressSettings = useMemo(() => readUserProgressSettings(currentTemplateDetail), [currentTemplateDetail]);
187612
+ const watcherEmailSettings = useMemo(() => {
187613
+ var _a, _b;
187614
+ const subject = (_a = currentTemplateDetail.watcher_email_subject) !== null && _a !== void 0 ? _a : null;
187615
+ const body = (_b = currentTemplateDetail.watcher_email_html_template) !== null && _b !== void 0 ? _b : null;
187616
+ return {
187617
+ subject: subject !== null && subject !== void 0 ? subject : '',
187618
+ body: body !== null && body !== void 0 ? body : '',
187619
+ // Both `null` means watchers currently receive the learner email.
187620
+ isCustomized: Boolean(subject) || Boolean(body),
187621
+ };
187622
+ }, [currentTemplateDetail]);
187623
+ const watcherContext = useMemo(() => normalizeTemplateContext(currentTemplateDetail.watcher_available_context), [currentTemplateDetail]);
187624
+ /**
187625
+ * No watcher variant means the watcher editor is hidden entirely.
187626
+ * `watcher_available_context` is the documented signal; the `audience:watcher`
187627
+ * tag covers backends that don't return that field yet.
187628
+ */
187629
+ const showWatcherEditor = useMemo(() => hasWatcherVariant(currentTemplateDetail, watcherContext.length), [currentTemplateDetail, watcherContext]);
186497
187630
  useEffect(() => {
186498
187631
  try {
186499
187632
  const storedUserName = getUserName$1();
@@ -186526,8 +187659,30 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186526
187659
  setHumanSupportCustomRecipients([]);
186527
187660
  return;
186528
187661
  }
186529
- setMessageTitle(pickFirstNonEmpty(currentTemplateDetail.email_subject, currentTemplateDetail.message_title, template.email_subject, template.message_title));
186530
- setMessageBody(pickFirstNonEmpty(currentTemplateDetail.email_html_template, currentTemplateDetail.message_body, template.email_html_template));
187662
+ setMessageTitle(learnerEmailSeed.subject);
187663
+ setMessageBody(learnerEmailSeed.body);
187664
+ if (isUserProgress) {
187665
+ setUserProgressFrequency(userProgressSettings.frequency);
187666
+ setUserProgressExecutionTime(userProgressSettings.executionTime);
187667
+ setUserProgressTimezone(userProgressSettings.timezone);
187668
+ setUserProgressCustomIntervalDays(userProgressSettings.customIntervalDays);
187669
+ setUserProgressLookbackDays(userProgressSettings.lookbackDays);
187670
+ setUserProgressDataPoints(userProgressSettings.enabledDataPoints);
187671
+ setWatcherEmailSubject(watcherEmailSettings.subject);
187672
+ setWatcherEmailBody(watcherEmailSettings.body);
187673
+ setIsWatcherEmailCustomized(watcherEmailSettings.isCustomized);
187674
+ }
187675
+ else {
187676
+ setUserProgressFrequency(DEFAULT_USER_PROGRESS_FREQUENCY);
187677
+ setUserProgressExecutionTime('');
187678
+ setUserProgressTimezone(userLocalTimezone);
187679
+ setUserProgressCustomIntervalDays('');
187680
+ setUserProgressLookbackDays(DEFAULT_USER_PROGRESS_LOOKBACK_DAYS);
187681
+ setUserProgressDataPoints([]);
187682
+ setWatcherEmailSubject('');
187683
+ setWatcherEmailBody('');
187684
+ setIsWatcherEmailCustomized(false);
187685
+ }
186531
187686
  if (isProactive) {
186532
187687
  setPeriodicLearnerScope(periodicSettings.learnerScope);
186533
187688
  setPeriodicFrequency(periodicSettings.frequency);
@@ -186561,12 +187716,16 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186561
187716
  open,
186562
187717
  template,
186563
187718
  currentTemplateDetail,
187719
+ learnerEmailSeed,
186564
187720
  isProactive,
186565
187721
  periodicSettings,
186566
187722
  isPolicyAssignment,
186567
187723
  policySettings,
186568
187724
  isHumanSupport,
186569
187725
  humanSupportSettings,
187726
+ isUserProgress,
187727
+ userProgressSettings,
187728
+ watcherEmailSettings,
186570
187729
  userLocalTimezone,
186571
187730
  ]);
186572
187731
  const handleMentorsChange = useCallback((mentors) => {
@@ -186579,19 +187738,80 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186579
187738
  setHumanSupportCustomRecipients(recipients);
186580
187739
  }, []);
186581
187740
  const handleSave = async () => {
186582
- var _a, _b, _c, _d;
187741
+ var _a, _b, _c, _d, _e, _f, _g;
186583
187742
  if (!template || !notificationType)
186584
187743
  return;
187744
+ setFieldErrors({});
186585
187745
  try {
186586
187746
  const templateDetail = currentTemplateDetail;
186587
187747
  const periodicConfig = templateDetail.periodic_config;
186588
187748
  const timezoneToSave = resolveTimezone(periodicTimezone, templateDetail.periodic_timezone, periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.timezone);
186589
187749
  const executionTimeValue = toTimeString(periodicExecutionTime);
186590
- const payload = {
186591
- message_title: messageTitle,
186592
- email_subject: messageTitle,
186593
- email_html_template: messageBody,
187750
+ const payload = {};
187751
+ /**
187752
+ * Writes a schedule field under both prefixes. Backends that understand
187753
+ * `user_progress_*` prefer it; older ones only read `courses_progress_*`,
187754
+ * and unknown keys are ignored either way.
187755
+ */
187756
+ const writeUserProgressField = (field, value) => {
187757
+ payload[`user_progress_${field}`] = value;
187758
+ payload[`courses_progress_${field}`] = value;
186594
187759
  };
187760
+ if (isUserProgress) {
187761
+ // Partial write: only send what the admin actually changed.
187762
+ if (messageTitle !== learnerEmailSeed.subject) {
187763
+ payload.message_title = messageTitle;
187764
+ payload.email_subject = messageTitle;
187765
+ }
187766
+ if (messageBody !== learnerEmailSeed.body) {
187767
+ payload.email_html_template = messageBody;
187768
+ }
187769
+ }
187770
+ else {
187771
+ payload.message_title = messageTitle;
187772
+ payload.email_subject = messageTitle;
187773
+ payload.email_html_template = messageBody;
187774
+ }
187775
+ if (isUserProgress) {
187776
+ if (userProgressFrequency !== userProgressSettings.frequency) {
187777
+ writeUserProgressField('frequency', userProgressFrequency);
187778
+ }
187779
+ const userProgressTime = toTimeString(userProgressExecutionTime);
187780
+ if (userProgressTime !== userProgressSettings.executionTime) {
187781
+ writeUserProgressField('execution_time', userProgressTime);
187782
+ }
187783
+ if (userProgressTimezone !== userProgressSettings.timezone) {
187784
+ writeUserProgressField('timezone', userProgressTimezone);
187785
+ }
187786
+ // Only honored for CUSTOM; the other frequencies use a fixed cadence.
187787
+ if (userProgressFrequency === 'CUSTOM' &&
187788
+ userProgressCustomIntervalDays !== userProgressSettings.customIntervalDays) {
187789
+ writeUserProgressField('custom_interval_days', Number(userProgressCustomIntervalDays));
187790
+ }
187791
+ if (userProgressLookbackDays !== userProgressSettings.lookbackDays) {
187792
+ writeUserProgressField('lookback_days', Number(userProgressLookbackDays));
187793
+ }
187794
+ if (!areDataPointsEqual(userProgressDataPoints, userProgressSettings.enabledDataPoints)) {
187795
+ writeUserProgressField('enabled_data_points', userProgressDataPoints);
187796
+ }
187797
+ if (showWatcherEditor) {
187798
+ // An empty field is sent as `null` so watchers fall back to the
187799
+ // matching learner field.
187800
+ const nextWatcherSubject = isWatcherEmailCustomized && watcherEmailSubject.trim() ? watcherEmailSubject : null;
187801
+ const nextWatcherBody = isWatcherEmailCustomized && hasHtmlContent(watcherEmailBody) ? watcherEmailBody : null;
187802
+ if (nextWatcherSubject !== ((_a = currentTemplateDetail.watcher_email_subject) !== null && _a !== void 0 ? _a : null)) {
187803
+ payload.watcher_email_subject = nextWatcherSubject;
187804
+ }
187805
+ if (nextWatcherBody !== ((_b = currentTemplateDetail.watcher_email_html_template) !== null && _b !== void 0 ? _b : null)) {
187806
+ payload.watcher_email_html_template = nextWatcherBody;
187807
+ }
187808
+ }
187809
+ if (!Object.keys(payload).length) {
187810
+ toast.message(t('notificationsEditAlertDialog.noChangesToast'));
187811
+ onOpenChange(false);
187812
+ return;
187813
+ }
187814
+ }
186595
187815
  if (isPolicyAssignment) {
186596
187816
  payload.policy_enabled_policies = policyConfigPayload;
186597
187817
  payload.policy_notify_on_assignment = policyNotifyOnAssignment;
@@ -186622,7 +187842,7 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186622
187842
  }
186623
187843
  }
186624
187844
  else {
186625
- const existingReportPeriod = (_a = templateDetail.periodic_report_period_days) !== null && _a !== void 0 ? _a : periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.report_period_days;
187845
+ const existingReportPeriod = (_c = templateDetail.periodic_report_period_days) !== null && _c !== void 0 ? _c : periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.report_period_days;
186626
187846
  if (typeof existingReportPeriod === 'number') {
186627
187847
  payload.periodic_report_period_days = existingReportPeriod;
186628
187848
  }
@@ -186644,7 +187864,7 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186644
187864
  (periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.execution_time));
186645
187865
  const customIntervalNumber = periodicFrequency === 'CUSTOM' && periodicCustomIntervalDays
186646
187866
  ? Number(periodicCustomIntervalDays)
186647
- : ((_b = template.periodic_custom_interval_days) !== null && _b !== void 0 ? _b : templateDetail.periodic_custom_interval_days);
187867
+ : ((_d = template.periodic_custom_interval_days) !== null && _d !== void 0 ? _d : templateDetail.periodic_custom_interval_days);
186648
187868
  onSave({
186649
187869
  ...template,
186650
187870
  message_title: messageTitle,
@@ -186656,7 +187876,7 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186656
187876
  periodic_execution_time: executionTimeForTemplate,
186657
187877
  periodic_report_period_days: periodicFrequency === 'CUSTOM' && periodicCustomIntervalDays
186658
187878
  ? Number(periodicCustomIntervalDays)
186659
- : ((_d = (_c = template.periodic_report_period_days) !== null && _c !== void 0 ? _c : templateDetail.periodic_report_period_days) !== null && _d !== void 0 ? _d : periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.report_period_days),
187879
+ : ((_f = (_e = template.periodic_report_period_days) !== null && _e !== void 0 ? _e : templateDetail.periodic_report_period_days) !== null && _f !== void 0 ? _f : periodicConfig === null || periodicConfig === void 0 ? void 0 : periodicConfig.report_period_days),
186660
187880
  periodic_timezone: timezoneToSave ||
186661
187881
  template.periodic_timezone ||
186662
187882
  templateDetail.periodic_timezone ||
@@ -186664,12 +187884,29 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186664
187884
  resolveTimezone(),
186665
187885
  periodic_mentors: mentorPayload,
186666
187886
  }),
187887
+ ...(isUserProgress && {
187888
+ user_progress_frequency: userProgressFrequency,
187889
+ user_progress_execution_time: toTimeString(userProgressExecutionTime),
187890
+ user_progress_timezone: userProgressTimezone,
187891
+ user_progress_lookback_days: Number(userProgressLookbackDays),
187892
+ user_progress_enabled_data_points: userProgressDataPoints,
187893
+ ...(userProgressFrequency === 'CUSTOM' && {
187894
+ user_progress_custom_interval_days: Number(userProgressCustomIntervalDays),
187895
+ }),
187896
+ }),
186667
187897
  });
186668
187898
  }
186669
187899
  onOpenChange(false);
186670
187900
  }
186671
187901
  catch (error) {
186672
187902
  console.error('Error updating template:', error);
187903
+ if (isValidationError(error)) {
187904
+ const { fieldErrors: parsedFieldErrors, generalError } = parseTemplateFieldErrors(error);
187905
+ setFieldErrors(parsedFieldErrors);
187906
+ const description = (_g = generalError !== null && generalError !== void 0 ? generalError : Object.values(parsedFieldErrors)[0]) !== null && _g !== void 0 ? _g : t('notificationsEditAlertDialog.toastValidationDescription');
187907
+ toast.error(t('notificationsEditAlertDialog.toastValidationTitle'), { description });
187908
+ return;
187909
+ }
186673
187910
  toast.error(t('notificationsEditAlertDialog.toastErrorTitle'), {
186674
187911
  description: t('notificationsEditAlertDialog.toastErrorDescription'),
186675
187912
  });
@@ -186689,17 +187926,57 @@ function EditAlertDialog({ open, onOpenChange, template, platformKey, templateTy
186689
187926
  setPolicyNotifyOnRemoval(true);
186690
187927
  setHumanSupportRecipientMode('platform_admins_and_mentor_owner');
186691
187928
  setHumanSupportCustomRecipients([]);
187929
+ setUserProgressFrequency(DEFAULT_USER_PROGRESS_FREQUENCY);
187930
+ setUserProgressExecutionTime('');
187931
+ setUserProgressTimezone(userLocalTimezone);
187932
+ setUserProgressCustomIntervalDays('');
187933
+ setUserProgressLookbackDays(DEFAULT_USER_PROGRESS_LOOKBACK_DAYS);
187934
+ setUserProgressDataPoints([]);
187935
+ setWatcherEmailSubject('');
187936
+ setWatcherEmailBody('');
187937
+ setIsWatcherEmailCustomized(false);
187938
+ setFieldErrors({});
186692
187939
  onOpenChange(false);
186693
187940
  };
187941
+ /** Starts from the learner copy so admins have something to edit down. */
187942
+ const handleWatcherCustomizedChange = (customized) => {
187943
+ setIsWatcherEmailCustomized(customized);
187944
+ if (customized && !watcherEmailSubject.trim() && !hasHtmlContent(watcherEmailBody)) {
187945
+ setWatcherEmailSubject(messageTitle);
187946
+ setWatcherEmailBody(messageBody);
187947
+ }
187948
+ };
186694
187949
  const isProactiveInvalid = isProactive &&
186695
187950
  periodicFrequency === 'CUSTOM' &&
186696
187951
  (!periodicCustomIntervalDays || !periodicExecutionTime.trim());
187952
+ const isUserProgressInvalid = isUserProgress &&
187953
+ Object.keys(validateUserProgressSettings({
187954
+ frequency: userProgressFrequency,
187955
+ executionTime: userProgressExecutionTime,
187956
+ customIntervalDays: userProgressCustomIntervalDays,
187957
+ lookbackDays: userProgressLookbackDays,
187958
+ })).length > 0;
186697
187959
  const isSaveDisabled = isSaving ||
186698
187960
  isTemplateDetailsLoading ||
186699
187961
  !messageTitle.trim() ||
186700
187962
  !messageBody.trim() ||
186701
- isProactiveInvalid;
186702
- return (jsx(Dialog, { open: open, onOpenChange: handleClose, children: jsxs(DialogContent, { className: "max-w-3xl h-[85vh] flex flex-col p-0", "aria-describedby": dialogDescriptionId, children: [jsxs(DialogHeader, { className: "px-6 py-4 border-b border-gray-200", children: [jsx(DialogTitle, { className: "text-xl font-semibold", children: t('notificationsEditAlertDialog.dialogTitle') }), jsx(DialogDescription, { id: dialogDescriptionId, children: t('notificationsEditAlertDialog.dialogDescription') })] }), jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: jsx("div", { className: "h-full overflow-y-auto px-6 py-4", children: jsxs("div", { className: "space-y-6", children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "template-name", className: "text-sm font-medium text-gray-700", children: t('notificationsEditAlertDialog.templateNameLabel') }), jsx(Input, { id: "template-name", value: (template === null || template === void 0 ? void 0 : template.description) || '', disabled: true, className: "bg-gray-50" })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "message-title", className: "text-sm font-medium text-gray-700", children: t('notificationsEditAlertDialog.messageTitleLabel') }), jsx("p", { id: messageTitleHelperId, className: "text-xs text-gray-500", children: t('notificationsEditAlertDialog.messageTitleHelper') }), jsx(Input, { id: "message-title", value: messageTitle, onChange: (e) => setMessageTitle(e.target.value), placeholder: t('notificationsEditAlertDialog.messageTitlePlaceholder'), className: "w-full", disabled: isSaving, required: true, "aria-required": "true", "aria-describedby": messageTitleHelperId })] }), jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label$2, { htmlFor: "message-body", id: messageBodyLabelId, className: "text-sm font-medium text-gray-700", children: t('notificationsEditAlertDialog.messageBodyLabel') }), currentTemplateDetail.can_customize === false && (jsx("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: t('notificationsEditAlertDialog.nonCustomizable') }))] }), jsx("p", { id: messageBodyHelperId, className: "text-xs text-gray-500", children: t('notificationsEditAlertDialog.messageBodyHelper') }), jsx(RichTextEditor, { value: messageBody, onChange: setMessageBody, placeholder: t('notificationsEditAlertDialog.messageBodyPlaceholder'), exportFormat: "html", disabled: isSaving || currentTemplateDetail.can_customize === false, id: "message-body", ariaLabelledBy: messageBodyLabelId, ariaDescribedBy: messageBodyHelperId, ariaRequired: true })] }), isPolicyAssignment && (jsx(PolicyAssignmentSection, { platformKey: platformKey, isSaving: isSaving, notifyOnAssignment: policyNotifyOnAssignment, onNotifyOnAssignmentChange: setPolicyNotifyOnAssignment, notifyOnRemoval: policyNotifyOnRemoval, onNotifyOnRemovalChange: setPolicyNotifyOnRemoval, initialPolicyConfigurations: policySettings.enabledPolicies, onPolicyConfigurationsChange: handlePolicyConfigurationsChange })), isHumanSupport && (jsx(HumanSupportSection, { platformKey: platformKey, isSaving: isSaving, recipientMode: humanSupportRecipientMode, onRecipientModeChange: setHumanSupportRecipientMode, initialCustomRecipients: humanSupportSettings.customRecipients, onCustomRecipientsChange: handleHumanSupportRecipientsChange })), isProactive && (jsx(ProactiveSettingsSection, { platformKey: platformKey, username: username, isSaving: isSaving, userLocalTimezone: userLocalTimezone, periodicLearnerScope: periodicLearnerScope, onLearnerScopeChange: setPeriodicLearnerScope, periodicFrequency: periodicFrequency, onFrequencyChange: setPeriodicFrequency, periodicCustomIntervalDays: periodicCustomIntervalDays, onCustomIntervalChange: setPeriodicCustomIntervalDays, periodicExecutionTime: periodicExecutionTime, onExecutionTimeChange: setPeriodicExecutionTime, periodicTimezone: periodicTimezone, onTimezoneChange: setPeriodicTimezone, initialMentors: periodicSettings.mentors, onMentorsChange: handleMentorsChange }))] }) }) }), jsxs("div", { className: "px-6 py-4 border-t border-gray-200 flex justify-end gap-3", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, disabled: isSaving, children: t('notificationsEditAlertDialog.cancel') }), jsx(Button$1, { onClick: handleSave, disabled: isSaveDisabled, className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] hover:opacity-90 text-white", children: isSaving
187963
+ isProactiveInvalid ||
187964
+ isUserProgressInvalid;
187965
+ const messageTitleError = (_c = (_b = fieldErrors.email_subject) !== null && _b !== void 0 ? _b : fieldErrors.message_title) !== null && _c !== void 0 ? _c : '';
187966
+ const messageBodyError = (_e = (_d = fieldErrors.email_html_template) !== null && _d !== void 0 ? _d : fieldErrors.message_body) !== null && _e !== void 0 ? _e : '';
187967
+ return (jsx(Dialog, { open: open, onOpenChange: handleClose, children: jsxs(DialogContent, { className: "max-w-3xl h-[85vh] flex flex-col p-0", "aria-describedby": dialogDescriptionId, children: [jsxs(DialogHeader, { className: "px-6 py-4 border-b border-gray-200", children: [jsx(DialogTitle, { className: "text-xl font-semibold", children: t('notificationsEditAlertDialog.dialogTitle') }), jsx(DialogDescription, { id: dialogDescriptionId, children: t('notificationsEditAlertDialog.dialogDescription') })] }), jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: jsx("div", { className: "h-full overflow-y-auto px-6 py-4", children: jsxs("div", { className: "space-y-6", children: [jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "template-name", className: "text-sm font-medium text-gray-700", children: t('notificationsEditAlertDialog.templateNameLabel') }), jsx(Input, { id: "template-name", value: (template === null || template === void 0 ? void 0 : template.description) || '', disabled: true, className: "bg-gray-50" })] }), jsxs("div", { className: "space-y-2", children: [jsx(Label$2, { htmlFor: "message-title", className: "text-sm font-medium text-gray-700", children: isUserProgress
187968
+ ? t('notificationsEditAlertDialog.learnerEmailSubjectLabel')
187969
+ : t('notificationsEditAlertDialog.messageTitleLabel') }), jsx("p", { id: messageTitleHelperId, className: "text-xs text-gray-500", children: isUserProgress
187970
+ ? t('notificationsEditAlertDialog.learnerEmailSubjectHelper')
187971
+ : t('notificationsEditAlertDialog.messageTitleHelper') }), jsx(Input, { id: "message-title", value: messageTitle, onChange: (e) => setMessageTitle(e.target.value), placeholder: t('notificationsEditAlertDialog.messageTitlePlaceholder'), className: "w-full", disabled: isSaving, required: true, "aria-required": "true", "aria-invalid": Boolean(messageTitleError), "aria-describedby": messageTitleError
187972
+ ? `${messageTitleHelperId} ${messageTitleErrorId}`
187973
+ : messageTitleHelperId }), messageTitleError && (jsx("p", { id: messageTitleErrorId, className: "text-sm text-red-600", role: "alert", children: messageTitleError }))] }), jsxs("div", { className: "space-y-2", children: [jsxs("div", { className: "flex items-center gap-2", children: [jsx(Label$2, { htmlFor: "message-body", id: messageBodyLabelId, className: "text-sm font-medium text-gray-700", children: isUserProgress
187974
+ ? t('notificationsEditAlertDialog.learnerEmailBodyLabel')
187975
+ : t('notificationsEditAlertDialog.messageBodyLabel') }), currentTemplateDetail.can_customize === false && (jsx("span", { className: "inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600", children: t('notificationsEditAlertDialog.nonCustomizable') }))] }), jsx("p", { id: messageBodyHelperId, className: "text-xs text-gray-500", children: isUserProgress
187976
+ ? t('notificationsEditAlertDialog.learnerEmailBodyHelper')
187977
+ : t('notificationsEditAlertDialog.messageBodyHelper') }), jsx(RichTextEditor, { value: messageBody, onChange: setMessageBody, placeholder: t('notificationsEditAlertDialog.messageBodyPlaceholder'), exportFormat: "html", disabled: isSaving || currentTemplateDetail.can_customize === false, id: "message-body", ariaLabelledBy: messageBodyLabelId, ariaDescribedBy: messageBodyError
187978
+ ? `${messageBodyHelperId} ${messageBodyErrorId}`
187979
+ : messageBodyHelperId, ariaRequired: true, ariaInvalid: Boolean(messageBodyError) }), messageBodyError && (jsx("p", { id: messageBodyErrorId, className: "text-sm text-red-600", role: "alert", children: messageBodyError })), isUserProgress && (jsx(TemplateVariablesHelper, { context: currentTemplateDetail.available_context }))] }), isPolicyAssignment && (jsx(PolicyAssignmentSection, { platformKey: platformKey, isSaving: isSaving, notifyOnAssignment: policyNotifyOnAssignment, onNotifyOnAssignmentChange: setPolicyNotifyOnAssignment, notifyOnRemoval: policyNotifyOnRemoval, onNotifyOnRemovalChange: setPolicyNotifyOnRemoval, initialPolicyConfigurations: policySettings.enabledPolicies, onPolicyConfigurationsChange: handlePolicyConfigurationsChange })), isHumanSupport && (jsx(HumanSupportSection, { platformKey: platformKey, isSaving: isSaving, recipientMode: humanSupportRecipientMode, onRecipientModeChange: setHumanSupportRecipientMode, initialCustomRecipients: humanSupportSettings.customRecipients, onCustomRecipientsChange: handleHumanSupportRecipientsChange })), isProactive && (jsx(ProactiveSettingsSection, { platformKey: platformKey, username: username, isSaving: isSaving, userLocalTimezone: userLocalTimezone, periodicLearnerScope: periodicLearnerScope, onLearnerScopeChange: setPeriodicLearnerScope, periodicFrequency: periodicFrequency, onFrequencyChange: setPeriodicFrequency, periodicCustomIntervalDays: periodicCustomIntervalDays, onCustomIntervalChange: setPeriodicCustomIntervalDays, periodicExecutionTime: periodicExecutionTime, onExecutionTimeChange: setPeriodicExecutionTime, periodicTimezone: periodicTimezone, onTimezoneChange: setPeriodicTimezone, initialMentors: periodicSettings.mentors, onMentorsChange: handleMentorsChange })), isUserProgress && (jsxs(Fragment$1, { children: [showWatcherEditor && (jsx(WatcherEmailSection, { isSaving: isSaving, isCustomized: isWatcherEmailCustomized, onCustomizedChange: handleWatcherCustomizedChange, subject: watcherEmailSubject, onSubjectChange: setWatcherEmailSubject, body: watcherEmailBody, onBodyChange: setWatcherEmailBody, context: currentTemplateDetail.watcher_available_context, serverErrors: fieldErrors })), jsx(UserProgressSettingsSection, { isSaving: isSaving, userLocalTimezone: userLocalTimezone, frequency: userProgressFrequency, onFrequencyChange: setUserProgressFrequency, executionTime: userProgressExecutionTime, onExecutionTimeChange: setUserProgressExecutionTime, timezone: userProgressTimezone, onTimezoneChange: setUserProgressTimezone, customIntervalDays: userProgressCustomIntervalDays, onCustomIntervalDaysChange: setUserProgressCustomIntervalDays, lookbackDays: userProgressLookbackDays, onLookbackDaysChange: setUserProgressLookbackDays, enabledDataPoints: userProgressDataPoints, onEnabledDataPointsChange: setUserProgressDataPoints, serverErrors: fieldErrors }), notificationType && (jsx(TemplateActionsSection, { platformKey: platformKey, notificationType: notificationType, isSaving: isSaving, onResetComplete: () => setFieldErrors({}) }))] }))] }) }) }), jsxs("div", { className: "px-6 py-4 border-t border-gray-200 flex justify-end gap-3", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, disabled: isSaving, children: t('notificationsEditAlertDialog.cancel') }), jsx(Button$1, { onClick: handleSave, disabled: isSaveDisabled, className: "bg-gradient-to-r from-[#2563EB] to-[#93C5FD] hover:opacity-90 text-white", children: isSaving
186703
187980
  ? t('notificationsEditAlertDialog.saving')
186704
187981
  : t('notificationsEditAlertDialog.saveChanges') })] })] }) }));
186705
187982
  }
@@ -186841,8 +188118,9 @@ function NotificationDisplay({ org, userId, isAdmin = false, selectedNotificatio
186841
188118
  // Helper function to strip HTML tags
186842
188119
  const stripHtml = (html) => {
186843
188120
  if (typeof window === 'undefined') {
186844
- // Server-side fallback: basic regex strip (not perfect but safe)
186845
- return html.replace(/<[^>]*>/g, '');
188121
+ // Server-side fallback: regex strip repeated to a fixed point so nested
188122
+ // markup cannot re-form a tag after the first pass.
188123
+ return stripHtmlTags(html);
186846
188124
  }
186847
188125
  const doc = new DOMParser().parseFromString(html, 'text/html');
186848
188126
  return doc.body.textContent || '';
@@ -187024,173 +188302,6 @@ function NotificationDisplay({ org, userId, isAdmin = false, selectedNotificatio
187024
188302
  } }), jsx(Dialog, { open: isMobileModalOpen, onOpenChange: setIsMobileModalOpen, children: jsx(DialogContent, { className: "max-h-[85vh] overflow-y-auto max-w-[95vw]", children: selectedNotification && (jsxs(Fragment$1, { children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { className: "text-xl font-semibold text-gray-900 pr-6", children: selectedNotification.title }), jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500 pt-2", children: [jsx(Clock, { className: "h-4 w-4", "aria-hidden": "true" }), jsx("time", { dateTime: selectedNotification.timestamp.toISOString(), children: format$1(selectedNotification.timestamp, "MMMM d, yyyy 'at' h:mm a") })] })] }), jsx("div", { className: "prose prose-sm max-w-none text-gray-700 mt-4", dangerouslySetInnerHTML: { __html: sanitizeHtml(selectedNotification.body) }, "aria-labelledby": "mobile-notification-title" })] })) }) })] }));
187025
188303
  }
187026
188304
 
187027
- var SLOTTABLE_IDENTIFIER = Symbol("radix.slottable");
187028
- // @__NO_SIDE_EFFECTS__
187029
- function createSlottable(ownerName) {
187030
- const Slottable2 = ({ children }) => {
187031
- return /* @__PURE__ */ jsx(Fragment$1, { children });
187032
- };
187033
- Slottable2.displayName = `${ownerName}.Slottable`;
187034
- Slottable2.__radixId = SLOTTABLE_IDENTIFIER;
187035
- return Slottable2;
187036
- }
187037
-
187038
- var ROOT_NAME = "AlertDialog";
187039
- var [createAlertDialogContext] = createContextScope$1(ROOT_NAME, [
187040
- createDialogScope
187041
- ]);
187042
- var useDialogScope = createDialogScope();
187043
- var AlertDialog$1 = (props) => {
187044
- const { __scopeAlertDialog, ...alertDialogProps } = props;
187045
- const dialogScope = useDialogScope(__scopeAlertDialog);
187046
- return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...dialogScope, ...alertDialogProps, modal: true });
187047
- };
187048
- AlertDialog$1.displayName = ROOT_NAME;
187049
- var TRIGGER_NAME = "AlertDialogTrigger";
187050
- var AlertDialogTrigger = React.forwardRef(
187051
- (props, forwardedRef) => {
187052
- const { __scopeAlertDialog, ...triggerProps } = props;
187053
- const dialogScope = useDialogScope(__scopeAlertDialog);
187054
- return /* @__PURE__ */ jsx(DialogPrimitive.Trigger, { ...dialogScope, ...triggerProps, ref: forwardedRef });
187055
- }
187056
- );
187057
- AlertDialogTrigger.displayName = TRIGGER_NAME;
187058
- var PORTAL_NAME = "AlertDialogPortal";
187059
- var AlertDialogPortal$1 = (props) => {
187060
- const { __scopeAlertDialog, ...portalProps } = props;
187061
- const dialogScope = useDialogScope(__scopeAlertDialog);
187062
- return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { ...dialogScope, ...portalProps });
187063
- };
187064
- AlertDialogPortal$1.displayName = PORTAL_NAME;
187065
- var OVERLAY_NAME = "AlertDialogOverlay";
187066
- var AlertDialogOverlay$1 = React.forwardRef(
187067
- (props, forwardedRef) => {
187068
- const { __scopeAlertDialog, ...overlayProps } = props;
187069
- const dialogScope = useDialogScope(__scopeAlertDialog);
187070
- return /* @__PURE__ */ jsx(DialogPrimitive.Overlay, { ...dialogScope, ...overlayProps, ref: forwardedRef });
187071
- }
187072
- );
187073
- AlertDialogOverlay$1.displayName = OVERLAY_NAME;
187074
- var CONTENT_NAME = "AlertDialogContent";
187075
- var [AlertDialogContentProvider, useAlertDialogContentContext] = createAlertDialogContext(CONTENT_NAME);
187076
- var Slottable = createSlottable("AlertDialogContent");
187077
- var AlertDialogContent$1 = React.forwardRef(
187078
- (props, forwardedRef) => {
187079
- const { __scopeAlertDialog, children, ...contentProps } = props;
187080
- const dialogScope = useDialogScope(__scopeAlertDialog);
187081
- const contentRef = React.useRef(null);
187082
- const composedRefs = useComposedRefs$1(forwardedRef, contentRef);
187083
- const cancelRef = React.useRef(null);
187084
- return /* @__PURE__ */ jsx(
187085
- DialogPrimitive.WarningProvider,
187086
- {
187087
- contentName: CONTENT_NAME,
187088
- titleName: TITLE_NAME,
187089
- docsSlug: "alert-dialog",
187090
- children: /* @__PURE__ */ jsx(AlertDialogContentProvider, { scope: __scopeAlertDialog, cancelRef, children: /* @__PURE__ */ jsxs(
187091
- DialogPrimitive.Content,
187092
- {
187093
- role: "alertdialog",
187094
- ...dialogScope,
187095
- ...contentProps,
187096
- ref: composedRefs,
187097
- onOpenAutoFocus: composeEventHandlers$2(contentProps.onOpenAutoFocus, (event) => {
187098
- event.preventDefault();
187099
- cancelRef.current?.focus({ preventScroll: true });
187100
- }),
187101
- onPointerDownOutside: (event) => event.preventDefault(),
187102
- onInteractOutside: (event) => event.preventDefault(),
187103
- children: [
187104
- /* @__PURE__ */ jsx(Slottable, { children }),
187105
- /* @__PURE__ */ jsx(DescriptionWarning, { contentRef })
187106
- ]
187107
- }
187108
- ) })
187109
- }
187110
- );
187111
- }
187112
- );
187113
- AlertDialogContent$1.displayName = CONTENT_NAME;
187114
- var TITLE_NAME = "AlertDialogTitle";
187115
- var AlertDialogTitle$1 = React.forwardRef(
187116
- (props, forwardedRef) => {
187117
- const { __scopeAlertDialog, ...titleProps } = props;
187118
- const dialogScope = useDialogScope(__scopeAlertDialog);
187119
- return /* @__PURE__ */ jsx(DialogPrimitive.Title, { ...dialogScope, ...titleProps, ref: forwardedRef });
187120
- }
187121
- );
187122
- AlertDialogTitle$1.displayName = TITLE_NAME;
187123
- var DESCRIPTION_NAME = "AlertDialogDescription";
187124
- var AlertDialogDescription$1 = React.forwardRef((props, forwardedRef) => {
187125
- const { __scopeAlertDialog, ...descriptionProps } = props;
187126
- const dialogScope = useDialogScope(__scopeAlertDialog);
187127
- return /* @__PURE__ */ jsx(DialogPrimitive.Description, { ...dialogScope, ...descriptionProps, ref: forwardedRef });
187128
- });
187129
- AlertDialogDescription$1.displayName = DESCRIPTION_NAME;
187130
- var ACTION_NAME = "AlertDialogAction";
187131
- var AlertDialogAction$1 = React.forwardRef(
187132
- (props, forwardedRef) => {
187133
- const { __scopeAlertDialog, ...actionProps } = props;
187134
- const dialogScope = useDialogScope(__scopeAlertDialog);
187135
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...actionProps, ref: forwardedRef });
187136
- }
187137
- );
187138
- AlertDialogAction$1.displayName = ACTION_NAME;
187139
- var CANCEL_NAME = "AlertDialogCancel";
187140
- var AlertDialogCancel$1 = React.forwardRef(
187141
- (props, forwardedRef) => {
187142
- const { __scopeAlertDialog, ...cancelProps } = props;
187143
- const { cancelRef } = useAlertDialogContentContext(CANCEL_NAME, __scopeAlertDialog);
187144
- const dialogScope = useDialogScope(__scopeAlertDialog);
187145
- const ref = useComposedRefs$1(forwardedRef, cancelRef);
187146
- return /* @__PURE__ */ jsx(DialogPrimitive.Close, { ...dialogScope, ...cancelProps, ref });
187147
- }
187148
- );
187149
- AlertDialogCancel$1.displayName = CANCEL_NAME;
187150
- var DescriptionWarning = ({ contentRef }) => {
187151
- const MESSAGE = `\`${CONTENT_NAME}\` requires a description for the component to be accessible for screen reader users.
187152
-
187153
- You can add a description to the \`${CONTENT_NAME}\` by passing a \`${DESCRIPTION_NAME}\` component as a child, which also benefits sighted users by adding visible context to the dialog.
187154
-
187155
- Alternatively, you can use your own component as a description by assigning it an \`id\` and passing the same value to the \`aria-describedby\` prop in \`${CONTENT_NAME}\`. If the description is confusing or duplicative for sighted users, you can use the \`@radix-ui/react-visually-hidden\` primitive as a wrapper around your description component.
187156
-
187157
- For more information, see https://radix-ui.com/primitives/docs/components/alert-dialog`;
187158
- React.useEffect(() => {
187159
- const hasDescription = document.getElementById(
187160
- contentRef.current?.getAttribute("aria-describedby")
187161
- );
187162
- if (!hasDescription) console.warn(MESSAGE);
187163
- }, [MESSAGE, contentRef]);
187164
- return null;
187165
- };
187166
- var Root2 = AlertDialog$1;
187167
- var Portal2 = AlertDialogPortal$1;
187168
- var Overlay2 = AlertDialogOverlay$1;
187169
- var Content2 = AlertDialogContent$1;
187170
- var Action = AlertDialogAction$1;
187171
- var Cancel = AlertDialogCancel$1;
187172
- var Title2 = AlertDialogTitle$1;
187173
- var Description2 = AlertDialogDescription$1;
187174
-
187175
- const AlertDialog = Root2;
187176
- const AlertDialogPortal = Portal2;
187177
- const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (jsx(Overlay2, { 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/80', className), ...props, ref: ref })));
187178
- AlertDialogOverlay.displayName = Overlay2.displayName;
187179
- const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (jsxs(AlertDialogPortal, { children: [jsx(AlertDialogOverlay, {}), jsx(Content2, { ref: ref, className: cn('bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg', className), ...props })] })));
187180
- AlertDialogContent.displayName = Content2.displayName;
187181
- const AlertDialogHeader = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col space-y-2 text-center sm:text-left', className), ...props }));
187182
- AlertDialogHeader.displayName = 'AlertDialogHeader';
187183
- const AlertDialogFooter = ({ className, ...props }) => (jsx("div", { className: cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className), ...props }));
187184
- AlertDialogFooter.displayName = 'AlertDialogFooter';
187185
- const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (jsx(Title2, { ref: ref, className: cn('text-lg font-semibold', className), ...props })));
187186
- AlertDialogTitle.displayName = Title2.displayName;
187187
- const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (jsx(Description2, { ref: ref, className: cn('text-muted-foreground text-sm', className), ...props })));
187188
- AlertDialogDescription.displayName = Description2.displayName;
187189
- const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (jsx(Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
187190
- AlertDialogAction.displayName = Action.displayName;
187191
- const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (jsx(Cancel, { ref: ref, className: cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className), ...props })));
187192
- AlertDialogCancel.displayName = Cancel.displayName;
187193
-
187194
188305
  /**
187195
188306
  * **Private mode** toggle for the platform nav-bar. A single pill that
187196
188307
  * collapses to an icon-only chip when off, and expands with a cool