@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,99 +1,286 @@
1
- import { Link } from "react-router";
1
+ import { useSearchParams, Link } from "react-router";
2
+ import { useCallback, useEffect, useState } from "react";
2
3
  import { Button } from "../components/ui/button";
3
4
  import { Input } from "../components/ui/input";
4
5
  import { Label } from "../components/ui/label";
5
- import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
6
- import { Tabs, TabsList, TabsTrigger, TabsContent } from "../components/ui/tabs";
6
+ import { Card, CardContent } from "../components/ui/card";
7
+ import {
8
+ fetchListingById,
9
+ fetchPropertyById,
10
+ fetchPrimaryImagesByPropertyIds,
11
+ } from "@/api/propertyDetailGraphQL";
12
+ import { createApplicationRecord } from "@/api/applicationApi";
13
+
14
+ const PREFERRED_TERM_OPTIONS = ["", "1 month", "6 months", "12 months"];
7
15
 
8
16
  export default function Application() {
17
+ const [searchParams] = useSearchParams();
18
+ const listingId = searchParams.get("listingId") ?? "";
19
+
20
+ const [listingName, setListingName] = useState<string | null>(null);
21
+ const [propertyAddress, setPropertyAddress] = useState<string | null>(null);
22
+ const [propertyId, setPropertyId] = useState<string | null>(null);
23
+ const [propertyImageUrl, setPropertyImageUrl] = useState<string | null>(null);
24
+ const [loading, setLoading] = useState(!!listingId);
25
+ const [loadError, setLoadError] = useState<string | null>(null);
26
+
27
+ // Form state – all map to Application__c fields
28
+ const [firstName, setFirstName] = useState("");
29
+ const [lastName, setLastName] = useState("");
30
+ const [email, setEmail] = useState("");
31
+ const [phone, setPhone] = useState("");
32
+ const [moveInDate, setMoveInDate] = useState("");
33
+ const [preferredTerm, setPreferredTerm] = useState("");
34
+ const [employmentInfo, setEmploymentInfo] = useState("");
35
+ const [references, setReferences] = useState("");
36
+
37
+ const [submitting, setSubmitting] = useState(false);
38
+ const [submitError, setSubmitError] = useState<string | null>(null);
39
+ const [submittedId, setSubmittedId] = useState<string | null>(null);
40
+
41
+ // Load listing and property when listingId is present
42
+ useEffect(() => {
43
+ if (!listingId?.trim()) {
44
+ setLoading(false);
45
+ return;
46
+ }
47
+ let cancelled = false;
48
+ setLoadError(null);
49
+ (async () => {
50
+ try {
51
+ const listing = await fetchListingById(listingId);
52
+ if (cancelled) return;
53
+ if (!listing) {
54
+ setLoadError("Listing not found.");
55
+ setLoading(false);
56
+ return;
57
+ }
58
+ setListingName(listing.name);
59
+ if (listing.propertyId) {
60
+ setPropertyId(listing.propertyId);
61
+ const [property, primaryImages] = await Promise.all([
62
+ fetchPropertyById(listing.propertyId),
63
+ fetchPrimaryImagesByPropertyIds([listing.propertyId]),
64
+ ]);
65
+ if (cancelled) return;
66
+ setPropertyAddress(property?.address ?? null);
67
+ setPropertyImageUrl(primaryImages[listing.propertyId] ?? null);
68
+ }
69
+ } catch (e) {
70
+ if (!cancelled) {
71
+ setLoadError(e instanceof Error ? e.message : "Failed to load listing.");
72
+ }
73
+ } finally {
74
+ if (!cancelled) setLoading(false);
75
+ }
76
+ })();
77
+ return () => {
78
+ cancelled = true;
79
+ };
80
+ }, [listingId]);
81
+
82
+ const handleSubmit = useCallback(
83
+ async (e: React.FormEvent) => {
84
+ e.preventDefault();
85
+ setSubmitError(null);
86
+ setSubmitting(true);
87
+ try {
88
+ const id = await createApplicationRecord({
89
+ Property__c: propertyId || null,
90
+ Status__c: "Submitted",
91
+ First_Name__c: firstName.trim() || null,
92
+ Last_Name__c: lastName.trim() || null,
93
+ Email__c: email.trim() || null,
94
+ Phone__c: phone.trim() || null,
95
+ Start_Date__c: moveInDate.trim() || null,
96
+ Preferred_Term__c: preferredTerm.trim() || null,
97
+ Employment_Info__c: employmentInfo.trim() || null,
98
+ References__c: references.trim() || null,
99
+ });
100
+ setSubmittedId(id.id);
101
+ } catch (err) {
102
+ setSubmitError(err instanceof Error ? err.message : "Failed to submit application.");
103
+ } finally {
104
+ setSubmitting(false);
105
+ }
106
+ },
107
+ [
108
+ propertyId,
109
+ firstName,
110
+ lastName,
111
+ email,
112
+ phone,
113
+ moveInDate,
114
+ preferredTerm,
115
+ employmentInfo,
116
+ references,
117
+ ],
118
+ );
119
+
120
+ if (loading) {
121
+ return (
122
+ <div className="mx-auto max-w-[800px]">
123
+ <div className="mb-6 h-48 animate-pulse rounded-xl bg-muted" />
124
+ <div className="h-64 animate-pulse rounded-lg bg-muted" />
125
+ </div>
126
+ );
127
+ }
128
+
129
+ if (submittedId) {
130
+ return (
131
+ <div className="mx-auto max-w-[800px]">
132
+ <Card className="mb-6 p-6">
133
+ <h2 className="mb-2 text-lg font-semibold text-primary">Application submitted</h2>
134
+ <p className="text-sm text-muted-foreground">
135
+ Your application has been saved. Reference: {submittedId}
136
+ </p>
137
+ <div className="mt-4 flex gap-2">
138
+ <Button asChild variant="outline" size="sm">
139
+ <Link to="/properties">Back to search</Link>
140
+ </Button>
141
+ <Button asChild size="sm">
142
+ <Link to="/application">Submit another</Link>
143
+ </Button>
144
+ </div>
145
+ </Card>
146
+ </div>
147
+ );
148
+ }
149
+
9
150
  return (
10
151
  <div className="mx-auto max-w-[800px]">
11
152
  <Card className="mb-6 flex gap-4 p-6">
12
- <div className="size-[200px] shrink-0 rounded-xl bg-muted" />
153
+ <div className="relative size-[200px] shrink-0 overflow-hidden rounded-xl bg-muted">
154
+ {propertyImageUrl ? (
155
+ <img src={propertyImageUrl} alt="" className="h-full w-full object-cover" />
156
+ ) : (
157
+ <div className="h-full w-full bg-muted" aria-hidden />
158
+ )}
159
+ </div>
13
160
  <div className="min-w-0 flex-1">
14
- <h2 className="mb-1 text-lg font-semibold text-primary">Verdana Apartments</h2>
15
- <p className="text-sm text-muted-foreground">301 Bryant St, San Francisco, CA 94107</p>
16
- <p className="mt-2 text-sm text-muted-foreground">
17
- I have read and agree to the Terms & Conditions.
161
+ <h2 className="mb-1 text-lg font-semibold text-primary">
162
+ {listingName ?? "Apply for a property"}
163
+ </h2>
164
+ <p className="text-sm text-muted-foreground">
165
+ {propertyAddress ??
166
+ (listingId
167
+ ? "Loading…"
168
+ : "Select a property from search or listing detail to apply.")}
18
169
  </p>
170
+ {loadError && <p className="mt-2 text-sm text-destructive">{loadError}</p>}
19
171
  </div>
20
172
  <div className="flex gap-2">
21
- <Button variant="outline" size="sm">
22
- Cancel Application
173
+ <Button asChild variant="outline" size="sm">
174
+ <Link to={listingId ? `/property/${listingId}` : "/properties"}>Back to listing</Link>
23
175
  </Button>
24
- <Button size="sm">Save for Later</Button>
25
176
  </div>
26
177
  </Card>
27
- <Tabs defaultValue="applicants" className="mb-6">
28
- <TabsList className="mb-4 flex h-auto flex-wrap gap-4 border-b border-border bg-transparent p-0">
29
- <TabsTrigger
30
- value="applicants"
31
- className="rounded-none border-b-2 border-primary bg-transparent px-0 pb-1 data-[state=active]:shadow-none"
32
- >
33
- APPLICANTS & OCCUPANTS
34
- </TabsTrigger>
35
- <TabsTrigger
36
- value="details"
37
- className="rounded-none border-b-0 bg-transparent px-0 pb-1 text-muted-foreground data-[state=active]:shadow-none"
38
- >
39
- YOUR DETAILS
40
- </TabsTrigger>
41
- <TabsTrigger
42
- value="screening"
43
- className="rounded-none border-b-0 bg-transparent px-0 pb-1 text-muted-foreground data-[state=active]:shadow-none"
44
- >
45
- SCREENING
46
- </TabsTrigger>
47
- <TabsTrigger
48
- value="submit"
49
- className="rounded-none border-b-0 bg-transparent px-0 pb-1 text-muted-foreground data-[state=active]:shadow-none"
50
- >
51
- FINISH & SUBMIT
52
- </TabsTrigger>
53
- </TabsList>
54
- <TabsContent value="applicants" className="mt-0" />
55
- </Tabs>
56
- <Card>
178
+
179
+ <Card className="mb-6">
57
180
  <CardContent className="pt-6">
58
- <h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-foreground">
59
- YOUR INFO
60
- </h3>
61
- <div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2">
62
- <div className="space-y-2">
63
- <Label>First Name *</Label>
64
- <Input type="text" defaultValue="Sarah" />
181
+ <form onSubmit={handleSubmit}>
182
+ <h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-foreground">
183
+ YOUR INFO
184
+ </h3>
185
+ <div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2">
186
+ <div className="space-y-2">
187
+ <Label htmlFor="app-first-name">First Name *</Label>
188
+ <Input
189
+ id="app-first-name"
190
+ type="text"
191
+ value={firstName}
192
+ onChange={(e) => setFirstName(e.target.value)}
193
+ />
194
+ </div>
195
+ <div className="space-y-2">
196
+ <Label htmlFor="app-last-name">Last Name</Label>
197
+ <Input
198
+ id="app-last-name"
199
+ type="text"
200
+ value={lastName}
201
+ onChange={(e) => setLastName(e.target.value)}
202
+ />
203
+ </div>
65
204
  </div>
66
- <div className="space-y-2">
67
- <Label>Last Name</Label>
68
- <Input type="text" />
205
+ <div className="mb-4 space-y-2">
206
+ <Label htmlFor="app-email">Email Address</Label>
207
+ <Input
208
+ id="app-email"
209
+ type="email"
210
+ value={email}
211
+ onChange={(e) => setEmail(e.target.value)}
212
+ />
69
213
  </div>
70
- </div>
71
- <div className="mb-4 space-y-2">
72
- <Label>Email Address</Label>
73
- <Input type="email" />
74
- </div>
75
- <div className="mb-4 space-y-2">
76
- <Label>Phone Number</Label>
77
- <Input type="tel" />
78
- </div>
79
- <h3 className="mb-4 mt-6 text-xs font-semibold uppercase tracking-wider text-foreground">
80
- MOVE IN
81
- </h3>
82
- <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
83
- <div className="space-y-2">
84
- <Label>MOVE IN DATE</Label>
85
- <Input type="text" placeholder="Select Date" />
214
+ <div className="mb-4 space-y-2">
215
+ <Label htmlFor="app-phone">Phone Number</Label>
216
+ <Input
217
+ id="app-phone"
218
+ type="tel"
219
+ value={phone}
220
+ onChange={(e) => setPhone(e.target.value)}
221
+ />
86
222
  </div>
87
- <div className="space-y-2">
88
- <Label>PREFERRED TERM</Label>
89
- <select className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring">
90
- <option>Select One</option>
91
- <option>1 month</option>
92
- <option>6 months</option>
93
- <option>12 months</option>
94
- </select>
223
+ <h3 className="mb-4 mt-6 text-xs font-semibold uppercase tracking-wider text-foreground">
224
+ MOVE IN
225
+ </h3>
226
+ <div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2">
227
+ <div className="space-y-2">
228
+ <Label htmlFor="app-move-in">Move in date</Label>
229
+ <Input
230
+ id="app-move-in"
231
+ type="date"
232
+ value={moveInDate}
233
+ onChange={(e) => setMoveInDate(e.target.value)}
234
+ />
235
+ </div>
236
+ <div className="space-y-2">
237
+ <Label htmlFor="app-term">Preferred term</Label>
238
+ <select
239
+ id="app-term"
240
+ className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
241
+ value={preferredTerm}
242
+ onChange={(e) => setPreferredTerm(e.target.value)}
243
+ >
244
+ {PREFERRED_TERM_OPTIONS.map((opt) => (
245
+ <option key={opt || "empty"} value={opt}>
246
+ {opt || "Select one"}
247
+ </option>
248
+ ))}
249
+ </select>
250
+ </div>
95
251
  </div>
96
- </div>
252
+ <div className="mb-4 space-y-2">
253
+ <Label htmlFor="app-employment">Employment info</Label>
254
+ <textarea
255
+ id="app-employment"
256
+ rows={3}
257
+ className="min-h-[80px] w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
258
+ value={employmentInfo}
259
+ onChange={(e) => setEmploymentInfo(e.target.value)}
260
+ />
261
+ </div>
262
+ <div className="mb-4 space-y-2">
263
+ <Label htmlFor="app-references">References</Label>
264
+ <textarea
265
+ id="app-references"
266
+ rows={3}
267
+ className="min-h-[80px] w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
268
+ value={references}
269
+ onChange={(e) => setReferences(e.target.value)}
270
+ />
271
+ </div>
272
+ {submitError && <p className="mb-4 text-sm text-destructive">{submitError}</p>}
273
+ <div className="flex gap-2">
274
+ <Button
275
+ type="submit"
276
+ size="sm"
277
+ className="bg-teal-600 hover:bg-teal-700"
278
+ disabled={submitting}
279
+ >
280
+ {submitting ? "Submitting…" : "Submit application"}
281
+ </Button>
282
+ </div>
283
+ </form>
97
284
  </CardContent>
98
285
  </Card>
99
286
  </div>
@@ -0,0 +1,158 @@
1
+ import { useState, useCallback } from "react";
2
+ import { Link } from "react-router";
3
+ import { Button } from "../components/ui/button";
4
+ import { Input } from "../components/ui/input";
5
+ import { Label } from "../components/ui/label";
6
+ import { Card, CardContent, CardHeader, CardTitle } from "../components/ui/card";
7
+ import { createContactUsLead } from "@/api/leadApi";
8
+
9
+ export default function Contact() {
10
+ const [firstName, setFirstName] = useState("");
11
+ const [lastName, setLastName] = useState("");
12
+ const [email, setEmail] = useState("");
13
+ const [phone, setPhone] = useState("");
14
+ const [subject, setSubject] = useState("");
15
+ const [message, setMessage] = useState("");
16
+ const [submitting, setSubmitting] = useState(false);
17
+ const [submitError, setSubmitError] = useState<string | null>(null);
18
+ const [submitted, setSubmitted] = useState(false);
19
+
20
+ const handleSubmit = useCallback(
21
+ async (e: React.FormEvent) => {
22
+ e.preventDefault();
23
+ setSubmitError(null);
24
+ setSubmitting(true);
25
+ try {
26
+ await createContactUsLead({
27
+ FirstName: firstName.trim(),
28
+ LastName: lastName.trim(),
29
+ Email: email.trim(),
30
+ Phone: phone.trim() || undefined,
31
+ Subject: subject.trim() || undefined,
32
+ Message: message.trim(),
33
+ });
34
+ setSubmitted(true);
35
+ setFirstName("");
36
+ setLastName("");
37
+ setEmail("");
38
+ setPhone("");
39
+ setSubject("");
40
+ setMessage("");
41
+ } catch (err) {
42
+ setSubmitError(
43
+ err instanceof Error ? err.message : "Something went wrong. Please try again.",
44
+ );
45
+ } finally {
46
+ setSubmitting(false);
47
+ }
48
+ },
49
+ [firstName, lastName, email, phone, subject, message],
50
+ );
51
+
52
+ if (submitted) {
53
+ return (
54
+ <div className="mx-auto max-w-[600px]">
55
+ <Card>
56
+ <CardHeader>
57
+ <CardTitle className="text-xl">Message sent</CardTitle>
58
+ </CardHeader>
59
+ <CardContent className="space-y-4">
60
+ <p className="text-muted-foreground">
61
+ Thank you for reaching out. We’ve received your message and will get back to you soon.
62
+ </p>
63
+ <Button asChild variant="outline">
64
+ <Link to="/">Back to Home</Link>
65
+ </Button>
66
+ <Button asChild>
67
+ <Link to="/contact">Send another message</Link>
68
+ </Button>
69
+ </CardContent>
70
+ </Card>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ return (
76
+ <div className="mx-auto max-w-[600px]">
77
+ <h1 className="mb-2 text-2xl font-semibold text-foreground">Contact Us</h1>
78
+ <p className="mb-6 text-muted-foreground">
79
+ Have a question or feedback? Send us a message and we’ll respond as soon as we can.
80
+ </p>
81
+ <Card>
82
+ <CardHeader>
83
+ <CardTitle className="text-lg">Send a message</CardTitle>
84
+ </CardHeader>
85
+ <CardContent>
86
+ <form onSubmit={handleSubmit} className="space-y-4">
87
+ <div className="grid gap-4 sm:grid-cols-2">
88
+ <div className="space-y-2">
89
+ <Label htmlFor="contact-first">First name *</Label>
90
+ <Input
91
+ id="contact-first"
92
+ type="text"
93
+ value={firstName}
94
+ onChange={(e) => setFirstName(e.target.value)}
95
+ required
96
+ />
97
+ </div>
98
+ <div className="space-y-2">
99
+ <Label htmlFor="contact-last">Last name *</Label>
100
+ <Input
101
+ id="contact-last"
102
+ type="text"
103
+ value={lastName}
104
+ onChange={(e) => setLastName(e.target.value)}
105
+ required
106
+ />
107
+ </div>
108
+ </div>
109
+ <div className="space-y-2">
110
+ <Label htmlFor="contact-email">Email *</Label>
111
+ <Input
112
+ id="contact-email"
113
+ type="email"
114
+ value={email}
115
+ onChange={(e) => setEmail(e.target.value)}
116
+ required
117
+ />
118
+ </div>
119
+ <div className="space-y-2">
120
+ <Label htmlFor="contact-phone">Phone</Label>
121
+ <Input
122
+ id="contact-phone"
123
+ type="tel"
124
+ value={phone}
125
+ onChange={(e) => setPhone(e.target.value)}
126
+ />
127
+ </div>
128
+ <div className="space-y-2">
129
+ <Label htmlFor="contact-subject">Subject</Label>
130
+ <Input
131
+ id="contact-subject"
132
+ type="text"
133
+ value={subject}
134
+ onChange={(e) => setSubject(e.target.value)}
135
+ placeholder="e.g. General inquiry, Property question"
136
+ />
137
+ </div>
138
+ <div className="space-y-2">
139
+ <Label htmlFor="contact-message">Message *</Label>
140
+ <textarea
141
+ id="contact-message"
142
+ rows={5}
143
+ value={message}
144
+ onChange={(e) => setMessage(e.target.value)}
145
+ required
146
+ className="min-h-[120px] w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
147
+ />
148
+ </div>
149
+ {submitError && <p className="text-sm text-destructive">{submitError}</p>}
150
+ <Button type="submit" disabled={submitting} className="bg-teal-600 hover:bg-teal-700">
151
+ {submitting ? "Sending…" : "Send message"}
152
+ </Button>
153
+ </form>
154
+ </CardContent>
155
+ </Card>
156
+ </div>
157
+ );
158
+ }