@vue-skuilder/common-ui 0.2.35 → 0.2.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/defineProperty.mjs +13 -0
  2. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPrimitive.mjs +15 -0
  3. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/toPropertyKey.mjs +10 -0
  4. package/dist/_virtual/_@oxc-project_runtime@0.115.0/helpers/typeof.mjs +12 -0
  5. package/dist/_virtual/_plugin-vue_export-helper.mjs +9 -0
  6. package/dist/_virtual/_rolldown/runtime.mjs +7 -0
  7. package/dist/admin/common-ui-admin.es.js +259 -23774
  8. package/dist/admin/common-ui-admin.es.js.map +1 -1
  9. package/dist/assets/index.css +2 -1
  10. package/dist/components/CardBrowser.mjs +63 -0
  11. package/dist/components/CardBrowser.mjs.map +1 -0
  12. package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs +50 -0
  13. package/dist/components/CardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
  14. package/dist/components/CardHistoryViewer.mjs +70 -0
  15. package/dist/components/CardHistoryViewer.mjs.map +1 -0
  16. package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs +108 -0
  17. package/dist/components/CardHistoryViewer.vue_vue_type_script_lang.mjs.map +1 -0
  18. package/dist/components/CardSearch.mjs +26 -0
  19. package/dist/components/CardSearch.mjs.map +1 -0
  20. package/dist/components/CardSearch.vue_vue_type_script_lang.mjs +17 -0
  21. package/dist/components/CardSearch.vue_vue_type_script_lang.mjs.map +1 -0
  22. package/dist/components/CardSearchResults.mjs +32 -0
  23. package/dist/components/CardSearchResults.mjs.map +1 -0
  24. package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs +71 -0
  25. package/dist/components/CardSearchResults.vue_vue_type_script_lang.mjs.map +1 -0
  26. package/dist/components/CourseCardBrowser.mjs +175 -0
  27. package/dist/components/CourseCardBrowser.mjs.map +1 -0
  28. package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs +173 -0
  29. package/dist/components/CourseCardBrowser.vue_vue_type_script_lang.mjs.map +1 -0
  30. package/dist/components/CourseInformation.mjs +132 -0
  31. package/dist/components/CourseInformation.mjs.map +1 -0
  32. package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs +56 -0
  33. package/dist/components/CourseInformation.vue_vue_type_script_lang.mjs.map +1 -0
  34. package/dist/components/CourseTagFilterWidget.mjs +114 -0
  35. package/dist/components/CourseTagFilterWidget.mjs.map +1 -0
  36. package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs +65 -0
  37. package/dist/components/CourseTagFilterWidget.vue_vue_type_script_lang.mjs.map +1 -0
  38. package/dist/components/HeatMap.mjs +40 -0
  39. package/dist/components/HeatMap.mjs.map +1 -0
  40. package/dist/components/HeatMap.vue_vue_type_script_lang.mjs +211 -0
  41. package/dist/components/HeatMap.vue_vue_type_script_lang.mjs.map +1 -0
  42. package/dist/components/PaginatingToolbar.mjs +94 -0
  43. package/dist/components/PaginatingToolbar.mjs.map +1 -0
  44. package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs +36 -0
  45. package/dist/components/PaginatingToolbar.vue_vue_type_script_lang.mjs.map +1 -0
  46. package/dist/components/SessionControllerDebug.mjs +130 -0
  47. package/dist/components/SessionControllerDebug.mjs.map +1 -0
  48. package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs +45 -0
  49. package/dist/components/SessionControllerDebug.vue_vue_type_script_lang.mjs.map +1 -0
  50. package/dist/components/SkMouseTrap.mjs +66 -0
  51. package/dist/components/SkMouseTrap.mjs.map +1 -0
  52. package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs +31 -0
  53. package/dist/components/SkMouseTrap.vue_vue_type_script_lang.mjs.map +1 -0
  54. package/dist/components/SkMouseTrapToolTip.mjs +28 -0
  55. package/dist/components/SkMouseTrapToolTip.mjs.map +1 -0
  56. package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs +91 -0
  57. package/dist/components/SkMouseTrapToolTip.vue_vue_type_script_lang.mjs.map +1 -0
  58. package/dist/components/SnackbarService.mjs +24 -0
  59. package/dist/components/SnackbarService.mjs.map +1 -0
  60. package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs +34 -0
  61. package/dist/components/SnackbarService.vue_vue_type_script_lang.mjs.map +1 -0
  62. package/dist/components/SnackbarService2.mjs +40 -0
  63. package/dist/components/SnackbarService2.mjs.map +1 -0
  64. package/dist/components/StudySession.mjs +123 -0
  65. package/dist/components/StudySession.mjs.map +1 -0
  66. package/dist/components/StudySession.vue_vue_type_script_lang.mjs +280 -0
  67. package/dist/components/StudySession.vue_vue_type_script_lang.mjs.map +1 -0
  68. package/dist/components/StudySessionTimer.mjs +50 -0
  69. package/dist/components/StudySessionTimer.mjs.map +1 -0
  70. package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs +43 -0
  71. package/dist/components/StudySessionTimer.vue_vue_type_script_lang.mjs.map +1 -0
  72. package/dist/components/TagsInput.mjs +45 -0
  73. package/dist/components/TagsInput.mjs.map +1 -0
  74. package/dist/components/TagsInput.vue_vue_type_script_lang.mjs +117 -0
  75. package/dist/components/TagsInput.vue_vue_type_script_lang.mjs.map +1 -0
  76. package/dist/components/UserTagPreferences.mjs +205 -0
  77. package/dist/components/UserTagPreferences.mjs.map +1 -0
  78. package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs +139 -0
  79. package/dist/components/UserTagPreferences.vue_vue_type_script_lang.mjs.map +1 -0
  80. package/dist/components/auth/RequestPasswordReset.mjs +96 -0
  81. package/dist/components/auth/RequestPasswordReset.mjs.map +1 -0
  82. package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs +47 -0
  83. package/dist/components/auth/RequestPasswordReset.vue_vue_type_script_lang.mjs.map +1 -0
  84. package/dist/components/auth/ResetPassword.mjs +129 -0
  85. package/dist/components/auth/ResetPassword.mjs.map +1 -0
  86. package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs +69 -0
  87. package/dist/components/auth/ResetPassword.vue_vue_type_script_lang.mjs.map +1 -0
  88. package/dist/components/auth/UserChip.mjs +9 -0
  89. package/dist/components/auth/UserChip.mjs.map +1 -0
  90. package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs +213 -0
  91. package/dist/components/auth/UserChip.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  92. package/dist/components/auth/UserLogin.mjs +9 -0
  93. package/dist/components/auth/UserLogin.mjs.map +1 -0
  94. package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs +155 -0
  95. package/dist/components/auth/UserLogin.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  96. package/dist/components/auth/UserLoginAndRegistrationContainer.mjs +9 -0
  97. package/dist/components/auth/UserLoginAndRegistrationContainer.mjs.map +1 -0
  98. package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs +126 -0
  99. package/dist/components/auth/UserLoginAndRegistrationContainer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  100. package/dist/components/auth/UserRegistration.mjs +147 -0
  101. package/dist/components/auth/UserRegistration.mjs.map +1 -0
  102. package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs +130 -0
  103. package/dist/components/auth/UserRegistration.vue_vue_type_script_lang.mjs.map +1 -0
  104. package/dist/components/auth/VerifyEmail.mjs +118 -0
  105. package/dist/components/auth/VerifyEmail.mjs.map +1 -0
  106. package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs +59 -0
  107. package/dist/components/auth/VerifyEmail.vue_vue_type_script_lang.mjs.map +1 -0
  108. package/dist/components/cardRendering/AudioAutoPlayer.mjs +9 -0
  109. package/dist/components/cardRendering/AudioAutoPlayer.mjs.map +1 -0
  110. package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs +69 -0
  111. package/dist/components/cardRendering/AudioAutoPlayer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  112. package/dist/components/cardRendering/CardLoader.mjs +31 -0
  113. package/dist/components/cardRendering/CardLoader.mjs.map +1 -0
  114. package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs +75 -0
  115. package/dist/components/cardRendering/CardLoader.vue_vue_type_script_lang.mjs.map +1 -0
  116. package/dist/components/cardRendering/CardViewer.mjs +39 -0
  117. package/dist/components/cardRendering/CardViewer.mjs.map +1 -0
  118. package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs +66 -0
  119. package/dist/components/cardRendering/CardViewer.vue_vue_type_script_lang.mjs.map +1 -0
  120. package/dist/components/cardRendering/CodeBlockRenderer.mjs +8 -0
  121. package/dist/components/cardRendering/CodeBlockRenderer.mjs.map +1 -0
  122. package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs +44 -0
  123. package/dist/components/cardRendering/CodeBlockRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  124. package/dist/components/cardRendering/MarkdownRenderer.mjs +21 -0
  125. package/dist/components/cardRendering/MarkdownRenderer.mjs.map +1 -0
  126. package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs +24 -0
  127. package/dist/components/cardRendering/MarkdownRenderer.vue_vue_type_script_lang.mjs.map +1 -0
  128. package/dist/components/cardRendering/MarkdownRendererHelpers.mjs +79 -0
  129. package/dist/components/cardRendering/MarkdownRendererHelpers.mjs.map +1 -0
  130. package/dist/components/cardRendering/MdTokenRenderer.mjs +9 -0
  131. package/dist/components/cardRendering/MdTokenRenderer.mjs.map +1 -0
  132. package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs +176 -0
  133. package/dist/components/cardRendering/MdTokenRenderer.vue_vue_type_script_setup_true_lang.mjs.map +1 -0
  134. package/dist/components/studentInputs/BaseUserInput.mjs +54 -0
  135. package/dist/components/studentInputs/BaseUserInput.mjs.map +1 -0
  136. package/dist/components/studentInputs/MultipleChoiceOption.mjs +26 -0
  137. package/dist/components/studentInputs/MultipleChoiceOption.mjs.map +1 -0
  138. package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs +76 -0
  139. package/dist/components/studentInputs/MultipleChoiceOption.vue_vue_type_script_lang.mjs.map +1 -0
  140. package/dist/components/studentInputs/RadioMultipleChoice.mjs +33 -0
  141. package/dist/components/studentInputs/RadioMultipleChoice.mjs.map +1 -0
  142. package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs +105 -0
  143. package/dist/components/studentInputs/RadioMultipleChoice.vue_vue_type_script_lang.mjs.map +1 -0
  144. package/dist/components/studentInputs/TrueFalse.mjs +19 -0
  145. package/dist/components/studentInputs/TrueFalse.mjs.map +1 -0
  146. package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs +21 -0
  147. package/dist/components/studentInputs/TrueFalse.vue_vue_type_script_lang.mjs.map +1 -0
  148. package/dist/components/studentInputs/UserInputNumber.mjs +33 -0
  149. package/dist/components/studentInputs/UserInputNumber.mjs.map +1 -0
  150. package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs +25 -0
  151. package/dist/components/studentInputs/UserInputNumber.vue_vue_type_script_lang.mjs.map +1 -0
  152. package/dist/components/studentInputs/UserInputString.mjs +22 -0
  153. package/dist/components/studentInputs/UserInputString.mjs.map +1 -0
  154. package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs +23 -0
  155. package/dist/components/studentInputs/UserInputString.vue_vue_type_script_lang.mjs.map +1 -0
  156. package/dist/components/studentInputs/fillInInput.mjs +25 -0
  157. package/dist/components/studentInputs/fillInInput.mjs.map +1 -0
  158. package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs +26 -0
  159. package/dist/components/studentInputs/fillInInput.vue_vue_type_script_lang.mjs.map +1 -0
  160. package/dist/composables/CompositionViewable.mjs +65 -0
  161. package/dist/composables/CompositionViewable.mjs.map +1 -0
  162. package/dist/composables/Displayable.mjs +36 -0
  163. package/dist/composables/Displayable.mjs.map +1 -0
  164. package/dist/composables/useAuthUI.mjs +38 -0
  165. package/dist/composables/useAuthUI.mjs.map +1 -0
  166. package/dist/composables/useEntitlements.mjs +58 -0
  167. package/dist/composables/useEntitlements.mjs.map +1 -0
  168. package/dist/index.mjs +49 -0
  169. package/dist/plugins/pinia.mjs +12 -0
  170. package/dist/plugins/pinia.mjs.map +1 -0
  171. package/dist/services/authAPI.mjs +159 -0
  172. package/dist/services/authAPI.mjs.map +1 -0
  173. package/dist/stores/useAuthStore.mjs +68 -0
  174. package/dist/stores/useAuthStore.mjs.map +1 -0
  175. package/dist/stores/useCardPreviewModeStore.mjs +20 -0
  176. package/dist/stores/useCardPreviewModeStore.mjs.map +1 -0
  177. package/dist/stores/useConfigStore.mjs +60 -0
  178. package/dist/stores/useConfigStore.mjs.map +1 -0
  179. package/dist/utils/SkldrMouseTrap.mjs +48 -0
  180. package/dist/utils/SkldrMouseTrap.mjs.map +1 -0
  181. package/dist/utils/passwordValidation.mjs +12 -0
  182. package/dist/utils/passwordValidation.mjs.map +1 -0
  183. package/package.json +7 -8
  184. package/dist/MarkdownRenderer-U_iPiA2x.mjs +0 -4966
  185. package/dist/MarkdownRenderer-U_iPiA2x.mjs.map +0 -1
  186. package/dist/assets/MarkdownRenderer.css +0 -1
  187. package/dist/common-ui.es.js +0 -5090
  188. package/dist/common-ui.es.js.map +0 -1
  189. package/dist/common-ui.umd.js +0 -77
  190. package/dist/common-ui.umd.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserTagPreferences.mjs","names":[],"sources":["../../src/components/UserTagPreferences.vue"],"sourcesContent":["<template>\n <v-card :loading=\"loading\">\n <v-card-title>\n <v-icon start>mdi-tune</v-icon>\n Learning Preferences\n </v-card-title>\n\n <v-card-subtitle v-if=\"courseId\">\n Customize how content is presented in this course\n </v-card-subtitle>\n\n <v-card-text>\n <v-alert v-if=\"!courseId\" type=\"info\" variant=\"tonal\" class=\"mb-4\">\n Select a course to configure your learning preferences.\n </v-alert>\n\n <div v-else-if=\"!loading\">\n <!-- Tag Preferences Section -->\n <div class=\"mb-6\">\n <h3 class=\"text-subtitle-1 font-weight-bold mb-2\">\n <v-icon start size=\"small\">mdi-tune-variant</v-icon>\n Tag Preferences\n </h3>\n <p class=\"text-body-2 text-medium-emphasis mb-3\">\n Adjust how much you want to see cards with specific tags. 0 = exclude, 1 = neutral, higher = prefer more.\n </p>\n\n <!-- Tag autocomplete -->\n <v-autocomplete\n v-model=\"tagToAdd\"\n :items=\"availableTagsToAdd\"\n item-title=\"name\"\n item-value=\"name\"\n label=\"Add tag preference\"\n placeholder=\"Search tags...\"\n density=\"compact\"\n variant=\"outlined\"\n clearable\n hide-details\n class=\"mb-4\"\n @update:model-value=\"addTag\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.snippet }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <!-- Tag slider list -->\n <v-list v-if=\"tagNames.length > 0\" class=\"mt-4\">\n <v-list-item\n v-for=\"tagName in tagNames\"\n :key=\"tagName\"\n class=\"px-0\"\n >\n <template #default>\n <div class=\"d-flex align-center ga-3 w-100\">\n <!-- Tag name -->\n <v-chip\n size=\"small\"\n variant=\"tonal\"\n class=\"flex-shrink-0\"\n style=\"min-width: 120px;\"\n >\n {{ tagName }}\n </v-chip>\n\n <!-- Slider -->\n <v-slider\n :model-value=\"preferences.boost[tagName]\"\n :min=\"sliderConfigResolved.min\"\n :max=\"globalSliderMax\"\n :step=\"0.01\"\n hide-details\n class=\"flex-grow-1\"\n thumb-label\n @update:model-value=\"(val: number) => updateBoost(tagName, val)\"\n >\n <template #thumb-label=\"{ modelValue }\">\n {{ formatMultiplier(modelValue) }}\n </template>\n </v-slider>\n\n <!-- Current value display -->\n <span class=\"text-body-2 flex-shrink-0\" style=\"min-width: 60px; text-align: right;\">\n {{ formatMultiplier(preferences.boost[tagName]) }}\n </span>\n\n <!-- Expand range button (always visible) -->\n <v-btn\n icon=\"mdi-plus\"\n size=\"small\"\n variant=\"text\"\n density=\"compact\"\n :disabled=\"globalSliderMax >= sliderConfigResolved.absoluteMax\"\n @click=\"expandSliderRange(tagName)\"\n />\n\n <!-- Delete button -->\n <v-btn\n icon=\"mdi-delete\"\n size=\"small\"\n variant=\"text\"\n density=\"compact\"\n @click=\"removeTag(tagName)\"\n />\n </div>\n </template>\n </v-list-item>\n </v-list>\n\n <v-alert v-else type=\"info\" variant=\"tonal\" class=\"mt-4\">\n No tag preferences configured yet. Add tags above to get started.\n </v-alert>\n </div>\n\n <!-- Status / Feedback -->\n <v-alert v-if=\"saveError\" type=\"error\" variant=\"tonal\" class=\"mt-4\" closable @click:close=\"saveError = ''\">\n {{ saveError }}\n </v-alert>\n\n <v-alert v-if=\"saveSuccess\" type=\"success\" variant=\"tonal\" class=\"mt-4\" closable @click:close=\"saveSuccess = false\">\n Preferences saved successfully\n </v-alert>\n </div>\n\n <div v-else class=\"d-flex justify-center py-8\">\n <v-progress-circular indeterminate color=\"primary\" />\n </div>\n </v-card-text>\n\n <v-card-actions v-if=\"courseId && !loading\">\n <v-spacer />\n <v-btn\n variant=\"text\"\n :disabled=\"!hasChanges\"\n @click=\"resetToSaved\"\n >\n Reset\n </v-btn>\n <v-btn\n color=\"primary\"\n variant=\"flat\"\n :loading=\"saving\"\n :disabled=\"!hasChanges\"\n @click=\"savePreferences\"\n >\n Save Preferences\n </v-btn>\n </v-card-actions>\n </v-card>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\nimport { getDataLayer, Tag, CourseDBInterface, UserDBInterface } from '@vue-skuilder/db';\nimport { getCurrentUser } from '../stores/useAuthStore';\n\n/**\n * User's tag preference state, matching the backend schema.\n */\ninterface UserTagPreferenceState {\n boost: Record<string, number>;\n updatedAt: string;\n}\n\n/**\n * Slider configuration for tag preferences.\n */\ninterface SliderConfig {\n min?: number; // default: 0\n startingMax?: number; // default: 2\n absoluteMax?: number; // default: 10\n}\n\nconst DEFAULT_SLIDER_CONFIG: Required<SliderConfig> = {\n min: 0,\n startingMax: 2,\n absoluteMax: 10,\n};\n\nconst STRATEGY_KEY = 'UserTagPreferenceFilter';\n\nexport default defineComponent({\n name: 'UserTagPreferences',\n\n props: {\n /**\n * Course ID to configure preferences for.\n * If not provided, component shows a prompt to select a course.\n */\n courseId: {\n type: String as PropType<string>,\n required: false,\n default: '',\n },\n\n /**\n * Slider configuration (min, startingMax, absoluteMax).\n * All fields optional, defaults: { min: 0, startingMax: 2, absoluteMax: 10 }\n */\n sliderConfig: {\n type: Object as PropType<SliderConfig>,\n required: false,\n default: () => ({}),\n },\n },\n\n emits: ['preferences-saved', 'preferences-changed'],\n\n data() {\n return {\n loading: true,\n saving: false,\n saveError: '',\n saveSuccess: false,\n\n // Current working state\n preferences: {\n boost: {} as Record<string, number>,\n },\n\n // Saved state for comparison\n savedPreferences: {\n boost: {} as Record<string, number>,\n },\n\n // Per-tag current max range (for dynamic expansion)\n tagMaxRanges: {} as Record<string, number>,\n\n // Available tags from course\n availableTags: [] as Tag[],\n\n // Autocomplete model\n tagToAdd: null as string | null,\n\n // DB references\n courseDB: null as CourseDBInterface | null,\n user: null as UserDBInterface | null,\n };\n },\n\n computed: {\n /**\n * Resolved slider config with defaults\n */\n sliderConfigResolved(): Required<SliderConfig> {\n return {\n min: this.sliderConfig.min ?? DEFAULT_SLIDER_CONFIG.min,\n startingMax: this.sliderConfig.startingMax ?? DEFAULT_SLIDER_CONFIG.startingMax,\n absoluteMax: this.sliderConfig.absoluteMax ?? DEFAULT_SLIDER_CONFIG.absoluteMax,\n };\n },\n\n /**\n * List of tag names that have preferences (sorted alphabetically)\n */\n tagNames(): string[] {\n return Object.keys(this.preferences.boost).sort();\n },\n\n /**\n * Global max for all sliders (highest tagMaxRanges value)\n * Ensures all sliders have the same visual scale\n */\n globalSliderMax(): number {\n const maxValues = Object.values(this.tagMaxRanges);\n if (maxValues.length === 0) {\n return this.sliderConfigResolved.startingMax;\n }\n return Math.max(...maxValues);\n },\n\n /**\n * Tags available to add (not already in preferences)\n */\n availableTagsToAdd(): Tag[] {\n const usedTags = new Set(Object.keys(this.preferences.boost));\n return this.availableTags.filter((t) => !usedTags.has(t.name));\n },\n\n /**\n * Check if current preferences differ from saved state\n */\n hasChanges(): boolean {\n const currentTags = Object.keys(this.preferences.boost).sort();\n const savedTags = Object.keys(this.savedPreferences.boost).sort();\n\n if (currentTags.length !== savedTags.length) {\n return true;\n }\n\n if (currentTags.join(',') !== savedTags.join(',')) {\n return true;\n }\n\n return currentTags.some(\n (tag) => this.preferences.boost[tag] !== this.savedPreferences.boost[tag]\n );\n },\n },\n\n watch: {\n courseId: {\n immediate: true,\n async handler(newCourseId: string) {\n if (newCourseId) {\n await this.loadPreferences();\n } else {\n this.loading = false;\n }\n },\n },\n },\n\n methods: {\n /**\n * Load preferences from strategy state and available tags from course\n */\n async loadPreferences() {\n this.loading = true;\n this.saveError = '';\n\n try {\n // Get user and course DB\n this.user = (await getCurrentUser()) ?? null;\n if (!this.user) {\n throw new Error('User not available');\n }\n\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n\n // Load available tags\n const tagStubs = await this.courseDB.getCourseTagStubs();\n this.availableTags = tagStubs.rows.map((r) => r.doc!).filter(Boolean);\n\n // Load saved preferences\n const state = await this.user.getStrategyState<UserTagPreferenceState>(\n this.courseId,\n STRATEGY_KEY\n );\n\n if (state) {\n this.preferences.boost = { ...state.boost };\n } else {\n // No preferences yet - start fresh\n this.preferences.boost = {};\n }\n\n // Store saved state for comparison\n this.savedPreferences.boost = { ...this.preferences.boost };\n\n // Initialize tag max ranges, auto-expanding if saved value exceeds startingMax\n this.tagMaxRanges = {};\n Object.keys(this.preferences.boost).forEach((tag) => {\n const savedValue = this.preferences.boost[tag];\n const startingMax = this.sliderConfigResolved.startingMax;\n\n // If saved value exceeds startingMax, expand range to accommodate it\n // (capped at absoluteMax)\n if (savedValue > startingMax) {\n this.tagMaxRanges[tag] = Math.min(\n Math.ceil(savedValue),\n this.sliderConfigResolved.absoluteMax\n );\n } else {\n this.tagMaxRanges[tag] = startingMax;\n }\n });\n } catch (e) {\n console.error('Failed to load preferences:', e);\n this.saveError = 'Failed to load preferences. Please try again.';\n } finally {\n this.loading = false;\n }\n },\n\n /**\n * Add a tag to preferences with default multiplier of 1.0\n */\n addTag(tagName: string | null) {\n if (tagName && !(tagName in this.preferences.boost)) {\n this.preferences.boost[tagName] = 1.0;\n this.tagMaxRanges[tagName] = this.sliderConfigResolved.startingMax;\n this.$emit('preferences-changed', this.preferences);\n }\n // Clear the autocomplete\n this.$nextTick(() => {\n this.tagToAdd = null;\n });\n },\n\n /**\n * Remove a tag from preferences\n */\n removeTag(tagName: string) {\n delete this.preferences.boost[tagName];\n delete this.tagMaxRanges[tagName];\n this.$emit('preferences-changed', this.preferences);\n },\n\n /**\n * Update boost multiplier for a tag\n */\n updateBoost(tagName: string, value: number) {\n this.preferences.boost[tagName] = value;\n this.$emit('preferences-changed', this.preferences);\n },\n\n /**\n * Expand the global slider range by 1 and move the triggering tag's slider to new max\n */\n expandSliderRange(tagName: string) {\n const currentGlobalMax = this.globalSliderMax;\n if (currentGlobalMax < this.sliderConfigResolved.absoluteMax) {\n const newGlobalMax = currentGlobalMax + 1;\n\n // Expand all tag ranges to the new global max\n // (ensures all sliders stay synchronized)\n Object.keys(this.tagMaxRanges).forEach((tag) => {\n this.tagMaxRanges[tag] = Math.max(this.tagMaxRanges[tag], newGlobalMax);\n });\n\n // Move the triggering tag's slider to new max\n this.preferences.boost[tagName] = newGlobalMax;\n this.$emit('preferences-changed', this.preferences);\n }\n },\n\n /**\n * Format multiplier for display\n */\n formatMultiplier(value: number): string {\n if (value === 0) {\n return '0x';\n }\n if (value < 0.1) {\n return value.toFixed(2) + 'x';\n }\n return value.toFixed(1) + 'x';\n },\n\n /**\n * Reset to last saved state\n */\n resetToSaved() {\n this.preferences.boost = { ...this.savedPreferences.boost };\n this.saveSuccess = false;\n this.saveError = '';\n\n // Reset tag max ranges, auto-expanding if saved value exceeds startingMax\n this.tagMaxRanges = {};\n Object.keys(this.preferences.boost).forEach((tag) => {\n const savedValue = this.preferences.boost[tag];\n const startingMax = this.sliderConfigResolved.startingMax;\n\n if (savedValue > startingMax) {\n this.tagMaxRanges[tag] = Math.min(\n Math.ceil(savedValue),\n this.sliderConfigResolved.absoluteMax\n );\n } else {\n this.tagMaxRanges[tag] = startingMax;\n }\n });\n },\n\n /**\n * Save preferences to strategy state\n */\n async savePreferences() {\n if (!this.user || !this.courseId) {\n this.saveError = 'Unable to save - user or course not available';\n return;\n }\n\n this.saving = true;\n this.saveError = '';\n this.saveSuccess = false;\n\n try {\n const state: UserTagPreferenceState = {\n boost: { ...this.preferences.boost },\n updatedAt: new Date().toISOString(),\n };\n\n await this.user.putStrategyState<UserTagPreferenceState>(\n this.courseId,\n STRATEGY_KEY,\n state\n );\n\n // Update saved state\n this.savedPreferences.boost = { ...this.preferences.boost };\n\n this.saveSuccess = true;\n this.$emit('preferences-saved', state);\n } catch (e) {\n console.error('Failed to save preferences:', e);\n this.saveError = 'Failed to save preferences. Please try again.';\n } finally {\n this.saving = false;\n }\n },\n },\n});\n</script>\n\n<style scoped>\n/* Additional styles if needed */\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,139 @@
