@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.
- package/LICENSE.txt +82 -0
- package/README.md +52 -0
- package/dist/.forceignore +15 -0
- package/dist/.husky/pre-commit +4 -0
- package/dist/.prettierignore +11 -0
- package/dist/.prettierrc +17 -0
- package/dist/AGENT.md +193 -0
- package/dist/CHANGELOG.md +2128 -0
- package/dist/README.md +112 -0
- package/dist/config/project-scratch-def.json +13 -0
- package/dist/eslint.config.js +7 -0
- package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls +69 -0
- package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls +308 -0
- package/dist/force-app/main/default/classes/MaintenanceRequestTriggerHandler_Test.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/TenantTriggerHandler.cls +77 -0
- package/dist/force-app/main/default/classes/TenantTriggerHandler.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/TenantTriggerHandler_Test.cls +100 -0
- package/dist/force-app/main/default/classes/TenantTriggerHandler_Test.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
- package/dist/force-app/main/default/cspTrustedSites/GitHub_Avatars.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Google_Fonts.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Google_Fonts_Static.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/OpenStreetMap_Nominatim.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/OpenStreetMap_Tiles.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Open_Meteo_API.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Pexels_Images.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Pexels_Videos.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/cspTrustedSites/Unsplash_Images.cspTrustedSite-meta.xml +15 -0
- package/dist/force-app/main/default/data/Agent__c.json +79 -0
- package/dist/force-app/main/default/data/Application__c.json +124 -0
- package/dist/force-app/main/default/data/Contact.json +44 -0
- package/dist/force-app/main/default/data/KPI_Snapshot__c.json +160 -0
- package/dist/force-app/main/default/data/Lease__c.json +9442 -0
- package/dist/force-app/main/default/data/Maintenance_Request__c.json +514 -0
- package/dist/force-app/main/default/data/Maintenance_Worker__c.json +304 -0
- package/dist/force-app/main/default/data/Notification__c.json +214 -0
- package/dist/force-app/main/default/data/Payment__c.json +1024 -0
- package/dist/force-app/main/default/data/Property_Cost__c.json +484 -0
- package/dist/force-app/main/default/data/Property_Feature__c.json +169 -0
- package/dist/force-app/main/default/data/Property_Image__c.json +148 -0
- package/dist/force-app/main/default/data/Property_Listing__c.json +130 -0
- package/dist/force-app/main/default/data/Property_Management_Company__c.json +70 -0
- package/dist/force-app/main/default/data/Property_Owner__c.json +184 -0
- package/dist/force-app/main/default/data/Property_Sale__c.json +246 -0
- package/dist/force-app/main/default/data/Property__c.json +704 -0
- package/dist/force-app/main/default/data/Tenant__c.json +184 -0
- package/dist/force-app/main/default/data/data-plan.json +110 -0
- package/dist/force-app/main/default/data/prepare-import-unique-fields.js +85 -0
- package/dist/force-app/main/default/digitalExperienceConfigs/propertyrentalapp1.digitalExperienceConfig-meta.xml +8 -0
- package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/propertyrentalapp1.digitalExperience-meta.xml +11 -0
- package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/sfdc_cms__site/propertyrentalapp1/_meta.json +5 -0
- package/dist/force-app/main/default/digitalExperiences/site/propertyrentalapp1/sfdc_cms__site/propertyrentalapp1/content.json +10 -0
- package/dist/force-app/main/default/layouts/Application__c-Application Layout.layout-meta.xml +58 -0
- package/dist/force-app/main/default/layouts/KPI_Snapshot__c-KPI Snapshot Layout.layout-meta.xml +87 -0
- package/dist/force-app/main/default/layouts/Lease__c-Lease Layout.layout-meta.xml +83 -0
- package/dist/force-app/main/default/layouts/Maintenance_Request__c-Maintenance Request Layout.layout-meta.xml +89 -0
- package/dist/force-app/main/default/layouts/Maintenance_Worker__c-Maintenance Worker Layout.layout-meta.xml +66 -0
- package/dist/force-app/main/default/layouts/Payment__c-Payment Layout.layout-meta.xml +88 -0
- package/dist/force-app/main/default/layouts/Property_Cost__c-Property Cost Layout.layout-meta.xml +88 -0
- package/dist/force-app/main/default/layouts/Property_Feature__c-Property Feature Layout.layout-meta.xml +80 -0
- package/dist/force-app/main/default/layouts/Property_Image__c-Property Image Layout.layout-meta.xml +75 -0
- package/dist/force-app/main/default/layouts/Property_Listing__c-Property Listing Layout.layout-meta.xml +92 -0
- package/dist/force-app/main/default/layouts/Property_Management_Company__c-Property Management Company Layout.layout-meta.xml +75 -0
- package/dist/force-app/main/default/layouts/Property_Owner__c-Property Owner Layout.layout-meta.xml +67 -0
- package/dist/force-app/main/default/layouts/Property_Sale__c-Property Sale Layout.layout-meta.xml +87 -0
- package/dist/force-app/main/default/layouts/Property__c-Property Layout.layout-meta.xml +130 -0
- package/dist/force-app/main/default/layouts/Tenant__c-Tenant Layout.layout-meta.xml +58 -0
- package/dist/force-app/main/default/networks/propertyrentalapp.network-meta.xml +60 -0
- package/dist/force-app/main/default/objects/Agent__c/Agent__c.object-meta.xml +66 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Agent_Type__c.field-meta.xml +37 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Availability__c.field-meta.xml +37 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Emergency_Alt__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Language__c.field-meta.xml +42 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/License_Expiry__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/License_Number__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Office_Location__c.field-meta.xml +42 -0
- package/dist/force-app/main/default/objects/Agent__c/fields/Territory__c.field-meta.xml +42 -0
- package/dist/force-app/main/default/objects/Application__c/Application__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Application__c/fields/Employment__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Application__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Application__c/fields/References__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Application__c/fields/Start_Date__c.field-meta.xml +9 -0
- package/dist/force-app/main/default/objects/Application__c/fields/Status__c.field-meta.xml +47 -0
- package/dist/force-app/main/default/objects/Application__c/fields/User__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/KPI_Snapshot__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Previous_Month_Sales__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Sales_MoM_Change__c.field-meta.xml +18 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Snapshot_Date__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Properties__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Sales_Amount__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Total_Sales_Count__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Units_Available__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/KPI_Snapshot__c/fields/Units_Occupied__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Lease__c/Lease__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/End_Date__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Lease_Status__c.field-meta.xml +36 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Monthly_Rent__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Security_Deposit__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Start_Date__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Lease__c/fields/Tenant__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Lease__c/validationRules/End_Date_After_Start_Date.validationRule-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/Maintenance_Request__c.object-meta.xml +73 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Actual_Cost__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Assigned_Worker__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Completed__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Description__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Est_Cost__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Priority__c.field-meta.xml +32 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Scheduled__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Status__c.field-meta.xml +42 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Tenant_Home__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/Type__c.field-meta.xml +62 -0
- package/dist/force-app/main/default/objects/Maintenance_Request__c/fields/User__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/Maintenance_Worker__c.object-meta.xml +71 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Certifications__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Employment_Type__c.field-meta.xml +32 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Hourly_Rate__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/IsActive__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Location__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Phone__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Rating__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Maintenance_Worker__c/fields/Type__c.field-meta.xml +57 -0
- package/dist/force-app/main/default/objects/Notification__c/Notification__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Is_Read__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Message__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Priority__c.field-meta.xml +36 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Related_Object_Type__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Related_Record_Id__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Title__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/Type__c.field-meta.xml +36 -0
- package/dist/force-app/main/default/objects/Notification__c/fields/User__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Payment__c/Payment__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Amount__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Lease__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Notes__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Date__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Method__c.field-meta.xml +41 -0
- package/dist/force-app/main/default/objects/Payment__c/fields/Payment_Status__c.field-meta.xml +36 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/Property_Cost__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Amount__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Category__c.field-meta.xml +56 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Cost_Date__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Description__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/fields/Vendor__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Cost__c/validationRules/Cost_Amount_Limit.validationRule-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Feature__c/Property_Feature__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/Property_Feature__c/fields/Description__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property_Feature__c/fields/Display_on_Listing__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Feature__c/fields/Feature_Category__c.field-meta.xml +51 -0
- package/dist/force-app/main/default/objects/Property_Feature__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property_Image__c/Property_Image__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/Property_Image__c/fields/Alt_Text__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Image__c/fields/Display_Order__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property_Image__c/fields/Image_Type__c.field-meta.xml +41 -0
- package/dist/force-app/main/default/objects/Property_Image__c/fields/Image_URL__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Image__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/Property_Listing__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Display_Order__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Featured__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Listing_Price__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Listing_Status__c.field-meta.xml +41 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Marketing_Description__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property_Listing__c/fields/Short_Description__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/Property_Management_Company__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Active__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Company_Code__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Email__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Phone__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Management_Company__c/fields/Primary_Contact__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property_Owner__c/Property_Owner__c.object-meta.xml +65 -0
- package/dist/force-app/main/default/objects/Property_Owner__c/fields/Address__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Owner__c/fields/Email__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property_Owner__c/fields/Phone__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/Property_Sale__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Buyer_Tenant__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Payment_Method__c.field-meta.xml +41 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Reference_Number__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Amount__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Date__c.field-meta.xml +10 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Status__c.field-meta.xml +36 -0
- package/dist/force-app/main/default/objects/Property_Sale__c/fields/Sale_Type__c.field-meta.xml +51 -0
- package/dist/force-app/main/default/objects/Property__c/Property__c.object-meta.xml +71 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Address__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Agent__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Available_Date__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Bathrooms__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Bedrooms__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Coordinates__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Deposit__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Description__c.field-meta.xml +12 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Features__c.field-meta.xml +38 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Hero_Image__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Lease_Term__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Monthly_Rent__c.field-meta.xml +13 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Parking__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Pet_Friendly__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Sq_Ft__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Status__c.field-meta.xml +37 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Tour_URL__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Type__c.field-meta.xml +37 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Utilities__c.field-meta.xml +38 -0
- package/dist/force-app/main/default/objects/Property__c/fields/Year_Built__c.field-meta.xml +14 -0
- package/dist/force-app/main/default/objects/Tenant__c/Tenant__c.object-meta.xml +67 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/End_Date__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/Property__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/Start_Date__c.field-meta.xml +11 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/Status__c.field-meta.xml +37 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/User_Status__c.field-meta.xml +42 -0
- package/dist/force-app/main/default/objects/Tenant__c/fields/User__c.field-meta.xml +15 -0
- package/dist/force-app/main/default/package.xml +20 -0
- package/dist/force-app/main/default/permissionsets/Property_Management_Access.permissionset-meta.xml +633 -0
- package/dist/force-app/main/default/permissionsets/Tenant_Maintenance_Access.permissionset-meta.xml +137 -0
- package/dist/force-app/main/default/sites/propertyrentalapp.site-meta.xml +31 -0
- package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger +5 -0
- package/dist/force-app/main/default/triggers/MaintenanceRequestTrigger.trigger-meta.xml +5 -0
- package/dist/force-app/main/default/triggers/TenantTrigger.trigger +8 -0
- package/dist/force-app/main/default/triggers/TenantTrigger.trigger-meta.xml +5 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/.forceignore +15 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/.graphqlrc.yml +2 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/.prettierignore +9 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/.prettierrc +11 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/CHANGELOG.md +10 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/README.md +35 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/codegen.yml +95 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/components.json +18 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/e2e/app.spec.ts +17 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/eslint.config.js +169 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/index.html +18 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/package.json +74 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/playwright.config.ts +24 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/propertyrentalapp.uibundle-meta.xml +8 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/scripts/get-graphql-schema.mjs +68 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/scripts/rewrite-e2e-assets.mjs +23 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/applications/applicationApi.ts +54 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/graphql-operations-types.ts +16005 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/graphqlClient.ts +25 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/leads/leadApi.ts +78 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/maintenanceRequests/maintenanceRequestApi.ts +74 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/maintenanceRequests/query/maintenanceRequests.graphql +60 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertyDetailGraphQL.ts +75 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertyNodeUtils.ts +29 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/propertySearchService.ts +56 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/distinctPropertyStatus.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/distinctPropertyType.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/listingById.graphql +29 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/propertyAddressesByIds.graphql +17 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/propertyDetailById.graphql +124 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/properties/query/searchProperties.graphql +85 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/query/tenantAccess.graphql +13 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/api/tenantApi.ts +12 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/app.tsx +43 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/appLayout.tsx +24 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/appliances.svg +13 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/book.svg +3 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/copy.svg +4 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/electrical.svg +39 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/hvac.svg +78 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/pest.svg +5 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/plumbing.svg +7 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/rocket.svg +3 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/star.svg +3 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/icons/zen-logo.svg +5 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-1.png +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-2.png +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/codey-3.png +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/assets/images/vibe-codey.svg +194 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/SkeletonPrimitives.tsx +36 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/alerts/status-alert.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/dashboard/WeatherWidget.tsx +235 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layout/TopBar.tsx +133 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layout/VerticalNav.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/layouts/card-layout.tsx +29 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestIcon.tsx +46 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestList.tsx +52 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceRequestListItem.tsx +74 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/MaintenanceSummaryDetailsModal.tsx +92 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/maintenanceRequests/StatusBadge.tsx +36 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyListingCard.tsx +178 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertyMap.tsx +186 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/properties/PropertySearchFilters.tsx +315 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/alert.tsx +76 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/badge.tsx +48 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/breadcrumb.tsx +109 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/button.tsx +67 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/calendar.tsx +232 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/card.tsx +103 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/checkbox.tsx +32 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/collapsible.tsx +33 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/datePicker.tsx +127 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/dialog.tsx +162 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/field.tsx +237 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/index.ts +84 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/input.tsx +19 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/label.tsx +22 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/pagination.tsx +132 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/popover.tsx +89 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/select.tsx +193 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/separator.tsx +26 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/skeleton.tsx +14 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/sonner.tsx +20 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/spinner.tsx +16 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/table.tsx +114 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/components/ui/tabs.tsx +88 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/api/userProfileApi.ts +95 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/authHelpers.ts +73 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/authenticationConfig.ts +61 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/context/AuthContext.tsx +95 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/footers/footer-link.tsx +36 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/forms/auth-form.tsx +81 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/forms/submit-button.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/form.tsx +120 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layout/card-skeleton.tsx +38 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layout/centered-page-layout.tsx +87 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/TenantRoute.tsx +22 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/layouts/privateRouteLayout.tsx +44 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ChangePassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ForgotPassword.tsx +73 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Login.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Profile.tsx +161 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/Register.tsx +133 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/pages/ResetPassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +602 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/sessionTimeService.ts +149 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/authentication/utils/helpers.ts +121 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/__examples__/pages/Home.tsx +34 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/api/objectSearchService.ts +84 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ActiveFilters.tsx +89 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/FilterContext.tsx +83 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/PaginationControls.tsx +109 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SearchBar.tsx +41 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/SortControl.tsx +143 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/BooleanFilter.tsx +78 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateFilter.tsx +128 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SearchFilter.tsx +50 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/components/filters/TextFilter.tsx +91 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useAsyncData.ts +54 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/hooks/useObjectSearchParams.ts +252 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/debounce.ts +25 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/fieldUtils.ts +29 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/filterUtils.ts +395 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/features/object-search/utils/sortUtils.ts +38 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useGeocode.ts +33 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useMaintenanceRequests.ts +43 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/usePropertyDetail.ts +63 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/usePropertyMapMarkers.ts +181 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useTenantAccess.ts +38 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/hooks/useWeather.ts +311 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/lib/utils.ts +6 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/navigationMenu.tsx +80 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Application.tsx +235 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Contact.tsx +247 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Dashboard.tsx +45 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Home.tsx +507 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/Maintenance.tsx +330 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/NotFound.tsx +14 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertyDetails.tsx +356 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearch.tsx +472 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/pages/PropertySearchPlaceholder.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-01.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-02.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-03.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-04.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-05.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-06.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-07.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-08.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-09.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-10.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-11.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-12.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-13.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-14.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-15.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-16.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-17.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-18.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-19.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-20.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-21.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-22.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-23.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-24.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/public/property-25.jpg +0 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/router-utils.tsx +35 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/routes.tsx +130 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/styles/global.css +229 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/types/leaflet.d.ts +17 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/types/searchResults.ts +229 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/utils/geocode.ts +90 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/src/utils/propertyListingPaginationUtils.ts +18 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.json +42 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/tsconfig.node.json +13 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/ui-bundle.json +7 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/vite-env.d.ts +1 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/vite.config.ts +106 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest-env.d.ts +2 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest.config.ts +11 -0
- package/dist/force-app/main/default/uiBundles/propertyrentalapp/vitest.setup.ts +1 -0
- package/dist/jest.config.js +6 -0
- package/dist/package-lock.json +9995 -0
- package/dist/package.json +40 -0
- package/dist/scripts/apex/hello.apex +10 -0
- package/dist/scripts/graphql-search.sh +191 -0
- package/dist/scripts/prepare-import-unique-fields.js +122 -0
- package/dist/scripts/setup-cli.mjs +563 -0
- package/dist/scripts/sf-project-setup.mjs +66 -0
- package/dist/scripts/soql/account.soql +6 -0
- package/dist/sfdx-project.json +12 -0
- package/package.json +43 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [Dev Note] Centralized configuration for Auth routes.
|
|
3
|
+
* Each route contains both the path and page title.
|
|
4
|
+
* Using constants prevents typos in route paths across the application.
|
|
5
|
+
*/
|
|
6
|
+
export const ROUTES = {
|
|
7
|
+
LOGIN: {
|
|
8
|
+
PATH: "/login",
|
|
9
|
+
TITLE: "Login | MyApp",
|
|
10
|
+
},
|
|
11
|
+
REGISTER: {
|
|
12
|
+
PATH: "/register",
|
|
13
|
+
TITLE: "Create Account | MyApp",
|
|
14
|
+
},
|
|
15
|
+
FORGOT_PASSWORD: {
|
|
16
|
+
PATH: "/forgot-password",
|
|
17
|
+
TITLE: "Recover Password | MyApp",
|
|
18
|
+
},
|
|
19
|
+
RESET_PASSWORD: {
|
|
20
|
+
PATH: "/reset-password",
|
|
21
|
+
TITLE: "Reset Password | MyApp",
|
|
22
|
+
},
|
|
23
|
+
PROFILE: {
|
|
24
|
+
PATH: "/profile",
|
|
25
|
+
TITLE: "My Profile | MyApp",
|
|
26
|
+
},
|
|
27
|
+
CHANGE_PASSWORD: {
|
|
28
|
+
PATH: "/change-password",
|
|
29
|
+
TITLE: "Change Password | MyApp",
|
|
30
|
+
},
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* [Dev Note] Centralized configuration for API endpoints.
|
|
35
|
+
* These are server-side endpoints, not client-side routes.
|
|
36
|
+
*/
|
|
37
|
+
export const API_ROUTES = {
|
|
38
|
+
// W-21253864: Logout URL integration is not currently supported
|
|
39
|
+
LOGOUT: "/secur/logout.jsp",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* [Dev Note] Query parameter key used to store the return URL.
|
|
44
|
+
* e.g. /login?startUrl=/profile
|
|
45
|
+
*/
|
|
46
|
+
export const AUTH_REDIRECT_PARAM = "startUrl";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Placeholder text constants for authentication form inputs.
|
|
50
|
+
*/
|
|
51
|
+
export const AUTH_PLACEHOLDERS = {
|
|
52
|
+
EMAIL: "asalesforce@example.com",
|
|
53
|
+
PASSWORD: "",
|
|
54
|
+
PASSWORD_CREATE: "",
|
|
55
|
+
PASSWORD_CONFIRM: "",
|
|
56
|
+
PASSWORD_NEW: "",
|
|
57
|
+
PASSWORD_NEW_CONFIRM: "",
|
|
58
|
+
FIRST_NAME: "Astro",
|
|
59
|
+
LAST_NAME: "Salesforce",
|
|
60
|
+
USERNAME: "asalesforce",
|
|
61
|
+
} as const;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
|
|
2
|
+
import { getCurrentUser } from "@salesforce/ui-bundle/api";
|
|
3
|
+
import { API_ROUTES } from "../authenticationConfig";
|
|
4
|
+
|
|
5
|
+
interface User {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AuthContextType {
|
|
11
|
+
user: User | null;
|
|
12
|
+
isAuthenticated: boolean;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
checkAuth: () => Promise<void>;
|
|
16
|
+
logout: (startURL?: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
20
|
+
|
|
21
|
+
interface AuthProviderProps {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function AuthProvider({ children }: AuthProviderProps) {
|
|
26
|
+
const [user, setUser] = useState<User | null>(null);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
const [error, setError] = useState<string | null>(null);
|
|
29
|
+
|
|
30
|
+
const checkAuth = useCallback(async () => {
|
|
31
|
+
setLoading(true);
|
|
32
|
+
setError(null);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const userData = await getCurrentUser();
|
|
36
|
+
setUser(userData);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
const errorMessage = err instanceof Error ? err.message : "Authentication failed";
|
|
39
|
+
setError(errorMessage);
|
|
40
|
+
setUser(null);
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const logout = useCallback((startURL?: string) => {
|
|
47
|
+
// Navigate to logout URL (server-side endpoint)
|
|
48
|
+
// Use replace to prevent back button from returning to authenticated session
|
|
49
|
+
const finalLogoutUrl = startURL
|
|
50
|
+
? `${API_ROUTES.LOGOUT}?startURL=${encodeURIComponent(startURL)}`
|
|
51
|
+
: API_ROUTES.LOGOUT;
|
|
52
|
+
window.location.replace(finalLogoutUrl);
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
checkAuth();
|
|
57
|
+
}, [checkAuth]);
|
|
58
|
+
|
|
59
|
+
const value: AuthContextType = {
|
|
60
|
+
user,
|
|
61
|
+
isAuthenticated: user !== null,
|
|
62
|
+
loading,
|
|
63
|
+
error,
|
|
64
|
+
checkAuth,
|
|
65
|
+
logout,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Hook to access the authentication context.
|
|
73
|
+
* @returns {AuthContextType} Authentication state (user, isAuthenticated, loading, error, checkAuth)
|
|
74
|
+
* @throws {Error} If used outside of an AuthProvider
|
|
75
|
+
*/
|
|
76
|
+
export function useAuth(): AuthContextType {
|
|
77
|
+
const context = useContext(AuthContext);
|
|
78
|
+
if (context === undefined) {
|
|
79
|
+
throw new Error("useAuth must be used within an AuthProvider");
|
|
80
|
+
}
|
|
81
|
+
return context;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns the current authenticated user.
|
|
86
|
+
* @returns {User} The authenticated user object
|
|
87
|
+
* @throws {Error} If not used within AuthProvider or user is not authenticated
|
|
88
|
+
*/
|
|
89
|
+
export function useUser(): User {
|
|
90
|
+
const context = useAuth();
|
|
91
|
+
if (!context.user) {
|
|
92
|
+
throw new Error("Authenticated context not established");
|
|
93
|
+
}
|
|
94
|
+
return context.user;
|
|
95
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Link } from "react-router";
|
|
2
|
+
import { cn } from "../../../lib/utils";
|
|
3
|
+
|
|
4
|
+
interface FooterLinkProps extends Omit<React.ComponentProps<typeof Link>, "children"> {
|
|
5
|
+
/** Link text prefix (e.g., "Don't have an account?") */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Link label (e.g., "Sign up") */
|
|
8
|
+
linkText: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Footer link component.
|
|
13
|
+
*/
|
|
14
|
+
export function FooterLink({ text, to, linkText, className, ...props }: FooterLinkProps) {
|
|
15
|
+
return (
|
|
16
|
+
<p className={cn("w-full text-center text-sm text-muted-foreground", className)}>
|
|
17
|
+
{text && (
|
|
18
|
+
<>
|
|
19
|
+
{text}
|
|
20
|
+
{/* Robustness: Explicit space ensures formatting tools don't strip it */}
|
|
21
|
+
{"\u00A0"}
|
|
22
|
+
</>
|
|
23
|
+
)}
|
|
24
|
+
<Link
|
|
25
|
+
to={to}
|
|
26
|
+
className={cn(
|
|
27
|
+
"font-medium underline hover:text-primary transition-colors",
|
|
28
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{linkText}
|
|
33
|
+
</Link>
|
|
34
|
+
</p>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { FieldGroup } from "../../../components/ui/field";
|
|
2
|
+
import { StatusAlert } from "../../../components/alerts/status-alert";
|
|
3
|
+
import { FooterLink } from "../footers/footer-link";
|
|
4
|
+
import { SubmitButton } from "./submit-button";
|
|
5
|
+
import { CardLayout } from "../../../components/layouts/card-layout";
|
|
6
|
+
import { useFormContext } from "../hooks/form";
|
|
7
|
+
import { useId } from "react";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* [Dev Note] A wrapper component that enforces consistent layout (Card) and error/success alert positioning
|
|
11
|
+
* for all authentication forms.
|
|
12
|
+
*/
|
|
13
|
+
interface AuthFormProps extends Omit<React.ComponentProps<"form">, "onSubmit"> {
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
error?: React.ReactNode;
|
|
17
|
+
success?: React.ReactNode;
|
|
18
|
+
submit: {
|
|
19
|
+
text: string;
|
|
20
|
+
loadingText?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
footer?: {
|
|
24
|
+
text?: string;
|
|
25
|
+
link: string;
|
|
26
|
+
linkText: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* [Dev Note] Standardized Authentication Layout:
|
|
32
|
+
* Wraps the specific logic of Login/Register forms with a consistent visual frame (Card),
|
|
33
|
+
* title, and error alert placement. Extends form element props for flexibility.
|
|
34
|
+
* This ensures all auth-related pages look and behave similarly.
|
|
35
|
+
*/
|
|
36
|
+
export function AuthForm({
|
|
37
|
+
id: providedId,
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
error,
|
|
41
|
+
success,
|
|
42
|
+
children,
|
|
43
|
+
submit,
|
|
44
|
+
footer,
|
|
45
|
+
...props
|
|
46
|
+
}: AuthFormProps) {
|
|
47
|
+
const form = useFormContext();
|
|
48
|
+
const generatedId = useId();
|
|
49
|
+
const id = providedId ?? generatedId;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<CardLayout title={title} description={description}>
|
|
53
|
+
<div className="space-y-6">
|
|
54
|
+
{/* [Dev Note] Global form error alert (e.g. "Invalid Credentials") */}
|
|
55
|
+
{error && <StatusAlert variant="error">{error}</StatusAlert>}
|
|
56
|
+
{success && <StatusAlert variant="success">{success}</StatusAlert>}
|
|
57
|
+
|
|
58
|
+
<form
|
|
59
|
+
id={id}
|
|
60
|
+
onSubmit={(e) => {
|
|
61
|
+
e.preventDefault();
|
|
62
|
+
e.stopPropagation();
|
|
63
|
+
form.handleSubmit();
|
|
64
|
+
}}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<FieldGroup>{children}</FieldGroup>
|
|
68
|
+
<SubmitButton
|
|
69
|
+
form={id}
|
|
70
|
+
label={submit.text}
|
|
71
|
+
loadingLabel={submit.loadingText}
|
|
72
|
+
disabled={submit.disabled}
|
|
73
|
+
className="mt-6"
|
|
74
|
+
/>
|
|
75
|
+
</form>
|
|
76
|
+
{/* [Dev Note] Navigation links (e.g. "Forgot Password?") */}
|
|
77
|
+
{footer && <FooterLink text={footer.text} to={footer.link} linkText={footer.linkText} />}
|
|
78
|
+
</div>
|
|
79
|
+
</CardLayout>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Button } from "../../../components/ui/button";
|
|
2
|
+
import { Spinner } from "../../../components/ui/spinner";
|
|
3
|
+
import { cn } from "../../../lib/utils";
|
|
4
|
+
import { useFormContext } from "../hooks/form";
|
|
5
|
+
|
|
6
|
+
interface SubmitButtonProps extends Omit<React.ComponentProps<typeof Button>, "type"> {
|
|
7
|
+
/** Button text when not submitting */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Button text while submitting */
|
|
10
|
+
loadingLabel?: string;
|
|
11
|
+
/** Form id to associate with (for buttons outside form element) */
|
|
12
|
+
form?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const isSubmittingSelector = (state: { isSubmitting: boolean }) => state.isSubmitting;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Submit button that subscribes to form submission state.
|
|
19
|
+
* UX Best Practice:
|
|
20
|
+
* 1. Disables interaction immediately upon click (via isLoading) to prevent
|
|
21
|
+
* accidental double-submissions.
|
|
22
|
+
* 2. Provides immediate visual feedback (Spinner).
|
|
23
|
+
*/
|
|
24
|
+
export function SubmitButton({
|
|
25
|
+
label,
|
|
26
|
+
loadingLabel = "Submitting…",
|
|
27
|
+
className,
|
|
28
|
+
form: formId,
|
|
29
|
+
disabled,
|
|
30
|
+
...props
|
|
31
|
+
}: SubmitButtonProps) {
|
|
32
|
+
const form = useFormContext();
|
|
33
|
+
return (
|
|
34
|
+
<form.Subscribe selector={isSubmittingSelector}>
|
|
35
|
+
{(isSubmitting: boolean) => (
|
|
36
|
+
<Button
|
|
37
|
+
type="submit"
|
|
38
|
+
form={formId}
|
|
39
|
+
className={cn("w-full", className)}
|
|
40
|
+
disabled={isSubmitting || disabled}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{isSubmitting && <Spinner className="mr-2" aria-hidden="true" />}
|
|
44
|
+
{isSubmitting ? loadingLabel : label}
|
|
45
|
+
</Button>
|
|
46
|
+
)}
|
|
47
|
+
</form.Subscribe>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
import { createFormHookContexts, createFormHook } from "@tanstack/react-form";
|
|
3
|
+
import {
|
|
4
|
+
Field,
|
|
5
|
+
FieldDescription,
|
|
6
|
+
FieldError,
|
|
7
|
+
FieldLabel,
|
|
8
|
+
} from "../../../components/ui/field";
|
|
9
|
+
import { Input } from "../../../components/ui/input";
|
|
10
|
+
import { cn } from "../../../lib/utils";
|
|
11
|
+
import { AUTH_PLACEHOLDERS } from "../authenticationConfig";
|
|
12
|
+
|
|
13
|
+
// Create form hook contexts
|
|
14
|
+
export const { fieldContext, formContext, useFieldContext, useFormContext } =
|
|
15
|
+
createFormHookContexts();
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Field Components
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
interface TextFieldProps extends Omit<
|
|
22
|
+
React.ComponentProps<typeof Input>,
|
|
23
|
+
"name" | "value" | "onBlur" | "onChange" | "aria-invalid"
|
|
24
|
+
> {
|
|
25
|
+
label: string;
|
|
26
|
+
labelAction?: React.ReactNode;
|
|
27
|
+
description?: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function TextField({
|
|
31
|
+
label,
|
|
32
|
+
id: providedId,
|
|
33
|
+
labelAction,
|
|
34
|
+
description,
|
|
35
|
+
type = "text",
|
|
36
|
+
...props
|
|
37
|
+
}: TextFieldProps) {
|
|
38
|
+
const field = useFieldContext<string>();
|
|
39
|
+
const generatedId = useId();
|
|
40
|
+
const id = providedId ?? generatedId;
|
|
41
|
+
const descriptionId = `${id}-description`;
|
|
42
|
+
const errorId = `${id}-error`;
|
|
43
|
+
const isInvalid = field.state.meta.isTouched && field.state.meta.errors.length > 0;
|
|
44
|
+
|
|
45
|
+
// Deduplicate errors by message
|
|
46
|
+
const errors = field.state.meta.errors;
|
|
47
|
+
const uniqueErrors = [...new Map(errors.map((item: any) => [item["message"], item])).values()];
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Field data-invalid={isInvalid}>
|
|
51
|
+
<div className="flex items-center">
|
|
52
|
+
<FieldLabel htmlFor={id}>{label}</FieldLabel>
|
|
53
|
+
{labelAction && <div className="ml-auto">{labelAction}</div>}
|
|
54
|
+
</div>
|
|
55
|
+
{description && <FieldDescription id={descriptionId}>{description}</FieldDescription>}
|
|
56
|
+
<Input
|
|
57
|
+
id={id}
|
|
58
|
+
name={field.name as string}
|
|
59
|
+
type={type}
|
|
60
|
+
value={field.state.value ?? ""}
|
|
61
|
+
onBlur={field.handleBlur}
|
|
62
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => field.handleChange(e.target.value)}
|
|
63
|
+
aria-invalid={isInvalid}
|
|
64
|
+
aria-describedby={cn(description && descriptionId, isInvalid && errorId)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
{isInvalid && uniqueErrors.length > 0 && <FieldError errors={uniqueErrors} />}
|
|
68
|
+
</Field>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Password field with preset type and autocomplete */
|
|
73
|
+
function PasswordField({
|
|
74
|
+
label,
|
|
75
|
+
autoComplete = "current-password",
|
|
76
|
+
placeholder = AUTH_PLACEHOLDERS.PASSWORD,
|
|
77
|
+
...props
|
|
78
|
+
}: Omit<TextFieldProps, "type">) {
|
|
79
|
+
return (
|
|
80
|
+
<TextField
|
|
81
|
+
label={label}
|
|
82
|
+
type="password"
|
|
83
|
+
autoComplete={autoComplete}
|
|
84
|
+
placeholder={placeholder}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Email field with preset type and autocomplete */
|
|
91
|
+
function EmailField({
|
|
92
|
+
label,
|
|
93
|
+
placeholder = AUTH_PLACEHOLDERS.EMAIL,
|
|
94
|
+
...props
|
|
95
|
+
}: Omit<TextFieldProps, "type">) {
|
|
96
|
+
return (
|
|
97
|
+
<TextField
|
|
98
|
+
label={label}
|
|
99
|
+
type="email"
|
|
100
|
+
autoComplete="username"
|
|
101
|
+
placeholder={placeholder}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Create Form Hook
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
export const { useAppForm } = createFormHook({
|
|
112
|
+
fieldContext,
|
|
113
|
+
formContext,
|
|
114
|
+
fieldComponents: {
|
|
115
|
+
TextField,
|
|
116
|
+
PasswordField,
|
|
117
|
+
EmailField,
|
|
118
|
+
},
|
|
119
|
+
formComponents: {},
|
|
120
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom hook for countdown timer with accessibility features
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Accessibility configuration for countdown timer
|
|
9
|
+
*/
|
|
10
|
+
export interface CountdownTimerA11yConfig {
|
|
11
|
+
/** Announce time remaining at these specific second marks */
|
|
12
|
+
ANNOUNCE_AT_SECONDS: readonly number[];
|
|
13
|
+
/** Announce every N seconds (after initial period) */
|
|
14
|
+
ANNOUNCE_INTERVAL_SECONDS: number;
|
|
15
|
+
/** Minimum elapsed time before starting interval announcements */
|
|
16
|
+
MIN_ELAPSED_FOR_INTERVAL: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Return value from useCountdownTimer hook
|
|
21
|
+
*/
|
|
22
|
+
export interface CountdownTimerResult {
|
|
23
|
+
/** Current time remaining in seconds */
|
|
24
|
+
displayTime: number;
|
|
25
|
+
/** Formatted time string (MM:SS) */
|
|
26
|
+
formattedTime: string;
|
|
27
|
+
/** ISO 8601 duration string (e.g., PT2M15S) */
|
|
28
|
+
isoTime: string;
|
|
29
|
+
/** Accessibility announcement text for screen readers */
|
|
30
|
+
accessibilityAnnouncement: string;
|
|
31
|
+
/** Start the countdown timer */
|
|
32
|
+
start: () => void;
|
|
33
|
+
/** Stop the countdown timer */
|
|
34
|
+
stop: () => void;
|
|
35
|
+
/** Reset the countdown timer to initial time */
|
|
36
|
+
reset: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configuration for countdown timer hook
|
|
41
|
+
*/
|
|
42
|
+
export interface CountdownTimerConfig {
|
|
43
|
+
/** Initial time in seconds */
|
|
44
|
+
initialTime: number;
|
|
45
|
+
/** Callback when countdown reaches 0 */
|
|
46
|
+
onExpire: () => void;
|
|
47
|
+
/** Optional accessibility configuration */
|
|
48
|
+
a11yConfig?: CountdownTimerA11yConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default accessibility configuration
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_A11Y_CONFIG: CountdownTimerA11yConfig = {
|
|
55
|
+
ANNOUNCE_AT_SECONDS: [5, 1],
|
|
56
|
+
ANNOUNCE_INTERVAL_SECONDS: 10,
|
|
57
|
+
MIN_ELAPSED_FOR_INTERVAL: 10,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format time remaining as MM:SS string
|
|
62
|
+
* Uses Intl.NumberFormat for zero-padding and internationalization
|
|
63
|
+
*
|
|
64
|
+
* @param seconds - Total seconds remaining
|
|
65
|
+
* @returns Formatted time string (e.g., "05:23")
|
|
66
|
+
*/
|
|
67
|
+
function formatTimeRemaining(seconds: number): string {
|
|
68
|
+
const minutes = Math.floor(seconds / 60);
|
|
69
|
+
const secs = seconds % 60;
|
|
70
|
+
|
|
71
|
+
// Use Intl.NumberFormat for zero-padding with internationalization
|
|
72
|
+
const formatter = new Intl.NumberFormat(navigator.language, {
|
|
73
|
+
minimumIntegerDigits: 2,
|
|
74
|
+
useGrouping: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return `${formatter.format(minutes)}:${formatter.format(secs)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Format time remaining as ISO 8601 duration for ARIA
|
|
82
|
+
* Used in datetime attribute of <time> element
|
|
83
|
+
*
|
|
84
|
+
* @param seconds - Total seconds remaining
|
|
85
|
+
* @returns ISO 8601 duration string (e.g., "PT2M15S")
|
|
86
|
+
*/
|
|
87
|
+
function formatISODuration(seconds: number): string {
|
|
88
|
+
const minutes = Math.floor(seconds / 60);
|
|
89
|
+
const secs = seconds % 60;
|
|
90
|
+
return `PT${minutes}M${secs}S`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Format time remaining for screen reader announcement
|
|
95
|
+
* Uses Intl.DurationFormat if available, falls back to manual formatting
|
|
96
|
+
*
|
|
97
|
+
* @param seconds - Total seconds remaining
|
|
98
|
+
* @returns Formatted announcement text (e.g., "2 minutes 15 seconds")
|
|
99
|
+
*/
|
|
100
|
+
function formatAccessibilityAnnouncement(seconds: number): string {
|
|
101
|
+
const minutes = Math.floor(seconds / 60);
|
|
102
|
+
const secs = seconds % 60;
|
|
103
|
+
|
|
104
|
+
// Try using Intl.DurationFormat (newer API, may not be available in all browsers)
|
|
105
|
+
if (typeof Intl !== "undefined" && "DurationFormat" in Intl) {
|
|
106
|
+
try {
|
|
107
|
+
// @ts-expect-error - DurationFormat is not yet in TypeScript lib
|
|
108
|
+
const formatter = new Intl.DurationFormat(navigator.language, { style: "long" });
|
|
109
|
+
return formatter.format({ minutes, seconds: secs });
|
|
110
|
+
} catch {
|
|
111
|
+
// Fallback to manual formatting
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Manual fallback
|
|
116
|
+
const parts: string[] = [];
|
|
117
|
+
if (minutes > 0) {
|
|
118
|
+
parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
|
|
119
|
+
}
|
|
120
|
+
if (secs > 0 || minutes === 0) {
|
|
121
|
+
parts.push(`${secs} ${secs === 1 ? "second" : "seconds"}`);
|
|
122
|
+
}
|
|
123
|
+
return parts.join(" ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Determine if an accessibility announcement should be made at this time
|
|
128
|
+
*
|
|
129
|
+
* @param currentTime - Current countdown time in seconds
|
|
130
|
+
* @param initialTime - Initial countdown time in seconds
|
|
131
|
+
* @param config - Accessibility configuration
|
|
132
|
+
* @returns True if announcement should be made
|
|
133
|
+
*/
|
|
134
|
+
function shouldAnnounce(
|
|
135
|
+
currentTime: number,
|
|
136
|
+
initialTime: number,
|
|
137
|
+
config: CountdownTimerA11yConfig,
|
|
138
|
+
): boolean {
|
|
139
|
+
// Announce at specific second marks (5s, 1s)
|
|
140
|
+
if (config.ANNOUNCE_AT_SECONDS.includes(currentTime)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Calculate elapsed time
|
|
145
|
+
const elapsed = initialTime - currentTime;
|
|
146
|
+
|
|
147
|
+
// Announce every N seconds after minimum elapsed time
|
|
148
|
+
if (elapsed >= config.MIN_ELAPSED_FOR_INTERVAL) {
|
|
149
|
+
return currentTime % config.ANNOUNCE_INTERVAL_SECONDS === 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Custom hook for countdown timer with accessibility
|
|
157
|
+
* Decrements from initial time to 0, provides formatted output for display and ARIA
|
|
158
|
+
*
|
|
159
|
+
* @param config - Timer configuration
|
|
160
|
+
* @returns Timer state and control functions
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const timer = useCountdownTimer({
|
|
164
|
+
* initialTime: 300, // 5 minutes
|
|
165
|
+
* onExpire: () => handleLogout()
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* timer.start(); // Begin countdown
|
|
169
|
+
*
|
|
170
|
+
* <time dateTime={timer.isoTime} role="timer">
|
|
171
|
+
* {timer.formattedTime}
|
|
172
|
+
* </time>
|
|
173
|
+
* <div role="status" aria-live="polite" className="sr-only">
|
|
174
|
+
* {timer.accessibilityAnnouncement}
|
|
175
|
+
* </div>
|
|
176
|
+
*/
|
|
177
|
+
export function useCountdownTimer({
|
|
178
|
+
initialTime,
|
|
179
|
+
onExpire,
|
|
180
|
+
a11yConfig = DEFAULT_A11Y_CONFIG,
|
|
181
|
+
}: CountdownTimerConfig): CountdownTimerResult {
|
|
182
|
+
const [displayTime, setDisplayTime] = useState(initialTime);
|
|
183
|
+
const [accessibilityAnnouncement, setAccessibilityAnnouncement] = useState("");
|
|
184
|
+
const [isActive, setIsActive] = useState(false);
|
|
185
|
+
|
|
186
|
+
// Use refs to avoid stale closure issues
|
|
187
|
+
const initialTimeRef = useRef(initialTime);
|
|
188
|
+
const onExpireRef = useRef(onExpire);
|
|
189
|
+
const a11yConfigRef = useRef(a11yConfig);
|
|
190
|
+
const endTimeRef = useRef<number>(0);
|
|
191
|
+
const previousTimeRef = useRef<number>(initialTime);
|
|
192
|
+
|
|
193
|
+
// Update refs when props change
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
initialTimeRef.current = initialTime;
|
|
196
|
+
}, [initialTime]);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
onExpireRef.current = onExpire;
|
|
200
|
+
}, [onExpire]);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
a11yConfigRef.current = a11yConfig;
|
|
204
|
+
}, [a11yConfig]);
|
|
205
|
+
|
|
206
|
+
// Countdown effect using Date.now() for accuracy
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (!isActive) return;
|
|
209
|
+
|
|
210
|
+
// Set the target end time when timer starts
|
|
211
|
+
endTimeRef.current = Date.now() + initialTimeRef.current * 1000;
|
|
212
|
+
previousTimeRef.current = initialTimeRef.current;
|
|
213
|
+
|
|
214
|
+
const intervalId = setInterval(() => {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const remainingMs = endTimeRef.current - now;
|
|
217
|
+
const newTime = Math.max(0, Math.ceil(remainingMs / 1000));
|
|
218
|
+
|
|
219
|
+
setDisplayTime(newTime);
|
|
220
|
+
|
|
221
|
+
// Check if we should make an accessibility announcement
|
|
222
|
+
// Only announce when the second value changes
|
|
223
|
+
if (
|
|
224
|
+
newTime !== previousTimeRef.current &&
|
|
225
|
+
shouldAnnounce(newTime, initialTimeRef.current, a11yConfigRef.current)
|
|
226
|
+
) {
|
|
227
|
+
setAccessibilityAnnouncement(formatAccessibilityAnnouncement(newTime));
|
|
228
|
+
}
|
|
229
|
+
previousTimeRef.current = newTime;
|
|
230
|
+
|
|
231
|
+
// Check if countdown expired
|
|
232
|
+
if (newTime <= 0) {
|
|
233
|
+
clearInterval(intervalId);
|
|
234
|
+
setIsActive(false);
|
|
235
|
+
onExpireRef.current();
|
|
236
|
+
}
|
|
237
|
+
}, 100); // Check more frequently for smoother updates
|
|
238
|
+
|
|
239
|
+
return () => clearInterval(intervalId);
|
|
240
|
+
}, [isActive]);
|
|
241
|
+
|
|
242
|
+
// Control functions
|
|
243
|
+
const start = useCallback(() => {
|
|
244
|
+
setIsActive(true);
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
const stop = useCallback(() => {
|
|
248
|
+
setIsActive(false);
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const reset = useCallback(() => {
|
|
252
|
+
setDisplayTime(initialTimeRef.current);
|
|
253
|
+
setIsActive(false);
|
|
254
|
+
setAccessibilityAnnouncement("");
|
|
255
|
+
}, []);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
displayTime,
|
|
259
|
+
formattedTime: formatTimeRemaining(displayTime),
|
|
260
|
+
isoTime: formatISODuration(displayTime),
|
|
261
|
+
accessibilityAnnouncement,
|
|
262
|
+
start,
|
|
263
|
+
stop,
|
|
264
|
+
reset,
|
|
265
|
+
};
|
|
266
|
+
}
|