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.
Files changed (78) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/docs/ContentOrganization.html +2 -2
  3. package/docs/Forums.html +2 -2
  4. package/docs/Gamification.html +2 -2
  5. package/docs/TestUser.html +2 -2
  6. package/docs/UserManagementSystem.html +2 -2
  7. package/docs/api_types.js.html +2 -2
  8. package/docs/config.js.html +2 -2
  9. package/docs/content-org_content-org.js.html +2 -2
  10. package/docs/content-org_guided-courses.ts.html +2 -2
  11. package/docs/content-org_learning-paths.ts.html +95 -31
  12. package/docs/content-org_playlists-types.js.html +2 -2
  13. package/docs/content-org_playlists.js.html +2 -2
  14. package/docs/content.js.html +2 -2
  15. package/docs/forums_categories.ts.html +2 -2
  16. package/docs/forums_forums.ts.html +2 -2
  17. package/docs/forums_posts.ts.html +2 -2
  18. package/docs/forums_threads.ts.html +2 -2
  19. package/docs/gamification_awards.ts.html +2 -2
  20. package/docs/gamification_gamification.js.html +2 -2
  21. package/docs/global.html +56 -8
  22. package/docs/index.html +2 -2
  23. package/docs/liveTesting.ts.html +2 -2
  24. package/docs/module-Accounts.html +191 -2
  25. package/docs/module-Awards.html +2 -2
  26. package/docs/module-Config.html +2 -2
  27. package/docs/module-Content-Services-V2.html +2 -2
  28. package/docs/module-Forums.html +2 -2
  29. package/docs/module-GuidedCourses.html +2 -2
  30. package/docs/module-Interests.html +2 -2
  31. package/docs/module-LearningPaths.html +602 -9
  32. package/docs/module-Onboarding.html +2 -2
  33. package/docs/module-Payments.html +2 -2
  34. package/docs/module-Permissions.html +2 -2
  35. package/docs/module-Playlists.html +2 -2
  36. package/docs/module-ProgressRow.html +108 -0
  37. package/docs/module-Railcontent-Services.html +2 -2
  38. package/docs/module-Sanity-Services.html +2 -2
  39. package/docs/module-Sessions.html +2 -2
  40. package/docs/module-UserActivity.html +22 -22
  41. package/docs/module-UserChat.html +2 -2
  42. package/docs/module-UserManagement.html +2 -2
  43. package/docs/module-UserMemberships.html +2 -2
  44. package/docs/module-UserNotifications.html +2 -2
  45. package/docs/module-UserProfile.html +2 -2
  46. package/docs/progress-row_method-card.js.html +182 -0
  47. package/docs/railcontent.js.html +2 -2
  48. package/docs/sanity.js.html +3 -3
  49. package/docs/userActivity.js.html +120 -65
  50. package/docs/user_account.ts.html +29 -2
  51. package/docs/user_chat.js.html +2 -2
  52. package/docs/user_interests.js.html +2 -2
  53. package/docs/user_management.js.html +2 -2
  54. package/docs/user_memberships.ts.html +2 -2
  55. package/docs/user_notifications.js.html +2 -2
  56. package/docs/user_onboarding.ts.html +2 -2
  57. package/docs/user_payments.ts.html +2 -2
  58. package/docs/user_permissions.js.html +2 -2
  59. package/docs/user_profile.js.html +2 -2
  60. package/docs/user_sessions.js.html +2 -2
  61. package/docs/user_types.js.html +4 -2
  62. package/docs/user_user-management-system.js.html +2 -2
  63. package/jsdoc.json +1 -0
  64. package/package.json +1 -1
  65. package/src/contentTypeConfig.js +18 -8
  66. package/src/index.d.ts +20 -1
  67. package/src/index.js +20 -1
  68. package/src/infrastructure/http/HttpClient.ts +5 -5
  69. package/src/services/content-org/learning-paths.ts +77 -28
  70. package/src/services/contentAggregator.js +56 -45
  71. package/src/services/contentProgress.js +7 -5
  72. package/src/services/dateUtils.js +25 -22
  73. package/src/services/progress-row/method-card.js +110 -0
  74. package/src/services/sanity.js +1 -1
  75. package/src/services/user/account.ts +27 -0
  76. package/src/services/user/types.js +2 -0
  77. package/src/services/userActivity.js +118 -63
  78. package/test/learningPaths.test.js +5 -3
@@ -1,12 +1,14 @@
1
1
  import {
2
- getLastInteractedOf, getNavigateTo,
3
- getNextLesson, getProgressDateByIds,
2
+ getLastInteractedOf,
3
+ getNavigateTo,
4
+ getNextLesson,
5
+ getProgressDateByIds,
4
6
  getProgressPercentageByIds,
5
7
  getProgressStateByIds,
6
- getResumeTimeSecondsByIds
7
- } from "./contentProgress"
8
- import {isContentLikedByIds} from "./contentLikes"
9
- import {fetchLastInteractedChild, fetchLikeCount} from "./railcontent"
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
- export async function addContextToContent(dataPromise, ...dataArgs)
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 [progressData, isLikedData, resumeTimeData, lastInteractedChildData, nextLessonData, navigateToData] = await Promise.all([
89
- addProgressPercentage || addProgressStatus || addProgressTimestamp ? getProgressDateByIds(ids) : Promise.resolve(null),
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) : Promise.resolve(null),
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 ? { nextLesson: nextLessonData?.[item.id] } : {}),
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((playlist) => allIds = [...allIds, ...playlist.items.map(a => a.content_id)])
118
- const progressOnItems = await getProgressStateByIds(allIds);
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(localContext, contentId, isCompleted, hierarchy) {
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 "dayjs";
3
- import utc from "dayjs/plugin/utc";
4
- import timezone from "dayjs/plugin/timezone";
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 "00:00:00";
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 "00:00:00";
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
+ }
@@ -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