@salesforce/ui-bundle-template-app-react-sample-b2x 12.6.3 → 12.7.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.
- package/dist/CHANGELOG.md +9 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/package.json +6 -6
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/graphql-operations-types.ts +1 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/appLayout.tsx +5 -4
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layout/VerticalNav.tsx +60 -27
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestList.tsx +18 -17
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyListingCard.tsx +2 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyMap.tsx +10 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertySearchFilters.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/button.tsx +2 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/checkbox.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/input.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/select.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/context/TenantAccessContext.tsx +23 -6
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/forms/auth-form.tsx +13 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/form.tsx +12 -3
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layout/centered-page-layout.tsx +8 -40
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/TenantRoute.tsx +28 -4
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/privateRouteLayout.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/menu/AuthMenu.tsx +43 -5
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ChangePassword.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ForgotPassword.tsx +8 -4
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Login.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Register.tsx +1 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ResetPassword.tsx +2 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +30 -7
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/api/objectSearchService.ts +5 -10
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/PaginationControls.tsx +38 -3
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useReanchorReadingCursor.ts +54 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Application.tsx +30 -8
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Contact.tsx +95 -48
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Home.tsx +34 -23
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Maintenance.tsx +5 -1
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertyDetails.tsx +48 -16
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearch.tsx +15 -5
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/styles/global.css +2 -2
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.tsbuildinfo +1 -1
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/dist/scripts/network-senders.mjs +84 -0
- package/dist/scripts/org-setup-xml.mjs +11 -6
- package/dist/scripts/org-setup.mjs +13 -5
- package/package.json +1 -1
|
@@ -4,7 +4,6 @@ import { Button } from "@/components/ui/button";
|
|
|
4
4
|
import { Input } from "@/components/ui/input";
|
|
5
5
|
import { Label } from "@/components/ui/label";
|
|
6
6
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
7
|
-
import { CenteredPageLayout } from "@/features/authentication/layout/centered-page-layout";
|
|
8
7
|
import { Loader2 } from "lucide-react";
|
|
9
8
|
import { createContactUsLead } from "@/api/leads/leadApi";
|
|
10
9
|
import { useAuth } from "@/features/authentication/context/AuthContext";
|
|
@@ -12,32 +11,35 @@ import { fetchUserProfile } from "@/features/authentication/api/userProfileApi";
|
|
|
12
11
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
13
12
|
import type { UserInfo } from "@/api/leads/leadApi";
|
|
14
13
|
|
|
15
|
-
function SuccessCard() {
|
|
16
|
-
const
|
|
14
|
+
function SuccessCard({ onAnotherMessage }: { onAnotherMessage: () => void }) {
|
|
15
|
+
const headingRef = useRef<HTMLHeadingElement>(null);
|
|
17
16
|
useEffect(() => {
|
|
18
|
-
|
|
17
|
+
headingRef.current?.focus();
|
|
19
18
|
}, []);
|
|
20
19
|
return (
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</
|
|
38
|
-
</
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
<Card>
|
|
21
|
+
<CardHeader>
|
|
22
|
+
<h2
|
|
23
|
+
ref={headingRef}
|
|
24
|
+
tabIndex={-1}
|
|
25
|
+
aria-describedby="contact-success-description"
|
|
26
|
+
className="text-xl font-semibold"
|
|
27
|
+
>
|
|
28
|
+
Message sent
|
|
29
|
+
</h2>
|
|
30
|
+
</CardHeader>
|
|
31
|
+
<CardContent className="space-y-4">
|
|
32
|
+
<p id="contact-success-description" className="text-muted-foreground">
|
|
33
|
+
Thank you for reaching out. We've received your message and will get back to you soon.
|
|
34
|
+
</p>
|
|
35
|
+
<Button asChild variant="outline">
|
|
36
|
+
<Link to="/">Back to Home</Link>
|
|
37
|
+
</Button>
|
|
38
|
+
<Button type="button" onClick={onAnotherMessage}>
|
|
39
|
+
Send another message
|
|
40
|
+
</Button>
|
|
41
|
+
</CardContent>
|
|
42
|
+
</Card>
|
|
41
43
|
);
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -48,6 +50,7 @@ interface ContactFormProps {
|
|
|
48
50
|
submitting: boolean;
|
|
49
51
|
submitError: string | null;
|
|
50
52
|
loading?: boolean;
|
|
53
|
+
focusOnReady?: boolean;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
function ContactForm({
|
|
@@ -57,7 +60,14 @@ function ContactForm({
|
|
|
57
60
|
submitting,
|
|
58
61
|
submitError,
|
|
59
62
|
loading,
|
|
63
|
+
focusOnReady,
|
|
60
64
|
}: ContactFormProps) {
|
|
65
|
+
const formRef = useRef<HTMLFormElement>(null);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (focusOnReady && !loading && document.activeElement === document.body) {
|
|
68
|
+
formRef.current?.querySelector<HTMLElement>("input:not(:disabled), textarea")?.focus();
|
|
69
|
+
}
|
|
70
|
+
}, [focusOnReady, loading]);
|
|
61
71
|
const [firstName, setFirstName] = useState("");
|
|
62
72
|
const [lastName, setLastName] = useState("");
|
|
63
73
|
const [email, setEmail] = useState("");
|
|
@@ -84,6 +94,7 @@ function ContactForm({
|
|
|
84
94
|
</CardHeader>
|
|
85
95
|
<CardContent>
|
|
86
96
|
<form
|
|
97
|
+
ref={formRef}
|
|
87
98
|
onSubmit={onSubmit}
|
|
88
99
|
className="space-y-4"
|
|
89
100
|
aria-disabled={loading}
|
|
@@ -180,11 +191,17 @@ export default function Contact() {
|
|
|
180
191
|
const [submitting, setSubmitting] = useState(false);
|
|
181
192
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
182
193
|
const [submitted, setSubmitted] = useState(false);
|
|
194
|
+
const [profileAttempt, setProfileAttempt] = useState(0);
|
|
195
|
+
const [focusForm, setFocusForm] = useState(false);
|
|
183
196
|
|
|
184
|
-
const {
|
|
197
|
+
const {
|
|
198
|
+
data: userInfo,
|
|
199
|
+
loading: profileLoading,
|
|
200
|
+
error: profileError,
|
|
201
|
+
} = useAsyncData(() => {
|
|
185
202
|
if (!isAuthenticated || !user?.id) return Promise.resolve(null);
|
|
186
203
|
return fetchUserProfile<UserInfo>(user.id);
|
|
187
|
-
}, [isAuthenticated, user?.id]);
|
|
204
|
+
}, [isAuthenticated, user?.id, profileAttempt]);
|
|
188
205
|
|
|
189
206
|
const handleSubmit = useCallback(
|
|
190
207
|
async (e: SubmitEvent<HTMLFormElement>) => {
|
|
@@ -224,34 +241,64 @@ export default function Contact() {
|
|
|
224
241
|
[isAuthenticated, userInfo],
|
|
225
242
|
);
|
|
226
243
|
|
|
227
|
-
const isLoading = loading || (isAuthenticated &&
|
|
244
|
+
const isLoading = loading || (isAuthenticated && profileLoading);
|
|
245
|
+
const profileUnavailable = isAuthenticated && !isLoading && (profileError || !userInfo);
|
|
228
246
|
|
|
229
247
|
if (submitted) {
|
|
230
248
|
return (
|
|
231
|
-
<
|
|
232
|
-
<
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
249
|
+
<div className="flex min-h-svh w-full justify-center p-6 md:p-10 items-start">
|
|
250
|
+
<div className="w-full max-w-md">
|
|
251
|
+
<h1 className="mb-2 text-2xl font-semibold text-foreground">Contact Us</h1>
|
|
252
|
+
<p className="mb-6 text-muted-foreground">
|
|
253
|
+
Have a question or feedback? Send us a message and we'll respond as soon as we can.
|
|
254
|
+
</p>
|
|
255
|
+
<SuccessCard
|
|
256
|
+
onAnotherMessage={() => {
|
|
257
|
+
setFocusForm(true);
|
|
258
|
+
setSubmitted(false);
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
238
263
|
);
|
|
239
264
|
}
|
|
240
265
|
|
|
241
266
|
return (
|
|
242
|
-
<
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
267
|
+
<div className="flex min-h-svh w-full justify-center p-6 md:p-10 items-start">
|
|
268
|
+
<div className="w-full max-w-md">
|
|
269
|
+
<h1 className="mb-2 text-2xl font-semibold text-foreground">Contact Us</h1>
|
|
270
|
+
<p className="mb-6 text-muted-foreground">
|
|
271
|
+
Have a question or feedback? Send us a message and we'll respond as soon as we can.
|
|
272
|
+
</p>
|
|
273
|
+
{profileUnavailable ? (
|
|
274
|
+
<Card>
|
|
275
|
+
<CardContent className="space-y-4 pt-6">
|
|
276
|
+
<p role="alert" className="text-destructive">
|
|
277
|
+
Unable to load your contact information. Please try again.
|
|
278
|
+
</p>
|
|
279
|
+
<Button
|
|
280
|
+
type="button"
|
|
281
|
+
onClick={() => {
|
|
282
|
+
setFocusForm(true);
|
|
283
|
+
setProfileAttempt((attempt) => attempt + 1);
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
Try again
|
|
287
|
+
</Button>
|
|
288
|
+
</CardContent>
|
|
289
|
+
</Card>
|
|
290
|
+
) : (
|
|
291
|
+
<ContactForm
|
|
292
|
+
isAuthenticated={isAuthenticated}
|
|
293
|
+
userInfo={userInfo}
|
|
294
|
+
onSubmit={handleSubmit}
|
|
295
|
+
submitting={submitting}
|
|
296
|
+
submitError={submitError}
|
|
297
|
+
loading={isLoading}
|
|
298
|
+
focusOnReady={focusForm}
|
|
299
|
+
/>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
256
303
|
);
|
|
257
304
|
}
|
|
@@ -106,10 +106,11 @@ export default function Home() {
|
|
|
106
106
|
text: string;
|
|
107
107
|
} | null>(null);
|
|
108
108
|
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const {
|
|
110
|
+
data: searchResult,
|
|
111
|
+
loading: featuredLoading,
|
|
112
|
+
error: featuredError,
|
|
113
|
+
} = useAsyncData<PropertySearchResult>(() => searchProperties({ first: FEATURED_PAGE_SIZE }), []);
|
|
113
114
|
|
|
114
115
|
const featuredResults = useMemo(
|
|
115
116
|
() =>
|
|
@@ -176,16 +177,20 @@ export default function Home() {
|
|
|
176
177
|
<div className="space-y-0">
|
|
177
178
|
{/* ——— Hero ——— */}
|
|
178
179
|
<div className="relative w-full overflow-hidden rounded-2xl">
|
|
179
|
-
<div className="relative
|
|
180
|
+
<div className="relative grid min-h-[280px] w-full md:min-h-[320px]">
|
|
181
|
+
<div
|
|
182
|
+
aria-hidden="true"
|
|
183
|
+
className="col-start-1 row-start-1 aspect-[21/9] w-full self-start md:aspect-[3/1]"
|
|
184
|
+
/>
|
|
180
185
|
<img
|
|
181
186
|
src={HERO_IMAGE}
|
|
182
187
|
alt=""
|
|
183
|
-
className="h-full w-full object-cover"
|
|
188
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
184
189
|
loading="eager"
|
|
185
190
|
fetchPriority="high"
|
|
186
191
|
/>
|
|
187
|
-
<div className="absolute inset-0 bg-black/
|
|
188
|
-
<div className="
|
|
192
|
+
<div className="absolute inset-0 bg-black/65" />
|
|
193
|
+
<div className="relative col-start-1 row-start-1 flex min-w-0 flex-col items-center justify-center gap-6 px-4 py-6">
|
|
189
194
|
<div className="w-full max-w-2xl text-center">
|
|
190
195
|
<p className="mb-2 text-xs font-medium uppercase tracking-[0.2em] text-white/80">
|
|
191
196
|
The Zen Way to Lease
|
|
@@ -212,7 +217,7 @@ export default function Home() {
|
|
|
212
217
|
<Button
|
|
213
218
|
type="submit"
|
|
214
219
|
size="lg"
|
|
215
|
-
className="min-h-12 shrink-0 cursor-pointer rounded-xl bg-primary px-8 font-medium text-primary-foreground shadow-md transition-colors duration-200 hover:bg-primary/
|
|
220
|
+
className="h-auto min-h-12 shrink-0 cursor-pointer whitespace-normal rounded-xl bg-primary px-8 font-medium text-primary-foreground shadow-md transition-colors duration-200 hover:bg-primary/95 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2"
|
|
216
221
|
>
|
|
217
222
|
Find Home
|
|
218
223
|
</Button>
|
|
@@ -237,12 +242,17 @@ export default function Home() {
|
|
|
237
242
|
Featured Properties
|
|
238
243
|
</h2>
|
|
239
244
|
{featuredLoading ? (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
<>
|
|
246
|
+
<p role="status" className="sr-only">
|
|
247
|
+
Loading featured properties…
|
|
248
|
+
</p>
|
|
249
|
+
<div aria-hidden="true" className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
250
|
+
{[1, 2, 3].map((i) => (
|
|
251
|
+
<PropertyListingCardSkeleton key={i} />
|
|
252
|
+
))}
|
|
253
|
+
</div>
|
|
254
|
+
</>
|
|
255
|
+
) : !featuredError && validFeatured.length > 0 ? (
|
|
246
256
|
<FeaturedPropertiesGrid
|
|
247
257
|
results={validFeatured}
|
|
248
258
|
primaryImagesMap={primaryImagesMap}
|
|
@@ -250,11 +260,12 @@ export default function Home() {
|
|
|
250
260
|
amenitiesMap={amenitiesMap}
|
|
251
261
|
/>
|
|
252
262
|
) : (
|
|
253
|
-
<div
|
|
254
|
-
role="status"
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
<div className="rounded-xl bg-white/60 px-6 py-10 text-center text-muted-foreground">
|
|
264
|
+
<p role={featuredError ? "alert" : "status"}>
|
|
265
|
+
{featuredError
|
|
266
|
+
? "Unable to load featured properties. Please try again later."
|
|
267
|
+
: "No featured properties at the moment."}
|
|
268
|
+
</p>
|
|
258
269
|
<Button asChild className="mt-3">
|
|
259
270
|
<Link to="/properties">Browse all properties</Link>
|
|
260
271
|
</Button>
|
|
@@ -287,7 +298,7 @@ export default function Home() {
|
|
|
287
298
|
<Button
|
|
288
299
|
asChild
|
|
289
300
|
size="lg"
|
|
290
|
-
className="cursor-pointer rounded-xl bg-primary transition-colors duration-200 hover:bg-primary/
|
|
301
|
+
className="cursor-pointer rounded-xl bg-primary transition-colors duration-200 [a]:hover:bg-primary/95"
|
|
291
302
|
>
|
|
292
303
|
<Link to="/contact">More About Us</Link>
|
|
293
304
|
</Button>
|
|
@@ -471,7 +482,7 @@ export default function Home() {
|
|
|
471
482
|
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-white">
|
|
472
483
|
Explore
|
|
473
484
|
</h3>
|
|
474
|
-
<ul className="space-y-2 text-sm">
|
|
485
|
+
<ul role="list" className="space-y-2 text-sm">
|
|
475
486
|
<li>
|
|
476
487
|
<Link to="/" className="text-teal-200 hover:text-white">
|
|
477
488
|
Home
|
|
@@ -493,7 +504,7 @@ export default function Home() {
|
|
|
493
504
|
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-white">
|
|
494
505
|
Support
|
|
495
506
|
</h3>
|
|
496
|
-
<ul className="space-y-2 text-sm">
|
|
507
|
+
<ul role="list" className="space-y-2 text-sm">
|
|
497
508
|
<li>
|
|
498
509
|
<Link to="/contact" className="text-teal-200 hover:text-white">
|
|
499
510
|
Contact Us
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type PaginationConfig,
|
|
20
20
|
} from "@/features/object-search/hooks/useObjectSearchParams";
|
|
21
21
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
22
|
+
import { useReanchorReadingCursor } from "@/hooks/useReanchorReadingCursor";
|
|
22
23
|
import PaginationControls from "@/features/object-search/components/PaginationControls";
|
|
23
24
|
import type {
|
|
24
25
|
Maintenance_Request__C_Filter,
|
|
@@ -113,6 +114,8 @@ export default function Maintenance() {
|
|
|
113
114
|
[query.where, query.orderBy, pagination.pageSize, pagination.afterCursor, refreshCounter],
|
|
114
115
|
);
|
|
115
116
|
|
|
117
|
+
useReanchorReadingCursor(loading);
|
|
118
|
+
|
|
116
119
|
const requests = useMemo(
|
|
117
120
|
() =>
|
|
118
121
|
(searchResult?.edges ?? []).reduce<MaintenanceRequestNode[]>((acc, edge) => {
|
|
@@ -340,7 +343,8 @@ export default function Maintenance() {
|
|
|
340
343
|
emptyMessage="No maintenance requests yet. Submit one above."
|
|
341
344
|
/>
|
|
342
345
|
</CardContent>
|
|
343
|
-
{!
|
|
346
|
+
{!error && requests.length > 0 && (
|
|
347
|
+
// Keep pagination mounted while results reload.
|
|
344
348
|
<div className="px-6 pb-6">
|
|
345
349
|
<PaginationControls
|
|
346
350
|
pageIndex={pagination.pageIndex}
|
package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertyDetails.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
1
2
|
import { useParams, Link } from "react-router";
|
|
2
3
|
import { Button } from "@/components/ui/button";
|
|
3
4
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
|
@@ -38,7 +39,7 @@ function formatDate(val: string | null): string {
|
|
|
38
39
|
|
|
39
40
|
function PropertyDetailsSkeleton() {
|
|
40
41
|
return (
|
|
41
|
-
<div className="mx-auto max-w-[900px]"
|
|
42
|
+
<div className="mx-auto max-w-[900px]">
|
|
42
43
|
<h1 className="sr-only">Property details</h1>
|
|
43
44
|
<Skeleton className="mb-4 h-4 w-32" />
|
|
44
45
|
|
|
@@ -100,8 +101,6 @@ function PropertyDetailsSkeleton() {
|
|
|
100
101
|
</Card>
|
|
101
102
|
|
|
102
103
|
<Skeleton className="mb-4 h-[52px] w-full rounded-xl" />
|
|
103
|
-
|
|
104
|
-
<span className="sr-only">Loading property details…</span>
|
|
105
104
|
</div>
|
|
106
105
|
);
|
|
107
106
|
}
|
|
@@ -110,6 +109,31 @@ export default function PropertyDetails() {
|
|
|
110
109
|
const { id } = useParams<{ id: string }>();
|
|
111
110
|
const { property, loading, error } = usePropertyDetail(id);
|
|
112
111
|
|
|
112
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
113
|
+
const statusRef = useRef<HTMLParagraphElement>(null);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
let message = loading ? "Loading property details…" : "";
|
|
116
|
+
const heading = containerRef.current?.querySelector<HTMLElement>("h1");
|
|
117
|
+
if (!loading && heading) {
|
|
118
|
+
const active = document.activeElement;
|
|
119
|
+
if (
|
|
120
|
+
active &&
|
|
121
|
+
active !== document.body &&
|
|
122
|
+
active !== heading &&
|
|
123
|
+
active !== containerRef.current?.closest("main")
|
|
124
|
+
) {
|
|
125
|
+
message = heading.textContent ?? "";
|
|
126
|
+
} else {
|
|
127
|
+
if (!heading.hasAttribute("tabindex")) heading.setAttribute("tabindex", "-1");
|
|
128
|
+
heading.focus();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (statusRef.current && statusRef.current.textContent !== message) {
|
|
132
|
+
statusRef.current.textContent = message;
|
|
133
|
+
}
|
|
134
|
+
}, [loading, error, property, id]);
|
|
135
|
+
const status = <p ref={statusRef} role="status" className="sr-only" />;
|
|
136
|
+
|
|
113
137
|
// Extract nested relationships from the single property node.
|
|
114
138
|
const images = (property?.Property_Images__r?.edges ?? []).flatMap((e) =>
|
|
115
139
|
e?.node ? [e.node] : [],
|
|
@@ -136,13 +160,21 @@ export default function PropertyDetails() {
|
|
|
136
160
|
: geocodedCoords;
|
|
137
161
|
|
|
138
162
|
if (loading) {
|
|
139
|
-
return
|
|
163
|
+
return (
|
|
164
|
+
<div ref={containerRef}>
|
|
165
|
+
{status}
|
|
166
|
+
<PropertyDetailsSkeleton />
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
140
169
|
}
|
|
141
170
|
|
|
142
171
|
if (error) {
|
|
143
172
|
return (
|
|
144
|
-
<div className="mx-auto max-w-[900px]">
|
|
145
|
-
|
|
173
|
+
<div ref={containerRef} className="mx-auto max-w-[900px]">
|
|
174
|
+
{status}
|
|
175
|
+
<h1 className="sr-only" aria-describedby="property-details-error">
|
|
176
|
+
Unable to load property details
|
|
177
|
+
</h1>
|
|
146
178
|
<div className="mb-4">
|
|
147
179
|
<Link to="/properties" className="text-sm text-primary no-underline hover:underline">
|
|
148
180
|
← Back to listings
|
|
@@ -150,7 +182,7 @@ export default function PropertyDetails() {
|
|
|
150
182
|
</div>
|
|
151
183
|
<Card className="rounded-2xl border border-border shadow-sm">
|
|
152
184
|
<CardContent className="pt-6">
|
|
153
|
-
<p className="text-destructive"
|
|
185
|
+
<p className="text-destructive" id="property-details-error">
|
|
154
186
|
Something went wrong. Please try again later.
|
|
155
187
|
</p>
|
|
156
188
|
</CardContent>
|
|
@@ -161,8 +193,9 @@ export default function PropertyDetails() {
|
|
|
161
193
|
|
|
162
194
|
if (!property && id) {
|
|
163
195
|
return (
|
|
164
|
-
<div className="mx-auto max-w-[900px]">
|
|
165
|
-
|
|
196
|
+
<div ref={containerRef} className="mx-auto max-w-[900px]">
|
|
197
|
+
{status}
|
|
198
|
+
<h1 className="sr-only">Listing not found</h1>
|
|
166
199
|
<div className="mb-4">
|
|
167
200
|
<Link to="/properties" className="text-sm text-primary no-underline hover:underline">
|
|
168
201
|
← Back to listings
|
|
@@ -170,9 +203,7 @@ export default function PropertyDetails() {
|
|
|
170
203
|
</div>
|
|
171
204
|
<Card className="rounded-2xl border border-border shadow-sm">
|
|
172
205
|
<CardContent className="pt-6">
|
|
173
|
-
<p className="text-destructive"
|
|
174
|
-
Listing not found.
|
|
175
|
-
</p>
|
|
206
|
+
<p className="text-destructive">Listing not found.</p>
|
|
176
207
|
</CardContent>
|
|
177
208
|
</Card>
|
|
178
209
|
</div>
|
|
@@ -183,7 +214,8 @@ export default function PropertyDetails() {
|
|
|
183
214
|
const otherImages = images.filter((i) => i.Id !== primaryImage?.Id);
|
|
184
215
|
|
|
185
216
|
return (
|
|
186
|
-
<div className="mx-auto max-w-[900px]">
|
|
217
|
+
<div ref={containerRef} className="mx-auto max-w-[900px]">
|
|
218
|
+
{status}
|
|
187
219
|
<div className="mb-4">
|
|
188
220
|
<Link to="/properties" className="text-sm text-primary no-underline hover:underline">
|
|
189
221
|
← Back to listings
|
|
@@ -299,7 +331,7 @@ export default function PropertyDetails() {
|
|
|
299
331
|
</CardTitle>
|
|
300
332
|
</CardHeader>
|
|
301
333
|
<CardContent>
|
|
302
|
-
<ul className="space-y-2">
|
|
334
|
+
<ul role="list" className="space-y-2">
|
|
303
335
|
{costs.slice(0, 10).map((c) => (
|
|
304
336
|
<li
|
|
305
337
|
key={c.Id}
|
|
@@ -340,7 +372,7 @@ export default function PropertyDetails() {
|
|
|
340
372
|
{features.map((f) => (
|
|
341
373
|
<span
|
|
342
374
|
key={f.Id}
|
|
343
|
-
className="rounded-full border border-border bg-muted/60 px-2.5 py-0.5 text-xs font-medium text-
|
|
375
|
+
className="rounded-full border border-border bg-muted/60 px-2.5 py-0.5 text-xs font-medium text-neutral-600 dark:text-neutral-300"
|
|
344
376
|
>
|
|
345
377
|
{f.Feature_Category__c?.value ? `${f.Feature_Category__c.value}: ` : ""}
|
|
346
378
|
{f.Description__c?.value ?? f.Name?.value ?? "—"}
|
|
@@ -355,7 +387,7 @@ export default function PropertyDetails() {
|
|
|
355
387
|
<Button
|
|
356
388
|
asChild
|
|
357
389
|
size="sm"
|
|
358
|
-
className="w-full cursor-pointer rounded-xl bg-primary px-5 py-5 text-lg font-medium transition-colors duration-200 hover:bg-primary/
|
|
390
|
+
className="w-full cursor-pointer rounded-xl bg-primary px-5 py-5 text-lg font-medium transition-colors duration-200 [a]:hover:bg-primary/95"
|
|
359
391
|
>
|
|
360
392
|
<Link to={`/application?propertyId=${encodeURIComponent(id ?? "")}`}>
|
|
361
393
|
Fill out an application
|
package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearch.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type PaginationConfig,
|
|
20
20
|
} from "@/features/object-search/hooks/useObjectSearchParams";
|
|
21
21
|
import { useAsyncData } from "@/hooks/useAsyncData";
|
|
22
|
+
import { useReanchorReadingCursor } from "@/hooks/useReanchorReadingCursor";
|
|
22
23
|
import type { FilterFieldConfig } from "@/features/object-search/utils/filterUtils";
|
|
23
24
|
import type { SortFieldConfig, SortState } from "@/features/object-search/utils/sortUtils";
|
|
24
25
|
import { usePropertyMapMarkers } from "@/hooks/usePropertyMapMarkers";
|
|
@@ -119,6 +120,8 @@ export default function PropertySearch() {
|
|
|
119
120
|
[query.where, query.orderBy, pagination.pageSize, pagination.afterCursor],
|
|
120
121
|
);
|
|
121
122
|
|
|
123
|
+
useReanchorReadingCursor(resultsLoading);
|
|
124
|
+
|
|
122
125
|
/* ── Derive results + maps ── */
|
|
123
126
|
const results = useMemo(
|
|
124
127
|
() =>
|
|
@@ -181,23 +184,24 @@ export default function PropertySearch() {
|
|
|
181
184
|
/* ── Staged filter state for popover edits (committed on Save) ── */
|
|
182
185
|
const committedSearchValue = filters.active.find((f) => f.field === "search")?.value ?? "";
|
|
183
186
|
const [searchQuery, setSearchQuery] = useState(committedSearchValue);
|
|
187
|
+
const { set: setFilter, remove: removeFilter } = filters;
|
|
184
188
|
|
|
185
189
|
// Debounce search text → filters.set
|
|
186
190
|
useEffect(() => {
|
|
187
191
|
const t = setTimeout(() => {
|
|
188
192
|
if (searchQuery.trim()) {
|
|
189
|
-
|
|
193
|
+
setFilter("search", {
|
|
190
194
|
field: "search",
|
|
191
195
|
label: "Search",
|
|
192
196
|
type: "search",
|
|
193
197
|
value: searchQuery,
|
|
194
198
|
});
|
|
195
199
|
} else {
|
|
196
|
-
|
|
200
|
+
removeFilter("search");
|
|
197
201
|
}
|
|
198
202
|
}, SEARCH_FILTER_DEBOUNCE_MS);
|
|
199
203
|
return () => clearTimeout(t);
|
|
200
|
-
}, [searchQuery,
|
|
204
|
+
}, [searchQuery, setFilter, removeFilter]);
|
|
201
205
|
|
|
202
206
|
// Sync local searchQuery when the committed value changes externally (e.g. URL nav)
|
|
203
207
|
const [prevCommittedSearchValue, setPrevCommittedSearchValue] = useState(committedSearchValue);
|
|
@@ -413,7 +417,8 @@ export default function PropertySearch() {
|
|
|
413
417
|
>
|
|
414
418
|
{apiUnavailable ? (
|
|
415
419
|
<PropertySearchPlaceholder message="Search is temporarily unavailable." />
|
|
416
|
-
) : resultsLoading ? (
|
|
420
|
+
) : resultsLoading && results.length === 0 ? (
|
|
421
|
+
// Keep loaded results mounted to preserve focus during reloads.
|
|
417
422
|
<div className="space-y-4">
|
|
418
423
|
{[1, 2, 3].map((i) => (
|
|
419
424
|
<PropertyListingCardSkeleton key={i} />
|
|
@@ -440,7 +445,12 @@ export default function PropertySearch() {
|
|
|
440
445
|
</div>
|
|
441
446
|
) : (
|
|
442
447
|
<>
|
|
443
|
-
<ul
|
|
448
|
+
<ul
|
|
449
|
+
className="space-y-4"
|
|
450
|
+
role="list"
|
|
451
|
+
aria-label="Search results"
|
|
452
|
+
aria-busy={resultsLoading || undefined}
|
|
453
|
+
>
|
|
444
454
|
{visibleResults.map((node, index) => {
|
|
445
455
|
const imageUrl = primaryImagesMap[node.Id] ?? null;
|
|
446
456
|
const address = propertyAddressMap[node.Id] ?? null;
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
--destructive: oklch(0.577 0.245 27.325);
|
|
76
76
|
--border: oklch(0.922 0 0);
|
|
77
77
|
--input: oklch(0.922 0 0);
|
|
78
|
-
--ring: oklch(0.
|
|
78
|
+
--ring: oklch(0.556 0 0);
|
|
79
79
|
--chart-1: oklch(0.646 0.222 41.116);
|
|
80
80
|
--chart-2: oklch(0.6 0.118 184.704);
|
|
81
81
|
--chart-3: oklch(0.398 0.07 227.392);
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
|
|
128
128
|
@layer base {
|
|
129
129
|
* {
|
|
130
|
-
@apply border-border outline-ring
|
|
130
|
+
@apply border-border outline-ring;
|
|
131
131
|
}
|
|
132
132
|
body {
|
|
133
133
|
@apply bg-background text-foreground;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/appLayout.tsx","./src/navigationMenu.tsx","./src/router-utils.tsx","./src/routes.tsx","./src/api/graphql-operations-types.ts","./src/api/graphqlClient.ts","./src/api/applications/applicationApi.ts","./src/api/leads/leadApi.ts","./src/api/maintenanceRequests/maintenanceRequestApi.ts","./src/api/properties/propertyDetailGraphQL.ts","./src/api/properties/propertyNodeUtils.ts","./src/api/properties/propertySearchService.ts","./src/api/tenants/tenantApi.ts","./src/components/ErrorBoundary.tsx","./src/components/SkeletonPrimitives.tsx","./src/components/alerts/status-alert.tsx","./src/components/dashboard/WeatherWidget.tsx","./src/components/layout/TopBar.tsx","./src/components/layout/VerticalNav.tsx","./src/components/layouts/card-layout.tsx","./src/components/maintenanceRequests/MaintenanceRequestIcon.tsx","./src/components/maintenanceRequests/MaintenanceRequestList.tsx","./src/components/maintenanceRequests/MaintenanceRequestListItem.tsx","./src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx","./src/components/maintenanceRequests/StatusBadge.tsx","./src/components/properties/PropertyListingCard.tsx","./src/components/properties/PropertyMap.tsx","./src/components/properties/PropertySearchFilters.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/datePicker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/field.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/spinner.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/context/TenantAccessContext.tsx","./src/features/authentication/authHelpers.ts","./src/features/authentication/authenticationConfig.ts","./src/features/authentication/api/userProfileApi.ts","./src/features/authentication/components/SocialLogin.tsx","./src/features/authentication/context/AuthContext.tsx","./src/features/authentication/footers/footer-link.tsx","./src/features/authentication/forms/auth-form.tsx","./src/features/authentication/forms/submit-button.tsx","./src/features/authentication/hooks/form.tsx","./src/features/authentication/hooks/useCountdownTimer.ts","./src/features/authentication/hooks/useRetryWithBackoff.ts","./src/features/authentication/layout/centered-page-layout.tsx","./src/features/authentication/layouts/AuthAppLayout.tsx","./src/features/authentication/layouts/TenantRoute.tsx","./src/features/authentication/layouts/privateRouteLayout.tsx","./src/features/authentication/menu/AuthMenu.tsx","./src/features/authentication/pages/ChangePassword.tsx","./src/features/authentication/pages/ForgotPassword.tsx","./src/features/authentication/pages/Login.tsx","./src/features/authentication/pages/Profile.tsx","./src/features/authentication/pages/Register.tsx","./src/features/authentication/pages/ResetPassword.tsx","./src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx","./src/features/authentication/sessionTimeout/sessionTimeService.ts","./src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts","./src/features/authentication/utils/helpers.ts","./src/features/object-search/api/objectSearchService.ts","./src/features/object-search/components/PaginationControls.tsx","./src/features/object-search/hooks/useObjectSearchParams.ts","./src/features/object-search/utils/debounce.ts","./src/features/object-search/utils/filterUtils.ts","./src/features/object-search/utils/sortUtils.ts","./src/hooks/useAsyncData.ts","./src/hooks/useGeocode.ts","./src/hooks/useMaintenanceRequests.ts","./src/hooks/usePropertyDetail.ts","./src/hooks/usePropertyMapMarkers.ts","./src/hooks/useWeather.ts","./src/lib/utils.ts","./src/pages/Application.tsx","./src/pages/Contact.tsx","./src/pages/Dashboard.tsx","./src/pages/Home.tsx","./src/pages/Maintenance.tsx","./src/pages/NotFound.tsx","./src/pages/PropertyDetails.tsx","./src/pages/PropertySearch.tsx","./src/pages/PropertySearchPlaceholder.tsx","./src/types/leaflet.d.ts","./src/types/searchResults.ts","./src/utils/geocode.ts","./src/utils/propertyListingPaginationUtils.ts","./e2e/app.spec.ts","./vite-env.d.ts","./vitest-env.d.ts","./vitest.setup.ts","../../../../../../../../../sdk/platform-sdk/types/env.d.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/app.tsx","./src/appLayout.tsx","./src/navigationMenu.tsx","./src/router-utils.tsx","./src/routes.tsx","./src/api/graphql-operations-types.ts","./src/api/graphqlClient.ts","./src/api/applications/applicationApi.ts","./src/api/leads/leadApi.ts","./src/api/maintenanceRequests/maintenanceRequestApi.ts","./src/api/properties/propertyDetailGraphQL.ts","./src/api/properties/propertyNodeUtils.ts","./src/api/properties/propertySearchService.ts","./src/api/tenants/tenantApi.ts","./src/components/ErrorBoundary.tsx","./src/components/SkeletonPrimitives.tsx","./src/components/alerts/status-alert.tsx","./src/components/dashboard/WeatherWidget.tsx","./src/components/layout/TopBar.tsx","./src/components/layout/VerticalNav.tsx","./src/components/layouts/card-layout.tsx","./src/components/maintenanceRequests/MaintenanceRequestIcon.tsx","./src/components/maintenanceRequests/MaintenanceRequestList.tsx","./src/components/maintenanceRequests/MaintenanceRequestListItem.tsx","./src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx","./src/components/maintenanceRequests/StatusBadge.tsx","./src/components/properties/PropertyListingCard.tsx","./src/components/properties/PropertyMap.tsx","./src/components/properties/PropertySearchFilters.tsx","./src/components/ui/alert.tsx","./src/components/ui/avatar.tsx","./src/components/ui/badge.tsx","./src/components/ui/breadcrumb.tsx","./src/components/ui/button.tsx","./src/components/ui/calendar.tsx","./src/components/ui/card.tsx","./src/components/ui/checkbox.tsx","./src/components/ui/collapsible.tsx","./src/components/ui/datePicker.tsx","./src/components/ui/dialog.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/field.tsx","./src/components/ui/index.ts","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/components/ui/pagination.tsx","./src/components/ui/popover.tsx","./src/components/ui/select.tsx","./src/components/ui/separator.tsx","./src/components/ui/skeleton.tsx","./src/components/ui/sonner.tsx","./src/components/ui/spinner.tsx","./src/components/ui/table.tsx","./src/components/ui/tabs.tsx","./src/context/TenantAccessContext.tsx","./src/features/authentication/authHelpers.ts","./src/features/authentication/authenticationConfig.ts","./src/features/authentication/api/userProfileApi.ts","./src/features/authentication/components/SocialLogin.tsx","./src/features/authentication/context/AuthContext.tsx","./src/features/authentication/footers/footer-link.tsx","./src/features/authentication/forms/auth-form.tsx","./src/features/authentication/forms/submit-button.tsx","./src/features/authentication/hooks/form.tsx","./src/features/authentication/hooks/useCountdownTimer.ts","./src/features/authentication/hooks/useRetryWithBackoff.ts","./src/features/authentication/layout/centered-page-layout.tsx","./src/features/authentication/layouts/AuthAppLayout.tsx","./src/features/authentication/layouts/TenantRoute.tsx","./src/features/authentication/layouts/privateRouteLayout.tsx","./src/features/authentication/menu/AuthMenu.tsx","./src/features/authentication/pages/ChangePassword.tsx","./src/features/authentication/pages/ForgotPassword.tsx","./src/features/authentication/pages/Login.tsx","./src/features/authentication/pages/Profile.tsx","./src/features/authentication/pages/Register.tsx","./src/features/authentication/pages/ResetPassword.tsx","./src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx","./src/features/authentication/sessionTimeout/sessionTimeService.ts","./src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts","./src/features/authentication/utils/helpers.ts","./src/features/object-search/api/objectSearchService.ts","./src/features/object-search/components/PaginationControls.tsx","./src/features/object-search/hooks/useObjectSearchParams.ts","./src/features/object-search/utils/debounce.ts","./src/features/object-search/utils/filterUtils.ts","./src/features/object-search/utils/sortUtils.ts","./src/hooks/useAsyncData.ts","./src/hooks/useGeocode.ts","./src/hooks/useMaintenanceRequests.ts","./src/hooks/usePropertyDetail.ts","./src/hooks/usePropertyMapMarkers.ts","./src/hooks/useReanchorReadingCursor.ts","./src/hooks/useWeather.ts","./src/lib/utils.ts","./src/pages/Application.tsx","./src/pages/Contact.tsx","./src/pages/Dashboard.tsx","./src/pages/Home.tsx","./src/pages/Maintenance.tsx","./src/pages/NotFound.tsx","./src/pages/PropertyDetails.tsx","./src/pages/PropertySearch.tsx","./src/pages/PropertySearchPlaceholder.tsx","./src/types/leaflet.d.ts","./src/types/searchResults.ts","./src/utils/geocode.ts","./src/utils/propertyListingPaginationUtils.ts","./e2e/app.spec.ts","./vite-env.d.ts","./vitest-env.d.ts","./vitest.setup.ts","../../../../../../../../../sdk/platform-sdk/types/env.d.ts"],"version":"5.9.3"}
|
package/dist/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@salesforce/ui-bundle-template-base-sfdx-project",
|
|
9
|
-
"version": "12.
|
|
9
|
+
"version": "12.7.0",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"fast-xml-parser": "^5.9.3",
|