Skip to content

Commit 90e773d

Browse files
committed
fix(framework): guard against null/undefined name causing crash on /framework page
## Problem When navigating to /framework on production (cyberpros.verifywise.ai), a TypeError crash occurs: "Cannot read properties of undefined (reading 'toLowerCase')". This happens when framework objects returned from the API have null/undefined name fields — likely due to partial data or auth issues in that environment. ## Changes - Add null guards before `.toLowerCase()` calls on `framework.name` in Framework/index.tsx (filteredFrameworks filter + AddFrameworkModal filter + renderFrameworkContent) - Add null guard in Framework/Settings/index.tsx (availableFrameworks filter) - Use optional chaining on `f.type?.toLowerCase()` in NISTFunctionsOverviewCard.tsx - Use optional chaining on `s.label?.toLowerCase()` across all calls in cardEnhancements.ts ## Benefits - Prevents page crash when framework data is incomplete - Frameworks with missing names are silently filtered out instead of crashing - Works locally and on production regardless of database state
1 parent c669bf6 commit 90e773d

File tree

4 files changed

+30
-27
lines changed

4 files changed

+30
-27
lines changed

Clients/src/presentation/pages/Framework/Dashboard/NISTFunctionsOverviewCard.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ const NISTFunctionsOverviewCard = ({ frameworksData, onNavigate }: NISTFunctions
187187

188188
const handleCardClick = () => {
189189
if (onNavigate) {
190-
onNavigate("NIST AI RMF", func.type.toLowerCase());
190+
onNavigate("NIST AI RMF", func.type?.toLowerCase() ?? "");
191191
}
192192
};
193193

@@ -313,10 +313,10 @@ const NISTFunctionsOverviewCard = ({ frameworksData, onNavigate }: NISTFunctions
313313
};
314314

315315
// Get functions by type for 2x2 grid layout
316-
const governFunc = functionsData.find(f => f.type.toLowerCase() === "govern");
317-
const mapFunc = functionsData.find(f => f.type.toLowerCase() === "map");
318-
const measureFunc = functionsData.find(f => f.type.toLowerCase() === "measure");
319-
const manageFunc = functionsData.find(f => f.type.toLowerCase() === "manage");
316+
const governFunc = functionsData.find(f => f.type?.toLowerCase() === "govern");
317+
const mapFunc = functionsData.find(f => f.type?.toLowerCase() === "map");
318+
const measureFunc = functionsData.find(f => f.type?.toLowerCase() === "measure");
319+
const manageFunc = functionsData.find(f => f.type?.toLowerCase() === "manage");
320320

321321
return (
322322
<Stack spacing={0}>

Clients/src/presentation/pages/Framework/Settings/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const FrameworkSettings: React.FC<FrameworkSettingsProps> = ({
8181

8282
// Get available frameworks for organizational projects (ISO 27001, ISO 42001, and NIST AI RMF)
8383
const availableFrameworks = allFrameworks.filter((framework) => {
84+
if (!framework.name) return false;
8485
const isNotEuAiAct = !framework.name.toLowerCase().includes("eu ai act");
8586
const isComplianceFramework =
8687
framework.name.toLowerCase().includes("iso 27001") ||

Clients/src/presentation/pages/Framework/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ const Framework = () => {
227227

228228
// Filter frameworks to only include those assigned to the project and exclude EU AI Act
229229
const filtered = allFrameworks.filter((framework) => {
230+
if (!framework.name) return false;
230231
const frameworkId = Number(framework.id);
231232
const isAssignedToProject = projectFrameworkIds.includes(frameworkId);
232233
const isNotEuAiAct = !framework.name.toLowerCase().includes("eu ai act");
@@ -586,7 +587,7 @@ const Framework = () => {
586587
}
587588

588589
const framework = filteredFrameworks[selectedFramework];
589-
if (!framework) return null;
590+
if (!framework || !framework.name) return null;
590591

591592
// Check if the selected framework is ISO 27001, ISO 42001, or NIST AI RMF
592593
const isISO27001 = framework.name.toLowerCase().includes("iso 27001");
@@ -1202,6 +1203,7 @@ const Framework = () => {
12021203
onClose={() => setIsFrameworkModalOpen(false)}
12031204
frameworks={allFrameworks
12041205
.filter((framework) => {
1206+
if (!framework.name) return false;
12051207
// Only show organizational frameworks (ISO 27001, ISO 42001, and NIST AI RMF) for organizational projects
12061208
const isNotEuAiAct = !framework.name
12071209
.toLowerCase()

Clients/src/presentation/utils/cardEnhancements.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,29 +35,29 @@ export const getQuickStats = (
3535
switch (entityType) {
3636
case "models": {
3737
const productionModels =
38-
statusData?.find((s) => s.label.toLowerCase().includes("production"))
38+
statusData?.find((s) => s.label?.toLowerCase().includes("production"))
3939
?.value || Math.floor(total * 0.4);
4040
return `${productionModels} in production`;
4141
}
4242

4343
case "trainings": {
4444
const completedTrainings =
45-
statusData?.find((s) => s.label.toLowerCase().includes("completed"))
45+
statusData?.find((s) => s.label?.toLowerCase().includes("completed"))
4646
?.value || Math.floor(total * 0.6);
4747
const completionRate = Math.round((completedTrainings / total) * 100);
4848
return `${completionRate}% completion rate`;
4949
}
5050

5151
case "policies": {
5252
const publishedPolicies =
53-
statusData?.find((s) => s.label.toLowerCase().includes("published"))
53+
statusData?.find((s) => s.label?.toLowerCase().includes("published"))
5454
?.value || Math.floor(total * 0.5);
5555
return `${publishedPolicies} published`;
5656
}
5757

5858
case "vendors": {
5959
const activeVendors =
60-
statusData?.find((s) => s.label.toLowerCase().includes("active"))
60+
statusData?.find((s) => s.label?.toLowerCase().includes("active"))
6161
?.value || Math.floor(total * 0.7);
6262
return `${activeVendors} active`;
6363
}
@@ -67,13 +67,13 @@ export const getQuickStats = (
6767
statusData
6868
?.filter(
6969
(s) =>
70-
s.label.toLowerCase().includes("high") &&
71-
!s.label.toLowerCase().includes("very")
70+
s.label?.toLowerCase().includes("high") &&
71+
!s.label?.toLowerCase().includes("very")
7272
)
7373
?.reduce((sum, item) => sum + item.value, 0) ||
7474
Math.floor(total * 0.2);
7575
const veryHighRisks =
76-
statusData?.find((s) => s.label.toLowerCase().includes("very high"))
76+
statusData?.find((s) => s.label?.toLowerCase().includes("very high"))
7777
?.value || Math.floor(total * 0.05);
7878
const criticalCount = highRisks + veryHighRisks;
7979
return criticalCount > 0
@@ -83,7 +83,7 @@ export const getQuickStats = (
8383

8484
case "incidents": {
8585
const openIncidents =
86-
statusData?.find((s) => s.label.toLowerCase().includes("open"))
86+
statusData?.find((s) => s.label?.toLowerCase().includes("open"))
8787
?.value || Math.floor(total * 0.3);
8888
return openIncidents > 0 ? `${openIncidents} open` : "All resolved";
8989
}
@@ -112,10 +112,10 @@ export const hasCriticalItems = (
112112
switch (entityType) {
113113
case "vendorRisks": {
114114
const highRisk =
115-
statusData.find((s) => s.label.toLowerCase().includes("high"))?.value ||
115+
statusData.find((s) => s.label?.toLowerCase().includes("high"))?.value ||
116116
0;
117117
const veryHighRisk =
118-
statusData.find((s) => s.label.toLowerCase().includes("very high"))
118+
statusData.find((s) => s.label?.toLowerCase().includes("very high"))
119119
?.value || 0;
120120
const criticalRisks = highRisk + veryHighRisk;
121121

@@ -128,10 +128,10 @@ export const hasCriticalItems = (
128128

129129
case "policies": {
130130
const inReview =
131-
statusData.find((s) => s.label.toLowerCase().includes("in review"))
131+
statusData.find((s) => s.label?.toLowerCase().includes("in review"))
132132
?.value || 0;
133133
const draft =
134-
statusData.find((s) => s.label.toLowerCase().includes("draft"))
134+
statusData.find((s) => s.label?.toLowerCase().includes("draft"))
135135
?.value || 0;
136136
const needsAttention = inReview + draft;
137137

@@ -144,7 +144,7 @@ export const hasCriticalItems = (
144144

145145
case "trainings": {
146146
const inProgress =
147-
statusData.find((s) => s.label.toLowerCase().includes("in progress"))
147+
statusData.find((s) => s.label?.toLowerCase().includes("in progress"))
148148
?.value || 0;
149149

150150
return {
@@ -156,7 +156,7 @@ export const hasCriticalItems = (
156156

157157
case "vendors": {
158158
const requiresFollowUp =
159-
statusData.find((s) => s.label.toLowerCase().includes("follow up"))
159+
statusData.find((s) => s.label?.toLowerCase().includes("follow up"))
160160
?.value || 0;
161161

162162
return {
@@ -168,7 +168,7 @@ export const hasCriticalItems = (
168168

169169
case "incidents": {
170170
const openIncidents =
171-
statusData.find((s) => s.label.toLowerCase().includes("open"))
171+
statusData.find((s) => s.label?.toLowerCase().includes("open"))
172172
?.value || 0;
173173

174174
return {
@@ -201,13 +201,13 @@ export const getPriorityLevel = (
201201
switch (entityType) {
202202
case "vendorRisks": {
203203
const veryHighRisk =
204-
statusData?.find((s) => s.label.toLowerCase().includes("very high"))
204+
statusData?.find((s) => s.label?.toLowerCase().includes("very high"))
205205
?.value || 0;
206206
const highRisk =
207207
statusData?.find(
208208
(s) =>
209-
s.label.toLowerCase().includes("high") &&
210-
!s.label.toLowerCase().includes("very")
209+
s.label?.toLowerCase().includes("high") &&
210+
!s.label?.toLowerCase().includes("very")
211211
)?.value || 0;
212212

213213
if (veryHighRisk > 0) return "high";
@@ -217,7 +217,7 @@ export const getPriorityLevel = (
217217

218218
case "policies": {
219219
const overdue =
220-
statusData?.find((s) => s.label.toLowerCase().includes("draft"))
220+
statusData?.find((s) => s.label?.toLowerCase().includes("draft"))
221221
?.value || 0;
222222
const draftPercentage = (overdue / total) * 100;
223223

@@ -228,7 +228,7 @@ export const getPriorityLevel = (
228228

229229
case "trainings": {
230230
const completed =
231-
statusData?.find((s) => s.label.toLowerCase().includes("completed"))
231+
statusData?.find((s) => s.label?.toLowerCase().includes("completed"))
232232
?.value || 0;
233233
const completionRate = (completed / total) * 100;
234234

@@ -239,7 +239,7 @@ export const getPriorityLevel = (
239239

240240
case "incidents": {
241241
const openIncidents =
242-
statusData?.find((s) => s.label.toLowerCase().includes("open"))
242+
statusData?.find((s) => s.label?.toLowerCase().includes("open"))
243243
?.value || 0;
244244
const openPercentage = (openIncidents / total) * 100;
245245

0 commit comments

Comments
 (0)