@crimson-education/sdk 0.2.5 → 0.3.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/README.md CHANGED
@@ -231,6 +231,100 @@ SDK 会将后端返回的 action item 做归一化,保证至少返回以下核
231
231
  - `getMissionById(missionId: string): Promise<MissionDetail | null>`
232
232
  - 注意:当后端返回非 2xx(例如 404)时,SDK 会抛异常;建议使用 `try/catch` 处理。
233
233
 
234
+ ### 7) AccountApi(`client.account`)
235
+
236
+ 涉及接口:
237
+
238
+ - `GET /api/v1/account/me/roles`
239
+ - `GET /api/v1/account/me/students`
240
+ - `GET /api/v1/account/me/profile`
241
+
242
+ 主要方法:
243
+
244
+ - `getCurrentUserRoles(): Promise<CurrentUserRoles>`
245
+ - 获取当前用户的角色列表
246
+ - 返回 `{ userId, roles: [{ roleId, isPrimary }] }`
247
+ - `getMyStudents(): Promise<StudentSummary[]>`
248
+ - 获取当前用户关联的学生列表(主要用于 Staff 用户)
249
+ - 返回学生基本信息:`userId`, `firstName`, `lastName`, `email`, `profileImageUrl`
250
+ - `getMyProfile(): Promise<UserProfile>`
251
+ - 获取当前用户的完整 Profile 信息
252
+ - 返回字段:
253
+ - `userId`, `email`, `firstName`, `lastName`
254
+ - `nickname` - 首选名称
255
+ - `name` - 完整显示名
256
+ - `picture` - 头像 URL
257
+ - `status` - 用户状态
258
+ - `isMultiTenant` - 是否多租户用户
259
+ - `roles[]` - 角色数组(支持多角色),每个包含 `roleId`, `roleName`, `isPrimary`
260
+ - `tenant` - 租户信息 `{ id, name }`
261
+
262
+ ### 8) IndigoApi(`client.indigo`)
263
+
264
+ Indigo 专用 API,提供用户身份识别和学生-Tutor 关系查询功能。
265
+
266
+ 涉及接口:
267
+
268
+ - `GET /api/v1/indigo/me`
269
+ - `GET /api/v1/indigo/student/tutors`
270
+ - `GET /api/v1/indigo/tutor/students`
271
+
272
+ > **注意**:所有 Indigo API 需要用户拥有 INDIGO 产品订阅才能访问。
273
+
274
+ 主要方法:
275
+
276
+ - `getMe(): Promise<IndigoMe>`
277
+ - 获取当前用户的 Indigo 身份信息
278
+ - 返回字段:
279
+ - `userId`, `email`, `firstName`, `lastName`
280
+ - `role` - 用户角色:`"student"` 或 `"tutor"`
281
+ - `tenantId`, `tenantName` - 主租户信息
282
+ - `isMultiTenant` - 是否跨租户用户
283
+ - `relatedAccounts[]` - 关联账户列表(多租户场景)
284
+ - `getStudentTutors(): Promise<IndigoTutor[]>`
285
+ - 获取当前学生的 Tutor 列表
286
+ - 支持跨租户聚合(多租户学生可看到所有租户的 Tutor)
287
+ - 返回字段:`userId`, `email`, `firstName`, `lastName`, `profileImageUrl`, `contractId`, `contractStatus`, `source`
288
+ - `getTutorStudents(): Promise<IndigoStudent[]>`
289
+ - 获取当前 Tutor 的学生列表
290
+ - 支持跨租户聚合(多租户 Tutor 可看到所有租户的学生)
291
+ - 返回字段:`userId`, `email`, `firstName`, `lastName`, `profileImageUrl`, `contractId`, `contractStatus`, `source`
292
+
293
+ 示例:
294
+
295
+ ```ts
296
+ const client = createCrimsonClient({
297
+ apiUrl: "https://api.crimson.io",
298
+ clientId: "indigo-website",
299
+ getToken: () => localStorage.getItem("token") || "",
300
+ });
301
+
302
+ // 获取用户身份
303
+ const me = await client.indigo.getMe();
304
+ console.log(`Role: ${me.role}, Multi-tenant: ${me.isMultiTenant}`);
305
+
306
+ // 学生获取 Tutor 列表
307
+ if (me.role === "student") {
308
+ const tutors = await client.indigo.getStudentTutors();
309
+ tutors.forEach((t) =>
310
+ console.log(`${t.firstName} ${t.lastName} (${t.source})`),
311
+ );
312
+ }
313
+
314
+ // Tutor 获取学生列表(按来源分组)
315
+ if (me.role === "tutor") {
316
+ const students = await client.indigo.getTutorStudents();
317
+ const bySource = students.reduce(
318
+ (acc, s) => {
319
+ (acc[s.source] ||= []).push(s);
320
+ return acc;
321
+ },
322
+ {} as Record<string, typeof students>,
323
+ );
324
+ console.log(`Crimson App: ${bySource.crimsonapp?.length || 0} students`);
325
+ }
326
+ ```
327
+
234
328
  ---
235
329
 
236
330
  ## iframe 层(嵌入/跨域场景)
@@ -334,6 +428,65 @@ Props:
334
428
  - `useTemplateMissions/useTemplateMissionsInfinite`
335
429
  - `useTemplateTasks/useTemplateTasksInfinite`
336
430
  - `useTemplateMissionDetail(missionId)`
431
+ - Indigo 身份相关:
432
+ - `useIndigoMe(enabled?)` - 获取当前用户的 Indigo 身份
433
+ - `useStudentTutors(enabled?)` - 获取学生的 Tutor 列表
434
+ - `useTutorStudents(enabled?)` - 获取 Tutor 的学生列表
435
+
436
+ ### Indigo Hooks 示例
437
+
438
+ ```tsx
439
+ import {
440
+ useIndigoMe,
441
+ useStudentTutors,
442
+ useTutorStudents,
443
+ } from "@crimson-education/sdk/react";
444
+
445
+ function IndigoProfile() {
446
+ const { data: me, isLoading: isMeLoading } = useIndigoMe();
447
+ // 仅当用户是 student 时才请求 tutors
448
+ const { data: tutors } = useStudentTutors(me?.role === "student");
449
+ // 仅当用户是 tutor 时才请求 students
450
+ const { data: students } = useTutorStudents(me?.role === "tutor");
451
+
452
+ if (isMeLoading) return <Spinner />;
453
+
454
+ return (
455
+ <div>
456
+ <h1>
457
+ {me?.firstName} {me?.lastName}
458
+ </h1>
459
+ <p>Role: {me?.role}</p>
460
+
461
+ {me?.role === "student" && tutors && (
462
+ <section>
463
+ <h2>My Tutors ({tutors.length})</h2>
464
+ <ul>
465
+ {tutors.map((t) => (
466
+ <li key={t.contractId}>
467
+ {t.firstName} {t.lastName}
468
+ </li>
469
+ ))}
470
+ </ul>
471
+ </section>
472
+ )}
473
+
474
+ {me?.role === "tutor" && students && (
475
+ <section>
476
+ <h2>My Students ({students.length})</h2>
477
+ <ul>
478
+ {students.map((s) => (
479
+ <li key={s.contractId}>
480
+ {s.firstName} {s.lastName} ({s.source})
481
+ </li>
482
+ ))}
483
+ </ul>
484
+ </section>
485
+ )}
486
+ </div>
487
+ );
488
+ }
489
+ ```
337
490
 
