@salesforce/webapp-template-app-react-sample-b2x-experimental 1.112.7 → 1.112.9

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 (138) hide show
  1. package/dist/CHANGELOG.md +19 -0
  2. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +6 -5
  3. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphql-operations-types.ts +12058 -214
  4. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +18 -15
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{propertyListingGraphQL.ts → properties/propertyListingGraphQL.ts} +1 -1
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +4 -2
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{TopBar.tsx → layout/TopBar.tsx} +2 -2
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestList.tsx → maintenanceRequests/MaintenanceRequestList.tsx} +4 -4
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestListItem.tsx → maintenanceRequests/MaintenanceRequestListItem.tsx} +3 -3
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx +87 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertyListingCard.tsx → properties/PropertyListingCard.tsx} +1 -1
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/{features/global-search/components/search/SearchPagination.tsx → components/properties/PropertyListingSearchPagination.tsx} +20 -28
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/constants/propertyListing.ts +4 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/AccountSearch.tsx +303 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/__examples__/pages/Home.tsx +34 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/api/objectSearchService.ts +84 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/ActiveFilters.tsx +89 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/FilterContext.tsx +73 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/PaginationControls.tsx +109 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/SearchBar.tsx +41 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/SortControl.tsx +143 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/BooleanFilter.tsx +74 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/DateFilter.tsx +121 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/DateRangeFilter.tsx +69 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/MultiSelectFilter.tsx +98 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/NumericRangeFilter.tsx +85 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/SearchFilter.tsx +37 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/SelectFilter.tsx +93 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/components/filters/TextFilter.tsx +74 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useAsyncData.ts +54 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/hooks/useObjectSearchParams.ts +247 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/debounce.ts +22 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/fieldUtils.ts +29 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/filterUtils.ts +372 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/object-search/utils/sortUtils.ts +38 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useMaintenanceRequests.ts +1 -1
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +2 -2
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyDetail.ts +1 -1
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingAmenities.ts +2 -2
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +2 -2
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +3 -3
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +2 -2
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +3 -3
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +2 -2
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +4 -2
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +2 -2
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +1 -1
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +12 -10
  57. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +6 -18
  58. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/propertyListingPaginationUtils.ts +18 -0
  59. package/dist/package-lock.json +2 -2
  60. package/dist/package.json +1 -1
  61. package/dist/scripts/graphql-search.sh +69 -17
  62. package/package.json +1 -1
  63. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/MaintenanceDetailsModal.tsx +0 -128
  64. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectDetailService.ts +0 -102
  65. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectInfoGraphQLService.ts +0 -137
  66. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/objectInfoService.ts +0 -95
  67. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/api/recordListGraphQLService.ts +0 -364
  68. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailFields.tsx +0 -55
  69. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailForm.tsx +0 -146
  70. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailHeader.tsx +0 -34
  71. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/DetailLayoutSections.tsx +0 -80
  72. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/Section.tsx +0 -108
  73. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/SectionRow.tsx +0 -20
  74. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/UiApiDetailForm.tsx +0 -140
  75. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FieldValueDisplay.tsx +0 -73
  76. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedAddress.tsx +0 -29
  77. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedEmail.tsx +0 -17
  78. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedPhone.tsx +0 -24
  79. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedText.tsx +0 -11
  80. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/detail/formatted/FormattedUrl.tsx +0 -29
  81. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterField.tsx +0 -54
  82. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterInput.tsx +0 -55
  83. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FilterSelect.tsx +0 -72
  84. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/filters/FiltersPanel.tsx +0 -380
  85. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/forms/filters-form.tsx +0 -114
  86. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/forms/submit-button.tsx +0 -47
  87. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/GlobalSearchInput.tsx +0 -114
  88. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/ResultCardFields.tsx +0 -71
  89. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchHeader.tsx +0 -31
  90. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchResultCard.tsx +0 -138
  91. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/search/SearchResultsPanel.tsx +0 -197
  92. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/components/shared/LoadingFallback.tsx +0 -61
  93. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/constants.ts +0 -39
  94. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/filters/FilterInput.tsx +0 -55
  95. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/filters/FilterSelect.tsx +0 -72
  96. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/form.tsx +0 -209
  97. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useObjectInfoBatch.ts +0 -72
  98. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useObjectSearchData.ts +0 -174
  99. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useRecordDetailLayout.ts +0 -137
  100. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/hooks/useRecordListGraphQL.ts +0 -135
  101. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/pages/DetailPage.tsx +0 -109
  102. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/pages/GlobalSearch.tsx +0 -235
  103. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/filters/filters.ts +0 -121
  104. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/filters/picklist.ts +0 -6
  105. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/objectInfo/objectInfo.ts +0 -49
  106. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/recordDetail/recordDetail.ts +0 -61
  107. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/types/schema.d.ts +0 -200
  108. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/apiUtils.ts +0 -59
  109. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/cacheUtils.ts +0 -76
  110. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/debounce.ts +0 -90
  111. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/fieldUtils.ts +0 -354
  112. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/fieldValueExtractor.ts +0 -67
  113. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/filterUtils.ts +0 -32
  114. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/formDataTransformUtils.ts +0 -260
  115. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/formUtils.ts +0 -142
  116. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLNodeFieldUtils.ts +0 -186
  117. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLObjectInfoAdapter.ts +0 -77
  118. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/graphQLRecordAdapter.ts +0 -90
  119. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/layoutTransformUtils.ts +0 -236
  120. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/linkUtils.ts +0 -14
  121. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/paginationUtils.ts +0 -49
  122. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/recordUtils.ts +0 -159
  123. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/utils/sanitizationUtils.ts +0 -50
  124. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingPriceRange.ts +0 -64
  125. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/index.ts +0 -120
  126. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/About.tsx +0 -8
  127. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/HelpCenter.tsx +0 -29
  128. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +0 -100
  129. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{applicationApi.ts → applications/applicationApi.ts} +0 -0
  130. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{maintenanceRequestApi.ts → maintenanceRequests/maintenanceRequestApi.ts} +0 -0
  131. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/{propertyDetailGraphQL.ts → properties/propertyDetailGraphQL.ts} +0 -0
  132. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{WeatherWidget.tsx → dashboard/WeatherWidget.tsx} +0 -0
  133. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{NavMenu.tsx → layout/VerticalNav.tsx} +0 -0
  134. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{MaintenanceRequestIcon.tsx → maintenanceRequests/MaintenanceRequestIcon.tsx} +0 -0
  135. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{StatusBadge.tsx → maintenanceRequests/StatusBadge.tsx} +0 -0
  136. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertyMap.tsx → properties/PropertyMap.tsx} +0 -0
  137. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/{PropertySearchFilters.tsx → properties/PropertySearchFilters.tsx} +0 -0
  138. /package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/{features/global-search/types/search → types}/searchResults.ts +0 -0
