@vue-skuilder/common-ui 0.1.13-1 → 0.1.13-11

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 (35) hide show
  1. package/dist/assets/index.css +2 -2
  2. package/dist/common-ui.es.js +1235 -261
  3. package/dist/common-ui.es.js.map +1 -1
  4. package/dist/common-ui.umd.js +2 -2
  5. package/dist/common-ui.umd.js.map +1 -1
  6. package/dist/components/auth/index.d.ts +4 -0
  7. package/dist/components/auth/index.d.ts.map +1 -1
  8. package/dist/composables/useEntitlements.d.ts +32 -0
  9. package/dist/composables/useEntitlements.d.ts.map +1 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/services/authAPI.d.ts +65 -0
  13. package/dist/services/authAPI.d.ts.map +1 -0
  14. package/dist/stores/useAuthStore.d.ts +2 -2
  15. package/dist/stores/useAuthStore.d.ts.map +1 -1
  16. package/dist/stores/useConfigStore.d.ts.map +1 -1
  17. package/dist/utils/passwordValidation.d.ts +13 -0
  18. package/dist/utils/passwordValidation.d.ts.map +1 -0
  19. package/package.json +4 -4
  20. package/src/components/SkMouseTrap.vue +1 -1
  21. package/src/components/StudySession.vue +29 -14
  22. package/src/components/auth/RequestPasswordReset.vue +131 -0
  23. package/src/components/auth/ResetPassword.vue +188 -0
  24. package/src/components/auth/UserLogin.vue +36 -9
  25. package/src/components/auth/UserLoginAndRegistrationContainer.vue +40 -10
  26. package/src/components/auth/UserRegistration.vue +90 -9
  27. package/src/components/auth/VerifyEmail.vue +156 -0
  28. package/src/components/auth/index.ts +19 -1
  29. package/src/components/cardRendering/CardViewer.vue +9 -19
  30. package/src/composables/useEntitlements.ts +118 -0
  31. package/src/index.ts +6 -0
  32. package/src/services/authAPI.ts +243 -0
  33. package/src/stores/useAuthStore.ts +6 -2
  34. package/src/stores/useConfigStore.ts +27 -13
  35. package/src/utils/passwordValidation.ts +31 -0
@@ -4001,7 +4001,7 @@ hooks.HTML5_FMT = {
4001
4001
  MONTH: "YYYY-MM"
4002
4002
  // <input type="month" />
4003
4003
  };
4004
- const _sfc_main$t = defineComponent({
4004
+ const _sfc_main$w = defineComponent({
4005
4005
  name: "HeatMap",
4006
4006
  props: {
4007
4007
  // Accept activity records directly as a prop
@@ -4246,10 +4246,10 @@ const _export_sfc = (sfc, props) => {
4246
4246
  }
4247
4247
  return target;
4248
4248
  };
4249
- const _hoisted_1$i = ["width", "height"];
4250
- const _hoisted_2$a = ["transform"];
4251
- const _hoisted_3$8 = ["y", "width", "height", "fill", "onMouseover"];
4252
- function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
4249
+ const _hoisted_1$m = ["width", "height"];
4250
+ const _hoisted_2$c = ["transform"];
4251
+ const _hoisted_3$9 = ["y", "width", "height", "fill", "onMouseover"];
4252
+ function _sfc_render$q(_ctx, _cache, $props, $setup, $data, $options) {
4253
4253
  return openBlock(), createElementBlock("div", null, [
4254
4254
  (openBlock(), createElementBlock("svg", {
4255
4255
  width: _ctx.width,
@@ -4270,11 +4270,11 @@ function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
4270
4270
  fill: _ctx.getColor(day.count),
4271
4271
  onMouseover: ($event) => _ctx.showTooltip(day, $event),
4272
4272
  onMouseout: _cache[0] || (_cache[0] = (...args) => _ctx.hideTooltip && _ctx.hideTooltip(...args))
4273
- }, null, 40, _hoisted_3$8);
4273
+ }, null, 40, _hoisted_3$9);
4274
4274
  }), 128))
4275
- ], 8, _hoisted_2$a);
4275
+ ], 8, _hoisted_2$c);
4276
4276
  }), 128))
4277
- ], 8, _hoisted_1$i)),
4277
+ ], 8, _hoisted_1$m)),
4278
4278
  _ctx.tooltipData ? (openBlock(), createElementBlock("div", {
4279
4279
  key: 0,
4280
4280
  class: "tooltip",
@@ -4282,7 +4282,7 @@ function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
4282
4282
  }, toDisplayString(_ctx.tooltipData.count) + " review" + toDisplayString(_ctx.tooltipData.count !== 1 ? "s" : "") + " on " + toDisplayString(_ctx.toDateString(_ctx.tooltipData.date)), 5)) : createCommentVNode("", true)
4283
4283
  ]);
4284
4284
  }
4285
- const HeatMap = /* @__PURE__ */ _export_sfc(_sfc_main$t, [["render", _sfc_render$n], ["__scopeId", "data-v-ca46239a"]]);
4285
+ const HeatMap = /* @__PURE__ */ _export_sfc(_sfc_main$w, [["render", _sfc_render$q], ["__scopeId", "data-v-ca46239a"]]);
4286
4286
  function getDefaultExportFromCjs(x) {
4287
4287
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4288
4288
  }
@@ -4862,7 +4862,7 @@ const _SkldrMouseTrap = class _SkldrMouseTrap {
4862
4862
  };
4863
4863
  __publicField(_SkldrMouseTrap, "_instance");
4864
4864
  let SkldrMouseTrap = _SkldrMouseTrap;