1
+ import "../_virtual/_rolldown/runtime.mjs";
2
+ import { getCurrentUser as e } from "../stores/useAuthStore.mjs";
3
+ import { defineComponent as t } from "vue";
4
+ import { getDataLayer as n } from "@vue-skuilder/db";
5
+ //#region src/components/UserTagPreferences.vue?vue&type=script&lang.ts
6
+ var r = {
7
+ min: 0,
8
+ startingMax: 2,
9
+ absoluteMax: 10
10
+ }, i = "UserTagPreferenceFilter", a = t({
11
+ name: "UserTagPreferences",
12
+ props: {
13
+ courseId: {
14
+ type: String,
15
+ required: !1,
16
+ default: ""
17
+ },
18
+ sliderConfig: {
19
+ type: Object,
20
+ required: !1,
21
+ default: () => ({})
22
+ }
23
+ },
24
+ emits: ["preferences-saved", "preferences-changed"],
25
+ data() {
26
+ return {
27
+ loading: !0,
28
+ saving: !1,
29
+ saveError: "",
30
+ saveSuccess: !1,
31
+ preferences: { boost: {} },
32
+ savedPreferences: { boost: {} },
33
+ tagMaxRanges: {},
34
+ availableTags: [],
35
+ tagToAdd: null,
36
+ courseDB: null,
37
+ user: null
38
+ };
39
+ },
40
+ computed: {
41
+ sliderConfigResolved() {
42
+ return {
43
+ min: this.sliderConfig.min ?? r.min,
44
+ startingMax: this.sliderConfig.startingMax ?? r.startingMax,
45
+ absoluteMax: this.sliderConfig.absoluteMax ?? r.absoluteMax
46
+ };
47
+ },
48
+ tagNames() {
49
+ return Object.keys(this.preferences.boost).sort();
50
+ },
51
+ globalSliderMax() {
52
+ let e = Object.values(this.tagMaxRanges);
53
+ return e.length === 0 ? this.sliderConfigResolved.startingMax : Math.max(...e);
54
+ },
55
+ availableTagsToAdd() {
56
+ let e = new Set(Object.keys(this.preferences.boost));
57
+ return this.availableTags.filter((t) => !e.has(t.name));
58
+ },
59
+ hasChanges() {
60
+ let e = Object.keys(this.preferences.boost).sort(), t = Object.keys(this.savedPreferences.boost).sort();
61
+ return e.length !== t.length || e.join(",") !== t.join(",") ? !0 : e.some((e) => this.preferences.boost[e] !== this.savedPreferences.boost[e]);
62
+ }
63
+ },
64
+ watch: { courseId: {
65
+ immediate: !0,
66
+ async handler(e) {
67
+ e ? await this.loadPreferences() : this.loading = !1;
68
+ }
69
+ } },
70
+ methods: {
71
+ async loadPreferences() {
72
+ this.loading = !0, this.saveError = "";
73
+ try {
74
+ if (this.user = await e() ?? null, !this.user) throw Error("User not available");
75
+ this.courseDB = n().getCourseDB(this.courseId), this.availableTags = (await this.courseDB.getCourseTagStubs()).rows.map((e) => e.doc).filter(Boolean);
76
+ let t = await this.user.getStrategyState(this.courseId, i);
77
+ t ? this.preferences.boost = { ...t.boost } : this.preferences.boost = {}, this.savedPreferences.boost = { ...this.preferences.boost }, this.tagMaxRanges = {}, Object.keys(this.preferences.boost).forEach((e) => {
78
+ let t = this.preferences.boost[e], n = this.sliderConfigResolved.startingMax;
79
+ t > n ? this.tagMaxRanges[e] = Math.min(Math.ceil(t), this.sliderConfigResolved.absoluteMax) : this.tagMaxRanges[e] = n;
80
+ });
81
+ } catch (e) {
82
+ console.error("Failed to load preferences:", e), this.saveError = "Failed to load preferences. Please try again.";
83
+ } finally {
84
+ this.loading = !1;
85
+ }
86
+ },
87
+ addTag(e) {
88
+ e && !(e in this.preferences.boost) && (this.preferences.boost[e] = 1, this.tagMaxRanges[e] = this.sliderConfigResolved.startingMax, this.$emit("preferences-changed", this.preferences)), this.$nextTick(() => {
89
+ this.tagToAdd = null;
90
+ });
91
+ },
92
+ removeTag(e) {
93
+ delete this.preferences.boost[e], delete this.tagMaxRanges[e], this.$emit("preferences-changed", this.preferences);
94
+ },
95
+ updateBoost(e, t) {
96
+ this.preferences.boost[e] = t, this.$emit("preferences-changed", this.preferences);
97
+ },
98
+ expandSliderRange(e) {
99
+ let t = this.globalSliderMax;
100
+ if (t < this.sliderConfigResolved.absoluteMax) {
101
+ let n = t + 1;
102
+ Object.keys(this.tagMaxRanges).forEach((e) => {
103
+ this.tagMaxRanges[e] = Math.max(this.tagMaxRanges[e], n);
104
+ }), this.preferences.boost[e] = n, this.$emit("preferences-changed", this.preferences);
105
+ }
106
+ },
107
+ formatMultiplier(e) {
108
+ return e === 0 ? "0x" : e < .1 ? e.toFixed(2) + "x" : e.toFixed(1) + "x";
109
+ },
110
+ resetToSaved() {
111
+ this.preferences.boost = { ...this.savedPreferences.boost }, this.saveSuccess = !1, this.saveError = "", this.tagMaxRanges = {}, Object.keys(this.preferences.boost).forEach((e) => {
112
+ let t = this.preferences.boost[e], n = this.sliderConfigResolved.startingMax;
113
+ t > n ? this.tagMaxRanges[e] = Math.min(Math.ceil(t), this.sliderConfigResolved.absoluteMax) : this.tagMaxRanges[e] = n;
114
+ });
115
+ },
116
+ async savePreferences() {
117
+ if (!this.user || !this.courseId) {
118
+ this.saveError = "Unable to save - user or course not available";
119
+ return;
120
+ }
121
+ this.saving = !0, this.saveError = "", this.saveSuccess = !1;
122
+ try {
123
+ let e = {
124
+ boost: { ...this.preferences.boost },
125
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
126
+ };
127
+ await this.user.putStrategyState(this.courseId, i, e), this.savedPreferences.boost = { ...this.preferences.boost }, this.saveSuccess = !0, this.$emit("preferences-saved", e);
128
+ } catch (e) {
129
+ console.error("Failed to save preferences:", e), this.saveError = "Failed to save preferences. Please try again.";
130
+ } finally {
131
+ this.saving = !1;
132
+ }
133
+ }
134
+ }
135
+ });
136
+ //#endregion
137
+ export { a as default };
138
+
139
+ //# sourceMappingURL=UserTagPreferences.vue_vue_type_script_lang.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserTagPreferences.vue_vue_type_script_lang.mjs","names":[],"sources":["../../src/components/UserTagPreferences.vue"],"sourcesContent":["<template>\n <v-card :loading=\"loading\">\n <v-card-title>\n <v-icon start>mdi-tune</v-icon>\n Learning Preferences\n </v-card-title>\n\n <v-card-subtitle v-if=\"courseId\">\n Customize how content is presented in this course\n </v-card-subtitle>\n\n <v-card-text>\n <v-alert v-if=\"!courseId\" type=\"info\" variant=\"tonal\" class=\"mb-4\">\n Select a course to configure your learning preferences.\n </v-alert>\n\n <div v-else-if=\"!loading\">\n <!-- Tag Preferences Section -->\n <div class=\"mb-6\">\n <h3 class=\"text-subtitle-1 font-weight-bold mb-2\">\n <v-icon start size=\"small\">mdi-tune-variant</v-icon>\n Tag Preferences\n </h3>\n <p class=\"text-body-2 text-medium-emphasis mb-3\">\n Adjust how much you want to see cards with specific tags. 0 = exclude, 1 = neutral, higher = prefer more.\n </p>\n\n <!-- Tag autocomplete -->\n <v-autocomplete\n v-model=\"tagToAdd\"\n :items=\"availableTagsToAdd\"\n item-title=\"name\"\n item-value=\"name\"\n label=\"Add tag preference\"\n placeholder=\"Search tags...\"\n density=\"compact\"\n variant=\"outlined\"\n clearable\n hide-details\n class=\"mb-4\"\n @update:model-value=\"addTag\"\n >\n <template #item=\"{ props, item }\">\n <v-list-item v-bind=\"props\">\n <template #subtitle>\n {{ item.raw.snippet }}\n </template>\n </v-list-item>\n </template>\n </v-autocomplete>\n\n <!-- Tag slider list -->\n <v-list v-if=\"tagNames.length > 0\" class=\"mt-4\">\n <v-list-item\n v-for=\"tagName in tagNames\"\n :key=\"tagName\"\n class=\"px-0\"\n >\n <template #default>\n <div class=\"d-flex align-center ga-3 w-100\">\n <!-- Tag name -->\n <v-chip\n size=\"small\"\n variant=\"tonal\"\n class=\"flex-shrink-0\"\n style=\"min-width: 120px;\"\n >\n {{ tagName }}\n </v-chip>\n\n <!-- Slider -->\n <v-slider\n :model-value=\"preferences.boost[tagName]\"\n :min=\"sliderConfigResolved.min\"\n :max=\"globalSliderMax\"\n :step=\"0.01\"\n hide-details\n class=\"flex-grow-1\"\n thumb-label\n @update:model-value=\"(val: number) => updateBoost(tagName, val)\"\n >\n <template #thumb-label=\"{ modelValue }\">\n {{ formatMultiplier(modelValue) }}\n </template>\n </v-slider>\n\n <!-- Current value display -->\n <span class=\"text-body-2 flex-shrink-0\" style=\"min-width: 60px; text-align: right;\">\n {{ formatMultiplier(preferences.boost[tagName]) }}\n </span>\n\n <!-- Expand range button (always visible) -->\n <v-btn\n icon=\"mdi-plus\"\n size=\"small\"\n variant=\"text\"\n density=\"compact\"\n :disabled=\"globalSliderMax >= sliderConfigResolved.absoluteMax\"\n @click=\"expandSliderRange(tagName)\"\n />\n\n <!-- Delete button -->\n <v-btn\n icon=\"mdi-delete\"\n size=\"small\"\n variant=\"text\"\n density=\"compact\"\n @click=\"removeTag(tagName)\"\n />\n </div>\n </template>\n </v-list-item>\n </v-list>\n\n <v-alert v-else type=\"info\" variant=\"tonal\" class=\"mt-4\">\n No tag preferences configured yet. Add tags above to get started.\n </v-alert>\n </div>\n\n <!-- Status / Feedback -->\n <v-alert v-if=\"saveError\" type=\"error\" variant=\"tonal\" class=\"mt-4\" closable @click:close=\"saveError = ''\">\n {{ saveError }}\n </v-alert>\n\n <v-alert v-if=\"saveSuccess\" type=\"success\" variant=\"tonal\" class=\"mt-4\" closable @click:close=\"saveSuccess = false\">\n Preferences saved successfully\n </v-alert>\n </div>\n\n <div v-else class=\"d-flex justify-center py-8\">\n <v-progress-circular indeterminate color=\"primary\" />\n </div>\n </v-card-text>\n\n <v-card-actions v-if=\"courseId && !loading\">\n <v-spacer />\n <v-btn\n variant=\"text\"\n :disabled=\"!hasChanges\"\n @click=\"resetToSaved\"\n >\n Reset\n </v-btn>\n <v-btn\n color=\"primary\"\n variant=\"flat\"\n :loading=\"saving\"\n :disabled=\"!hasChanges\"\n @click=\"savePreferences\"\n >\n Save Preferences\n </v-btn>\n </v-card-actions>\n </v-card>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent, PropType } from 'vue';\nimport { getDataLayer, Tag, CourseDBInterface, UserDBInterface } from '@vue-skuilder/db';\nimport { getCurrentUser } from '../stores/useAuthStore';\n\n/**\n * User's tag preference state, matching the backend schema.\n */\ninterface UserTagPreferenceState {\n boost: Record<string, number>;\n updatedAt: string;\n}\n\n/**\n * Slider configuration for tag preferences.\n */\ninterface SliderConfig {\n min?: number; // default: 0\n startingMax?: number; // default: 2\n absoluteMax?: number; // default: 10\n}\n\nconst DEFAULT_SLIDER_CONFIG: Required<SliderConfig> = {\n min: 0,\n startingMax: 2,\n absoluteMax: 10,\n};\n\nconst STRATEGY_KEY = 'UserTagPreferenceFilter';\n\nexport default defineComponent({\n name: 'UserTagPreferences',\n\n props: {\n /**\n * Course ID to configure preferences for.\n * If not provided, component shows a prompt to select a course.\n */\n courseId: {\n type: String as PropType<string>,\n required: false,\n default: '',\n },\n\n /**\n * Slider configuration (min, startingMax, absoluteMax).\n * All fields optional, defaults: { min: 0, startingMax: 2, absoluteMax: 10 }\n */\n sliderConfig: {\n type: Object as PropType<SliderConfig>,\n required: false,\n default: () => ({}),\n },\n },\n\n emits: ['preferences-saved', 'preferences-changed'],\n\n data() {\n return {\n loading: true,\n saving: false,\n saveError: '',\n saveSuccess: false,\n\n // Current working state\n preferences: {\n boost: {} as Record<string, number>,\n },\n\n // Saved state for comparison\n savedPreferences: {\n boost: {} as Record<string, number>,\n },\n\n // Per-tag current max range (for dynamic expansion)\n tagMaxRanges: {} as Record<string, number>,\n\n // Available tags from course\n availableTags: [] as Tag[],\n\n // Autocomplete model\n tagToAdd: null as string | null,\n\n // DB references\n courseDB: null as CourseDBInterface | null,\n user: null as UserDBInterface | null,\n };\n },\n\n computed: {\n /**\n * Resolved slider config with defaults\n */\n sliderConfigResolved(): Required<SliderConfig> {\n return {\n min: this.sliderConfig.min ?? DEFAULT_SLIDER_CONFIG.min,\n startingMax: this.sliderConfig.startingMax ?? DEFAULT_SLIDER_CONFIG.startingMax,\n absoluteMax: this.sliderConfig.absoluteMax ?? DEFAULT_SLIDER_CONFIG.absoluteMax,\n };\n },\n\n /**\n * List of tag names that have preferences (sorted alphabetically)\n */\n tagNames(): string[] {\n return Object.keys(this.preferences.boost).sort();\n },\n\n /**\n * Global max for all sliders (highest tagMaxRanges value)\n * Ensures all sliders have the same visual scale\n */\n globalSliderMax(): number {\n const maxValues = Object.values(this.tagMaxRanges);\n if (maxValues.length === 0) {\n return this.sliderConfigResolved.startingMax;\n }\n return Math.max(...maxValues);\n },\n\n /**\n * Tags available to add (not already in preferences)\n */\n availableTagsToAdd(): Tag[] {\n const usedTags = new Set(Object.keys(this.preferences.boost));\n return this.availableTags.filter((t) => !usedTags.has(t.name));\n },\n\n /**\n * Check if current preferences differ from saved state\n */\n hasChanges(): boolean {\n const currentTags = Object.keys(this.preferences.boost).sort();\n const savedTags = Object.keys(this.savedPreferences.boost).sort();\n\n if (currentTags.length !== savedTags.length) {\n return true;\n }\n\n if (currentTags.join(',') !== savedTags.join(',')) {\n return true;\n }\n\n return currentTags.some(\n (tag) => this.preferences.boost[tag] !== this.savedPreferences.boost[tag]\n );\n },\n },\n\n watch: {\n courseId: {\n immediate: true,\n async handler(newCourseId: string) {\n if (newCourseId) {\n await this.loadPreferences();\n } else {\n this.loading = false;\n }\n },\n },\n },\n\n methods: {\n /**\n * Load preferences from strategy state and available tags from course\n */\n async loadPreferences() {\n this.loading = true;\n this.saveError = '';\n\n try {\n // Get user and course DB\n this.user = (await getCurrentUser()) ?? null;\n if (!this.user) {\n throw new Error('User not available');\n }\n\n this.courseDB = getDataLayer().getCourseDB(this.courseId);\n\n // Load available tags\n const tagStubs = await this.courseDB.getCourseTagStubs();\n this.availableTags = tagStubs.rows.map((r) => r.doc!).filter(Boolean);\n\n // Load saved preferences\n const state = await this.user.getStrategyState<UserTagPreferenceState>(\n this.courseId,\n STRATEGY_KEY\n );\n\n if (state) {\n this.preferences.boost = { ...state.boost };\n } else {\n // No preferences yet - start fresh\n this.preferences.boost = {};\n }\n\n // Store saved state for comparison\n this.savedPreferences.boost = { ...this.preferences.boost };\n\n // Initialize tag max ranges, auto-expanding if saved value exceeds startingMax\n this.tagMaxRanges = {};\n Object.keys(this.preferences.boost).forEach((tag) => {\n const savedValue = this.preferences.boost[tag];\n const startingMax = this.sliderConfigResolved.startingMax;\n\n // If saved value exceeds startingMax, expand range to accommodate it\n // (capped at absoluteMax)\n if (savedValue > startingMax) {\n this.tagMaxRanges[tag] = Math.min(\n Math.ceil(savedValue),\n this.sliderConfigResolved.absoluteMax\n );\n } else {\n this.tagMaxRanges[tag] = startingMax;\n }\n });\n } catch (e) {\n console.error('Failed to load preferences:', e);\n this.saveError = 'Failed to load preferences. Please try again.';\n } finally {\n this.loading = false;\n }\n },\n\n /**\n * Add a tag to preferences with default multiplier of 1.0\n */\n addTag(tagName: string | null) {\n if (tagName && !(tagName in this.preferences.boost)) {\n this.preferences.boost[tagName] = 1.0;\n this.tagMaxRanges[tagName] = this.sliderConfigResolved.startingMax;\n this.$emit('preferences-changed', this.preferences);\n }\n // Clear the autocomplete\n this.$nextTick(() => {\n this.tagToAdd = null;\n });\n },\n\n /**\n * Remove a tag from preferences\n */\n removeTag(tagName: string) {\n delete this.preferences.boost[tagName];\n delete this.tagMaxRanges[tagName];\n this.$emit('preferences-changed', this.preferences);\n },\n\n /**\n * Update boost multiplier for a tag\n */\n updateBoost(tagName: string, value: number) {\n this.preferences.boost[tagName] = value;\n this.$emit('preferences-changed', this.preferences);\n },\n\n /**\n * Expand the global slider range by 1 and move the triggering tag's slider to new max\n */\n expandSliderRange(tagName: string) {\n const currentGlobalMax = this.globalSliderMax;\n if (currentGlobalMax < this.sliderConfigResolved.absoluteMax) {\n const newGlobalMax = currentGlobalMax + 1;\n\n // Expand all tag ranges to the new global max\n // (ensures all sliders stay synchronized)\n Object.keys(this.tagMaxRanges).forEach((tag) => {\n this.tagMaxRanges[tag] = Math.max(this.tagMaxRanges[tag], newGlobalMax);\n });\n\n // Move the triggering tag's slider to new max\n this.preferences.boost[tagName] = newGlobalMax;\n this.$emit('preferences-changed', this.preferences);\n }\n },\n\n /**\n * Format multiplier for display\n */\n formatMultiplier(value: number): string {\n if (value === 0) {\n return '0x';\n }\n if (value < 0.1) {\n return value.toFixed(2) + 'x';\n }\n return value.toFixed(1) + 'x';\n },\n\n /**\n * Reset to last saved state\n */\n resetToSaved() {\n this.preferences.boost = { ...this.savedPreferences.boost };\n this.saveSuccess = false;\n this.saveError = '';\n\n // Reset tag max ranges, auto-expanding if saved value exceeds startingMax\n this.tagMaxRanges = {};\n Object.keys(this.preferences.boost).forEach((tag) => {\n const savedValue = this.preferences.boost[tag];\n const startingMax = this.sliderConfigResolved.startingMax;\n\n if (savedValue > startingMax) {\n this.tagMaxRanges[tag] = Math.min(\n Math.ceil(savedValue),\n this.sliderConfigResolved.absoluteMax\n );\n } else {\n this.tagMaxRanges[tag] = startingMax;\n }\n });\n },\n\n /**\n * Save preferences to strategy state\n */\n async savePreferences() {\n if (!this.user || !this.courseId) {\n this.saveError = 'Unable to save - user or course not available';\n return;\n }\n\n this.saving = true;\n this.saveError = '';\n this.saveSuccess = false;\n\n try {\n const state: UserTagPreferenceState = {\n boost: { ...this.preferences.boost },\n updatedAt: new Date().toISOString(),\n };\n\n await this.user.putStrategyState<UserTagPreferenceState>(\n this.courseId,\n STRATEGY_KEY,\n state\n );\n\n // Update saved state\n this.savedPreferences.boost = { ...this.preferences.boost };\n\n this.saveSuccess = true;\n this.$emit('preferences-saved', state);\n } catch (e) {\n console.error('Failed to save preferences:', e);\n this.saveError = 'Failed to save preferences. Please try again.';\n } finally {\n this.saving = false;\n }\n },\n },\n});\n</script>\n\n<style scoped>\n/* Additional styles if needed */\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import "../../_virtual/_rolldown/runtime.mjs";
2
+ import e from "../../_virtual/_plugin-vue_export-helper.mjs";
3
+ import t from "./RequestPasswordReset.vue_vue_type_script_lang.mjs";
4
+ import { createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, openBlock as c, renderSlot as l, resolveComponent as u, toDisplayString as d, withCtx as f, withModifiers as p } from "vue";
5
+ //#region src/components/auth/RequestPasswordReset.vue
6
+ var m = {
7
+ key: 1,
8
+ class: "text-center"
9
+ };
10
+ function _sfc_render(e, t, h, g, _, v) {
11
+ let y = u("v-card-title"), b = u("v-text-field"), x = u("v-icon"), S = u("v-btn"), C = u("v-form"), w = u("v-card-text"), T = u("v-spacer"), E = u("v-card-actions"), D = u("v-card");
12
+ return c(), n(D, null, {
13
+ default: f(() => [
14
+ s(y, { class: "text-h5 bg-grey-lighten-2" }, {
15
+ default: f(() => t[2] || (t[2] = [o(" Reset Password ")])),
16
+ _: 1
17
+ }),
18
+ s(w, { class: "pa-6" }, {
19
+ default: f(() => [e.requestSent ? (c(), i("div", m, [
20
+ s(x, {
21
+ color: "success",
22
+ size: "64",
23
+ class: "mb-4"
24
+ }, {
25
+ default: f(() => t[6] || (t[6] = [o("mdi-email-check")])),
26
+ _: 1
27
+ }),
28
+ t[9] || (t[9] = a("h3", { class: "mb-2" }, "Check Your Email", -1)),
29
+ a("p", null, [
30
+ t[7] || (t[7] = o(" If an account exists with ")),
31
+ a("strong", null, d(e.email), 1),
32
+ t[8] || (t[8] = o(", you will receive a password reset link shortly. "))
33
+ ]),
34
+ t[10] || (t[10] = a("p", { class: "text-caption mt-4" }, "Didn't receive an email? Check your spam folder.", -1))
35
+ ])) : (c(), n(C, {
36
+ key: 0,
37
+ onSubmit: p(e.handleSubmit, ["prevent"])
38
+ }, {
39
+ default: f(() => [
40
+ t[5] || (t[5] = a("p", { class: "mb-4" }, " Enter your email address and we'll send you a link to reset your password. ", -1)),
41
+ s(b, {
42
+ modelValue: e.email,
43
+ "onUpdate:modelValue": t[0] || (t[0] = (t) => e.email = t),
44
+ name: "email",
45
+ label: "Email address",
46
+ type: "email",
47
+ "prepend-icon": "mdi-email",
48
+ error: e.emailError,
49
+ hint: e.emailHint,
50
+ disabled: e.isSubmitting,
51
+ onBlur: e.validateEmail
52
+ }, null, 8, [
53
+ "modelValue",
54
+ "error",
55
+ "hint",
56
+ "disabled",
57
+ "onBlur"
58
+ ]),
59
+ s(S, {
60
+ type: "submit",
61
+ color: "primary",
62
+ class: "mt-4",
63
+ loading: e.isSubmitting,
64
+ disabled: !e.email || e.emailError,
65
+ block: ""
66
+ }, {
67
+ default: f(() => [s(x, { start: "" }, {
68
+ default: f(() => t[3] || (t[3] = [o("mdi-email-send")])),
69
+ _: 1
70
+ }), t[4] || (t[4] = o(" Send Reset Link "))]),
71
+ _: 1
72
+ }, 8, ["loading", "disabled"])
73
+ ]),
74
+ _: 1
75
+ }, 8, ["onSubmit"]))]),
76
+ _: 1
77
+ }),
78
+ e.requestSent ? r("", !0) : (c(), n(E, { key: 0 }, {
79
+ default: f(() => [l(e.$slots, "back-action", {}, () => [s(S, {
80
+ variant: "text",
81
+ onClick: t[1] || (t[1] = (t) => e.$emit("cancel"))
82
+ }, {
83
+ default: f(() => t[11] || (t[11] = [o(" Cancel ")])),
84
+ _: 1
85
+ })]), s(T)]),
86
+ _: 3
87
+ }))
88
+ ]),
89
+ _: 3
90
+ });
91
+ }
92
+ var h = /* @__PURE__ */ e(t, [["render", _sfc_render]]);
93
+ //#endregion
94
+ export { h as default };
95
+
96
+ //# sourceMappingURL=RequestPasswordReset.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestPasswordReset.mjs","names":[],"sources":["../../../src/components/auth/RequestPasswordReset.vue"],"sourcesContent":["<template>\n <v-card>\n <v-card-title class=\"text-h5 bg-grey-lighten-2\">\n Reset Password\n </v-card-title>\n\n <v-card-text class=\"pa-6\">\n <v-form v-if=\"!requestSent\" @submit.prevent=\"handleSubmit\">\n <p class=\"mb-4\">\n Enter your email address and we'll send you a link to reset your password.\n </p>\n\n <v-text-field\n v-model=\"email\"\n name=\"email\"\n label=\"Email address\"\n type=\"email\"\n prepend-icon=\"mdi-email\"\n :error=\"emailError\"\n :hint=\"emailHint\"\n :disabled=\"isSubmitting\"\n @blur=\"validateEmail\"\n ></v-text-field>\n\n <v-btn\n type=\"submit\"\n color=\"primary\"\n class=\"mt-4\"\n :loading=\"isSubmitting\"\n :disabled=\"!email || emailError\"\n block\n >\n <v-icon start>mdi-email-send</v-icon>\n Send Reset Link\n </v-btn>\n </v-form>\n\n <!-- Success message -->\n <div v-else class=\"text-center\">\n <v-icon color=\"success\" size=\"64\" class=\"mb-4\">mdi-email-check</v-icon>\n <h3 class=\"mb-2\">Check Your Email</h3>\n <p>\n If an account exists with <strong>{{ email }}</strong>, you will receive a password\n reset link shortly.\n </p>\n <p class=\"text-caption mt-4\">Didn't receive an email? Check your spam folder.</p>\n </div>\n </v-card-text>\n\n <v-card-actions v-if=\"!requestSent\">\n <slot name=\"back-action\">\n <!-- External apps can provide their own back button -->\n <v-btn variant=\"text\" @click=\"$emit('cancel')\">\n Cancel\n </v-btn>\n </slot>\n <v-spacer></v-spacer>\n </v-card-actions>\n </v-card>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { requestPasswordReset } from '../../services/authAPI';\nimport { alertUser } from '../SnackbarService';\nimport { Status } from '@vue-skuilder/common';\n\nexport default defineComponent({\n name: 'RequestPasswordReset',\n\n emits: ['cancel', 'success'],\n\n data() {\n return {\n email: '',\n emailError: false,\n emailHint: '',\n isSubmitting: false,\n requestSent: false,\n };\n },\n\n methods: {\n validateEmail() {\n this.emailError = false;\n this.emailHint = '';\n\n if (!this.email) return;\n\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(this.email)) {\n this.emailError = true;\n this.emailHint = 'Please enter a valid email address';\n }\n },\n\n async handleSubmit() {\n this.validateEmail();\n\n if (this.emailError || !this.email) {\n return;\n }\n\n this.isSubmitting = true;\n\n try {\n // Pass current origin for correct reset link in email\n const origin = typeof window !== 'undefined' ? window.location.origin : undefined;\n const result = await requestPasswordReset(this.email, origin);\n\n if (result.ok) {\n this.requestSent = true;\n this.$emit('success', this.email);\n } else {\n alertUser({\n text: result.error || 'Failed to send reset email',\n status: Status.error,\n });\n }\n } catch (error) {\n alertUser({\n text: 'An unexpected error occurred',\n status: Status.error,\n });\n } finally {\n this.isSubmitting = false;\n }\n },\n },\n});\n</script>\n"],"mappings":";;;;;;CAAA,KAAA;CAsCkB,OAAM;;;;aArCtB,EAyDS,GAAA,MAAA;EA1DX,SAAA,QAImB;GAFf,EAEe,GAAA,EAFD,OAAM,6BAA2B,EAAA;IAFnD,SAAA,QAII,EAAA,OAAA,EAAA,KAAA,CAJJ,EAEoD,mBAEhD,CAAA,EAAA;IAJJ,GAAA;;GAMI,EAyCc,GAAA,EAzCD,OAAM,QAAM,EAAA;IAN7B,SAAA,QAmCe,CA5BM,EAAA,oBA+Bf,EAQM,OARN,GAQM;KAPJ,EAAuE,GAAA;MAA/D,OAAM;MAAU,MAAK;MAAK,OAAM;;MAvChD,SAAA,QAuCsE,EAAA,OAAA,EAAA,KAAA,CAvCtE,EAuCuD,kBAAe,CAAA,EAAA;MAvCtE,GAAA;;qBAwCQ,EAAsC,MAAA,EAAlC,OAAM,QAAM,EAAC,oBAAgB,GAAA;KACjC,EAGI,KAAA,MAAA;sBA5CZ,EAyCW,8BACyB;MAAA,EAA4B,UAAA,MAAA,EAAjB,EAAA,MAAK,EAAA,EAAA;sBA1CpD,EA0CgE,qDAExD;;uBACA,EAAiF,KAAA,EAA9E,OAAM,qBAAmB,EAAC,oDAAgD,GAAA;WAtChE,GAAA,EAAf,EA4BS,GAAA;KAnCf,KAAA;KAOmC,UAPnC,EAOmD,EAAA,cAAY,CAAA,UAAA,CAAA;;KAP/D,SAAA,QAUY;sBAFJ,EAEI,KAAA,EAFD,OAAM,QAAM,EAAC,gFAEhB,GAAA;MAEA,EAUgB,GAAA;OAtBxB,YAamB,EAAA;OAbnB,uBAAA,EAAA,OAAA,EAAA,MAAA,MAAA,EAamB,QAAK;OACd,MAAK;OACL,OAAM;OACN,MAAK;OACL,gBAAa;OACZ,OAAO,EAAA;OACP,MAAM,EAAA;OACN,UAAU,EAAA;OACV,QAAM,EAAA;;;;;;;;MAGT,EAUQ,GAAA;OATN,MAAK;OACL,OAAM;OACN,OAAM;OACL,SAAS,EAAA;OACT,UAAQ,CAAG,EAAA,SAAS,EAAA;OACrB,OAAA;;OA9BV,SAAA,QAgC+C,CAArC,EAAqC,GAAA,EAA7B,OAAA,IAAK,EAAA;QAhCvB,SAAA,QAgCsC,EAAA,OAAA,EAAA,KAAA,CAhCtC,EAgCwB,iBAAc,CAAA,EAAA;QAhCtC,GAAA;2BAAA,EAgC+C,oBAEvC,EAAA,CAAA;OAlCR,GAAA;;;KAAA,GAAA;;IAAA,GAAA;;GAiD2B,EAAA,cAjD3B,EAAA,IAAA,GAAA,IAiD2B,GAAA,EAAvB,EAQiB,GAAA,EAzDrB,KAAA,GAAA,EAAA;IAAA,SAAA,QAuDa,CALP,EAKO,EAAA,QAAA,eAAA,EAAA,QAAA,CAHL,EAEQ,GAAA;KAFD,SAAQ;KAAQ,SAAK,EAAA,OAAA,EAAA,MAAA,MAAE,EAAA,MAAK,SAAA;;KApD3C,SAAA,QAsDQ,EAAA,QAAA,EAAA,MAAA,CAtDR,EAoDuD,WAE/C,CAAA,EAAA;KAtDR,GAAA;UAwDM,EAAqB,EAAA,CAAA,CAAA;IAxD3B,GAAA;;;EAAA,GAAA"}
@@ -0,0 +1,47 @@
1
+ import "../../_virtual/_rolldown/runtime.mjs";
2
+ import { alertUser as e } from "../SnackbarService.mjs";
3
+ import { requestPasswordReset as t } from "../../services/authAPI.mjs";
4
+ import { defineComponent as n } from "vue";
5
+ import { Status as r } from "@vue-skuilder/common";
6
+ //#region src/components/auth/RequestPasswordReset.vue?vue&type=script&lang.ts
7
+ var i = n({
8
+ name: "RequestPasswordReset",
9
+ emits: ["cancel", "success"],
10
+ data() {
11
+ return {
12
+ email: "",
13
+ emailError: !1,
14
+ emailHint: "",
15
+ isSubmitting: !1,
16
+ requestSent: !1
17
+ };
18
+ },
19
+ methods: {
20
+ validateEmail() {
21
+ this.emailError = !1, this.emailHint = "", this.email && (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email) || (this.emailError = !0, this.emailHint = "Please enter a valid email address"));
22
+ },
23
+ async handleSubmit() {
24
+ if (this.validateEmail(), !(this.emailError || !this.email)) {
25
+ this.isSubmitting = !0;
26
+ try {
27
+ let n = typeof window < "u" ? window.location.origin : void 0, i = await t(this.email, n);
28
+ i.ok ? (this.requestSent = !0, this.$emit("success", this.email)) : e({
29
+ text: i.error || "Failed to send reset email",
30
+ status: r.error
31
+ });
32
+ } catch {
33
+ e({
34
+ text: "An unexpected error occurred",
35
+ status: r.error
36
+ });
37
+ } finally {
38
+ this.isSubmitting = !1;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ });
44
+ //#endregion
45
+ export { i as default };
46
+
47
+ //# sourceMappingURL=RequestPasswordReset.vue_vue_type_script_lang.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RequestPasswordReset.vue_vue_type_script_lang.mjs","names":[],"sources":["../../../src/components/auth/RequestPasswordReset.vue"],"sourcesContent":["<template>\n <v-card>\n <v-card-title class=\"text-h5 bg-grey-lighten-2\">\n Reset Password\n </v-card-title>\n\n <v-card-text class=\"pa-6\">\n <v-form v-if=\"!requestSent\" @submit.prevent=\"handleSubmit\">\n <p class=\"mb-4\">\n Enter your email address and we'll send you a link to reset your password.\n </p>\n\n <v-text-field\n v-model=\"email\"\n name=\"email\"\n label=\"Email address\"\n type=\"email\"\n prepend-icon=\"mdi-email\"\n :error=\"emailError\"\n :hint=\"emailHint\"\n :disabled=\"isSubmitting\"\n @blur=\"validateEmail\"\n ></v-text-field>\n\n <v-btn\n type=\"submit\"\n color=\"primary\"\n class=\"mt-4\"\n :loading=\"isSubmitting\"\n :disabled=\"!email || emailError\"\n block\n >\n <v-icon start>mdi-email-send</v-icon>\n Send Reset Link\n </v-btn>\n </v-form>\n\n <!-- Success message -->\n <div v-else class=\"text-center\">\n <v-icon color=\"success\" size=\"64\" class=\"mb-4\">mdi-email-check</v-icon>\n <h3 class=\"mb-2\">Check Your Email</h3>\n <p>\n If an account exists with <strong>{{ email }}</strong>, you will receive a password\n reset link shortly.\n </p>\n <p class=\"text-caption mt-4\">Didn't receive an email? Check your spam folder.</p>\n </div>\n </v-card-text>\n\n <v-card-actions v-if=\"!requestSent\">\n <slot name=\"back-action\">\n <!-- External apps can provide their own back button -->\n <v-btn variant=\"text\" @click=\"$emit('cancel')\">\n Cancel\n </v-btn>\n </slot>\n <v-spacer></v-spacer>\n </v-card-actions>\n </v-card>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { requestPasswordReset } from '../../services/authAPI';\nimport { alertUser } from '../SnackbarService';\nimport { Status } from '@vue-skuilder/common';\n\nexport default defineComponent({\n name: 'RequestPasswordReset',\n\n emits: ['cancel', 'success'],\n\n data() {\n return {\n email: '',\n emailError: false,\n emailHint: '',\n isSubmitting: false,\n requestSent: false,\n };\n },\n\n methods: {\n validateEmail() {\n this.emailError = false;\n this.emailHint = '';\n\n if (!this.email) return;\n\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(this.email)) {\n this.emailError = true;\n this.emailHint = 'Please enter a valid email address';\n }\n },\n\n async handleSubmit() {\n this.validateEmail();\n\n if (this.emailError || !this.email) {\n return;\n }\n\n this.isSubmitting = true;\n\n try {\n // Pass current origin for correct reset link in email\n const origin = typeof window !== 'undefined' ? window.location.origin : undefined;\n const result = await requestPasswordReset(this.email, origin);\n\n if (result.ok) {\n this.requestSent = true;\n this.$emit('success', this.email);\n } else {\n alertUser({\n text: result.error || 'Failed to send reset email',\n status: Status.error,\n });\n }\n } catch (error) {\n alertUser({\n text: 'An unexpected error occurred',\n status: Status.error,\n });\n } finally {\n this.isSubmitting = false;\n }\n },\n },\n});\n</script>\n"],"mappings":";;;;;;AAmEA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CAEN,OAAO,CAAC,UAAU,UAAU;CAE5B,OAAO;AACL,SAAO;GACL,OAAO;GACP,YAAY;GACZ,WAAW;GACX,cAAc;GACd,aAAa;GACd;;CAGH,SAAS;EACP,gBAAgB;AACd,QAAK,aAAa,IAClB,KAAK,YAAY,IAEZ,KAAK,UAES,6BACH,KAAK,KAAK,MAAM,KAC9B,KAAK,aAAa,IAClB,KAAK,YAAY;;EAIrB,MAAM,eAAe;AACnB,YAAK,eAAe,EAEhB,OAAK,cAAc,CAAC,KAAK,QAI7B;SAAK,eAAe;AAEpB,QAAI;KAEF,IAAM,IAAS,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,KAAA,GAClE,IAAS,MAAM,EAAqB,KAAK,OAAO,EAAO;AAE7D,KAAI,EAAO,MACT,KAAK,cAAc,IACnB,KAAK,MAAM,WAAW,KAAK,MAAM,IAEjC,EAAU;MACR,MAAM,EAAO,SAAS;MACtB,QAAQ,EAAO;MAChB,CAAC;YAEU;AACd,OAAU;MACR,MAAM;MACN,QAAQ,EAAO;MAChB,CAAC;cACM;AACR,UAAK,eAAe;;;;EAGzB;CACF,CAAC"}
@@ -0,0 +1,129 @@
1
+ import "../../_virtual/_rolldown/runtime.mjs";
2
+ import e from "../../_virtual/_plugin-vue_export-helper.mjs";
3
+ import t from "./ResetPassword.vue_vue_type_script_lang.mjs";
4
+ import { createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createTextVNode as o, createVNode as s, openBlock as c, renderSlot as l, resolveComponent as u, withCtx as d, withModifiers as f } from "vue";
5
+ //#region src/components/auth/ResetPassword.vue
6
+ var p = {
7
+ key: 1,
8
+ class: "text-center"
9
+ };
10
+ function _sfc_render(e, t, m, h, g, _) {
11
+ let v = u("v-card-title"), y = u("v-text-field"), b = u("v-icon"), x = u("v-btn"), S = u("v-form"), C = u("v-card-text"), w = u("v-spacer"), T = u("v-card-actions"), E = u("v-card"), D = u("v-col"), O = u("v-row"), k = u("v-container");
12
+ return c(), n(k, {
13
+ class: "fill-height",
14
+ fluid: ""
15
+ }, {
16
+ default: d(() => [s(O, {
17
+ align: "center",
18
+ justify: "center"
19
+ }, {
20
+ default: d(() => [s(D, {
21
+ cols: "12",
22
+ sm: "8",
23
+ md: "6"
24
+ }, {
25
+ default: d(() => [s(E, null, {
26
+ default: d(() => [
27
+ s(v, { class: "text-h5 bg-grey-lighten-2" }, {
28
+ default: d(() => t[4] || (t[4] = [o(" Set New Password ")])),
29
+ _: 1
30
+ }),
31
+ s(C, { class: "pa-6" }, {
32
+ default: d(() => [e.isComplete ? (c(), i("div", p, [
33
+ s(b, {
34
+ color: "success",
35
+ size: "64",
36
+ class: "mb-4"
37
+ }, {
38
+ default: d(() => t[8] || (t[8] = [o("mdi-check-circle")])),
39
+ _: 1
40
+ }),
41
+ t[9] || (t[9] = a("h3", { class: "mb-2" }, "Password Reset Successful!", -1)),
42
+ t[10] || (t[10] = a("p", null, "Your password has been updated. You can now log in with your new password.", -1))
43
+ ])) : (c(), n(S, {
44
+ key: 0,
45
+ onSubmit: f(e.handleSubmit, ["prevent"])
46
+ }, {
47
+ default: d(() => [
48
+ t[7] || (t[7] = a("p", { class: "mb-4" }, "Enter your new password below.", -1)),
49
+ s(y, {
50
+ modelValue: e.password,
51
+ "onUpdate:modelValue": t[0] || (t[0] = (t) => e.password = t),
52
+ "prepend-icon": "mdi-lock",
53
+ name: "password",
54
+ label: "New password",
55
+ min: "4",
56
+ "append-icon": e.passwordVisible ? "mdi-eye-off" : "mdi-eye",
57
+ type: e.passwordVisible ? "text" : "password",
58
+ disabled: e.isSubmitting,
59
+ "onClick:append": t[1] || (t[1] = () => e.passwordVisible = !e.passwordVisible)
60
+ }, null, 8, [
61
+ "modelValue",
62
+ "append-icon",
63
+ "type",
64
+ "disabled"
65
+ ]),
66
+ s(y, {
67
+ modelValue: e.confirmPassword,
68
+ "onUpdate:modelValue": t[2] || (t[2] = (t) => e.confirmPassword = t),
69
+ "prepend-icon": "mdi-lock",
70
+ name: "confirmPassword",
71
+ label: "Confirm new password",
72
+ min: "4",
73
+ type: e.passwordVisible ? "text" : "password",
74
+ error: e.confirmPasswordError,
75
+ hint: e.confirmPasswordHint,
76
+ disabled: e.isSubmitting,
77
+ onBlur: e.validateConfirmPassword
78
+ }, null, 8, [
79
+ "modelValue",
80
+ "type",
81
+ "error",
82
+ "hint",
83
+ "disabled",
84
+ "onBlur"
85
+ ]),
86
+ s(x, {
87
+ type: "submit",
88
+ color: "primary",
89
+ class: "mt-4",
90
+ loading: e.isSubmitting,
91
+ disabled: !e.canSubmit,
92
+ block: ""
93
+ }, {
94
+ default: d(() => [s(b, { start: "" }, {
95
+ default: d(() => t[5] || (t[5] = [o("mdi-lock-reset")])),
96
+ _: 1
97
+ }), t[6] || (t[6] = o(" Reset Password "))]),
98
+ _: 1
99
+ }, 8, ["loading", "disabled"])
100
+ ]),
101
+ _: 1
102
+ }, 8, ["onSubmit"]))]),
103
+ _: 1
104
+ }),
105
+ e.isComplete ? (c(), n(T, { key: 0 }, {
106
+ default: d(() => [s(w), l(e.$slots, "success-action", {}, () => [s(x, {
107
+ color: "primary",
108
+ onClick: t[3] || (t[3] = (t) => e.$emit("complete"))
109
+ }, {
110
+ default: d(() => t[11] || (t[11] = [o(" Continue to Login ")])),
111
+ _: 1
112
+ })])]),
113
+ _: 3
114
+ })) : r("", !0)
115
+ ]),
116
+ _: 3
117
+ })]),
118
+ _: 3
119
+ })]),
120
+ _: 3
121
+ })]),
122
+ _: 3
123
+ });
124
+ }
125
+ var m = /* @__PURE__ */ e(t, [["render", _sfc_render]]);
126
+ //#endregion
127
+ export { m as default };
128
+
129
+ //# sourceMappingURL=ResetPassword.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetPassword.mjs","names":[],"sources":["../../../src/components/auth/ResetPassword.vue"],"sourcesContent":["<template>\n <v-container class=\"fill-height\" fluid>\n <v-row align=\"center\" justify=\"center\">\n <v-col cols=\"12\" sm=\"8\" md=\"6\">\n <v-card>\n <v-card-title class=\"text-h5 bg-grey-lighten-2\">\n Set New Password\n </v-card-title>\n\n <v-card-text class=\"pa-6\">\n <v-form v-if=\"!isComplete\" @submit.prevent=\"handleSubmit\">\n <p class=\"mb-4\">Enter your new password below.</p>\n\n <v-text-field\n v-model=\"password\"\n prepend-icon=\"mdi-lock\"\n name=\"password\"\n label=\"New password\"\n min=\"4\"\n :append-icon=\"passwordVisible ? 'mdi-eye-off' : 'mdi-eye'\"\n :type=\"passwordVisible ? 'text' : 'password'\"\n :disabled=\"isSubmitting\"\n @click:append=\"() => (passwordVisible = !passwordVisible)\"\n ></v-text-field>\n\n <v-text-field\n v-model=\"confirmPassword\"\n prepend-icon=\"mdi-lock\"\n name=\"confirmPassword\"\n label=\"Confirm new password\"\n min=\"4\"\n :type=\"passwordVisible ? 'text' : 'password'\"\n :error=\"confirmPasswordError\"\n :hint=\"confirmPasswordHint\"\n :disabled=\"isSubmitting\"\n @blur=\"validateConfirmPassword\"\n ></v-text-field>\n\n <v-btn\n type=\"submit\"\n color=\"primary\"\n class=\"mt-4\"\n :loading=\"isSubmitting\"\n :disabled=\"!canSubmit\"\n block\n >\n <v-icon start>mdi-lock-reset</v-icon>\n Reset Password\n </v-btn>\n </v-form>\n\n <!-- Success state -->\n <div v-else class=\"text-center\">\n <v-icon color=\"success\" size=\"64\" class=\"mb-4\">mdi-check-circle</v-icon>\n <h3 class=\"mb-2\">Password Reset Successful!</h3>\n <p>Your password has been updated. You can now log in with your new password.</p>\n </div>\n </v-card-text>\n\n <v-card-actions v-if=\"isComplete\">\n <v-spacer></v-spacer>\n <slot name=\"success-action\">\n <!-- External apps can provide their own navigation -->\n <v-btn color=\"primary\" @click=\"$emit('complete')\">\n Continue to Login\n </v-btn>\n </slot>\n </v-card-actions>\n </v-card>\n </v-col>\n </v-row>\n </v-container>\n</template>\n\n<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { resetPassword } from '../../services/authAPI';\nimport { alertUser } from '../SnackbarService';\nimport { Status } from '@vue-skuilder/common';\n\nexport default defineComponent({\n name: 'ResetPassword',\n\n emits: ['complete', 'error'],\n\n props: {\n /**\n * Reset token. If not provided, will try to read from URL query params.\n */\n token: {\n type: String,\n default: null,\n },\n },\n\n data() {\n return {\n password: '',\n confirmPassword: '',\n passwordVisible: false,\n confirmPasswordError: false,\n confirmPasswordHint: '',\n isSubmitting: false,\n isComplete: false,\n };\n },\n\n computed: {\n canSubmit(): boolean {\n return (\n this.password.length >= 4 &&\n this.confirmPassword.length >= 4 &&\n this.password === this.confirmPassword &&\n !this.confirmPasswordError\n );\n },\n },\n\n methods: {\n validateConfirmPassword() {\n this.confirmPasswordError = false;\n this.confirmPasswordHint = '';\n\n if (!this.confirmPassword) return;\n\n if (this.password !== this.confirmPassword) {\n this.confirmPasswordError = true;\n this.confirmPasswordHint = 'Passwords do not match';\n }\n },\n\n async handleSubmit() {\n this.validateConfirmPassword();\n\n if (!this.canSubmit) {\n return;\n }\n\n // Get token from prop or URL query params\n const token = this.token || this.getTokenFromURL();\n\n if (!token) {\n alertUser({\n text: 'No reset token provided. Please use the link from your email.',\n status: Status.error,\n });\n this.$emit('error', 'No token');\n return;\n }\n\n this.isSubmitting = true;\n\n try {\n const result = await resetPassword(token, this.password);\n\n if (result.ok) {\n this.isComplete = true;\n alertUser({\n text: 'Password reset successfully!',\n status: Status.ok,\n });\n } else {\n alertUser({\n text: result.error || 'Failed to reset password',\n status: Status.error,\n });\n this.$emit('error', result.error);\n }\n } catch (error) {\n alertUser({\n text: 'An unexpected error occurred',\n status: Status.error,\n });\n this.$emit('error', 'Unexpected error');\n } finally {\n this.isSubmitting = false;\n }\n },\n\n getTokenFromURL(): string | null {\n if (typeof window === 'undefined') return null;\n\n const params = new URLSearchParams(window.location.search);\n return params.get('token');\n },\n },\n});\n</script>\n"],"mappings":";;;;;;CAAA,KAAA;CAoDwB,OAAM;;;;aAnD5B,EAsEc,GAAA;EAtED,OAAM;EAAc,OAAA;;EADnC,SAAA,QAsEY,CApER,EAoEQ,GAAA;GApED,OAAM;GAAS,SAAQ;;GAFlC,SAAA,QAqEc,CAlER,EAkEQ,GAAA;IAlED,MAAK;IAAK,IAAG;IAAI,IAAG;;IAHjC,SAAA,QAoEiB,CAhET,EAgES,GAAA,MAAA;KApEjB,SAAA,QAOyB;MAFf,EAEe,GAAA,EAFD,OAAM,6BAA2B,EAAA;OALzD,SAAA,QAOU,EAAA,OAAA,EAAA,KAAA,CAPV,EAK0D,qBAEhD,CAAA,EAAA;OAPV,GAAA;;MASU,EAgDc,GAAA,EAhDD,OAAM,QAAM,EAAA;OATnC,SAAA,QAiDqB,CAvCM,EAAA,mBA0Cf,EAIM,OAJN,GAIM;QAHJ,EAAwE,GAAA;SAAhE,OAAM;SAAU,MAAK;SAAK,OAAM;;SArDtD,SAAA,QAqD6E,EAAA,OAAA,EAAA,KAAA,CArD7E,EAqD6D,mBAAgB,CAAA,EAAA;SArD7E,GAAA;;wBAsDc,EAAgD,MAAA,EAA5C,OAAM,QAAM,EAAC,8BAA0B,GAAA;0BAC3C,EAAiF,KAAA,MAA9E,8EAA0E,GAAA;cA7ChE,GAAA,EAAf,EAuCS,GAAA;QAjDrB,KAAA;QAUwC,UAVxC,EAUwD,EAAA,cAAY,CAAA,UAAA,CAAA;;QAVpE,SAAA,QAWgE;yBAAlD,EAAkD,KAAA,EAA/C,OAAM,QAAM,EAAC,kCAA8B,GAAA;SAE9C,EAUgB,GAAA;UAvB9B,YAcyB,EAAA;UAdzB,uBAAA,EAAA,OAAA,EAAA,MAAA,MAAA,EAcyB,WAAQ;UACjB,gBAAa;UACb,MAAK;UACL,OAAM;UACN,KAAI;UACH,eAAa,EAAA,kBAAe,gBAAA;UAC5B,MAAM,EAAA,kBAAe,SAAA;UACrB,UAAU,EAAA;UACV,kBAAY,EAAA,OAAA,EAAA,WAAS,EAAA,kBAAe,CAAI,EAAA;;;;;;;SAG3C,EAWgB,GAAA;UApC9B,YA0ByB,EAAA;UA1BzB,uBAAA,EAAA,OAAA,EAAA,MAAA,MAAA,EA0ByB,kBAAe;UACxB,gBAAa;UACb,MAAK;UACL,OAAM;UACN,KAAI;UACH,MAAM,EAAA,kBAAe,SAAA;UACrB,OAAO,EAAA;UACP,MAAM,EAAA;UACN,UAAU,EAAA;UACV,QAAM,EAAA;;;;;;;;;SAGT,EAUQ,GAAA;UATN,MAAK;UACL,OAAM;UACN,OAAM;UACL,SAAS,EAAA;UACT,UAAQ,CAAG,EAAA;UACZ,OAAA;;UA5ChB,SAAA,QA8CqD,CAArC,EAAqC,GAAA,EAA7B,OAAA,IAAK,EAAA;WA9C7B,SAAA,QA8C4C,EAAA,OAAA,EAAA,KAAA,CA9C5C,EA8C8B,iBAAc,CAAA,EAAA;WA9C5C,GAAA;8BAAA,EA8CqD,mBAEvC,EAAA,CAAA;UAhDd,GAAA;;;QAAA,GAAA;;OAAA,GAAA;;MA2DgC,EAAA,cAAA,GAAA,EAAtB,EAQiB,GAAA,EAnE3B,KAAA,GAAA,EAAA;OAAA,SAAA,QA4DiC,CAArB,EAAqB,EAAA,EACrB,EAKO,EAAA,QAAA,kBAAA,EAAA,QAAA,CAHL,EAEQ,GAAA;QAFD,OAAM;QAAW,SAAK,EAAA,OAAA,EAAA,MAAA,MAAE,EAAA,MAAK,WAAA;;QA/DlD,SAAA,QAiEc,EAAA,QAAA,EAAA,MAAA,CAjEd,EA+DgE,sBAElD,CAAA,EAAA;QAjEd,GAAA;;OAAA,GAAA;YAAA,EAAA,IAAA,GAAA;;KAAA,GAAA;;IAAA,GAAA;;GAAA,GAAA;;EAAA,GAAA"}
@@ -0,0 +1,69 @@
1
+ import "../../_virtual/_rolldown/runtime.mjs";
2
+ import { alertUser as e } from "../SnackbarService.mjs";
3
+ import { resetPassword as t } from "../../services/authAPI.mjs";
4
+ import { defineComponent as n } from "vue";
5
+ import { Status as r } from "@vue-skuilder/common";
6
+ //#region src/components/auth/ResetPassword.vue?vue&type=script&lang.ts
7
+ var i = n({
8
+ name: "ResetPassword",
9
+ emits: ["complete", "error"],
10
+ props: { token: {
11
+ type: String,
12
+ default: null
13
+ } },
14
+ data() {
15
+ return {
16
+ password: "",
17
+ confirmPassword: "",
18
+ passwordVisible: !1,
19
+ confirmPasswordError: !1,
20
+ confirmPasswordHint: "",
21
+ isSubmitting: !1,
22
+ isComplete: !1
23
+ };
24
+ },
25
+ computed: { canSubmit() {
26
+ return this.password.length >= 4 && this.confirmPassword.length >= 4 && this.password === this.confirmPassword && !this.confirmPasswordError;
27
+ } },
28
+ methods: {
29
+ validateConfirmPassword() {
30
+ this.confirmPasswordError = !1, this.confirmPasswordHint = "", this.confirmPassword && this.password !== this.confirmPassword && (this.confirmPasswordError = !0, this.confirmPasswordHint = "Passwords do not match");
31
+ },
32
+ async handleSubmit() {
33
+ if (this.validateConfirmPassword(), !this.canSubmit) return;
34
+ let n = this.token || this.getTokenFromURL();
35
+ if (!n) {
36
+ e({
37
+ text: "No reset token provided. Please use the link from your email.",
38
+ status: r.error
39
+ }), this.$emit("error", "No token");
40
+ return;
41
+ }
42
+ this.isSubmitting = !0;
43
+ try {
44
+ let i = await t(n, this.password);
45
+ i.ok ? (this.isComplete = !0, e({
46
+ text: "Password reset successfully!",
47
+ status: r.ok
48
+ })) : (e({
49
+ text: i.error || "Failed to reset password",
50
+ status: r.error
51
+ }), this.$emit("error", i.error));
52
+ } catch {
53
+ e({
54
+ text: "An unexpected error occurred",
55
+ status: r.error
56
+ }), this.$emit("error", "Unexpected error");
57
+ } finally {
58
+ this.isSubmitting = !1;
59
+ }
60
+ },
61
+ getTokenFromURL() {
62
+ return typeof window > "u" ? null : new URLSearchParams(window.location.search).get("token");
63
+ }
64
+ }
65
+ });
66
+ //#endregion
67
+ export { i as default };
68
+
69
+ //# sourceMappingURL=ResetPassword.vue_vue_type_script_lang.mjs.map