@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
@@ -1,30 +1,478 @@
1
+ import { Link, useNavigate } from "react-router";
2
+ import { useRef, useState } from "react";
1
3
  import { Button } from "../components/ui/button";
2
4
  import { Input } from "../components/ui/input";
5
+ import { Card, CardContent } from "../components/ui/card";
6
+ import { usePropertyListingSearch } from "@/hooks/usePropertyListingSearch";
7
+ import {
8
+ usePropertyPrimaryImages,
9
+ getPropertyIdFromRecord,
10
+ } from "@/hooks/usePropertyPrimaryImages";
11
+ import { usePropertyAddresses } from "@/hooks/usePropertyAddresses";
12
+ import { createNewsletterLead } from "@/api/leadApi";
13
+ import { Phone, Send, ChevronDown, ChevronUp, MessageCircle, HelpCircle } from "lucide-react";
14
+
15
+ const HERO_IMAGE = "https://images.unsplash.com/photo-1600596542815-ffad4c1539a9?w=1200&q=85";
16
+ const CITY_IMAGE = "https://images.unsplash.com/photo-1514565131-fce0801e5785?w=800&q=85";
17
+ const RELAX_IMAGE = "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=600&q=85";
18
+
19
+ const FEATURED_PAGE_SIZE = 3;
20
+
21
+ function fieldVal(
22
+ fields: Record<string, { value?: unknown; displayValue?: string | null }> | undefined,
23
+ apiName: string,
24
+ ): string | null {
25
+ const f = fields?.[apiName];
26
+ if (!f || typeof f !== "object") return null;
27
+ if (f.displayValue != null && f.displayValue !== "") return String(f.displayValue);
28
+ if (f.value != null) return typeof f.value === "object" ? null : String(f.value);
29
+ return null;
30
+ }
31
+
32
+ function formatPrice(val: string | number | null): string {
33
+ if (val == null) return "—";
34
+ const n = typeof val === "number" ? val : Number(val);
35
+ if (Number.isNaN(n)) return String(val);
36
+ return new Intl.NumberFormat("en-US", {
37
+ style: "currency",
38
+ currency: "USD",
39
+ maximumFractionDigits: 0,
40
+ }).format(n);
41
+ }
42
+
43
+ const FAQ_ITEMS = [
44
+ {
45
+ q: "How do I apply for an apartment?",
46
+ a: "Applications can be completed online directly through our website at your convenience. Go to Property Search, choose a listing, and click Apply to start. If you need assistance, please reach out via Contact Us.",
47
+ },
48
+ {
49
+ q: "How long does the application approval process take?",
50
+ a: "Most applications are reviewed within 2–3 business days. You'll receive an email once your application has been processed.",
51
+ },
52
+ {
53
+ q: "Do you offer flexible lease terms?",
54
+ a: "Yes. Many of our properties offer 1-, 6-, or 12-month lease terms. Check the listing details or contact us for options.",
55
+ },
56
+ {
57
+ q: "What is the penalty for breaking a lease early?",
58
+ a: "Early termination terms vary by property. Please review your lease agreement or contact the property manager for details.",
59
+ },
60
+ {
61
+ q: "What are the income and credit qualifications?",
62
+ a: "Requirements vary by property. Typical guidelines include income of at least 3x monthly rent and a credit check. See each listing for specifics.",
63
+ },
64
+ ];
3
65
 