4865
- const _sfc_main$s = defineComponent({
4865
+ const _sfc_main$v = defineComponent({
4866
4866
  name: "SkMouseTrap",
4867
4867
  props: {
4868
4868
  refreshInterval: {
@@ -4892,8 +4892,8 @@ const _sfc_main$s = defineComponent({
4892
4892
  }
4893
4893
  }
4894
4894
  });
4895
- const _hoisted_1$h = { class: "text-caption ml-2" };
4896
- function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
4895
+ const _hoisted_1$l = { class: "text-caption ml-2" };
4896
+ function _sfc_render$p(_ctx, _cache, $props, $setup, $data, $options) {
4897
4897
  const _component_v_icon = resolveComponent("v-icon");
4898
4898
  const _component_v_btn = resolveComponent("v-btn");
4899
4899
  const _component_v_toolbar_title = resolveComponent("v-toolbar-title");
@@ -4928,7 +4928,7 @@ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
4928
4928
  createVNode(_component_v_card, null, {
4929
4929
  default: withCtx(() => [
4930
4930
  createVNode(_component_v_toolbar, {
4931
- color: "teal",
4931
+ color: "secondary",
4932
4932
  dark: "",
4933
4933
  dense: ""
4934
4934
  }, {
@@ -4962,7 +4962,7 @@ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
4962
4962
  _: 2
4963
4963
  }, 1024),
4964
4964
  createVNode(_component_v_spacer),
4965
- createElementVNode("span", _hoisted_1$h, toDisplayString(hk.command), 1)
4965
+ createElementVNode("span", _hoisted_1$l, toDisplayString(hk.command), 1)
4966
4966
  ]),
4967
4967
  _: 2
4968
4968
  }, 1024);
@@ -4977,8 +4977,8 @@ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
4977
4977
  _: 1
4978
4978
  })) : createCommentVNode("", true);
4979
4979
  }
4980
- const SkMouseTrap = /* @__PURE__ */ _export_sfc(_sfc_main$s, [["render", _sfc_render$m]]);
4981
- const _sfc_main$r = defineComponent({
4980
+ const SkMouseTrap = /* @__PURE__ */ _export_sfc(_sfc_main$v, [["render", _sfc_render$p]]);
4981
+ const _sfc_main$u = defineComponent({
4982
4982
  name: "SkMouseTrapToolTip",
4983
4983
  props: {
4984
4984
  hotkey: {
@@ -5127,7 +5127,7 @@ const _sfc_main$r = defineComponent({
5127
5127
  };
5128
5128
  }
5129
5129
  });
5130
- function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
5130
+ function _sfc_render$o(_ctx, _cache, $props, $setup, $data, $options) {
5131
5131
  return openBlock(), createElementBlock("div", {
5132
5132
  class: normalizeClass(["sk-mousetrap-tooltip-wrapper", [
5133
5133
  _ctx.isControlKeyPressed && !_ctx.disabled && _ctx.highlightEffect !== "none" ? `sk-mousetrap-highlight-${_ctx.highlightEffect}` : ""
@@ -5151,7 +5151,7 @@ function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
5151
5151
  })
5152
5152
  ], 2);
5153
5153
  }
5154
- const SkMouseTrapToolTip = /* @__PURE__ */ _export_sfc(_sfc_main$r, [["render", _sfc_render$l], ["__scopeId", "data-v-5d6fb09c"]]);
5154
+ const SkMouseTrapToolTip = /* @__PURE__ */ _export_sfc(_sfc_main$u, [["render", _sfc_render$o], ["__scopeId", "data-v-5d6fb09c"]]);
5155
5155
  const SnackbarServiceModule = /* @__PURE__ */ (() => {
5156
5156
  let _instance = null;
5157
5157
  return {
@@ -5175,7 +5175,7 @@ const SnackbarServiceModule = /* @__PURE__ */ (() => {
5175
5175
  };
5176
5176
  })();
5177
5177
  const { setInstance, alertUser } = SnackbarServiceModule;
5178
- const _sfc_main$q = defineComponent({
5178
+ const _sfc_main$t = defineComponent({
5179
5179
  name: "SnackbarService",
5180
5180
  data() {
5181
5181
  return {
@@ -5213,8 +5213,8 @@ const _sfc_main$q = defineComponent({
5213
5213
  }
5214
5214
  }
5215
5215
  });
5216
- const _hoisted_1$g = { class: "d-flex align-center justify-space-between w-100" };
5217
- function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
5216
+ const _hoisted_1$k = { class: "d-flex align-center justify-space-between w-100" };
5217
+ function _sfc_render$n(_ctx, _cache, $props, $setup, $data, $options) {
5218
5218
  const _component_v_icon = resolveComponent("v-icon");
5219
5219
  const _component_v_btn = resolveComponent("v-btn");
5220
5220
  const _component_v_snackbar = resolveComponent("v-snackbar");
@@ -5229,7 +5229,7 @@ function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
5229
5229
  color: _ctx.getColor(snack)
5230
5230
  }, {
5231
5231
  default: withCtx(() => [
5232
- createElementVNode("div", _hoisted_1$g, [
5232
+ createElementVNode("div", _hoisted_1$k, [
5233
5233
  createElementVNode("span", null, toDisplayString(snack.text), 1),
5234
5234
  createVNode(_component_v_btn, {
5235
5235
  icon: "",
@@ -5253,8 +5253,8 @@ function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
5253
5253
  }), 128))
5254
5254
  ]);
5255
5255
  }
5256
- const SnackbarService = /* @__PURE__ */ _export_sfc(_sfc_main$q, [["render", _sfc_render$k]]);
5257
- const _sfc_main$p = defineComponent({
5256
+ const SnackbarService = /* @__PURE__ */ _export_sfc(_sfc_main$t, [["render", _sfc_render$n]]);
5257
+ const _sfc_main$s = defineComponent({
5258
5258
  name: "PaginatingToolbar",
5259
5259
  props: {
5260
5260
  pages: {
@@ -5278,12 +5278,12 @@ const _sfc_main$p = defineComponent({
5278
5278
  },
5279
5279
  emits: ["first", "prev", "next", "last", "set-page"]
5280
5280
  });
5281
- const _hoisted_1$f = {
5281
+ const _hoisted_1$j = {
5282
5282
  key: 0,
5283
5283
  class: "ms-2 text-subtitle-2",
5284
5284
  "data-cy": "paginating-toolbar-subtitle"
5285
5285
  };
5286
- function _sfc_render$j(_ctx, _cache, $props, $setup, $data, $options) {
5286
+ function _sfc_render$m(_ctx, _cache, $props, $setup, $data, $options) {
5287
5287
  const _component_v_toolbar_title = resolveComponent("v-toolbar-title");
5288
5288
  const _component_v_spacer = resolveComponent("v-spacer");
5289
5289
  const _component_v_icon = resolveComponent("v-icon");
@@ -5295,7 +5295,7 @@ function _sfc_render$j(_ctx, _cache, $props, $setup, $data, $options) {
5295
5295
  createVNode(_component_v_toolbar_title, null, {
5296
5296
  default: withCtx(() => [
5297
5297
  createElementVNode("span", null, toDisplayString(_ctx.title), 1),
5298
- _ctx.subtitle ? (openBlock(), createElementBlock("span", _hoisted_1$f, toDisplayString(_ctx.subtitle), 1)) : createCommentVNode("", true)
5298
+ _ctx.subtitle ? (openBlock(), createElementBlock("span", _hoisted_1$j, toDisplayString(_ctx.subtitle), 1)) : createCommentVNode("", true)
5299
5299
  ]),
5300
5300
  _: 1
5301
5301
  }),
@@ -5387,8 +5387,8 @@ function _sfc_render$j(_ctx, _cache, $props, $setup, $data, $options) {
5387
5387
  _: 1
5388
5388
  });
5389
5389
  }
5390
- const PaginatingToolbar = /* @__PURE__ */ _export_sfc(_sfc_main$p, [["render", _sfc_render$j], ["__scopeId", "data-v-a75fea7e"]]);
5391
- const _sfc_main$o = defineComponent({
5390
+ const PaginatingToolbar = /* @__PURE__ */ _export_sfc(_sfc_main$s, [["render", _sfc_render$m], ["__scopeId", "data-v-a75fea7e"]]);
5391
+ const _sfc_main$r = defineComponent({
5392
5392
  name: "CardSearch",
5393
5393
  emits: {
5394
5394
  search: (query) => typeof query === "string"
@@ -5404,10 +5404,10 @@ const _sfc_main$o = defineComponent({
5404
5404
  }
5405
5405
  }
5406
5406
  });
5407
- const _hoisted_1$e = { class: "card-search" };
5408
- function _sfc_render$i(_ctx, _cache, $props, $setup, $data, $options) {
5407
+ const _hoisted_1$i = { class: "card-search" };
5408
+ function _sfc_render$l(_ctx, _cache, $props, $setup, $data, $options) {
5409
5409
  const _component_v_text_field = resolveComponent("v-text-field");
5410
- return openBlock(), createElementBlock("div", _hoisted_1$e, [
5410
+ return openBlock(), createElementBlock("div", _hoisted_1$i, [
5411
5411
  createVNode(_component_v_text_field, {
5412
5412
  modelValue: _ctx.query,
5413
5413
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.query = $event),
@@ -5418,8 +5418,8 @@ function _sfc_render$i(_ctx, _cache, $props, $setup, $data, $options) {
5418
5418
  }, null, 8, ["modelValue", "onClick:append", "onKeydown"])
5419
5419
  ]);
5420
5420
  }
5421
- const CardSearch = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["render", _sfc_render$i]]);
5422
- const _sfc_main$n = defineComponent({
5421
+ const CardSearch = /* @__PURE__ */ _export_sfc(_sfc_main$r, [["render", _sfc_render$l]]);
5422
+ const _sfc_main$q = defineComponent({
5423
5423
  name: "CardSearchResults",
5424
5424
  emits: {
5425
5425
  "card-selected": (payload) => typeof payload.cardId === "string" && typeof payload.courseId === "string"
@@ -5497,17 +5497,17 @@ const _sfc_main$n = defineComponent({
5497
5497
  }
5498
5498
  }
5499
5499
  });
5500
- const _hoisted_1$d = { class: "card-search-results" };
5501
- const _hoisted_2$9 = { key: 0 };
5502
- const _hoisted_3$7 = { key: 1 };
5503
- const _hoisted_4$5 = { key: 2 };
5504
- function _sfc_render$h(_ctx, _cache, $props, $setup, $data, $options) {
5500
+ const _hoisted_1$h = { class: "card-search-results" };
5501
+ const _hoisted_2$b = { key: 0 };
5502
+ const _hoisted_3$8 = { key: 1 };
5503
+ const _hoisted_4$6 = { key: 2 };
5504
+ function _sfc_render$k(_ctx, _cache, $props, $setup, $data, $options) {
5505
5505
  const _component_v_list_item_title = resolveComponent("v-list-item-title");
5506
5506
  const _component_v_list_item_subtitle = resolveComponent("v-list-item-subtitle");
5507
5507
  const _component_v_list_item = resolveComponent("v-list-item");
5508
5508
  const _component_v_list = resolveComponent("v-list");
5509
- return openBlock(), createElementBlock("div", _hoisted_1$d, [
5510
- _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_2$9, "Loading...")) : _ctx.error ? (openBlock(), createElementBlock("div", _hoisted_3$7, toDisplayString(_ctx.error), 1)) : (openBlock(), createElementBlock("div", _hoisted_4$5, [
5509
+ return openBlock(), createElementBlock("div", _hoisted_1$h, [
5510
+ _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_2$b, "Loading...")) : _ctx.error ? (openBlock(), createElementBlock("div", _hoisted_3$8, toDisplayString(_ctx.error), 1)) : (openBlock(), createElementBlock("div", _hoisted_4$6, [
5511
5511
  createVNode(_component_v_list, null, {
5512
5512
  default: withCtx(() => [
5513
5513
  (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.cards, (card) => {
@@ -5539,8 +5539,8 @@ function _sfc_render$h(_ctx, _cache, $props, $setup, $data, $options) {
5539
5539
  ]))
5540
5540
  ]);
5541
5541
  }
5542
- const CardSearchResults = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["render", _sfc_render$h], ["__scopeId", "data-v-285e13bd"]]);
5543
- const _sfc_main$m = defineComponent({
5542
+ const CardSearchResults = /* @__PURE__ */ _export_sfc(_sfc_main$q, [["render", _sfc_render$k], ["__scopeId", "data-v-285e13bd"]]);
5543
+ const _sfc_main$p = defineComponent({
5544
5544
  name: "CardHistoryViewer",
5545
5545
  props: {
5546
5546
  cardId: {
@@ -5635,15 +5635,15 @@ const _sfc_main$m = defineComponent({
5635
5635
  }
5636
5636
  }
5637
5637
  });
5638
- const _hoisted_1$c = { class: "card-history-viewer" };
5639
- const _hoisted_2$8 = { key: 0 };
5640
- const _hoisted_3$6 = { key: 1 };
5641
- const _hoisted_4$4 = {
5638
+ const _hoisted_1$g = { class: "card-history-viewer" };
5639
+ const _hoisted_2$a = { key: 0 };
5640
+ const _hoisted_3$7 = { key: 1 };
5641
+ const _hoisted_4$5 = {
5642
5642
  key: 2,
5643
5643
  class: "error-message"
5644
5644
  };
5645
- const _hoisted_5$4 = { key: 3 };
5646
- function _sfc_render$g(_ctx, _cache, $props, $setup, $data, $options) {
5645
+ const _hoisted_5$5 = { key: 3 };
5646
+ function _sfc_render$j(_ctx, _cache, $props, $setup, $data, $options) {
5647
5647
  const _component_v_card_title = resolveComponent("v-card-title");
5648
5648
  const _component_v_list_item_title = resolveComponent("v-list-item-title");
5649
5649
  const _component_v_list_item_subtitle = resolveComponent("v-list-item-subtitle");
@@ -5651,9 +5651,9 @@ function _sfc_render$g(_ctx, _cache, $props, $setup, $data, $options) {
5651
5651
  const _component_v_list_item = resolveComponent("v-list-item");
5652
5652
  const _component_v_card = resolveComponent("v-card");
5653
5653
  const _component_v_data_table = resolveComponent("v-data-table");
5654
- return openBlock(), createElementBlock("div", _hoisted_1$c, [
5655
- _ctx.userId ? (openBlock(), createElementBlock("h3", _hoisted_2$8, "Card History for User: " + toDisplayString(_ctx.userId), 1)) : createCommentVNode("", true),
5656
- _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_3$6, "Loading...")) : _ctx.error ? (openBlock(), createElementBlock("div", _hoisted_4$4, toDisplayString(_ctx.error), 1)) : _ctx.cardHistory ? (openBlock(), createElementBlock("div", _hoisted_5$4, [
5654
+ return openBlock(), createElementBlock("div", _hoisted_1$g, [
5655
+ _ctx.userId ? (openBlock(), createElementBlock("h3", _hoisted_2$a, "Card History for User: " + toDisplayString(_ctx.userId), 1)) : createCommentVNode("", true),
5656
+ _ctx.loading ? (openBlock(), createElementBlock("div", _hoisted_3$7, "Loading...")) : _ctx.error ? (openBlock(), createElementBlock("div", _hoisted_4$5, toDisplayString(_ctx.error), 1)) : _ctx.cardHistory ? (openBlock(), createElementBlock("div", _hoisted_5$5, [
5657
5657
  createVNode(_component_v_card, { class: "mb-4" }, {
5658
5658
  default: withCtx(() => [
5659
5659
  createVNode(_component_v_card_title, null, {
@@ -5739,7 +5739,7 @@ function _sfc_render$g(_ctx, _cache, $props, $setup, $data, $options) {
5739
5739
  ])) : createCommentVNode("", true)
5740
5740
  ]);
5741
5741
  }
5742
- const CardHistoryViewer = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["render", _sfc_render$g], ["__scopeId", "data-v-a4bdfdfb"]]);
5742
+ const CardHistoryViewer = /* @__PURE__ */ _export_sfc(_sfc_main$p, [["render", _sfc_render$j], ["__scopeId", "data-v-a4bdfdfb"]]);
5743
5743
  function useViewable(props, emit, componentName) {
5744
5744
  const startTime = ref(hooks.utc());
5745
5745
  const hotKeys = ref([]);
@@ -5908,7 +5908,212 @@ class Question extends Displayable {
5908
5908
  with 7 * 4 = 21
5909
5909
  */
5910
5910
  }
5911
- const _sfc_main$l = defineComponent({
5911
+ const getApiBase = () => {
5912
+ let source = "fallback (empty string)";
5913
+ let result = "";
5914
+ if (!result && typeof window !== "undefined" && window.__SKUILDER_CONFIG__?.apiBase) {
5915
+ const base = window.__SKUILDER_CONFIG__.apiBase;
5916
+ const cleaned = base.replace(/\/$/, "");
5917
+ result = cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
5918
+ source = "window.__SKUILDER_CONFIG__.apiBase";
5919
+ }
5920
+ console.log("[authAPI] getApiBase() called");
5921
+ console.log("[authAPI] source:", source);
5922
+ console.log("[authAPI] result:", result);
5923
+ return result;
5924
+ };
5925
+ async function sendVerificationEmail(username, email, origin) {
5926
+ try {
5927
+ const body = { username, email };
5928
+ if (origin) {
5929
+ body.origin = origin;
5930
+ }
5931
+ const response = await fetch(`${getApiBase()}/auth/send-verification`, {
5932
+ method: "POST",
5933
+ headers: { "Content-Type": "application/json" },
5934
+ credentials: "include",
5935
+ body: JSON.stringify(body)
5936
+ });
5937
+ if (!response.ok) {
5938
+ const errorText = await response.text();
5939
+ console.error("Send verification email error:", errorText);
5940
+ return {
5941
+ ok: false,
5942
+ error: `HTTP ${response.status}: ${response.statusText}`
5943
+ };
5944
+ }
5945
+ return await response.json();
5946
+ } catch (error) {
5947
+ console.error("Send verification email fetch error:", error);
5948
+ return {
5949
+ ok: false,
5950
+ error: error instanceof Error ? error.message : "Network error"
5951
+ };
5952
+ }
5953
+ }
5954
+ async function verifyEmail(token2) {
5955
+ try {
5956
+ const response = await fetch(`${getApiBase()}/auth/verify`, {
5957
+ method: "POST",
5958
+ headers: { "Content-Type": "application/json" },
5959
+ credentials: "include",
5960
+ body: JSON.stringify({ token: token2 })
5961
+ });
5962
+ if (!response.ok) {
5963
+ return {
5964
+ ok: false,
5965
+ error: `HTTP ${response.status}: ${response.statusText}`
5966
+ };
5967
+ }
5968
+ return await response.json();
5969
+ } catch (error) {
5970
+ return {
5971
+ ok: false,
5972
+ error: error instanceof Error ? error.message : "Network error"
5973
+ };
5974
+ }
5975
+ }
5976
+ async function getUserStatus() {
5977
+ try {
5978
+ const response = await fetch(`${getApiBase()}/auth/status`, {
5979
+ method: "GET",
5980
+ headers: { "Content-Type": "application/json" },
5981
+ credentials: "include"
5982
+ });
5983
+ if (!response.ok) {
5984
+ return {
5985
+ ok: false,
5986
+ error: `HTTP ${response.status}: ${response.statusText}`
5987
+ };
5988
+ }
5989
+ return await response.json();
5990
+ } catch (error) {
5991
+ return {
5992
+ ok: false,
5993
+ error: error instanceof Error ? error.message : "Network error"
5994
+ };
5995
+ }
5996
+ }
5997
+ async function requestPasswordReset(email, origin) {
5998
+ try {
5999
+ const body = { email };
6000
+ if (origin) {
6001
+ body.origin = origin;
6002
+ }
6003
+ const response = await fetch(`${getApiBase()}/auth/request-reset`, {
6004
+ method: "POST",
6005
+ headers: { "Content-Type": "application/json" },
6006
+ credentials: "include",
6007
+ body: JSON.stringify(body)
6008
+ });
6009
+ if (!response.ok) {
6010
+ return {
6011
+ ok: false,
6012
+ error: `HTTP ${response.status}: ${response.statusText}`
6013
+ };
6014
+ }
6015
+ return await response.json();
6016
+ } catch (error) {
6017
+ return {
6018
+ ok: false,
6019
+ error: error instanceof Error ? error.message : "Network error"
6020
+ };
6021
+ }
6022
+ }
6023
+ async function resetPassword(token2, newPassword) {
6024
+ try {
6025
+ const response = await fetch(`${getApiBase()}/auth/reset-password`, {
6026
+ method: "POST",
6027
+ headers: { "Content-Type": "application/json" },
6028
+ credentials: "include",
6029
+ body: JSON.stringify({ token: token2, newPassword })
6030
+ });
6031
+ if (!response.ok) {
6032
+ return {
6033
+ ok: false,
6034
+ error: `HTTP ${response.status}: ${response.statusText}`
6035
+ };
6036
+ }
6037
+ return await response.json();
6038
+ } catch (error) {
6039
+ return {
6040
+ ok: false,
6041
+ error: error instanceof Error ? error.message : "Network error"
6042
+ };
6043
+ }
6044
+ }
6045
+ function useEntitlements(courseId) {
6046
+ const entitlements = ref({});
6047
+ const loading = ref(false);
6048
+ const error = ref(null);
6049
+ const trialStatus = computed(() => {
6050
+ const entitlement = entitlements.value[courseId];
6051
+ if (!entitlement) {
6052
+ return {
6053
+ isActive: false,
6054
+ isPaid: false,
6055
+ daysRemaining: null,
6056
+ expiresDate: null
6057
+ };
6058
+ }
6059
+ if (entitlement.status === "paid") {
6060
+ return {
6061
+ isActive: true,
6062
+ isPaid: true,
6063
+ daysRemaining: null,
6064
+ expiresDate: null
6065
+ };
6066
+ }
6067
+ const expiresDate = entitlement.expires;
6068
+ if (!expiresDate) {
6069
+ return {
6070
+ isActive: true,
6071
+ isPaid: false,
6072
+ daysRemaining: null,
6073
+ expiresDate: null
6074
+ };
6075
+ }
6076
+ const expires = new Date(expiresDate);
6077
+ const now2 = /* @__PURE__ */ new Date();
6078
+ const daysLeft = Math.ceil((expires.getTime() - now2.getTime()) / (1e3 * 60 * 60 * 24));
6079
+ return {
6080
+ isActive: daysLeft > 0,
6081
+ isPaid: false,
6082
+ daysRemaining: Math.max(0, daysLeft),
6083
+ expiresDate
6084
+ };
6085
+ });
6086
+ const hasPremiumAccess = computed(() => {
6087
+ return trialStatus.value.isPaid;
6088
+ });
6089
+ async function fetchEntitlements() {
6090
+ loading.value = true;
6091
+ error.value = null;
6092
+ try {
6093
+ const result = await getUserStatus();
6094
+ if (result.ok) {
6095
+ entitlements.value = result.entitlements || {};
6096
+ } else {
6097
+ error.value = result.error || "Failed to fetch entitlements";
6098
+ console.error("[useEntitlements] Error:", error.value);
6099
+ }
6100
+ } catch (e) {
6101
+ error.value = e instanceof Error ? e.message : "Unknown error";
6102
+ console.error("[useEntitlements] Exception:", e);
6103
+ } finally {
6104
+ loading.value = false;
6105
+ }
6106
+ }
6107
+ return {
6108
+ entitlements,
6109
+ trialStatus,
6110
+ hasPremiumAccess,
6111
+ loading,
6112
+ error,
6113
+ fetchEntitlements
6114
+ };
6115
+ }
6116
+ const _sfc_main$o = defineComponent({
5912
6117
  name: "StudySessionTimer",
5913
6118
  props: {
5914
6119
  /**
@@ -5962,7 +6167,7 @@ const _sfc_main$l = defineComponent({
5962
6167
  };
5963
6168
  }
5964
6169
  });
5965
- function _sfc_render$f(_ctx, _cache, $props, $setup, $data, $options) {
6170
+ function _sfc_render$i(_ctx, _cache, $props, $setup, $data, $options) {
5966
6171
  const _component_v_icon = resolveComponent("v-icon");
5967
6172
  const _component_v_btn = resolveComponent("v-btn");
5968
6173
  const _component_v_progress_circular = resolveComponent("v-progress-circular");
@@ -6016,8 +6221,8 @@ function _sfc_render$f(_ctx, _cache, $props, $setup, $data, $options) {
6016
6221
  _: 1
6017
6222
  });
6018
6223
  }
6019
- const StudySessionTimer = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["render", _sfc_render$f], ["__scopeId", "data-v-5960940a"]]);
6020
- const _sfc_main$k = defineComponent({
6224
+ const StudySessionTimer = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["render", _sfc_render$i], ["__scopeId", "data-v-5960940a"]]);
6225
+ const _sfc_main$n = defineComponent({
6021
6226
  name: "CardViewer",
6022
6227
  ref: {},
6023
6228
  props: {
@@ -6071,31 +6276,23 @@ const _sfc_main$k = defineComponent({
6071
6276
  }
6072
6277
  }
6073
6278
  });
6074
- function _sfc_render$e(_ctx, _cache, $props, $setup, $data, $options) {
6279
+ function _sfc_render$h(_ctx, _cache, $props, $setup, $data, $options) {
6075
6280
  const _component_v_card = resolveComponent("v-card");
6076
6281
  return openBlock(), createBlock(_component_v_card, { elevation: "12" }, {
6077
6282
  default: withCtx(() => [
6078
- createVNode(Transition, {
6079
- name: "component-fade",
6080
- mode: "out-in"
6081
- }, {
6082
- default: withCtx(() => [
6083
- (openBlock(), createBlock(resolveDynamicComponent(_ctx.view), {
6084
- ref: "activeView",
6085
- key: _ctx.course_id + "-" + _ctx.card_id + "-" + _ctx.sessionOrder,
6086
- data: _ctx.data,
6087
- "modify-difficulty": _ctx.user_elo.global.score - _ctx.card_elo,
6088
- class: "cardView ma-2 pa-2",
6089
- onEmitResponse: _cache[0] || (_cache[0] = ($event) => _ctx.processResponse($event))
6090
- }, null, 40, ["data", "modify-difficulty"]))
6091
- ]),
6092
- _: 1
6093
- })
6283
+ (openBlock(), createBlock(resolveDynamicComponent(_ctx.view), {
6284
+ ref: "activeView",
6285
+ key: _ctx.course_id + "-" + _ctx.card_id + "-" + _ctx.sessionOrder,
6286
+ data: _ctx.data,
6287
+ "modify-difficulty": _ctx.user_elo.global.score - _ctx.card_elo,
6288
+ class: "cardView ma-2 pa-2",
6289
+ onEmitResponse: _cache[0] || (_cache[0] = ($event) => _ctx.processResponse($event))
6290
+ }, null, 40, ["data", "modify-difficulty"]))
6094
6291
  ]),
6095
6292
  _: 1
6096
6293
  });
6097
6294
  }
6098
- const CardViewer = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["render", _sfc_render$e], ["__scopeId", "data-v-a180fe1c"]]);
6295
+ const CardViewer = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["render", _sfc_render$h], ["__scopeId", "data-v-318fb94f"]]);
6099
6296
  var module$1 = {};
6100
6297
  (function main(global, module2, isWorker, workerSize) {
6101
6298
  var canUseWorker = !!(global.Worker && global.Blob && global.Promise && global.OffscreenCanvas && global.OffscreenCanvasRenderingContext2D && global.HTMLCanvasElement && global.HTMLCanvasElement.prototype.transferControlToOffscreen && global.URL && global.URL.createObjectURL);
@@ -6792,7 +6989,7 @@ var module$1 = {};
6792
6989
  }(), module$1, false);
6793
6990
  const confetti = module$1.exports;
6794
6991
  module$1.exports.create;
6795
- const _sfc_main$j = defineComponent({
6992
+ const _sfc_main$m = defineComponent({
6796
6993
  name: "StudySession",
6797
6994
  ref: {},
6798
6995
  components: {
@@ -7028,7 +7225,13 @@ const _sfc_main$j = defineComponent({
7028
7225
  maxSessionViews,
7029
7226
  sessionViews
7030
7227
  );
7031
- this.handleUIFeedback(result);
7228
+ try {
7229
+ this.handleUIFeedback(result);
7230
+ } catch (error) {
7231
+ console.error(`[StudySession] Error handling UI feedback: ${error}.
7232
+
7233
+ Result: ${JSON.stringify(result)}`);
7234
+ }
7032
7235
  if (result.shouldLoadNextCard) {
7033
7236
  this.loadCard(await this.sessionController.nextCard(result.nextCardAction));
7034
7237
  }
@@ -7131,21 +7334,21 @@ const _sfc_main$j = defineComponent({
7131
7334
  }
7132
7335
  }
7133
7336
  });
7134
- const _hoisted_1$b = {
7337
+ const _hoisted_1$f = {
7135
7338
  key: 0,
7136
7339
  class: "StudySession"
7137
7340
  };
7138
- const _hoisted_2$7 = {
7341
+ const _hoisted_2$9 = {
7139
7342
  key: 0,
7140
7343
  class: "text-h4"
7141
7344
  };
7142
- const _hoisted_3$5 = { key: 0 };
7143
- const _hoisted_4$3 = {
7345
+ const _hoisted_3$6 = { key: 0 };
7346
+ const _hoisted_4$4 = {
7144
7347
  key: 1,
7145
7348
  ref: "shadowWrapper"
7146
7349
  };
7147
- const _hoisted_5$3 = { key: 2 };
7148
- function _sfc_render$d(_ctx, _cache, $props, $setup, $data, $options) {
7350
+ const _hoisted_5$4 = { key: 2 };
7351
+ function _sfc_render$g(_ctx, _cache, $props, $setup, $data, $options) {
7149
7352
  const _component_v_spacer = resolveComponent("v-spacer");
7150
7353
  const _component_v_progress_circular = resolveComponent("v-progress-circular");
7151
7354
  const _component_v_row = resolveComponent("v-row");
@@ -7154,7 +7357,7 @@ function _sfc_render$d(_ctx, _cache, $props, $setup, $data, $options) {
7154
7357
  const _component_StudySessionTimer = resolveComponent("StudySessionTimer");
7155
7358
  const _component_v_col = resolveComponent("v-col");
7156
7359
  const _component_SkMouseTrap = resolveComponent("SkMouseTrap");
7157
- return _ctx.sessionPrepared ? (openBlock(), createElementBlock("div", _hoisted_1$b, [
7360
+ return _ctx.sessionPrepared ? (openBlock(), createElementBlock("div", _hoisted_1$f, [
7158
7361
  createVNode(_component_v_row, { align: "center" }, {
7159
7362
  default: withCtx(() => [
7160
7363
  createVNode(_component_v_spacer),
@@ -7169,29 +7372,37 @@ function _sfc_render$d(_ctx, _cache, $props, $setup, $data, $options) {
7169
7372
  _: 1
7170
7373
  }),
7171
7374
  _cache[2] || (_cache[2] = createElementVNode("br", null, null, -1)),
7172
- _ctx.sessionFinished ? (openBlock(), createElementBlock("div", _hoisted_2$7, [
7375
+ _ctx.sessionFinished ? (openBlock(), createElementBlock("div", _hoisted_2$9, [
7173
7376
  _cache[1] || (_cache[1] = createElementVNode("p", null, "Study session finished! Great job!", -1)),
7174
- _ctx.sessionController ? (openBlock(), createElementBlock("p", _hoisted_3$5, toDisplayString(_ctx.sessionController.report), 1)) : createCommentVNode("", true),
7377
+ _ctx.sessionController ? (openBlock(), createElementBlock("p", _hoisted_3$6, toDisplayString(_ctx.sessionController.report), 1)) : createCommentVNode("", true),
7175
7378
  createVNode(_component_heat_map, {
7176
7379
  "activity-records-getter": () => _ctx.user.getActivityRecords()
7177
7380
  }, null, 8, ["activity-records-getter"])
7178
- ])) : (openBlock(), createElementBlock("div", _hoisted_4$3, [
7179
- (openBlock(), createBlock(_component_card_viewer, {
7180
- ref: "cardViewer",
7181
- key: _ctx.cardID,
7182
- class: normalizeClass(_ctx.loading ? "muted" : ""),
7183
- view: _ctx.view,
7184
- data: _ctx.data,
7185
- card_id: _ctx.cardID,
7186
- course_id: _ctx.courseID,
7187
- "session-order": _ctx.cardCount,
7188
- user_elo: _ctx.user_elo(_ctx.courseID),
7189
- card_elo: _ctx.card_elo,
7190
- onEmitResponse: _cache[0] || (_cache[0] = ($event) => _ctx.processResponse($event))
7191
- }, null, 8, ["class", "view", "data", "card_id", "course_id", "session-order", "user_elo", "card_elo"]))
7381
+ ])) : (openBlock(), createElementBlock("div", _hoisted_4$4, [
7382
+ createVNode(Transition, {
7383
+ name: "component-fade",
7384
+ mode: "out-in"
7385
+ }, {
7386
+ default: withCtx(() => [
7387
+ (openBlock(), createBlock(_component_card_viewer, {
7388
+ ref: "cardViewer",
7389
+ key: _ctx.cardID,
7390
+ class: normalizeClass(_ctx.loading ? "muted" : ""),
7391
+ view: _ctx.view,
7392
+ data: _ctx.data,
7393
+ card_id: _ctx.cardID,
7394
+ course_id: _ctx.courseID,
7395
+ "session-order": _ctx.cardCount,
7396
+ user_elo: _ctx.user_elo(_ctx.courseID),
7397
+ card_elo: _ctx.card_elo,
7398
+ onEmitResponse: _cache[0] || (_cache[0] = ($event) => _ctx.processResponse($event))
7399
+ }, null, 8, ["class", "view", "data", "card_id", "course_id", "session-order", "user_elo", "card_elo"]))
7400
+ ]),
7401
+ _: 1
7402
+ })
7192
7403
  ], 512)),
7193
7404
  _cache[3] || (_cache[3] = createElementVNode("br", null, null, -1)),
7194
- _ctx.sessionController ? (openBlock(), createElementBlock("div", _hoisted_5$3, [
7405
+ _ctx.sessionController ? (openBlock(), createElementBlock("div", _hoisted_5$4, [
7195
7406
  (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.sessionController.failedCount, (i) => {
7196
7407
  return openBlock(), createElementBlock("span", {
7197
7408
  key: i,
@@ -7232,7 +7443,7 @@ function _sfc_render$d(_ctx, _cache, $props, $setup, $data, $options) {
7232
7443
  })
7233
7444
  ])) : createCommentVNode("", true);
7234
7445
  }
7235
- const StudySession = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["render", _sfc_render$d], ["__scopeId", "data-v-49a65e47"]]);
7446
+ const StudySession = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["render", _sfc_render$g], ["__scopeId", "data-v-f4aeb100"]]);
7236
7447
  let _pinia = null;
7237
7448
  const setPinia = (pinia) => {
7238
7449
  _pinia = pinia;
@@ -7327,7 +7538,7 @@ Exceeded maximum ancestor lookup depth.`;
7327
7538
  }
7328
7539
  }
7329
7540
  });
7330
- const _sfc_main$i = defineComponent({
7541
+ const _sfc_main$l = defineComponent({
7331
7542
  name: "MultipleChoiceOption",
7332
7543
  components: {
7333
7544
  MarkdownRenderer: defineAsyncComponent(() => Promise.resolve().then(() => MarkdownRenderer$1))
@@ -7413,7 +7624,7 @@ const _sfc_main$i = defineComponent({
7413
7624
  }
7414
7625
  }
7415
7626
  });
7416
- function _sfc_render$c(_ctx, _cache, $props, $setup, $data, $options) {
7627
+ function _sfc_render$f(_ctx, _cache, $props, $setup, $data, $options) {
7417
7628
  const _component_markdown_renderer = resolveComponent("markdown-renderer");
7418
7629
  const _component_v_card = resolveComponent("v-card");
7419
7630
  return openBlock(), createBlock(_component_v_card, {
@@ -7427,8 +7638,8 @@ function _sfc_render$c(_ctx, _cache, $props, $setup, $data, $options) {
7427
7638
  _: 1
7428
7639
  }, 8, ["class", "onMouseover", "onClick"]);
7429
7640
  }
7430
- const MultipleChoiceOption = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["render", _sfc_render$c], ["__scopeId", "data-v-96de7172"]]);
7431
- const _sfc_main$h = defineComponent({
7641
+ const MultipleChoiceOption = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["render", _sfc_render$f], ["__scopeId", "data-v-96de7172"]]);
7642
+ const _sfc_main$k = defineComponent({
7432
7643
  name: "RadioMultipleChoice",
7433
7644
  components: {
7434
7645
  MultipleChoiceOption
@@ -7567,13 +7778,13 @@ const _sfc_main$h = defineComponent({
7567
7778
  // },
7568
7779
  }
7569
7780
  });
7570
- const _hoisted_1$a = {
7781
+ const _hoisted_1$e = {
7571
7782
  ref: "containerRef",
7572
7783
  class: "multipleChoice"
7573
7784
  };
7574
- function _sfc_render$b(_ctx, _cache, $props, $setup, $data, $options) {
7785
+ function _sfc_render$e(_ctx, _cache, $props, $setup, $data, $options) {
7575
7786
  const _component_MultipleChoiceOption = resolveComponent("MultipleChoiceOption");
7576
- return openBlock(), createElementBlock("div", _hoisted_1$a, [
7787
+ return openBlock(), createElementBlock("div", _hoisted_1$e, [
7577
7788
  (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.choiceList, (choice, i) => {
7578
7789
  return openBlock(), createBlock(_component_MultipleChoiceOption, {
7579
7790
  key: i,
@@ -7587,8 +7798,8 @@ function _sfc_render$b(_ctx, _cache, $props, $setup, $data, $options) {
7587
7798
  }), 128))
7588
7799
  ], 512);
7589
7800
  }
7590
- const RadioMultipleChoice = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["render", _sfc_render$b]]);
7591
- const _sfc_main$g = defineComponent({
7801
+ const RadioMultipleChoice = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["render", _sfc_render$e]]);
7802
+ const _sfc_main$j = defineComponent({
7592
7803
  name: "TrueFalse",
7593
7804
  components: {
7594
7805
  RadioMultipleChoice
@@ -7604,10 +7815,10 @@ const _sfc_main$g = defineComponent({
7604
7815
  }
7605
7816
  }
7606
7817
  });
7607
- const _hoisted_1$9 = { "data-viewable": "TrueFalse" };
7608
- function _sfc_render$a(_ctx, _cache, $props, $setup, $data, $options) {
7818
+ const _hoisted_1$d = { "data-viewable": "TrueFalse" };
7819
+ function _sfc_render$d(_ctx, _cache, $props, $setup, $data, $options) {
7609
7820
  const _component_RadioMultipleChoice = resolveComponent("RadioMultipleChoice");
7610
- return openBlock(), createElementBlock("div", _hoisted_1$9, [
7821
+ return openBlock(), createElementBlock("div", _hoisted_1$d, [
7611
7822
  createVNode(_component_RadioMultipleChoice, {
7612
7823
  "choice-list": ["True", "False"],
7613
7824
  MouseTrap: _ctx.MouseTrap,
@@ -7615,8 +7826,8 @@ function _sfc_render$a(_ctx, _cache, $props, $setup, $data, $options) {
7615
7826
  }, null, 8, ["MouseTrap", "submit"])
7616
7827
  ]);
7617
7828
  }
7618
- const TrueFalse = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["render", _sfc_render$a]]);
7619
- const _sfc_main$f = defineComponent({
7829
+ const TrueFalse = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["render", _sfc_render$d]]);
7830
+ const _sfc_main$i = defineComponent({
7620
7831
  name: "UserInputNumber",
7621
7832
  ref: {},
7622
7833
  extends: UserInput,
@@ -7636,7 +7847,7 @@ const _sfc_main$f = defineComponent({
7636
7847
  }
7637
7848
  }
7638
7849
  });
7639
- function _sfc_render$9(_ctx, _cache, $props, $setup, $data, $options) {
7850
+ function _sfc_render$c(_ctx, _cache, $props, $setup, $data, $options) {
7640
7851
  const _component_v_text_field = resolveComponent("v-text-field");
7641
7852
  const _component_v_container = resolveComponent("v-container");
7642
7853
  return openBlock(), createBlock(_component_v_container, { class: "pa-0" }, {
@@ -7657,8 +7868,8 @@ function _sfc_render$9(_ctx, _cache, $props, $setup, $data, $options) {
7657
7868
  _: 1
7658
7869
  });
7659
7870
  }
7660
- const UserInputNumber = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["render", _sfc_render$9], ["__scopeId", "data-v-a56dcd1c"]]);
7661
- const _sfc_main$e = defineComponent({
7871
+ const UserInputNumber = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["render", _sfc_render$c], ["__scopeId", "data-v-a56dcd1c"]]);
7872
+ const _sfc_main$h = defineComponent({
7662
7873
  name: "UserInputString",
7663
7874
  extends: UserInput,
7664
7875
  props: {
@@ -7695,10 +7906,10 @@ const _sfc_main$e = defineComponent({
7695
7906
  // },
7696
7907
  }
7697
7908
  });
7698
- const _hoisted_1$8 = { class: "user-input-container" };
7699
- const _hoisted_2$6 = ["autofocus"];
7700
- function _sfc_render$8(_ctx, _cache, $props, $setup, $data, $options) {
7701
- return openBlock(), createElementBlock("span", _hoisted_1$8, [
7909
+ const _hoisted_1$c = { class: "user-input-container" };
7910
+ const _hoisted_2$8 = ["autofocus"];
7911
+ function _sfc_render$b(_ctx, _cache, $props, $setup, $data, $options) {
7912
+ return openBlock(), createElementBlock("span", _hoisted_1$c, [
7702
7913
  withDirectives(createElementVNode("input", {
7703
7914
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.answer = $event),
7704
7915
  autofocus: _ctx.autofocus,
@@ -7706,13 +7917,13 @@ function _sfc_render$8(_ctx, _cache, $props, $setup, $data, $options) {
7706
7917
  class: "user-input-string",
7707
7918
  ref: "input",
7708
7919
  onKeyup: _cache[1] || (_cache[1] = withKeys(($event) => _ctx.submitAnswer(_ctx.answer), ["enter"]))
7709
- }, null, 40, _hoisted_2$6), [
7920
+ }, null, 40, _hoisted_2$8), [
7710
7921
  [vModelText, _ctx.answer]
7711
7922
  ])
7712
7923
  ]);
7713
7924
  }
7714
- const UserInputString = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["render", _sfc_render$8], ["__scopeId", "data-v-aa14961f"]]);
7715
- const _sfc_main$d = defineComponent({
7925
+ const UserInputString = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["render", _sfc_render$b], ["__scopeId", "data-v-aa14961f"]]);
7926
+ const _sfc_main$g = defineComponent({
7716
7927
  name: "FillInInput",
7717
7928
  components: {
7718
7929
  UserInputString
@@ -7745,13 +7956,13 @@ const _sfc_main$d = defineComponent({
7745
7956
  };
7746
7957
  }
7747
7958
  });
7748
- const _hoisted_1$7 = {
7959
+ const _hoisted_1$b = {
7749
7960
  key: 0,
7750
7961
  class: "text-h5 underline"
7751
7962
  };
7752
- function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
7963
+ function _sfc_render$a(_ctx, _cache, $props, $setup, $data, $options) {
7753
7964
  const _component_user_input_string = resolveComponent("user-input-string");
7754
- return _ctx.radioType ? (openBlock(), createElementBlock("span", _hoisted_1$7, "             ")) : (openBlock(), createBlock(_component_user_input_string, {
7965
+ return _ctx.radioType ? (openBlock(), createElementBlock("span", _hoisted_1$b, "             ")) : (openBlock(), createBlock(_component_user_input_string, {
7755
7966
  key: 1,
7756
7967
  id: "input",
7757
7968
  icon: false,
@@ -7759,8 +7970,8 @@ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
7759
7970
  value: _ctx.processedText
7760
7971
  }, null, 8, ["value"]));
7761
7972
  }
7762
- const FillInInput = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["render", _sfc_render$7], ["__scopeId", "data-v-486ac035"]]);
7763
- const _sfc_main$c = defineComponent({
7973
+ const FillInInput = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["render", _sfc_render$a], ["__scopeId", "data-v-486ac035"]]);
7974
+ const _sfc_main$f = defineComponent({
7764
7975
  name: "CardLoader",
7765
7976
  components: {
7766
7977
  CardViewer
@@ -7842,7 +8053,7 @@ const _sfc_main$c = defineComponent({
7842
8053
  }
7843
8054
  }
7844
8055
  });
7845
- function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
8056
+ function _sfc_render$9(_ctx, _cache, $props, $setup, $data, $options) {
7846
8057
  const _component_card_viewer = resolveComponent("card-viewer");
7847
8058
  return !_ctx.loading ? (openBlock(), createBlock(_component_card_viewer, {
7848
8059
  key: 0,
@@ -7855,7 +8066,7 @@ function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
7855
8066
  onEmitResponse: _cache[0] || (_cache[0] = ($event) => _ctx.processResponse($event))
7856
8067
  }, null, 8, ["class", "view", "data", "card_id", "course_id", "session-order"])) : createCommentVNode("", true);
7857
8068
  }
7858
- const CardLoader = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["render", _sfc_render$6], ["__scopeId", "data-v-93f758b5"]]);
8069
+ const CardLoader = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["render", _sfc_render$9], ["__scopeId", "data-v-93f758b5"]]);
7859
8070
  function _getDefaults() {
7860
8071
  return {
7861
8072
  async: false,
@@ -10066,7 +10277,7 @@ function isComponent(token2) {
10066
10277
  return token2.type === "text" && token2.text.startsWith("{{") && token2.text.endsWith("}}");
10067
10278
  }
10068
10279
  const playbackGap = 500;
10069
- const _sfc_main$b = /* @__PURE__ */ defineComponent({
10280
+ const _sfc_main$e = /* @__PURE__ */ defineComponent({
10070
10281
  __name: "AudioAutoPlayer",
10071
10282
  props: {
10072
10283
  src: {}
@@ -10177,7 +10388,7 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
10177
10388
  };
10178
10389
  }
10179
10390
  });
10180
- const AudioAutoPlayer = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-e1a0f62c"]]);
10391
+ const AudioAutoPlayer = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__scopeId", "data-v-e1a0f62c"]]);
10181
10392
  var core;
10182
10393
  var hasRequiredCore;
10183
10394
  function requireCore() {
@@ -15410,12 +15621,12 @@ function python(hljs) {
15410
15621
  ]
15411
15622
  };
15412
15623
  }
15413
- const _hoisted_1$6 = { class: "code-block-wrapper pa-2" };
15414
- const _hoisted_2$5 = {
15624
+ const _hoisted_1$a = { class: "code-block-wrapper pa-2" };
15625
+ const _hoisted_2$7 = {
15415
15626
  key: 0,
15416
15627
  class: "language-indicator"
15417
15628
  };
15418
- const _sfc_main$a = /* @__PURE__ */ defineComponent({
15629
+ const _sfc_main$d = /* @__PURE__ */ defineComponent({
15419
15630
  __name: "CodeBlockRenderer",
15420
15631
  props: {
15421
15632
  code: {
@@ -15446,8 +15657,8 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
15446
15657
  }
15447
15658
  return (_ctx, _cache) => {
15448
15659
  const _component_highlightjs = resolveComponent("highlightjs");
15449
- return openBlock(), createElementBlock("div", _hoisted_1$6, [
15450
- __props.language ? (openBlock(), createElementBlock("div", _hoisted_2$5, toDisplayString(__props.language), 1)) : createCommentVNode("", true),
15660
+ return openBlock(), createElementBlock("div", _hoisted_1$a, [
15661
+ __props.language ? (openBlock(), createElementBlock("div", _hoisted_2$7, toDisplayString(__props.language), 1)) : createCommentVNode("", true),
15451
15662
  createVNode(_component_highlightjs, {
15452
15663
  language: __props.language,
15453
15664
  code: __props.code
@@ -15456,11 +15667,11 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
15456
15667
  };
15457
15668
  }
15458
15669
  });
15459
- const _hoisted_1$5 = { key: 0 };
15460
- const _hoisted_2$4 = { key: 0 };
15461
- const _hoisted_3$4 = { key: 0 };
15462
- const _hoisted_4$2 = { key: 1 };
15463
- const _hoisted_5$2 = { key: 2 };
15670
+ const _hoisted_1$9 = { key: 0 };
15671
+ const _hoisted_2$6 = { key: 0 };
15672
+ const _hoisted_3$5 = { key: 0 };
15673
+ const _hoisted_4$3 = { key: 1 };
15674
+ const _hoisted_5$3 = { key: 2 };
15464
15675
  const _hoisted_6$1 = { key: 1 };
15465
15676
  const _hoisted_7 = { key: 1 };
15466
15677
  const _hoisted_8 = {
@@ -15498,7 +15709,7 @@ const _hoisted_27 = ["innerHTML"];
15498
15709
  const _hoisted_28 = { key: 16 };
15499
15710
  const _hoisted_29 = { key: 17 };
15500
15711
  const _hoisted_30 = { key: 18 };
15501
- const _sfc_main$9 = /* @__PURE__ */ defineComponent({
15712
+ const _sfc_main$c = /* @__PURE__ */ defineComponent({
15502
15713
  __name: "MdTokenRenderer",
15503
15714
  props: {
15504
15715
  token: {
@@ -15565,21 +15776,21 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
15565
15776
  });
15566
15777
  return (_ctx, _cache) => {
15567
15778
  const _component_md_token_renderer = resolveComponent("md-token-renderer", true);
15568
- return isText(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_1$5, [
15569
- !__props.token.tokens || __props.token.tokens.length === 0 ? (openBlock(), createElementBlock("span", _hoisted_2$4, [
15570
- isComponent$1(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_3$4, [
15779
+ return isText(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_1$9, [
15780
+ !__props.token.tokens || __props.token.tokens.length === 0 ? (openBlock(), createElementBlock("span", _hoisted_2$6, [
15781
+ isComponent$1(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_3$5, [
15571
15782
  !__props.last ? (openBlock(), createBlock(resolveDynamicComponent(getComponent(parsedComponent(__props.token).is)), {
15572
15783
  key: 0,
15573
15784
  text: parsedComponent(__props.token).text
15574
15785
  }, null, 8, ["text"])) : createCommentVNode("", true)
15575
- ])) : containsComponent$1(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_4$2, [
15786
+ ])) : containsComponent$1(__props.token) ? (openBlock(), createElementBlock("span", _hoisted_4$3, [
15576
15787
  (openBlock(true), createElementBlock(Fragment, null, renderList(splitTextToken$1(__props.token), (subTok, j) => {
15577
15788
  return openBlock(), createBlock(_component_md_token_renderer, {
15578
15789
  key: j,
15579
15790
  token: subTok
15580
15791
  }, null, 8, ["token"]);
15581
15792
  }), 128))
15582
- ])) : (openBlock(), createElementBlock("span", _hoisted_5$2, toDisplayString(decodeBasicEntities(__props.token.text)), 1))
15793
+ ])) : (openBlock(), createElementBlock("span", _hoisted_5$3, toDisplayString(decodeBasicEntities(__props.token.text)), 1))
15583
15794
  ])) : __props.token.tokens && __props.token.tokens.length !== 0 ? (openBlock(), createElementBlock("span", _hoisted_6$1, [
15584
15795
  (openBlock(true), createElementBlock(Fragment, null, renderList(__props.token.tokens, (subTok, j) => {
15585
15796
  return openBlock(), createBlock(_component_md_token_renderer, {
@@ -15712,7 +15923,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
15712
15923
  ], 8, _hoisted_25)) : __props.token.type === "html" ? (openBlock(), createElementBlock("span", {
15713
15924
  key: 13,
15714
15925
  innerHTML: __props.token.raw
15715
- }, null, 8, _hoisted_26)) : __props.token.type === "code" ? (openBlock(), createBlock(_sfc_main$a, {
15926
+ }, null, 8, _hoisted_26)) : __props.token.type === "code" ? (openBlock(), createBlock(_sfc_main$d, {
15716
15927
  key: 14,
15717
15928
  code: __props.token.text,
15718
15929
  language: __props.token.lang
@@ -15738,8 +15949,8 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
15738
15949
  };
15739
15950
  }
15740
15951
  });
15741
- const MdTokenRenderer = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-047d0fa4"]]);
15742
- const _sfc_main$8 = defineComponent({
15952
+ const MdTokenRenderer = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-047d0fa4"]]);
15953
+ const _sfc_main$b = defineComponent({
15743
15954
  name: "MarkdownRenderer",
15744
15955
  components: {
15745
15956
  MdTokenRenderer,
@@ -15757,7 +15968,7 @@ const _sfc_main$8 = defineComponent({
15757
15968
  }
15758
15969
  }
15759
15970
  });
15760
- function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
15971
+ function _sfc_render$8(_ctx, _cache, $props, $setup, $data, $options) {
15761
15972
  const _component_md_token_renderer = resolveComponent("md-token-renderer");
15762
15973
  const _component_audio_auto_player = resolveComponent("audio-auto-player");
15763
15974
  return openBlock(), createElementBlock("div", null, [
@@ -15775,7 +15986,7 @@ function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
15775
15986
  }), 128))
15776
15987
  ]);
15777
15988
  }
15778
- const MarkdownRenderer = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["render", _sfc_render$5]]);
15989
+ const MarkdownRenderer = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["render", _sfc_render$8]]);
15779
15990
  const MarkdownRenderer$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
15780
15991
  __proto__: null,
15781
15992
  default: MarkdownRenderer
@@ -15815,11 +16026,14 @@ const useAuthStore = () => {
15815
16026
  async init() {
15816
16027
  try {
15817
16028
  this._user = getDataLayer().getUserDB();
15818
- this.loginAndRegistration.loggedIn = this._user.isLoggedIn();
16029
+ this.loginAndRegistration.loggedIn = this._user ? this._user.isLoggedIn() : false;
15819
16030
  this.onLoadComplete = true;
15820
16031
  this.loginAndRegistration.init = true;
15821
16032
  } catch (e) {
15822
16033
  console.error("Failed to initialize auth store:", e);
16034
+ this.loginAndRegistration.loggedIn = false;
16035
+ this.onLoadComplete = true;
16036
+ this.loginAndRegistration.init = true;
15823
16037
  }
15824
16038
  },
15825
16039
  setLoginDialog(open) {
@@ -15880,29 +16094,43 @@ const useConfigStore = () => {
15880
16094
  async updateDarkMode(darkMode) {
15881
16095
  this.config.darkMode = darkMode;
15882
16096
  const user = await getCurrentUser();
15883
- await user.setConfig({
15884
- darkMode
15885
- });
16097
+ if (user) {
16098
+ await user.setConfig({
16099
+ darkMode
16100
+ });
16101
+ }
15886
16102
  },
15887
16103
  async updateLikesConfetti(likesConfetti) {
15888
16104
  this.config.likesConfetti = likesConfetti;
15889
16105
  const user = await getCurrentUser();
15890
- await user.setConfig({
15891
- likesConfetti
15892
- });
16106
+ if (user) {
16107
+ await user.setConfig({
16108
+ likesConfetti
16109
+ });
16110
+ }
15893
16111
  },
15894
16112
  async updateSessionTimeLimit(sessionTimeLimit) {
15895
16113
  this.config.sessionTimeLimit = sessionTimeLimit;
15896
16114
  const user = await getCurrentUser();
15897
- await user.setConfig({
15898
- sessionTimeLimit
15899
- });
16115
+ if (user) {
16116
+ await user.setConfig({
16117
+ sessionTimeLimit
16118
+ });
16119
+ }
15900
16120
  },
15901
16121
  async hydrate() {
15902
- const user = await getCurrentUser();
15903
- const cfg = await user.getConfig();
15904
- console.log(`user config: ${JSON.stringify(cfg)}`);
15905
- this.updateConfig(cfg);
16122
+ try {
16123
+ const user = await getCurrentUser();
16124
+ if (user) {
16125
+ const cfg = await user.getConfig();
16126
+ console.log(`user config: ${JSON.stringify(cfg)}`);
16127
+ this.updateConfig(cfg);
16128
+ } else {
16129
+ console.log("No user logged in, using default config");
16130
+ }
16131
+ } catch (e) {
16132
+ console.warn("Failed to hydrate config store, using defaults:", e);
16133
+ }
15906
16134
  },
15907
16135
  async init() {
15908
16136
  await this.hydrate();
@@ -15964,7 +16192,7 @@ function useAuthUI() {
15964
16192
  detectSyncStrategy
15965
16193
  };
15966
16194
  }
15967
- const _sfc_main$7 = /* @__PURE__ */ defineComponent({
16195
+ const _sfc_main$a = /* @__PURE__ */ defineComponent({
15968
16196
  __name: "UserChip",
15969
16197
  props: {
15970
16198
  showLoginButton: { type: Boolean },
@@ -16277,13 +16505,15 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
16277
16505
  };
16278
16506
  }
16279
16507
  });
16280
- const UserChip = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-9a38a213"]]);
16281
- const _sfc_main$6 = /* @__PURE__ */ defineComponent({
16508
+ const UserChip = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-9a38a213"]]);
16509
+ const _hoisted_1$8 = { class: "d-flex flex-column align-start" };
16510
+ const _hoisted_2$5 = { class: "mb-2" };
16511
+ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
16282
16512
  __name: "UserLogin",
16283
16513
  props: {
16284
16514
  redirectTo: { default: "/study" }
16285
16515
  },
16286
- emits: ["toggle", "loginSuccess"],
16516
+ emits: ["toggle", "loginSuccess", "forgotPassword"],
16287
16517
  setup(__props, { emit: __emit }) {
16288
16518
  const props = __props;
16289
16519
  const emit = __emit;
@@ -16347,6 +16577,14 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
16347
16577
  log("Toggling registration / login forms.");
16348
16578
  emit("toggle");
16349
16579
  };
16580
+ const handleForgotPassword = () => {
16581
+ log("Forgot password clicked");
16582
+ if (loginRoute.value) {
16583
+ router.push("/request-reset");
16584
+ } else {
16585
+ emit("forgotPassword");
16586
+ }
16587
+ };
16350
16588
  return (_ctx, _cache) => {
16351
16589
  const _component_v_card_title = resolveComponent("v-card-title");
16352
16590
  const _component_v_text_field = resolveComponent("v-text-field");
@@ -16418,68 +16656,96 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
16418
16656
  ]),
16419
16657
  _: 1
16420
16658
  }, 8, ["modelValue", "timeout"]),
16421
- createVNode(_component_v_btn, {
16422
- class: "mr-2",
16423
- type: "submit",
16424
- loading: awaitingResponse.value,
16425
- color: buttonStatus.value.color
16426
- }, {
16427
- default: withCtx(() => [
16428
- createVNode(_component_v_icon, { start: "" }, {
16429
- default: withCtx(() => _cache[8] || (_cache[8] = [
16430
- createTextVNode("mdi-lock-open")
16431
- ])),
16659
+ createElementVNode("div", _hoisted_1$8, [
16660
+ createElementVNode("div", _hoisted_2$5, [
16661
+ createVNode(_component_v_btn, {
16662
+ class: "mr-2",
16663
+ type: "submit",
16664
+ loading: awaitingResponse.value,
16665
+ color: buttonStatus.value.color
16666
+ }, {
16667
+ default: withCtx(() => [
16668
+ createVNode(_component_v_icon, { start: "" }, {
16669
+ default: withCtx(() => _cache[8] || (_cache[8] = [
16670
+ createTextVNode("mdi-lock-open")
16671
+ ])),
16672
+ _: 1
16673
+ }),
16674
+ _cache[9] || (_cache[9] = createTextVNode(" Log In "))
16675
+ ]),
16432
16676
  _: 1
16433
- }),
16434
- _cache[9] || (_cache[9] = createTextVNode(" Log In "))
16435
- ]),
16436
- _: 1
16437
- }, 8, ["loading", "color"]),
16438
- loginRoute.value ? (openBlock(), createBlock(_component_router_link, {
16439
- key: 0,
16440
- to: "signup"
16441
- }, {
16442
- default: withCtx(() => [
16443
- createVNode(_component_v_btn, { variant: "text" }, {
16444
- default: withCtx(() => _cache[10] || (_cache[10] = [
16677
+ }, 8, ["loading", "color"]),
16678
+ loginRoute.value ? (openBlock(), createBlock(_component_router_link, {
16679
+ key: 0,
16680
+ to: "signup"
16681
+ }, {
16682
+ default: withCtx(() => [
16683
+ createVNode(_component_v_btn, { variant: "text" }, {
16684
+ default: withCtx(() => _cache[10] || (_cache[10] = [
16685
+ createTextVNode("Create New Account")
16686
+ ])),
16687
+ _: 1
16688
+ })
16689
+ ]),
16690
+ _: 1
16691
+ })) : (openBlock(), createBlock(_component_v_btn, {
16692
+ key: 1,
16693
+ variant: "text",
16694
+ onClick: toggle
16695
+ }, {
16696
+ default: withCtx(() => _cache[11] || (_cache[11] = [
16445
16697
  createTextVNode("Create New Account")
16446
16698
  ])),
16447
16699
  _: 1
16448
- })
16700
+ }))
16449
16701
  ]),
16450
- _: 1
16451
- })) : (openBlock(), createBlock(_component_v_btn, {
16452
- key: 1,
16453
- variant: "text",
16454
- onClick: toggle
16455
- }, {
16456
- default: withCtx(() => _cache[11] || (_cache[11] = [
16457
- createTextVNode("Create New Account")
16458
- ])),
16459
- _: 1
16460
- }))
16702
+ renderSlot(_ctx.$slots, "forgot-password", {}, () => [
16703
+ createElementVNode("a", {
16704
+ href: "#",
16705
+ class: "text-caption text-decoration-none",
16706
+ onClick: withModifiers(handleForgotPassword, ["prevent"])
16707
+ }, " Forgot password? ")
16708
+ ], true)
16709
+ ])
16461
16710
  ]),
16462
- _: 1
16711
+ _: 3
16463
16712
  })
16464
16713
  ]),
16465
- _: 1
16714
+ _: 3
16466
16715
  })
16467
16716
  ]),
16468
- _: 1
16717
+ _: 3
16469
16718
  });
16470
16719
  };
16471
16720
  }
16472
16721
  });
16473
- const UserLogin = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-5bbe9623"]]);
16474
- const _sfc_main$5 = defineComponent({
16722
+ const UserLogin = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-563b0048"]]);
16723
+ function validatePassword(password) {
16724
+ if (!password) return "";
16725
+ if (password.length < 6) {
16726
+ return "Password must be at least 6 characters";
16727
+ }
16728
+ const uniqueChars = new Set(password).size;
16729
+ if (uniqueChars < 2) {
16730
+ return "Password must contain at least 2 different characters";
16731
+ }
16732
+ return "";
16733
+ }
16734
+ function isPasswordValid(password) {
16735
+ return validatePassword(password) === "";
16736
+ }
16737
+ const _sfc_main$8 = defineComponent({
16475
16738
  name: "UserRegistration",
16476
- emits: ["toggle"],
16739
+ emits: ["toggle", "signup-success"],
16477
16740
  data() {
16478
16741
  return {
16742
+ email: "",
16479
16743
  username: "",
16480
16744
  password: "",
16481
16745
  retypedPassword: "",
16482
16746
  passwordVisible: false,
16747
+ emailError: false,
16748
+ emailHint: "",
16483
16749
  usernameValidationInProgress: false,
16484
16750
  usernameError: false,
16485
16751
  usernameHint: "",
@@ -16504,6 +16770,9 @@ const _sfc_main$5 = defineComponent({
16504
16770
  color: this.badLoginAttempt ? "error" : "success",
16505
16771
  text: this.badLoginAttempt ? "Try again" : "Log In"
16506
16772
  };
16773
+ },
16774
+ passwordError() {
16775
+ return validatePassword(this.password);
16507
16776
  }
16508
16777
  },
16509
16778
  async created() {
@@ -16514,11 +16783,29 @@ const _sfc_main$5 = defineComponent({
16514
16783
  log("Toggling registration / login forms.");
16515
16784
  this.$emit("toggle");
16516
16785
  },
16786
+ validateEmail() {
16787
+ this.emailError = false;
16788
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
16789
+ if (this.email && !emailRegex.test(this.email)) {
16790
+ this.emailError = true;
16791
+ this.emailHint = "Please enter a valid email address";
16792
+ } else {
16793
+ this.emailHint = "";
16794
+ }
16795
+ },
16517
16796
  validateUsername() {
16518
16797
  this.usernameError = false;
16519
16798
  },
16520
16799
  async createUser() {
16521
16800
  this.awaitingResponse = true;
16801
+ if (this.passwordError) {
16802
+ alertUser({
16803
+ text: this.passwordError,
16804
+ status: Status.error
16805
+ });
16806
+ this.awaitingResponse = false;
16807
+ return;
16808
+ }
16522
16809
  log(`
16523
16810
  User creation
16524
16811
  -------------
@@ -16529,13 +16816,36 @@ Teacher: ${this.teacher}
16529
16816
  Author: ${this.author}
16530
16817
  `);
16531
16818
  if (this.password === this.retypedPassword) {
16532
- if (!this.user) return;
16819
+ if (!this.user) {
16820
+ console.error("ERROR: No user object available");
16821
+ return;
16822
+ }
16533
16823
  this.user.createAccount(this.username, this.password).then(async (resp) => {
16534
16824
  if (resp.status === Status.ok) {
16535
16825
  this.authStore.loginAndRegistration.loggedIn = true;
16536
16826
  this.authStore.loginAndRegistration.init = false;
16537
16827
  this.authStore.loginAndRegistration.init = true;
16538
- this.$router.push(`/u/${(await getCurrentUser()).getUsername()}/new`);
16828
+ if (this.email) {
16829
+ try {
16830
+ const currentUser = await getCurrentUser();
16831
+ await currentUser.setConfig({ email: this.email });
16832
+ const origin = typeof window !== "undefined" ? window.location.origin : void 0;
16833
+ const verificationResult = await sendVerificationEmail(this.username, this.email, origin);
16834
+ if (verificationResult.ok) {
16835
+ alertUser({
16836
+ text: "Account created! Please check your email to verify your account.",
16837
+ status: Status.ok
16838
+ });
16839
+ } else {
16840
+ log(`Warning: Failed to send verification email: ${verificationResult.error}`);
16841
+ }
16842
+ } catch (emailError) {
16843
+ console.error("Email save/send error:", emailError);
16844
+ log(`Warning: Failed to save email or send verification: ${emailError}`);
16845
+ }
16846
+ }
16847
+ const username = (await getCurrentUser()).getUsername();
16848
+ this.$emit("signup-success", { username });
16539
16849
  } else {
16540
16850
  if (resp.error === "This username is taken!") {
16541
16851
  this.usernameError = true;
@@ -16553,11 +16863,14 @@ Author: ${this.author}
16553
16863
  }
16554
16864
  }
16555
16865
  }).catch((e) => {
16556
- if (e)
16866
+ console.error("createAccount error:", e);
16867
+ if (e) {
16868
+ const errorText = e?.message || e?.error || e?.toString() || "Account creation failed";
16557
16869
  alertUser({
16558
- text: JSON.stringify(e),
16870
+ text: errorText,
16559
16871
  status: Status.error
16560
16872
  });
16873
+ }
16561
16874
  });
16562
16875
  this.awaitingResponse = false;
16563
16876
  } else {
@@ -16570,7 +16883,7 @@ Author: ${this.author}
16570
16883
  }
16571
16884
  }
16572
16885
  });
16573
- function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16886
+ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
16574
16887
  const _component_v_card_title = resolveComponent("v-card-title");
16575
16888
  const _component_v_text_field = resolveComponent("v-text-field");
16576
16889
  const _component_v_btn = resolveComponent("v-btn");
@@ -16586,7 +16899,7 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16586
16899
  key: 0,
16587
16900
  class: "text-h5 bg-grey-lighten-2"
16588
16901
  }, {
16589
- default: withCtx(() => _cache[6] || (_cache[6] = [
16902
+ default: withCtx(() => _cache[7] || (_cache[7] = [
16590
16903
  createTextVNode(" Create an Account ")
16591
16904
  ])),
16592
16905
  _: 1
@@ -16597,12 +16910,22 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16597
16910
  onSubmit: withModifiers(_ctx.createUser, ["prevent"])
16598
16911
  }, {
16599
16912
  default: withCtx(() => [
16913
+ createVNode(_component_v_text_field, {
16914
+ modelValue: _ctx.email,
16915
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.email = $event),
16916
+ name: "email",
16917
+ label: "Email address",
16918
+ type: "email",
16919
+ "prepend-icon": "mdi-email",
16920
+ error: _ctx.emailError,
16921
+ hint: _ctx.emailHint,
16922
+ onBlur: _ctx.validateEmail
16923
+ }, null, 8, ["modelValue", "error", "hint", "onBlur"]),
16600
16924
  createVNode(_component_v_text_field, {
16601
16925
  id: "",
16602
16926
  ref: "userNameTextField",
16603
16927
  modelValue: _ctx.username,
16604
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.username = $event),
16605
- autofocus: "",
16928
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => _ctx.username = $event),
16606
16929
  name: "username",
16607
16930
  label: "Choose a Username",
16608
16931
  "prepend-icon": "mdi-account-circle",
@@ -16612,20 +16935,21 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16612
16935
  }, null, 8, ["modelValue", "error", "hint", "onBlur"]),
16613
16936
  createVNode(_component_v_text_field, {
16614
16937
  modelValue: _ctx.password,
16615
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => _ctx.password = $event),
16938
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => _ctx.password = $event),
16616
16939
  "prepend-icon": "mdi-lock",
16617
16940
  name: "password",
16618
16941
  hover: "Show password",
16619
16942
  label: "Create a password",
16620
- hint: "",
16943
+ hint: _ctx.passwordError,
16944
+ error: !!_ctx.passwordError,
16621
16945
  min: "4",
16622
16946
  "append-icon": _ctx.passwordVisible ? "mdi-eye-off" : "mdi-eye",
16623
16947
  type: _ctx.passwordVisible ? "text" : "password",
16624
- "onClick:append": _cache[2] || (_cache[2] = () => _ctx.passwordVisible = !_ctx.passwordVisible)
16625
- }, null, 8, ["modelValue", "append-icon", "type"]),
16948
+ "onClick:append": _cache[3] || (_cache[3] = () => _ctx.passwordVisible = !_ctx.passwordVisible)
16949
+ }, null, 8, ["modelValue", "hint", "error", "append-icon", "type"]),
16626
16950
  createVNode(_component_v_text_field, {
16627
16951
  modelValue: _ctx.retypedPassword,
16628
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => _ctx.retypedPassword = $event),
16952
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => _ctx.retypedPassword = $event),
16629
16953
  "prepend-icon": "mdi-lock",
16630
16954
  name: "retypedPassword",
16631
16955
  hover: "Show password",
@@ -16636,18 +16960,18 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16636
16960
  }, null, 8, ["modelValue", "type"]),
16637
16961
  createVNode(_component_v_snackbar, {
16638
16962
  modelValue: _ctx.badLoginAttempt,
16639
- "onUpdate:modelValue": _cache[5] || (_cache[5] = ($event) => _ctx.badLoginAttempt = $event),
16963
+ "onUpdate:modelValue": _cache[6] || (_cache[6] = ($event) => _ctx.badLoginAttempt = $event),
16640
16964
  location: "bottom right",
16641
16965
  timeout: 5e3
16642
16966
  }, {
16643
16967
  default: withCtx(() => [
16644
- _cache[8] || (_cache[8] = createTextVNode(" Username or password was incorrect. ")),
16968
+ _cache[9] || (_cache[9] = createTextVNode(" Username or password was incorrect. ")),
16645
16969
  createVNode(_component_v_btn, {
16646
16970
  color: "pink",
16647
16971
  variant: "text",
16648
- onClick: _cache[4] || (_cache[4] = ($event) => _ctx.badLoginAttempt = false)
16972
+ onClick: _cache[5] || (_cache[5] = ($event) => _ctx.badLoginAttempt = false)
16649
16973
  }, {
16650
- default: withCtx(() => _cache[7] || (_cache[7] = [
16974
+ default: withCtx(() => _cache[8] || (_cache[8] = [
16651
16975
  createTextVNode(" Close ")
16652
16976
  ])),
16653
16977
  _: 1
@@ -16659,26 +16983,27 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16659
16983
  class: "mr-2",
16660
16984
  type: "submit",
16661
16985
  loading: _ctx.awaitingResponse,
16662
- color: _ctx.buttonStatus.color
16986
+ color: _ctx.buttonStatus.color,
16987
+ disabled: !!_ctx.passwordError || _ctx.password !== _ctx.retypedPassword
16663
16988
  }, {
16664
16989
  default: withCtx(() => [
16665
16990
  createVNode(_component_v_icon, { start: "" }, {
16666
- default: withCtx(() => _cache[9] || (_cache[9] = [
16991
+ default: withCtx(() => _cache[10] || (_cache[10] = [
16667
16992
  createTextVNode("mdi-lock-open")
16668
16993
  ])),
16669
16994
  _: 1
16670
16995
  }),
16671
- _cache[10] || (_cache[10] = createTextVNode(" Create Account "))
16996
+ _cache[11] || (_cache[11] = createTextVNode(" Create Account "))
16672
16997
  ]),
16673
16998
  _: 1
16674
- }, 8, ["loading", "color"]),
16999
+ }, 8, ["loading", "color", "disabled"]),
16675
17000
  _ctx.registrationRoute ? (openBlock(), createBlock(_component_router_link, {
16676
17001
  key: 0,
16677
17002
  to: "login"
16678
17003
  }, {
16679
17004
  default: withCtx(() => [
16680
17005
  createVNode(_component_v_btn, { variant: "text" }, {
16681
- default: withCtx(() => _cache[11] || (_cache[11] = [
17006
+ default: withCtx(() => _cache[12] || (_cache[12] = [
16682
17007
  createTextVNode("Log In")
16683
17008
  ])),
16684
17009
  _: 1
@@ -16690,7 +17015,7 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16690
17015
  variant: "text",
16691
17016
  onClick: _ctx.toggle
16692
17017
  }, {
16693
- default: withCtx(() => _cache[12] || (_cache[12] = [
17018
+ default: withCtx(() => _cache[13] || (_cache[13] = [
16694
17019
  createTextVNode(" Log In ")
16695
17020
  ])),
16696
17021
  _: 1
@@ -16705,17 +17030,178 @@ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
16705
17030
  _: 1
16706
17031
  });
16707
17032
  }
16708
- const UserRegistration = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["render", _sfc_render$4]]);
16709
- const _hoisted_1$4 = { key: 0 };
16710
- const _hoisted_2$3 = { key: "login-buttons" };
16711
- const _hoisted_3$3 = { key: "user-chip" };
16712
- const _sfc_main$4 = /* @__PURE__ */ defineComponent({
17033
+ const UserRegistration = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["render", _sfc_render$7]]);
17034
+ const _sfc_main$7 = defineComponent({
17035
+ name: "RequestPasswordReset",
17036
+ emits: ["cancel", "success"],
17037
+ data() {
17038
+ return {
17039
+ email: "",
17040
+ emailError: false,
17041
+ emailHint: "",
17042
+ isSubmitting: false,
17043
+ requestSent: false
17044
+ };
17045
+ },
17046
+ methods: {
17047
+ validateEmail() {
17048
+ this.emailError = false;
17049
+ this.emailHint = "";
17050
+ if (!this.email) return;
17051
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
17052
+ if (!emailRegex.test(this.email)) {
17053
+ this.emailError = true;
17054
+ this.emailHint = "Please enter a valid email address";
17055
+ }
17056
+ },
17057
+ async handleSubmit() {
17058
+ this.validateEmail();
17059
+ if (this.emailError || !this.email) {
17060
+ return;
17061
+ }
17062
+ this.isSubmitting = true;
17063
+ try {
17064
+ const origin = typeof window !== "undefined" ? window.location.origin : void 0;
17065
+ const result = await requestPasswordReset(this.email, origin);
17066
+ if (result.ok) {
17067
+ this.requestSent = true;
17068
+ this.$emit("success", this.email);
17069
+ } else {
17070
+ alertUser({
17071
+ text: result.error || "Failed to send reset email",
17072
+ status: Status.error
17073
+ });
17074
+ }
17075
+ } catch (error) {
17076
+ alertUser({
17077
+ text: "An unexpected error occurred",
17078
+ status: Status.error
17079
+ });
17080
+ } finally {
17081
+ this.isSubmitting = false;
17082
+ }
17083
+ }
17084
+ }
17085
+ });
17086
+ const _hoisted_1$7 = {
17087
+ key: 1,
17088
+ class: "text-center"
17089
+ };
17090
+ function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
17091
+ const _component_v_card_title = resolveComponent("v-card-title");
17092
+ const _component_v_text_field = resolveComponent("v-text-field");
17093
+ const _component_v_icon = resolveComponent("v-icon");
17094
+ const _component_v_btn = resolveComponent("v-btn");
17095
+ const _component_v_form = resolveComponent("v-form");
17096
+ const _component_v_card_text = resolveComponent("v-card-text");
17097
+ const _component_v_spacer = resolveComponent("v-spacer");
17098
+ const _component_v_card_actions = resolveComponent("v-card-actions");
17099
+ const _component_v_card = resolveComponent("v-card");
17100
+ return openBlock(), createBlock(_component_v_card, null, {
17101
+ default: withCtx(() => [
17102
+ createVNode(_component_v_card_title, { class: "text-h5 bg-grey-lighten-2" }, {
17103
+ default: withCtx(() => _cache[2] || (_cache[2] = [
17104
+ createTextVNode(" Reset Password ")
17105
+ ])),
17106
+ _: 1
17107
+ }),
17108
+ createVNode(_component_v_card_text, { class: "pa-6" }, {
17109
+ default: withCtx(() => [
17110
+ !_ctx.requestSent ? (openBlock(), createBlock(_component_v_form, {
17111
+ key: 0,
17112
+ onSubmit: withModifiers(_ctx.handleSubmit, ["prevent"])
17113
+ }, {
17114
+ default: withCtx(() => [
17115
+ _cache[5] || (_cache[5] = createElementVNode("p", { class: "mb-4" }, " Enter your email address and we'll send you a link to reset your password. ", -1)),
17116
+ createVNode(_component_v_text_field, {
17117
+ modelValue: _ctx.email,
17118
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.email = $event),
17119
+ name: "email",
17120
+ label: "Email address",
17121
+ type: "email",
17122
+ "prepend-icon": "mdi-email",
17123
+ error: _ctx.emailError,
17124
+ hint: _ctx.emailHint,
17125
+ disabled: _ctx.isSubmitting,
17126
+ onBlur: _ctx.validateEmail
17127
+ }, null, 8, ["modelValue", "error", "hint", "disabled", "onBlur"]),
17128
+ createVNode(_component_v_btn, {
17129
+ type: "submit",
17130
+ color: "primary",
17131
+ class: "mt-4",
17132
+ loading: _ctx.isSubmitting,
17133
+ disabled: !_ctx.email || _ctx.emailError,
17134
+ block: ""
17135
+ }, {
17136
+ default: withCtx(() => [
17137
+ createVNode(_component_v_icon, { start: "" }, {
17138
+ default: withCtx(() => _cache[3] || (_cache[3] = [
17139
+ createTextVNode("mdi-email-send")
17140
+ ])),
17141
+ _: 1
17142
+ }),
17143
+ _cache[4] || (_cache[4] = createTextVNode(" Send Reset Link "))
17144
+ ]),
17145
+ _: 1
17146
+ }, 8, ["loading", "disabled"])
17147
+ ]),
17148
+ _: 1
17149
+ }, 8, ["onSubmit"])) : (openBlock(), createElementBlock("div", _hoisted_1$7, [
17150
+ createVNode(_component_v_icon, {
17151
+ color: "success",
17152
+ size: "64",
17153
+ class: "mb-4"
17154
+ }, {
17155
+ default: withCtx(() => _cache[6] || (_cache[6] = [
17156
+ createTextVNode("mdi-email-check")
17157
+ ])),
17158
+ _: 1
17159
+ }),
17160
+ _cache[9] || (_cache[9] = createElementVNode("h3", { class: "mb-2" }, "Check Your Email", -1)),
17161
+ createElementVNode("p", null, [
17162
+ _cache[7] || (_cache[7] = createTextVNode(" If an account exists with ")),
17163
+ createElementVNode("strong", null, toDisplayString(_ctx.email), 1),
17164
+ _cache[8] || (_cache[8] = createTextVNode(", you will receive a password reset link shortly. "))
17165
+ ]),
17166
+ _cache[10] || (_cache[10] = createElementVNode("p", { class: "text-caption mt-4" }, "Didn't receive an email? Check your spam folder.", -1))
17167
+ ]))
17168
+ ]),
17169
+ _: 1
17170
+ }),
17171
+ !_ctx.requestSent ? (openBlock(), createBlock(_component_v_card_actions, { key: 0 }, {
17172
+ default: withCtx(() => [
17173
+ renderSlot(_ctx.$slots, "back-action", {}, () => [
17174
+ createVNode(_component_v_btn, {
17175
+ variant: "text",
17176
+ onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("cancel"))
17177
+ }, {
17178
+ default: withCtx(() => _cache[11] || (_cache[11] = [
17179
+ createTextVNode(" Cancel ")
17180
+ ])),
17181
+ _: 1
17182
+ })
17183
+ ]),
17184
+ createVNode(_component_v_spacer)
17185
+ ]),
17186
+ _: 3
17187
+ })) : createCommentVNode("", true)
17188
+ ]),
17189
+ _: 3
17190
+ });
17191
+ }
17192
+ const RequestPasswordReset = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["render", _sfc_render$6]]);
17193
+ const _hoisted_1$6 = { key: 0 };
17194
+ const _hoisted_2$4 = { key: "login-buttons" };
17195
+ const _hoisted_3$4 = { key: "user-chip" };
17196
+ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
16713
17197
  __name: "UserLoginAndRegistrationContainer",
16714
17198
  props: {
16715
17199
  showLoginButton: { type: Boolean },
16716
- redirectToPath: {}
17200
+ redirectToPath: {},
17201
+ showRegistration: { type: Boolean }
16717
17202
  },
16718
17203
  setup(__props) {
17204
+ const props = __props;
16719
17205
  const route = useRoute();
16720
17206
  const authStore = useAuthStore();
16721
17207
  const authUI = useAuthUI();
@@ -16736,12 +17222,18 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16736
17222
  }
16737
17223
  return !authStore.loginAndRegistration.loggedIn;
16738
17224
  });
16739
- const authUIConfig = computed(() => authUI.config.value || {
16740
- showLoginRegistration: true,
16741
- showLogout: true,
16742
- showResetData: false,
16743
- logoutLabel: "Log out",
16744
- resetLabel: ""
17225
+ const authUIConfig = computed(() => {
17226
+ const baseConfig2 = authUI.config.value || {
17227
+ showLoginRegistration: true,
17228
+ showLogout: true,
17229
+ showResetData: false,
17230
+ logoutLabel: "Log out",
17231
+ resetLabel: ""
17232
+ };
17233
+ return {
17234
+ ...baseConfig2,
17235
+ ...props.showRegistration !== void 0 && { showLoginRegistration: props.showRegistration }
17236
+ };
16745
17237
  });
16746
17238
  const regDialog = computed({
16747
17239
  get: () => authStore.loginAndRegistration.regDialogOpen,
@@ -16755,6 +17247,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16755
17247
  authStore.loginAndRegistration.loginDialogOpen = value;
16756
17248
  }
16757
17249
  });
17250
+ const resetDialog = ref(false);
16758
17251
  const toggle = () => {
16759
17252
  if (regDialog.value && loginDialog.value) {
16760
17253
  throw new Error("Registration / Login dialogs both activated.");
@@ -16765,17 +17258,25 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16765
17258
  loginDialog.value = !loginDialog.value;
16766
17259
  }
16767
17260
  };
17261
+ const openResetDialog = () => {
17262
+ loginDialog.value = false;
17263
+ resetDialog.value = true;
17264
+ };
17265
+ const closeResetDialog = () => {
17266
+ resetDialog.value = false;
17267
+ };
16768
17268
  return (_ctx, _cache) => {
16769
17269
  const _component_v_btn = resolveComponent("v-btn");
16770
17270
  const _component_v_dialog = resolveComponent("v-dialog");
16771
- return userReady.value && display.value ? (openBlock(), createElementBlock("div", _hoisted_1$4, [
17271
+ return userReady.value && display.value ? (openBlock(), createElementBlock("div", _hoisted_1$6, [
16772
17272
  createVNode(Transition, {
16773
17273
  name: "component-fade",
16774
17274
  mode: "out-in"
16775
17275
  }, {
16776
17276
  default: withCtx(() => [
16777
- guestMode.value && authUIConfig.value.showLoginRegistration ? (openBlock(), createElementBlock("div", _hoisted_2$3, [
16778
- createVNode(_component_v_dialog, {
17277
+ guestMode.value ? (openBlock(), createElementBlock("div", _hoisted_2$4, [
17278
+ authUIConfig.value.showLoginRegistration ? (openBlock(), createBlock(_component_v_dialog, {
17279
+ key: 0,
16779
17280
  modelValue: regDialog.value,
16780
17281
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => regDialog.value = $event),
16781
17282
  width: "500px"
@@ -16786,7 +17287,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16786
17287
  size: "small",
16787
17288
  color: "success"
16788
17289
  }, props2), {
16789
- default: withCtx(() => _cache[2] || (_cache[2] = [
17290
+ default: withCtx(() => _cache[3] || (_cache[3] = [
16790
17291
  createTextVNode("Sign Up")
16791
17292
  ])),
16792
17293
  _: 2
@@ -16796,7 +17297,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16796
17297
  createVNode(UserRegistration, { onToggle: toggle })
16797
17298
  ]),
16798
17299
  _: 1
16799
- }, 8, ["modelValue"]),
17300
+ }, 8, ["modelValue"])) : createCommentVNode("", true),
16800
17301
  createVNode(_component_v_dialog, {
16801
17302
  modelValue: loginDialog.value,
16802
17303
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => loginDialog.value = $event),
@@ -16807,18 +17308,34 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16807
17308
  size: "small",
16808
17309
  color: "success"
16809
17310
  }, props2), {
16810
- default: withCtx(() => _cache[3] || (_cache[3] = [
17311
+ default: withCtx(() => _cache[4] || (_cache[4] = [
16811
17312
  createTextVNode("Log In")
16812
17313
  ])),
16813
17314
  _: 2
16814
17315
  }, 1040)
16815
17316
  ]),
16816
17317
  default: withCtx(() => [
16817
- createVNode(UserLogin, { onToggle: toggle })
17318
+ createVNode(UserLogin, {
17319
+ onToggle: toggle,
17320
+ onForgotPassword: openResetDialog
17321
+ })
17322
+ ]),
17323
+ _: 1
17324
+ }, 8, ["modelValue"]),
17325
+ createVNode(_component_v_dialog, {
17326
+ modelValue: resetDialog.value,
17327
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => resetDialog.value = $event),
17328
+ width: "500px"
17329
+ }, {
17330
+ default: withCtx(() => [
17331
+ createVNode(RequestPasswordReset, {
17332
+ onCancel: closeResetDialog,
17333
+ onSuccess: closeResetDialog
17334
+ })
16818
17335
  ]),
16819
17336
  _: 1
16820
17337
  }, 8, ["modelValue"])
16821
- ])) : (openBlock(), createElementBlock("div", _hoisted_3$3, [
17338
+ ])) : (openBlock(), createElementBlock("div", _hoisted_3$4, [
16822
17339
  createVNode(UserChip)
16823
17340
  ]))
16824
17341
  ]),
@@ -16828,7 +17345,453 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
16828
17345
  };
16829
17346
  }
16830
17347
  });
16831
- const UserLoginAndRegistrationContainer = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-c17fbd4e"]]);
17348
+ const UserLoginAndRegistrationContainer = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-efb42c02"]]);
17349
+ const _sfc_main$5 = defineComponent({
17350
+ name: "VerifyEmail",
17351
+ emits: ["verified", "error"],
17352
+ props: {
17353
+ /**
17354
+ * Verification token. If not provided, will try to read from URL query params.
17355
+ */
17356
+ token: {
17357
+ type: String,
17358
+ default: null
17359
+ }
17360
+ },
17361
+ data() {
17362
+ return {
17363
+ isVerifying: false,
17364
+ verificationStatus: null,
17365
+ errorMessage: "",
17366
+ username: ""
17367
+ };
17368
+ },
17369
+ async mounted() {
17370
+ await this.performVerification();
17371
+ },
17372
+ methods: {
17373
+ async performVerification() {
17374
+ const token2 = this.token || this.getTokenFromURL();
17375
+ if (!token2) {
17376
+ this.verificationStatus = "error";
17377
+ this.errorMessage = "No verification token provided";
17378
+ return;
17379
+ }
17380
+ this.isVerifying = true;
17381
+ try {
17382
+ const result = await verifyEmail(token2);
17383
+ if (result.ok) {
17384
+ this.verificationStatus = "success";
17385
+ this.username = result.username || "";
17386
+ alertUser({
17387
+ text: "Email verified successfully!",
17388
+ status: Status.ok
17389
+ });
17390
+ } else {
17391
+ this.verificationStatus = "error";
17392
+ this.errorMessage = result.error || "Verification failed";
17393
+ alertUser({
17394
+ text: result.error || "Verification failed",
17395
+ status: Status.error
17396
+ });
17397
+ }
17398
+ } catch (error) {
17399
+ this.verificationStatus = "error";
17400
+ this.errorMessage = "An unexpected error occurred";
17401
+ alertUser({
17402
+ text: "An unexpected error occurred",
17403
+ status: Status.error
17404
+ });
17405
+ } finally {
17406
+ this.isVerifying = false;
17407
+ }
17408
+ },
17409
+ getTokenFromURL() {
17410
+ if (typeof window === "undefined") return null;
17411
+ const params = new URLSearchParams(window.location.search);
17412
+ return params.get("token");
17413
+ }
17414
+ }
17415
+ });
17416
+ const _hoisted_1$5 = {
17417
+ key: 0,
17418
+ class: "text-center"
17419
+ };
17420
+ const _hoisted_2$3 = {
17421
+ key: 1,
17422
+ class: "text-center"
17423
+ };
17424
+ const _hoisted_3$3 = { key: 0 };
17425
+ const _hoisted_4$2 = {
17426
+ key: 2,
17427
+ class: "text-center"
17428
+ };
17429
+ const _hoisted_5$2 = {
17430
+ key: 3,
17431
+ class: "text-center"
17432
+ };
17433
+ function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
17434
+ const _component_v_card_title = resolveComponent("v-card-title");
17435
+ const _component_v_progress_circular = resolveComponent("v-progress-circular");
17436
+ const _component_v_icon = resolveComponent("v-icon");
17437
+ const _component_v_card_text = resolveComponent("v-card-text");
17438
+ const _component_v_spacer = resolveComponent("v-spacer");
17439
+ const _component_v_btn = resolveComponent("v-btn");
17440
+ const _component_v_card_actions = resolveComponent("v-card-actions");
17441
+ const _component_v_card = resolveComponent("v-card");
17442
+ const _component_v_col = resolveComponent("v-col");
17443
+ const _component_v_row = resolveComponent("v-row");
17444
+ const _component_v_container = resolveComponent("v-container");
17445
+ return openBlock(), createBlock(_component_v_container, {
17446
+ class: "fill-height",
17447
+ fluid: ""
17448
+ }, {
17449
+ default: withCtx(() => [
17450
+ createVNode(_component_v_row, {
17451
+ align: "center",
17452
+ justify: "center"
17453
+ }, {
17454
+ default: withCtx(() => [
17455
+ createVNode(_component_v_col, {
17456
+ cols: "12",
17457
+ sm: "8",
17458
+ md: "6"
17459
+ }, {
17460
+ default: withCtx(() => [
17461
+ createVNode(_component_v_card, null, {
17462
+ default: withCtx(() => [
17463
+ createVNode(_component_v_card_title, { class: "text-h5 bg-grey-lighten-2" }, {
17464
+ default: withCtx(() => _cache[2] || (_cache[2] = [
17465
+ createTextVNode(" Email Verification ")
17466
+ ])),
17467
+ _: 1
17468
+ }),
17469
+ createVNode(_component_v_card_text, { class: "pa-6" }, {
17470
+ default: withCtx(() => [
17471
+ _ctx.isVerifying ? (openBlock(), createElementBlock("div", _hoisted_1$5, [
17472
+ createVNode(_component_v_progress_circular, {
17473
+ indeterminate: "",
17474
+ color: "primary",
17475
+ size: "64",
17476
+ class: "mb-4"
17477
+ }),
17478
+ _cache[3] || (_cache[3] = createElementVNode("p", null, "Verifying your email...", -1))
17479
+ ])) : _ctx.verificationStatus === "success" ? (openBlock(), createElementBlock("div", _hoisted_2$3, [
17480
+ createVNode(_component_v_icon, {
17481
+ color: "success",
17482
+ size: "64",
17483
+ class: "mb-4"
17484
+ }, {
17485
+ default: withCtx(() => _cache[4] || (_cache[4] = [
17486
+ createTextVNode("mdi-check-circle")
17487
+ ])),
17488
+ _: 1
17489
+ }),
17490
+ _cache[5] || (_cache[5] = createElementVNode("h3", { class: "mb-2" }, "Email Verified!", -1)),
17491
+ _cache[6] || (_cache[6] = createElementVNode("p", null, "Your account has been successfully verified.", -1)),
17492
+ _ctx.username ? (openBlock(), createElementBlock("p", _hoisted_3$3, "Welcome, " + toDisplayString(_ctx.username) + "!", 1)) : createCommentVNode("", true)
17493
+ ])) : _ctx.verificationStatus === "error" ? (openBlock(), createElementBlock("div", _hoisted_4$2, [
17494
+ createVNode(_component_v_icon, {
17495
+ color: "error",
17496
+ size: "64",
17497
+ class: "mb-4"
17498
+ }, {
17499
+ default: withCtx(() => _cache[7] || (_cache[7] = [
17500
+ createTextVNode("mdi-alert-circle")
17501
+ ])),
17502
+ _: 1
17503
+ }),
17504
+ _cache[8] || (_cache[8] = createElementVNode("h3", { class: "mb-2" }, "Verification Failed", -1)),
17505
+ createElementVNode("p", null, toDisplayString(_ctx.errorMessage), 1)
17506
+ ])) : (openBlock(), createElementBlock("div", _hoisted_5$2, [
17507
+ createVNode(_component_v_icon, {
17508
+ color: "warning",
17509
+ size: "64",
17510
+ class: "mb-4"
17511
+ }, {
17512
+ default: withCtx(() => _cache[9] || (_cache[9] = [
17513
+ createTextVNode("mdi-help-circle")
17514
+ ])),
17515
+ _: 1
17516
+ }),
17517
+ _cache[10] || (_cache[10] = createElementVNode("h3", { class: "mb-2" }, "No Verification Token", -1)),
17518
+ _cache[11] || (_cache[11] = createElementVNode("p", null, "Please use the link from your verification email.", -1))
17519
+ ]))
17520
+ ]),
17521
+ _: 1
17522
+ }),
17523
+ createVNode(_component_v_card_actions, null, {
17524
+ default: withCtx(() => [
17525
+ createVNode(_component_v_spacer),
17526
+ renderSlot(_ctx.$slots, "actions", {
17527
+ status: _ctx.verificationStatus,
17528
+ username: _ctx.username
17529
+ }, () => [
17530
+ _ctx.verificationStatus === "success" ? (openBlock(), createBlock(_component_v_btn, {
17531
+ key: 0,
17532
+ color: "primary",
17533
+ onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("verified", _ctx.username))
17534
+ }, {
17535
+ default: withCtx(() => _cache[12] || (_cache[12] = [
17536
+ createTextVNode(" Continue ")
17537
+ ])),
17538
+ _: 1
17539
+ })) : _ctx.verificationStatus === "error" ? (openBlock(), createBlock(_component_v_btn, {
17540
+ key: 1,
17541
+ variant: "text",
17542
+ onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("error", _ctx.errorMessage))
17543
+ }, {
17544
+ default: withCtx(() => _cache[13] || (_cache[13] = [
17545
+ createTextVNode(" Close ")
17546
+ ])),
17547
+ _: 1
17548
+ })) : createCommentVNode("", true)
17549
+ ])
17550
+ ]),
17551
+ _: 3
17552
+ })
17553
+ ]),
17554
+ _: 3
17555
+ })
17556
+ ]),
17557
+ _: 3
17558
+ })
17559
+ ]),
17560
+ _: 3
17561
+ })
17562
+ ]),
17563
+ _: 3
17564
+ });
17565
+ }
17566
+ const VerifyEmail = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["render", _sfc_render$5]]);
17567
+ const _sfc_main$4 = defineComponent({
17568
+ name: "ResetPassword",
17569
+ emits: ["complete", "error"],
17570
+ props: {
17571
+ /**
17572
+ * Reset token. If not provided, will try to read from URL query params.
17573
+ */
17574
+ token: {
17575
+ type: String,
17576
+ default: null
17577
+ }
17578
+ },
17579
+ data() {
17580
+ return {
17581
+ password: "",
17582
+ confirmPassword: "",
17583
+ passwordVisible: false,
17584
+ confirmPasswordError: false,
17585
+ confirmPasswordHint: "",
17586
+ isSubmitting: false,
17587
+ isComplete: false
17588
+ };
17589
+ },
17590
+ computed: {
17591
+ canSubmit() {
17592
+ return this.password.length >= 4 && this.confirmPassword.length >= 4 && this.password === this.confirmPassword && !this.confirmPasswordError;
17593
+ }
17594
+ },
17595
+ methods: {
17596
+ validateConfirmPassword() {
17597
+ this.confirmPasswordError = false;
17598
+ this.confirmPasswordHint = "";
17599
+ if (!this.confirmPassword) return;
17600
+ if (this.password !== this.confirmPassword) {
17601
+ this.confirmPasswordError = true;
17602
+ this.confirmPasswordHint = "Passwords do not match";
17603
+ }
17604
+ },
17605
+ async handleSubmit() {
17606
+ this.validateConfirmPassword();
17607
+ if (!this.canSubmit) {
17608
+ return;
17609
+ }
17610
+ const token2 = this.token || this.getTokenFromURL();
17611
+ if (!token2) {
17612
+ alertUser({
17613
+ text: "No reset token provided. Please use the link from your email.",
17614
+ status: Status.error
17615
+ });
17616
+ this.$emit("error", "No token");
17617
+ return;
17618
+ }
17619
+ this.isSubmitting = true;
17620
+ try {
17621
+ const result = await resetPassword(token2, this.password);
17622
+ if (result.ok) {
17623
+ this.isComplete = true;
17624
+ alertUser({
17625
+ text: "Password reset successfully!",
17626
+ status: Status.ok
17627
+ });
17628
+ } else {
17629
+ alertUser({
17630
+ text: result.error || "Failed to reset password",
17631
+ status: Status.error
17632
+ });
17633
+ this.$emit("error", result.error);
17634
+ }
17635
+ } catch (error) {
17636
+ alertUser({
17637
+ text: "An unexpected error occurred",
17638
+ status: Status.error
17639
+ });
17640
+ this.$emit("error", "Unexpected error");
17641
+ } finally {
17642
+ this.isSubmitting = false;
17643
+ }
17644
+ },
17645
+ getTokenFromURL() {
17646
+ if (typeof window === "undefined") return null;
17647
+ const params = new URLSearchParams(window.location.search);
17648
+ return params.get("token");
17649
+ }
17650
+ }
17651
+ });
17652
+ const _hoisted_1$4 = {
17653
+ key: 1,
17654
+ class: "text-center"
17655
+ };
17656
+ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
17657
+ const _component_v_card_title = resolveComponent("v-card-title");
17658
+ const _component_v_text_field = resolveComponent("v-text-field");
17659
+ const _component_v_icon = resolveComponent("v-icon");
17660
+ const _component_v_btn = resolveComponent("v-btn");
17661
+ const _component_v_form = resolveComponent("v-form");
17662
+ const _component_v_card_text = resolveComponent("v-card-text");
17663
+ const _component_v_spacer = resolveComponent("v-spacer");
17664
+ const _component_v_card_actions = resolveComponent("v-card-actions");
17665
+ const _component_v_card = resolveComponent("v-card");
17666
+ const _component_v_col = resolveComponent("v-col");
17667
+ const _component_v_row = resolveComponent("v-row");
17668
+ const _component_v_container = resolveComponent("v-container");
17669
+ return openBlock(), createBlock(_component_v_container, {
17670
+ class: "fill-height",
17671
+ fluid: ""
17672
+ }, {
17673
+ default: withCtx(() => [
17674
+ createVNode(_component_v_row, {
17675
+ align: "center",
17676
+ justify: "center"
17677
+ }, {
17678
+ default: withCtx(() => [
17679
+ createVNode(_component_v_col, {
17680
+ cols: "12",
17681
+ sm: "8",
17682
+ md: "6"
17683
+ }, {
17684
+ default: withCtx(() => [
17685
+ createVNode(_component_v_card, null, {
17686
+ default: withCtx(() => [
17687
+ createVNode(_component_v_card_title, { class: "text-h5 bg-grey-lighten-2" }, {
17688
+ default: withCtx(() => _cache[4] || (_cache[4] = [
17689
+ createTextVNode(" Set New Password ")
17690
+ ])),
17691
+ _: 1
17692
+ }),
17693
+ createVNode(_component_v_card_text, { class: "pa-6" }, {
17694
+ default: withCtx(() => [
17695
+ !_ctx.isComplete ? (openBlock(), createBlock(_component_v_form, {
17696
+ key: 0,
17697
+ onSubmit: withModifiers(_ctx.handleSubmit, ["prevent"])
17698
+ }, {
17699
+ default: withCtx(() => [
17700
+ _cache[7] || (_cache[7] = createElementVNode("p", { class: "mb-4" }, "Enter your new password below.", -1)),
17701
+ createVNode(_component_v_text_field, {
17702
+ modelValue: _ctx.password,
17703
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => _ctx.password = $event),
17704
+ "prepend-icon": "mdi-lock",
17705
+ name: "password",
17706
+ label: "New password",
17707
+ min: "4",
17708
+ "append-icon": _ctx.passwordVisible ? "mdi-eye-off" : "mdi-eye",
17709
+ type: _ctx.passwordVisible ? "text" : "password",
17710
+ disabled: _ctx.isSubmitting,
17711
+ "onClick:append": _cache[1] || (_cache[1] = () => _ctx.passwordVisible = !_ctx.passwordVisible)
17712
+ }, null, 8, ["modelValue", "append-icon", "type", "disabled"]),
17713
+ createVNode(_component_v_text_field, {
17714
+ modelValue: _ctx.confirmPassword,
17715
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => _ctx.confirmPassword = $event),
17716
+ "prepend-icon": "mdi-lock",
17717
+ name: "confirmPassword",
17718
+ label: "Confirm new password",
17719
+ min: "4",
17720
+ type: _ctx.passwordVisible ? "text" : "password",
17721
+ error: _ctx.confirmPasswordError,
17722
+ hint: _ctx.confirmPasswordHint,
17723
+ disabled: _ctx.isSubmitting,
17724
+ onBlur: _ctx.validateConfirmPassword
17725
+ }, null, 8, ["modelValue", "type", "error", "hint", "disabled", "onBlur"]),
17726
+ createVNode(_component_v_btn, {
17727
+ type: "submit",
17728
+ color: "primary",
17729
+ class: "mt-4",
17730
+ loading: _ctx.isSubmitting,
17731
+ disabled: !_ctx.canSubmit,
17732
+ block: ""
17733
+ }, {
17734
+ default: withCtx(() => [
17735
+ createVNode(_component_v_icon, { start: "" }, {
17736
+ default: withCtx(() => _cache[5] || (_cache[5] = [
17737
+ createTextVNode("mdi-lock-reset")
17738
+ ])),
17739
+ _: 1
17740
+ }),
17741
+ _cache[6] || (_cache[6] = createTextVNode(" Reset Password "))
17742
+ ]),
17743
+ _: 1
17744
+ }, 8, ["loading", "disabled"])
17745
+ ]),
17746
+ _: 1
17747
+ }, 8, ["onSubmit"])) : (openBlock(), createElementBlock("div", _hoisted_1$4, [
17748
+ createVNode(_component_v_icon, {
17749
+ color: "success",
17750
+ size: "64",
17751
+ class: "mb-4"
17752
+ }, {
17753
+ default: withCtx(() => _cache[8] || (_cache[8] = [
17754
+ createTextVNode("mdi-check-circle")
17755
+ ])),
17756
+ _: 1
17757
+ }),
17758
+ _cache[9] || (_cache[9] = createElementVNode("h3", { class: "mb-2" }, "Password Reset Successful!", -1)),
17759
+ _cache[10] || (_cache[10] = createElementVNode("p", null, "Your password has been updated. You can now log in with your new password.", -1))
17760
+ ]))
17761
+ ]),
17762
+ _: 1
17763
+ }),
17764
+ _ctx.isComplete ? (openBlock(), createBlock(_component_v_card_actions, { key: 0 }, {
17765
+ default: withCtx(() => [
17766
+ createVNode(_component_v_spacer),
17767
+ renderSlot(_ctx.$slots, "success-action", {}, () => [
17768
+ createVNode(_component_v_btn, {
17769
+ color: "primary",
17770
+ onClick: _cache[3] || (_cache[3] = ($event) => _ctx.$emit("complete"))
17771
+ }, {
17772
+ default: withCtx(() => _cache[11] || (_cache[11] = [
17773
+ createTextVNode(" Continue to Login ")
17774
+ ])),
17775
+ _: 1
17776
+ })
17777
+ ])
17778
+ ]),
17779
+ _: 3
17780
+ })) : createCommentVNode("", true)
17781
+ ]),
17782
+ _: 3
17783
+ })
17784
+ ]),
17785
+ _: 3
17786
+ })
17787
+ ]),
17788
+ _: 3
17789
+ })
17790
+ ]),
17791
+ _: 3
17792
+ });
17793
+ }
17794
+ const ResetPassword = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["render", _sfc_render$4]]);
16832
17795
  const _sfc_main$3 = defineComponent({
16833
17796
  name: "SkTagsInput",
16834
17797
  components: {
@@ -17844,7 +18807,7 @@ export {
17844
18807
  CardSearch,
17845
18808
  CardSearchResults,
17846
18809
  CardViewer,
17847
- _sfc_main$a as CodeBlockRenderer,
18810
+ _sfc_main$d as CodeBlockRenderer,
17848
18811
  CourseCardBrowser,
17849
18812
  CourseInformation,
17850
18813
  Displayable,
@@ -17856,6 +18819,8 @@ export {
17856
18819
  PaginatingToolbar,
17857
18820
  Question,
17858
18821
  RadioMultipleChoice,
18822
+ RequestPasswordReset,
18823
+ ResetPassword,
17859
18824
  SkMouseTrap,
17860
18825
  SkMouseTrapToolTip,
17861
18826
  SkldrMouseTrap,
@@ -17870,20 +18835,29 @@ export {
17870
18835
  UserLogin,
17871
18836
  UserLoginAndRegistrationContainer,
17872
18837
  UserRegistration,
18838
+ VerifyEmail,
17873
18839
  alertUser,
17874
18840
  containsComponent,
17875
18841
  getCurrentUser,
18842
+ getUserStatus,
17876
18843
  isComponent,
17877
18844
  isDefineComponent,
18845
+ isPasswordValid,
17878
18846
  isQuestionView,
17879
18847
  piniaPlugin,
18848
+ requestPasswordReset,
18849
+ resetPassword,
18850
+ sendVerificationEmail,
17880
18851
  splitByDelimiters,
17881
18852
  splitParagraphToken,
17882
18853
  splitTextToken,
17883
18854
  useAuthStore,
17884
18855
  useCardPreviewModeStore,
17885
18856
  useConfigStore,
18857
+ useEntitlements,
17886
18858
  useQuestionView,
17887
- useViewable
18859
+ useViewable,
18860
+ validatePassword,
18861
+ verifyEmail
17888
18862
  };
17889
18863
  //# sourceMappingURL=common-ui.es.js.map