338
491
  ---
339
492
 
@@ -362,7 +515,7 @@ SDK 支持向后端发送客户端标识信息,用于追踪 API 调用来源
362
515
  | Header | 说明 | 示例值 |
363
516
  | ------------------- | -------------- | ------------------------- |
364
517
  | `X-Client-ID` | 客户端应用标识 | `new-roadmap`, `capstone` |
365
- | `X-Client-Version` | SDK 版本 | `0.2.0` |
518
+ | `X-Client-Version` | SDK 版本 | `0.3.0` |
366
519
  | `X-Client-Platform` | 运行环境 | `browser`, `node` |
367
520
 
368
521
  ### 后端日志
@@ -374,7 +527,7 @@ SDK 支持向后端发送客户端标识信息,用于追踪 API 调用来源
374
527
  "type": "api_request",
375
528
  "request_id": "uuid",
376
529
  "client_id": "new-roadmap",
377
- "client_version": "0.2.0",
530
+ "client_version": "0.3.0",
378
531
  "client_platform": "browser",
379
532
  "auth_mode": "bearer",
380
533
  "user_id": "auth0-xxx",
@@ -1,5 +1,5 @@
1
1
  import type { CrimsonClient } from "./client";
2
- import type { CurrentUserRoles, StudentSummary } from "./types";
2
+ import type { CurrentUserRoles, StudentSummary, UserProfile } from "./types";
3
3
  export declare class AccountApi {
4
4
  private client;
5
5
  constructor(client: CrimsonClient);
@@ -22,4 +22,19 @@ export declare class AccountApi {
22
22
  * @returns Array of student summaries with basic info for display
23
23
  */
24
24
  getMyStudents(): Promise<StudentSummary[]>;
25
+ /**
26
+ * Get the current logged-in user's profile information.
27
+ *
28
+ * This is a unified endpoint that returns comprehensive user profile data including:
29
+ * - Basic info (userId, email, name)
30
+ * - Nickname/preferred name
31
+ * - Profile picture URL
32
+ * - User status
33
+ * - Multi-tenant status
34
+ * - All user roles (with names)
35
+ * - Tenant information
36
+ *
37
+ * @returns User profile information
38
+ */
39
+ getMyProfile(): Promise<UserProfile>;
25
40
  }
@@ -43,5 +43,24 @@ class AccountApi {
43
43
  return this.client.fetch("/api/v1/account/me/students");
44
44
  });
45
45
  }
46
+ /**
47
+ * Get the current logged-in user's profile information.
48
+ *
49
+ * This is a unified endpoint that returns comprehensive user profile data including:
50
+ * - Basic info (userId, email, name)
51
+ * - Nickname/preferred name
52
+ * - Profile picture URL
53
+ * - User status
54
+ * - Multi-tenant status
55
+ * - All user roles (with names)
56
+ * - Tenant information
57
+ *
58
+ * @returns User profile information
59
+ */
60
+ getMyProfile() {
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ return this.client.fetch("/api/v1/account/me/profile");
63
+ });
64
+ }
46
65
  }
47
66
  exports.AccountApi = AccountApi;
@@ -6,6 +6,8 @@ import { MissionLibraryApi } from "./missionLibrary";
6
6
  import { UsersApi } from "./users";
7
7
  import { AccountApi } from "./account";
8
8
  import { StudentProfileApi } from "./studentProfile";
9
+ import { IndigoApi } from "./indigo";
10
+ import { GradeTemplatesApi } from "./gradeTemplates";
9
11
  import { OAuthAdapter } from "./auth/oauth-adapter";
10
12
  import type { OAuthAuthState, OAuthAuthStateListener } from "./auth/types";
