@salesforce/webapp-template-app-react-sample-b2e-experimental 1.48.3 → 1.50.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 (58) hide show
  1. package/dist/CHANGELOG.md +16 -0
  2. package/dist/force-app/main/default/data/Property__c.json +2 -2
  3. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/dashboard.ts +170 -0
  4. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/maintenance.ts +221 -0
  5. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/properties.ts +157 -0
  6. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/api/utils.ts +4 -0
  7. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/appLayout.tsx +20 -8
  8. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/appliances.svg +13 -0
  9. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/dashboard.svg +4 -0
  10. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/downgraph.svg +3 -0
  11. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/electrical.svg +41 -0
  12. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/files.svg +7 -0
  13. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/hvac.svg +79 -0
  14. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/maintenance.svg +4 -0
  15. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/pest.svg +5 -0
  16. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/plumbing.svg +7 -0
  17. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/properties.svg +14 -0
  18. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/support.svg +6 -0
  19. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/upgraph.svg +3 -0
  20. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/users.svg +8 -0
  21. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/assets/icons/zen-logo.svg +5 -0
  22. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/AnalyticsTile.tsx +29 -0
  23. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ApplicationCard.tsx +43 -0
  24. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/IssuesDonutChart.tsx +66 -0
  25. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceRequestCard.tsx +71 -0
  26. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/MaintenanceTable.tsx +110 -0
  27. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/PriorityBadge.tsx +29 -0
  28. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/PropertyCard.tsx +61 -0
  29. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/StatCard.tsx +52 -0
  30. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/StatusBadge.tsx +37 -0
  31. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/TopBar.tsx +72 -0
  32. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/UserAvatar.tsx +35 -0
  33. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/VerticalNav.tsx +54 -0
  34. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/alert.tsx +69 -0
  35. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/button.tsx +67 -0
  36. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/card.tsx +92 -0
  37. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/dialog.tsx +143 -0
  38. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/field.tsx +222 -0
  39. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/index.ts +72 -0
  40. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/input.tsx +19 -0
  41. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/label.tsx +19 -0
  42. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/pagination.tsx +112 -0
  43. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/select.tsx +183 -0
  44. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/separator.tsx +26 -0
  45. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/skeleton.tsx +14 -0
  46. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/spinner.tsx +15 -0
  47. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/table.tsx +87 -0
  48. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components/ui/tabs.tsx +78 -0
  49. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/components.json +18 -0
  50. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/types.ts +57 -0
  51. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/lib/utils.ts +6 -0
  52. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Home.tsx +163 -10
  53. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Maintenance.tsx +176 -0
  54. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/pages/Properties.tsx +94 -0
  55. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/routes.tsx +19 -7
  56. package/dist/force-app/main/default/webapplications/appreactsampleb2e/src/styles/global.css +160 -0
  57. package/dist/package.json +1 -1
  58. package/package.json +2 -2
package/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.50.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.49.0...v1.50.0) (2026-02-24)
7
+
8
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
9
+
10
+
11
+
12
+
13
+
14
+ # [1.49.0](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.48.3...v1.49.0) (2026-02-23)
15
+
16
+ **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
17
+
18
+
19
+
20
+
21
+
6
22
  ## [1.48.3](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.48.2...v1.48.3) (2026-02-23)
7
23
 
8
24
  **Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
@@ -387,7 +387,7 @@
387
387
  "Utilities__c": "Water;Trash",
388
388
  "Description__c": "Ultra-luxury penthouse with concierge service, gym, and incredible bay and city views. Currently under maintenance.",
389
389
  "Tour_URL__c": "https://example.com/tour/luxury-highrise",
390
- "Hero_Image__c": "https://images.unsplash.com/photo-1600607687644-aac4c3eac7f4?w=800",
390
+ "Hero_Image__c": "https://images.unsplash.com/photo-1737898378296-94dc316cd443?w=800",
391
391
  "Agent__c": null
392
392
  },
393
393
  {
@@ -543,7 +543,7 @@
543
543
  "Utilities__c": "Water;Trash",
544
544
  "Description__c": "Comfortable townhouse with private patio, attached garage, and close to UC Berkeley. Great for students or faculty.",
545
545
  "Tour_URL__c": null,
546
- "Hero_Image__c": "https://images.unsplash.com/photo-1600573472556-e636b2f50cf9?w=800",
546
+ "Hero_Image__c": "https://images.unsplash.com/photo-1603661764782-a3c9812afada?w=800",
547
547
  "Agent__c": null
548
548
  },
