@salesforce/webapp-template-app-react-sample-b2x-experimental 1.68.0 → 1.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/data/Lease__c.json +13 -0
  3. package/dist/force-app/main/default/webapplications/appreactsampleb2x/package.json +13 -8
  4. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/applicationApi.ts +78 -0
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/graphqlClient.ts +17 -0
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/index.ts +19 -0
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/leadApi.ts +69 -0
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/maintenanceRequestApi.ts +177 -0
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectDetailService.ts +125 -0
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectInfoGraphQLService.ts +194 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/objectInfoService.ts +199 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyDetailGraphQL.ts +497 -0
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/propertyListingGraphQL.ts +190 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/api/recordListGraphQLService.ts +365 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/appLayout.tsx +20 -30
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/FiltersPanel.tsx +375 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/LoadingFallback.tsx +61 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyListingCard.tsx +164 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/PropertyMap.tsx +113 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/SearchResultCard.tsx +131 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/alerts/status-alert.tsx +45 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailFields.tsx +55 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailForm.tsx +146 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailHeader.tsx +34 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/DetailLayoutSections.tsx +80 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/Section.tsx +108 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/SectionRow.tsx +20 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/UiApiDetailForm.tsx +140 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FieldValueDisplay.tsx +73 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedAddress.tsx +29 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedEmail.tsx +17 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedPhone.tsx +24 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedText.tsx +11 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/FormattedUrl.tsx +29 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/detail/formatted/index.ts +6 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterField.tsx +54 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterInput.tsx +55 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/filters/FilterSelect.tsx +72 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/forms/filters-form.tsx +114 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/forms/submit-button.tsx +47 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/layout/card-layout.tsx +19 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/ResultCardFields.tsx +71 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchHeader.tsx +31 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchPagination.tsx +144 -0
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/search/SearchResultsPanel.tsx +197 -0
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/components/shared/GlobalSearchInput.tsx +114 -0
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/constants.ts +39 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/features/global-search/index.ts +33 -0
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/form.tsx +204 -0
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/index.ts +22 -0
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useGeocode.ts +35 -0
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useMaintenanceRequests.ts +39 -0
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useObjectInfoBatch.ts +65 -0
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useObjectSearchData.ts +395 -0
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyAddresses.ts +36 -0
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyDetail.ts +99 -0
  57. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyListingSearch.ts +75 -0
  58. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyMapMarkers.ts +100 -0
  59. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/usePropertyPrimaryImages.ts +51 -0
  60. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useRecordDetailLayout.ts +156 -0
  61. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useRecordListGraphQL.ts +135 -0
  62. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/hooks/useWeather.ts +173 -0
  63. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Application.tsx +263 -76
  64. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Contact.tsx +158 -0
  65. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Dashboard.tsx +137 -65
  66. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/DetailPage.tsx +109 -0
  67. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/GlobalSearch.tsx +229 -0
  68. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Home.tsx +469 -21
  69. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/Maintenance.tsx +244 -95
  70. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyDetails.tsx +211 -39
  71. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertyListings.tsx +26 -10
  72. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearch.tsx +165 -0
  73. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/pages/PropertySearchPlaceholder.tsx +49 -0
  74. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-01.jpg +0 -0
  75. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-02.jpg +0 -0
  76. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-03.jpg +0 -0
  77. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-04.jpg +0 -0
  78. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-05.jpg +0 -0
  79. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-06.jpg +0 -0
  80. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-07.jpg +0 -0
  81. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-08.jpg +0 -0
  82. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-09.jpg +0 -0
  83. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-10.jpg +0 -0
  84. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-11.jpg +0 -0
  85. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-12.jpg +0 -0
  86. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-13.jpg +0 -0
  87. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-14.jpg +0 -0
  88. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-15.jpg +0 -0
  89. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-16.jpg +0 -0
  90. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-17.jpg +0 -0
  91. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-18.jpg +0 -0
  92. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-19.jpg +0 -0
  93. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-20.jpg +0 -0
  94. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-21.jpg +0 -0
  95. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-22.jpg +0 -0
  96. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-23.jpg +0 -0
  97. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-24.jpg +0 -0
  98. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/public/property-25.jpg +0 -0
  99. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/routes.tsx +32 -6
  100. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/styles/global.css +23 -63
  101. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/filters/filters.ts +120 -0
  102. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/filters/picklist.ts +32 -0
  103. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/index.ts +4 -0
  104. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/leaflet.d.ts +17 -0
  105. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/objectInfo/objectInfo.ts +166 -0
  106. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/recordDetail/recordDetail.ts +61 -0
  107. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/types/search/searchResults.ts +229 -0
  108. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/apiUtils.ts +125 -0
  109. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/cacheUtils.ts +76 -0
  110. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/debounce.ts +89 -0
  111. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/fieldUtils.ts +354 -0
  112. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/fieldValueExtractor.ts +67 -0
  113. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/filterUtils.ts +32 -0
  114. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/formDataTransformUtils.ts +260 -0
  115. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/formUtils.ts +142 -0
  116. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/geocode.ts +65 -0
  117. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLNodeFieldUtils.ts +186 -0
  118. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLObjectInfoAdapter.ts +319 -0
  119. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/graphQLRecordAdapter.ts +90 -0
  120. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/index.ts +59 -0
  121. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/layoutTransformUtils.ts +236 -0
  122. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/linkUtils.ts +14 -0
  123. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/paginationUtils.ts +49 -0
  124. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/recordUtils.ts +159 -0
  125. package/dist/force-app/main/default/webapplications/appreactsampleb2x/src/utils/sanitizationUtils.ts +49 -0
  126. package/dist/package.json +1 -1
  127. package/package.json +2 -2
  128. package/dist/force-app/main/default/classes/MaintenanceRequestListAction.cls +0 -111
  129. package/dist/force-app/main/default/classes/MaintenanceRequestListAction.cls-meta.xml +0 -6
  130. package/dist/force-app/main/default/classes/MaintenanceRequestUpdatePriorityAction.cls +0 -93
  131. package/dist/force-app/main/default/classes/MaintenanceRequestUpdatePriorityAction.cls-meta.xml +0 -6
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,49 @@
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
+ }
@@ -0,0 +1,159 @@
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 ? 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 ? 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
+ }
@@ -0,0 +1,49 @@
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
+ sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
47
+
48
+ return sanitized;
49
+ }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.68.0",
3
+ "version": "1.69.0",
4
4
  "description": "Base SFDX project template",