11
13
  export declare class CrimsonClient {
@@ -80,5 +82,7 @@ export declare class CrimsonClient {
80
82
  users: UsersApi;
81
83
  account: AccountApi;
82
84
  studentProfile: StudentProfileApi;
85
+ indigo: IndigoApi;
86
+ gradeTemplates: GradeTemplatesApi;
83
87
  }
84
88
  export declare function createCrimsonClient(config: CrimsonClientConfig): CrimsonClient;
@@ -18,9 +18,11 @@ const missionLibrary_1 = require("./missionLibrary");
18
18
  const users_1 = require("./users");
19
19
  const account_1 = require("./account");
20
20
  const studentProfile_1 = require("./studentProfile");
21
+ const indigo_1 = require("./indigo");
22
+ const gradeTemplates_1 = require("./gradeTemplates");
21
23
  const oauth_adapter_1 = require("./auth/oauth-adapter");
22
24
  /** SDK version from package.json */
23
- const SDK_VERSION = "0.2.0";
25
+ const SDK_VERSION = "0.3.0";
24
26
  const normalizeBase = (url) => url.replace(/\/+$/, "");
25
27
  const normalizeToken = (token) => token.replace(/^Bearer\s+/i, "");
26
28
  /**
@@ -50,6 +52,8 @@ class CrimsonClient {
50
52
  this.users = new users_1.UsersApi(this);
51
53
  this.account = new account_1.AccountApi(this);
52
54
  this.studentProfile = new studentProfile_1.StudentProfileApi(this);
55
+ this.indigo = new indigo_1.IndigoApi(this);
56
+ this.gradeTemplates = new gradeTemplates_1.GradeTemplatesApi(this);
53
57
  this.baseUrl = normalizeBase(config.apiUrl);
54
58
  this.clientId = config.clientId || "unknown";
55
59
  this.clientVersion = config.clientVersion || SDK_VERSION;
@@ -0,0 +1,117 @@
1
+ import type { CrimsonClient } from "./client";
2
+ export interface GradeTemplateMission {
3
+ id: string;
4
+ gradeTemplateId: string;
5
+ templateMissionId: string;
6
+ startYearOffset?: number;
7
+ startMonth: number;
8
+ startDay?: number;
9
+ endYearOffset?: number;
10
+ endMonth: number;
11
+ endDay?: number;
12
+ createdAt: string;
13
+ updatedAt: string;
14
+ title?: string;
15
+ category?: string;
16
+ description?: string;
17
+ subcategory?: string;
18
+ tasks?: GradeTemplateTask[];
19
+ }
20
+ export interface GradeTemplateTaskResource {
21
+ id: string;
22
+ title: string;
23
+ type: string;
24
+ url: string;
25
+ mediaType?: string;
26
+ orderIndex?: number;
27
+ }
28
+ export interface GradeTemplateTask {
29
+ id: string;
30
+ description: string;
31
+ content?: string;
32
+ resources?: GradeTemplateTaskResource[];
33
+ }
34
+ export interface CreateGradeTemplateMissionInput {
35
+ templateMissionId: string;
36
+ startYearOffset?: number;
37
+ startMonth: number;
38
+ startDay?: number;
39
+ endYearOffset?: number;
40
+ endMonth: number;
41
+ endDay?: number;
42
+ }
43
+ export interface GradeTemplateBrief {
44
+ id: string;
45
+ title: string;
46
+ gradeLevel: number;
47
+ startYearOffset?: number;
48
+ startMonth: number;
49
+ startDay?: number;
50
+ endYearOffset?: number;
51
+ endMonth: number;
52
+ endDay?: number;
53
+ }
54
+ export interface GradeTemplate extends GradeTemplateBrief {
55
+ missions: GradeTemplateMission[];
56
+ createdAt: string;
57
+ updatedAt: string;
58
+ }
59
+ export interface CreateGradeTemplateInput {
60
+ title: string;
61
+ gradeLevel: number;
62
+ startYearOffset?: number;
63
+ startMonth: number;
64
+ startDay?: number;
65
+ endYearOffset?: number;
66
+ endMonth: number;
67
+ endDay?: number;
68
+ }
69
+ export interface GradeTemplateFilters {
70
+ ids?: string[];
71
+ startGrade?: number;
72
+ endGrade?: number;
73
+ }
74
+ export interface AddGradeTemplateToRoadmapInput {
75
+ studentId: string;
76
+ startYear: number;
77
+ missions?: {
78
+ gradeTemplateMissionId: string;
79
+ taskIds?: string[];
80
+ startDate?: string;
81
+ endDate?: string;
82
+ }[];
83
+ }
84
+ export declare class GradeTemplatesApi {
85
+ private client;
86
+ constructor(client: CrimsonClient);
87
+ /**
88
+ * List grade templates
89
+ */
90
+ listGradeTemplates(filters?: GradeTemplateFilters): Promise<GradeTemplate[]>;
91
+ /**
92
+ * Get grade template by ID
93
+ */
94
+ getGradeTemplateById(id: string): Promise<GradeTemplate>;
95
+ /**
96
+ * Create a new grade template
97
+ */
98
+ createGradeTemplate(input: CreateGradeTemplateInput): Promise<GradeTemplate>;
99
+ /**
100
+ * Delete a grade template
101
+ */
102
+ deleteGradeTemplate(id: string): Promise<void>;
103
+ /**
104
+ * Add mission to grade template
105
+ */
106
+ addMissionToGradeTemplate(id: string, input: CreateGradeTemplateMissionInput): Promise<GradeTemplateMission>;
107
+ /**
108
+ * Remove mission from grade template
109
+ */
110
+ removeMissionFromGradeTemplate(missionId: string): Promise<void>;
111
+ /**
112
+ * Add grade template to roadmap
113
+ */
114
+ addGradeTemplateToRoadmap(id: string, input: AddGradeTemplateToRoadmapInput): Promise<{
115
+ success: boolean;
116
+ }>;
117
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.GradeTemplatesApi = void 0;
13
+ class GradeTemplatesApi {
14
+ constructor(client) {
15
+ this.client = client;
16
+ }
17
+ /**
18
+ * List grade templates
19
+ */
20
+ listGradeTemplates(filters) {
21
+ return __awaiter(this, void 0, void 0, function* () {
22
+ const params = new URLSearchParams();
23
+ if ((filters === null || filters === void 0 ? void 0 : filters.startGrade) !== undefined) {
24
+ params.append("startgrade", String(filters.startGrade));
25
+ }
26
+ if ((filters === null || filters === void 0 ? void 0 : filters.endGrade) !== undefined) {
27
+ params.append("endgrade", String(filters.endGrade));
28
+ }
29
+ const query = params.toString();
30
+ const path = query
31
+ ? `/roadmap/grade-templates?${query}`
32
+ : "/roadmap/grade-templates";
33
+ return this.client.fetch(path);
34
+ });
35
+ }
36
+ /**
37
+ * Get grade template by ID
38
+ */
39
+ getGradeTemplateById(id) {
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ return this.client.fetch(`/roadmap/grade-templates/${id}`);
42
+ });
43
+ }
44
+ /**
45
+ * Create a new grade template
46
+ */
47
+ createGradeTemplate(input) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ return this.client.fetch("/roadmap/grade-templates", {
50
+ method: "POST",
51
+ body: JSON.stringify(input),
52
+ });
53
+ });
54
+ }
55
+ /**
56
+ * Delete a grade template
57
+ */
58
+ deleteGradeTemplate(id) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ return this.client.fetch(`/roadmap/grade-templates/${id}`, {
61
+ method: "DELETE",
62
+ });
63
+ });
64
+ }
65
+ /**
66
+ * Add mission to grade template
67
+ */
68
+ addMissionToGradeTemplate(id, input) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ return this.client.fetch(`/roadmap/grade-templates/${id}/missions`, {
71
+ method: "POST",
72
+ body: JSON.stringify(input),
73
+ });
74
+ });
75
+ }
76
+ /**
77
+ * Remove mission from grade template
78
+ */
79
+ removeMissionFromGradeTemplate(missionId) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ return this.client.fetch(`/roadmap/grade-templates/missions/${missionId}`, {
82
+ method: "DELETE",
83
+ });
84
+ });
85
+ }
86
+ /**
87
+ * Add grade template to roadmap
88
+ */
89
+ addGradeTemplateToRoadmap(id, input) {
90
+ return __awaiter(this, void 0, void 0, function* () {
91
+ return this.client.fetch(`/roadmap/grade-templates/${id}/add-to-roadmap`, {
92
+ method: "POST",
93
+ body: JSON.stringify(input),
94
+ });
95
+ });
96
+ }
97
+ }
98
+ exports.GradeTemplatesApi = GradeTemplatesApi;
@@ -6,6 +6,8 @@ export { MissionLibraryApi } from "./missionLibrary";
6
6
  export { UsersApi } from "./users";
7
7
  export { AccountApi } from "./account";
8
8
  export { StudentProfileApi } from "./studentProfile";
9
+ export { IndigoApi } from "./indigo";
10
+ export * from "./gradeTemplates";
9
11
  export * from "./types";
10
12
  export { OAuthAdapter, createCrimsonOAuthAdapter, TokenManager, LocalStorageTokenStorage, SessionStorageTokenStorage, MemoryTokenStorage, createDefaultTokenStorage, generateCodeVerifier, generateCodeChallenge, generateState, } from "./auth";
11
13
  export type { OAuthConfig, OAuthTokens, TokenStorage, OAuthAuthState, OAuthAuthStateListener, AuthorizeOptions, CallbackParams, OAuthError, OAuthAdapterConfig, TokenManagerConfig, } from "./auth";
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.generateState = exports.generateCodeChallenge = exports.generateCodeVerifier = exports.createDefaultTokenStorage = exports.MemoryTokenStorage = exports.SessionStorageTokenStorage = exports.LocalStorageTokenStorage = exports.TokenManager = exports.createCrimsonOAuthAdapter = exports.OAuthAdapter = exports.StudentProfileApi = exports.AccountApi = exports.UsersApi = exports.MissionLibraryApi = exports.RoadmapApi = exports.TasksApi = exports.MissionsApi = exports.createCrimsonClient = exports.CrimsonClient = void 0;
17
+ exports.generateState = exports.generateCodeChallenge = exports.generateCodeVerifier = exports.createDefaultTokenStorage = exports.MemoryTokenStorage = exports.SessionStorageTokenStorage = exports.LocalStorageTokenStorage = exports.TokenManager = exports.createCrimsonOAuthAdapter = exports.OAuthAdapter = exports.IndigoApi = exports.StudentProfileApi = exports.AccountApi = exports.UsersApi = exports.MissionLibraryApi = exports.RoadmapApi = exports.TasksApi = exports.MissionsApi = exports.createCrimsonClient = exports.CrimsonClient = void 0;
18
18
  var client_1 = require("./client");