549
549
  {
@@ -0,0 +1,170 @@
1
+ import { executeGraphQL } from "@salesforce/webapp-experimental/api";
2
+ import type { DashboardMetrics, Application } from "../lib/types.js";
3
+ import { gql } from "./utils.js";
4
+ import type {
5
+ GetDashboardMetricsQuery,
6
+ GetOpenApplicationsQuery,
7
+ GetOpenApplicationsQueryVariables,
8
+ GetUserInfoQuery,
9
+ } from "./graphql-operations-types.js";
10
+
11
+ // Query to get property counts for dashboard metrics
12
+ const GET_DASHBOARD_METRICS = gql`
13
+ query GetDashboardMetrics {
14
+ uiapi {
15
+ query {
16
+ allProperties: Property__c {
17
+ edges {
18
+ node {
19
+ Id
20
+ Status__c {
21
+ value
22
+ }
23
+ }
24
+ }
25
+ }
26
+ maintenanceRequests: Maintenance_Request__c(first: 100) {
27
+ edges {
28
+ node {
29
+ Id
30
+ Type__c {
31
+ value
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ `;
40
+
41
+ // Query to get open applications
42
+ const GET_OPEN_APPLICATIONS = gql`
43
+ query GetOpenApplications($first: Int) {
44
+ uiapi {
45
+ query {
46
+ Application__c(
47
+ first: $first
48
+ where: { Status__c: { in: ["Submitted", "Background Check"] } }
49
+ orderBy: { CreatedDate: { order: ASC } }
50
+ ) {
51
+ edges {
52
+ node {
53
+ Id
54
+ Name {
55
+ value
56
+ }
57
+ User__r {
58
+ Name {
59
+ value
60
+ }
61
+ }
62
+ Property__r {
63
+ Address__c {
64
+ value
65
+ }
66
+ }
67
+ Status__c {
68
+ value
69
+ }
70
+ CreatedDate {
71
+ value
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ `;
80
+
81
+ // Query to get current user information
82
+ const GET_USER_INFO = gql`
83
+ query GetUserInfo {
84
+ uiapi {
85
+ query {
86
+ User(first: 1) {
87
+ edges {
88
+ node {
89
+ Id
90
+ Name {
91
+ value
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ `;
100
+
101
+ // Fetch dashboard metrics
102
+ export async function getDashboardMetrics(): Promise<{
103
+ properties: any[];
104
+ maintenanceRequests: any[];
105
+ }> {
106
+ const response = await executeGraphQL<GetDashboardMetricsQuery>(GET_DASHBOARD_METRICS);
107
+ const properties = response?.uiapi?.query?.allProperties?.edges?.map((edge) => edge?.node) || [];
108
+ const maintenanceRequests =
109
+ response?.uiapi?.query?.maintenanceRequests?.edges?.map((edge) => edge?.node) || [];
110
+ return { properties, maintenanceRequests };
111
+ }
112
+
113
+ // Fetch open applications
114
+ export async function getOpenApplications(first: number = 5): Promise<Application[]> {
115
+ const variables: GetOpenApplicationsQueryVariables = { first };
116
+ const response = await executeGraphQL<GetOpenApplicationsQuery>(GET_OPEN_APPLICATIONS, variables);
117
+ const apps =
118
+ response?.uiapi?.query?.Application__c?.edges?.map((edge) =>
119
+ transformApplication(edge?.node),
120
+ ) || [];
121
+ return apps;
122
+ }
123
+
124
+ // Fetch current user information
125
+ export async function getUserInfo(): Promise<{ name: string; id: string } | null> {
126
+ try {
127
+ const response = await executeGraphQL<GetUserInfoQuery>(GET_USER_INFO);
128
+ const user = response?.uiapi?.query?.User?.edges?.[0]?.node;
129
+ if (user) {
130
+ return {
131
+ id: user.Id,
132
+ name: user.Name?.value || "User",
133
+ };
134
+ }
135
+ return null;
136
+ } catch (error) {
137
+ console.error("Error fetching user info:", error);
138
+ return null;
139
+ }
140
+ }
141
+
142
+ // Helper function to calculate dashboard metrics from properties
143
+ export const calculateMetrics = (properties: any[]): DashboardMetrics => {
144
+ const total = properties.length;
145
+ const available = properties.filter((p) => p.Status__c?.value === "Available").length;
146
+ const occupied = properties.filter((p) => p.Status__c?.value === "Rented").length;
147
+
148
+ return {
149
+ totalProperties: total,
150
+ unitsAvailable: available,
151
+ occupiedUnits: occupied,
152
+ topMaintenanceIssue: "Plumbing",
153
+ topMaintenanceIssueCount: 0,
154
+ };
155
+ };
156
+
157
+ // Helper function to transform application data
158
+ function transformApplication(node: any): Application {
159
+ const createdDate = new Date(node.CreatedDate?.value);
160
+ const daysAgo = Math.floor((Date.now() - createdDate.getTime()) / (1000 * 60 * 60 * 24));
161
+ const timeAgo = daysAgo === 0 ? "today" : daysAgo === 1 ? "1 day ago" : `${daysAgo} days ago`;
162
+
163
+ return {
164
+ id: node.Id,
165
+ applicantName: node.User__r?.Name?.value || "Unknown",
166
+ propertyAddress: node.Property__r?.Address__c?.value || "Unknown Address",
167
+ submittedDate: timeAgo,
168
+ status: node.Status__c?.value?.toLowerCase() || "pending",
169
+ };
170
+ }
@@ -0,0 +1,221 @@
1
+ import { executeGraphQL } from "@salesforce/webapp-experimental/api";
2
+ import type { MaintenanceRequest } from "../lib/types.js";
3
+ import { gql } from "./utils.js";
4
+ import type {
5
+ GetMaintenanceRequestsQuery,
6
+ GetMaintenanceRequestsQueryVariables,
7
+ GetAllMaintenanceRequestsQuery,
8
+ GetAllMaintenanceRequestsQueryVariables,
9
+ } from "./graphql-operations-types.js";
10
+
11
+ // Query to get recent maintenance requests
12
+ const GET_MAINTENANCE_REQUESTS = gql`
13
+ query GetMaintenanceRequests($first: Int) {
14
+ uiapi {
15
+ query {
16
+ Maintenance_Request__c(first: $first, orderBy: { Priority__c: { order: DESC } }) {
17
+ edges {
18
+ node {
19
+ Id
20
+ Name {
21
+ value
22
+ }
23
+ Property__r {
24
+ Address__c {
25
+ value
26
+ }
27
+ }
28
+ User__r {
29
+ Name {
30
+ value
31
+ }
32
+ }
33
+ Type__c {
34
+ value
35
+ }
36
+ Priority__c {
37
+ value
38
+ }
39
+ Status__c {
40
+ value
41
+ }
42
+ Description__c {
43
+ value
44
+ }
45
+ Scheduled__c {
46
+ value
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ `;
55
+
56
+ // Query to get all maintenance requests for the maintenance page
57
+ const GET_ALL_MAINTENANCE_REQUESTS = gql`
58
+ query GetAllMaintenanceRequests($first: Int, $after: String) {
59
+ uiapi {
60
+ query {
61
+ Maintenance_Request__c(
62
+ first: $first
63
+ after: $after
64
+ orderBy: { Priority__c: { order: DESC }, Scheduled__c: { order: ASC } }
65
+ ) {
66
+ edges {
67
+ node {
68
+ Id
69
+ Name {
70
+ value
71
+ }
72
+ Description__c {
73
+ value
74
+ }
75
+ Type__c {
76
+ value
77
+ }
78
+ Priority__c {
79
+ value
80
+ }
81
+ Status__c {
82
+ value
83
+ }
84
+ Scheduled__c {
85
+ value
86
+ }
87
+ Property__r {
88
+ Address__c {
89
+ value
90
+ }
91
+ Name {
92
+ value
93
+ }
94
+ }
95
+ User__r {
96
+ Name {
97
+ value
98
+ }
99
+ }
100
+ Owner {
101
+ ... on User {
102
+ Name {
103
+ value
104
+ }
105
+ }
106
+ }
107
+ ContentDocumentLinks(first: 1) {
108
+ edges {
109
+ node {
110
+ ContentDocument {
111
+ LatestPublishedVersionId {
112
+ value
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ pageInfo {
121
+ hasNextPage
122
+ endCursor
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ `;
129
+
130
+ // Fetch maintenance requests for dashboard
131
+ export async function getMaintenanceRequests(first: number = 5): Promise<MaintenanceRequest[]> {
132
+ const variables: GetMaintenanceRequestsQueryVariables = { first };
133
+ const response = await executeGraphQL<GetMaintenanceRequestsQuery>(
134
+ GET_MAINTENANCE_REQUESTS,
135
+ variables,
136
+ );
137
+ const requests =
138
+ response?.uiapi?.query?.Maintenance_Request__c?.edges?.map((edge) =>
139
+ transformMaintenanceRequest(edge?.node),
140
+ ) || [];
141
+ return requests;
142
+ }
143
+
144
+ // Fetch all maintenance requests for the maintenance page
145
+ export async function getAllMaintenanceRequests(
146
+ first: number = 100,
147
+ ): Promise<MaintenanceRequest[]> {
148
+ const variables: GetAllMaintenanceRequestsQueryVariables = { first };
149
+ const response = await executeGraphQL<GetAllMaintenanceRequestsQuery>(
150
+ GET_ALL_MAINTENANCE_REQUESTS,
151
+ variables,
152
+ );
153
+ const requests =
154
+ response?.uiapi?.query?.Maintenance_Request__c?.edges?.map((edge: any) =>
155
+ transformMaintenanceTaskFull(edge?.node),
156
+ ) || [];
157
+ return requests;
158
+ }
159
+
160
+ // Helper function to transform maintenance request data
161
+ function transformMaintenanceRequest(node: any): MaintenanceRequest {
162
+ const scheduledDate = node.Scheduled__c?.value
163
+ ? new Date(node.Scheduled__c.value).toLocaleString()
164
+ : undefined;
165
+
166
+ return {
167
+ id: node.Id,
168
+ propertyAddress: node.Property__r?.Address__c?.value || "Unknown Address",
169
+ issueType: node.Type__c?.value || "General",
170
+ priority: node.Priority__c?.value?.toLowerCase() || "medium",
171
+ status: node.Status__c?.value?.toLowerCase() || "new",
172
+ assignedWorker: undefined,
173
+ scheduledDateTime: scheduledDate,
174
+ description: node.Description__c?.value || "",
175
+ tenantName: node.User__r?.Name?.value || "Unknown",
176
+ };
177
+ }
178
+
179
+ // Helper function to transform maintenance request data with all fields for maintenance page
180
+ function transformMaintenanceTaskFull(node: any): MaintenanceRequest {
181
+ const scheduledDate = node.Scheduled__c?.value ? new Date(node.Scheduled__c.value) : null;
182
+ const formattedDate = scheduledDate
183
+ ? scheduledDate.toLocaleDateString("en-US", { month: "short", day: "numeric" }) +
184
+ ", " +
185
+ scheduledDate.toLocaleTimeString("en-US", {
186
+ hour: "numeric",
187
+ minute: "2-digit",
188
+ hour12: true,
189
+ })
190
+ : undefined;
191
+
192
+ // Get image URL from ContentDocumentLinks
193
+ const imageVersionId =
194
+ node.ContentDocumentLinks?.edges?.[0]?.node?.ContentDocument?.LatestPublishedVersionId?.value;
195
+ const imageUrl = imageVersionId
196
+ ? `/sfc/servlet.shepherd/version/download/${imageVersionId}`
197
+ : undefined;
198
+
199
+ // Get tenant unit from Property
200
+ const tenantUnit = node.Property__r?.Name?.value || node.Property__r?.Address__c?.value;
201
+
202
+ // Get assigned worker name from Owner
203
+ const assignedWorkerName = node.Owner?.Name?.value;
204
+
205
+ return {
206
+ id: node.Id,
207
+ propertyAddress: node.Property__r?.Address__c?.value || "Unknown Address",
208
+ issueType: node.Type__c?.value || "General",
209
+ priority: node.Priority__c?.value?.toLowerCase() || "medium",
210
+ status: node.Status__c?.value?.toLowerCase().replace(" ", "_") || "new",
211
+ assignedWorker: assignedWorkerName,
212
+ scheduledDateTime: scheduledDate?.toLocaleString(),
213
+ description: node.Description__c?.value || "",
214
+ tenantName: node.User__r?.Name?.value || "Unknown",
215
+ imageUrl,
216
+ tenantUnit,
217
+ assignedWorkerName,
218
+ assignedWorkerOrg: "ABC Diamond Technicians", // This would come from a related object in real scenario
219
+ formattedDate,
220
+ };
221
+ }
@@ -0,0 +1,157 @@
1
+ import { executeGraphQL } from "@salesforce/webapp-experimental/api";
2
+ import type { Property } from "../lib/types.js";
3
+ import { gql } from "./utils.js";
4
+ import type {
5
+ GetPropertiesQueryVariables,
6
+ GetPropertiesQuery,
7
+ } from "./graphql-operations-types.js";
8
+
9
+ // GraphQL query to get properties with pagination
10
+ const GET_PROPERTIES_PAGINATED = gql`
11
+ query GetProperties($first: Int, $after: String) {
12
+ uiapi {
13
+ query {
14
+ Property__c(first: $first, after: $after, orderBy: { CreatedDate: { order: DESC } }) {
15
+ edges {
16
+ node {
17
+ Id
18
+ Name {
19
+ value
20
+ }
21
+ Address__c {
22
+ value
23
+ }
24
+ Description__c {
25
+ value
26
+ }
27
+ Type__c {
28
+ value
29
+ }
30
+ Status__c {
31
+ value
32
+ }
33
+ Monthly_Rent__c {
34
+ value
35
+ }
36
+ Bedrooms__c {
37
+ value
38
+ }
39
+ Bathrooms__c {
40
+ value
41
+ }
42
+ Sq_Ft__c {
43
+ value
44
+ }
45
+ Year_Built__c {
46
+ value
47
+ }
48
+ Hero_Image__c {
49
+ value
50
+ }
51
+ Deposit__c {
52
+ value
53
+ }
54
+ Parking__c {
55
+ value
56
+ }
57
+ Pet_Friendly__c {
58
+ value
59
+ }
60
+ Available_Date__c {
61
+ value
62
+ }
63
+ Lease_Term__c {
64
+ value
65
+ }
66
+ Features__c {
67
+ value
68
+ }
69
+ Utilities__c {
70
+ value
71
+ }
72
+ Tour_URL__c {
73
+ value
74
+ }
75
+ CreatedDate {
76
+ value
77
+ }
78
+ }
79
+ }
80
+ pageInfo {
81
+ hasNextPage
82
+ endCursor
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ `;
89
+
90
+ export interface PropertiesResult {
91
+ properties: Property[];
92
+ pageInfo: {
93
+ hasNextPage: boolean;
94
+ endCursor?: string | null;
95
+ };
96
+ }
97
+
98
+ // Fetch properties with pagination
99
+ export async function getProperties(first: number = 12, after?: string): Promise<PropertiesResult> {
100
+ const variables: GetPropertiesQueryVariables = { first };
101
+ if (after) {
102
+ variables.after = after;
103
+ }
104
+
105
+ const response = await executeGraphQL<GetPropertiesQuery>(GET_PROPERTIES_PAGINATED, variables);
106
+ const edges = response?.uiapi?.query?.Property__c?.edges || [];
107
+ const pageInfo = response?.uiapi?.query?.Property__c?.pageInfo || {
108
+ hasNextPage: false,
109
+ endCursor: null,
110
+ };
111
+
112
+ const properties = edges.map((edge) => transformProperty(edge?.node));
113
+
114
+ return {
115
+ properties,
116
+ pageInfo,
117
+ };
118
+ }
119
+
120
+ // Helper function to transform property data from GraphQL to Property type
121
+ function transformProperty(node: any): Property {
122
+ // Extract year from CreatedDate for "Since [year]" display
123
+ const createdYear = node.CreatedDate?.value
124
+ ? new Date(node.CreatedDate.value).getFullYear().toString()
125
+ : undefined;
126
+
127
+ // Parse multi-picklist values (comma-separated strings to arrays)
128
+ const features = node.Features__c?.value ? node.Features__c.value.split(";") : undefined;
129
+ const utilities = node.Utilities__c?.value ? node.Utilities__c.value.split(";") : undefined;
130
+
131
+ return {
132
+ id: node.Id,
133
+ name: node.Name?.value || "Unnamed Property",
134
+ address: node.Address__c?.value || "Address not available",
135
+ type:
136
+ (node.Type__c?.value?.toLowerCase() as "apartment" | "house" | "commercial") || "apartment",
137
+ status:
138
+ (node.Status__c?.value?.toLowerCase() as "available" | "rented" | "maintenance") ||
139
+ "available",
140
+ monthlyRent: node.Monthly_Rent__c?.value || 0,
141
+ bedrooms: node.Bedrooms__c?.value,
142
+ bathrooms: node.Bathrooms__c?.value,
143
+ heroImage: node.Hero_Image__c?.value,
144
+ description: node.Description__c?.value,
145
+ sqFt: node.Sq_Ft__c?.value,
146
+ yearBuilt: node.Year_Built__c?.value,
147
+ deposit: node.Deposit__c?.value,
148
+ parking: node.Parking__c?.value,
149
+ petFriendly: node.Pet_Friendly__c?.value,
150
+ availableDate: node.Available_Date__c?.value,
151
+ leaseTerm: node.Lease_Term__c?.value,
152
+ features,
153
+ utilities,
154
+ tourUrl: node.Tour_URL__c?.value,
155
+ createdDate: createdYear,
156
+ };
157
+ }
@@ -0,0 +1,4 @@
1
+ // Simple gql template tag function
2
+ export const gql = (strings: TemplateStringsArray, ...values: unknown[]): string => {
3
+ return strings.reduce((result, str, i) => result + str + (values[i] ?? ""), "");
4
+ };
@@ -1,11 +1,23 @@
1
- import { Outlet } from 'react-router';
2
- import NavigationMenu from './navigationMenu';
1
+ import { Outlet } from "react-router";
2
+ import { TopBar } from "./components/TopBar.js";
3
+ import { VerticalNav } from "./components/VerticalNav.js";
3
4
 
4
5
  export default function AppLayout() {
5
- return (
6
- <>
7
- <NavigationMenu />
8
- <Outlet />
9
- </>
10
- );
6
+ return (
7
+ <div className="flex flex-col h-screen">
8
+ {/* Top Bar */}
9
+ <TopBar />
10
+
11
+ {/* Main Content Area with Sidebar */}
12
+ <div className="flex flex-1 overflow-hidden">
13
+ {/* Vertical Navigation */}
14
+ <VerticalNav />
15
+
16
+ {/* Page Content */}
17
+ <main className="flex-1 overflow-auto">
18
+ <Outlet />
19
+ </main>
20
+ </div>
21
+ </div>
22
+ );
11
23
  }
@@ -0,0 +1,13 @@
1
+ <?xml version='1.0' encoding='iso-8859-1'?>
2
+ <!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
3
+ <svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 463 463" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 463 463">
4
+ <g>
5
+ <path d="m367.5,0h-272c-21.78,0-39.5,17.72-39.5,39.5v368c0,10.336 6.71,19.128 16,22.266v9.734c0,12.958 10.542,23.5 23.5,23.5h272c12.958,0 23.5-10.542 23.5-23.5v-9.734c9.29-3.138 16-11.93 16-22.266v-368c0-21.78-17.72-39.5-39.5-39.5zm-272,15h272c13.51,0 24.5,10.991 24.5,24.5v56.5h-321v-56.5c0-13.509 10.99-24.5 24.5-24.5zm272,433h-272c-4.687,0-8.5-3.813-8.5-8.5v-8.5h289v8.5c0,4.687-3.813,8.5-8.5,8.5zm16-32h-304c-4.687,0-8.5-3.813-8.5-8.5v-296.5h321v296.5c0,4.687-3.813,8.5-8.5,8.5z"/>
6
+ <path d="M231.5,136C161.196,136,104,193.196,104,263.5S161.196,391,231.5,391S359,333.804,359,263.5S301.804,136,231.5,136z M231.5,376C169.468,376,119,325.533,119,263.5S169.468,151,231.5,151S344,201.467,344,263.5S293.532,376,231.5,376z"/>
7
+ <path d="m279.5,79c12.958,0 23.5-10.542 23.5-23.5s-10.542-23.5-23.5-23.5-23.5,10.542-23.5,23.5 10.542,23.5 23.5,23.5zm0-32c4.687,0 8.5,3.813 8.5,8.5s-3.813,8.5-8.5,8.5-8.5-3.813-8.5-8.5 3.813-8.5 8.5-8.5z"/>
8
+ <path d="m343.5,79c12.958,0 23.5-10.542 23.5-23.5s-10.542-23.5-23.5-23.5-23.5,10.542-23.5,23.5 10.542,23.5 23.5,23.5zm0-32c4.687,0 8.5,3.813 8.5,8.5s-3.813,8.5-8.5,8.5-8.5-3.813-8.5-8.5 3.813-8.5 8.5-8.5z"/>
9
+ <path d="m111.5,79h104c8.547,0 15.5-6.953 15.5-15.5v-16c0-8.547-6.953-15.5-15.5-15.5h-104c-8.547,0-15.5,6.953-15.5,15.5v16c0,8.547 6.953,15.5 15.5,15.5zm-.5-31.5c0-0.276 0.225-0.5 0.5-0.5h104c0.275,0 0.5,0.224 0.5,0.5v16c0,0.276-0.225,0.5-0.5,0.5h-104c-0.275,0-0.5-0.224-0.5-0.5v-16z"/>
10
+ <path d="m231.5,168c-52.659,0-95.5,42.841-95.5,95.5s42.841,95.5 95.5,95.5 95.5-42.841 95.5-95.5-42.841-95.5-95.5-95.5zm0,176c-44.388,0-80.5-36.112-80.5-80.5s36.112-80.5 80.5-80.5 80.5,36.112 80.5,80.5-36.112,80.5-80.5,80.5z"/>
11
+ <path d="m231.5,200c-4.143,0-7.5,3.358-7.5,7.5s3.357,7.5 7.5,7.5c26.743,0 48.5,21.757 48.5,48.5 0,4.142 3.357,7.5 7.5,7.5s7.5-3.358 7.5-7.5c0-35.014-28.486-63.5-63.5-63.5z"/>
12
+ </g>
13
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M12 21C7.125 21 3 16.875 3 12C3 8.89234 4.67627 6.08945 7.15451 4.46562C8.48099 3.59646 10.0372 3.0651 11.6892 3.00559L12 3V6.75C9.10051 6.75 6.75 9.10051 6.75 12C6.75 14.8995 9.10051 17.25 12 17.25C14.8391 17.25 17.1518 14.9964 17.247 12.1805L17.25 12H21C21 16.8 17.0009 20.8729 12.2245 20.9971L12 21Z" stroke="#65185C" stroke-width="2" stroke-linecap="round"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M15.0006 7.69119L15.0003 3.5128C16.2044 3.91976 17.3294 4.62383 18.3753 5.625C19.3399 6.54825 20.0166 7.5898 20.4054 8.74965L20.4844 9L16.3089 8.9994C15.9535 8.49 15.51 8.04657 15.0006 7.69119Z" fill="#65185C" stroke="#65185C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M11.2117 8.64608C11.1612 8.76858 11.0637 8.86608 10.9412 8.91658C10.8802 8.94208 10.8152 8.95508 10.7502 8.95508H8.2957C8.0192 8.95508 7.7957 8.73158 7.7957 8.45508C7.7957 8.17858 8.0192 7.95508 8.2957 7.95508H9.5427L6.8637 5.2771L5.1722 6.9691C4.98469 7.15658 4.65319 7.15658 4.46519 6.9691L1.39619 3.8986C1.20119 3.7031 1.20119 3.3866 1.39669 3.1916C1.49419 3.0941 1.62219 3.0451 1.75019 3.0451C1.87819 3.0451 2.00619 3.0941 2.10369 3.1916L4.81869 5.9081L6.5097 4.2166C6.7052 4.0211 7.0217 4.0211 7.2172 4.2166L10.2502 7.24858V6.0001C10.2502 5.7236 10.4737 5.5001 10.7502 5.5001C11.0267 5.5001 11.2502 5.7236 11.2502 6.0001V8.45508C11.2502 8.52008 11.2367 8.58508 11.2117 8.64608Z" fill="#114C50"/>
3
+ </svg>