placementt-core 1.20.232 → 1.300.4

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 (101) hide show
  1. package/.eslintrc.js +40 -40
  2. package/.gitattributes +2 -2
  3. package/deploy-all.js +75 -0
  4. package/lib/config.d.ts +0 -13
  5. package/lib/config.js +1 -3
  6. package/lib/config.js.map +1 -1
  7. package/lib/constants.d.ts +1 -0
  8. package/lib/constants.js +48 -1
  9. package/lib/constants.js.map +1 -1
  10. package/lib/features/config.d.ts +133 -133
  11. package/lib/features/config.js +35 -35
  12. package/lib/features/contacts/contacts.d.ts +75 -75
  13. package/lib/features/contacts/contacts.js +105 -105
  14. package/lib/features/downtime/useDowntime.d.ts +11 -11
  15. package/lib/features/downtime/useDowntime.js +22 -22
  16. package/lib/features/placements/studentPlacements/activePlacement.js +1 -1
  17. package/lib/features/placements/studentPlacements/activePlacement.js.map +1 -1
  18. package/lib/features/placements/studentPlacements/studentPlacementsSlice.d.ts +63 -63
  19. package/lib/features/placements/studentPlacements/studentPlacementsSlice.js +81 -81
  20. package/lib/features/providerPlacements/providerPlacementsSlice.d.ts +19 -19
  21. package/lib/features/providerPlacements/providerPlacementsSlice.js +24 -24
  22. package/lib/features/studentPlacements/studentPlacementsSlice.d.ts +62 -62
  23. package/lib/features/studentPlacements/studentPlacementsSlice.js +87 -87
  24. package/lib/features/studentPlacements/useStudentPlacements.d.ts +6 -6
  25. package/lib/features/studentPlacements/useStudentPlacements.js +18 -18
  26. package/lib/features/userSlice.d.ts +26 -26
  27. package/lib/features/userSlice.js +23 -23
  28. package/lib/features/users/useUserFunctions.d.ts +25 -25
  29. package/lib/features/users/useUserFunctions.js +124 -124
  30. package/lib/features/users/userSlice.d.ts +46 -46
  31. package/lib/features/users/userSlice.js +48 -48
  32. package/lib/firebase/firebase.d.ts +1 -1
  33. package/lib/firebase/firebase.js +8 -3
  34. package/lib/firebase/firebase.js.map +1 -1
  35. package/lib/firebase/firebaseConfig.js +1 -0
  36. package/lib/firebase/firebaseConfig.js.map +1 -1
  37. package/lib/firebase/readDatabase.js +7 -7
  38. package/lib/firebase/readDatabase.js.map +1 -1
  39. package/lib/hooks.d.ts +85 -192
  40. package/lib/hooks.js +731 -671
  41. package/lib/hooks.js.map +1 -1
  42. package/lib/images/GatsbyBenchmarks.d.ts +1 -2
  43. package/lib/index.d.ts +0 -1
  44. package/lib/index.js +0 -1
  45. package/lib/index.js.map +1 -1
  46. package/lib/reduxHooks.d.ts +9 -41
  47. package/lib/reduxHooks.js +1 -49
  48. package/lib/reduxHooks.js.map +1 -1
  49. package/lib/tasksAndTips.js +368 -418
  50. package/lib/tasksAndTips.js.map +1 -1
  51. package/lib/typeDefinitions.d.ts +162 -83
  52. package/lib/util.d.ts +25 -1
  53. package/lib/util.js +329 -2
  54. package/lib/util.js.map +1 -1
  55. package/package.json +56 -56
  56. package/src/DatabaseDefinitions.ts +18 -18
  57. package/src/apiCalls.ts +128 -128
  58. package/src/config.ts +50 -53
  59. package/src/constants.ts +846 -796
  60. package/src/databaseTypes.ts +42 -42
  61. package/src/features/analytics/useAnalytics.tsx +63 -63
  62. package/src/features/contacts/contactsSlice.ts +147 -147
  63. package/src/features/contacts/useContacts.tsx +73 -73
  64. package/src/features/dropdown/useDropdown.tsx +52 -52
  65. package/src/features/global/downtime/useDowntime.tsx +23 -23
  66. package/src/features/global/users/useUserFunctions.tsx +132 -132
  67. package/src/features/jobs/jobsSlice.ts +71 -71
  68. package/src/features/placements/studentPlacements/activePlacement.ts +68 -68
  69. package/src/features/placements/studentPlacements/completedStudentPlacementsSlice.ts +97 -97
  70. package/src/features/placements/studentPlacements/upcomingStudentPlacementsSlice.ts +108 -108
  71. package/src/features/placements/studentPlacements/useStudentPlacements.tsx +9 -9
  72. package/src/features/placements/types.ts +10 -10
  73. package/src/features/referrals/useReferrals.tsx +56 -56
  74. package/src/features/updates/useUpdates.tsx +38 -38
  75. package/src/firebase/firebase.tsx +158 -153
  76. package/src/firebase/firebaseConfig.tsx +47 -45
  77. package/src/firebase/firebaseQuery.tsx +151 -151
  78. package/src/firebase/persistence.ts +84 -84
  79. package/src/firebase/readDatabase.tsx +236 -236
  80. package/src/firebase/util.tsx +352 -352
  81. package/src/firebase/writeDatabase.tsx +77 -77
  82. package/src/hooks.tsx +4476 -4463
  83. package/src/images/GatsbyBenchmarks.tsx +711 -711
  84. package/src/images/LogFuturePlacement.jsx +64 -64
  85. package/src/images/LogPreviousPlacement.jsx +228 -228
  86. package/src/images/gatsby_benchmarks.svg +466 -466
  87. package/src/images/log_future_placement.svg +114 -114
  88. package/src/images/log_previous_placement.svg +199 -199
  89. package/src/index.ts +34 -35
  90. package/src/readDatabase.tsx +3 -3
  91. package/src/reduxHooks.ts +295 -352
  92. package/src/tasksAndTips.ts +1267 -1267
  93. package/src/tutorialTips.ts +58 -58
  94. package/src/typeDefinitions.ts +1260 -1129
  95. package/src/util.ts +523 -177
  96. package/tsconfig.dev.json +5 -5
  97. package/tsconfig.json +21 -21
  98. package/lib/features/logs/logsSlice.d.ts +0 -93
  99. package/lib/features/logs/logsSlice.js +0 -146
  100. package/lib/features/logs/logsSlice.js.map +0 -1
  101. package/src/features/logs/logsSlice.ts +0 -208
