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..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';
@@ -613,6 +614,29 @@ class Listing extends AbstractDTO {
@Type(() => ApplicationLotteryTotal)
@ApiProperty({ type: ApplicationLotteryTotal, isArray: true })
applicationLotteryTotals: ApplicationLotteryTotal[];
+
+ @Expose()
+ @ApiPropertyOptional()
+ @IsBoolean({ groups: [ValidationsGroupsEnum.default] })
+ includeCommunityDisclaimer?: boolean;
+
+ @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;
}
export { Listing as default, Listing };
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 9fc0e9db3b..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,6 +346,13 @@ describe('Listing Controller Tests', () => {
phone: false,
internet: true,
},
+ 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/shared-helpers/src/types/backend-swagger.ts b/shared-helpers/src/types/backend-swagger.ts
index daaa151478..548ddfa508 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[]
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 (