5
5
  "private": true,
6
6
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-app-react-sample-b2x-experimental",
3
- "version": "1.68.0",
3
+ "version": "1.69.0",
4
4
  "description": "B2C sample app template with app shell",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",
@@ -26,7 +26,7 @@
26
26
  "build": {
27
27
  "executor": "@salesforce/webapp-template-cli-experimental:apply-patches",
28
28
  "options": {
29
- "skipDependencyChanges": true
29
+ "skipDependencyChanges": false
30
30
  }
31
31
  },
32
32
  "watch": {
@@ -1,111 +0,0 @@
1
- public with sharing class MaintenanceRequestListAction {
2
- /**
3
- * Lists open Maintenance_Request__c records for Agentforce.
4
- * Returns key fields with security enforced.
5
- */
6
- @InvocableMethod
7
- public static List<MaintenanceRequestRecord> listOpen(
8
- List<ListRequest> requests
9
- ) {
10
- Integer maxResults = 200;
11
- if (requests != null && !requests.isEmpty()) {
12
- Integer candidate = requests[0] != null ? requests[0].maxResults : null;
13
- if (candidate != null) {
14
- if (candidate < 1) {
15
- candidate = 1;
16
- }
17
- if (candidate > 200) {
18
- candidate = 200;
19
- }
20
- maxResults = candidate;
21
- }
22
- }
23
-
24
- try {
25
- List<Maintenance_Request__c> records = [
26
- SELECT
27
- Id,
28
- Name,
29
- Title__c,
30
- Priority__c,
31
- Status__c,
32
- Type__c,
33
- Date_Requested__c,
34
- Estimated_Cost__c,
35
- Property__r.Name,
36
- Tenant__r.Name,
37
- Assigned_Worker__r.Name
38
- FROM Maintenance_Request__c
39
- WHERE Status__c = 'Open'
40
- WITH SECURITY_ENFORCED
41
- ORDER BY Date_Requested__c DESC, CreatedDate DESC
42
- LIMIT :maxResults
43
- ];
44
-
45
- List<MaintenanceRequestRecord> out = new List<MaintenanceRequestRecord>();
46
- for (Maintenance_Request__c mr : records) {
47
- MaintenanceRequestRecord r = new MaintenanceRequestRecord();
48
- r.recordId = mr.Id;
49
- r.requestNumber = mr.Name;
50
- r.title = mr.Title__c;
51
- r.priority = mr.Priority__c;
52
- r.status = mr.Status__c;
53
- r.type = mr.Type__c;
54
- r.dateRequested = mr.Date_Requested__c;
55
- r.estimatedCost = mr.Estimated_Cost__c;
56
- r.propertyName = mr.Property__r != null ? mr.Property__r.Name : null;
57
- r.tenantName = mr.Tenant__r != null ? mr.Tenant__r.Name : null;
58
- r.assignedWorkerName = mr.Assigned_Worker__r != null
59
- ? mr.Assigned_Worker__r.Name
60
- : null;
61
- out.add(r);
62
- }
63
- return out;
64
- } catch (Exception e) {
65
- // Surface a safe error for Agentforce/Flow
66
- throw new AuraHandledException(
67
- 'Unable to list maintenance requests: ' + e.getMessage()
68
- );
69
- }
70
- }
71
-
72
- public class ListRequest {
73
- @InvocableVariable
74
- public Integer maxResults;
75
- }
76
-
77
- public class MaintenanceRequestRecord {
78
- @InvocableVariable
79
- public Id recordId;
80
-
81
- @InvocableVariable
82
- public String requestNumber;
83
-
84
- @InvocableVariable
85
- public String title;
86
-
87
- @InvocableVariable
88
- public String priority;
89
-
90
- @InvocableVariable
91
- public String status;
92
-
93
- @InvocableVariable
94
- public String type;
95
-
96
- @InvocableVariable
97
- public Date dateRequested;
98
-
99
- @InvocableVariable
100
- public Decimal estimatedCost;
101
-
102
- @InvocableVariable
103
- public String propertyName;
104
-
105
- @InvocableVariable
106
- public String tenantName;
107
-
108
- @InvocableVariable
109
- public String assignedWorkerName;
110
- }
111
- }
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" ?>
2
- <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3
- <apiVersion>62.0</apiVersion>
4
- <status>Active</status>
5
-
6
- </ApexClass>
@@ -1,93 +0,0 @@
1
- public with sharing class MaintenanceRequestUpdatePriorityAction {
2
- /**
3
- * Updates the Priority__c field on a Maintenance_Request__c record.
4
- * Validates against allowed picklist values.
5
- */
6
- @InvocableMethod
7
- public static List<UpdateResult> updatePriority(
8
- List<UpdateRequest> requests
9
- ) {
10
- if (requests == null || requests.isEmpty() || requests[0] == null) {
11
- throw new AuraHandledException('Input is required');
12
- }
13
-
14
- UpdateRequest req = requests[0];
15
- if (req.recordId == null) {
16
- throw new AuraHandledException('recordId is required');
17
- }
18
- if (String.isBlank(req.priority)) {
19
- throw new AuraHandledException('priority is required');
20
- }
21
-
22
- // Normalize priority input (trim and case-insensitive match)
23
- String input = req.priority.trim();
24
- Set<String> allowed = new Set<String>{
25
- 'Low',
26
- 'Medium',
27
- 'High',
28
- 'Emergency'
29
- };
30
- // Try to map case-insensitive matches to canonical labels
31
- String canonical = null;
32
- for (String a : allowed) {
33
- if (a.equalsIgnoreCase(input)) {
34
- canonical = a;
35
- break;
36
- }
37
- }
38
- if (canonical == null) {
39
- throw new AuraHandledException(
40
- 'Invalid priority. Allowed values: Low, Medium, High, Emergency'
41
- );
42
- }
43
-
44
- try {
45
- // Field-level security check for Priority__c
46
- if (
47
- !Schema.SObjectType.Maintenance_Request__c.fields.Priority__c.isUpdateable()
48
- ) {
49
- throw new AuraHandledException(
50
- 'Insufficient field permissions to update Priority'
51
- );
52
- }
53
- Maintenance_Request__c recordToUpdate = new Maintenance_Request__c(
54
- Id = req.recordId,
55
- Priority__c = canonical
56
- );
57
- update recordToUpdate;
58
-
59
- UpdateResult result = new UpdateResult();
60
- result.recordId = req.recordId;
61
- result.priority = canonical;
62
- result.message = 'Priority updated';
63
- return new List<UpdateResult>{ result };
64
- } catch (DmlException d) {
65
- throw new AuraHandledException(
66
- 'Failed to update priority: ' + d.getDmlMessage(0)
67
- );
68
- } catch (Exception e) {
69
- throw new AuraHandledException(
70
- 'Failed to update priority: ' + e.getMessage()
71
- );
72
- }
73
- }
74
-
75
- public class UpdateRequest {
76
- @InvocableVariable
77
- public Id recordId;
78
-
79
- @InvocableVariable
80
- public String priority;
81
- }
82
-
83
- public class UpdateResult {
84
- @InvocableVariable
85
- public Id recordId;
86
-
87
- @InvocableVariable
88
- public String priority;
89
-
90
- @InvocableVariable
91
- public String message;
92
- }
93
- }
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8" ?>
2
- <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3
- <apiVersion>62.0</apiVersion>
4
- <status>Active</status>
5
-
6
- </ApexClass>