@salesforce/ui-bundle-template-app-react-sample-b2x 1.117.2

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 (444) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +52 -0
  3. package/dist/.forceignore +15 -0
  4. package/dist/.husky/pre-commit +4 -0
  5. package/dist/.prettierignore +11 -0
  6. package/dist/.prettierrc +17 -0
  7. package/dist/AGENT.md +193 -0
  8. package/dist/CHANGELOG.md +2128 -0
  9. package/dist/README.md +112 -0
  10. package/dist/config/project-scratch-def.json +13 -0
  11. package/dist/eslint.config.js +7 -0
  12. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls +69 -0
  13. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls-meta.xml +5 -0
  14. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls +308 -0
  15. package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls-meta.xml +5 -0
  16. package/dist/force-app/main/default/classes/TenantTriggerHandler.cls +77 -0
  17. package/dist/force-app/main/default/classes/TenantTriggerHandler.cls-meta.xml +5 -0
  18. package/dist/force-app/main/default/classes/TenantTriggerHandler_Test.cls +100 -0
  19. package/dist/force-app/main/default/classes/TenantTriggerHandler_Test.cls-meta.xml +5 -0
  20. package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
  21. package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
  22. package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
  23. package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
  24. package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
  25. package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
  26. package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
  27. package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
  28. package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
  29. package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
  30. package/dist/force-app/main/default/cspTrustedSites/GitHub_Avatars.cspTrustedSite-meta.xml +15 -0
  31. package/dist/force-app/main/default/cspTrustedSites/Google_Fonts.cspTrustedSite-meta.xml +15 -0
  32. package/dist/force-app/main/default/cspTrustedSites/Google_Fonts_Static.cspTrustedSite-meta.xml +15 -0
  33. package/dist/force-app/main/default/cspTrustedSites/OpenStreetMap_Nominatim.cspTrustedSite-meta.xml +15 -0
  34. package/dist/force-app/main/default/cspTrustedSites/OpenStreetMap_Tiles.cspTrustedSite-meta.xml +15 -0
  35. package/dist/force-app/main/default/cspTrustedSites/Open_Meteo_API.cspTrustedSite-meta.xml +15 -0
  36. package/dist/force-app/main/default/cspTrustedSites/Pexels_Images.cspTrustedSite-meta.xml +15 -0
  37. package/dist/force-app/main/default/cspTrustedSites/Pexels_Videos.cspTrustedSite-meta.xml +15 -0
  38. package/dist/force-app/main/default/cspTrustedSites/Unsplash_Images.cspTrustedSite-meta.xml +15 -0
  39. package/dist/force-app/main/default/data/Agent__c.json +79 -0
  40. package/dist/force-app/main/default/data/Application__c.json +124 -0
  41. package/dist/force-app/main/default/data/Contact.json +44 -0
  42. package/dist/force-app/main/default/data/KPI_Snapshot__c.json +160 -0
  43. package/dist/force-app/main/default/data/Lease__c.json +9442 -0
  44. package/dist/force-app/main/default/data/Maintenance_Request__c.json +514 -0
  45. package/dist/force-app/main/default/data/Maintenance_Worker__c.json +304 -0
  46. package/dist/force-app/main/default/data/Notification__c.json +214 -0
  47. package/dist/force-app/main/default/data/Payment__c.json +1024 -0
  48. package/dist/force-app/main/default/data/Property_Cost__c.json +484 -0
  49. package/dist/force-app/main/default/data/Property_Feature__c.json +169 -0
  50. package/dist/force-app/main/default/data/Property_Image__c.json +148 -0
  51. package/dist/force-app/main/default/data/Property_Listing__c.json +130 -0
  52. package/dist/force-app/main/default/data/Property_Management_Company__c.json +70 -0
  53. package/dist/force-app/main/default/data/Property_Owner__c.json +184 -0
  54. package/dist/force-app/main/default/data/Property_Sale__c.json +246 -0
  55. package/dist/force-app/main/default/data/Property__c.json +704 -0
  56. package/dist/force-app/main/default/data/Tenant__c.json +184 -0
  57. package/dist/force-app/main/default/data/data-plan.json +110 -0
  58. package/dist/force-app/main/default/data/prepare-import-unique-fields.js +85 -0
  59. package/dist/force-app/main/default/digitalExperienceConfigs/propertyrentalapp1.digitalExperienceConfig-meta.xml +8 -0
  60. package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/propertyrentalapp1.digitalExperience-meta.xml +11 -0
  61. package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/sfdc_cms__site/propertyrentalapp1/_meta.json +5 -0
  62. package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/sfdc_cms__site/propertyrentalapp1/content.json +10 -0
  63. package/dist/force-app/main/default/layouts/Application__c-Application Layout.layout-meta.xml +58 -0
  64. package/dist/force-app/main/default/layouts/KPI_Snapshot__c-KPI Snapshot Layout.layout-meta.xml +87 -0
  65. package/dist/force-app/main/default/layouts/Lease__c-Lease Layout.layout-meta.xml +83 -0
  66. package/dist/force-app/main/default/layouts/Maintenance_Request__c-Maintenance Request Layout.layout-meta.xml +89 -0
  67. package/dist/force-app/main/default/layouts/Maintenance_Worker__c-Maintenance Worker Layout.layout-meta.xml +66 -0
  68. package/dist/force-app/main/default/layouts/Payment__c-Payment Layout.layout-meta.xml +88 -0
  69. package/dist/force-app/main/default/layouts/Property_Cost__c-Property Cost Layout.layout-meta.xml +88 -0
  70. package/dist/force-app/main/default/layouts/Property_Feature__c-Property Feature Layout.layout-meta.xml +80 -0
  71. package/dist/force-app/main/default/layouts/Property_Image__c-Property Image Layout.layout-meta.xml +75 -0
  72. package/dist/force-app/main/default/layouts/Property_Listing__c-Property Listing Layout.layout-meta.xml +92 -0
  73. package/dist/force-app/main/default/layouts/Property_Management_Company__c-Property Management Company Layout.layout-meta.xml +75 -0
  74. package/dist/force-app/main/default/layouts/Property_Owner__c-Property Owner Layout.layout-meta.xml +67 -0
  75. package/dist/force-app/main/default/layouts/Property_Sale__c-Property Sale Layout.layout-meta.xml +87 -0
  76. package/dist/force-app/main/default/layouts/Property__c-Property Layout.layout-meta.xml +130 -0
  77. package/dist/force-app/main/default/layouts/Tenant__c-Tenant Layout.layout-meta.xml +58 -0
  78. package/dist/force-app/main/default/networks/propertyrentalapp.network-meta.xml +60 -0
  79. package/dist/force-app/main/default/objects/Agent__c/Agent__c.object-meta.xml +66 -0
  80. package/dist/force-app/main/default/objects/Agent__c/fields/Agent_Type__c.field-meta.xml +37 -0
  81. package/dist/force-app/main/default/objects/Agent__c/fields/Availability__c.field-meta.xml +37 -0
  82. package/dist/force-app/main/default/objects/Agent__c/fields/Emergency_Alt__c.field-meta.xml +11 -0
  83. package/dist/force-app/main/default/objects/Agent__c/fields/Language__c.field-meta.xml +42 -0
  84. package/dist/force-app/main/default/objects/Agent__c/fields/License_Expiry__c.field-meta.xml +11 -0
  85. package/dist/force-app/main/default/objects/Agent__c/fields/License_Number__c.field-meta.xml +14 -0
  86. package/dist/force-app/main/default/objects/Agent__c/fields/Office_Location__c.field-meta.xml +42 -0
  87. package/dist/force-app/main/default/objects/Agent__c/fields/Territory__c.field-meta.xml +42 -0
  88. package/dist/force-app/main/default/objects/Application__c/Application__c.object-meta.xml +67 -0
  89. package/dist/force-app/main/default/objects/Application__c/fields/Employment__c.field-meta.xml +10 -0
  90. package/dist/force-app/main/default/objects/Application__c/fields/Property__c.field-meta.xml +15 -0
  91. package/dist/force-app/main/default/objects/Application__c/fields/References__c.field-meta.xml +10 -0
  92. package/dist/force-app/main/default/objects/Application__c/fields/Start_Date__c.field-meta.xml +9 -0
  93. package/dist/force-app/main/default/objects/Application__c/fields/Status__c.field-meta.xml +47 -0
  94. package/dist/force-app/main/default/objects/Application__c/fields/User__c.field-meta.xml +15 -0
  95. package/dist/force-app/main/default/objects/KPI_Snapshot__c/KPI_Snapshot__c.object-meta.xml +65 -0
  96. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Previous_Month_Sales__c.field-meta.xml +12 -0
  97. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Sales_MoM_Change__c.field-meta.xml +18 -0
  98. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Snapshot_Date__c.field-meta.xml +10 -0
  99. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Properties__c.field-meta.xml +13 -0
  100. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Sales_Amount__c.field-meta.xml +12 -0
  101. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Sales_Count__c.field-meta.xml +13 -0
  102. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Units_Available__c.field-meta.xml +13 -0
  103. package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Units_Occupied__c.field-meta.xml +13 -0
  104. package/dist/force-app/main/default/objects/Lease__c/Lease__c.object-meta.xml +67 -0
  105. package/dist/force-app/main/default/objects/Lease__c/fields/End_Date__c.field-meta.xml +10 -0
  106. package/dist/force-app/main/default/objects/Lease__c/fields/Lease_Status__c.field-meta.xml +36 -0
  107. package/dist/force-app/main/default/objects/Lease__c/fields/Monthly_Rent__c.field-meta.xml +12 -0
  108. package/dist/force-app/main/default/objects/Lease__c/fields/Property__c.field-meta.xml +15 -0
  109. package/dist/force-app/main/default/objects/Lease__c/fields/Security_Deposit__c.field-meta.xml +12 -0
  110. package/dist/force-app/main/default/objects/Lease__c/fields/Start_Date__c.field-meta.xml +10 -0
  111. package/dist/force-app/main/default/objects/Lease__c/fields/Tenant__c.field-meta.xml +14 -0
  112. package/dist/force-app/main/default/objects/Lease__c/validationRules/End_Date_After_Start_Date.validationRule-meta.xml +10 -0
  113. package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +73 -0
  114. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Actual_Cost__c.field-meta.xml +13 -0
  115. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Assigned_Worker__c.field-meta.xml +15 -0
  116. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Completed__c.field-meta.xml +11 -0
  117. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Description__c.field-meta.xml +12 -0
  118. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Est_Cost__c.field-meta.xml +13 -0
  119. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Priority__c.field-meta.xml +32 -0
  120. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Property__c.field-meta.xml +15 -0
  121. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Scheduled__c.field-meta.xml +11 -0
  122. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Status__c.field-meta.xml +42 -0
  123. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Tenant_Home__c.field-meta.xml +11 -0
  124. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Type__c.field-meta.xml +62 -0
  125. package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/User__c.field-meta.xml +15 -0
  126. package/dist/force-app/main/default/objects/Maintenance_Worker__c/Maintenance_Worker__c.object-meta.xml +71 -0
  127. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Certifications__c.field-meta.xml +12 -0
  128. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Employment_Type__c.field-meta.xml +32 -0
  129. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Hourly_Rate__c.field-meta.xml +13 -0
  130. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/IsActive__c.field-meta.xml +11 -0
  131. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Location__c.field-meta.xml +13 -0
  132. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Phone__c.field-meta.xml +11 -0
  133. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Rating__c.field-meta.xml +14 -0
  134. package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Type__c.field-meta.xml +57 -0
  135. package/dist/force-app/main/default/objects/Notification__c/Notification__c.object-meta.xml +67 -0
  136. package/dist/force-app/main/default/objects/Notification__c/fields/Is_Read__c.field-meta.xml +10 -0
  137. package/dist/force-app/main/default/objects/Notification__c/fields/Message__c.field-meta.xml +11 -0
  138. package/dist/force-app/main/default/objects/Notification__c/fields/Priority__c.field-meta.xml +36 -0
  139. package/dist/force-app/main/default/objects/Notification__c/fields/Related_Object_Type__c.field-meta.xml +12 -0
  140. package/dist/force-app/main/default/objects/Notification__c/fields/Related_Record_Id__c.field-meta.xml +12 -0
  141. package/dist/force-app/main/default/objects/Notification__c/fields/Title__c.field-meta.xml +12 -0
  142. package/dist/force-app/main/default/objects/Notification__c/fields/Type__c.field-meta.xml +36 -0
  143. package/dist/force-app/main/default/objects/Notification__c/fields/User__c.field-meta.xml +14 -0
  144. package/dist/force-app/main/default/objects/Payment__c/Payment__c.object-meta.xml +67 -0
  145. package/dist/force-app/main/default/objects/Payment__c/fields/Amount__c.field-meta.xml +12 -0
  146. package/dist/force-app/main/default/objects/Payment__c/fields/Lease__c.field-meta.xml +15 -0
  147. package/dist/force-app/main/default/objects/Payment__c/fields/Notes__c.field-meta.xml +11 -0
  148. package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Date__c.field-meta.xml +10 -0
  149. package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Method__c.field-meta.xml +41 -0
  150. package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Status__c.field-meta.xml +36 -0
  151. package/dist/force-app/main/default/objects/Property_Cost__c/Property_Cost__c.object-meta.xml +67 -0
  152. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Amount__c.field-meta.xml +12 -0
  153. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Category__c.field-meta.xml +56 -0
  154. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Date__c.field-meta.xml +11 -0
  155. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Description__c.field-meta.xml +12 -0
  156. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Property__c.field-meta.xml +15 -0
  157. package/dist/force-app/main/default/objects/Property_Cost__c/fields/Vendor__c.field-meta.xml +12 -0
  158. package/dist/force-app/main/default/objects/Property_Cost__c/validationRules/Cost_Amount_Limit.validationRule-meta.xml +10 -0
  159. package/dist/force-app/main/default/objects/Property_Feature__c/Property_Feature__c.object-meta.xml +65 -0
  160. package/dist/force-app/main/default/objects/Property_Feature__c/fields/Description__c.field-meta.xml +11 -0
  161. package/dist/force-app/main/default/objects/Property_Feature__c/fields/Display_on_Listing__c.field-meta.xml +10 -0
  162. package/dist/force-app/main/default/objects/Property_Feature__c/fields/Feature_Category__c.field-meta.xml +51 -0
  163. package/dist/force-app/main/default/objects/Property_Feature__c/fields/Property__c.field-meta.xml +15 -0
  164. package/dist/force-app/main/default/objects/Property_Image__c/Property_Image__c.object-meta.xml +65 -0
  165. package/dist/force-app/main/default/objects/Property_Image__c/fields/Alt_Text__c.field-meta.xml +12 -0
  166. package/dist/force-app/main/default/objects/Property_Image__c/fields/Display_Order__c.field-meta.xml +13 -0
  167. package/dist/force-app/main/default/objects/Property_Image__c/fields/Image_Type__c.field-meta.xml +41 -0
  168. package/dist/force-app/main/default/objects/Property_Image__c/fields/Image_URL__c.field-meta.xml +10 -0
  169. package/dist/force-app/main/default/objects/Property_Image__c/fields/Property__c.field-meta.xml +15 -0
  170. package/dist/force-app/main/default/objects/Property_Listing__c/Property_Listing__c.object-meta.xml +65 -0
  171. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Display_Order__c.field-meta.xml +13 -0
  172. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Featured__c.field-meta.xml +10 -0
  173. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Listing_Price__c.field-meta.xml +12 -0
  174. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Listing_Status__c.field-meta.xml +41 -0
  175. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Marketing_Description__c.field-meta.xml +11 -0
  176. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Property__c.field-meta.xml +15 -0
  177. package/dist/force-app/main/default/objects/Property_Listing__c/fields/Short_Description__c.field-meta.xml +12 -0
  178. package/dist/force-app/main/default/objects/Property_Management_Company__c/Property_Management_Company__c.object-meta.xml +65 -0
  179. package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Active__c.field-meta.xml +10 -0
  180. package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Company_Code__c.field-meta.xml +13 -0
  181. package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Email__c.field-meta.xml +10 -0
  182. package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Phone__c.field-meta.xml +10 -0
  183. package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Primary_Contact__c.field-meta.xml +14 -0
  184. package/dist/force-app/main/default/objects/Property_Owner__c/Property_Owner__c.object-meta.xml +65 -0
  185. package/dist/force-app/main/default/objects/Property_Owner__c/fields/Address__c.field-meta.xml +10 -0
  186. package/dist/force-app/main/default/objects/Property_Owner__c/fields/Email__c.field-meta.xml +11 -0
  187. package/dist/force-app/main/default/objects/Property_Owner__c/fields/Phone__c.field-meta.xml +10 -0
  188. package/dist/force-app/main/default/objects/Property_Sale__c/Property_Sale__c.object-meta.xml +67 -0
  189. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Buyer_Tenant__c.field-meta.xml +13 -0
  190. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Payment_Method__c.field-meta.xml +41 -0
  191. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Property__c.field-meta.xml +15 -0
  192. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Reference_Number__c.field-meta.xml +12 -0
  193. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Amount__c.field-meta.xml +12 -0
  194. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Date__c.field-meta.xml +10 -0
  195. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Status__c.field-meta.xml +36 -0
  196. package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Type__c.field-meta.xml +51 -0
  197. package/dist/force-app/main/default/objects/Property__c/Property__c.object-meta.xml +71 -0
  198. package/dist/force-app/main/default/objects/Property__c/fields/Address__c.field-meta.xml +13 -0
  199. package/dist/force-app/main/default/objects/Property__c/fields/Agent__c.field-meta.xml +15 -0
  200. package/dist/force-app/main/default/objects/Property__c/fields/Available_Date__c.field-meta.xml +11 -0
  201. package/dist/force-app/main/default/objects/Property__c/fields/Bathrooms__c.field-meta.xml +14 -0
  202. package/dist/force-app/main/default/objects/Property__c/fields/Bedrooms__c.field-meta.xml +14 -0
  203. package/dist/force-app/main/default/objects/Property__c/fields/Coordinates__c.field-meta.xml +12 -0
  204. package/dist/force-app/main/default/objects/Property__c/fields/Deposit__c.field-meta.xml +13 -0
  205. package/dist/force-app/main/default/objects/Property__c/fields/Description__c.field-meta.xml +12 -0
  206. package/dist/force-app/main/default/objects/Property__c/fields/Features__c.field-meta.xml +38 -0
  207. package/dist/force-app/main/default/objects/Property__c/fields/Hero_Image__c.field-meta.xml +11 -0
  208. package/dist/force-app/main/default/objects/Property__c/fields/Lease_Term__c.field-meta.xml +14 -0
  209. package/dist/force-app/main/default/objects/Property__c/fields/Monthly_Rent__c.field-meta.xml +13 -0
  210. package/dist/force-app/main/default/objects/Property__c/fields/Parking__c.field-meta.xml +14 -0
  211. package/dist/force-app/main/default/objects/Property__c/fields/Pet_Friendly__c.field-meta.xml +11 -0
  212. package/dist/force-app/main/default/objects/Property__c/fields/Sq_Ft__c.field-meta.xml +14 -0
  213. package/dist/force-app/main/default/objects/Property__c/fields/Status__c.field-meta.xml +37 -0
  214. package/dist/force-app/main/default/objects/Property__c/fields/Tour_URL__c.field-meta.xml +11 -0
  215. package/dist/force-app/main/default/objects/Property__c/fields/Type__c.field-meta.xml +37 -0
  216. package/dist/force-app/main/default/objects/Property__c/fields/Utilities__c.field-meta.xml +38 -0
  217. package/dist/force-app/main/default/objects/Property__c/fields/Year_Built__c.field-meta.xml +14 -0
  218. package/dist/force-app/main/default/objects/Tenant__c/Tenant__c.object-meta.xml +67 -0
  219. package/dist/force-app/main/default/objects/Tenant__c/fields/End_Date__c.field-meta.xml +11 -0
  220. package/dist/force-app/main/default/objects/Tenant__c/fields/Property__c.field-meta.xml +15 -0
  221. package/dist/force-app/main/default/objects/Tenant__c/fields/Start_Date__c.field-meta.xml +11 -0
  222. package/dist/force-app/main/default/objects/Tenant__c/fields/Status__c.field-meta.xml +37 -0
  223. package/dist/force-app/main/default/objects/Tenant__c/fields/User_Status__c.field-meta.xml +42 -0
  224. package/dist/force-app/main/default/objects/Tenant__c/fields/User__c.field-meta.xml +15 -0
  225. package/dist/force-app/main/default/package.xml +20 -0
  226. package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +633 -0
  227. package/dist/force-app/main/default/permissionsets/Tenant_Maintenance_Access.permissionset-meta.xml +137 -0
  228. package/dist/force-app/main/default/sites/propertyrentalapp.site-meta.xml +31 -0
  229. package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger +5 -0
  230. package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger-meta.xml +5 -0
  231. package/dist/force-app/main/default/triggers/TenantTrigger.trigger +8 -0
  232. package/dist/force-app/main/default/triggers/TenantTrigger.trigger-meta.xml +5 -0
  233. package/dist/force-app/main/default/uiBundles/propertyrentalapp/.forceignore +15 -0
  234. package/dist/force-app/main/default/uiBundles/propertyrentalapp/.graphqlrc.yml +2 -0
  235. package/dist/force-app/main/default/uiBundles/propertyrentalapp/.prettierignore +9 -0
  236. package/dist/force-app/main/default/uiBundles/propertyrentalapp/.prettierrc +11 -0
  237. package/dist/force-app/main/default/uiBundles/propertyrentalapp/CHANGELOG.md +10 -0
  238. package/dist/force-app/main/default/uiBundles/propertyrentalapp/README.md +35 -0
  239. package/dist/force-app/main/default/uiBundles/propertyrentalapp/codegen.yml +95 -0
  240. package/dist/force-app/main/default/uiBundles/propertyrentalapp/components.json +18 -0
  241. package/dist/force-app/main/default/uiBundles/propertyrentalapp/e2e/app.spec.ts +17 -0
  242. package/dist/force-app/main/default/uiBundles/propertyrentalapp/eslint.config.js +169 -0
  243. package/dist/force-app/main/default/uiBundles/propertyrentalapp/index.html +18 -0
  244. package/dist/force-app/main/default/uiBundles/propertyrentalapp/package.json +74 -0
  245. package/dist/force-app/main/default/uiBundles/propertyrentalapp/playwright.config.ts +24 -0
  246. package/dist/force-app/main/default/uiBundles/propertyrentalapp/propertyrentalapp.uibundle-meta.xml +8 -0
  247. package/dist/force-app/main/default/uiBundles/propertyrentalapp/scripts/get-graphql-schema.mjs +68 -0
  248. package/dist/force-app/main/default/uiBundles/propertyrentalapp/scripts/rewrite-e2e-assets.mjs +23 -0
  249. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/applications/applicationApi.ts +54 -0
  250. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/graphql-operations-types.ts +16005 -0
  251. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/graphqlClient.ts +25 -0
  252. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/leads/leadApi.ts +78 -0
  253. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/maintenanceRequests/maintenanceRequestApi.ts +74 -0
  254. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/maintenanceRequests/query/maintenanceRequests.graphql +60 -0
  255. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertyDetailGraphQL.ts +75 -0
  256. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertyNodeUtils.ts +29 -0
  257. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertySearchService.ts +56 -0
  258. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/distinctPropertyStatus.graphql +19 -0
  259. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/distinctPropertyType.graphql +19 -0
  260. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/listingById.graphql +29 -0
  261. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/propertyAddressesByIds.graphql +17 -0
  262. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/propertyDetailById.graphql +124 -0
  263. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/searchProperties.graphql +85 -0
  264. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/query/tenantAccess.graphql +13 -0
  265. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/tenantApi.ts +12 -0
  266. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/app.tsx +43 -0
  267. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/appLayout.tsx +24 -0
  268. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/appliances.svg +13 -0
  269. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/book.svg +3 -0
  270. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/copy.svg +4 -0
  271. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/electrical.svg +39 -0
  272. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/hvac.svg +78 -0
  273. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/pest.svg +5 -0
  274. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/plumbing.svg +7 -0
  275. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/rocket.svg +3 -0
  276. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/star.svg +3 -0
  277. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/zen-logo.svg +5 -0
  278. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-1.png +0 -0
  279. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-2.png +0 -0
  280. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-3.png +0 -0
  281. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/vibe-codey.svg +194 -0
  282. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/SkeletonPrimitives.tsx +36 -0
  283. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/alerts/status-alert.tsx +49 -0
  284. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/dashboard/WeatherWidget.tsx +235 -0
  285. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layout/TopBar.tsx +133 -0
  286. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layout/VerticalNav.tsx +97 -0
  287. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layouts/card-layout.tsx +29 -0
  288. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestIcon.tsx +46 -0
  289. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestList.tsx +52 -0
  290. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestListItem.tsx +74 -0
  291. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx +92 -0
  292. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/StatusBadge.tsx +36 -0
  293. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyListingCard.tsx +178 -0
  294. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyMap.tsx +186 -0
  295. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertySearchFilters.tsx +315 -0
  296. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/alert.tsx +76 -0
  297. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/badge.tsx +48 -0
  298. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/breadcrumb.tsx +109 -0
  299. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/button.tsx +67 -0
  300. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/calendar.tsx +232 -0
  301. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/card.tsx +103 -0
  302. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/checkbox.tsx +32 -0
  303. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/collapsible.tsx +33 -0
  304. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/datePicker.tsx +127 -0
  305. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/dialog.tsx +162 -0
  306. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/field.tsx +237 -0
  307. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/index.ts +84 -0
  308. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/input.tsx +19 -0
  309. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/label.tsx +22 -0
  310. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/pagination.tsx +132 -0
  311. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/popover.tsx +89 -0
  312. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/select.tsx +193 -0
  313. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/separator.tsx +26 -0
  314. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/skeleton.tsx +14 -0
  315. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/sonner.tsx +20 -0
  316. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/spinner.tsx +16 -0
  317. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/table.tsx +114 -0
  318. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/tabs.tsx +88 -0
  319. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/api/userProfileApi.ts +95 -0
  320. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/authHelpers.ts +73 -0
  321. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/authenticationConfig.ts +61 -0
  322. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/context/AuthContext.tsx +95 -0
  323. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/footers/footer-link.tsx +36 -0
  324. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/forms/auth-form.tsx +81 -0
  325. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/forms/submit-button.tsx +49 -0
  326. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/form.tsx +120 -0
  327. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
  328. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
  329. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layout/card-skeleton.tsx +38 -0
  330. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layout/centered-page-layout.tsx +87 -0
  331. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
  332. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/TenantRoute.tsx +22 -0
  333. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
  334. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/privateRouteLayout.tsx +44 -0
  335. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ChangePassword.tsx +107 -0
  336. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ForgotPassword.tsx +73 -0
  337. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Login.tsx +97 -0
  338. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Profile.tsx +161 -0
  339. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Register.tsx +133 -0
  340. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ResetPassword.tsx +107 -0
  341. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +602 -0
  342. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/sessionTimeService.ts +149 -0
  343. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
  344. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/utils/helpers.ts +121 -0
  345. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
  346. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
  347. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
  348. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
  349. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
  350. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
  351. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
  352. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/Home.tsx +34 -0
  353. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/api/objectSearchService.ts +84 -0
  354. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ActiveFilters.tsx +89 -0
  355. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/FilterContext.tsx +83 -0
  356. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
  357. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/PaginationControls.tsx +109 -0
  358. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SearchBar.tsx +41 -0
  359. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SortControl.tsx +143 -0
  360. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/BooleanFilter.tsx +78 -0
  361. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateFilter.tsx +128 -0
  362. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
  363. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
  364. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
  365. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
  366. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SearchFilter.tsx +50 -0
  367. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
  368. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/TextFilter.tsx +91 -0
  369. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useAsyncData.ts +54 -0
  370. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
  371. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
  372. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useObjectSearchParams.ts +252 -0
  373. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/debounce.ts +25 -0
  374. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/fieldUtils.ts +29 -0
  375. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/filterUtils.ts +395 -0
  376. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/sortUtils.ts +38 -0
  377. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useGeocode.ts +33 -0
  378. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useMaintenanceRequests.ts +43 -0
  379. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/usePropertyDetail.ts +63 -0
  380. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/usePropertyMapMarkers.ts +181 -0
  381. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useTenantAccess.ts +38 -0
  382. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useWeather.ts +311 -0
  383. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/lib/utils.ts +6 -0
  384. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/navigationMenu.tsx +80 -0
  385. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Application.tsx +235 -0
  386. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Contact.tsx +247 -0
  387. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Dashboard.tsx +45 -0
  388. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Home.tsx +507 -0
  389. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Maintenance.tsx +330 -0
  390. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/NotFound.tsx +14 -0
  391. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertyDetails.tsx +356 -0
  392. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearch.tsx +472 -0
  393. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearchPlaceholder.tsx +49 -0
  394. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-01.jpg +0 -0
  395. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-02.jpg +0 -0
  396. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-03.jpg +0 -0
  397. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-04.jpg +0 -0
  398. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-05.jpg +0 -0
  399. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-06.jpg +0 -0
  400. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-07.jpg +0 -0
  401. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-08.jpg +0 -0
  402. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-09.jpg +0 -0
  403. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-10.jpg +0 -0
  404. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-11.jpg +0 -0
  405. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-12.jpg +0 -0
  406. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-13.jpg +0 -0
  407. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-14.jpg +0 -0
  408. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-15.jpg +0 -0
  409. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-16.jpg +0 -0
  410. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-17.jpg +0 -0
  411. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-18.jpg +0 -0
  412. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-19.jpg +0 -0
  413. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-20.jpg +0 -0
  414. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-21.jpg +0 -0
  415. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-22.jpg +0 -0
  416. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-23.jpg +0 -0
  417. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-24.jpg +0 -0
  418. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-25.jpg +0 -0
  419. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/router-utils.tsx +35 -0
  420. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/routes.tsx +130 -0
  421. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/styles/global.css +229 -0
  422. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/types/leaflet.d.ts +17 -0
  423. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/types/searchResults.ts +229 -0
  424. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/utils/geocode.ts +90 -0
  425. package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/utils/propertyListingPaginationUtils.ts +18 -0
  426. package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.json +42 -0
  427. package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.node.json +13 -0
  428. package/dist/force-app/main/default/uiBundles/propertyrentalapp/ui-bundle.json +7 -0
  429. package/dist/force-app/main/default/uiBundles/propertyrentalapp/vite-env.d.ts +1 -0
  430. package/dist/force-app/main/default/uiBundles/propertyrentalapp/vite.config.ts +106 -0
  431. package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest-env.d.ts +2 -0
  432. package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest.config.ts +11 -0
  433. package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest.setup.ts +1 -0
  434. package/dist/jest.config.js +6 -0
  435. package/dist/package-lock.json +9995 -0
  436. package/dist/package.json +40 -0
  437. package/dist/scripts/apex/hello.apex +10 -0
  438. package/dist/scripts/graphql-search.sh +191 -0
  439. package/dist/scripts/prepare-import-unique-fields.js +122 -0
  440. package/dist/scripts/setup-cli.mjs +563 -0
  441. package/dist/scripts/sf-project-setup.mjs +66 -0
  442. package/dist/scripts/soql/account.soql +6 -0
  443. package/dist/sfdx-project.json +12 -0
  444. package/package.json +43 -0