4
66
  export default function Home() {
67
+ const searchInputRef = useRef<HTMLInputElement>(null);
68
+ const navigate = useNavigate();
69
+ const [faqOpen, setFaqOpen] = useState<number | null>(0);
70
+ const [footerEmail, setFooterEmail] = useState("");
71
+ const [newsletterSubmitting, setNewsletterSubmitting] = useState(false);
72
+ const [newsletterMessage, setNewsletterMessage] = useState<{
73
+ type: "success" | "error";
74
+ text: string;
75
+ } | null>(null);
76
+
77
+ const { results: featuredResults } = usePropertyListingSearch("", FEATURED_PAGE_SIZE, "0");
78
+ const primaryImagesMap = usePropertyPrimaryImages(featuredResults);
79
+ const propertyAddressMap = usePropertyAddresses(featuredResults);
80
+
81
+ const handleFindHome = (e: React.FormEvent) => {
82
+ e.preventDefault();
83
+ const q = searchInputRef.current?.value?.trim() ?? "";
84
+ navigate(q ? `/properties?search=${encodeURIComponent(q)}` : "/properties");
85
+ };
86
+
87
+ const handleFooterSubmit = async (e: React.FormEvent) => {
88
+ e.preventDefault();
89
+ const email = footerEmail.trim();
90
+ if (!email) return;
91
+ setNewsletterMessage(null);
92
+ setNewsletterSubmitting(true);
93
+ try {
94
+ await createNewsletterLead(email);
95
+ setFooterEmail("");
96
+ setNewsletterMessage({ type: "success", text: "Thanks! You’re subscribed." });
97
+ } catch {
98
+ setNewsletterMessage({ type: "error", text: "Something went wrong. Try again later." });
99
+ } finally {
100
+ setNewsletterSubmitting(false);
101
+ }
102
+ };
103
+
104
+ const validFeatured = featuredResults.filter((r) => r?.record?.id);
105
+
5
106
  return (
6
- <div className="mx-auto max-w-[1100px]">
7
- <div className="mb-6 rounded-2xl bg-primary px-8 py-10 text-primary-foreground shadow-md">
8
- <p className="mb-2 text-xs uppercase tracking-widest opacity-90">THE ZEN WAY TO LEASE</p>
9
- <h1 className="mb-2 text-3xl font-bold leading-tight">Your Dream Place Starts Here</h1>
10
- <p className="mb-6 text-[0.95rem] opacity-90">
11
- Luxury Properties, Managed With Uncompromising Care
12
- </p>
13
- <Button size="lg" className="font-semibold">
14
- Browse All Properties
15
- </Button>
16
- </div>
17
- <div className="flex flex-wrap items-center gap-3 rounded-xl bg-primary/10 p-5">
18
- <Input
19
- type="text"
20
- placeholder="Search by address, city, location, or zipcode"
21
- aria-label="Search"
22
- className="min-w-[200px] flex-1 flex-[1_1_240px]"
23
- />
24
- <Button variant="secondary" className="font-semibold">
25
- Find Home
26
- </Button>
107
+ <div className="space-y-0">
108
+ {/* ——— Hero ——— */}
109
+ <div className="relative w-full overflow-hidden rounded-2xl">
110
+ <div className="relative aspect-[21/9] min-h-[280px] w-full md:aspect-[3/1] md:min-h-[320px]">
111
+ <img
112
+ src={HERO_IMAGE}
113
+ alt=""
114
+ className="h-full w-full object-cover"
115
+ loading="eager"
116
+ fetchPriority="high"
117
+ />
118
+ <div className="absolute inset-0 bg-black/40" />
119
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-6 px-4">
120
+ <div className="w-full max-w-2xl text-center">
121
+ <p className="mb-2 text-xs font-medium uppercase tracking-[0.2em] text-white/80">
122
+ The Zen Way to Lease
123
+ </p>
124
+ <h1 className="mb-2 text-3xl font-semibold tracking-tight text-white drop-shadow-md md:text-4xl lg:text-5xl">
125
+ Your Dream Place Starts Here
126
+ </h1>
127
+ <p className="mb-6 text-base text-white/90 md:text-lg">
128
+ Search properties, manage applications, and move in with ease.
129
+ </p>
130
+ </div>
131
+ <form
132
+ onSubmit={handleFindHome}
133
+ className="flex w-full max-w-xl flex-col gap-2 sm:flex-row sm:rounded-lg"
134
+ >
135
+ <Input
136
+ ref={searchInputRef}
137
+ type="text"
138
+ name="search"
139
+ placeholder="Search by address, city, or zip code"
140
+ aria-label="Search properties"
141
+ className="min-h-12 flex-1 rounded-lg border-0 bg-white/95 px-4 text-foreground shadow-lg placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-white"
142
+ />
143
+ <Button
144
+ type="submit"
145
+ size="lg"
146
+ 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/90"
147
+ >
148
+ Find Home
149
+ </Button>
150
+ </form>
151
+ <p className="text-sm text-white/80">
152
+ or{" "}
153
+ <Link
154
+ to="/properties"
155
+ className="cursor-pointer font-medium text-white underline underline-offset-2 transition-colors duration-200 hover:no-underline"
156
+ >
157
+ browse all properties
158
+ </Link>
159
+ </p>
160
+ </div>
161
+ </div>
27
162
  </div>
163
+
164
+ {/* ——— Featured Properties ——— */}
165
+ <section className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-violet-100/80 to-pink-100/80 px-4 py-12 md:px-6">
166
+ <div className="relative mx-auto max-w-6xl">
167
+ <h2 className="mb-8 text-2xl font-bold tracking-tight text-violet-900 md:text-3xl">
168
+ Featured Properties
169
+ </h2>
170
+ {validFeatured.length > 0 ? (
171
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
172
+ {validFeatured.map((item, index) => {
173
+ const record = item.record;
174
+ const propertyId = getPropertyIdFromRecord(record);
175
+ const imageUrl = propertyId ? (primaryImagesMap[propertyId] ?? null) : null;
176
+ const address = propertyId ? (propertyAddressMap[propertyId] ?? null) : null;
177
+ const name = fieldVal(record.fields, "Name") ?? "Untitled";
178
+ const priceRaw = fieldVal(record.fields, "Listing_Price__c");
179
+ const price = formatPrice(priceRaw);
180
+ const displayAddress = (address ?? "").trim().replace(/\n/g, ", ") || null;
181
+ return (
182
+ <Card
183
+ key={record.id ?? index}
184
+ className="overflow-hidden rounded-2xl border-0 shadow-md"
185
+ >
186
+ <Link to={`/property/${record.id}`} className="block cursor-pointer">
187
+ <div className="aspect-[4/3] overflow-hidden bg-muted">
188
+ {imageUrl ? (
189
+ <img
190
+ src={imageUrl}
191
+ alt=""
192
+ className="h-full w-full object-cover transition-transform hover:scale-105"
193
+ />
194
+ ) : (
195
+ <div className="flex h-full items-center justify-center text-muted-foreground">
196
+ No image
197
+ </div>
198
+ )}
199
+ </div>
200
+ <CardContent className="p-4">
201
+ <h3 className="mb-1 font-semibold text-foreground">{name}</h3>
202
+ {displayAddress && (
203
+ <p className="mb-2 line-clamp-1 text-sm text-muted-foreground">
204
+ {displayAddress}
205
+ </p>
206
+ )}
207
+ <p className="mb-3 text-sm font-medium text-primary">{price}</p>
208
+ <Button
209
+ size="sm"
210
+ className="w-full cursor-pointer rounded-xl bg-primary transition-colors duration-200 hover:bg-primary/90"
211
+ >
212
+ View Property Details
213
+ </Button>
214
+ </CardContent>
215
+ </Link>
216
+ </Card>
217
+ );
218
+ })}
219
+ </div>
220
+ ) : (
221
+ <div className="rounded-xl bg-white/60 px-6 py-10 text-center text-muted-foreground">
222
+ <p>No featured properties at the moment.</p>
223
+ <Button asChild className="mt-3">
224
+ <Link to="/properties">Browse all properties</Link>
225
+ </Button>
226
+ </div>
227
+ )}
228
+ </div>
229
+ </section>
230
+
231
+ {/* ——— Stats / Trust ——— */}
232
+ <section className="grid min-h-[320px] grid-cols-1 overflow-hidden rounded-2xl bg-card shadow-md md:grid-cols-2">
233
+ <div className="relative min-h-[240px] md:min-h-0">
234
+ <img src={CITY_IMAGE} alt="" className="h-full w-full object-cover" />
235
+ <div className="absolute inset-0 bg-gradient-to-r from-black/20 to-transparent md:from-transparent" />
236
+ </div>
237
+ <div className="flex flex-col justify-center gap-6 bg-gradient-to-br from-amber-50 to-stone-100 px-6 py-10 md:px-10">
238
+ <div className="grid gap-4 sm:grid-cols-2">
239
+ {[
240
+ { value: "20K+", label: "Satisfied tenants and still counting" },
241
+ { value: "10+", label: "Years of experience you can count on" },
242
+ { value: "15+", label: "Award-winning service, trusted results" },
243
+ { value: "5K+", label: "Happy tenants and clients" },
244
+ ].map((stat, i) => (
245
+ <div key={i} className="rounded-xl bg-white/80 px-4 py-4 shadow-sm">
246
+ <p className="text-2xl font-bold text-teal-800">{stat.value}</p>
247
+ <p className="text-sm text-muted-foreground">{stat.label}</p>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ <div className="flex flex-wrap items-center gap-4">
252
+ <Button
253
+ asChild
254
+ size="lg"
255
+ className="cursor-pointer rounded-xl bg-primary transition-colors duration-200 hover:bg-primary/90"
256
+ >
257
+ <Link to="/contact">More About Us</Link>
258
+ </Button>
259
+ <a
260
+ href="tel:18005550120"
261
+ className="inline-flex cursor-pointer items-center gap-2 text-sm font-medium text-foreground transition-colors duration-200 hover:underline"
262
+ >
263
+ <Phone className="size-4" />
264
+ Talk to an Agent 1.800.555.0120
265
+ </a>
266
+ </div>
267
+ </div>
268
+ </section>
269
+
270
+ {/* ——— Testimonials ——— */}
271
+ <section className="overflow-hidden rounded-2xl bg-gradient-to-br from-violet-100/80 to-pink-100/80 px-4 py-12 md:px-6">
272
+ <div className="relative mx-auto grid max-w-6xl gap-8 lg:grid-cols-3">
273
+ <div className="lg:col-span-2">
274
+ <h2 className="mb-8 text-2xl font-bold tracking-tight text-violet-900 md:text-3xl">
275
+ What Our Clients Are Saying
276
+ </h2>
277
+ <div className="grid gap-6 sm:grid-cols-2">
278
+ <Card className="border-0 bg-white/80 shadow-md">
279
+ <CardContent className="p-6">
280
+ <h3 className="mb-2 font-semibold text-foreground">Game-changing convenience</h3>
281
+ <p className="mb-4 text-sm text-muted-foreground">
282
+ Applying online was so easy. I had a response within a day and moved in the
283
+ following week. Highly recommend.
284
+ </p>
285
+ <p className="text-sm font-medium text-foreground">— Des M.</p>
286
+ </CardContent>
287
+ </Card>
288
+ <Card className="border-0 bg-white/80 shadow-md">
289
+ <CardContent className="p-6">
290
+ <h3 className="mb-2 font-semibold text-foreground">
291
+ Highly personalized experience
292
+ </h3>
293
+ <p className="mb-4 text-sm text-muted-foreground">
294
+ The team was responsive and helped me find exactly what I was looking for. The
295
+ whole process felt smooth and professional.
296
+ </p>
297
+ <p className="text-sm font-medium text-foreground">— Zoe T.</p>
298
+ </CardContent>
299
+ </Card>
300
+ </div>
301
+ </div>
302
+ <div className="relative hidden overflow-hidden rounded-2xl lg:block">
303
+ <img src={RELAX_IMAGE} alt="" className="h-full w-full object-cover" />
304
+ <div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/60 to-transparent p-6">
305
+ <p className="text-lg font-semibold text-white">
306
+ Relax and Get Zen. Your Home Awaits.
307
+ </p>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </section>
312
+
313
+ {/* ——— AI / Peace of Mind ——— */}
314
+ <section className="rounded-2xl bg-teal-50 px-4 py-12 md:px-6">
315
+ <div className="mx-auto max-w-3xl text-center">
316
+ <h2 className="mb-3 text-2xl font-bold tracking-tight text-teal-900 md:text-3xl">
317
+ AI-Powered, Peace of Mind
318
+ </h2>
319
+ <p className="mb-8 text-muted-foreground">
320
+ From inquiry to move-in: our AI assists with the hard part so you can focus on living.
321
+ </p>
322
+ <div className="flex flex-wrap justify-center gap-3">
323
+ <Button asChild variant="secondary" size="lg" className="rounded-full">
324
+ <Link to="/properties">Find My Home</Link>
325
+ </Button>
326
+ <Button asChild variant="outline" size="lg" className="rounded-full gap-2">
327
+ <Link to="/contact">
328
+ <MessageCircle className="size-4" />
329
+ Contact
330
+ </Link>
331
+ </Button>
332
+ <Button asChild variant="ghost" size="lg" className="rounded-full gap-2">
333
+ <Link to="/contact">
334
+ <HelpCircle className="size-4" />
335
+ Help
336
+ </Link>
337
+ </Button>
338
+ </div>
339
+ </div>
340
+ </section>
341
+
342
+ {/* ——— FAQ ——— */}
343
+ <section className="rounded-2xl bg-teal-50/80 px-4 py-12 md:px-6">
344
+ <div className="mx-auto max-w-3xl">
345
+ <h2 className="mb-8 text-2xl font-bold tracking-tight text-teal-900 md:text-3xl">
346
+ Frequently Asked Questions
347
+ </h2>
348
+ <div className="space-y-2">
349
+ {FAQ_ITEMS.map((item, index) => (
350
+ <div
351
+ key={index}
352
+ className="overflow-hidden rounded-xl border border-teal-100 bg-white shadow-sm"
353
+ >
354
+ <button
355
+ type="button"
356
+ onClick={() => setFaqOpen(faqOpen === index ? null : index)}
357
+ className="flex w-full cursor-pointer items-center justify-between gap-4 px-5 py-4 text-left font-medium text-foreground transition-colors duration-200 hover:bg-muted/50"
358
+ >
359
+ <span>{item.q}</span>
360
+ {faqOpen === index ? (
361
+ <ChevronUp className="size-5 shrink-0 text-muted-foreground" />
362
+ ) : (
363
+ <ChevronDown className="size-5 shrink-0 text-muted-foreground" />
364
+ )}
365
+ </button>
366
+ {faqOpen === index && (
367
+ <div className="border-t border-teal-100 px-5 py-4 text-sm text-muted-foreground">
368
+ {item.a}
369
+ </div>
370
+ )}
371
+ </div>
372
+ ))}
373
+ </div>
374
+ </div>
375
+ </section>
376
+
377
+ {/* ——— Footer ——— */}
378
+ <footer className="overflow-hidden rounded-2xl bg-teal-800 text-teal-100">
379
+ <div className="grid gap-8 px-6 py-10 md:grid-cols-2 lg:grid-cols-4">
380
+ <div className="lg:col-span-2">
381
+ <p className="mb-4 text-xl font-semibold tracking-wide text-white">ZENLEASE</p>
382
+ <p className="mb-4 text-sm text-teal-200">Stay updated with new listings and tips.</p>
383
+ <form onSubmit={handleFooterSubmit} className="flex flex-col gap-2">
384
+ <div className="flex gap-2">
385
+ <Input
386
+ type="email"
387
+ placeholder="Enter your email"
388
+ value={footerEmail}
389
+ onChange={(e) => setFooterEmail(e.target.value)}
390
+ disabled={newsletterSubmitting}
391
+ className="max-w-xs border-teal-600 bg-teal-900/50 text-white placeholder:text-teal-300"
392
+ aria-label="Email for updates"
393
+ />
394
+ <Button
395
+ type="submit"
396
+ size="icon"
397
+ className="shrink-0 bg-teal-600 hover:bg-teal-700"
398
+ disabled={newsletterSubmitting}
399
+ >
400
+ <Send className="size-4" aria-hidden />
401
+ </Button>
402
+ </div>
403
+ {newsletterMessage && (
404
+ <p
405
+ className={
406
+ newsletterMessage.type === "success" ? "text-teal-200" : "text-red-300"
407
+ }
408
+ >
409
+ {newsletterMessage.text}
410
+ </p>
411
+ )}
412
+ </form>
413
+ </div>
414
+ <div>
415
+ <h4 className="mb-3 text-sm font-semibold uppercase tracking-wider text-white">
416
+ Explore
417
+ </h4>
418
+ <ul className="space-y-2 text-sm">
419
+ <li>
420
+ <Link to="/" className="text-teal-200 hover:text-white">
421
+ Home
422
+ </Link>
423
+ </li>
424
+ <li>
425
+ <Link to="/properties" className="text-teal-200 hover:text-white">
426
+ Property Search
427
+ </Link>
428
+ </li>
429
+ <li>
430
+ <Link to="/application" className="text-teal-200 hover:text-white">
431
+ Apply
432
+ </Link>
433
+ </li>
434
+ </ul>
435
+ </div>
436
+ <div>
437
+ <h4 className="mb-3 text-sm font-semibold uppercase tracking-wider text-white">
438
+ Support
439
+ </h4>
440
+ <ul className="space-y-2 text-sm">
441
+ <li>
442
+ <Link to="/contact" className="text-teal-200 hover:text-white">
443
+ Contact Us
444
+ </Link>
445
+ </li>
446
+ <li>
447
+ <Link to="/maintenance" className="text-teal-200 hover:text-white">
448
+ Maintenance
449
+ </Link>
450
+ </li>
451
+ <li>
452
+ <a href="tel:18005550120" className="text-teal-200 hover:text-white">
453
+ Talk to an agent
454
+ </a>
455
+ </li>
456
+ </ul>
457
+ </div>
458
+ </div>
459
+ <div className="flex flex-col items-center justify-between gap-4 border-t border-teal-700 px-6 py-4 sm:flex-row">
460
+ <p className="text-xs text-teal-300">
461
+ © {new Date().getFullYear()} ZENLEASE. All rights reserved. Terms & Conditions
462
+ </p>
463
+ <div className="flex gap-4">
464
+ <a href="#" className="text-teal-300 hover:text-white" aria-label="Facebook">
465
+ f
466
+ </a>
467
+ <a href="#" className="text-teal-300 hover:text-white" aria-label="Instagram">
468
+ ig
469
+ </a>
470
+ <a href="#" className="text-teal-300 hover:text-white" aria-label="Twitter">
471
+ X
472
+ </a>
473
+ </div>
474
+ </div>
475
+ </footer>
28
476
  </div>
29
477
  );
30
478
  }