@@ -1,90 +0,0 @@
1
- /**
2
- * Adapts GraphQL UI API node shape to SearchResultRecordData so existing list UI
3
- * (SearchResultCard, ResultCardFields, getNestedFieldValue) works unchanged.
4
- *
5
- * GraphQL node: { Id, Name: { value }, Owner: { Alias: { value } }, ... }
6
- * SearchResultRecordData: { id, fields: Record<string, FieldValue>, apiName, ... }
7
- * FieldValue: { displayValue, value }
8
- */
9
-
10
- import type { FieldValue, SearchResultRecordData } from "../types/search/searchResults";
11
-
12
- function isValueLeaf(obj: unknown): obj is { value: string | number | boolean | null } {
13
- return (
14
- typeof obj === "object" &&
15
- obj !== null &&
16
- "value" in obj &&
17
- (Object.keys(obj).length === 1 || (Object.keys(obj).length === 2 && "displayValue" in obj))
18
- );
19
- }
20
-
21
- function graphQLValueToFieldValue(val: unknown): FieldValue {
22
- if (val === null || val === undefined) {
23
- return { displayValue: null, value: null };
24
- }
25
- if (isValueLeaf(val)) {
26
- const v = val.value;
27
- const display =
28
- typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? v : null;
29
- return { displayValue: display as string | null, value: v };
30
- }
31
- if (typeof val === "object" && val !== null && !Array.isArray(val)) {
32
- const nested = graphQLNodeToFields(val as Record<string, unknown>);
33
- const firstFv = nested && Object.values(nested)[0];
34
- const display =
35
- firstFv && typeof (firstFv as FieldValue).value !== "object"
36
- ? ((firstFv as FieldValue).value as string | null)
37
- : null;
38
- return { displayValue: display, value: { fields: nested ?? {} } };
39
- }
40
- return { displayValue: null, value: val };
41
- }
42
-
43
- function graphQLNodeToFields(node: Record<string, unknown>): Record<string, FieldValue> {
44
- const fields: Record<string, FieldValue> = {};
45
- for (const [key, val] of Object.entries(node)) {
46
- if (key === "Id" || val === undefined) continue;
47
- fields[key] = graphQLValueToFieldValue(val);
48
- }
49
- return fields;
50
- }
51
-
52
- /**
53
- * Converts a GraphQL connection node (from getRecordsGraphQL) to SearchResultRecordData
54
- * so it can be passed to SearchResultCard and other components that expect the keyword-search record shape.
55
- */
56
- export function graphQLNodeToSearchResultRecordData(
57
- node: Record<string, unknown> | undefined,
58
- objectApiName: string,
59
- ): SearchResultRecordData {
60
- if (!node || typeof node !== "object") {
61
- return {
62
- id: "",
63
- apiName: objectApiName,
64
- childRelationships: {},
65
- eTag: "",
66
- fields: {},
67
- lastModifiedById: null,
68
- lastModifiedDate: null,
69
- recordTypeId: null,
70
- recordTypeInfo: null,
71
- systemModstamp: null,
72
- weakEtag: 0,
73
- };
74
- }
75
- const id = (node.Id as string) ?? "";
76
- const fields = graphQLNodeToFields(node);
77
- return {
78
- id,
79
- apiName: objectApiName,
80
- childRelationships: {},
81
- eTag: "",
82
- fields,
83
- lastModifiedById: null,
84
- lastModifiedDate: null,
85
- recordTypeId: null,
86
- recordTypeInfo: null,
87
- systemModstamp: null,
88
- weakEtag: 0,
89
- };
90
- }
@@ -1,236 +0,0 @@
1
- /**
2
- * Transforms Layout API sections into a structure for the detail form: section → rows → items.
3
- * Uses layout response (sections, layoutRows, layoutItems, layoutComponents) and optional
4
- * object info for compound field names and dataType. Section merge when useHeading === false.
5
- */
6
-
7
- import type { LayoutSection, LayoutRow, LayoutItem } from "../types/recordDetail/recordDetail";
8
-
9
- const HIDE_EMPTY_SECTIONS = true;
10
- const EMPTY_OPTIONS: PicklistOption[] = [];
11
-
12
- export interface ObjectInfoField {
13
- compoundFieldName?: string;
14
- dataType?: string;
15
- }
16
-
17
- export interface ObjectInfo {
18
- apiName?: string;
19
- fields?: Record<string, ObjectInfoField>;
20
- }
21
-
22
- /** Picklist/lookup options for a field (e.g. [{ label, value }]). */
23
- export type PicklistOption = {
24
- label: string | null;
25
- value: string | number | boolean;
26
- validFor?: unknown[];
27
- };
28
-
29
- export interface LayoutTransformContext {
30
- recordId: string;
31
- objectInfo?: ObjectInfo | null;
32
- lookupRecords?: Record<string, PicklistOption[] | null> | null;
33
- getSectionCollapsedState: (sectionId: string) => boolean;
34
- calculatePicklistValues?: (itemApiName: string, item: LayoutItem) => PicklistOption[] | null;
35
- formOverrides?: { fieldVariant?: string } | null;
36
- }
37
-
38
- export interface TransformedLayoutItem {
39
- key: string;
40
- isField: boolean;
41
- label?: string;
42
- required?: boolean;
43
- readOnly?: boolean;
44
- apiName?: string;
45
- contextName?: string;
46
- options?: PicklistOption[];
47
- variant?: string;
48
- dataType?: string;
49
- layoutComponentApiNames?: string[];
50
- }
51
-
52
- /** Single row in a section. */
53
- export interface TransformedLayoutRow {
54
- key: string;
55
- layoutItems: TransformedLayoutItem[];
56
- }
57
-
58
- /** Section ready for Section/SectionRow rendering. */
59
- export interface TransformedSection {
60
- id: string;
61
- key: string;
62
- heading: string;
63
- useHeading: boolean;
64
- collapsible: boolean;
65
- collapsed: boolean;
66
- layoutRows: TransformedLayoutRow[];
67
- }
68
-
69
- export function createSectionKey(index: number): string {
70
- return "section-" + index;
71
- }
72
-
73
- export function getTransformedSections(
74
- sections: LayoutSection[],
75
- transformContext: LayoutTransformContext,
76
- ): TransformedSection[] {
77
- const calculatedSections: TransformedSection[] = [];
78
- let previousSection: TransformedSection | null = null;
79
-
80
- sections.forEach((section, index) => {
81
- if (previousSection !== null && section.useHeading === false) {
82
- const sectionKey = createSectionKey(index);
83
- const appendedRows = section.layoutRows
84
- .map((row, i) => rowTransform(row, i, sectionKey, transformContext))
85
- .filter((r): r is TransformedLayoutRow => r !== null);
86
- previousSection.layoutRows.push(...appendedRows);
87
- return;
88
- }
89
-
90
- const newSection = sectionTransform(section, index, transformContext);
91
- if (newSection) {
92
- calculatedSections.push(newSection);
93
- previousSection = newSection;
94
- }
95
- });
96
-
97
- return calculatedSections;
98
- }
99
-
100
- export function sectionTransform(
101
- section: LayoutSection,
102
- index: number,
103
- transformContext: LayoutTransformContext,
104
- ): TransformedSection | null {
105
- const { getSectionCollapsedState } = transformContext;
106
- const sectionKey = createSectionKey(index);
107
- const layoutRows = section.layoutRows
108
- .map((row, i) => rowTransform(row, i, sectionKey, transformContext))
109
- .filter((r): r is TransformedLayoutRow => r !== null);
110
-
111
- if (layoutRows.length === 0 && HIDE_EMPTY_SECTIONS) {
112
- return null;
113
- }
114
-
115
- return {
116
- key: sectionKey,
117
- collapsible: section.collapsible,
118
- collapsed: getSectionCollapsedState(section.id),
119
- useHeading: section.useHeading,
120
- heading: section.heading,
121
- id: section.id,
122
- layoutRows,
123
- };
124
- }
125
-
126
- export function rowTransform(
127
- row: LayoutRow,
128
- index: number,
129
- sectionKey: string,
130
- transformContext: LayoutTransformContext,
131
- ): TransformedLayoutRow | null {
132
- const layoutItems = row.layoutItems.map((item, i) => transformItem(item, i, transformContext));
133
-
134
- const allItemsHaveNoComponents = layoutItems.every((item) => !item.apiName || !item.isField);
135
- if (allItemsHaveNoComponents) {
136
- return null;
137
- }
138
- return {
139
- key: sectionKey + "-" + index,
140
- layoutItems,
141
- };
142
- }
143
-
144
- export function transformItem(
145
- item: LayoutItem,
146
- index: number,
147
- transformContext: LayoutTransformContext,
148
- ): TransformedLayoutItem {
149
- const { recordId, objectInfo, lookupRecords, calculatePicklistValues, formOverrides } =
150
- transformContext;
151
-
152
- let itemApiName: string | undefined;
153
- let itemComponentType: string | undefined;
154
-
155
- if (item.layoutComponents.length >= 1) {
156
- const itemComponent = item.layoutComponents[0];
157
- itemComponentType = itemComponent.componentType;
158
- const componentApiName = itemComponent.apiName;
159
- const topLevelCompoundName =
160
- item.layoutComponents.length > 1 &&
161
- componentApiName &&
162
- objectInfo?.fields?.[componentApiName]?.compoundFieldName;
163
- if (topLevelCompoundName) {
164
- itemApiName = topLevelCompoundName;
165
- } else {
166
- itemApiName = componentApiName ?? undefined;
167
- }
168
- }
169
-
170
- const lookupOptions =
171
- itemApiName != null && lookupRecords?.[itemApiName] != null ? lookupRecords[itemApiName] : null;
172
-
173
- const isFieldType = itemComponentType === "Field";
174
-
175
- const options: PicklistOption[] =
176
- lookupOptions ??
177
- (itemApiName ? (calculatePicklistValues?.(itemApiName, item) ?? null) : null) ??
178
- EMPTY_OPTIONS;
179
-
180
- const fieldMeta = itemApiName ? objectInfo?.fields?.[itemApiName] : undefined;
181
- const layoutComponentApiNames = item.layoutComponents
182
- .filter((c) => c.componentType === "Field" && c.apiName != null)
183
- .map((c) => c.apiName as string);
184
-
185
- let newItem: TransformedLayoutItem = {
186
- key: "item-" + index,
187
- apiName: itemApiName,
188
- contextName: recordId,
189
- label: item.label,
190
- required: item.required,
191
- variant: formOverrides?.fieldVariant ?? "label-stacked",
192
- readOnly: !item.editableForUpdate,
193
- isField: isFieldType,
194
- options,
195
- dataType: fieldMeta?.dataType,
196
- layoutComponentApiNames:
197
- layoutComponentApiNames.length > 0 ? layoutComponentApiNames : undefined,
198
- };
199
-
200
- if (objectInfo?.apiName?.endsWith("__kav")) {
201
- newItem = { ...newItem, readOnly: true };
202
- }
203
-
204
- if (newItem.required === true && newItem.readOnly === true) {
205
- newItem = { ...newItem, required: false };
206
- }
207
-
208
- return newItem;
209
- }
210
-
211
- export function layoutReducer<T>(
212
- sections: TransformedSection[],
213
- reducer: (
214
- acc: T,
215
- ctx: {
216
- section: TransformedSection;
217
- layoutRow: TransformedLayoutRow;
218
- layoutItem: TransformedLayoutItem;
219
- },
220
- ) => T,
221
- initialValue: T,
222
- ): T {
223
- let accumulator = initialValue;
224
- sections.forEach((section) =>
225
- section.layoutRows.forEach((layoutRow) =>
226
- layoutRow.layoutItems.forEach((layoutItem) => {
227
- accumulator = reducer(accumulator, {
228
- section,
229
- layoutRow,
230
- layoutItem,
231
- });
232
- }),
233
- ),
234
- );
235
- return accumulator;
236
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Shared allowlist for link protocols (e.g. for FormattedUrl, and future mailto/tel if rendered as links).
3
- * Centralizes protocol checks so new link types can be added in one place.
4
- */
5
- export const ALLOWED_LINK_PROTOCOLS = ["http:", "https:"] as const;
6
-
7
- export function isAllowedLinkUrl(value: string): boolean {
8
- try {
9
- const u = new URL(value);
10
- return (ALLOWED_LINK_PROTOCOLS as readonly string[]).includes(u.protocol);
11
- } catch {
12
- return false;
13
- }
14
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * Pagination Utilities
3
- *
4
- * Utility functions for pagination-related operations including page size validation.
5
- */
6
-
7
- /**
8
- * Default page size options for pagination
9
- */
10
- export const PAGE_SIZE_OPTIONS = [
11
- { value: "10", label: "10" },
12
- { value: "20", label: "20" },
13
- { value: "50", label: "50" },
14
- ] as const;
15
-
16
- /**
17
- * Valid page size values extracted from PAGE_SIZE_OPTIONS
18
- */
19
- export const VALID_PAGE_SIZES = PAGE_SIZE_OPTIONS.map((opt) => parseInt(opt.value, 10));
20
-
21
- /**
22
- * Validates that a page size is one of the allowed options
23
- * @param size - The page size to validate
24
- * @returns true if valid, false otherwise
25
- *
26
- * @example
27
- * ```tsx
28
- * if (isValidPageSize(userInput)) {
29
- * setPageSize(userInput);
30
- * }
31
- * ```
32
- */
33
- export function isValidPageSize(size: number): boolean {
34
- return VALID_PAGE_SIZES.includes(size);
35
- }
36
-
37
- /**
38
- * Gets a valid page size, defaulting to the first option if invalid
39
- * @param size - The page size to validate
40
- * @returns A valid page size
41
- *
42
- * @example
43
- * ```tsx
44
- * const safePageSize = getValidPageSize(userInput); // Returns valid size or default
45
- * ```
46
- */
47
- export function getValidPageSize(size: number): number {
48
- return isValidPageSize(size) ? size : VALID_PAGE_SIZES[0];
49
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * Record utilities: layout-derived fields for GraphQL fetch, safe keys, ID validation.
3
- *
4
- * calculateFieldsToFetch: from layout + object metadata → field names and relation map;
5
- * used by objectDetailService and list to build columns. findIdFieldForRelationship ensures
6
- * Id + relationship name are both requested for reference display.
7
- *
8
- * @module utils/recordUtils
9
- */
10
-
11
- import type { ObjectInfoResult } from "../types/objectInfo/objectInfo";
12
- import type {
13
- LayoutResponse,
14
- LayoutRow,
15
- LayoutSection,
16
- LayoutItem,
17
- LayoutComponent,
18
- } from "../types/recordDetail/recordDetail";
19
-
20
- /**
21
- * Find the Id field (reference foreign key) whose relationshipName matches the given name,
22
- * so we can request both Id and relationship in the record query for display.
23
- */
24
- function findIdFieldForRelationship(
25
- metadata: ObjectInfoResult,
26
- relationshipName: string,
27
- ): string | null {
28
- if (!metadata.fields || !relationshipName) return null;
29
- for (const [apiName, field] of Object.entries(metadata.fields)) {
30
- const isReference = field.dataType != null && field.dataType.toLowerCase() === "reference";
31
- if (field.relationshipName === relationshipName && (isReference || apiName.endsWith("Id"))) {
32
- return apiName;
33
- }
34
- }
35
- return null;
36
- }
37
-
38
- const getFetchableFieldsFromLayoutItem = function (
39
- metadata: ObjectInfoResult,
40
- layoutItem: LayoutItem,
41
- relationFieldMap: Record<string, string>,
42
- ) {
43
- const fields: Record<string, string> = {};
44
- layoutItem.layoutComponents.forEach((comp: LayoutComponent) => {
45
- // check if this is a field to add
46
- if (!comp.apiName || comp.componentType !== "Field") {
47
- return;
48
- }
49
-
50
- // add field: fieldType
51
- const fieldMetadata = metadata.fields[comp.apiName];
52
- fields[comp.apiName] = fieldMetadata?.dataType ?? "";
53
-
54
- // add relatedField if one exists (Id field -> add relationship name so we request Owner.Name)
55
- if (comp.apiName in metadata.fields) {
56
- const relationshipName = fieldMetadata?.relationshipName;
57
- if (relationshipName) {
58
- fields[relationshipName] = fieldMetadata.dataType ?? "";
59
-
60
- relationFieldMap[comp.apiName] = relationshipName;
61
- }
62
- } else {
63
- // layout component is relationship name (e.g. Owner); ensure we also request the Id
64
- // so buildSelectionTree sees both OwnerId and Owner and requests Owner { Name { value } }
65
- const idField = findIdFieldForRelationship(metadata, comp.apiName);
66
- if (idField) {
67
- const idMeta = metadata.fields[idField];
68
- fields[idField] = idMeta?.dataType ?? "";
69
- relationFieldMap[idField] = comp.apiName;
70
- }
71
- }
72
- });
73
- return fields;
74
- };
75
-
76
- const getFetchableFieldsFromLayoutRow = function (
77
- metadata: ObjectInfoResult,
78
- layoutRow: LayoutRow,
79
- relationFieldMap: Record<string, string>,
80
- ) {
81
- let fieldsFromRow: Record<string, string> = {};
82
- layoutRow.layoutItems.forEach((item: LayoutItem) => {
83
- Object.assign(
84
- fieldsFromRow,
85
- getFetchableFieldsFromLayoutItem(metadata, item, relationFieldMap),
86
- );
87
- });
88
- return fieldsFromRow;
89
- };
90
-
91
- const getFetchableFieldsFromSection = function (
92
- metadata: ObjectInfoResult,
93
- section: LayoutSection,
94
- relationFieldMap: Record<string, string>,
95
- ) {
96
- let fieldsFromSection: Record<string, string> = {};
97
- section.layoutRows.forEach((row: LayoutRow) => {
98
- Object.assign(
99
- fieldsFromSection,
100
- getFetchableFieldsFromLayoutRow(metadata, row, relationFieldMap),
101
- );
102
- });
103
- return fieldsFromSection;
104
- };
105
-
106
- const getFetchableFieldsFromLayout = function (
107
- metadata: ObjectInfoResult,
108
- layout: LayoutResponse,
109
- relationFieldMap: Record<string, string>,
110
- ) {
111
- let fieldsFromLayout: Record<string, string> = {};
112
- layout.sections.forEach((section) => {
113
- Object.assign(
114
- fieldsFromLayout,
115
- getFetchableFieldsFromSection(metadata, section, relationFieldMap),
116
- );
117
- });
118
- return fieldsFromLayout;
119
- };
120
-
121
- /**
122
- * Returns field API names to request for records from layout + object metadata.
123
- * Includes both Id and relationship name for reference fields so GraphQL can fetch display value.
124
- *
125
- * @param metadata - Object info (fields with dataType, relationshipName).
126
- * @param layout - Layout response (sections, layoutItems, layoutComponents).
127
- * @param shouldPrefixedWithEntityName - If true, prefix names with object (e.g. Account.Name).
128
- * @returns [fieldNames, fieldTypes, relationFieldMap] for buildSelectionTree / optionalFields.
129
- */
130
- export const calculateFieldsToFetch = function (
131
- metadata: ObjectInfoResult,
132
- layout: LayoutResponse,
133
- shouldPrefixedWithEntityName: boolean,
134
- ): [string[], string[], Record<string, string>] {
135
- const relationFieldMap: Record<string, string> = {};
136
- // populating fields to query for layout
137
- const fields = getFetchableFieldsFromLayout(metadata, layout, relationFieldMap);
138
- let fieldsToFetch = Object.keys(fields);
139
- if (shouldPrefixedWithEntityName) {
140
- fieldsToFetch = fieldsToFetch.map((field) => `${metadata.ApiName}.${field}`);
141
- }
142
- // populate field types for o11y logging
143
- const fieldTypes = Object.values(fields).filter((fieldType) => fieldType !== "");
144
- return [fieldsToFetch, fieldTypes, relationFieldMap];
145
- };
146
- /** Type guard: true if id is a non-empty string matching 15- or 18-char Salesforce ID format. */
147
- export function isValidSalesforceId(id: string | null | undefined): id is string {
148
- if (!id || typeof id !== "string") return false;
149
- return /^[a-zA-Z0-9]{15}$|^[a-zA-Z0-9]{18}$/.test(id);
150
- }
151
-
152
- /** Safe React key from record id or fallback to prefix-index. */
153
- export function getSafeKey(
154
- recordId: string | null | undefined,
155
- index: number,
156
- prefix: string = "result",
157
- ): string {
158
- return isValidSalesforceId(recordId) ? recordId : `${prefix}-${index}`;
159
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * Sanitization Utilities
3
- *
4
- * Utility functions for sanitizing user input to prevent injection attacks.
5
- * These utilities provide basic sanitization for filter values.
6
- */
7
-
8
- /**
9
- * Sanitizes a string value by removing potentially dangerous characters
10
- * and trimming whitespace.
11
- *
12
- * This is a basic sanitization - for production, consider using a library like DOMPurify for more
13
- * comprehensive sanitization.
14
- * Also, note this is NOT an end-to-end security control.
15
- * Client-side sanitization can be bypassed by any attacker using `curl` or Postman.
16
- * To prevent injection attacks (SOSL Injection, XSS):
17
- * 1. The BACKEND (Salesforce API) handles SOSL injection if parameters are passed correctly.
18
- * 2. React handles XSS automatically when rendering variables in JSX (e.g., <div>{value}</div>).
19
- * Do not rely on this function for end-to-end security enforcement.
20
- *
21
- * @param value - The string value to sanitize
22
- * @returns Sanitized string value
23
- *
24
- * @remarks
25
- * - Removes control characters (except newlines, tabs, carriage returns)
26
- * - Trims leading/trailing whitespace
27
- * - Limits length to prevent DoS attacks (default: 1000 characters)
28
- * - Preserves alphanumeric, spaces, and common punctuation
29
- *
30
- * @example
31
- * ```tsx
32
- * const sanitized = sanitizeFilterValue(userInput);
33
- * ```
34
- */
35
- export function sanitizeFilterValue(value: string, maxLength: number = 1000): string {
36
- if (typeof value !== "string") {
37
- return "";
38
- }
39
-
40
- let sanitized = value.trim();
41
-
42
- if (sanitized.length > maxLength) {
43
- sanitized = sanitized.substring(0, maxLength);
44
- }
45
-
46
- // eslint-disable-next-line no-control-regex -- intentionally matching control chars for sanitization
47
- sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
48
-
49
- return sanitized;
50
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * Fetches the available min/max listing price for the current search (no price/bedroom filters).
3
- * Use to render the filter bar only after knowing the available price range.
4
- */
5
- import { useState, useEffect, useRef } from "react";
6
- import { queryPropertyListingPriceRange } from "@/api/propertyListingGraphQL";
7
-
8
- /** Cap for slider max when dataset has outliers; UI never sees a higher max. */
9
- const SLIDER_PRICE_CAP = 50_000;
10
-
11
- export interface PropertyListingPriceRange {
12
- priceMin: number;
13
- priceMax: number;
14
- /** True when raw max was > cap and we capped for the slider (show "50,000+"). */
15
- maxCapped?: boolean;
16
- }
17
-
18
- /** Fallback when the price-range API call fails. */
19
- const DEFAULT_PRICE_RANGE: PropertyListingPriceRange = { priceMin: 0, priceMax: 100_000 };
20
-
21
- function capPriceRange(range: { priceMin: number; priceMax: number }): PropertyListingPriceRange {
22
- if (range.priceMax <= SLIDER_PRICE_CAP)
23
- return { priceMin: range.priceMin, priceMax: range.priceMax };
24
- return {
25
- priceMin: range.priceMin,
26
- priceMax: SLIDER_PRICE_CAP,
27
- maxCapped: true,
28
- };
29
- }
30
-
31
- export function usePropertyListingPriceRange(searchQuery: string): {
32
- priceRange: PropertyListingPriceRange | null;
33
- loading: boolean;
34
- error: string | null;
35
- } {
36
- const [priceRange, setPriceRange] = useState<PropertyListingPriceRange | null>(null);
37
- const [loading, setLoading] = useState(true);
38
- const [error, setError] = useState<string | null>(null);
39
- const cancelledRef = useRef(false);
40
-
41
- useEffect(() => {
42
- cancelledRef.current = false;
43
- setLoading(true);
44
- setError(null);
45
- queryPropertyListingPriceRange(searchQuery)
46
- .then((range) => {
47
- if (!cancelledRef.current) setPriceRange(range ? capPriceRange(range) : null);
48
- })
49
- .catch((err) => {
50
- if (!cancelledRef.current) {
51
- setError(err instanceof Error ? err.message : "Failed to load price range");
52
- setPriceRange(capPriceRange(DEFAULT_PRICE_RANGE));
53
- }
54
- })
55
- .finally(() => {
56
- if (!cancelledRef.current) setLoading(false);
57
- });
58
- return () => {
59
- cancelledRef.current = true;
60
- };
61
- }, [searchQuery]);
62
-
63
- return { priceRange, loading, error };
64
- }