@@ -0,0 +1,38 @@
1
+ import { ResultOrder, NullOrder } from "../../../api/graphql-operations-types";
2
+
3
+ export type SortFieldConfig<TFieldName extends string = string> = {
4
+ field: TFieldName;
5
+ label: string;
6
+ };
7
+
8
+ export type SortState<TFieldName extends string = string> = {
9
+ field: TFieldName;
10
+ direction: "ASC" | "DESC";
11
+ };
12
+
13
+ /**
14
+ * Converts a {@link SortState} into a GraphQL order-by object.
15
+ *
16
+ * @typeParam TOrderBy - The GraphQL order-by input type (e.g. `AccountOrderByInput`).
17
+ * @param sort - The current sort state from the UI, or `null` if no sort is applied.
18
+ * @returns An order-by object for the GraphQL query's `orderBy` variable, or
19
+ * `undefined` if no sort is active (which uses the API's default ordering).
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const orderBy = buildOrderBy<AccountOrderByInput>({
24
+ * field: "Name",
25
+ * direction: "ASC",
26
+ * });
27
+ * // orderBy => { Name: { order: ResultOrder.Asc, nulls: NullOrder.Last } }
28
+ * ```
29
+ */
30
+ export function buildOrderBy<TOrderBy>(sort: SortState | null): TOrderBy | undefined {
31
+ if (!sort) return undefined;
32
+ return {
33
+ [sort.field]: {
34
+ order: sort.direction === "ASC" ? ResultOrder.Asc : ResultOrder.Desc,
35
+ nulls: NullOrder.Last,
36
+ },
37
+ } as TOrderBy;
38
+ }
@@ -0,0 +1,33 @@
1
+ import { geocodeAddress, getStateZipFromAddress, type GeocodeResult } from "@/utils/geocode";
2
+ import { useCachedAsyncData } from "@/features/object-search/hooks/useCachedAsyncData";
3
+
4
+ async function geocodeWithFallback(address: string): Promise<GeocodeResult | null> {
5
+ const normalized = address.replace(/\n/g, ", ").trim();
6
+ const result = await geocodeAddress(normalized);
7
+ if (result != null) return result;
8
+
9
+ // Fallback: try state + zip if full address failed
10
+ const stateZip = getStateZipFromAddress(normalized);
11
+ if (stateZip !== normalized) {
12
+ return geocodeAddress(stateZip);
13
+ }
14
+ return null;
15
+ }
16
+
17
+ export function useGeocode(address: string | null | undefined): {
18
+ coords: GeocodeResult | null;
19
+ loading: boolean;
20
+ } {
21
+ const trimmed = address?.trim() ?? "";
22
+
23
+ const { data: coords, loading } = useCachedAsyncData(
24
+ () => {
25
+ if (!trimmed) return Promise.resolve(null);
26
+ return geocodeWithFallback(trimmed);
27
+ },
28
+ [trimmed],
29
+ { key: `geocode:${trimmed}`, ttl: 600_000 },
30
+ );
31
+
32
+ return { coords, loading };
33
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Fetches Maintenance_Request__c list and exposes refetch for after create.
3
+ */
4
+ import { useState, useCallback } from "react";
5
+ import {
6
+ searchMaintenanceRequests,
7
+ type MaintenanceRequestNode,
8
+ } from "@/api/maintenanceRequests/maintenanceRequestApi";
9
+ import { ResultOrder } from "@/api/graphql-operations-types";
10
+ import {
11
+ useCachedAsyncData,
12
+ clearCacheEntry,
13
+ } from "@/features/object-search/hooks/useCachedAsyncData";
14
+
15
+ const CACHE_KEY = "maintenance-requests";
16
+
17
+ async function fetchMaintenanceNodes(): Promise<MaintenanceRequestNode[]> {
18
+ const result = await searchMaintenanceRequests({
19
+ first: 50,
20
+ orderBy: { Scheduled__c: { order: ResultOrder.Desc } },
21
+ });
22
+ return (result.edges ?? []).flatMap((e) => (e?.node ? [e.node] : []));
23
+ }
24
+
25
+ export function useMaintenanceRequests(): {
26
+ requests: MaintenanceRequestNode[];
27
+ loading: boolean;
28
+ error: string | null;
29
+ refetch: () => void;
30
+ } {
31
+ const [generation, setGeneration] = useState(0);
32
+
33
+ const { data, loading, error } = useCachedAsyncData(fetchMaintenanceNodes, [generation], {
34
+ key: `${CACHE_KEY}:${generation}`,
35
+ });
36
+
37
+ const refetch = useCallback(() => {
38
+ clearCacheEntry(`${CACHE_KEY}:${generation}`);
39
+ setGeneration((g) => g + 1);
40
+ }, [generation]);
41
+
42
+ return { requests: data ?? [], loading, error, refetch };
43
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Fetches Property__c by id with all related data (images, costs, features, listings)
3
+ * in a single GraphQL query.
4
+ */
5
+ import { useState, useCallback } from "react";
6
+ import {
7
+ fetchPropertyDetailById,
8
+ fetchListingById,
9
+ type PropertyDetailNode,
10
+ } from "@/api/properties/propertyDetailGraphQL";
11
+ import {
12
+ useCachedAsyncData,
13
+ clearCacheEntry,
14
+ } from "@/features/object-search/hooks/useCachedAsyncData";
15
+
16
+ export interface PropertyDetailState {
17
+ property: PropertyDetailNode | null;
18
+ loading: boolean;
19
+ error: string | null;
20
+ }
21
+
22
+ const CACHE_KEY_PREFIX = "property-detail";
23
+
24
+ async function fetchDetail(id: string): Promise<PropertyDetailNode | null> {
25
+ // First try directly as a Property__c ID (common path).
26
+ const detail = await fetchPropertyDetailById(id);
27
+ if (detail) return detail;
28
+
29
+ // Fall back: treat as a Property_Listing__c ID and resolve to its Property__c.
30
+ const listing = await fetchListingById(id);
31
+ const propertyId = listing?.Property__c?.value ?? null;
32
+ if (!propertyId) return null;
33
+ return fetchPropertyDetailById(propertyId);
34
+ }
35
+
36
+ export function usePropertyDetail(
37
+ id: string | undefined,
38
+ ): PropertyDetailState & { refetch: () => void } {
39
+ const [generation, setGeneration] = useState(0);
40
+ const trimmedId = id?.trim() ?? "";
41
+ const cacheKey = `${CACHE_KEY_PREFIX}:${trimmedId}:${generation}`;
42
+
43
+ const { data, loading, error } = useCachedAsyncData(
44
+ () => {
45
+ if (!trimmedId) return Promise.resolve(null);
46
+ return fetchDetail(trimmedId);
47
+ },
48
+ [trimmedId, generation],
49
+ { key: cacheKey },
50
+ );
51
+
52
+ const refetch = useCallback(() => {
53
+ clearCacheEntry(cacheKey);
54
+ setGeneration((g) => g + 1);
55
+ }, [cacheKey]);
56
+
57
+ return {
58
+ property: data ?? null,
59
+ loading,
60
+ error,
61
+ refetch,
62
+ };
63
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Builds map markers from search result nodes. Uses coordinates when available,
3
+ * falls back to geocoding addresses for properties missing coordinates.
4
+ */
5
+ import { useState, useEffect, useRef } from "react";
6
+ import { fetchPropertyAddresses } from "@/api/properties/propertyDetailGraphQL";
7
+ import { geocodeAddress, getStateZipFromAddress } from "@/utils/geocode";
8
+ import type { PropertySearchNode } from "@/api/properties/propertySearchService";
9
+ import type { MapMarker } from "@/components/properties/PropertyMap";
10
+
11
+ function getListingName(node: PropertySearchNode): string {
12
+ if (node.Name?.displayValue != null && node.Name.displayValue !== "")
13
+ return node.Name.displayValue;
14
+ if (node.Name?.value != null && node.Name.value !== "") return node.Name.value;
15
+ return "Property";
16
+ }
17
+
18
+ function toFiniteNumber(value: unknown): number | null {
19
+ if (typeof value === "number" && Number.isFinite(value)) return value;
20
+ if (typeof value === "string" && value.trim() !== "") {
21
+ const n = Number(value);
22
+ return Number.isFinite(n) ? n : null;
23
+ }
24
+ return null;
25
+ }
26
+
27
+ function getCoordinatesFromNode(node: PropertySearchNode): { lat: number; lng: number } | null {
28
+ const lat = toFiniteNumber(node.Coordinates__Latitude__s?.value);
29
+ const lng = toFiniteNumber(node.Coordinates__Longitude__s?.value);
30
+ if (lat == null || lng == null) return null;
31
+ return { lat, lng };
32
+ }
33
+
34
+ /** Round to 5 decimals (~1 m) so near-duplicate coords group together */
35
+ function key(lat: number, lng: number): string {
36
+ return `${lat.toFixed(5)},${lng.toFixed(5)}`;
37
+ }
38
+
39
+ /**
40
+ * When multiple markers share the same lat/lng, offset them in a small circle so they appear
41
+ * close together but are all visible (no stacking).
42
+ */
43
+ function spreadDuplicateMarkers(markers: MapMarker[]): MapMarker[] {
44
+ const groups = new Map<string, MapMarker[]>();
45
+ for (const m of markers) {
46
+ const k = key(m.lat, m.lng);
47
+ if (!groups.has(k)) groups.set(k, []);
48
+ groups.get(k)!.push(m);
49
+ }
50
+ const result: MapMarker[] = [];
51
+ const radiusDeg = 0.0004; // ~40–50 m so pins sit close but visible
52
+ for (const group of groups.values()) {
53
+ if (group.length === 1) {
54
+ result.push(group[0]);
55
+ continue;
56
+ }
57
+ for (let i = 0; i < group.length; i++) {
58
+ const m = group[i];
59
+ const angle = (i / group.length) * 2 * Math.PI;
60
+ result.push({
61
+ ...m,
62
+ lat: m.lat + radiusDeg * Math.cos(angle),
63
+ lng: m.lng + radiusDeg * Math.sin(angle),
64
+ });
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+
70
+ export function usePropertyMapMarkers(results: PropertySearchNode[]): {
71
+ markers: MapMarker[];
72
+ loading: boolean;
73
+ } {
74
+ const [markers, setMarkers] = useState<MapMarker[]>([]);
75
+ const [loading, setLoading] = useState(false);
76
+
77
+ const propertyIds = results.map((r) => r.Id).filter(Boolean);
78
+ const propertyIdToLabel = new Map<string, string>();
79
+ for (const node of results) {
80
+ if (!propertyIdToLabel.has(node.Id)) {
81
+ propertyIdToLabel.set(node.Id, getListingName(node));
82
+ }
83
+ }
84
+ const idsKey = [...new Set(propertyIds)].join(",");
85
+
86
+ const resultsRef = useRef(results);
87
+ const labelMapRef = useRef(propertyIdToLabel);
88
+ useEffect(() => {
89
+ resultsRef.current = results;
90
+ labelMapRef.current = propertyIdToLabel;
91
+ });
92
+
93
+ useEffect(() => {
94
+ const uniqIds = idsKey === "" ? [] : idsKey.split(",");
95
+ if (uniqIds.length === 0) {
96
+ setMarkers([]);
97
+ setLoading(false);
98
+ return;
99
+ }
100
+ let cancelled = false;
101
+ setLoading(true);
102
+ const currentLabels = labelMapRef.current;
103
+ const directMarkers: MapMarker[] = [];
104
+ const missingIds: string[] = [];
105
+ for (const node of results) {
106
+ if (!uniqIds.includes(node.Id)) continue;
107
+ const coords = getCoordinatesFromNode(node);
108
+ if (coords) {
109
+ directMarkers.push({
110
+ lat: coords.lat,
111
+ lng: coords.lng,
112
+ label: propertyIdToLabel.get(node.Id) ?? "Property",
113
+ propertyId: node.Id,
114
+ });
115
+ }
116
+ }
117
+ for (const id of uniqIds) {
118
+ const hasDirect = directMarkers.some((m) => m.propertyId === id);
119
+ if (!hasDirect) missingIds.push(id);
120
+ }
121
+ if (missingIds.length === 0) {
122
+ setMarkers(spreadDuplicateMarkers(directMarkers));
123
+ setLoading(false);
124
+ return;
125
+ }
126
+ fetchPropertyAddresses(missingIds)
127
+ .then((idToAddress) => {
128
+ if (cancelled) return;
129
+ const toGeocode = Object.entries(idToAddress).filter(
130
+ ([, addr]) => addr != null && addr.trim() !== "",
131
+ );
132
+ if (toGeocode.length === 0) {
133
+ setMarkers(spreadDuplicateMarkers(directMarkers));
134
+ setLoading(false);
135
+ return;
136
+ }
137
+ Promise.all(
138
+ toGeocode.map(async ([id, address]) => {
139
+ const normalized = address.replace(/\n/g, ", ").trim();
140
+ let coords = await geocodeAddress(normalized);
141
+ if (!coords) {
142
+ const stateZip = getStateZipFromAddress(normalized);
143
+ if (stateZip !== normalized) {
144
+ coords = await geocodeAddress(stateZip);
145
+ }
146
+ }
147
+ return coords ? { id, coords } : null;
148
+ }),
149
+ )
150
+ .then((resolved) => {
151
+ if (cancelled) return;
152
+ const geocoded: MapMarker[] = resolved
153
+ .filter((r): r is { id: string; coords: { lat: number; lng: number } } => r != null)
154
+ .map(({ id, coords }) => ({
155
+ lat: coords.lat,
156
+ lng: coords.lng,
157
+ label: currentLabels.get(id) ?? "Property",
158
+ propertyId: id,
159
+ }));
160
+ setMarkers(spreadDuplicateMarkers([...directMarkers, ...geocoded]));
161
+ })
162
+ .catch(() => {
163
+ if (!cancelled) setMarkers([]);
164
+ })
165
+ .finally(() => {
166
+ if (!cancelled) setLoading(false);
167
+ });
168
+ })
169
+ .catch(() => {
170
+ if (!cancelled) {
171
+ setMarkers([]);
172
+ setLoading(false);
173
+ }
174
+ });
175
+ return () => {
176
+ cancelled = true;
177
+ };
178
+ }, [idsKey]);
179
+
180
+ return { markers, loading };
181
+ }
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from "react";
2
+ import { hasTenantAccess } from "@/api/tenantApi";
3
+
4
+ export function useTenantAccess(userId: string | undefined): {
5
+ hasTenantRecord: boolean;
6
+ loading: boolean;
7
+ } {
8
+ const [hasTenantRecord, setHasTenantRecord] = useState(false);
9
+ const [loading, setLoading] = useState(false);
10
+
11
+ useEffect(() => {
12
+ const id = userId?.trim() ?? "";
13
+ if (!id) {
14
+ setHasTenantRecord(false);
15
+ setLoading(false);
16
+ return;
17
+ }
18
+
19
+ let cancelled = false;
20
+ setLoading(true);
21
+ hasTenantAccess(id)
22
+ .then((allowed) => {
23
+ if (!cancelled) setHasTenantRecord(allowed);
24
+ })
25
+ .catch(() => {
26
+ if (!cancelled) setHasTenantRecord(false);
27
+ })
28
+ .finally(() => {
29
+ if (!cancelled) setLoading(false);
30
+ });
31
+
32
+ return () => {
33
+ cancelled = true;
34
+ };
35
+ }, [userId]);
36
+
37
+ return { hasTenantRecord, loading };
38
+ }
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Real weather data via Open-Meteo (free, no API key).
3
+ * Detects user location via Geolocation API; falls back to San Francisco.
4
+ */
5
+ import { useState, useEffect } from "react";
6
+ import { useCachedAsyncData } from "@/features/object-search/hooks/useCachedAsyncData";
7
+
8
+ const FALLBACK = {
9
+ LAT: 37.7749,
10
+ LNG: -122.4194,
11
+ CITY: "San Francisco",
12
+ TIMEZONE: "America/Los_Angeles",
13
+ } as const;
14
+ const IMPERIAL_REGIONS = new Set(["US", "LR", "MM"]);
15
+ const MIN_TODAY_TILES = 3;
16
+
17
+ /** WMO weather codes → short label (Open-Meteo) */
18
+ const WEATHER_LABELS: Record<number, string> = {
19
+ 0: "Clear",
20
+ 1: "Mainly clear",
21
+ 2: "Partly cloudy",
22
+ 3: "Overcast",
23
+ 45: "Foggy",
24
+ 48: "Depositing rime fog",
25
+ 51: "Light drizzle",
26
+ 53: "Drizzle",
27
+ 55: "Dense drizzle",
28
+ 61: "Slight rain",
29
+ 63: "Rain",
30
+ 65: "Heavy rain",
31
+ 71: "Slight snow",
32
+ 73: "Snow",
33
+ 75: "Heavy snow",
34
+ 77: "Snow grains",
35
+ 80: "Slight rain showers",
36
+ 81: "Rain showers",
37
+ 82: "Heavy rain showers",
38
+ 85: "Slight snow showers",
39
+ 86: "Heavy snow showers",
40
+ 95: "Thunderstorm",
41
+ 96: "Thunderstorm + hail",
42
+ 99: "Thunderstorm + heavy hail",
43
+ };
44
+
45
+ /** Splits an API timestamp like "2026-03-12T21:00" into its date and hour parts. */
46
+ function parseTimestamp(ts: string): { dateStr: string; hour: number } {
47
+ const [date, time] = ts.split("T");
48
+ return { dateStr: date!, hour: Number(time?.split(":")[0]) };
49
+ }
50
+
51
+ /** Returns { date: "YYYY-MM-DD", hour: 0-23 } for a Date in the given IANA timezone. */
52
+ function datePartsInTimezone(
53
+ date: Date,
54
+ timezone: string,
55
+ locale: string,
56
+ ): { date: string; hour: number } {
57
+ const fmt = new Intl.DateTimeFormat(locale, {
58
+ year: "numeric",
59
+ month: "2-digit",
60
+ day: "2-digit",
61
+ hour: "numeric",
62
+ hour12: false,
63
+ timeZone: timezone,
64
+ });
65
+ const p = Object.fromEntries(fmt.formatToParts(date).map((x) => [x.type, x.value]));
66
+ return { date: `${p.year}-${p.month}-${p.day}`, hour: Number(p.hour) };
67
+ }
68
+
69
+ /** Formats an hour (0-23) as a localized time string like "9 PM" or "21". */
70
+ function formatHourLabel(hour: number, locale: string): string {
71
+ // Dummy date — only the hour matters. Avoids parsing API timestamps through
72
+ // new Date(ts), which would misinterpret them in the user's local timezone.
73
+ return new Date(2000, 0, 1, hour).toLocaleTimeString(locale, { hour: "numeric" });
74
+ }
75
+
76
+ /** Returns the short weekday name for a "YYYY-MM-DD" date string. */
77
+ function weekdayLabel(dateStr: string, locale: string): string {
78
+ // Parse at noon UTC and format in UTC so the user's local timezone can't shift the date.
79
+ return new Date(dateStr + "T12:00:00Z").toLocaleDateString(locale, {
80
+ weekday: "short",
81
+ timeZone: "UTC",
82
+ });
83
+ }
84
+
85
+ function isImperialLocale(): boolean {
86
+ const region = navigator.language.split("-")[1]?.toUpperCase() ?? "";
87
+ return IMPERIAL_REGIONS.has(region);
88
+ }
89
+
90
+ async function reverseGeocode(lat: number, lng: number): Promise<string> {
91
+ try {
92
+ const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&zoom=10`;
93
+ const response = await fetch(url, {
94
+ headers: { "Accept-Language": navigator.language },
95
+ });
96
+ if (!response.ok) return "";
97
+ const data = (await response.json()) as {
98
+ address?: { city?: string; town?: string; village?: string; county?: string };
99
+ };
100
+ const addr = data.address;
101
+ return addr?.city ?? addr?.town ?? addr?.village ?? addr?.county ?? "";
102
+ } catch {
103
+ return "";
104
+ }
105
+ }
106
+
107
+ export type TempUnit = "°F" | "°C";
108
+ export type WindUnit = "mph" | "km/h";
109
+
110
+ export interface WeatherCurrent {
111
+ description: string;
112
+ temp: number;
113
+ tempUnit: TempUnit;
114
+ humidity: number;
115
+ windSpeed: number;
116
+ windUnit: WindUnit;
117
+ weatherCode: number;
118
+ precipitationProbability: number;
119
+ }
120
+
121
+ export interface WeatherHour {
122
+ time: string;
123
+ temp: number;
124
+ weatherCode: number;
125
+ }
126
+
127
+ export interface WeatherData {
128
+ current: WeatherCurrent;
129
+ city: string;
130
+ todayHourly: WeatherHour[];
131
+ tomorrowHourly: WeatherHour[];
132
+ next3DaysHourly: WeatherHour[];
133
+ timezone: string;
134
+ }
135
+
136
+ interface OpenMeteoResponse {
137
+ current?: {
138
+ temperature_2m?: number;
139
+ relative_humidity_2m?: number;
140
+ weather_code?: number;
141
+ wind_speed_10m?: number;
142
+ };
143
+ hourly?: {
144
+ time?: string[];
145
+ temperature_2m?: (number | null)[];
146
+ weather_code?: (number | null)[];
147
+ precipitation_probability?: (number | null)[];
148
+ };
149
+ timezone?: string;
150
+ }
151
+
152
+ async function fetchWeather(lat: number, lng: number): Promise<WeatherData> {
153
+ const url = new URL("https://api.open-meteo.com/v1/forecast");
154
+ url.searchParams.set("latitude", String(lat));
155
+ url.searchParams.set("longitude", String(lng));
156
+ url.searchParams.set(
157
+ "current",
158
+ "temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m",
159
+ );
160
+ url.searchParams.set("hourly", "temperature_2m,weather_code,precipitation_probability");
161
+ url.searchParams.set("timezone", "auto");
162
+ url.searchParams.set("forecast_days", "4");
163
+
164
+ const [response, geocodedCity] = await Promise.all([
165
+ fetch(url.toString()),
166
+ reverseGeocode(lat, lng),
167
+ ]);
168
+ if (!response.ok) throw new Error(`Weather failed: ${response.status}`);
169
+
170
+ const data = (await response.json()) as OpenMeteoResponse;
171
+ const timezone = data.timezone ?? FALLBACK.TIMEZONE;
172
+ const locale = navigator.language;
173
+ const imperial = isImperialLocale();
174
+ const tempUnit: TempUnit = imperial ? "°F" : "°C";
175
+ const windUnit: WindUnit = imperial ? "mph" : "km/h";
176
+ const convertTemp = (c: number) => (imperial ? Math.round((c * 9) / 5 + 32) : Math.round(c));
177
+ const convertWind = (kmh: number) =>
178
+ imperial ? Math.round(kmh * 0.621371 * 10) / 10 : Math.round(kmh * 10) / 10;
179
+
180
+ const cur = data.current ?? {};
181
+ const { date: todayDateStr, hour: currentHour } = datePartsInTimezone(
182
+ new Date(),
183
+ timezone,
184
+ locale,
185
+ );
186
+ const [y, m, d] = todayDateStr.split("-").map(Number);
187
+ const tomorrowDateStr = new Date(Date.UTC(y!, m! - 1, d! + 1)).toISOString().slice(0, 10);
188
+
189
+ const {
190
+ time: times = [],
191
+ temperature_2m: temps = [],
192
+ weather_code: codes = [],
193
+ precipitation_probability: precips = [],
194
+ } = data.hourly ?? {};
195
+
196
+ let precipProbability = 0;
197
+ let foundPrecip = false;
198
+ const todayFuture: WeatherHour[] = [];
199
+ const tomorrowAll: WeatherHour[] = [];
200
+ const tomorrowHourly: WeatherHour[] = [];
201
+ const next3DaysHourly: WeatherHour[] = [];
202
+
203
+ for (let i = 0; i < times.length; i++) {
204
+ const ts = times[i];
205
+ if (!ts) continue;
206
+
207
+ const { dateStr, hour: entryHour } = parseTimestamp(ts);
208
+ const isFuture =
209
+ dateStr > todayDateStr || (dateStr === todayDateStr && entryHour >= currentHour);
210
+
211
+ if (!foundPrecip && isFuture) {
212
+ precipProbability = precips[i] ?? 0;
213
+ foundPrecip = true;
214
+ }
215
+
216
+ const tempC = temps[i];
217
+ if (tempC == null) continue;
218
+
219
+ const timeLabel = formatHourLabel(entryHour, locale);
220
+ const entry: WeatherHour = {
221
+ time: timeLabel,
222
+ temp: convertTemp(tempC),
223
+ weatherCode: codes[i] ?? 0,
224
+ };
225
+
226
+ if (dateStr === todayDateStr && isFuture) {
227
+ todayFuture.push(entry);
228
+ }
229
+ if (dateStr === tomorrowDateStr) {
230
+ tomorrowAll.push(entry);
231
+ if (entryHour % 3 === 0) {
232
+ tomorrowHourly.push(entry);
233
+ }
234
+ }
235
+ if (dateStr > todayDateStr && entryHour % 6 === 0) {
236
+ next3DaysHourly.push({
237
+ ...entry,
238
+ time: weekdayLabel(dateStr, locale) + " " + timeLabel,
239
+ });
240
+ }
241
+ }
242
+
243
+ const backfill = Math.max(0, MIN_TODAY_TILES - todayFuture.length);
244
+ const todayHourly = [...todayFuture, ...tomorrowAll.slice(0, backfill)];
245
+
246
+ return {
247
+ current: {
248
+ description: WEATHER_LABELS[cur.weather_code ?? 0] ?? "Unknown",
249
+ temp: convertTemp(cur.temperature_2m ?? 0),
250
+ tempUnit,
251
+ humidity: cur.relative_humidity_2m ?? 0,
252
+ windSpeed: convertWind(cur.wind_speed_10m ?? 0),
253
+ windUnit,
254
+ weatherCode: cur.weather_code ?? 0,
255
+ precipitationProbability: precipProbability,
256
+ },
257
+ city: geocodedCity || FALLBACK.CITY,
258
+ todayHourly,
259
+ tomorrowHourly,
260
+ next3DaysHourly,
261
+ timezone,
262
+ };
263
+ }
264
+
265
+ interface GeoPosition {
266
+ latitude: number;
267
+ longitude: number;
268
+ resolved: boolean;
269
+ }
270
+
271
+ function useGeolocation(): GeoPosition {
272
+ const [position, setPosition] = useState<GeoPosition>(() => ({
273
+ latitude: FALLBACK.LAT,
274
+ longitude: FALLBACK.LNG,
275
+ resolved: typeof navigator === "undefined" || !navigator.geolocation,
276
+ }));
277
+
278
+ useEffect(() => {
279
+ if (!navigator.geolocation) return;
280
+ navigator.geolocation.getCurrentPosition(
281
+ (pos) =>
282
+ setPosition({
283
+ latitude: pos.coords.latitude,
284
+ longitude: pos.coords.longitude,
285
+ resolved: true,
286
+ }),
287
+ () => setPosition((prev) => ({ ...prev, resolved: true })),
288
+ );
289
+ }, []);
290
+
291
+ return position;
292
+ }
293
+
294
+ export function useWeather(lat?: number | null, lng?: number | null) {
295
+ const geo = useGeolocation();
296
+ const latitude = lat ?? geo.latitude;
297
+ const longitude = lng ?? geo.longitude;
298
+ const canFetch = lat != null || geo.resolved;
299
+
300
+ const cached = useCachedAsyncData(
301
+ () => fetchWeather(latitude, longitude),
302
+ [latitude, longitude, canFetch],
303
+ { key: `weather:${latitude},${longitude}:${canFetch}`, ttl: 300_000 },
304
+ );
305
+
306
+ if (!canFetch) {
307
+ return { data: null, loading: true, error: null };
308
+ }
309
+
310
+ return { data: cached.data, loading: cached.loading, error: cached.error };
311
+ }