package/src/util.ts CHANGED
@@ -1,177 +1,523 @@
1
- import {where} from "firebase/firestore";
2
- import FirebaseQuery from "./firebase/firebaseQuery";
3
- import {AlumniConversation, FlagCodes, InstituteData, StudentPlacementData, UserData, WorkflowStage} from "./typeDefinitions";
4
- import {convertDate} from "./firebase/util";
5
-
6
- type PlacementFlagCodeParams = {
7
- placement : StudentPlacementData, // Placement we get flag codes for
8
- studentData: UserData, // Student associated with the placement
9
- workflow: WorkflowStage[], // Workflow associated with the placement
10
- institute: InstituteData, // Associated institutes
11
- user: UserData // User that is fetching flag codes (staff or student)
12
- };
13
- /**
14
- * Description of function
15
- *
16
- */
17
-
18
- type PlaceFlagCodeReturn = Promise<FlagCodes[]>;
19
-
20
- export const getPlacementFlagCodes = async ({placement, studentData, workflow, institute, user}: PlacementFlagCodeParams):PlaceFlagCodeReturn => {
21
- let flags:FlagCodes[] = placement.flags || [];
22
-
23
- const firebaseQuery = new FirebaseQuery();
24
-
25
- if (!studentData.details.parentEmail && !flags.find((x) => x.includes("noParentEmail"))) {
26
- if (workflow.find((obj) => obj.id === placement.status)?.userType === "Parent") {
27
- flags.includes("noParentEmailWarning") || flags.push("noParentEmailError");
28
- } else if (workflow.find((obj) => obj.userType === "Parent")) {
29
- flags.includes("noParentEmailWarning") || flags.push("noParentEmailError");
30
- }
31
- }
32
-
33
- // If placement after provider review and not verified
34
- const placementIsPostProviderReview = placement.leadTimes.some((x) => x.split("_")[0] === "3");
35
- const placementNotEnded = !placement.leadTimes.some((x) => x.split("_")[0] === "8");
36
- const providerUnverified = placement.providerId && !institute?.verifiedProviders?.includes(placement.providerId);
37
- const awaitingProviderInsurance = placement.providerId && institute?.awaitingProviderInsurance?.includes(placement.providerId);
38
-
39
- const riskAssessmentNotVerified = !institute?.verifiedRiskAssessments?.includes(placement.placementId || placement.id);
40
- const awaitingRiskAssessment = !placement.riskAssessment || institute?.awaitingPlacementRiskAssessments?.includes(placement.placementId || placement.id);
41
-
42
- const dbsCheckNotVerified = !institute?.verifiedDbsChecks?.includes(placement.placementId || placement.id);
43
- const awaitingDbsCheck = !placement.dbsCheck || institute?.awaitingPlacementDbsChecks?.includes(placement.placementId || placement.id);
44
-
45
- const reminderCount = (await firebaseQuery.getCount(["reminders"], [where("oId", "==", user.oId), where("uid", "==", user.id), where("dueDate", "<=", convertDate(new Date(), "dbstring") as string), where("documentId", "==", placement.id), where("status", "==", "upcoming")]));
46
-
47
- if (reminderCount) {
48
- flags.push("reminder");
49
- }
50
-
51
- if (user.userType === "Staff" && user.product === "institutes" && placement.insurance && placementIsPostProviderReview && placementNotEnded && providerUnverified && !awaitingProviderInsurance) {
52
- console.log("Add insurance flag!");
53
- flags.includes("noInsurance") || flags.push("noInsurance");
54
- } else {
55
- flags = flags.filter((x) => x !== "noInsurance");
56
- }
57
-
58
- if (user.product === "providers" && placement.statusType === "requested") {
59
- flags.includes("studentNotAccepted") || flags.push("studentNotAccepted");
60
- } else {
61
- flags = flags.filter((x) => x !== "studentNotAccepted");
62
- }
63
-
64
-
65
- if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && placementNotEnded && awaitingProviderInsurance) {
66
- flags.includes("awaitingInsurance") || flags.push("awaitingInsurance");
67
- } else {
68
- flags = flags.filter((x) => x !== "awaitingInsurance");
69
- }
70
-
71
- if (user.userType === "Staff" && user.product === "institutes" && placement.dbsCheck && workflow.find((stage) => stage.dbsCheck) && placement.dbsCheck !== true && placementIsPostProviderReview && placementNotEnded && dbsCheckNotVerified && !awaitingDbsCheck) {
72
- flags.includes("noDbsCheck") || flags.push("noDbsCheck");
73
- } else {
74
- flags = flags.filter((x) => x !== "noDbsCheck");
75
- }
76
-
77
- if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.dbsCheck) && placementNotEnded && awaitingProviderInsurance) {
78
- flags.includes("awaitingDbsCheck") || flags.push("awaitingDbsCheck");
79
- } else {
80
- flags = flags.filter((x) => x !== "awaitingDbsCheck");
81
- }
82
-
83
- if (user.userType === "Staff" && user.product === "institutes" && placement.riskAssessment && workflow.find((stage) => stage.riskAssessment) && placement.riskAssessment !== true && placementIsPostProviderReview && placementNotEnded && riskAssessmentNotVerified && !awaitingRiskAssessment) {
84
- console.log("Add RA flag!");
85
- flags.includes("noRiskAssessment") || flags.push("noRiskAssessment");
86
- } else {
87
- flags = flags.filter((x) => x !== "noRiskAssessment");
88
- }
89
-
90
- if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.riskAssessment) && placementNotEnded && awaitingProviderInsurance) {
91
- flags.includes("awaitingRiskAssessment") || flags.push("awaitingRiskAssessment");
92
- } else {
93
- flags = flags.filter((x) => x !== "awaitingRiskAssessment");
94
- }
95
-
96
- if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding === null) {
97
- flags.includes("addOnboarding") || flags.push("addOnboarding");
98
- } else {
99
- flags = flags.filter((x) => x !== "addOnboarding");
100
- }
101
-
102
- if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding && (placement.onboarding.completed?.submitted && !placement.onboarding.completed.accepted) && (placement.inProgress || placement.active)) {
103
- flags.includes("reviewOnboarding") || flags.push("reviewOnboarding");
104
- } else {
105
- flags = flags.filter((x) => x !== "reviewOnboarding");
106
- }
107
-
108
- if (placement.inProgress && user.userType === "Students" && placement.onboarding && (!placement.onboarding.completed || (placement.onboarding.completed && !placement.onboarding.completed?.submitted)) && (placement.inProgress || placement.active)) {
109
- flags.includes("completeOnboarding") || flags.push("completeOnboarding");
110
- } else {
111
- flags = flags.filter((x) => x !== "completeOnboarding");
112
- }
113
-
114
-
115
- return flags;
116
- };
117
-
118
-
119
- export function objectsEqualNew(a: any, b: any): boolean { // Works with arrays or objects.
120
- if (a === b) {
121
- return true;
122
- }
123
-
124
- if (typeof a !== typeof b) {
125
- return false;
126
- }
127
-
128
- if (Array.isArray(a) && Array.isArray(b)) {
129
- if (a.length !== b.length) {
130
- return false;
131
- }
132
- for (let i = 0; i < a.length; i++) {
133
- if (!objectsEqualNew(a[i], b[i])) {
134
- return false;
135
- }
136
- }
137
- return true;
138
- }
139
-
140
- if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
141
- const keysA = Object.keys(a);
142
- const keysB = Object.keys(b);
143
-
144
- if (keysA.length !== keysB.length) {
145
- return false;
146
- }
147
-
148
- for (const key of keysA) {
149
- if (!keysB.includes(key)) {
150
- return false;
151
- }
152
- if (!objectsEqualNew(a[key], b[key])) {
153
- return false;
154
- }
155
- }
156
- return true;
157
- }
158
-
159
- return false;
160
- }
161
-
162
- export const getMostRecentAlumniMessage = (conversation: AlumniConversation) => {
163
- const { messages } = conversation;
164
-
165
- if (!messages || Object.keys(messages).length === 0) {
166
- return null; // No messages available
167
- }
168
-
169
- const mostRecent = Object.entries(messages)
170
- .map(([id, message]) => ({
171
- id,
172
- ...message,
173
- }))
174
- .sort((a, b) => new Date(b.sentAt).getTime() - new Date(a.sentAt).getTime())[0];
175
-
176
- return mostRecent || null; // Return most recent or null if none found
177
- };
1
+ import {where} from "firebase/firestore";
2
+ import FirebaseQuery from "./firebase/firebaseQuery";
3
+ import {AlumniConversation, EmailTemplateConfig, FlagCodes, InstituteData, StudentPlacementData, UserData, WorkflowStage} from "./typeDefinitions";
4
+ import {convertDate} from "./firebase/util";
5
+ import {Descendant} from "slate";
6
+ import {PRIMARY_COLOUR} from "./constants";
7
+
8
+ type PlacementFlagCodeParams = {
9
+ placement : StudentPlacementData, // Placement we get flag codes for
10
+ studentData: UserData, // Student associated with the placement
11
+ workflow: WorkflowStage[], // Workflow associated with the placement
12
+ institute: InstituteData, // Associated institutes
13
+ user: UserData // User that is fetching flag codes (staff or student)
14
+ };
15
+ /**
16
+ * Description of function
17
+ *
18
+ */
19
+
20
+ type PlaceFlagCodeReturn = Promise<FlagCodes[]>;
21
+
22
+ export const getPlacementFlagCodes = async ({placement, studentData, workflow, institute, user}: PlacementFlagCodeParams):PlaceFlagCodeReturn => {
23
+ let flags:FlagCodes[] = placement.flags || [];
24
+
25
+ const firebaseQuery = new FirebaseQuery();
26
+
27
+ if (!studentData.details.parentEmail && !flags.find((x) => x.includes("noParentEmail"))) {
28
+ if (workflow.find((obj) => obj.id === placement.status)?.userType === "Parent") {
29
+ flags.includes("noParentEmailWarning") || flags.push("noParentEmailError");
30
+ } else if (workflow.find((obj) => obj.userType === "Parent")) {
31
+ flags.includes("noParentEmailWarning") || flags.push("noParentEmailError");
32
+ }
33
+ }
34
+
35
+ // If placement after provider review and not verified
36
+ const placementIsPostProviderReview = placement.leadTimes.some((x) => x.split("_")[0] === "3");
37
+ const placementNotEnded = !placement.leadTimes.some((x) => x.split("_")[0] === "8");
38
+ const providerUnverified = placement.providerContactId && !institute?.verifiedProviders?.includes(placement.providerContactId);
39
+ const awaitingProviderInsurance = placement.providerContactId && institute?.awaitingProviderInsurance?.includes(placement.providerContactId);
40
+
41
+ const riskAssessmentNotVerified = !institute?.verifiedRiskAssessments?.includes(placement.placementId || placement.id);
42
+ const awaitingRiskAssessment = !placement.riskAssessment || institute?.awaitingPlacementRiskAssessments?.includes(placement.placementId || placement.id);
43
+
44
+ const dbsCheckNotVerified = !institute?.verifiedDbsChecks?.includes(placement.placementId || placement.id);
45
+ const awaitingDbsCheck = !placement.dbsCheck || institute?.awaitingPlacementDbsChecks?.includes(placement.placementId || placement.id);
46
+
47
+ const reminderCount = (await firebaseQuery.getCount(["reminders"], [where("oId", "==", user.oId), where("uid", "==", user.id), where("dueDate", "<=", convertDate(new Date(), "dbstring") as string), where("documentId", "==", placement.id), where("status", "==", "upcoming")]));
48
+
49
+ if (reminderCount) {
50
+ flags.push("reminder");
51
+ }
52
+
53
+ if (user.userType === "Staff" && user.product === "institutes" && placement.insurance && placementIsPostProviderReview && placementNotEnded && providerUnverified && !awaitingProviderInsurance) {
54
+ console.log("Add insurance flag!");
55
+ flags.includes("noInsurance") || flags.push("noInsurance");
56
+ } else {
57
+ flags = flags.filter((x) => x !== "noInsurance");
58
+ }
59
+
60
+ if (user.product === "providers" && placement.statusType === "requested") {
61
+ flags.includes("studentNotAccepted") || flags.push("studentNotAccepted");
62
+ } else {
63
+ flags = flags.filter((x) => x !== "studentNotAccepted");
64
+ }
65
+
66
+
67
+ if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && placementNotEnded && awaitingProviderInsurance) {
68
+ flags.includes("awaitingInsurance") || flags.push("awaitingInsurance");
69
+ } else {
70
+ flags = flags.filter((x) => x !== "awaitingInsurance");
71
+ }
72
+
73
+ if (user.userType === "Staff" && user.product === "institutes" && placement.dbsCheck && workflow.find((stage) => stage.dbsCheck) && placement.dbsCheck !== true && placementIsPostProviderReview && placementNotEnded && dbsCheckNotVerified && !awaitingDbsCheck) {
74
+ flags.includes("noDbsCheck") || flags.push("noDbsCheck");
75
+ } else {
76
+ flags = flags.filter((x) => x !== "noDbsCheck");
77
+ }
78
+
79
+ if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.dbsCheck) && placementNotEnded && awaitingProviderInsurance) {
80
+ flags.includes("awaitingDbsCheck") || flags.push("awaitingDbsCheck");
81
+ } else {
82
+ flags = flags.filter((x) => x !== "awaitingDbsCheck");
83
+ }
84
+
85
+ if (user.userType === "Staff" && user.product === "institutes" && placement.riskAssessment && workflow.find((stage) => stage.riskAssessment) && placement.riskAssessment !== true && placementIsPostProviderReview && placementNotEnded && riskAssessmentNotVerified && !awaitingRiskAssessment) {
86
+ console.log("Add RA flag!");
87
+ flags.includes("noRiskAssessment") || flags.push("noRiskAssessment");
88
+ } else {
89
+ flags = flags.filter((x) => x !== "noRiskAssessment");
90
+ }
91
+
92
+ if (user.userType === "Staff" && user.product === "institutes" && placementIsPostProviderReview && workflow.find((stage) => stage.riskAssessment) && placementNotEnded && awaitingProviderInsurance) {
93
+ flags.includes("awaitingRiskAssessment") || flags.push("awaitingRiskAssessment");
94
+ } else {
95
+ flags = flags.filter((x) => x !== "awaitingRiskAssessment");
96
+ }
97
+
98
+ if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding === null) {
99
+ flags.includes("addOnboarding") || flags.push("addOnboarding");
100
+ } else {
101
+ flags = flags.filter((x) => x !== "addOnboarding");
102
+ }
103
+
104
+ if (placement.inProgress && user.userType === "Staff" && user.product === "providers" && user.oId === placement.providerId && placement.onboarding && (placement.onboarding.completed?.submitted && !placement.onboarding.completed.accepted) && (placement.inProgress || placement.active)) {
105
+ flags.includes("reviewOnboarding") || flags.push("reviewOnboarding");
106
+ } else {
107
+ flags = flags.filter((x) => x !== "reviewOnboarding");
108
+ }
109
+
110
+ if (placement.inProgress && user.userType === "Students" && placement.onboarding && (!placement.onboarding.completed || (placement.onboarding.completed && !placement.onboarding.completed?.submitted)) && (placement.inProgress || placement.active)) {
111
+ flags.includes("completeOnboarding") || flags.push("completeOnboarding");
112
+ } else {
113
+ flags = flags.filter((x) => x !== "completeOnboarding");
114
+ }
115
+
116
+
117
+ return flags;
118
+ };
119
+
120
+
121
+ export function objectsEqualNew(a: any, b: any): boolean { // Works with arrays or objects.
122
+ if (a === b) {
123
+ return true;
124
+ }
125
+
126
+ if (typeof a !== typeof b) {
127
+ return false;
128
+ }
129
+
130
+ if (Array.isArray(a) && Array.isArray(b)) {
131
+ if (a.length !== b.length) {
132
+ return false;
133
+ }
134
+ for (let i = 0; i < a.length; i++) {
135
+ if (!objectsEqualNew(a[i], b[i])) {
136
+ return false;
137
+ }
138
+ }
139
+ return true;
140
+ }
141
+
142
+ if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
143
+ const keysA = Object.keys(a);
144
+ const keysB = Object.keys(b);
145
+
146
+ if (keysA.length !== keysB.length) {
147
+ return false;
148
+ }
149
+
150
+ for (const key of keysA) {
151
+ if (!keysB.includes(key)) {
152
+ return false;
153
+ }
154
+ if (!objectsEqualNew(a[key], b[key])) {
155
+ return false;
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+
161
+ return false;
162
+ }
163
+
164
+ export const getMostRecentAlumniMessage = (conversation: AlumniConversation) => {
165
+ const { messages } = conversation;
166
+
167
+ if (!messages || Object.keys(messages).length === 0) {
168
+ return null; // No messages available
169
+ }
170
+
171
+ const mostRecent = Object.entries(messages)
172
+ .map(([id, message]) => ({
173
+ id,
174
+ ...message,
175
+ }))
176
+ .sort((a, b) => new Date(b.sentAt).getTime() - new Date(a.sentAt).getTime())[0];
177
+
178
+ return mostRecent || null; // Return most recent or null if none found
179
+ };
180
+
181
+
182
+ export function buildEmailHTML({preheader, title, salutation, body, primaryColor=PRIMARY_COLOUR, secondaryBody, primaryButton, secondaryButton, primaryImage="http://cdn.mcauto-images-production.sendgrid.net/d17240a596025cf2/637d5af1-79d2-42c9-a663-e88abfea32b9/686x143.png", designatedStaffEmail, organisationName, params, data}:{preheader: string, title: string, salutation?: string, body: string|Descendant[], primaryColor?: string, secondaryBody?: string, primaryButton?: {title: string, url: string}, secondaryButton?: {title: string, url: string}, primaryImage?: string, designatedStaffEmail?: string, organisationName?: string, params?: EmailTemplateConfig, data?: {[key: string]: string|undefined}}) {
183
+ const serialiseSlate = (nodes: Descendant[]) => {
184
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
185
+ return nodes.map((n:any) => {
186
+ if (n.type === "paragraph") {
187
+ const text:string[]=[];
188
+ for (let i = 0; i < n.children.length; i++) {
189
+ const item = n.children[i];
190
+ let subtext = item.text as string || "<br/>";
191
+ console.log("serialise text", subtext);
192
+
193
+ subtext = subtext.replace("​", "");
194
+ if (item.underline) {
195
+ subtext = `<u>${subtext}</u>`;
196
+ }
197
+ if (item.bold) {
198
+ subtext = `<strong>${subtext}</strong>`;
199
+ }
200
+ if (item.italic) {
201
+ subtext = `<i>${subtext}</i>`;
202
+ }
203
+ text.push(subtext);
204
+ }
205
+ return `<div style="font-family: inherit; text-align: inherit">${text.join("")}</div>`;
206
+ }
207
+ return;
208
+ }).filter((a) => a).join("<div style=\"font-family: inherit; text-align: inherit\"><br></div>");
209
+ };
210
+
211
+ const getEmailContentFromTemplate = (template: string|Descendant[], params: EmailTemplateConfig, data: {[key: string]: string|undefined}) => {
212
+ let finalBody = typeof template === "string" ? template : serialiseSlate(template);
213
+ console.log("Body before processing", finalBody);
214
+
215
+ // Replace all instances of placeholder with actual data.
216
+ [...params.params, ...Object.keys(data)].forEach((param) => {
217
+ console.log(`Replacing {{${param}}} with "${data[param]}"`);
218
+ finalBody = finalBody.replace(`{{${param}}}`, data[param] as string);
219
+ });
220
+ console.log("Processed body", finalBody);
221
+ // Still have the {{button}} elements to replace. In this case, split the string up between the button elements.
222
+ const itemsInBody = finalBody.split(/(\{\{button\}\})/);
223
+
224
+ const itemsInBodyWithFormattedButtons = itemsInBody.map((item) => {
225
+ if (item !== "{{button}}" || !params.button) return item;
226
+
227
+ let title = params.button.text;
228
+ let url = "https://placementt.co.uk"+params.button.link;
229
+
230
+ // Replace any params in the url or title with the relevant data.
231
+ [...params.params, ...Object.keys(data)].forEach((param) => {
232
+ title = title.replace(`{{${param}}}`, data[param] as string);
233
+ });
234
+ [...params.params, ...Object.keys(data)].forEach((param) => {
235
+ url = url.replace(`{{${param}}}`, data[param] as string);
236
+ });
237
+ return {
238
+ title: title,
239
+ url: url,
240
+ };
241
+ });
242
+
243
+ return itemsInBodyWithFormattedButtons;
244
+ };
245
+
246
+ let formattedBody = (params && data) ? "" : body;
247
+ let formattedSecondaryBody = (params && data) ? "" : secondaryBody;
248
+ let formattedPrimaryButton = (params && data) ? undefined : primaryButton;
249
+ let formattedSecondaryButton = (params && data) ? undefined : secondaryButton;
250
+
251
+ if (params && data) {
252
+ const processedBody = getEmailContentFromTemplate(body, params, data);
253
+
254
+ processedBody.forEach((item) => {
255
+ if (typeof item === "string") {
256
+ if (!formattedBody) {
257
+ formattedBody = item;
258
+ return;
259
+ }
260
+ formattedSecondaryBody = item;
261
+ return;
262
+ }
263
+ // Must be a button
264
+ if (!formattedPrimaryButton) {
265
+ formattedPrimaryButton = item;
266
+ return;
267
+ }
268
+ formattedSecondaryButton = item;
269
+ });
270
+ formattedBody = processedBody[0] as string;
271
+ }
272
+
273
+ const emailHeadAndStylesHTML = `
274
+ <head>
275
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
276
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
277
+ <!--[if !mso]><!-->
278
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge">
279
+ <!--<![endif]--><!--[if (gte mso 9)|(IE)]>
280
+ <xml>
281
+ <o:OfficeDocumentSettings>
282
+ <o:AllowPNG/>
283
+ <o:PixelsPerInch>96</o:PixelsPerInch>
284
+ </o:OfficeDocumentSettings>
285
+ </xml>
286
+ <![endif]--><!--[if (gte mso 9)|(IE)]>
287
+ <style type="text/css"> body {width: 600px;margin: 0 auto;} table {border-collapse: collapse;} table, td {mso-table-lspace: 0pt;mso-table-rspace: 0pt;} img {-ms-interpolation-mode: bicubic;} </style>
288
+ <![endif]-->
289
+ <style type="text/css"> body, p, div {font-family: inherit;font-size: 14px;} body {color: #000000;} body a {color: #000000;text-decoration: none;} p { margin: 0; padding: 0; } table.wrapper {width:100% !important;table-layout: fixed;-webkit-font-smoothing: antialiased;-webkit-text-size-adjust: 100%;-moz-text-size-adjust: 100%;-ms-text-size-adjust: 100%;} img.max-width {max-width: 100% !important;} .column.of-2 {width: 50%;} .column.of-3 {width: 33.333%;} .column.of-4 {width: 25%;} ul ul ul ul {list-style-type: disc !important;} ol ol {list-style-type: lower-roman !important;} ol ol ol {list-style-type: lower-latin !important;} ol ol ol ol {list-style-type: decimal !important;} @media screen and (max-width:480px) {.preheader .rightColumnContent, .footer .rightColumnContent {text-align: left !important;} .preheader .rightColumnContent div, .preheader .rightColumnContent span, .footer .rightColumnContent div, .footer .rightColumnContent span {text-align: left !important;} .preheader .rightColumnContent, .preheader .leftColumnContent {font-size: 80% !important;padding: 5px 0;} table.wrapper-mobile {width: 100% !important;table-layout: fixed;} img.max-width {height: auto !important;max-width: 100% !important;} a.bulletproof-button {display: block !important;width: auto !important;font-size: 80%;padding-left: 0 !important;padding-right: 0 !important;} .columns {width: 100% !important;} .column {display: block !important;width: 100% !important;padding-left: 0 !important;padding-right: 0 !important;margin-left: 0 !important;margin-right: 0 !important;} .social-icon-column {display: inline-block !important;} } </style>
290
+ <style> @media screen and (max-width:480px) {table\0 {width: 480px !important;} } </style>
291
+ <!--user entered Head Start-->
292
+ <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
293
+ <style> * {font-family: 'nunito', sans-serif;} </style>
294
+ <!--End Head user entered-->
295
+ </head>
296
+ `;
297
+
298
+ const primaryButtonTextLinkHTML = formattedPrimaryButton ? `
299
+ <tr>
300
+ <td style="padding:0px 15px 0px 15px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
301
+ <div>
302
+ <div style="font-family: inherit; text-align: inherit">Or go to <a href="${formattedPrimaryButton.url}">${formattedPrimaryButton.url}</a></div>
303
+ <div></div>
304
+ </div>
305
+ </td>
306
+ </tr>` : "";
307
+
308
+ const primaryButtonHTML = formattedPrimaryButton ? `
309
+ <tr>
310
+ <td align="center" bgcolor="${primaryColor}" class="inner-td" style="border-radius:6px; font-size:16px; text-align:center; background-color:inherit;"><a href="${formattedPrimaryButton.url}" style="background-color:${primaryColor}; border:1px solid #ffffff; border-color:#ffffff; border-radius:30px; border-width:1px; color:#ffffff; display:inline-block; font-size:16px; font-weight:normal; letter-spacing:0px; line-height:normal; padding:12px 40px 12px 40px; text-align:center; text-decoration:none; border-style:solid; font-family:nunito,sans-serif;" target="_blank">${formattedPrimaryButton.title}</a></td>
311
+ </tr>` : "";
312
+
313
+ const secondaryButtonTextHTML = formattedSecondaryButton ? `
314
+ <tr>
315
+ <td align="center" class="inner-td" style="text-align:center; background-color:inherit;"><a href="${formattedSecondaryButton.url}" style="display:inline-block; font-weight:normal; color:#aeaeae; letter-spacing:0px; line-height:normal; text-align:center; text-decoration:none; font-family:nunito,sans-serif;" target="_blank">${formattedSecondaryButton.title}</a></td>
316
+ </tr>` : "";
317
+
318
+ const buttonTableHTML = (primaryButtonHTML || secondaryButtonTextHTML) ? `
319
+ <table border="0" cellpadding="0" cellspacing="0" class="module" data-role="module-button" data-type="button" role="module" style="table-layout:fixed;" width="100%" data-muid="3757586a-ce69-48ba-bd9a-0c0b7937a616">
320
+ <tbody>
321
+ <tr>
322
+ <td align="center" bgcolor="" class="outer-td" style="padding:0px 0px 40px 0px;">
323
+ <table border="0" cellpadding="0" cellspacing="0" class="wrapper-mobile" style="text-align:center;">
324
+ <tbody>
325
+ ${primaryButtonHTML}
326
+ ${secondaryButtonTextHTML}
327
+ </tbody>
328
+ </table>
329
+ </td>
330
+ </tr>
331
+ ${primaryButtonTextLinkHTML}
332
+ </tbody>
333
+ </table>` : "";
334
+
335
+ const secondaryBodyHTML = formattedSecondaryBody ? `
336
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="c589a68d-fd31-4291-b822-cddb80753f13" data-mc-module-version="2019-10-22">
337
+ <tbody>
338
+ <tr>
339
+ <td style="padding:0px 15px 0px 15px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
340
+ <div>
341
+ <div style="font-family: inherit; text-align: inherit"><br></div>
342
+ <div style="font-family: inherit; text-align: inherit">${formattedSecondaryBody}</div>
343
+ <div></div>
344
+ </div>
345
+ </td>
346
+ </tr>
347
+ </tbody>
348
+ </table>` : "";
349
+
350
+ const organisationContactSentence = (designatedStaffEmail || organisationName) ?
351
+ `
352
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="50de54bb-94d0-4d21-a7f2-100719150961" data-mc-module-version="2019-10-22">
353
+ <tbody>
354
+ <tr>
355
+ <td style="padding:0px 15px 18px 15px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
356
+ <div>
357
+
358
+ <div style="font-family: inherit; text-align: inherit">
359
+
360
+ ${organisationName ? `<span style="color: #aeaeae">This email was sent by Placementt on behalf of ${organisationName}.</span>` :
361
+ designatedStaffEmail ? `Please do not reply directly to this email. For any queries, contact <a href="mailto:${designatedStaffEmail}">${designatedStaffEmail}</a>` : ""}
362
+ </div>
363
+ </div>
364
+ </td>
365
+ </tr>
366
+ </tbody>
367
+ </table>` :
368
+ "";
369
+
370
+ return `<html data-editor-version="2" class="sg-campaigns" xmlns="http://www.w3.org/1999/xhtml">
371
+ ${emailHeadAndStylesHTML}
372
+ <body>
373
+ <center class="wrapper" data-link-color="#000000" data-body-style="font-size:14px; font-family:inherit; color:#000000; background-color:#F9F4FF;">
374
+ <div class="webkit">
375
+ <table cellpadding="0" cellspacing="0" border="0" width="100%" class="wrapper" bgcolor="#F9F4FF">
376
+ <tbody>
377
+ <tr>
378
+ <td valign="top" bgcolor="#F9F4FF" width="100%">
379
+ <table width="100%" role="content-container" class="outer" align="center" cellpadding="0" cellspacing="0" border="0">
380
+ <tbody>
381
+ <tr>
382
+ <td width="100%">
383
+ <table width="100%" cellpadding="0" cellspacing="0" border="0">
384
+ <tbody>
385
+ <tr>
386
+ <td>
387
+ <!--[if mso]>
388
+ <center>
389
+ <table>
390
+ <tr>
391
+ <td width="600">
392
+ <![endif]-->
393
+ <table width="100%" cellpadding="0" cellspacing="0" border="0" style="width:100%; max-width:600px;" align="center">
394
+ <tbody>
395
+ <tr>
396
+ <td role="modules-container" style="padding:0px 0px 0px 0px; color:#000000; text-align:left;" bgcolor="#ffffff" width="100%" align="left">
397
+ <table class="module preheader preheader-hide" role="module" data-type="preheader" border="0" cellpadding="0" cellspacing="0" width="100%" style="display: none !important; mso-hide: all; visibility: hidden; opacity: 0; color: transparent; height: 0; width: 0;">
398
+ <tbody>
399
+ <tr>
400
+ <td role="module-content">
401
+ <p>${preheader}</p>
402
+ </td>
403
+ </tr>
404
+ </tbody>
405
+ </table>
406
+ <table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" role="module" data-type="columns" style="padding:20px 0px 20px 0px;" bgcolor="#ffffff" data-distribution="1">
407
+ <tbody>
408
+ <tr role="module-content">
409
+ <td height="100%" valign="top">
410
+ <table width="600" style="width:600px; border-spacing:0; border-collapse:collapse; margin:0px 0px 0px 0px;" cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-0">
411
+ <tbody>
412
+ <tr>
413
+ <td style="padding:0px;margin:0px;border-spacing:0;">
414
+ <table class="wrapper" role="module" data-type="image" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="9f29c991-6500-41ef-9f0e-d56cb5dc1238">
415
+ <tbody>
416
+ <tr>
417
+ <td style="font-size:6px; line-height:10px; padding:0px 0px 0px 0px;" valign="top" align="center"><img class="max-width" border="0" style="display:block; color:#000000; text-decoration:none; font-family:Helvetica, arial, sans-serif; font-size:16px; max-width:52% !important; width:52%; height:auto !important; max-height: 100px; object-fit: contain" width="312" alt="" data-proportionally-constrained="false" data-responsive="true" src="${primaryImage}"></td>
418
+ </tr>
419
+ </tbody>
420
+ </table>
421
+ </td>
422
+ </tr>
423
+ </tbody>
424
+ </table>
425
+ </td>
426
+ </tr>
427
+ </tbody>
428
+ </table>
429
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="b35b8ff4-8b3c-4b35-9ed3-f9f25170affc" data-mc-module-version="2019-10-22">
430
+ <tbody>
431
+ <tr>
432
+ <td style="padding:20px 20px 18px 20px; line-height:28px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
433
+ <div>
434
+ <div style="font-family: nunito, sans-serif; text-align: center"><span style="font-size: 24px; font-family: inherit">${title}</span></div>
435
+ <div></div>
436
+ </div>
437
+ </td>
438
+ </tr>
439
+ </tbody>
440
+ </table>
441
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="c589a68d-fd31-4291-b822-cddb80753f13" data-mc-module-version="2019-10-22">
442
+ <tbody>
443
+ <tr>
444
+ <td style="padding:18px 15px 18px 15px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
445
+ <div>
446
+ ${salutation ? `<div style="font-family: inherit; text-align: inherit"><span style="color: #000000; font-family: Colfax, Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: pre-wrap; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; float: none; display: inline">${salutation},</span></div><div style="font-family: inherit; text-align: inherit"><br></div>` : ""}
447
+ <div style="font-family: inherit; text-align: inherit">${formattedBody}</div>
448
+ <div></div>
449
+ </div>
450
+ </td>
451
+ </tr>
452
+ </tbody>
453
+ </table>
454
+ ${buttonTableHTML}
455
+ ${secondaryBodyHTML}
456
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="50de54bb-94d0-4d21-a7f2-100719150961" data-mc-module-version="2019-10-22">
457
+ <tbody>
458
+ <tr>
459
+ <td style="padding:18px 15px 9px 15px; line-height:22px; text-align:inherit;" height="100%" valign="top" bgcolor="" role="module-content">
460
+ <div>
461
+ <div style="font-family: inherit; text-align: inherit"><span style="color: #aeaeae">If you do not recognise this email, you can safely ignore it.</span></div>
462
+ <div></div>
463
+ </div>
464
+ </td>
465
+ </tr>
466
+ </tbody>
467
+ </table>
468
+ ${organisationContactSentence}
469
+ <table border="0" cellpadding="0" cellspacing="0" align="center" width="100%" role="module" data-type="columns" style="padding:0px 0px 0px 0px;" bgcolor="#FFFFFF" data-distribution="1">
470
+ <tbody>
471
+ <tr role="module-content">
472
+ <td height="100%" valign="top">
473
+ <table width="600" style="width:600px; border-spacing:0; border-collapse:collapse; margin:0px 0px 0px 0px;" cellpadding="0" cellspacing="0" align="left" border="0" bgcolor="" class="column column-0">
474
+ <tbody>
475
+ <tr>
476
+ <td style="padding:0px;margin:0px;border-spacing:0;">
477
+ <table class="module" role="module" data-type="text" border="0" cellpadding="0" cellspacing="0" width="100%" style="table-layout: fixed;" data-muid="a49405df-a253-4a28-8d3d-be95449c7d30" data-mc-module-version="2019-10-22">
478
+ <tbody>
479
+ <tr>
480
+ <td style="padding:18px 0px 18px 0px; line-height:0px; text-align:inherit; background-color:${primaryColor};" height="100%" valign="top" bgcolor="${primaryColor}" role="module-content">
481
+ <div>
482
+ <div style="font-family: inherit; text-align: center"><a href="http://"><span style="font-size: 12px; color: #ffffff; font-family: inherit">www.placementt.co.uk</span></a></div>
483
+ <div></div>
484
+ </div>
485
+ </td>
486
+ </tr>
487
+ </tbody>
488
+ </table>
489
+ </td>
490
+ </tr>
491
+ </tbody>
492
+ </table>
493
+ </td>
494
+ </tr>
495
+ </tbody>
496
+ </table>
497
+ </td>
498
+ </tr>
499
+ </tbody>
500
+ </table>
501
+ <!--[if mso]>
502
+ </td>
503
+ </tr>
504
+ </table>
505
+ </center>
506
+ <![endif]-->
507
+ </td>
508
+ </tr>
509
+ </tbody>
510
+ </table>
511
+ </td>
512
+ </tr>
513
+ </tbody>
514
+ </table>
515
+ </td>
516
+ </tr>
517
+ </tbody>
518
+ </table>
519
+ </div>
520
+ </center>
521
+ </body>
522
+ </html>`;
523
+ }