musora-content-services 2.74.1 → 2.76.0
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/CHANGELOG.md +22 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +95 -31
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +2 -2
- package/docs/forums_categories.ts.html +2 -2
- package/docs/forums_forums.ts.html +2 -2
- package/docs/forums_posts.ts.html +2 -2
- package/docs/forums_threads.ts.html +2 -2
- package/docs/gamification_awards.ts.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/global.html +56 -8
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +191 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Forums.html +2 -2
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +602 -9
- package/docs/module-Onboarding.html +2 -2
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-ProgressRow.html +108 -0
- package/docs/module-Railcontent-Services.html +2 -2
- package/docs/module-Sanity-Services.html +2 -2
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +22 -22
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/progress-row_method-card.js.html +182 -0
- package/docs/railcontent.js.html +2 -2
- package/docs/sanity.js.html +3 -3
- package/docs/userActivity.js.html +120 -65
- package/docs/user_account.ts.html +29 -2
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +2 -2
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +4 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/jsdoc.json +1 -0
- package/package.json +1 -1
- package/src/contentTypeConfig.js +18 -8
- package/src/index.d.ts +20 -1
- package/src/index.js +20 -1
- package/src/infrastructure/http/HttpClient.ts +5 -5
- package/src/services/content-org/learning-paths.ts +77 -28
- package/src/services/contentAggregator.js +56 -45
- package/src/services/contentProgress.js +7 -5
- package/src/services/dateUtils.js +25 -22
- package/src/services/progress-row/method-card.js +110 -0
- package/src/services/sanity.js +1 -1
- package/src/services/user/account.ts +27 -0
- package/src/services/user/types.js +2 -0
- package/src/services/userActivity.js +118 -63
- package/test/learningPaths.test.js +5 -3
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getLastInteractedOf,
|
|
3
|
-
|
|
2
|
+
getLastInteractedOf,
|
|
3
|
+
getNavigateTo,
|
|
4
|
+
getNextLesson,
|
|
5
|
+
getProgressDateByIds,
|
|
4
6
|
getProgressPercentageByIds,
|
|
5
7
|
getProgressStateByIds,
|
|
6
|
-
getResumeTimeSecondsByIds
|
|
7
|
-
} from
|
|
8
|
-
import {isContentLikedByIds} from
|
|
9
|
-
import {fetchLastInteractedChild, fetchLikeCount} from
|
|
8
|
+
getResumeTimeSecondsByIds,
|
|
9
|
+
} from './contentProgress'
|
|
10
|
+
import { isContentLikedByIds } from './contentLikes'
|
|
11
|
+
import { fetchLastInteractedChild, fetchLikeCount } from './railcontent'
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Combine sanity data with BE contextual data.
|
|
@@ -55,8 +57,9 @@ import {fetchLastInteractedChild, fetchLikeCount} from "./railcontent"
|
|
|
55
57
|
*
|
|
56
58
|
*/
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
// need to add method support.
|
|
61
|
+
// this means returning collection_type and collection_id
|
|
62
|
+
export async function addContextToContent(dataPromise, ...dataArgs) {
|
|
60
63
|
const lastArg = dataArgs[dataArgs.length - 1]
|
|
61
64
|
const options = typeof lastArg === 'object' && !Array.isArray(lastArg) ? lastArg : {}
|
|
62
65
|
|
|
@@ -78,18 +81,27 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
78
81
|
|
|
79
82
|
let data = await dataPromise(...dataParam)
|
|
80
83
|
const isDataAnArray = Array.isArray(data)
|
|
81
|
-
if(isDataAnArray && data.length === 0) return data
|
|
82
|
-
if(!data) return false
|
|
84
|
+
if (isDataAnArray && data.length === 0) return data
|
|
85
|
+
if (!data) return false
|
|
83
86
|
|
|
84
87
|
const items = extractItemsFromData(data, dataField, isDataAnArray, dataField_includeParent) ?? []
|
|
85
|
-
const ids = items.map(item => item?.id).filter(Boolean)
|
|
86
|
-
if(ids.length === 0) return data
|
|
88
|
+
const ids = items.map((item) => item?.id).filter(Boolean)
|
|
89
|
+
if (ids.length === 0) return data
|
|
87
90
|
|
|
88
|
-
const [
|
|
89
|
-
|
|
91
|
+
const [
|
|
92
|
+
progressData,
|
|
93
|
+
isLikedData,
|
|
94
|
+
resumeTimeData,
|
|
95
|
+
lastInteractedChildData,
|
|
96
|
+
nextLessonData,
|
|
97
|
+
navigateToData,
|
|
98
|
+
] = await Promise.all([
|
|
99
|
+
addProgressPercentage || addProgressStatus || addProgressTimestamp
|
|
100
|
+
? getProgressDateByIds(ids)
|
|
101
|
+
: Promise.resolve(null),
|
|
90
102
|
addIsLiked ? isContentLikedByIds(ids) : Promise.resolve(null),
|
|
91
103
|
addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids) : Promise.resolve(null),
|
|
92
|
-
addLastInteractedChild ? fetchLastInteractedChild(ids)
|
|
104
|
+
addLastInteractedChild ? fetchLastInteractedChild(ids) : Promise.resolve(null),
|
|
93
105
|
addNextLesson ? getNextLesson(items) : Promise.resolve(null),
|
|
94
106
|
addNavigateTo ? getNavigateTo(items) : Promise.resolve(null),
|
|
95
107
|
])
|
|
@@ -103,33 +115,40 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
103
115
|
...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(item.id) } : {}),
|
|
104
116
|
...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[item.id] } : {}),
|
|
105
117
|
...(addLastInteractedChild ? { lastInteractedChild: lastInteractedChildData?.[item.id] } : {}),
|
|
106
|
-
...(addNextLesson
|
|
118
|
+
...(addNextLesson
|
|
119
|
+
? {
|
|
120
|
+
nextLesson: nextLessonData?.[item.id], //deprecated
|
|
121
|
+
next_lesson_id: nextLessonData?.[item.id],
|
|
122
|
+
next_lesson: item?.children?.find((child) => child.id === nextLessonData?.[item.id]),
|
|
123
|
+
}
|
|
124
|
+
: {}),
|
|
107
125
|
...(addNavigateTo ? { navigateTo: navigateToData?.[item.id] } : {}),
|
|
108
126
|
})
|
|
109
127
|
|
|
110
128
|
return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
export async function getNavigateToForPlaylists(data, {dataField = null} = {}
|
|
114
|
-
{
|
|
131
|
+
export async function getNavigateToForPlaylists(data, { dataField = null } = {}) {
|
|
115
132
|
let playlists = extractItemsFromData(data, dataField, false, false)
|
|
116
133
|
let allIds = []
|
|
117
|
-
playlists.forEach(
|
|
118
|
-
|
|
134
|
+
playlists.forEach(
|
|
135
|
+
(playlist) => (allIds = [...allIds, ...playlist.items.map((a) => a.content_id)])
|
|
136
|
+
)
|
|
137
|
+
const progressOnItems = await getProgressStateByIds(allIds)
|
|
119
138
|
const addContext = async (playlist) => {
|
|
120
|
-
const allItemsCompleted = playlist.items.every(i => {
|
|
121
|
-
const itemId = i.content_id
|
|
122
|
-
const progress = progressOnItems[itemId]
|
|
123
|
-
return progress && progress === 'completed'
|
|
124
|
-
})
|
|
125
|
-
let nextItem = playlist.items[0] ?? null
|
|
139
|
+
const allItemsCompleted = playlist.items.every((i) => {
|
|
140
|
+
const itemId = i.content_id
|
|
141
|
+
const progress = progressOnItems[itemId]
|
|
142
|
+
return progress && progress === 'completed'
|
|
143
|
+
})
|
|
144
|
+
let nextItem = playlist.items[0] ?? null
|
|
126
145
|
if (!allItemsCompleted) {
|
|
127
|
-
const lastItemProgress = progressOnItems[playlist.last_engaged_on]
|
|
128
|
-
const index = playlist.items.findIndex(i => i.content_id === playlist.last_engaged_on)
|
|
146
|
+
const lastItemProgress = progressOnItems[playlist.last_engaged_on]
|
|
147
|
+
const index = playlist.items.findIndex((i) => i.content_id === playlist.last_engaged_on)
|
|
129
148
|
if (lastItemProgress === 'completed') {
|
|
130
|
-
nextItem = playlist.items[index + 1] ?? nextItem
|
|
149
|
+
nextItem = playlist.items[index + 1] ?? nextItem
|
|
131
150
|
} else {
|
|
132
|
-
nextItem = playlist.items[index] ?? nextItem
|
|
151
|
+
nextItem = playlist.items[index] ?? nextItem
|
|
133
152
|
}
|
|
134
153
|
}
|
|
135
154
|
playlist.navigateTo = {
|
|
@@ -138,11 +157,10 @@ export async function getNavigateToForPlaylists(data, {dataField = null} = {} )
|
|
|
138
157
|
}
|
|
139
158
|
return playlist
|
|
140
159
|
}
|
|
141
|
-
return await processItems(data, addContext, dataField, false, false
|
|
160
|
+
return await processItems(data, addContext, dataField, false, false)
|
|
142
161
|
}
|
|
143
162
|
|
|
144
|
-
function extractItemsFromData(data, dataField, isParentArray, includeParent)
|
|
145
|
-
{
|
|
163
|
+
function extractItemsFromData(data, dataField, isParentArray, includeParent) {
|
|
146
164
|
let items = []
|
|
147
165
|
if (dataField) {
|
|
148
166
|
if (isParentArray) {
|
|
@@ -162,18 +180,17 @@ function extractItemsFromData(data, dataField, isParentArray, includeParent)
|
|
|
162
180
|
}
|
|
163
181
|
}
|
|
164
182
|
} else if (Array.isArray(data)) {
|
|
165
|
-
items = data
|
|
183
|
+
items = data
|
|
166
184
|
} else if (data?.id) {
|
|
167
185
|
items = [data]
|
|
168
186
|
}
|
|
169
187
|
return items
|
|
170
188
|
}
|
|
171
189
|
|
|
172
|
-
async function processItems(data, addContext, dataField, isParentArray, includeParent)
|
|
173
|
-
{
|
|
190
|
+
async function processItems(data, addContext, dataField, isParentArray, includeParent) {
|
|
174
191
|
if (dataField) {
|
|
175
192
|
if (isParentArray) {
|
|
176
|
-
for(let parent of data) {
|
|
193
|
+
for (let parent of data) {
|
|
177
194
|
parent[dataField] = Array.isArray(parent[dataField])
|
|
178
195
|
? await Promise.all(parent[dataField].map(addContext))
|
|
179
196
|
: await addContext(parent[dataField])
|
|
@@ -184,16 +201,10 @@ async function processItems(data, addContext, dataField, isParentArray, includeP
|
|
|
184
201
|
: await addContext(data[dataField])
|
|
185
202
|
}
|
|
186
203
|
if (includeParent) {
|
|
187
|
-
data = isParentArray
|
|
188
|
-
? await Promise.all(data.map(addContext))
|
|
189
|
-
: await addContext(data)
|
|
204
|
+
data = isParentArray ? await Promise.all(data.map(addContext)) : await addContext(data)
|
|
190
205
|
}
|
|
191
206
|
return data
|
|
192
207
|
} else {
|
|
193
|
-
return Array.isArray(data)
|
|
194
|
-
? await Promise.all(data.map(addContext))
|
|
195
|
-
: await addContext(data)
|
|
208
|
+
return Array.isArray(data) ? await Promise.all(data.map(addContext)) : await addContext(data)
|
|
196
209
|
}
|
|
197
210
|
}
|
|
198
|
-
|
|
199
|
-
|
|
@@ -117,10 +117,7 @@ export async function getNavigateTo(data) {
|
|
|
117
117
|
const firstChild = content.children[0]
|
|
118
118
|
let lastInteractedChildNavToData = await getNavigateTo([firstChild])
|
|
119
119
|
lastInteractedChildNavToData = lastInteractedChildNavToData[firstChild.id] ?? null
|
|
120
|
-
navigateToData[content.id] = buildNavigateTo(
|
|
121
|
-
firstChild,
|
|
122
|
-
lastInteractedChildNavToData
|
|
123
|
-
)
|
|
120
|
+
navigateToData[content.id] = buildNavigateTo(firstChild, lastInteractedChildNavToData)
|
|
124
121
|
} else {
|
|
125
122
|
const childrenStates = await getProgressStateByIds(childrenIds)
|
|
126
123
|
const lastInteracted = await getLastInteractedOf(childrenIds)
|
|
@@ -400,7 +397,12 @@ function startStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
|
400
397
|
setStartedOrCompletedStatusInLocalContext(localContext, contentId, false, hierarchy)
|
|
401
398
|
}
|
|
402
399
|
|
|
403
|
-
function setStartedOrCompletedStatusInLocalContext(
|
|
400
|
+
function setStartedOrCompletedStatusInLocalContext(
|
|
401
|
+
localContext,
|
|
402
|
+
contentId,
|
|
403
|
+
isCompleted,
|
|
404
|
+
hierarchy
|
|
405
|
+
) {
|
|
404
406
|
let data = localContext.data[contentId] ?? {}
|
|
405
407
|
data[DATA_KEY_PROGRESS] = isCompleted ? 100 : 0
|
|
406
408
|
data[DATA_KEY_STATUS] = isCompleted ? STATE_COMPLETED : STATE_STARTED
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// dateUtils.js
|
|
2
|
-
import dayjs from
|
|
3
|
-
import utc from
|
|
4
|
-
import timezone from
|
|
2
|
+
import dayjs from 'dayjs'
|
|
3
|
+
import utc from 'dayjs/plugin/utc'
|
|
4
|
+
import timezone from 'dayjs/plugin/timezone'
|
|
5
5
|
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
|
|
6
|
-
import isoWeek from 'dayjs/plugin/isoWeek'
|
|
6
|
+
import isoWeek from 'dayjs/plugin/isoWeek'
|
|
7
7
|
import isBetween from 'dayjs/plugin/isBetween'
|
|
8
8
|
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
|
|
9
9
|
|
|
10
|
-
dayjs.extend(utc)
|
|
11
|
-
dayjs.extend(timezone)
|
|
10
|
+
dayjs.extend(utc)
|
|
11
|
+
dayjs.extend(timezone)
|
|
12
12
|
dayjs.extend(isSameOrAfter)
|
|
13
13
|
dayjs.extend(isoWeek)
|
|
14
14
|
dayjs.extend(isBetween)
|
|
@@ -19,7 +19,7 @@ export function toDayjs(date, timeZone = 'UTC') {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function convertToTimeZone(date, timeZone) {
|
|
22
|
-
return dayjs(date).tz(timeZone).format('YYYY-MM-DD')
|
|
22
|
+
return dayjs(date).tz(timeZone).format('YYYY-MM-DD')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function getMonday(date, timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone) {
|
|
@@ -42,32 +42,35 @@ export function isNextDay(date1, date2) {
|
|
|
42
42
|
const d2 = dayjs(date2).startOf('day')
|
|
43
43
|
return d2.diff(d1, 'day') === 1
|
|
44
44
|
}
|
|
45
|
-
export function getTimeRemainingUntilLocal(targetUtcIsoString, {withTotalSeconds} = {}) {
|
|
46
|
-
const targetUTC = new Date(targetUtcIsoString)
|
|
45
|
+
export function getTimeRemainingUntilLocal(targetUtcIsoString, { withTotalSeconds } = {}) {
|
|
46
|
+
const targetUTC = new Date(targetUtcIsoString)
|
|
47
47
|
if (isNaN(targetUTC.getTime())) {
|
|
48
|
-
return
|
|
48
|
+
return '00:00:00'
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const now = new Date()
|
|
52
|
-
const diff = targetUTC.getTime() - now.getTime()
|
|
51
|
+
const now = new Date()
|
|
52
|
+
const diff = targetUTC.getTime() - now.getTime()
|
|
53
53
|
|
|
54
54
|
if (diff <= 0) {
|
|
55
|
-
return
|
|
55
|
+
return '00:00:00'
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const totalSeconds = Math.floor(diff / 1000)
|
|
59
|
-
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
|
|
60
|
-
const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
|
|
61
|
-
const seconds = String(totalSeconds % 60).padStart(2, '0')
|
|
62
|
-
if(withTotalSeconds) {
|
|
58
|
+
const totalSeconds = Math.floor(diff / 1000)
|
|
59
|
+
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
|
|
60
|
+
const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
|
|
61
|
+
const seconds = String(totalSeconds % 60).padStart(2, '0')
|
|
62
|
+
if (withTotalSeconds) {
|
|
63
63
|
return {
|
|
64
64
|
totalSeconds,
|
|
65
|
-
formatted: `${hours}:${minutes}:${seconds}
|
|
65
|
+
formatted: `${hours}:${minutes}:${seconds}`,
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
return `${hours}:${minutes}:${seconds}
|
|
69
|
+
return `${hours}:${minutes}:${seconds}`
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
export function getToday() {
|
|
73
|
+
const now = dayjs()
|
|
74
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
75
|
+
return dayjs().tz(timeZone).startOf('day')
|
|
76
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module ProgressRow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getDailySession,
|
|
7
|
+
getActivePath,
|
|
8
|
+
resetAllLearningPaths,
|
|
9
|
+
startLearningPath,
|
|
10
|
+
fetchLearningPathLessons,
|
|
11
|
+
} from '../content-org/learning-paths'
|
|
12
|
+
import { getToday } from '../dateUtils.js'
|
|
13
|
+
import { fetchByRailContentId, fetchByRailContentIds, fetchMethodV2IntroVideo } from '../sanity'
|
|
14
|
+
import { addContextToContent } from '../contentAggregator.js'
|
|
15
|
+
import { getProgressState } from '../contentProgress'
|
|
16
|
+
|
|
17
|
+
export async function getMethodCard(brand) {
|
|
18
|
+
const introVideo = await fetchMethodV2IntroVideo(brand)
|
|
19
|
+
const introVideoProgressState = await getProgressState(introVideo.id)
|
|
20
|
+
//resetAllLearningPaths()
|
|
21
|
+
if (introVideoProgressState != 'completed') {
|
|
22
|
+
//startLearningPath('drumeo', 422533)
|
|
23
|
+
const timestamp = Math.floor(Date.now() / 1000)
|
|
24
|
+
return {
|
|
25
|
+
id: 0, // method card has no id
|
|
26
|
+
type: 'method',
|
|
27
|
+
header: 'Method',
|
|
28
|
+
body: {
|
|
29
|
+
thumbnail: introVideo.thumbnail,
|
|
30
|
+
title: introVideo.title,
|
|
31
|
+
subtitle: `${introVideo.difficulty_string} • ${introVideo.artist_name}`,
|
|
32
|
+
},
|
|
33
|
+
cta: {
|
|
34
|
+
text: 'Get Started',
|
|
35
|
+
action: getMethodActionCTA(introVideo),
|
|
36
|
+
},
|
|
37
|
+
progressTimestamp: timestamp,
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
//TODO: Optimize loading of dailySessions/Path, should not need multiple requests
|
|
41
|
+
const activeLearningPath = await getActivePath(brand)
|
|
42
|
+
const learningPath = await fetchLearningPathLessons(
|
|
43
|
+
activeLearningPath.active_learning_path_id,
|
|
44
|
+
brand,
|
|
45
|
+
getToday()
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const allCompleted = learningPath?.todays_lessons.every(
|
|
49
|
+
(lesson) => lesson.progressStatus === 'completed'
|
|
50
|
+
)
|
|
51
|
+
const anyCompleted = learningPath?.todays_lessons.some(
|
|
52
|
+
(lesson) => lesson.progressStatus === 'completed'
|
|
53
|
+
)
|
|
54
|
+
const noneCompleted = learningPath?.todays_lessons.every(
|
|
55
|
+
(lesson) => lesson.progressStatus !== 'completed'
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const nextIncompleteLesson = learningPath?.todays_lessons.find(
|
|
59
|
+
(lesson) => lesson.progressStatus !== 'completed'
|
|
60
|
+
)
|
|
61
|
+
let ctaText,
|
|
62
|
+
action,
|
|
63
|
+
nextLesson = null
|
|
64
|
+
if (noneCompleted) {
|
|
65
|
+
ctaText = 'Start Session'
|
|
66
|
+
action = getMethodActionCTA(nextIncompleteLesson)
|
|
67
|
+
} else if (anyCompleted && !allCompleted) {
|
|
68
|
+
ctaText = 'Continue Session'
|
|
69
|
+
action = getMethodActionCTA(nextIncompleteLesson)
|
|
70
|
+
} else if (allCompleted) {
|
|
71
|
+
ctaText = learningPath.next_lesson ? 'Start Next Lesson' : 'Browse Lessons'
|
|
72
|
+
action = learningPath.next_lesson
|
|
73
|
+
? getMethodActionCTA(learningPath.next_lesson)
|
|
74
|
+
: {
|
|
75
|
+
type: 'lessons',
|
|
76
|
+
brand: brand,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let maxProgressTimestamp = Math.max(
|
|
81
|
+
...learningPath?.children.map((lesson) => lesson.progressTimestamp)
|
|
82
|
+
)
|
|
83
|
+
if (!maxProgressTimestamp) {
|
|
84
|
+
maxProgressTimestamp = learningPath.active_learning_path_created_at
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: 0,
|
|
89
|
+
type: 'learning-path-v2',
|
|
90
|
+
progressType: 'content',
|
|
91
|
+
header: 'Method',
|
|
92
|
+
body: learningPath,
|
|
93
|
+
cta: {
|
|
94
|
+
text: ctaText,
|
|
95
|
+
action: action,
|
|
96
|
+
},
|
|
97
|
+
// *1000 is to match playlists which are saved in millisecond accuracy
|
|
98
|
+
progressTimestamp: maxProgressTimestamp * 1000,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getMethodActionCTA(item) {
|
|
104
|
+
return {
|
|
105
|
+
type: item.type,
|
|
106
|
+
brand: item.brand,
|
|
107
|
+
id: item.id,
|
|
108
|
+
slug: item.slug,
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/services/sanity.js
CHANGED
|
@@ -2299,7 +2299,7 @@ export async function fetchShows(brand, type, sort = 'sort') {
|
|
|
2299
2299
|
export async function fetchMethodV2IntroVideo(brand) {
|
|
2300
2300
|
const type = "method-intro";
|
|
2301
2301
|
const filter = `_type == '${type}' && brand == '${brand}'`;
|
|
2302
|
-
const fields = getIntroVideoFields();
|
|
2302
|
+
const fields = getIntroVideoFields('method');
|
|
2303
2303
|
|
|
2304
2304
|
const query = `*[${filter}] { ${fields.join(", ")} }`;
|
|
2305
2305
|
return fetchSanity(query, false);
|
|
@@ -163,3 +163,30 @@ export async function numberOfActiveUsers(): Promise<number> {
|
|
|
163
163
|
const response = await httpClient.get<{ active_users: number }>(apiUrl)
|
|
164
164
|
return response.active_users
|
|
165
165
|
}
|
|
166
|
+
|
|
167
|
+
export interface UserResource {
|
|
168
|
+
id: number
|
|
169
|
+
email: string
|
|
170
|
+
display_name: string
|
|
171
|
+
first_name: string
|
|
172
|
+
last_name: string
|
|
173
|
+
permission_level: string
|
|
174
|
+
use_student_view: boolean
|
|
175
|
+
is_admin: boolean
|
|
176
|
+
show_admin_toggle: boolean
|
|
177
|
+
[key: string]: any // Allow additional properties from the API
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Toggles the student view mode for admin users.
|
|
182
|
+
* When enabled, admins see the platform as a regular student would.
|
|
183
|
+
*
|
|
184
|
+
* @param {boolean} useStudentView - Whether to enable student view mode (true) or admin view mode (false).
|
|
185
|
+
* @returns {Promise<UserResource>} - A promise that resolves to the updated user resource.
|
|
186
|
+
* @throws {HttpError} - Throws HttpError if the request fails or user is not an admin.
|
|
187
|
+
*/
|
|
188
|
+
export async function toggleStudentView(useStudentView: boolean): Promise<UserResource> {
|
|
189
|
+
const apiUrl = `/api/user-management-system/v1/user/student-view`
|
|
190
|
+
const httpClient = new HttpClient(globalConfig.baseUrl, globalConfig.sessionConfig.token)
|
|
191
|
+
return httpClient.patch<UserResource>(apiUrl, { use_student_view: useStudentView })
|
|
192
|
+
}
|
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
* @property {number} send_mobile_app_push_notifications
|
|
74
74
|
* @property {number} send_email_notifications
|
|
75
75
|
* @property {number} use_legacy_video_player
|
|
76
|
+
* @property {boolean} use_student_view
|
|
77
|
+
* @property {boolean} show_admin_toggle
|
|
76
78
|
* @property {number} drumeo_ship_magazine
|
|
77
79
|
* @property {string|null} magazine_shipping_address_id
|
|
78
80
|
* @property {string|null} ios_latest_review_display_date
|