19
19
  Object.defineProperty(exports, "CrimsonClient", { enumerable: true, get: function () { return client_1.CrimsonClient; } });
20
20
  Object.defineProperty(exports, "createCrimsonClient", { enumerable: true, get: function () { return client_1.createCrimsonClient; } });
@@ -32,6 +32,9 @@ var account_1 = require("./account");
32
32
  Object.defineProperty(exports, "AccountApi", { enumerable: true, get: function () { return account_1.AccountApi; } });
33
33
  var studentProfile_1 = require("./studentProfile");
34
34
  Object.defineProperty(exports, "StudentProfileApi", { enumerable: true, get: function () { return studentProfile_1.StudentProfileApi; } });
35
+ var indigo_1 = require("./indigo");
36
+ Object.defineProperty(exports, "IndigoApi", { enumerable: true, get: function () { return indigo_1.IndigoApi; } });
37
+ __exportStar(require("./gradeTemplates"), exports);
35
38
  __exportStar(require("./types"), exports);
36
39
  // OAuth authentication
37
40
  var auth_1 = require("./auth");
@@ -0,0 +1,103 @@
1
+ import type { CrimsonClient } from "./client";
2
+ import type { IndigoMe, IndigoTutor, IndigoStudent } from "./types";
3
+ /**
4
+ * Indigo API Module
5
+ *
6
+ * Provides methods for Indigo-specific functionality including:
7
+ * - User identity retrieval
8
+ * - Student-tutor relationships
9
+ * - Cross-tenant aggregation for multi-tenant users
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const client = createCrimsonClient({
14
+ * apiUrl: 'https://api.crimson.io',
15
+ * clientId: 'indigo-website',
16
+ * getToken: () => localStorage.getItem('token') || '',
17
+ * });
18
+ *
19
+ * // Get current user's Indigo identity
20
+ * const me = await client.indigo.getMe();
21
+ * console.log(`Role: ${me.role}, Multi-tenant: ${me.isMultiTenant}`);
22
+ *
23
+ * // For students: get tutors
24
+ * if (me.role === 'student') {
25
+ * const tutors = await client.indigo.getStudentTutors();
26
+ * }
27
+ *
28
+ * // For tutors: get students (aggregated across tenants)
29
+ * if (me.role === 'tutor') {
30
+ * const students = await client.indigo.getTutorStudents();
31
+ * }
32
+ * ```
33
+ */
34
+ export declare class IndigoApi {
35
+ private client;
36
+ constructor(client: CrimsonClient);
37
+ /**
38
+ * Get the current user's Indigo identity information
39
+ *
40
+ * Returns the authenticated user's identity including:
41
+ * - Basic profile (userId, email, name)
42
+ * - Role (student or tutor)
43
+ * - Tenant information
44
+ * - Multi-tenant status and related accounts
45
+ *
46
+ * @returns Current user's Indigo identity
47
+ * @throws Error if user is not authenticated or doesn't have Indigo subscription
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const me = await client.indigo.getMe();
52
+ * if (me.isMultiTenant) {
53
+ * console.log(`User has ${me.relatedAccounts.length} related accounts`);
54
+ * }
55
+ * ```
56
+ */
57
+ getMe(): Promise<IndigoMe>;
58
+ /**
59
+ * Get the list of tutors for the current student
60
+ *
61
+ * Returns tutors associated with the authenticated student user.
62
+ * For multi-tenant students, this aggregates tutors across all
63
+ * related accounts.
64
+ *
65
+ * @returns Array of tutors with contract information
66
+ * @throws Error if user is not a student or not authenticated
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const tutors = await client.indigo.getStudentTutors();
71
+ * tutors.forEach(tutor => {
72
+ * console.log(`${tutor.firstName} ${tutor.lastName} (${tutor.source})`);
73
+ * });
74
+ * ```
75
+ */
76
+ getStudentTutors(): Promise<IndigoTutor[]>;
77
+ /**
78
+ * Get the list of students for the current tutor
79
+ *
80
+ * Returns students associated with the authenticated tutor user.
81
+ * For multi-tenant tutors (e.g., tutors working with both Crimson App
82
+ * and Collegewise students), this aggregates students across all
83
+ * related accounts, with source tracking.
84
+ *
85
+ * @returns Array of students with contract and source information
86
+ * @throws Error if user is not a tutor or not authenticated
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * const students = await client.indigo.getTutorStudents();
91
+ *
92
+ * // Group by source tenant
93
+ * const bySource = students.reduce((acc, s) => {
94
+ * (acc[s.source] ||= []).push(s);
95
+ * return acc;
96
+ * }, {} as Record<string, IndigoStudent[]>);
97
+ *
98
+ * console.log(`Crimson App: ${bySource.crimsonapp?.length || 0} students`);
99
+ * console.log(`Collegewise: ${bySource.collegewise?.length || 0} students`);
100
+ * ```
101
+ */
102
+ getTutorStudents(): Promise<IndigoStudent[]>;
103
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.IndigoApi = void 0;
13
+ /**
14
+ * Indigo API Module
15
+ *
16
+ * Provides methods for Indigo-specific functionality including:
17
+ * - User identity retrieval
18
+ * - Student-tutor relationships
19
+ * - Cross-tenant aggregation for multi-tenant users
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const client = createCrimsonClient({
24
+ * apiUrl: 'https://api.crimson.io',
25
+ * clientId: 'indigo-website',
26
+ * getToken: () => localStorage.getItem('token') || '',
27
+ * });
28
+ *
29
+ * // Get current user's Indigo identity
30
+ * const me = await client.indigo.getMe();
31
+ * console.log(`Role: ${me.role}, Multi-tenant: ${me.isMultiTenant}`);
32
+ *
33
+ * // For students: get tutors
34
+ * if (me.role === 'student') {
35
+ * const tutors = await client.indigo.getStudentTutors();
36
+ * }
37
+ *
38
+ * // For tutors: get students (aggregated across tenants)
39
+ * if (me.role === 'tutor') {
40
+ * const students = await client.indigo.getTutorStudents();
41
+ * }
42
+ * ```
43
+ */
44
+ class IndigoApi {
45
+ constructor(client) {
46
+ this.client = client;
47
+ }
48
+ /**
49
+ * Get the current user's Indigo identity information
50
+ *
51
+ * Returns the authenticated user's identity including:
52
+ * - Basic profile (userId, email, name)
53
+ * - Role (student or tutor)
54
+ * - Tenant information
55
+ * - Multi-tenant status and related accounts
56
+ *
57
+ * @returns Current user's Indigo identity
58
+ * @throws Error if user is not authenticated or doesn't have Indigo subscription
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const me = await client.indigo.getMe();
63
+ * if (me.isMultiTenant) {
64
+ * console.log(`User has ${me.relatedAccounts.length} related accounts`);
65
+ * }
66
+ * ```
67
+ */
68
+ getMe() {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ return this.client.fetch("/api/v1/indigo/me");
71
+ });
72
+ }
73
+ /**
74
+ * Get the list of tutors for the current student
75
+ *
76
+ * Returns tutors associated with the authenticated student user.
77
+ * For multi-tenant students, this aggregates tutors across all
78
+ * related accounts.
79
+ *
80
+ * @returns Array of tutors with contract information
81
+ * @throws Error if user is not a student or not authenticated
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const tutors = await client.indigo.getStudentTutors();
86
+ * tutors.forEach(tutor => {
87
+ * console.log(`${tutor.firstName} ${tutor.lastName} (${tutor.source})`);
88
+ * });
89
+ * ```
90
+ */
91
+ getStudentTutors() {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ return this.client.fetch("/api/v1/indigo/student/tutors");
94
+ });
95
+ }
96
+ /**
97
+ * Get the list of students for the current tutor
98
+ *
99
+ * Returns students associated with the authenticated tutor user.
100
+ * For multi-tenant tutors (e.g., tutors working with both Crimson App
101
+ * and Collegewise students), this aggregates students across all
102
+ * related accounts, with source tracking.
103
+ *
104
+ * @returns Array of students with contract and source information
105
+ * @throws Error if user is not a tutor or not authenticated
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const students = await client.indigo.getTutorStudents();
110
+ *
111
+ * // Group by source tenant
112
+ * const bySource = students.reduce((acc, s) => {
113
+ * (acc[s.source] ||= []).push(s);
114
+ * return acc;
115
+ * }, {} as Record<string, IndigoStudent[]>);
116
+ *
117
+ * console.log(`Crimson App: ${bySource.crimsonapp?.length || 0} students`);
118
+ * console.log(`Collegewise: ${bySource.collegewise?.length || 0} students`);
119
+ * ```
120
+ */
121
+ getTutorStudents() {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ return this.client.fetch("/api/v1/indigo/tutor/students");
124
+ });
125
+ }
126
+ }
127
+ exports.IndigoApi = IndigoApi;
@@ -383,6 +383,106 @@ export interface StudentSummary {
383
383
  export interface MyStudentsResponse {
384
384
  data: StudentSummary[];
385
385
  }
386
+ /**
387
+ * Subscription source - matches production tenant names
388
+ */
389
+ export type SubscriptionSource = "crimsonapp" | "collegewise" | "crimsonmoe" | "delta" | "indigo" | "kfs" | "mawhiba";
390
+ /**
391
+ * Related account for multi-tenant users
392
+ */
393
+ export interface IndigoRelatedAccount {
394
+ userId: string;
395
+ email: string;
396
+ tenantName: string;
397
+ }
398
+ /**
399
+ * Response from GET /api/v1/indigo/me
400
+ * Contains current user's Indigo identity information
401
+ */
402
+ export interface IndigoMe {
403
+ userId: string;
404
+ email: string;
405
+ firstName: string;
406
+ lastName: string;
407
+ /** User role: student or tutor */
408
+ role: "student" | "tutor";
409
+ /** Primary tenant ID */
410
+ tenantId: string;
411
+ /** Primary tenant name */
412
+ tenantName: string;
413
+ /** Whether user has accounts across multiple tenants */
414
+ isMultiTenant: boolean;
415
+ /** Related accounts in other tenants (for multi-tenant users) */
416
+ relatedAccounts: IndigoRelatedAccount[];
417
+ }
418
+ /**
419
+ * Tutor information for students
420
+ * Response item from GET /api/v1/indigo/student/tutors
421
+ */
422
+ export interface IndigoTutor {
423
+ userId: string;
424
+ email: string;
425
+ firstName: string;
426
+ lastName: string;
427
+ profileImageUrl?: string;
428
+ /** Contract ID linking student and tutor */
429
+ contractId: string;
430
+ /** Contract status (e.g., 'active', 'accepted') */
431
+ contractStatus: string;
432
+ /** Source tenant where this relationship exists */
433
+ source: string;
434
+ }
435
+ /**
436
+ * Student information for tutors
437
+ * Response item from GET /api/v1/indigo/tutor/students
438
+ */
439
+ export interface IndigoStudent {
440
+ userId: string;
441
+ email: string;
442
+ firstName: string;
443
+ lastName: string;
444
+ profileImageUrl?: string;
445
+ /** Contract ID linking student and tutor */
446
+ contractId: string;
447
+ /** Contract status (e.g., 'active', 'accepted') */
448
+ contractStatus: string;
449
+ /** Source tenant where this relationship exists (e.g., 'crimsonapp', 'collegewise') */
450
+ source: string;
451
+ }
452
+ /**
453
+ * Role information in user profile
454
+ */
455
+ export interface UserProfileRole {
456
+ roleId: string;
457
+ roleName: string;
458
+ isPrimary: boolean;
459
+ }
460
+ /**
461
+ * User profile response from GET /api/v1/account/me/profile
462
+ */
463
+ export interface UserProfile {
464
+ userId: string;
465
+ email: string;
466
+ firstName: string;
467
+ lastName: string;
468
+ /** Preferred name / nickname */
469
+ nickname: string | null;
470
+ /** Full display name (firstName + lastName) */
471
+ name: string;
472
+ /** Profile picture URL */
473
+ picture: string | null;
474
+ /** User status */
475
+ status: string;
476
+ /** Whether user has accounts in multiple tenants */
477
+ isMultiTenant: boolean;
478
+ /** User roles (can have multiple) */
479
+ roles: UserProfileRole[];
480
+ /** Tenant information */
481
+ tenant: {
482
+ id: string;
483
+ name: string;
484
+ };
485
+ }
386
486
  /**
387
487
  * Prefilled field with value and source information
388
488
  */
@@ -2,9 +2,11 @@ export { useAuthState } from "./useAuthState";
2
2
  export { useOAuth, useOAuthCallback } from "./useOAuth";
3
3
  export type { UseOAuthResult } from "./useOAuth";
4
4
  export { useMissions, useMissionsInfinite, useCreateMission, useUpdateMission, useDeleteMission, missionKeys, } from "./useMissions";
5
- export { useTasks, useAllUserTasks, useTasksByRoadmapId, useTasksInfinite, useCreateTask, useUpdateTask, useDeleteTask, useBulkDeleteTasks, useBulkUpdateTasks, taskKeys, useGetDownloadUrl, useTaskCreators } from "./useTasks";
5
+ export { useTasks, useAllUserTasks, useTasksByRoadmapId, useTasksInfinite, useCreateTask, useUpdateTask, useDeleteTask, useBulkDeleteTasks, useBulkUpdateTasks, taskKeys, useGetDownloadUrl, useTaskCreators, } from "./useTasks";
6
6
  export { useUsers, useUser, userKeys } from "./useUsers";
7
7
  export { useCurrentUserRoles, useMyStudents, accountKeys } from "./useAccount";
8
8
  export { useRoadmapContext } from "./useRoadmapContext";
9
9
  export { useTemplateMissions, useTemplateMissionsInfinite, useTemplateTasks, useTemplateTasksInfinite, useTemplateMissionDetail, useUpdateTemplateMission, useCopyTemplateMission, useAssignBulkMission, useAssignBulkTask, useCreateFromPredefinedTasks, missionLibraryKeys, useCreateTemplateMission, useDeleteTemplateMission, } from "./useMissionLibrary";
10
10
  export { useStudentProfile, useStudentPrefilledInfo, studentProfileKeys, } from "./useStudentProfile";
11
+ export { useIndigoMe, useStudentTutors, useTutorStudents, indigoKeys, } from "./useIndigo";
12
+ export * from "./useGradeTemplates";
@@ -1,6 +1,20 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.studentProfileKeys = exports.useStudentPrefilledInfo = exports.useStudentProfile = exports.useDeleteTemplateMission = exports.useCreateTemplateMission = exports.missionLibraryKeys = exports.useCreateFromPredefinedTasks = exports.useAssignBulkTask = exports.useAssignBulkMission = exports.useCopyTemplateMission = exports.useUpdateTemplateMission = exports.useTemplateMissionDetail = exports.useTemplateTasksInfinite = exports.useTemplateTasks = exports.useTemplateMissionsInfinite = exports.useTemplateMissions = exports.useRoadmapContext = exports.accountKeys = exports.useMyStudents = exports.useCurrentUserRoles = exports.userKeys = exports.useUser = exports.useUsers = exports.useTaskCreators = exports.useGetDownloadUrl = exports.taskKeys = exports.useBulkUpdateTasks = exports.useBulkDeleteTasks = exports.useDeleteTask = exports.useUpdateTask = exports.useCreateTask = exports.useTasksInfinite = exports.useTasksByRoadmapId = exports.useAllUserTasks = exports.useTasks = exports.missionKeys = exports.useDeleteMission = exports.useUpdateMission = exports.useCreateMission = exports.useMissionsInfinite = exports.useMissions = exports.useOAuthCallback = exports.useOAuth = exports.useAuthState = void 0;
17
+ exports.indigoKeys = exports.useTutorStudents = exports.useStudentTutors = exports.useIndigoMe = exports.studentProfileKeys = exports.useStudentPrefilledInfo = exports.useStudentProfile = exports.useDeleteTemplateMission = exports.useCreateTemplateMission = exports.missionLibraryKeys = exports.useCreateFromPredefinedTasks = exports.useAssignBulkTask = exports.useAssignBulkMission = exports.useCopyTemplateMission = exports.useUpdateTemplateMission = exports.useTemplateMissionDetail = exports.useTemplateTasksInfinite = exports.useTemplateTasks = exports.useTemplateMissionsInfinite = exports.useTemplateMissions = exports.useRoadmapContext = exports.accountKeys = exports.useMyStudents = exports.useCurrentUserRoles = exports.userKeys = exports.useUser = exports.useUsers = exports.useTaskCreators = exports.useGetDownloadUrl = exports.taskKeys = exports.useBulkUpdateTasks = exports.useBulkDeleteTasks = exports.useDeleteTask = exports.useUpdateTask = exports.useCreateTask = exports.useTasksInfinite = exports.useTasksByRoadmapId = exports.useAllUserTasks = exports.useTasks = exports.missionKeys = exports.useDeleteMission = exports.useUpdateMission = exports.useCreateMission = exports.useMissionsInfinite = exports.useMissions = exports.useOAuthCallback = exports.useOAuth = exports.useAuthState = void 0;
4
18
  var useAuthState_1 = require("./useAuthState");
5
19
  Object.defineProperty(exports, "useAuthState", { enumerable: true, get: function () { return useAuthState_1.useAuthState; } });
6
20
  var useOAuth_1 = require("./useOAuth");
@@ -54,3 +68,9 @@ var useStudentProfile_1 = require("./useStudentProfile");
54
68
  Object.defineProperty(exports, "useStudentProfile", { enumerable: true, get: function () { return useStudentProfile_1.useStudentProfile; } });
55
69
  Object.defineProperty(exports, "useStudentPrefilledInfo", { enumerable: true, get: function () { return useStudentProfile_1.useStudentPrefilledInfo; } });
56
70
  Object.defineProperty(exports, "studentProfileKeys", { enumerable: true, get: function () { return useStudentProfile_1.studentProfileKeys; } });
71
+ var useIndigo_1 = require("./useIndigo");
72
+ Object.defineProperty(exports, "useIndigoMe", { enumerable: true, get: function () { return useIndigo_1.useIndigoMe; } });
73
+ Object.defineProperty(exports, "useStudentTutors", { enumerable: true, get: function () { return useIndigo_1.useStudentTutors; } });
74
+ Object.defineProperty(exports, "useTutorStudents", { enumerable: true, get: function () { return useIndigo_1.useTutorStudents; } });
75
+ Object.defineProperty(exports, "indigoKeys", { enumerable: true, get: function () { return useIndigo_1.indigoKeys; } });
76
+ __exportStar(require("./useGradeTemplates"), exports);
@@ -0,0 +1,24 @@
1
+ import type { GradeTemplateFilters, CreateGradeTemplateInput, CreateGradeTemplateMissionInput, AddGradeTemplateToRoadmapInput } from "../../core/gradeTemplates";
2
+ export declare const gradeTemplateKeys: {
3
+ all: readonly ["gradeTemplates"];
4
+ list: (filters?: GradeTemplateFilters) => readonly ["gradeTemplates", "list", GradeTemplateFilters | undefined];
5
+ detail: (id: string) => readonly ["gradeTemplates", "detail", string];
6
+ };
7
+ export declare function useGradeTemplates(filters?: GradeTemplateFilters, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<import("../../core/gradeTemplates").GradeTemplate[], Error>;
8
+ export declare function useGradeTemplate(id: string, enabled?: boolean): import("@tanstack/react-query").UseQueryResult<import("../../core/gradeTemplates").GradeTemplate, Error>;
9
+ export declare function useCreateGradeTemplate(): import("@tanstack/react-query").UseMutationResult<import("../../core/gradeTemplates").GradeTemplate, Error, CreateGradeTemplateInput, unknown>;
10
+ export declare function useDeleteGradeTemplate(): import("@tanstack/react-query").UseMutationResult<void, Error, string, unknown>;
11
+ export declare function useAddMissionToGradeTemplate(): import("@tanstack/react-query").UseMutationResult<import("../../core/gradeTemplates").GradeTemplateMission, Error, {
12
+ id: string;
13
+ input: CreateGradeTemplateMissionInput;
14
+ }, unknown>;
15
+ export declare function useRemoveMissionFromGradeTemplate(): import("@tanstack/react-query").UseMutationResult<void, Error, {
16
+ gradeTemplateId: string;
17
+ missionId: string;
18
+ }, unknown>;
19
+ export declare function useAddGradeTemplateToRoadmap(): import("@tanstack/react-query").UseMutationResult<{
20
+ success: boolean;
21
+ }, Error, {
22
+ id: string;
23
+ input: AddGradeTemplateToRoadmapInput;
24
+ }, unknown>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.gradeTemplateKeys = void 0;
5
+ exports.useGradeTemplates = useGradeTemplates;
6
+ exports.useGradeTemplate = useGradeTemplate;
7
+ exports.useCreateGradeTemplate = useCreateGradeTemplate;
8
+ exports.useDeleteGradeTemplate = useDeleteGradeTemplate;
9
+ exports.useAddMissionToGradeTemplate = useAddMissionToGradeTemplate;
10
+ exports.useRemoveMissionFromGradeTemplate = useRemoveMissionFromGradeTemplate;
11
+ exports.useAddGradeTemplateToRoadmap = useAddGradeTemplateToRoadmap;
12
+ const react_query_1 = require("@tanstack/react-query");
13
+ const provider_1 = require("../provider");
14
+ const useMissions_1 = require("./useMissions");
15
+ const useTasks_1 = require("./useTasks");
16
+ exports.gradeTemplateKeys = {
17
+ all: ["gradeTemplates"],
18
+ list: (filters) => [...exports.gradeTemplateKeys.all, "list", filters],
19
+ detail: (id) => [...exports.gradeTemplateKeys.all, "detail", id],
20
+ };
21
+ // --- Queries ---
22
+ function useGradeTemplates(filters, enabled = true) {
23
+ const client = (0, provider_1.useCrimsonClient)();
24
+ return (0, react_query_1.useQuery)({
25
+ queryKey: exports.gradeTemplateKeys.list(filters),
26
+ queryFn: () => client.gradeTemplates.listGradeTemplates(filters),
27
+ enabled,
28
+ });
29
+ }
30
+ function useGradeTemplate(id, enabled = true) {
31
+ const client = (0, provider_1.useCrimsonClient)();
32
+ return (0, react_query_1.useQuery)({
33
+ queryKey: exports.gradeTemplateKeys.detail(id),
34
+ queryFn: () => client.gradeTemplates.getGradeTemplateById(id),
35
+ enabled: !!id && enabled,
36
+ });
37
+ }
38
+ // --- Mutations ---
39
+ function useCreateGradeTemplate() {
40
+ const client = (0, provider_1.useCrimsonClient)();
41
+ const queryClient = (0, react_query_1.useQueryClient)();
42
+ return (0, react_query_1.useMutation)({
43
+ mutationFn: (input) => client.gradeTemplates.createGradeTemplate(input),
44
+ onSuccess: () => {
45
+ queryClient.invalidateQueries({
46
+ queryKey: exports.gradeTemplateKeys.list(),
47
+ });
48
+ },
49
+ });
50
+ }
51
+ function useDeleteGradeTemplate() {
52
+ const client = (0, provider_1.useCrimsonClient)();
53
+ const queryClient = (0, react_query_1.useQueryClient)();
54
+ return (0, react_query_1.useMutation)({
55
+ mutationFn: (id) => client.gradeTemplates.deleteGradeTemplate(id),
56
+ onSuccess: () => {
57
+ queryClient.invalidateQueries({
58
+ queryKey: exports.gradeTemplateKeys.list(),
59
+ });
60
+ },
61
+ });
62
+ }
63
+ function useAddMissionToGradeTemplate() {
64
+ const client = (0, provider_1.useCrimsonClient)();
65
+ const queryClient = (0, react_query_1.useQueryClient)();
66
+ return (0, react_query_1.useMutation)({
67
+ mutationFn: ({ id, input, }) => client.gradeTemplates.addMissionToGradeTemplate(id, input),
68
+ onSuccess: (_, variables) => {
69
+ queryClient.invalidateQueries({
70
+ queryKey: exports.gradeTemplateKeys.detail(variables.id),
71
+ });
72
+ },
73
+ });
74
+ }
75
+ function useRemoveMissionFromGradeTemplate() {
76
+ const client = (0, provider_1.useCrimsonClient)();
77
+ const queryClient = (0, react_query_1.useQueryClient)();
78
+ return (0, react_query_1.useMutation)({
79
+ mutationFn: ({ gradeTemplateId, missionId }) => client.gradeTemplates.removeMissionFromGradeTemplate(missionId),
80
+ onSuccess: (_, variables) => {
81
+ queryClient.invalidateQueries({
82
+ queryKey: exports.gradeTemplateKeys.detail(variables.gradeTemplateId),
83
+ });
84
+ },
85
+ });
86
+ }
87
+ function useAddGradeTemplateToRoadmap() {
88
+ const client = (0, provider_1.useCrimsonClient)();
89
+ const queryClient = (0, react_query_1.useQueryClient)();
90
+ return (0, react_query_1.useMutation)({
91
+ mutationFn: ({ id, input, }) => client.gradeTemplates.addGradeTemplateToRoadmap(id, input),
92
+ onSuccess: () => {
93
+ queryClient.invalidateQueries({ queryKey: useMissions_1.missionKeys.all });
94
+ queryClient.invalidateQueries({ queryKey: useTasks_1.taskKeys.all });
95
+ },
96
+ });
97
+ }
@@ -0,0 +1,112 @@
1
+ import type { IndigoMe, IndigoTutor, IndigoStudent } from "../../core/types";
2
+ export declare const indigoKeys: {
3
+ all: readonly ["indigo"];
4
+ me: () => readonly ["indigo", "me"];
5
+ studentTutors: () => readonly ["indigo", "studentTutors"];
6
+ tutorStudents: () => readonly ["indigo", "tutorStudents"];
7
+ };
8
+ /**
9
+ * Hook to get current user's Indigo identity
10
+ *
11
+ * Returns the authenticated user's identity including:
12
+ * - Basic profile (userId, email, name)
13
+ * - Role (student or tutor)
14
+ * - Tenant information
15
+ * - Multi-tenant status and related accounts
16
+ *
17
+ * @param enabled - Whether to enable the query (default: true)
18
+ * @returns Query result containing IndigoMe data
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * function IndigoProfile() {
23
+ * const { data: me, isLoading } = useIndigoMe();
24
+ *
25
+ * if (isLoading) return <Spinner />;
26
+ *
27
+ * return (
28
+ * <div>
29
+ * <h1>{me.firstName} {me.lastName}</h1>
30
+ * <p>Role: {me.role}</p>
31
+ * {me.isMultiTenant && (
32
+ * <p>Has {me.relatedAccounts.length} related accounts</p>
33
+ * )}
34
+ * </div>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ export declare function useIndigoMe(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<IndigoMe, Error>;
40
+ /**
41
+ * Hook to get tutors for the current student
42
+ *
43
+ * Returns tutors associated with the authenticated student user.
44
+ * For multi-tenant students, this aggregates tutors across all
45
+ * related accounts.
46
+ *
47
+ * @param enabled - Whether to enable the query (default: true)
48
+ * @returns Query result containing array of IndigoTutor
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * function StudentTutorList() {
53
+ * const { data: me } = useIndigoMe();
54
+ * const { data: tutors, isLoading } = useStudentTutors(me?.role === 'student');
55
+ *
56
+ * if (isLoading) return <Spinner />;
57
+ *
58
+ * return (
59
+ * <ul>
60
+ * {tutors?.map(tutor => (
61
+ * <li key={tutor.contractId}>
62
+ * {tutor.firstName} {tutor.lastName}
63
+ * </li>
64
+ * ))}
65
+ * </ul>
66
+ * );
67
+ * }
68
+ * ```
69
+ */
70
+ export declare function useStudentTutors(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<IndigoTutor[], Error>;
71
+ /**
72
+ * Hook to get students for the current tutor
73
+ *
74
+ * Returns students associated with the authenticated tutor user.
75
+ * For multi-tenant tutors, this aggregates students across all
76
+ * related accounts with source tracking.
77
+ *
78
+ * @param enabled - Whether to enable the query (default: true)
79
+ * @returns Query result containing array of IndigoStudent
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * function TutorStudentList() {
84
+ * const { data: me } = useIndigoMe();
85
+ * const { data: students, isLoading } = useTutorStudents(me?.role === 'tutor');
86
+ *
87
+ * if (isLoading) return <Spinner />;
88
+ *
89
+ * // Group by source tenant
90
+ * const bySource = students?.reduce((acc, s) => {
91
+ * (acc[s.source] ||= []).push(s);
92
+ * return acc;
93
+ * }, {} as Record<string, IndigoStudent[]>);
94
+ *
95
+ * return (
96
+ * <div>
97
+ * {Object.entries(bySource || {}).map(([source, list]) => (
98
+ * <section key={source}>
99
+ * <h2>{source} ({list.length})</h2>
100
+ * <ul>
101
+ * {list.map(s => (
102
+ * <li key={s.contractId}>{s.firstName} {s.lastName}</li>
103
+ * ))}
104
+ * </ul>
105
+ * </section>
106
+ * ))}
107
+ * </div>
108
+ * );
109
+ * }
110
+ * ```
111
+ */
112
+ export declare function useTutorStudents(enabled?: boolean): import("@tanstack/react-query").UseQueryResult<IndigoStudent[], Error>;
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ "use client";
3
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
+ return new (P || (P = Promise))(function (resolve, reject) {
6
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10
+ });
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.indigoKeys = void 0;
14
+ exports.useIndigoMe = useIndigoMe;
15
+ exports.useStudentTutors = useStudentTutors;
16
+ exports.useTutorStudents = useTutorStudents;
17
+ const react_query_1 = require("@tanstack/react-query");
18
+ const provider_1 = require("../provider");
19
+ // Query keys for Indigo module
20
+ exports.indigoKeys = {
21
+ all: ["indigo"],
22
+ me: () => [...exports.indigoKeys.all, "me"],
23
+ studentTutors: () => [...exports.indigoKeys.all, "studentTutors"],
24
+ tutorStudents: () => [...exports.indigoKeys.all, "tutorStudents"],
25
+ };
26
+ /**
27
+ * Hook to get current user's Indigo identity
28
+ *
29
+ * Returns the authenticated user's identity including:
30
+ * - Basic profile (userId, email, name)
31
+ * - Role (student or tutor)
32
+ * - Tenant information
33
+ * - Multi-tenant status and related accounts
34
+ *
35
+ * @param enabled - Whether to enable the query (default: true)
36
+ * @returns Query result containing IndigoMe data
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * function IndigoProfile() {
41
+ * const { data: me, isLoading } = useIndigoMe();
42
+ *
43
+ * if (isLoading) return <Spinner />;
44
+ *
45
+ * return (
46
+ * <div>
47
+ * <h1>{me.firstName} {me.lastName}</h1>
48
+ * <p>Role: {me.role}</p>
49
+ * {me.isMultiTenant && (
50
+ * <p>Has {me.relatedAccounts.length} related accounts</p>
51
+ * )}
52
+ * </div>
53
+ * );
54
+ * }
55
+ * ```
56
+ */
57
+ function useIndigoMe(enabled = true) {
58
+ const client = (0, provider_1.useCrimsonClient)();
59
+ return (0, react_query_1.useQuery)({
60
+ queryKey: exports.indigoKeys.me(),
61
+ queryFn: () => __awaiter(this, void 0, void 0, function* () {
62
+ return client.indigo.getMe();
63
+ }),
64
+ enabled,
65
+ staleTime: 1000 * 60 * 5, // 5 minutes - identity doesn't change often
66
+ });
67
+ }
68
+ /**
69
+ * Hook to get tutors for the current student
70
+ *
71
+ * Returns tutors associated with the authenticated student user.
72
+ * For multi-tenant students, this aggregates tutors across all
73
+ * related accounts.
74
+ *
75
+ * @param enabled - Whether to enable the query (default: true)
76
+ * @returns Query result containing array of IndigoTutor
77
+ *
78
+ * @example
79
+ * ```tsx
80
+ * function StudentTutorList() {
81
+ * const { data: me } = useIndigoMe();
82
+ * const { data: tutors, isLoading } = useStudentTutors(me?.role === 'student');
83
+ *
84
+ * if (isLoading) return <Spinner />;
85
+ *
86
+ * return (
87
+ * <ul>
88
+ * {tutors?.map(tutor => (
89
+ * <li key={tutor.contractId}>
90
+ * {tutor.firstName} {tutor.lastName}
91
+ * </li>
92
+ * ))}
93
+ * </ul>
94
+ * );
95
+ * }
96
+ * ```
97
+ */
98
+ function useStudentTutors(enabled = true) {
99
+ const client = (0, provider_1.useCrimsonClient)();
100
+ return (0, react_query_1.useQuery)({
101
+ queryKey: exports.indigoKeys.studentTutors(),
102
+ queryFn: () => __awaiter(this, void 0, void 0, function* () {
103
+ return client.indigo.getStudentTutors();
104
+ }),
105
+ enabled,
106
+ staleTime: 1000 * 60 * 5, // 5 minutes
107
+ });
108
+ }
109
+ /**
110
+ * Hook to get students for the current tutor
111
+ *
112
+ * Returns students associated with the authenticated tutor user.
113
+ * For multi-tenant tutors, this aggregates students across all
114
+ * related accounts with source tracking.
115
+ *
116
+ * @param enabled - Whether to enable the query (default: true)
117
+ * @returns Query result containing array of IndigoStudent
118
+ *
119
+ * @example
120
+ * ```tsx
121
+ * function TutorStudentList() {
122
+ * const { data: me } = useIndigoMe();
123
+ * const { data: students, isLoading } = useTutorStudents(me?.role === 'tutor');
124
+ *
125
+ * if (isLoading) return <Spinner />;
126
+ *
127
+ * // Group by source tenant
128
+ * const bySource = students?.reduce((acc, s) => {
129
+ * (acc[s.source] ||= []).push(s);
130
+ * return acc;
131
+ * }, {} as Record<string, IndigoStudent[]>);
132
+ *
133
+ * return (
134
+ * <div>
135
+ * {Object.entries(bySource || {}).map(([source, list]) => (
136
+ * <section key={source}>
137
+ * <h2>{source} ({list.length})</h2>
138
+ * <ul>
139
+ * {list.map(s => (
140
+ * <li key={s.contractId}>{s.firstName} {s.lastName}</li>
141
+ * ))}
142
+ * </ul>
143
+ * </section>
144
+ * ))}
145
+ * </div>
146
+ * );
147
+ * }
148
+ * ```
149
+ */
150
+ function useTutorStudents(enabled = true) {
151
+ const client = (0, provider_1.useCrimsonClient)();
152
+ return (0, react_query_1.useQuery)({
153
+ queryKey: exports.indigoKeys.tutorStudents(),
154
+ queryFn: () => __awaiter(this, void 0, void 0, function* () {
155
+ return client.indigo.getTutorStudents();
156
+ }),
157
+ enabled,
158
+ staleTime: 1000 * 60 * 5, // 5 minutes
159
+ });
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crimson-education/sdk",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Crimson SDK for accessing Crimson App APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",