From 2769691424c96b8adac228ce89c29edaecefd222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Zi=C4=99cina?= Date: Thu, 5 Dec 2024 17:22:09 +0100 Subject: [PATCH] fix: community type disclaimer frontend (#4496) * fix: add community type disclaimer to public site * fix: add community type disclaimer to partners * fix: add partners tests * fix: form type issue * fix: failing build * fix: allow reserved community disclaimer only when community type selected * fix: tests * fix: be tests * fix: add translations with tests --- api/src/dtos/listings/listing.dto.ts | 9 ++ api/src/services/translation.service.ts | 7 ++ api/test/integration/listing.e2e-spec.ts | 14 ++- .../unit/services/translation.service.spec.ts | 12 +++ .../cypress/e2e/default/03-listing.spec.ts | 10 ++ sites/partners/cypress/fixtures/listing.json | 2 + .../locale_overrides/general.json | 7 ++ .../sections/DetailCommunityType.tsx | 29 ++++++ .../sections/CommunityType.tsx | 81 +++++++++++++++- .../listings/AdditionalMetadataFormatter.ts | 15 ++- .../src/lib/listings/BooleansFormatter.ts | 5 + sites/partners/src/lib/listings/formTypes.ts | 3 + .../lib/applications/AppSubmissionContext.ts | 95 +++++++++++-------- .../lib/applications/ApplicationConductor.ts | 3 + .../start/community-disclaimer.tsx | 64 +++++++++++++ .../applications/start/what-to-expect.tsx | 4 +- 16 files changed, 307 insertions(+), 53 deletions(-) create mode 100644 sites/public/src/pages/applications/start/community-disclaimer.tsx diff --git a/api/src/dtos/listings/listing.dto.ts b/api/src/dtos/listings/listing.dto.ts index 980c2f439a..9aad02b107 100644 --- a/api/src/dtos/listings/listing.dto.ts +++ b/api/src/dtos/listings/listing.dto.ts @@ -11,6 +11,7 @@ import { IsUrl, MaxLength, Validate, + ValidateIf, ValidateNested, } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -622,11 +623,19 @@ class Listing extends AbstractDTO { @Expose() @ApiPropertyOptional() + @ValidateIf((o) => o.includeCommunityDisclaimer, { + groups: [ValidationsGroupsEnum.default], + }) + @IsDefined({ groups: [ValidationsGroupsEnum.default] }) @IsString({ groups: [ValidationsGroupsEnum.default] }) communityDisclaimerTitle?: string; @Expose() @ApiPropertyOptional() + @ValidateIf((o) => o.includeCommunityDisclaimer, { + groups: [ValidationsGroupsEnum.default], + }) + @IsDefined({ groups: [ValidationsGroupsEnum.default] }) @IsString({ groups: [ValidationsGroupsEnum.default] }) communityDisclaimerDescription?: string; diff --git a/api/src/services/translation.service.ts b/api/src/services/translation.service.ts index b9a6f233d9..afe3c98a3b 100644 --- a/api/src/services/translation.service.ts +++ b/api/src/services/translation.service.ts @@ -166,6 +166,13 @@ export class TranslationService { }); } + if (listing.includeCommunityDisclaimer) { + pathsToFilter[`communityDisclaimerTitle`] = + listing.communityDisclaimerTitle; + pathsToFilter[`communityDisclaimerDescription`] = + listing.communityDisclaimerDescription; + } + const persistedTranslationsFromDB = await this.getPersistedTranslatedValues( listing, language, diff --git a/api/test/integration/listing.e2e-spec.ts b/api/test/integration/listing.e2e-spec.ts index 8a3613256f..4c516cc315 100644 --- a/api/test/integration/listing.e2e-spec.ts +++ b/api/test/integration/listing.e2e-spec.ts @@ -149,6 +149,8 @@ describe('Listing Controller Tests', () => { label: 'example asset label', }; + const shouldIncludeCommunityDisclaimer = Math.random() >= 0.5; + return { id: listingId ?? undefined, assets: [exampleAsset], @@ -344,11 +346,13 @@ describe('Listing Controller Tests', () => { phone: false, internet: true, }, - includeCommunityDisclaimer: Math.random() >= 0.5, - communityDisclaimerTitle: - Math.random() >= 0.5 ? 'example title' : undefined, - communityDisclaimerDescription: - Math.random() >= 0.5 ? 'example description' : undefined, + includeCommunityDisclaimer: shouldIncludeCommunityDisclaimer, + communityDisclaimerTitle: shouldIncludeCommunityDisclaimer + ? 'example title' + : undefined, + communityDisclaimerDescription: shouldIncludeCommunityDisclaimer + ? 'example description' + : undefined, homeType: 'apartment', }; }; diff --git a/api/test/unit/services/translation.service.spec.ts b/api/test/unit/services/translation.service.spec.ts index 7cd8f2de98..72d932e288 100644 --- a/api/test/unit/services/translation.service.spec.ts +++ b/api/test/unit/services/translation.service.spec.ts @@ -101,6 +101,10 @@ const mockListing = (): Listing => { }, }, ], + includeCommunityDisclaimer: true, + communityDisclaimerTitle: 'untranslated community disclaimer title', + communityDisclaimerDescription: + 'untranslated community disclaimer description', }; }; @@ -133,6 +137,8 @@ const translatedStrings = [ 'translated multiselect description', 'translated multiselect subtext', 'translated multiselect opt out text', + 'translated community disclaimer title', + 'translated community disclaimer description', ]; describe('Testing translations service', () => { @@ -395,4 +401,10 @@ const validateTranslatedFields = (listing: Listing) => { expect( listing.listingMultiselectQuestions[0].multiselectQuestions.optOutText, ).toEqual('translated multiselect opt out text'); + expect(listing.communityDisclaimerTitle).toEqual( + 'translated community disclaimer title', + ); + expect(listing.communityDisclaimerDescription).toEqual( + 'translated community disclaimer description', + ); }; diff --git a/sites/partners/cypress/e2e/default/03-listing.spec.ts b/sites/partners/cypress/e2e/default/03-listing.spec.ts index 631040a870..922acc5af8 100644 --- a/sites/partners/cypress/e2e/default/03-listing.spec.ts +++ b/sites/partners/cypress/e2e/default/03-listing.spec.ts @@ -27,6 +27,8 @@ describe("Listing Management Tests", () => { cy.contains("Listing Data") // Try to publish a listing and should show errors for appropriate fields cy.getByID("listingEditButton").contains("Edit").click() + cy.getByID("reservedCommunityTypes.id").select(1) + cy.getByID("includeCommunityDisclaimerYes").check() cy.getByID("publishButton").contains("Publish").click() cy.getByID("publishButtonConfirm").contains("Publish").click() cy.contains("Please resolve any errors before saving or publishing your listing.") @@ -42,6 +44,8 @@ describe("Listing Management Tests", () => { expect($alertButtons[1]).to.have.id("addUnitsButton") }) cy.getByID("units-error").contains("This field is required") + cy.getByID("communityDisclaimerTitle-error").contains("Enter title") + cy.get(".textarea-error-message").contains("Enter description") cy.getByID("applicationProcessButton").contains("Application Process").click() cy.getByID("leasingAgentName-error").contains("This field is required") cy.getByID("leasingAgentEmail-error").contains("This field is required") @@ -158,6 +162,9 @@ describe("Listing Management Tests", () => { cy.get(".addressPopup").contains(listing["buildingAddress.street"]) cy.getByID("reservedCommunityTypes.id").select(listing["reservedCommunityType.id"]) cy.getByID("reservedCommunityDescription").type(listing["reservedCommunityDescription"]) + cy.getByID("includeCommunityDisclaimerYes").check() + cy.getByID("communityDisclaimerTitle").type(listing["communityDisclaimerTitle"]) + cy.getByID("communityDisclaimerDescription").type(listing["communityDisclaimerDescription"]) cy.getByTestId("unit-types").check() cy.getByTestId("listingAvailability.availableUnits").check() if (listing["homeType"]) { @@ -306,6 +313,9 @@ describe("Listing Management Tests", () => { cy.getByID("latitude").should("include.text", "37.7") cy.getByID("reservedCommunityType").contains(listing["reservedCommunityType.id"]) cy.getByID("reservedCommunityDescription").contains(listing["reservedCommunityDescription"]) + cy.getByID("includeCommunityDisclaimer").contains("Yes") + cy.getByID("communityDisclaimerTitle").contains(listing["communityDisclaimerTitle"]) + cy.getByID("communityDisclaimerDescription").contains(listing["communityDisclaimerDescription"]) if (listing["homeType"]) { cy.getByID("homeType").contains(listing["homeType"]) } diff --git a/sites/partners/cypress/fixtures/listing.json b/sites/partners/cypress/fixtures/listing.json index 681127bde4..7c99054fd6 100644 --- a/sites/partners/cypress/fixtures/listing.json +++ b/sites/partners/cypress/fixtures/listing.json @@ -10,6 +10,8 @@ "yearBuilt": "2021", "reservedCommunityType.id": "Seniors", "reservedCommunityDescription": "Basic Test Description", + "communityDisclaimerTitle": "Basic Test Title", + "communityDisclaimerDescription": "Basic Test Description", "homeType": "Apartment", "number": "2", "unitType.id": "One Bedroom", diff --git a/sites/partners/page_content/locale_overrides/general.json b/sites/partners/page_content/locale_overrides/general.json index 1a7cf3fe18..82dbf7523e 100644 --- a/sites/partners/page_content/locale_overrides/general.json +++ b/sites/partners/page_content/locale_overrides/general.json @@ -167,6 +167,7 @@ "listings.addPreferences": "Add Preferences", "listings.additionalApplicationSubmissionNotes": "Additional Application Submission Notes", "listings.amiOverrideTitle": "Override for household size of %{householdSize}", + "listings.appearsInListing": "Appears in listing", "listings.applicationDropOffQuestion": "Can applications be dropped off?", "listings.applicationDueTime": "Application Due Time", "listings.applicationPickupQuestion": "Can applications be picked up?", @@ -316,7 +317,11 @@ "listings.referralContactPhone": "Referral Contact Phone", "listings.referralSummary": "Referral Summary", "listings.requiredToPublish": "Required to publish", + "listings.requiredToPublishAppearsAsFirstPage": "Required to publish, appears as first page of application", "listings.reservedCommunityDescription": "Reserved Community Description", + "listings.reservedCommunityDisclaimer": "Reserved Community Disclaimer", + "listings.reservedCommunityDisclaimerTitle": "Reserved Community Disclaimer Title", + "listings.includeCommunityDisclaimer": "Do you want to include a community type disclaimer as the first page of the application?", "listings.reviewOrderQuestion": "How is the application review order determined?", "listings.sections.addOpenHouse": "Add Open House", "listings.sections.additionalDetails": "Additional Details", @@ -475,6 +480,8 @@ "t.end": "End", "t.endTime": "End Time", "t.enterAmount": "Enter amount", + "t.enterDescription": "Enter description", + "t.enterTitle": "Enter title", "t.error": "Error", "t.errorOccurred": "An error has occurred.", "t.exit": "Exit", diff --git a/sites/partners/src/components/listings/PaperListingDetails/sections/DetailCommunityType.tsx b/sites/partners/src/components/listings/PaperListingDetails/sections/DetailCommunityType.tsx index 12bdab2cc8..328d9ed86a 100644 --- a/sites/partners/src/components/listings/PaperListingDetails/sections/DetailCommunityType.tsx +++ b/sites/partners/src/components/listings/PaperListingDetails/sections/DetailCommunityType.tsx @@ -8,6 +8,8 @@ import SectionWithGrid from "../../../shared/SectionWithGrid" const DetailCommunityType = () => { const listing = useContext(ListingContext) + const includeCommunityDisclaimer = listing.includeCommunityDisclaimer + return ( @@ -27,6 +29,33 @@ const DetailCommunityType = () => { {getDetailFieldString(listing.reservedCommunityDescription)} + + + + {includeCommunityDisclaimer ? t("t.yes") : t("t.no")} + + + {includeCommunityDisclaimer && ( + <> + + + {getDetailFieldString(listing.communityDisclaimerTitle)} + + + {getDetailFieldString(listing.communityDisclaimerDescription)} + + + + )} ) } diff --git a/sites/partners/src/components/listings/PaperListingForm/sections/CommunityType.tsx b/sites/partners/src/components/listings/PaperListingForm/sections/CommunityType.tsx index 4c956b7561..bd28f0c3fa 100644 --- a/sites/partners/src/components/listings/PaperListingForm/sections/CommunityType.tsx +++ b/sites/partners/src/components/listings/PaperListingForm/sections/CommunityType.tsx @@ -1,8 +1,11 @@ import React, { useEffect, useState } from "react" import { useFormContext } from "react-hook-form" -import { t, Select, Textarea } from "@bloom-housing/ui-components" +import { t, Select, Textarea, FieldGroup, Field } from "@bloom-housing/ui-components" import { FieldValue, Grid } from "@bloom-housing/ui-seeds" -import { ReservedCommunityType } from "@bloom-housing/shared-helpers/src/types/backend-swagger" +import { + ReservedCommunityType, + YesNoEnum, +} from "@bloom-housing/shared-helpers/src/types/backend-swagger" import { useReservedCommunityTypeList } from "../../../../lib/hooks" import { arrayToFormOptions } from "../../../../lib/helpers" import { FormListing } from "../../../../lib/listings/formTypes" @@ -16,7 +19,7 @@ const CommunityType = ({ listing }: CommunityTypeProps) => { const formMethods = useFormContext() // eslint-disable-next-line @typescript-eslint/unbound-method - const { register, setValue, watch } = formMethods + const { register, setValue, watch, errors } = formMethods const reservedCommunityType = watch("reservedCommunityTypes.id") @@ -44,6 +47,19 @@ const CommunityType = ({ listing }: CommunityTypeProps) => { } }, [reservedCommunityType, listing?.reservedCommunityTypes?.id]) + useEffect(() => { + if ( + listing && + watch("includeCommunityDisclaimerQuestion") === null && + listing?.includeCommunityDisclaimer !== null + ) { + setValue( + "includeCommunityDisclaimerQuestion", + listing?.includeCommunityDisclaimer ? YesNoEnum.yes : YesNoEnum.no + ) + } + }, [setValue, listing?.includeCommunityDisclaimer, watch, listing]) + return ( <>
@@ -79,9 +95,68 @@ const CommunityType = ({ listing }: CommunityTypeProps) => { id={"reservedCommunityDescription"} fullWidth={true} register={register} + note={t("listings.appearsInListing")} /> + + + + + + + + {watch("includeCommunityDisclaimerQuestion") === YesNoEnum.yes && currentCommunityType && ( + <> + + + + + + + + +