@salesforce/webapp-template-app-react-sample-b2x-experimental 1.116.4 → 1.116.6
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.
- package/dist/CHANGELOG.md +16 -0
- package/dist/force-app/main/default/data/Property__c.json +75 -25
- package/dist/force-app/main/default/objects/Property__c/fields/Coordinates__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +5 -0
- package/dist/force-app/main/default/permissionsets/Tenant_Maintenance_Access.permissionset-meta.xml +5 -0
- package/dist/force-app/main/default/webapplications/propertyrentalapp/index.html +1 -32
- package/dist/force-app/main/default/webapplications/propertyrentalapp/package.json +3 -3
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/api/graphql-operations-types.ts +380 -618
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/api/properties/propertyDetailGraphQL.ts +29 -0
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/api/properties/propertyListingGraphQL.ts +22 -0
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/app.tsx +1 -4
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/hooks/usePropertyMapMarkers.ts +49 -4
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/pages/PropertyDetails.tsx +7 -1
- package/dist/force-app/main/default/webapplications/propertyrentalapp/src/routes.tsx +2 -2
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/package.json +1 -1
|
@@ -101,6 +101,14 @@ const PROPERTY_QUERY = gql`
|
|
|
101
101
|
value
|
|
102
102
|
displayValue
|
|
103
103
|
}
|
|
104
|
+
Coordinates__Latitude__s @optional {
|
|
105
|
+
value
|
|
106
|
+
displayValue
|
|
107
|
+
}
|
|
108
|
+
Coordinates__Longitude__s @optional {
|
|
109
|
+
value
|
|
110
|
+
displayValue
|
|
111
|
+
}
|
|
104
112
|
Type__c @optional {
|
|
105
113
|
value
|
|
106
114
|
displayValue
|
|
@@ -143,6 +151,10 @@ export interface PropertyDetail {
|
|
|
143
151
|
bathrooms: number | string | null;
|
|
144
152
|
squareFootage: number | string | null;
|
|
145
153
|
description: string | null;
|
|
154
|
+
coordinates: {
|
|
155
|
+
lat: number | null;
|
|
156
|
+
lng: number | null;
|
|
157
|
+
} | null;
|
|
146
158
|
}
|
|
147
159
|
|
|
148
160
|
export async function fetchPropertyById(propertyId: string): Promise<PropertyDetail | null> {
|
|
@@ -178,6 +190,23 @@ export async function fetchPropertyById(propertyId: string): Promise<PropertyDet
|
|
|
178
190
|
node.Description__c?.value != null
|
|
179
191
|
? String(node.Description__c.value)
|
|
180
192
|
: (node.Description__c?.displayValue ?? null),
|
|
193
|
+
coordinates:
|
|
194
|
+
node.Coordinates__Latitude__s && node.Coordinates__Longitude__s
|
|
195
|
+
? {
|
|
196
|
+
lat:
|
|
197
|
+
typeof node.Coordinates__Latitude__s.value === "number"
|
|
198
|
+
? node.Coordinates__Latitude__s.value
|
|
199
|
+
: node.Coordinates__Latitude__s.displayValue != null
|
|
200
|
+
? Number(node.Coordinates__Latitude__s.displayValue)
|
|
201
|
+
: null,
|
|
202
|
+
lng:
|
|
203
|
+
typeof node.Coordinates__Longitude__s.value === "number"
|
|
204
|
+
? node.Coordinates__Longitude__s.value
|
|
205
|
+
: node.Coordinates__Longitude__s.displayValue != null
|
|
206
|
+
? Number(node.Coordinates__Longitude__s.displayValue)
|
|
207
|
+
: null,
|
|
208
|
+
}
|
|
209
|
+
: null,
|
|
181
210
|
};
|
|
182
211
|
}
|
|
183
212
|
|
|
@@ -28,6 +28,8 @@ type PropertyListingNode = {
|
|
|
28
28
|
Property__r?: {
|
|
29
29
|
Name?: { value?: string | null; displayValue?: string | null } | null;
|
|
30
30
|
Address__c?: { value?: string | null; displayValue?: string | null } | null;
|
|
31
|
+
Coordinates__Latitude__s?: { value?: number | null; displayValue?: string | null } | null;
|
|
32
|
+
Coordinates__Longitude__s?: { value?: number | null; displayValue?: string | null } | null;
|
|
31
33
|
Bedrooms__c?: { value?: number | null; displayValue?: string | null } | null;
|
|
32
34
|
} | null;
|
|
33
35
|
};
|
|
@@ -83,6 +85,18 @@ function nodeToSearchResultRecordData(node: PropertyListingNode): SearchResultRe
|
|
|
83
85
|
prop.Bedrooms__c as { value?: unknown; displayValue?: string | null },
|
|
84
86
|
);
|
|
85
87
|
}
|
|
88
|
+
const lat = prop?.Coordinates__Latitude__s;
|
|
89
|
+
if (lat != null && typeof lat === "object" && "value" in lat) {
|
|
90
|
+
fields["Property__r.Coordinates__Latitude__s"] = nodeToFieldValue(
|
|
91
|
+
lat as { value?: unknown; displayValue?: string | null },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const lng = prop?.Coordinates__Longitude__s;
|
|
95
|
+
if (lng != null && typeof lng === "object" && "value" in lng) {
|
|
96
|
+
fields["Property__r.Coordinates__Longitude__s"] = nodeToFieldValue(
|
|
97
|
+
lng as { value?: unknown; displayValue?: string | null },
|
|
98
|
+
);
|
|
99
|
+
}
|
|
86
100
|
return {
|
|
87
101
|
id: node.Id,
|
|
88
102
|
apiName: typeof node.ApiName === "string" ? node.ApiName : OBJECT_API_NAME,
|
|
@@ -134,6 +148,14 @@ const PROPERTY_LISTINGS_QUERY = gql`
|
|
|
134
148
|
value
|
|
135
149
|
displayValue
|
|
136
150
|
}
|
|
151
|
+
Coordinates__Latitude__s @optional {
|
|
152
|
+
value
|
|
153
|
+
displayValue
|
|
154
|
+
}
|
|
155
|
+
Coordinates__Longitude__s @optional {
|
|
156
|
+
value
|
|
157
|
+
displayValue
|
|
158
|
+
}
|
|
137
159
|
Bedrooms__c @optional {
|
|
138
160
|
value
|
|
139
161
|
displayValue
|
|
@@ -2,7 +2,6 @@ import { createBrowserRouter, RouterProvider } from "react-router";
|
|
|
2
2
|
import { routes } from "@/routes";
|
|
3
3
|
import { StrictMode, Component, type ReactNode } from "react";
|
|
4
4
|
import { createRoot } from "react-dom/client";
|
|
5
|
-
import { AuthProvider } from "./features/authentication/context/AuthContext";
|
|
6
5
|
import "./styles/global.css";
|
|
7
6
|
|
|
8
7
|
class ErrorBoundary extends Component<
|
|
@@ -37,9 +36,7 @@ if (rootEl) {
|
|
|
37
36
|
createRoot(rootEl).render(
|
|
38
37
|
<StrictMode>
|
|
39
38
|
<ErrorBoundary>
|
|
40
|
-
<
|
|
41
|
-
<RouterProvider router={router} />
|
|
42
|
-
</AuthProvider>
|
|
39
|
+
<RouterProvider router={router} />
|
|
43
40
|
</ErrorBoundary>
|
|
44
41
|
</StrictMode>,
|
|
45
42
|
);
|
|
@@ -19,6 +19,26 @@ function getListingName(record: {
|
|
|
19
19
|
return "Property";
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function toFiniteNumber(value: unknown): number | null {
|
|
23
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
24
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
25
|
+
const n = Number(value);
|
|
26
|
+
return Number.isFinite(n) ? n : null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getCoordinatesFromRecord(record: {
|
|
32
|
+
fields?: Record<string, { value?: unknown }>;
|
|
33
|
+
}): { lat: number; lng: number } | null {
|
|
34
|
+
const latRaw = record.fields?.["Property__r.Coordinates__Latitude__s"]?.value;
|
|
35
|
+
const lngRaw = record.fields?.["Property__r.Coordinates__Longitude__s"]?.value;
|
|
36
|
+
const lat = toFiniteNumber(latRaw);
|
|
37
|
+
const lng = toFiniteNumber(lngRaw);
|
|
38
|
+
if (lat == null || lng == null) return null;
|
|
39
|
+
return { lat, lng };
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
/** Round to 5 decimals (~1 m) so near-duplicate coords group together */
|
|
23
43
|
function key(lat: number, lng: number): string {
|
|
24
44
|
return `${lat.toFixed(5)},${lng.toFixed(5)}`;
|
|
@@ -84,14 +104,39 @@ export function usePropertyMapMarkers(results: SearchResultRecord[]): {
|
|
|
84
104
|
let cancelled = false;
|
|
85
105
|
setLoading(true);
|
|
86
106
|
const uniqIds = [...new Set(propertyIds)];
|
|
87
|
-
|
|
107
|
+
const directMarkers: MapMarker[] = [];
|
|
108
|
+
const missingIds: string[] = [];
|
|
109
|
+
for (const r of results) {
|
|
110
|
+
if (!r?.record) continue;
|
|
111
|
+
const id = getPropertyIdFromRecord(r.record);
|
|
112
|
+
if (!id || !uniqIds.includes(id)) continue;
|
|
113
|
+
const coords = getCoordinatesFromRecord(r.record);
|
|
114
|
+
if (coords) {
|
|
115
|
+
directMarkers.push({
|
|
116
|
+
lat: coords.lat,
|
|
117
|
+
lng: coords.lng,
|
|
118
|
+
label: propertyIdToLabel.get(id) ?? "Property",
|
|
119
|
+
propertyId: id,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const id of uniqIds) {
|
|
124
|
+
const hasDirect = directMarkers.some((m) => m.propertyId === id);
|
|
125
|
+
if (!hasDirect) missingIds.push(id);
|
|
126
|
+
}
|
|
127
|
+
if (missingIds.length === 0) {
|
|
128
|
+
setMarkers(spreadDuplicateMarkers(directMarkers));
|
|
129
|
+
setLoading(false);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
fetchPropertyAddresses(missingIds)
|
|
88
133
|
.then((idToAddress) => {
|
|
89
134
|
if (cancelled) return;
|
|
90
135
|
const toGeocode = Object.entries(idToAddress).filter(
|
|
91
136
|
([, addr]) => addr != null && addr.trim() !== "",
|
|
92
137
|
);
|
|
93
138
|
if (toGeocode.length === 0) {
|
|
94
|
-
setMarkers(
|
|
139
|
+
setMarkers(spreadDuplicateMarkers(directMarkers));
|
|
95
140
|
setLoading(false);
|
|
96
141
|
return;
|
|
97
142
|
}
|
|
@@ -111,7 +156,7 @@ export function usePropertyMapMarkers(results: SearchResultRecord[]): {
|
|
|
111
156
|
)
|
|
112
157
|
.then((resolved) => {
|
|
113
158
|
if (cancelled) return;
|
|
114
|
-
const
|
|
159
|
+
const geocoded: MapMarker[] = resolved
|
|
115
160
|
.filter((r): r is { id: string; coords: { lat: number; lng: number } } => r != null)
|
|
116
161
|
.map(({ id, coords }) => ({
|
|
117
162
|
lat: coords.lat,
|
|
@@ -119,7 +164,7 @@ export function usePropertyMapMarkers(results: SearchResultRecord[]): {
|
|
|
119
164
|
label: propertyIdToLabel.get(id) ?? "Property",
|
|
120
165
|
propertyId: id,
|
|
121
166
|
}));
|
|
122
|
-
setMarkers(spreadDuplicateMarkers(
|
|
167
|
+
setMarkers(spreadDuplicateMarkers([...directMarkers, ...geocoded]));
|
|
123
168
|
})
|
|
124
169
|
.catch(() => {
|
|
125
170
|
if (!cancelled) setMarkers([]);
|
package/dist/force-app/main/default/webapplications/propertyrentalapp/src/pages/PropertyDetails.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|
|
5
5
|
import PropertyMap from "@/components/properties/PropertyMap";
|
|
6
6
|
import { usePropertyDetail } from "@/hooks/usePropertyDetail";
|
|
7
7
|
import { useGeocode } from "@/hooks/useGeocode";
|
|
8
|
+
import type { GeocodeResult } from "@/utils/geocode";
|
|
8
9
|
|
|
9
10
|
function formatCurrency(val: number | string | null): string {
|
|
10
11
|
if (val == null) return "—";
|
|
@@ -108,7 +109,12 @@ export default function PropertyDetails() {
|
|
|
108
109
|
const { id } = useParams<{ id: string }>();
|
|
109
110
|
const { listing, property, images, costs, features, loading, error } = usePropertyDetail(id);
|
|
110
111
|
const addressForGeocode = property?.address?.replace(/\n/g, ", ") ?? null;
|
|
111
|
-
|
|
112
|
+
// Always call hook in the same order; disable geocoding when coordinates already exist.
|
|
113
|
+
const { coords: geocodedCoords } = useGeocode(property?.coordinates ? null : addressForGeocode);
|
|
114
|
+
const addressCoords: GeocodeResult | null =
|
|
115
|
+
property?.coordinates?.lat != null && property?.coordinates?.lng != null
|
|
116
|
+
? { lat: property.coordinates.lat, lng: property.coordinates.lng }
|
|
117
|
+
: geocodedCoords;
|
|
112
118
|
|
|
113
119
|
if (loading) {
|
|
114
120
|
return <PropertyDetailsSkeleton />;
|
|
@@ -3,6 +3,7 @@ import Home from '@/pages/Home';
|
|
|
3
3
|
import NotFound from '@/pages/NotFound';
|
|
4
4
|
import AccountSearch from "./features/object-search/__examples__/pages/AccountSearch";
|
|
5
5
|
import AccountObjectDetail from "./features/object-search/__examples__/pages/AccountObjectDetailPage";
|
|
6
|
+
import AuthAppLayout from "./features/authentication/layouts/AuthAppLayout";
|
|
6
7
|
import Login from "./features/authentication/pages/Login";
|
|
7
8
|
import Register from "./features/authentication/pages/Register";
|
|
8
9
|
import ForgotPassword from "./features/authentication/pages/ForgotPassword";
|
|
@@ -12,7 +13,6 @@ import ChangePassword from "./features/authentication/pages/ChangePassword";
|
|
|
12
13
|
import AuthenticationRoute from "./features/authentication/layouts/authenticationRouteLayout";
|
|
13
14
|
import PrivateRoute from "./features/authentication/layouts/privateRouteLayout";
|
|
14
15
|
import { ROUTES } from "./features/authentication/authenticationConfig";
|
|
15
|
-
import AppLayout from "@/appLayout";
|
|
16
16
|
import Dashboard from "@/pages/Dashboard";
|
|
17
17
|
import Maintenance from "@/pages/Maintenance";
|
|
18
18
|
import PropertySearch from "@/pages/PropertySearch";
|
|
@@ -23,7 +23,7 @@ import Contact from "@/pages/Contact";
|
|
|
23
23
|
export const routes: RouteObject[] = [
|
|
24
24
|
{
|
|
25
25
|
path: "/",
|
|
26
|
-
element: <
|
|
26
|
+
element: <AuthAppLayout />,
|
|
27
27
|
children: [
|
|
28
28
|
{
|
|
29
29
|
index: true,
|
package/dist/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
3
|
-
"version": "1.116.
|
|
3
|
+
"version": "1.116.6",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
9
|
-
"version": "1.116.
|
|
9
|
+
"version": "1.116.6",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@lwc/eslint-plugin-lwc": "^3.3.0",
|
package/dist/package.json
CHANGED
package/package.json
CHANGED