heartraite 1.0.193 → 1.0.195
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/types/consultation.types.d.ts +19 -3
- package/dist/types/dq.types.d.ts +18 -0
- package/dist/types/request.types.d.ts +11 -2
- package/dist/utils/date/age.util.js +4 -4
- package/package.json +1 -1
- package/src/types/consultation.types.ts +19 -3
- package/src/types/dq.types.ts +19 -0
- package/src/types/request.types.ts +18 -7
- package/src/utils/date/age.util.ts +4 -4
|
@@ -7,13 +7,23 @@ import { ConsultationInterestStatus } from "../enum";
|
|
|
7
7
|
* (`Consultation`) so subsequent professionals can land here
|
|
8
8
|
* without a schema rename.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* `expertId` scopes the record to a specific professional. v1 has
|
|
11
|
+
* exactly one value (`"bohm"`); future additions (other
|
|
12
|
+
* psychologists, therapists, sex therapists, financial planners,
|
|
13
|
+
* etc.) plug in without a schema migration. Idempotency at the
|
|
14
|
+
* backend is `(userId, expertId)` — a user can express interest in
|
|
15
|
+
* multiple experts independently, but only one pending record per
|
|
16
|
+
* (user, expert) pair at any time.
|
|
17
|
+
*
|
|
18
|
+
* The slug format (e.g. `"bohm"`) reads cleanly in logs / admin
|
|
19
|
+
* dashboards. Future v2 may move expert metadata (name, photo,
|
|
20
|
+
* pricing, credentials) into DatoCMS and key entries by their CMS
|
|
21
|
+
* slug — the runtime contract stays the same string.
|
|
13
22
|
*/
|
|
14
23
|
export type ConsultationInterest = {
|
|
15
24
|
id: string;
|
|
16
25
|
userId: string;
|
|
26
|
+
expertId: string;
|
|
17
27
|
submittedAt: string;
|
|
18
28
|
status: ConsultationInterestStatus;
|
|
19
29
|
/**
|
|
@@ -35,11 +45,17 @@ export type ConsultationInterest = {
|
|
|
35
45
|
* to show the "Anmäl intresse" CTA or the post-submit "Tack — vi
|
|
36
46
|
* hör av oss" confirmation state.
|
|
37
47
|
*
|
|
48
|
+
* Scoped to a single `expertId` (the one the client asked about) —
|
|
49
|
+
* a user with interest in multiple experts gets multiple summaries
|
|
50
|
+
* via multiple calls. v1 only ever asks about `"bohm"`, so the app
|
|
51
|
+
* still has a single CTA flow.
|
|
52
|
+
*
|
|
38
53
|
* Carries just enough for the UI to render the two states; the
|
|
39
54
|
* full `ConsultationInterest` record (notes, internal status
|
|
40
55
|
* machinery) stays admin-only.
|
|
41
56
|
*/
|
|
42
57
|
export type ConsultationInterestSummary = {
|
|
58
|
+
expertId: string;
|
|
43
59
|
hasInterest: boolean;
|
|
44
60
|
submittedAt: string | null;
|
|
45
61
|
status: ConsultationInterestStatus | null;
|
package/dist/types/dq.types.d.ts
CHANGED
|
@@ -109,6 +109,24 @@ export interface GetQuestionResponse {
|
|
|
109
109
|
quotaResetsAt?: string;
|
|
110
110
|
/** Number of questions remaining for today */
|
|
111
111
|
questionsRemainingToday: number;
|
|
112
|
+
/**
|
|
113
|
+
* True when the user has answered every question currently in the
|
|
114
|
+
* DQ pool. Distinct from `quotaExhausted` (the daily 3/day cap):
|
|
115
|
+
*
|
|
116
|
+
* - `quotaExhausted: true` → user used today's 3 — come back tomorrow.
|
|
117
|
+
* - `allQuestionsCompleted: true` → user has cleared the entire pool;
|
|
118
|
+
* no more questions exist until new ones
|
|
119
|
+
* are added. Tomorrow's quota reset won't
|
|
120
|
+
* bring new questions.
|
|
121
|
+
*
|
|
122
|
+
* Optional for backward compatibility — older clients that don't
|
|
123
|
+
* read this field will continue inferring "no question available"
|
|
124
|
+
* from `question: null && !quotaExhausted`, but they'll show the
|
|
125
|
+
* generic "come back tomorrow" message which is misleading in this
|
|
126
|
+
* state. New clients should render a dedicated "all caught up"
|
|
127
|
+
* surface when this is `true`.
|
|
128
|
+
*/
|
|
129
|
+
allQuestionsCompleted?: boolean;
|
|
112
130
|
/** User's discovery progress (streaks, counts) */
|
|
113
131
|
progress?: DiscoveryProgress;
|
|
114
132
|
}
|
|
@@ -59,14 +59,23 @@ export type SubmitFeedbackRequest = {
|
|
|
59
59
|
context?: string;
|
|
60
60
|
rating?: number;
|
|
61
61
|
};
|
|
62
|
-
export type ExpressConsultationInterestRequest =
|
|
63
|
-
|
|
62
|
+
export type ExpressConsultationInterestRequest = {
|
|
63
|
+
expertId: string;
|
|
64
|
+
};
|
|
65
|
+
export type GetMyConsultationInterestRequest = {
|
|
66
|
+
expertId: string;
|
|
67
|
+
};
|
|
64
68
|
export type GetConsultationInterestsRequest = {
|
|
65
69
|
/**
|
|
66
70
|
* Optional status filter. Omit to fetch every record. Most
|
|
67
71
|
* common use is `PENDING` for the team's outreach worklist.
|
|
68
72
|
*/
|
|
69
73
|
status?: ConsultationInterestStatus;
|
|
74
|
+
/**
|
|
75
|
+
* Optional expert filter. Omit for the cross-expert worklist;
|
|
76
|
+
* pass to scope a single professional's outreach queue.
|
|
77
|
+
*/
|
|
78
|
+
expertId?: string;
|
|
70
79
|
};
|
|
71
80
|
export type UpdateConsultationInterestRequest = {
|
|
72
81
|
id: string;
|
|
@@ -4,9 +4,9 @@ exports.calculateAgeFromDateString = calculateAgeFromDateString;
|
|
|
4
4
|
function calculateAgeFromDateString(isoDateString) {
|
|
5
5
|
const birthDate = new Date(isoDateString);
|
|
6
6
|
const today = new Date();
|
|
7
|
-
const ageDiff = today.
|
|
8
|
-
const hasBirthdayPassed = today.
|
|
9
|
-
(today.
|
|
10
|
-
today.
|
|
7
|
+
const ageDiff = today.getUTCFullYear() - birthDate.getUTCFullYear();
|
|
8
|
+
const hasBirthdayPassed = today.getUTCMonth() > birthDate.getUTCMonth() ||
|
|
9
|
+
(today.getUTCMonth() === birthDate.getUTCMonth() &&
|
|
10
|
+
today.getUTCDate() >= birthDate.getUTCDate());
|
|
11
11
|
return hasBirthdayPassed ? ageDiff : ageDiff - 1;
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -8,13 +8,23 @@ import { ConsultationInterestStatus } from "../enum";
|
|
|
8
8
|
* (`Consultation`) so subsequent professionals can land here
|
|
9
9
|
* without a schema rename.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* `expertId` scopes the record to a specific professional. v1 has
|
|
12
|
+
* exactly one value (`"bohm"`); future additions (other
|
|
13
|
+
* psychologists, therapists, sex therapists, financial planners,
|
|
14
|
+
* etc.) plug in without a schema migration. Idempotency at the
|
|
15
|
+
* backend is `(userId, expertId)` — a user can express interest in
|
|
16
|
+
* multiple experts independently, but only one pending record per
|
|
17
|
+
* (user, expert) pair at any time.
|
|
18
|
+
*
|
|
19
|
+
* The slug format (e.g. `"bohm"`) reads cleanly in logs / admin
|
|
20
|
+
* dashboards. Future v2 may move expert metadata (name, photo,
|
|
21
|
+
* pricing, credentials) into DatoCMS and key entries by their CMS
|
|
22
|
+
* slug — the runtime contract stays the same string.
|
|
14
23
|
*/
|
|
15
24
|
export type ConsultationInterest = {
|
|
16
25
|
id: string;
|
|
17
26
|
userId: string;
|
|
27
|
+
expertId: string;
|
|
18
28
|
submittedAt: string;
|
|
19
29
|
status: ConsultationInterestStatus;
|
|
20
30
|
/**
|
|
@@ -37,11 +47,17 @@ export type ConsultationInterest = {
|
|
|
37
47
|
* to show the "Anmäl intresse" CTA or the post-submit "Tack — vi
|
|
38
48
|
* hör av oss" confirmation state.
|
|
39
49
|
*
|
|
50
|
+
* Scoped to a single `expertId` (the one the client asked about) —
|
|
51
|
+
* a user with interest in multiple experts gets multiple summaries
|
|
52
|
+
* via multiple calls. v1 only ever asks about `"bohm"`, so the app
|
|
53
|
+
* still has a single CTA flow.
|
|
54
|
+
*
|
|
40
55
|
* Carries just enough for the UI to render the two states; the
|
|
41
56
|
* full `ConsultationInterest` record (notes, internal status
|
|
42
57
|
* machinery) stays admin-only.
|
|
43
58
|
*/
|
|
44
59
|
export type ConsultationInterestSummary = {
|
|
60
|
+
expertId: string;
|
|
45
61
|
hasInterest: boolean;
|
|
46
62
|
submittedAt: string | null;
|
|
47
63
|
status: ConsultationInterestStatus | null;
|
package/src/types/dq.types.ts
CHANGED
|
@@ -149,6 +149,25 @@ export interface GetQuestionResponse {
|
|
|
149
149
|
/** Number of questions remaining for today */
|
|
150
150
|
questionsRemainingToday: number;
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* True when the user has answered every question currently in the
|
|
154
|
+
* DQ pool. Distinct from `quotaExhausted` (the daily 3/day cap):
|
|
155
|
+
*
|
|
156
|
+
* - `quotaExhausted: true` → user used today's 3 — come back tomorrow.
|
|
157
|
+
* - `allQuestionsCompleted: true` → user has cleared the entire pool;
|
|
158
|
+
* no more questions exist until new ones
|
|
159
|
+
* are added. Tomorrow's quota reset won't
|
|
160
|
+
* bring new questions.
|
|
161
|
+
*
|
|
162
|
+
* Optional for backward compatibility — older clients that don't
|
|
163
|
+
* read this field will continue inferring "no question available"
|
|
164
|
+
* from `question: null && !quotaExhausted`, but they'll show the
|
|
165
|
+
* generic "come back tomorrow" message which is misleading in this
|
|
166
|
+
* state. New clients should render a dedicated "all caught up"
|
|
167
|
+
* surface when this is `true`.
|
|
168
|
+
*/
|
|
169
|
+
allQuestionsCompleted?: boolean;
|
|
170
|
+
|
|
152
171
|
/** User's discovery progress (streaks, counts) */
|
|
153
172
|
progress?: DiscoveryProgress;
|
|
154
173
|
}
|
|
@@ -90,14 +90,20 @@ export type SubmitFeedbackRequest = {
|
|
|
90
90
|
|
|
91
91
|
// consultation — 1:1 video sessions with credentialed professionals
|
|
92
92
|
//
|
|
93
|
-
// v1 endpoints are
|
|
94
|
-
// my-interest) and AdminGuard'd from the
|
|
95
|
-
// + admin-update-interest).
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
93
|
+
// v1 endpoints are CAGuard'd from the user-facing side (express +
|
|
94
|
+
// my-interest, gated on completed CA) and AdminGuard'd from the
|
|
95
|
+
// team side (admin-interests + admin-update-interest). userId
|
|
96
|
+
// always comes from the authed token, never the client. `expertId`
|
|
97
|
+
// scopes the request to a specific professional — v1 only sends
|
|
98
|
+
// `"bohm"` but the field is required so the schema is stable when
|
|
99
|
+
// future experts land.
|
|
100
|
+
export type ExpressConsultationInterestRequest = {
|
|
101
|
+
expertId: string;
|
|
102
|
+
};
|
|
99
103
|
|
|
100
|
-
export type GetMyConsultationInterestRequest =
|
|
104
|
+
export type GetMyConsultationInterestRequest = {
|
|
105
|
+
expertId: string;
|
|
106
|
+
};
|
|
101
107
|
|
|
102
108
|
export type GetConsultationInterestsRequest = {
|
|
103
109
|
/**
|
|
@@ -105,6 +111,11 @@ export type GetConsultationInterestsRequest = {
|
|
|
105
111
|
* common use is `PENDING` for the team's outreach worklist.
|
|
106
112
|
*/
|
|
107
113
|
status?: ConsultationInterestStatus;
|
|
114
|
+
/**
|
|
115
|
+
* Optional expert filter. Omit for the cross-expert worklist;
|
|
116
|
+
* pass to scope a single professional's outreach queue.
|
|
117
|
+
*/
|
|
118
|
+
expertId?: string;
|
|
108
119
|
};
|
|
109
120
|
|
|
110
121
|
export type UpdateConsultationInterestRequest = {
|
|
@@ -2,11 +2,11 @@ export function calculateAgeFromDateString(isoDateString: string): number {
|
|
|
2
2
|
const birthDate = new Date(isoDateString);
|
|
3
3
|
const today = new Date();
|
|
4
4
|
|
|
5
|
-
const ageDiff = today.
|
|
5
|
+
const ageDiff = today.getUTCFullYear() - birthDate.getUTCFullYear();
|
|
6
6
|
const hasBirthdayPassed =
|
|
7
|
-
today.
|
|
8
|
-
(today.
|
|
9
|
-
today.
|
|
7
|
+
today.getUTCMonth() > birthDate.getUTCMonth() ||
|
|
8
|
+
(today.getUTCMonth() === birthDate.getUTCMonth() &&
|
|
9
|
+
today.getUTCDate() >= birthDate.getUTCDate());
|
|
10
10
|
|
|
11
11
|
return hasBirthdayPassed ? ageDiff : ageDiff - 1;
|
|
12
12
|
}
|