placementt-core 1.400.1014 → 1.400.1015

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.
@@ -9,17 +9,12 @@ import { InstituteData } from "./typeDefinitions";
9
9
  * Three of those are also sub-flags that only apply when `employerEvents` (or the
10
10
  * careersHub package) is switched on.
11
11
  *
12
- * Every surface used to re-derive those rules by hand, and they had drifted:
13
- * the student home rendered skills cards on `skillsAssessmentsProduct !== false`
14
- * with no parent check, while UserProfile required the parent — so a student saw
15
- * skills on their dashboard that staff could not see on their profile. Wex was
16
- * opt-in for staff (`workExperience &&`) and opt-out for students (`!== false`).
17
- *
18
- * This is the single answer. Nav, home, student surfaces and the backend all read
19
- * it, so they cannot disagree about what a customer bought. Dependency-free.
12
+ * `parents` has no flag at all — a school has a parent database when there are
13
+ * parents in it — so this is a hook: the count is a query, and the answer arrives
14
+ * a tick after the flags do.
20
15
  */
21
16
  /** The product a surface can gate on. */
22
- export type ProductKey = "wex" | "network" | "events" | "skills" | "activities" | "destinations" | "alumni";
17
+ export type ProductKey = "wex" | "network" | "parents" | "events" | "skills" | "activities" | "destinations" | "alumni";
23
18
  export type ResolvedProducts = Record<ProductKey, boolean> & {
24
19
  /** Package shape, so callers stop testing `package ===` themselves. */
25
20
  isTrust: boolean;
@@ -41,12 +36,15 @@ export type ProductResolverInput = Partial<Pick<InstituteData, "package" | "work
41
36
  *
42
37
  * @param {ProductResolverInput} [institute] the institute, or a partial of it.
43
38
  * @param {object} [options] extra context.
39
+ * @param {string[]} [options.oIds] whose parent database to count. A trust holds
40
+ * none on its own oId, so pass its schools'.
44
41
  * @param {ProductResolverInput[]} [options.trustSchools] schools beneath a trust.
45
42
  * A trust shows a product when any of its schools has it, matching the nav's
46
43
  * existing destinations rule.
47
44
  * @return {ResolvedProducts} what this institute has.
48
45
  */
49
- export declare function resolveProduct(institute?: ProductResolverInput, options?: {
46
+ export declare function useProductResolver(institute?: ProductResolverInput, options?: {
47
+ oIds?: string[];
50
48
  trustSchools?: ProductResolverInput[];
51
49
  }): ResolvedProducts;
52
50
  /** Human labels, for checklists, settings and the trust product strip. */
@@ -1,21 +1,42 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.PRODUCT_LABELS = void 0;
4
- exports.resolveProduct = resolveProduct;
5
- const PRODUCT_ORDER = ["wex", "destinations", "events", "skills", "activities", "network", "alumni"];
7
+ exports.useProductResolver = useProductResolver;
8
+ const react_1 = require("react");
9
+ const firestore_1 = require("firebase/firestore");
10
+ const firebaseQuery_1 = __importDefault(require("./firebase/firebaseQuery"));
11
+ const PRODUCT_ORDER = ["wex", "destinations", "events", "skills", "activities", "network", "parents", "alumni"];
6
12
  /** Opt-out flag: undefined means on, only an explicit `false` switches it off. */
7
13
  const on = (flag) => flag !== false;
14
+ const firebaseQuery = new firebaseQuery_1.default();
8
15
  /**
9
16
  * Resolve an institute's products.
10
17
  *
11
18
  * @param {ProductResolverInput} [institute] the institute, or a partial of it.
12
19
  * @param {object} [options] extra context.
20
+ * @param {string[]} [options.oIds] whose parent database to count. A trust holds
21
+ * none on its own oId, so pass its schools'.
13
22
  * @param {ProductResolverInput[]} [options.trustSchools] schools beneath a trust.
14
23
  * A trust shows a product when any of its schools has it, matching the nav's
15
24
  * existing destinations rule.
16
25
  * @return {ResolvedProducts} what this institute has.
17
26
  */
18
- function resolveProduct(institute, options) {
27
+ function useProductResolver(institute, options) {
28
+ const [parentCount, setParentCount] = (0, react_1.useState)(0);
29
+ const oIds = options?.oIds || [];
30
+ const scope = oIds.join(",");
31
+ (0, react_1.useEffect)(() => {
32
+ if (!oIds.length) {
33
+ setParentCount(0);
34
+ return;
35
+ }
36
+ Promise.all(oIds.map((o) => firebaseQuery.getCount(["parents"], [(0, firestore_1.where)("oId", "==", o)])))
37
+ .then((counts) => setParentCount(counts.reduce((t, c) => t + c, 0)))
38
+ .catch(() => setParentCount(0));
39
+ }, [scope]);
19
40
  const i = institute || {};
20
41
  const pkg = i.package;
21
42
  const isTrust = pkg === "institutes-two";
@@ -39,10 +60,12 @@ function resolveProduct(institute, options) {
39
60
  // Opt-out and ungated: every institute gets the employer network unless it is
40
61
  // explicitly switched off. Matches Institutes.tsx and the staff home today.
41
62
  const network = on(i.network);
63
+ // No flag: the database exists if anyone is in it.
64
+ const parents = parentCount > 0;
42
65
  const hubIds = Object.entries(i.careersHubLinks || {})
43
66
  .filter(([, link]) => link?.status === "approved")
44
67
  .map(([oId]) => oId);
45
- const products = { wex, network, events, skills, activities, destinations, alumni };
68
+ const products = { wex, network, parents, events, skills, activities, destinations, alumni };
46
69
  return {
47
70
  ...products,
48
71
  isTrust,
@@ -61,6 +84,7 @@ exports.PRODUCT_LABELS = {
61
84
  skills: "Skills",
62
85
  activities: "Activities",
63
86
  network: "Employer network",
87
+ parents: "Parent database",
64
88
  alumni: "Alumni",
65
89
  };
66
90
  //# sourceMappingURL=productResolver.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"productResolver.js","sourceRoot":"","sources":["../src/productResolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"productResolver.js","sourceRoot":"","sources":["../src/productResolver.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1014",
5
+ "version": "1.400.1015",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -1,3 +1,6 @@
1
+ import {useEffect, useState} from "react";
2
+ import {where} from "firebase/firestore";
3
+ import FirebaseQuery from "./firebase/firebaseQuery";
1
4
  import {InstituteData} from "./typeDefinitions";
2
5
 
3
6
  /**
@@ -10,19 +13,14 @@ import {InstituteData} from "./typeDefinitions";
10
13
  * Three of those are also sub-flags that only apply when `employerEvents` (or the
11
14
  * careersHub package) is switched on.
12
15
  *
13
- * Every surface used to re-derive those rules by hand, and they had drifted:
14
- * the student home rendered skills cards on `skillsAssessmentsProduct !== false`
15
- * with no parent check, while UserProfile required the parent — so a student saw
16
- * skills on their dashboard that staff could not see on their profile. Wex was
17
- * opt-in for staff (`workExperience &&`) and opt-out for students (`!== false`).
18
- *
19
- * This is the single answer. Nav, home, student surfaces and the backend all read
20
- * it, so they cannot disagree about what a customer bought. Dependency-free.
16
+ * `parents` has no flag at all — a school has a parent database when there are
17
+ * parents in it — so this is a hook: the count is a query, and the answer arrives
18
+ * a tick after the flags do.
21
19
  */
22
20
 
23
21
  /** The product a surface can gate on. */
24
22
  export type ProductKey =
25
- "wex"|"network"|"events"|"skills"|"activities"|"destinations"|"alumni";
23
+ "wex"|"network"|"parents"|"events"|"skills"|"activities"|"destinations"|"alumni";
26
24
 
27
25
  export type ResolvedProducts = Record<ProductKey, boolean> & {
28
26
  /** Package shape, so callers stop testing `package ===` themselves. */
@@ -46,25 +44,45 @@ export type ProductResolverInput = Partial<Pick<InstituteData,
46
44
  "alumniManagement"|"careersHubLinks">>;
47
45
 
48
46
  const PRODUCT_ORDER: ProductKey[] =
49
- ["wex", "destinations", "events", "skills", "activities", "network", "alumni"];
47
+ ["wex", "destinations", "events", "skills", "activities", "network", "parents", "alumni"];
50
48
 
51
49
  /** Opt-out flag: undefined means on, only an explicit `false` switches it off. */
52
50
  const on = (flag?: boolean) => flag !== false;
53
51
 
52
+ const firebaseQuery = new FirebaseQuery();
53
+
54
54
  /**
55
55
  * Resolve an institute's products.
56
56
  *
57
57
  * @param {ProductResolverInput} [institute] the institute, or a partial of it.
58
58
  * @param {object} [options] extra context.
59
+ * @param {string[]} [options.oIds] whose parent database to count. A trust holds
60
+ * none on its own oId, so pass its schools'.
59
61
  * @param {ProductResolverInput[]} [options.trustSchools] schools beneath a trust.
60
62
  * A trust shows a product when any of its schools has it, matching the nav's
61
63
  * existing destinations rule.
62
64
  * @return {ResolvedProducts} what this institute has.
63
65
  */
64
- export function resolveProduct(
66
+ export function useProductResolver(
65
67
  institute?: ProductResolverInput,
66
- options?: {trustSchools?: ProductResolverInput[]},
68
+ options?: {oIds?: string[], trustSchools?: ProductResolverInput[]},
67
69
  ): ResolvedProducts {
70
+ const [parentCount, setParentCount] = useState(0);
71
+
72
+ const oIds = options?.oIds || [];
73
+ const scope = oIds.join(",");
74
+
75
+ useEffect(() => {
76
+ if (!oIds.length) {
77
+ setParentCount(0);
78
+ return;
79
+ }
80
+ Promise.all(oIds.map((o) =>
81
+ firebaseQuery.getCount(["parents"], [where("oId", "==", o)])))
82
+ .then((counts) => setParentCount(counts.reduce((t, c) => t + c, 0)))
83
+ .catch(() => setParentCount(0));
84
+ }, [scope]);
85
+
68
86
  const i = institute || {};
69
87
  const pkg = i.package;
70
88
 
@@ -115,12 +133,15 @@ export function resolveProduct(
115
133
  // explicitly switched off. Matches Institutes.tsx and the staff home today.
116
134
  const network = on(i.network);
117
135
 
136
+ // No flag: the database exists if anyone is in it.
137
+ const parents = parentCount > 0;
138
+
118
139
  const hubIds = Object.entries(i.careersHubLinks || {})
119
140
  .filter(([, link]) => link?.status === "approved")
120
141
  .map(([oId]) => oId);
121
142
 
122
143
  const products: Record<ProductKey, boolean> =
123
- {wex, network, events, skills, activities, destinations, alumni};
144
+ {wex, network, parents, events, skills, activities, destinations, alumni};
124
145
 
125
146
  return {
126
147
  ...products,
@@ -141,5 +162,6 @@ export const PRODUCT_LABELS: Record<ProductKey, string> = {
141
162
  skills: "Skills",
142
163
  activities: "Activities",
143
164
  network: "Employer network",
165
+ parents: "Parent database",
144
166
  alumni: "Alumni",
145
167
  };
@@ -1,156 +0,0 @@
1
- import {resolveProduct} from "./productResolver";
2
-
3
- /**
4
- * The defaults are the point of these tests. Four flags are opt-out, so a refactor
5
- * that "tidies" `!== false` into a truthy check would silently switch four products
6
- * off for every institute that has never touched the toggle — which is most of them.
7
- */
8
- describe("resolveProduct", () => {
9
- describe("opt-out defaults", () => {
10
- it("gives a bare institute the employer network", () => {
11
- expect(resolveProduct({package: "institutes-one"}).network).toBe(true);
12
- });
13
-
14
- it("switches the network off only on an explicit false", () => {
15
- expect(resolveProduct({network: false}).network).toBe(false);
16
- expect(resolveProduct({network: undefined}).network).toBe(true);
17
- });
18
-
19
- it("turns on events, skills and activities when employerEvents is set and the sub-flags are undefined", () => {
20
- const p = resolveProduct({package: "institutes-one", employerEvents: true});
21
- expect(p.events).toBe(true);
22
- expect(p.skills).toBe(true);
23
- expect(p.activities).toBe(true);
24
- });
25
-
26
- it("lets each sub-flag be switched off independently", () => {
27
- const p = resolveProduct({
28
- package: "institutes-one",
29
- employerEvents: true,
30
- careersEvents: false,
31
- trackExtracurriculars: false,
32
- });
33
- expect(p.events).toBe(false);
34
- expect(p.activities).toBe(false);
35
- expect(p.skills).toBe(true);
36
- });
37
- });
38
-
39
- describe("sub-flags need their parent", () => {
40
- it("does not give skills or activities to a school without employerEvents", () => {
41
- const p = resolveProduct({package: "institutes-one", workExperience: true});
42
- expect(p.skills).toBe(false);
43
- expect(p.activities).toBe(false);
44
- expect(p.events).toBe(false);
45
- });
46
-
47
- it("does not resurrect them from an explicit true on the sub-flag alone", () => {
48
- const p = resolveProduct({
49
- package: "institutes-one",
50
- skillsAssessmentsProduct: true,
51
- trackExtracurriculars: true,
52
- });
53
- expect(p.skills).toBe(false);
54
- expect(p.activities).toBe(false);
55
- });
56
- });
57
-
58
- describe("opt-in products", () => {
59
- it("leaves wex, destinations and alumni off by default", () => {
60
- const p = resolveProduct({package: "institutes-one"});
61
- expect(p.wex).toBe(false);
62
- expect(p.destinations).toBe(false);
63
- expect(p.alumni).toBe(false);
64
- });
65
-
66
- it("turns them on when explicitly true", () => {
67
- const p = resolveProduct({
68
- package: "institutes-one",
69
- workExperience: true,
70
- destinationTracking: true,
71
- alumniManagement: true,
72
- });
73
- expect(p.wex).toBe(true);
74
- expect(p.destinations).toBe(true);
75
- expect(p.alumni).toBe(true);
76
- });
77
- });
78
-
79
- describe("packages", () => {
80
- it("gives a careersHub events without employerEvents, but no wex or skills", () => {
81
- const p = resolveProduct({package: "careersHub"});
82
- expect(p.events).toBe(true);
83
- expect(p.isHub).toBe(true);
84
- expect(p.wex).toBe(false);
85
- expect(p.skills).toBe(false);
86
- expect(p.activities).toBe(false);
87
- });
88
-
89
- it("does not give a careersHub school its own wex even with the flag set", () => {
90
- const p = resolveProduct({package: "careersSchool", workExperience: true});
91
- expect(p.wex).toBe(false);
92
- expect(p.isHubSchool).toBe(true);
93
- });
94
-
95
- it("rolls a trust's products up from its schools", () => {
96
- const p = resolveProduct({package: "institutes-two"}, {
97
- trustSchools: [
98
- {destinationTracking: true},
99
- {employerEvents: true, careersEvents: false},
100
- ],
101
- });
102
- expect(p.isTrust).toBe(true);
103
- expect(p.destinations).toBe(true);
104
- expect(p.skills).toBe(true); // sub-flag undefined at the school
105
- expect(p.events).toBe(false); // explicitly off at the only events school
106
- expect(p.wex).toBe(false);
107
- });
108
-
109
- it("does not roll schools up for a non-trust", () => {
110
- const p = resolveProduct({package: "institutes-one"}, {
111
- trustSchools: [{destinationTracking: true}],
112
- });
113
- expect(p.destinations).toBe(false);
114
- });
115
- });
116
-
117
- describe("hub links", () => {
118
- it("counts only approved links", () => {
119
- const p = resolveProduct({
120
- careersHubLinks: {
121
- hubA: {exists: true, invited: "2026-01-01", status: "approved"},
122
- hubB: {exists: true, invited: "2026-01-01", status: "invited"},
123
- hubC: {exists: true, invited: "2026-01-01", status: "declined"},
124
- },
125
- });
126
- expect(p.hubLinked).toBe(true);
127
- expect(p.hubIds).toEqual(["hubA"]);
128
- });
129
-
130
- it("is not hub-linked with no links", () => {
131
- expect(resolveProduct({}).hubLinked).toBe(false);
132
- });
133
- });
134
-
135
- describe("enabled", () => {
136
- it("lists products in display order and drives the layout mode", () => {
137
- const skillsOnly = resolveProduct({
138
- package: "institutes-one",
139
- employerEvents: true,
140
- careersEvents: false,
141
- network: false,
142
- });
143
- expect(skillsOnly.enabled).toEqual(["skills", "activities"]);
144
-
145
- const fullStack = resolveProduct({
146
- package: "institutes-one",
147
- workExperience: true,
148
- employerEvents: true,
149
- destinationTracking: true,
150
- alumniManagement: true,
151
- });
152
- expect(fullStack.enabled).toEqual(
153
- ["wex", "destinations", "events", "skills", "activities", "network", "alumni"]);
154
- });
155
- });
156
- });