@vue-skuilder/common-ui 0.2.21 → 0.2.22
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.
- package/dist/assets/index.css +1 -1
- package/dist/common-ui.es.js +104 -98
- package/dist/common-ui.es.js.map +1 -1
- package/dist/common-ui.umd.js +7 -7
- package/dist/common-ui.umd.js.map +1 -1
- package/dist/components/StudySession.types.d.ts +7 -1
- package/dist/components/StudySession.types.d.ts.map +1 -1
- package/dist/components/StudySession.vue.d.ts.map +1 -1
- package/dist/components/auth/UserLogin.vue.d.ts.map +1 -1
- package/dist/components/auth/UserRegistration.vue.d.ts +8 -0
- package/dist/components/auth/UserRegistration.vue.d.ts.map +1 -1
- package/dist/services/authAPI.d.ts +10 -0
- package/dist/services/authAPI.d.ts.map +1 -1
- package/dist/stores/useAuthStore.d.ts +8 -0
- package/dist/stores/useAuthStore.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/StudySession.types.ts +8 -1
- package/src/components/StudySession.vue +26 -0
- package/src/components/auth/UserLogin.vue +23 -5
- package/src/services/authAPI.ts +13 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReplanHints, OutcomeObserver } from '../../../db/dist/index.mjs';
|
|
1
|
+
import { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '../../../db/dist/index.mjs';
|
|
2
2
|
/**
|
|
3
3
|
* Misc. config for a study StudySessionConfig
|
|
4
4
|
*/
|
|
@@ -40,6 +40,12 @@ export type StudySessionConfig = {
|
|
|
40
40
|
* session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
|
|
41
41
|
*/
|
|
42
42
|
outcomeObservers?: OutcomeObserver[];
|
|
43
|
+
/**
|
|
44
|
+
* Thin learner-state snapshot taken at session open and close, stored on the
|
|
45
|
+
* session's `StudySessionDoc`. Keep the returned object small — counts, ids,
|
|
46
|
+
* scores. Time-boxed and error-isolated by the controller.
|
|
47
|
+
*/
|
|
48
|
+
sessionStateSnapshot?: SessionStateSnapshotProvider;
|
|
43
49
|
};
|
|
44
50
|
/**
|
|
45
51
|
* Built-in card transition presets provided by StudySession.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"StudySession.types.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,4BAA4B,EAAE,MAAM,kBAAkB,CAAC;AAEnG;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,OAAO,CAAC;IAEvB;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;IAExB;;;;;;;;;OASG;IACH,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IAErC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,4BAA4B,CAAC;CACrD,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,CAAC;AAElF,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"AAiFA;AAAA,
|
|
1
|
+
{"version":3,"file":"StudySession.vue.d.ts","sourceRoot":"","sources":["../../src/components/StudySession.vue"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;
|
|
1
|
+
{"version":3,"file":"UserLogin.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserLogin.vue"],"names":[],"mappings":"AA2DA;AA2OA,UAAU,KAAK;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqJD,iBAAS,cAAc;WAwPT,OAAO,IAA6B;;mCAVV,GAAG;;;;EAe1C;AAsBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;gBA1aN,MAAM;6EAmbnB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -44,6 +44,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
44
44
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
45
45
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
46
46
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
47
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
47
48
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
48
49
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
49
50
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -73,6 +74,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
73
74
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
74
75
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
75
76
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
77
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
76
78
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
77
79
|
createAccount: (username: string, password: string) => Promise<{
|
|
78
80
|
status: Status;
|
|
@@ -109,6 +111,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
109
111
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
110
112
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
111
113
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
114
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
112
115
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
113
116
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
114
117
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -138,6 +141,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
138
141
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
139
142
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
140
143
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
144
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
141
145
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
142
146
|
createAccount: (username: string, password: string) => Promise<{
|
|
143
147
|
status: Status;
|
|
@@ -174,6 +178,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
174
178
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
175
179
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
176
180
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
181
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
177
182
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
178
183
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
179
184
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -203,6 +208,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
203
208
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
204
209
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
205
210
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
211
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
206
212
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
207
213
|
createAccount: (username: string, password: string) => Promise<{
|
|
208
214
|
status: Status;
|
|
@@ -240,6 +246,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
240
246
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../../db/dist/index.mjs').CourseRegistration>;
|
|
241
247
|
getActiveCourses: () => Promise<import('../../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
242
248
|
getPendingReviews: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
249
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
243
250
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
244
251
|
getActivityRecords: () => Promise<import('../../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
245
252
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -269,6 +276,7 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
|
|
|
269
276
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
270
277
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
271
278
|
putUserOutcome: (record: import('../../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
279
|
+
putStudySession: (doc: import('../../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
272
280
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
273
281
|
createAccount: (username: string, password: string) => Promise<{
|
|
274
282
|
status: Status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,OAgSO,EAAmB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAO,MAAM,sBAAsB,CAAC;;;cAU3B,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;UAuBrD,eAAe,GAAG,IAAI;WACO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"UserRegistration.vue.d.ts","sourceRoot":"","sources":["../../../src/components/auth/UserRegistration.vue"],"names":[],"mappings":"AA8EA;AAAA,OAgSO,EAAmB,QAAQ,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAO,MAAM,sBAAsB,CAAC;;;cAU3B,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;;;;;;;;;;;;UAuBrD,eAAe,GAAG,IAAI;WACO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA8U9C,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBA1CN,CAAC;;;;;;;;;;;;;wBAyC+D,CAAC;yBAC3D,CAAA;;;;;;;;;;;;;;;;;yBA/WQ,OAAO;;;;;qBASX,MAAM;2BAGA,MAAM;;;;;;;;cA7CT,QAAQ,CAAC,CAAC,OAAO,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;;;;;;;AALzE,wBAoMG"}
|
|
@@ -32,6 +32,16 @@ export interface UserStatusResponse extends AuthResponse {
|
|
|
32
32
|
}
|
|
33
33
|
export interface ResolveLoginResponse extends AuthResponse {
|
|
34
34
|
username?: string;
|
|
35
|
+
/**
|
|
36
|
+
* HTTP status of the resolve call on failure, so callers can distinguish a
|
|
37
|
+
* genuine credential rejection from a system/service failure. The server
|
|
38
|
+
* returns a uniform 401 for every real auth failure (unknown identifier,
|
|
39
|
+
* unverified email, wrong password — no enumeration leak); anything else
|
|
40
|
+
* (404 = route not deployed, 5xx, or `undefined` for a network error) means
|
|
41
|
+
* the resolver itself is unavailable, not that the credentials were wrong.
|
|
42
|
+
* Undefined on success.
|
|
43
|
+
*/
|
|
44
|
+
status?: number;
|
|
35
45
|
}
|
|
36
46
|
/**
|
|
37
47
|
* Triggers verification email send for a newly created account.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"authAPI.d.ts","sourceRoot":"","sources":["../../src/services/authAPI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,mBAAmB,CAAC,EAAE;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC;KACH;CACF;AAgCD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE3D,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,UAAU,GAAG,WAAW,CAAC;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+BvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+B7E;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAgC/B;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAsBjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA4BvB;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
|
|
@@ -26,6 +26,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
26
26
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
27
27
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
28
28
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
29
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
29
30
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
30
31
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
31
32
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -55,6 +56,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
55
56
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
56
57
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
57
58
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
59
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
58
60
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
59
61
|
createAccount: (username: string, password: string) => Promise<{
|
|
60
62
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -91,6 +93,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
91
93
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
92
94
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
93
95
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
96
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
94
97
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
95
98
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
96
99
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -120,6 +123,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
120
123
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
121
124
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
122
125
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
126
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
123
127
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
124
128
|
createAccount: (username: string, password: string) => Promise<{
|
|
125
129
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -156,6 +160,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
156
160
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
157
161
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
158
162
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
163
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
159
164
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
160
165
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
161
166
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -185,6 +190,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
185
190
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
186
191
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
187
192
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
193
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
188
194
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
189
195
|
createAccount: (username: string, password: string) => Promise<{
|
|
190
196
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -222,6 +228,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
222
228
|
getCourseRegDoc: (courseId: string) => Promise<import('../../../db/dist/index.mjs').CourseRegistration>;
|
|
223
229
|
getActiveCourses: () => Promise<import('../../../db/dist/index.mjs').CourseRegistration[]>;
|
|
224
230
|
getPendingReviews: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
231
|
+
getStudySessions: (courseId?: string) => Promise<import('../../../db/dist/index.mjs').StudySessionDoc[]>;
|
|
225
232
|
getReviewsForcast: (daysCount: number, courseId?: string) => Promise<import('../../../db/dist/index.mjs').ScheduledCard[]>;
|
|
226
233
|
getActivityRecords: () => Promise<import('../../../db/dist/index.mjs').ActivityRecord[]>;
|
|
227
234
|
getStrategyState: <T>(courseId: string, strategyKey: string) => Promise<T | null>;
|
|
@@ -251,6 +258,7 @@ export declare const useAuthStore: () => import('pinia').Store<"auth", AuthState
|
|
|
251
258
|
putStrategyState: <T>(courseId: string, strategyKey: string, data: T) => Promise<void>;
|
|
252
259
|
deleteStrategyState: (courseId: string, strategyKey: string) => Promise<void>;
|
|
253
260
|
putUserOutcome: (record: import('../../../db/dist/index.mjs').UserOutcomeRecord) => Promise<void>;
|
|
261
|
+
putStudySession: (doc: import('../../../db/dist/index.mjs').StudySessionDoc) => Promise<void>;
|
|
254
262
|
update: <T extends PouchDB.Core.Document<object>>(id: string, update: Partial<T> | ((x: T) => T)) => Promise<T>;
|
|
255
263
|
createAccount: (username: string, password: string) => Promise<{
|
|
256
264
|
status: import('../../../common/dist/index.mjs').Status;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"useAuthStore.d.ts","sourceRoot":"","sources":["../../src/stores/useAuthStore.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGjE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,eAAe,GAAG,SAAS,CAAC;IACnC,oBAAoB,EAAE;QACpB,IAAI,EAAE,OAAO,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;QAClB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;KAC1B,CAAC;IACF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAiB3E;AAED,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAgFk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;kBA3G96tB,OAAO;sBACH,OAAO;2BACF,OAAO;6BACL,OAAO;;wBAEV,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAsGk9pB,CAAC;;;;;;;;;;;;;oBAAm7D,CAAC;qBAAuB,CAAC;;;;;;;;;yBA1C75tB,OAAO;uBAIT,OAAO;;;;;EAqC/B,CAAC"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.22",
|
|
7
7
|
"sideEffects": false,
|
|
8
8
|
"main": "./dist/common-ui.umd.js",
|
|
9
9
|
"module": "./dist/common-ui.es.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"@highlightjs/vue-plugin": "^2.1.2",
|
|
35
35
|
"@mdi/font": "^7.3.67",
|
|
36
36
|
"@vojtechlanka/vue-tags-input": "^3",
|
|
37
|
-
"@vue-skuilder/common": "0.2.
|
|
38
|
-
"@vue-skuilder/db": "0.2.
|
|
37
|
+
"@vue-skuilder/common": "0.2.22",
|
|
38
|
+
"@vue-skuilder/db": "0.2.22",
|
|
39
39
|
"highlight.js": "^11.0.1",
|
|
40
40
|
"marked": "^15.0.6",
|
|
41
41
|
"moment": "^2.29.4",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"vite-plugin-dts": "^4.5.3",
|
|
72
72
|
"vitest": "^4.1.0"
|
|
73
73
|
},
|
|
74
|
-
"stableVersion": "0.2.
|
|
74
|
+
"stableVersion": "0.2.22"
|
|
75
75
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReplanHints, OutcomeObserver } from '@vue-skuilder/db';
|
|
1
|
+
import type { ReplanHints, OutcomeObserver, SessionStateSnapshotProvider } from '@vue-skuilder/db';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Misc. config for a study StudySessionConfig
|
|
@@ -44,6 +44,13 @@ export type StudySessionConfig = {
|
|
|
44
44
|
* session hints, request a replan). See `OutcomeObserver` in `@vue-skuilder/db`.
|
|
45
45
|
*/
|
|
46
46
|
outcomeObservers?: OutcomeObserver[];
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Thin learner-state snapshot taken at session open and close, stored on the
|
|
50
|
+
* session's `StudySessionDoc`. Keep the returned object small — counts, ids,
|
|
51
|
+
* scores. Time-boxed and error-isolated by the controller.
|
|
52
|
+
*/
|
|
53
|
+
sessionStateSnapshot?: SessionStateSnapshotProvider;
|
|
47
54
|
};
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
import confetti from 'canvas-confetti';
|
|
114
114
|
|
|
115
115
|
import { StudySessionConfig, CardTransitionPreset, CardTransitionMode } from './StudySession.types';
|
|
116
|
+
import type { SessionStateSnapshotProvider } from '@vue-skuilder/db';
|
|
116
117
|
|
|
117
118
|
interface StudyRefs {
|
|
118
119
|
shadowWrapper: HTMLDivElement;
|
|
@@ -273,6 +274,17 @@ export default defineComponent({
|
|
|
273
274
|
console.log('[StudySession] InitSession completed in created hook');
|
|
274
275
|
},
|
|
275
276
|
|
|
277
|
+
beforeUnmount() {
|
|
278
|
+
// Torn down mid-session (route change, parent v-if) = abandoned.
|
|
279
|
+
// `endSession` is idempotent, so a naturally-ended session keeps `closed`.
|
|
280
|
+
if (this.sessionController && !this.sessionFinished) {
|
|
281
|
+
void this.sessionController.endSession('abandoned');
|
|
282
|
+
}
|
|
283
|
+
if (this.intervalHandler) {
|
|
284
|
+
clearInterval(this.intervalHandler);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
|
|
276
288
|
errorCaptured(err: unknown, _instance: unknown, info: string) {
|
|
277
289
|
console.error(`[StudySession] Card render error (${info}), skipping card "${this.cardID}":`, err);
|
|
278
290
|
if (this.sessionController) {
|
|
@@ -399,6 +411,8 @@ export default defineComponent({
|
|
|
399
411
|
const scOptions: {
|
|
400
412
|
defaultBatchLimit?: number;
|
|
401
413
|
outcomeObservers?: OutcomeObserver[];
|
|
414
|
+
courseId?: string;
|
|
415
|
+
stateSnapshotProvider?: SessionStateSnapshotProvider;
|
|
402
416
|
} = {};
|
|
403
417
|
if (this.sessionConfig?.defaultBatchLimit !== undefined) {
|
|
404
418
|
scOptions.defaultBatchLimit = this.sessionConfig.defaultBatchLimit;
|
|
@@ -406,6 +420,15 @@ export default defineComponent({
|
|
|
406
420
|
if (this.sessionConfig?.outcomeObservers?.length) {
|
|
407
421
|
scOptions.outcomeObservers = this.sessionConfig.outcomeObservers;
|
|
408
422
|
}
|
|
423
|
+
// A session can span several sources; the first course-type one owns
|
|
424
|
+
// the record. Absent it, the controller skips session recording.
|
|
425
|
+
const recordingCourseId = this.contentSources.find((s) => s.type === 'course')?.id;
|
|
426
|
+
if (recordingCourseId) {
|
|
427
|
+
scOptions.courseId = recordingCourseId;
|
|
428
|
+
}
|
|
429
|
+
if (this.sessionConfig?.sessionStateSnapshot) {
|
|
430
|
+
scOptions.stateSnapshotProvider = this.sessionConfig.sessionStateSnapshot;
|
|
431
|
+
}
|
|
409
432
|
|
|
410
433
|
this.sessionController = markRaw(
|
|
411
434
|
new SessionController<ViewComponent>(
|
|
@@ -487,6 +510,9 @@ export default defineComponent({
|
|
|
487
510
|
|
|
488
511
|
r.cardID = this.cardID;
|
|
489
512
|
r.courseID = this.courseID;
|
|
513
|
+
// The single chokepoint every response passes through en route to
|
|
514
|
+
// putCardRecord.
|
|
515
|
+
r.sessionId = this.sessionController?.sessionId;
|
|
490
516
|
this.currentCard.records.push(r);
|
|
491
517
|
|
|
492
518
|
console.log(`[StudySession] StudySession.processResponse is running...`);
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
></v-text-field>
|
|
27
27
|
|
|
28
28
|
<v-snackbar v-model="badLoginAttempt" location="bottom right" :timeout="errorTimeout">
|
|
29
|
-
|
|
29
|
+
{{ errorMessage }}
|
|
30
30
|
<v-btn color="pink" variant="text" @click="badLoginAttempt = false">Close</v-btn>
|
|
31
31
|
</v-snackbar>
|
|
32
32
|
|
|
@@ -97,6 +97,15 @@ const passwordVisible = ref(false);
|
|
|
97
97
|
const awaitingResponse = ref(false);
|
|
98
98
|
const badLoginAttempt = ref(false);
|
|
99
99
|
const errorTimeout = ref(7000);
|
|
100
|
+
const errorMessage = ref('Username or password was incorrect.');
|
|
101
|
+
|
|
102
|
+
// Shown when the email→username resolver is unreachable (route missing / 5xx /
|
|
103
|
+
// network) rather than when credentials are actually wrong. Telling the two
|
|
104
|
+
// apart matters: "wrong password" sends the user to re-check what is in fact
|
|
105
|
+
// correct, and hides a real backend outage.
|
|
106
|
+
const BAD_CREDENTIALS_MSG = 'Username or password was incorrect.';
|
|
107
|
+
const LOGIN_UNAVAILABLE_MSG =
|
|
108
|
+
'Login is temporarily unavailable. Please try again in a moment.';
|
|
100
109
|
const user = ref<User | undefined>(undefined);
|
|
101
110
|
|
|
102
111
|
const loginRoute = computed(() => route.name === 'login');
|
|
@@ -106,11 +115,12 @@ const buttonStatus = computed(() => ({
|
|
|
106
115
|
text: badLoginAttempt.value ? 'Try again' : 'Log In',
|
|
107
116
|
}));
|
|
108
117
|
|
|
109
|
-
const initBadLogin = () => {
|
|
118
|
+
const initBadLogin = (message: string = BAD_CREDENTIALS_MSG) => {
|
|
119
|
+
errorMessage.value = message;
|
|
110
120
|
badLoginAttempt.value = true;
|
|
111
121
|
|
|
112
122
|
alertUser({
|
|
113
|
-
text:
|
|
123
|
+
text: message,
|
|
114
124
|
status: Status.error,
|
|
115
125
|
timeout: errorTimeout.value,
|
|
116
126
|
});
|
|
@@ -138,8 +148,16 @@ const login = async () => {
|
|
|
138
148
|
if (identifier.value.includes('@')) {
|
|
139
149
|
const resolved = await resolveLoginIdentifier(identifier.value, password.value);
|
|
140
150
|
if (!resolved.ok || !resolved.username) {
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
// 401 is the server's uniform "real auth failure" code; anything else
|
|
152
|
+
// (404 = route not deployed, 5xx, or no status = network) means the
|
|
153
|
+
// resolver is unavailable, not that the credentials were wrong.
|
|
154
|
+
if (resolved.status === 401) {
|
|
155
|
+
log('Email identifier did not resolve: invalid credentials');
|
|
156
|
+
initBadLogin();
|
|
157
|
+
} else {
|
|
158
|
+
log(`Email login unavailable (resolver error: ${resolved.error ?? 'network'})`);
|
|
159
|
+
initBadLogin(LOGIN_UNAVAILABLE_MSG);
|
|
160
|
+
}
|
|
143
161
|
awaitingResponse.value = false;
|
|
144
162
|
return;
|
|
145
163
|
}
|
package/src/services/authAPI.ts
CHANGED
|
@@ -70,6 +70,16 @@ export interface UserStatusResponse extends AuthResponse {
|
|
|
70
70
|
|
|
71
71
|
export interface ResolveLoginResponse extends AuthResponse {
|
|
72
72
|
username?: string;
|
|
73
|
+
/**
|
|
74
|
+
* HTTP status of the resolve call on failure, so callers can distinguish a
|
|
75
|
+
* genuine credential rejection from a system/service failure. The server
|
|
76
|
+
* returns a uniform 401 for every real auth failure (unknown identifier,
|
|
77
|
+
* unverified email, wrong password — no enumeration leak); anything else
|
|
78
|
+
* (404 = route not deployed, 5xx, or `undefined` for a network error) means
|
|
79
|
+
* the resolver itself is unavailable, not that the credentials were wrong.
|
|
80
|
+
* Undefined on success.
|
|
81
|
+
*/
|
|
82
|
+
status?: number;
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/**
|
|
@@ -181,12 +191,15 @@ export async function resolveLoginIdentifier(
|
|
|
181
191
|
}
|
|
182
192
|
return {
|
|
183
193
|
ok: false,
|
|
194
|
+
status: response.status,
|
|
184
195
|
error: serverError || `HTTP ${response.status}: ${response.statusText}`,
|
|
185
196
|
};
|
|
186
197
|
}
|
|
187
198
|
|
|
188
199
|
return await response.json();
|
|
189
200
|
} catch (error) {
|
|
201
|
+
// Network / fetch failure — no HTTP status. Left undefined so callers treat
|
|
202
|
+
// it as a system failure, not a credential rejection.
|
|
190
203
|
return {
|
|
191
204
|
ok: false,
|
|
192
205
|
error: error instanceof Error ? error.message : 'Network error',
|