From cc2b7b84ed5a2595d79fc6dbcd441fbb3dda64a6 Mon Sep 17 00:00:00 2001 From: Yazeed Loonat Date: Mon, 25 Nov 2024 10:42:12 -0700 Subject: [PATCH 1/3] feat: adding community type disclaimer fields (#4486) * feat: adding community type disclaimer fields * fix: updates per morgan (cherry picked from commit baa2f437915c8e2a15e8b6f452fc7c2750d3539f) --- .../migration.sql | 4 +++ api/prisma/schema.prisma | 19 +++++++----- api/src/dtos/listings/listing.dto.ts | 15 ++++++++++ api/test/integration/listing.e2e-spec.ts | 5 ++++ shared-helpers/src/types/backend-swagger.ts | 29 +++++++++++++++++++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 api/prisma/migrations/21_community_type_description/migration.sql diff --git a/api/prisma/migrations/21_community_type_description/migration.sql b/api/prisma/migrations/21_community_type_description/migration.sql new file mode 100644 index 0000000000..9f6d42eeba --- /dev/null +++ b/api/prisma/migrations/21_community_type_description/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "listings" ADD COLUMN "community_disclaimer_description" TEXT, +ADD COLUMN "community_disclaimer_title" TEXT, +ADD COLUMN "include_community_disclaimer" BOOLEAN; diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index cdc54d8937..7d897a6b8e 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -275,13 +275,13 @@ model Demographics { } model FeatureFlags { - id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6) - updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6) - name String @unique() - description String - active Boolean @default(true) - jurisdictions Jurisdictions[] + id String @id() @default(dbgenerated("uuid_generate_v4()")) @db.Uuid + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6) + updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(6) + name String @unique() + description String + active Boolean @default(true) + jurisdictions Jurisdictions[] @@map("feature_flags") } @@ -572,6 +572,9 @@ model Listings { resultId String? @map("result_id") @db.Uuid featuresId String? @unique() @map("features_id") @db.Uuid utilitiesId String? @unique() @map("utilities_id") @db.Uuid + includeCommunityDisclaimer Boolean? @map("include_community_disclaimer") + communityDisclaimerTitle String? @map("community_disclaimer_title") + communityDisclaimerDescription String? @map("community_disclaimer_description") // START DETROIT SPECIFIC hrdId String? @map("hrd_id") ownerCompany String? @map("owner_company") @@ -621,7 +624,7 @@ model Listings { requestedChangesUser UserAccounts? @relation("requested_changes_user", fields: [requestedChangesUserId], references: [id], onDelete: NoAction, onUpdate: NoAction) applicationLotteryPositions ApplicationLotteryPositions[] applicationLotteryTotals ApplicationLotteryTotal[] - copyOf Listings? @relation("copy_of", fields: [copyOfId], references: [id]) + copyOf Listings? @relation("copy_of", fields: [copyOfId], references: [id], onUpdate: NoAction) Listings Listings[] @relation("copy_of") @@index([jurisdictionId]) diff --git a/api/src/dtos/listings/listing.dto.ts b/api/src/dtos/listings/listing.dto.ts index d4817ac293..2d47d13804 100644 --- a/api/src/dtos/listings/listing.dto.ts +++ b/api/src/dtos/listings/listing.dto.ts @@ -613,6 +613,21 @@ class Listing extends AbstractDTO { @Type(() => ApplicationLotteryTotal) @ApiProperty({ type: ApplicationLotteryTotal, isArray: true }) applicationLotteryTotals: ApplicationLotteryTotal[]; + + @Expose() + @ApiPropertyOptional() + @IsBoolean({ groups: [ValidationsGroupsEnum.default] }) + includeCommunityDisclaimer?: boolean; + + @Expose() + @ApiPropertyOptional() + @IsString({ groups: [ValidationsGroupsEnum.default] }) + communityDisclaimerTitle?: string; + + @Expose() + @ApiPropertyOptional() + @IsString({ groups: [ValidationsGroupsEnum.default] }) + communityDisclaimerDescription?: string; } export { Listing as default, Listing }; diff --git a/api/test/integration/listing.e2e-spec.ts b/api/test/integration/listing.e2e-spec.ts index 9fc0e9db3b..3a2fb04b88 100644 --- a/api/test/integration/listing.e2e-spec.ts +++ b/api/test/integration/listing.e2e-spec.ts @@ -344,6 +344,11 @@ 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, }; }; diff --git a/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts index daaa151478..e457baa33a 100644 --- a/shared-helpers/src/types/backend-swagger.ts +++ b/shared-helpers/src/types/backend-swagger.ts @@ -3653,6 +3653,15 @@ export interface Listing { /** */ applicationLotteryTotals: ApplicationLotteryTotal[] + + /** */ + includeCommunityDisclaimer?: boolean + + /** */ + communityDisclaimerTitle?: string + + /** */ + communityDisclaimerDescription?: string } export interface PaginationMeta { @@ -4115,6 +4124,15 @@ export interface ListingCreate { /** */ lotteryOptIn?: boolean + /** */ + includeCommunityDisclaimer?: boolean + + /** */ + communityDisclaimerTitle?: string + + /** */ + communityDisclaimerDescription?: string + /** */ listingMultiselectQuestions?: IdDTO[] @@ -4389,6 +4407,15 @@ export interface ListingUpdate { /** */ lotteryOptIn?: boolean + /** */ + includeCommunityDisclaimer?: boolean + + /** */ + communityDisclaimerTitle?: string + + /** */ + communityDisclaimerDescription?: string + /** */ listingMultiselectQuestions?: IdDTO[] @@ -6318,6 +6345,8 @@ export enum LanguagesEnum { "vi" = "vi", "zh" = "zh", "tl" = "tl", + "bn" = "bn", + "ar" = "ar", } export enum ListingEventsTypeEnum { From c7fdafa6d922d6dbdbcf78b4a0ec349f7a29ba00 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 2/3] 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 (cherry picked from commit 2769691424c96b8adac228ce89c29edaecefd222) --- 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 | 3 + .../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 + .../locale_overrides/general.json | 7 ++ .../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, 308 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 2d47d13804..cd2f92b991 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'; @@ -621,11 +622,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 3a2fb04b88..5bc526f920 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, }; }; 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 0ef2c555d0..b2b8b1a8de 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") @@ -136,6 +140,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() cy.getByID("addUnitsButton").contains("Add Unit").click() @@ -271,6 +278,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"]) cy.getByTestId("unit-types-or-individual").contains("Unit Types") cy.getByTestId("listing-availability-question").contains("Available Units") cy.getByID("unitTable").contains(listing["number"]) diff --git a/sites/partners/cypress/fixtures/listing.json b/sites/partners/cypress/fixtures/listing.json index 41a33fc8ef..5f5625f6a8 100644 --- a/sites/partners/cypress/fixtures/listing.json +++ b/sites/partners/cypress/fixtures/listing.json @@ -10,6 +10,9 @@ "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", "numBathrooms": "2", 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 && ( + <> + + + + + + + + +