From 2964d7956adedd25a567176ca2a58c070bd0c5e3 Mon Sep 17 00:00:00 2001 From: Morgan Ludtke <42942267+ludtkemorgan@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:30:09 -0500 Subject: [PATCH] feat: release 2022-10-25 * feat: new listing type field (#3085) * 3032/new what to expect core (#3087) * feat: bring over What to Expect and update Markdown styles * feat: show dedicated content on the What to Expect form step * feat: add additional text to terms and confirmation * feat: update form conditionals to use waitlist enum * fix: remove stray import * fix: typos and improved switch statements * fix: confirmation text and remove lottery date * fix: add back in eligibility translations (#3134) * fix: correct the cron jobs * feat(expandablecontent): remove classname from ExpandableContent move styling of more/less button to parent component #3140 * Fix code style issues with Prettier * feat(expandabletext): add classname to stories add margin to instances of ExpandableText #3140 * fix: switch listing to server side props (#3145) * fix: update activity-log relationship to users for deletion (#3139) * Allow markup for descriptions and optional classname (#3131) * feat: display income currency value with commas * feat: accept only numeric values in currency field * feat: submit income as a string * feat: remove redundant step prop from currency field * feat: add thank you translations to email footer * fix: change the cron job logging level (#3150) * fix: what to expect missed changes * fix: email tests * chore(release): version - @bloom-housing/backend-core@7.0.1 - @bloom-housing/shared-helpers@7.0.1 - @bloom-housing/partners@7.0.1 - @bloom-housing/public@7.0.1 - @bloom-housing/ui-components@7.0.1 Co-authored-by: Jared White Co-authored-by: Emily Jablonski <65367387+emilyjablonski@users.noreply.github.com> Co-authored-by: Mark Buckner Co-authored-by: Lint Action Co-authored-by: christine-sfg <98352600+christine-sfg@users.noreply.github.com> Co-authored-by: krzysztof ziecina --- backend/core/CHANGELOG.md | 71 ++++++++ backend/core/package.json | 2 +- .../entities/activity-log.entity.ts | 2 +- ...n-flagged-sets-cronjob-boostrap.service.ts | 14 +- ...plication-flagged-sets-cronjob-consumer.ts | 6 +- .../application-flagged-sets.module.ts | 3 +- backend/core/src/email/email.service.spec.ts | 5 +- backend/core/src/email/email.service.ts | 39 +++-- .../dto/listing-published-create.dto.ts | 6 - .../dto/listing-published-update.dto.ts | 6 - .../src/listings/entities/listing.entity.ts | 11 -- .../src/listings/listings-cron.service.ts | 33 ++++ backend/core/src/listings/listings.module.ts | 5 +- backend/core/src/listings/listings.service.ts | 24 +-- .../types/listing-availability-enum.ts | 4 - .../types/listing-review-order-enum.ts | 1 + backend/core/src/listings/views/config.ts | 1 - ...106-new-confirmation-email-translations.ts | 40 +++++ .../1663959354563-new-listing-type-enum.ts | 61 +++++++ .../1664882024743-add-thank-you-to-email.ts | 50 ++++++ .../1665528174645-email-reset-translations.ts | 40 +++++ ...ctivity-log-user-relationship-on-delete.ts | 23 +++ .../seeds/listings/listing-coliseum-seed.ts | 2 - .../seeds/listings/listing-triton-seed.ts | 2 - .../core/src/seeder/seeds/listings/shared.ts | 2 - .../core/src/shared/units-transformations.ts | 4 +- .../core/src/shared/views/confirmation.hbs | 9 +- backend/core/types/src/backend-swagger.ts | 15 +- shared-helpers/CHANGELOG.md | 88 ++++++++++ shared-helpers/package.json | 6 +- shared-helpers/src/summaryTables.tsx | 12 +- sites/partners/CHANGELOG.md | 88 ++++++++++ .../sections/DetailUnits.test.tsx | 8 +- sites/partners/__tests__/testHelpers.ts | 2 - .../cypress/integration/03-listing.spec.ts | 1 - sites/partners/package.json | 8 +- .../sections/DetailRankingsAndResults.tsx | 41 +++-- .../sections/DetailUnits.tsx | 6 +- .../listings/PaperListingForm/formTypes.ts | 1 - .../formatters/AdditionalMetadataFormatter.ts | 9 +- .../sections/RankingsAndResults.tsx | 52 +++--- .../PaperListingForm/sections/Units.tsx | 7 +- sites/public/CHANGELOG.md | 107 ++++++++++++ sites/public/lib/helpers.tsx | 11 +- sites/public/package.json | 8 +- .../pages/applications/financial/income.tsx | 5 +- .../applications/review/confirmation.tsx | 39 ++--- .../pages/applications/review/terms.tsx | 58 +++++-- .../applications/start/what-to-expect.tsx | 66 ++++++- sites/public/pages/listings.tsx | 3 +- sites/public/src/ListingView.tsx | 16 +- ui-components/CHANGELOG.md | 57 ++++++ .../__tests__/text/Description.test.tsx | 17 +- ui-components/package.json | 2 +- .../src/actions/ExpandableText.stories.tsx | 18 +- ui-components/src/actions/ExpandableText.tsx | 2 +- ui-components/src/forms/Field.tsx | 33 +++- ui-components/src/global/markdown.scss | 162 +++++++++++++++--- ui-components/src/locales/general.json | 22 ++- .../src/sections/InfoCardGrid.stories.tsx | 2 +- ui-components/src/text/Description.tsx | 16 +- 61 files changed, 1169 insertions(+), 285 deletions(-) create mode 100644 backend/core/src/listings/listings-cron.service.ts delete mode 100644 backend/core/src/listings/types/listing-availability-enum.ts create mode 100644 backend/core/src/migration/1663104141106-new-confirmation-email-translations.ts create mode 100644 backend/core/src/migration/1663959354563-new-listing-type-enum.ts create mode 100644 backend/core/src/migration/1664882024743-add-thank-you-to-email.ts create mode 100644 backend/core/src/migration/1665528174645-email-reset-translations.ts create mode 100644 backend/core/src/migration/1665762602664-remove-activity-log-user-relationship-on-delete.ts diff --git a/backend/core/CHANGELOG.md b/backend/core/CHANGELOG.md index a724f288c2..54901efc5e 100644 --- a/backend/core/CHANGELOG.md +++ b/backend/core/CHANGELOG.md @@ -3,6 +3,77 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.0.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.5...@bloom-housing/backend-core@7.0.1) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/backend-core + + + + + +## [7.0.1-alpha.5](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.4...@bloom-housing/backend-core@7.0.1-alpha.5) (2022-10-26) + + +### Bug Fixes + +* change the cron job logging level ([#3150](https://github.com/bloom-housing/bloom/issues/3150)) ([fe43154](https://github.com/bloom-housing/bloom/commit/fe4315423ffd17ee22d16018cc938271592dfcf5)) + + + + + +## [7.0.1-alpha.4](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.3...@bloom-housing/backend-core@7.0.1-alpha.4) (2022-10-25) + + +### Features + +* add thank you translations to email footer ([5f3c7b1](https://github.com/bloom-housing/bloom/commit/5f3c7b1e81357e1aff5f3f3532dbb7691ac243ce)) + + + + + +## [7.0.1-alpha.3](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.2...@bloom-housing/backend-core@7.0.1-alpha.3) (2022-10-21) + + +### Bug Fixes + +* update activity-log relationship to users for deletion ([#3139](https://github.com/bloom-housing/bloom/issues/3139)) ([f95532e](https://github.com/bloom-housing/bloom/commit/f95532e4ebe693c128d742399030054d0f735164)) + + + + + +## [7.0.1-alpha.2](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.1...@bloom-housing/backend-core@7.0.1-alpha.2) (2022-10-19) + + +### Bug Fixes + +* correct the cron jobs ([407c0fb](https://github.com/bloom-housing/bloom/commit/407c0fbe10e3150047dadbb43c5248b768e8dc63)) + + + + + +## [7.0.1-alpha.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@7.0.1-alpha.0...@bloom-housing/backend-core@7.0.1-alpha.1) (2022-10-13) + + +### Bug Fixes + +* add back in eligibility translations ([#3134](https://github.com/bloom-housing/bloom/issues/3134)) ([a9ba463](https://github.com/bloom-housing/bloom/commit/a9ba463bfb414ff13820616f7045f43cc997dca2)) + + + + + +## [7.0.1-alpha.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@6.0.1-alpha.2...@bloom-housing/backend-core@7.0.1-alpha.0) (2022-10-05) + +**Note:** Version bump only for package @bloom-housing/backend-core + + + + + # [7.0.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/backend-core@5.1.1-alpha.19...@bloom-housing/backend-core@7.0.0) (2022-09-29) diff --git a/backend/core/package.json b/backend/core/package.json index 198e912707..fb52a52d30 100644 --- a/backend/core/package.json +++ b/backend/core/package.json @@ -1,6 +1,6 @@ { "name": "@bloom-housing/backend-core", - "version": "7.0.0", + "version": "7.0.1", "description": "Listings service reference implementation for the Bloom affordable housing system", "author": "Sean Albert ", "private": false, diff --git a/backend/core/src/activity-log/entities/activity-log.entity.ts b/backend/core/src/activity-log/entities/activity-log.entity.ts index 9fbb0632e1..878bfe1c0d 100644 --- a/backend/core/src/activity-log/entities/activity-log.entity.ts +++ b/backend/core/src/activity-log/entities/activity-log.entity.ts @@ -17,7 +17,7 @@ export class ActivityLog extends AbstractEntity { @Expose() action: string - @ManyToOne(() => User, { nullable: true }) + @ManyToOne(() => User, { nullable: true, onDelete: "SET NULL" }) @JoinColumn() @Expose() @Type(() => User) diff --git a/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-boostrap.service.ts b/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-boostrap.service.ts index 544803ba25..7ba42bf63a 100644 --- a/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-boostrap.service.ts +++ b/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-boostrap.service.ts @@ -1,5 +1,5 @@ import { InjectQueue } from "@nestjs/bull" -import { Injectable } from "@nestjs/common" +import { Inject, Injectable, Logger } from "@nestjs/common" import { Queue } from "bull" import { AFSProcessingQueueNames } from "./constants/applications-flagged-sets-constants" import { ConfigService } from "@nestjs/config" @@ -8,15 +8,17 @@ import { ConfigService } from "@nestjs/config" export class ApplicationFlaggedSetsCronjobBoostrapService { constructor( @InjectQueue(AFSProcessingQueueNames.afsProcessing) private afsProcessingQueue: Queue, - private readonly config: ConfigService + private readonly config: ConfigService, + @Inject(Logger) + private readonly logger = new Logger(ApplicationFlaggedSetsCronjobBoostrapService.name) ) { + const repeatCron = this.config.get("AFS_PROCESSING_CRON_STRING") + this.logger.warn(`Setting up AFS processing cron to frequency ${repeatCron}`) + void this.afsProcessingQueue.empty() void this.afsProcessingQueue.add(null, { repeat: { - cron: config.get("AFS_PROCESSING_CRON_STRING"), + cron: repeatCron, }, - // NOTE: This is not unique on purpose because Bull will not add a job twice with an ID - // which already exists. - id: "afs-process", }) } } diff --git a/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-consumer.ts b/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-consumer.ts index 4d56a13fdd..29d038bec5 100644 --- a/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-consumer.ts +++ b/backend/core/src/application-flagged-sets/application-flagged-sets-cronjob-consumer.ts @@ -9,6 +9,7 @@ import { Listing } from "../listings/entities/listing.entity" import { ApplicationFlaggedSet } from "./entities/application-flagged-set.entity" import { FlaggedSetStatus } from "./types/flagged-set-status-enum" import { getView } from "../applications/views/view" +import { Inject, Logger } from "@nestjs/common" @Processor(AFSProcessingQueueNames.afsProcessing) export class ApplicationFlaggedSetsCronjobConsumer { @@ -16,11 +17,13 @@ export class ApplicationFlaggedSetsCronjobConsumer { @InjectRepository(ListingRepository) private readonly listingRepository: ListingRepository, @InjectRepository(ApplicationFlaggedSet) private readonly afsRepository: Repository, - @InjectRepository(Application) private readonly applicationRepository: Repository + @InjectRepository(Application) private readonly applicationRepository: Repository, + @Inject(Logger) private readonly logger = new Logger(ApplicationFlaggedSetsCronjobConsumer.name) ) {} @Process({ concurrency: 1 }) async process() { + this.logger.warn("running the Application flagged sets cron job") const outOfDateListings = await this.listingRepository .createQueryBuilder("listings") .select(["listings.id", "listings.afsLastRunAt"]) @@ -34,6 +37,7 @@ export class ApplicationFlaggedSetsCronjobConsumer { ) .getMany() + this.logger.warn(`updating the flagged sets for ${outOfDateListings.length} listings`) for (const outOfDateListing of outOfDateListings) { try { await this.generateAFSesForListingRules(outOfDateListing) diff --git a/backend/core/src/application-flagged-sets/application-flagged-sets.module.ts b/backend/core/src/application-flagged-sets/application-flagged-sets.module.ts index 605ad6254f..23d9e72b8b 100644 --- a/backend/core/src/application-flagged-sets/application-flagged-sets.module.ts +++ b/backend/core/src/application-flagged-sets/application-flagged-sets.module.ts @@ -1,4 +1,4 @@ -import { Module } from "@nestjs/common" +import { Logger, Module } from "@nestjs/common" import { ApplicationFlaggedSetsController } from "./application-flagged-sets.controller" import { ApplicationFlaggedSetsService } from "./application-flagged-sets.service" import { TypeOrmModule } from "@nestjs/typeorm" @@ -40,6 +40,7 @@ import { ListingRepository } from "../listings/db/listing.repository" ApplicationFlaggedSetsService, ApplicationFlaggedSetsCronjobBoostrapService, ApplicationFlaggedSetsCronjobConsumer, + Logger, ], exports: [ ApplicationFlaggedSetsService, diff --git a/backend/core/src/email/email.service.spec.ts b/backend/core/src/email/email.service.spec.ts index 397c2a3554..3084f1f808 100644 --- a/backend/core/src/email/email.service.spec.ts +++ b/backend/core/src/email/email.service.spec.ts @@ -94,7 +94,6 @@ const translationServiceMock = { readHowYouCanPrepare: "Read about how you can prepare for next steps", needToMakeUpdates: "Need to make updates?", ifYouNeedToUpdateInformation: "", - shouldBeChosen: "Should your application be chosen, be prepared to fill out a more detailed application and provide required supporting documents.", subject: "Your Application Confirmation", @@ -259,7 +258,7 @@ describe("EmailService", () => { expect(emailMock.html).toMatch("Your Confirmation Number") expect(emailMock.html).toMatch("Marisela Baca") expect(emailMock.html).toMatch( - /If you are contacted for an interview, you will need to fill out a more detailed application and provide supporting documents./ + /Eligible applicants will be contacted on a first come first serve basis until vacancies are filled./ ) expect(emailMock.html).toMatch(/http:\/\/localhost:3000\/listing\/Uvbk5qurpB2WI9V6WnNdH/) // contains application id @@ -279,7 +278,7 @@ describe("EmailService", () => { const emailMock = sendMock.mock.calls[0][0] expect(emailMock.html).toMatch( - /The lottery will be held on December 31, 2019. Eligible applicants will be placed in order based on preference and lottery rank<\/strong>./ + /Once the application period closes, eligible applicants will be placed in order based on lottery rank order./ ) }) diff --git a/backend/core/src/email/email.service.ts b/backend/core/src/email/email.service.ts index f2ad2a1553..c2f8aed885 100644 --- a/backend/core/src/email/email.service.ts +++ b/backend/core/src/email/email.service.ts @@ -6,7 +6,6 @@ import Handlebars from "handlebars" import path from "path" import Polyglot from "node-polyglot" import fs from "fs" -import dayjs from "dayjs" import { ConfigService } from "@nestjs/config" import { TranslationsService } from "../translations/services/translations.service" import { JurisdictionResolverService } from "../jurisdictions/services/jurisdiction-resolver.service" @@ -133,7 +132,6 @@ export class EmailService { public async confirmation(listing: Listing, application: Application, appUrl: string) { const jurisdiction = await this.getListingJurisdiction(listing) void (await this.loadTranslations(jurisdiction, application.language || Language.en)) - let eligibleApplicantsText const listingUrl = `${appUrl}/listing/${listing.id}` const compiledTemplate = this.template("confirmation") @@ -143,29 +141,31 @@ export class EmailService { ) } + let eligibleText + let preferenceText + let contactText = null + if (listing.reviewOrderType === ListingReviewOrder.firstComeFirstServe) { + eligibleText = this.polyglot.t("confirmation.eligible.fcfs") + preferenceText = this.polyglot.t("confirmation.eligible.fcfsPreference") + } if (listing.reviewOrderType === ListingReviewOrder.lottery) { - const lotteryText = [] - if (listing.applicationDueDate) { - lotteryText.push( - this.polyglot.t("confirmation.eligibleApplicants.lotteryDate", { - lotteryDate: dayjs(listing.applicationDueDate).format("MMMM D, YYYY"), - }) - ) - } - lotteryText.push(this.polyglot.t("confirmation.eligibleApplicants.lottery")) - eligibleApplicantsText = new Handlebars.SafeString(lotteryText.join(" ")) - } else { - // for when listing.reviewOrderType === ListingReviewOrder.firstComeFirstServe - eligibleApplicantsText = new Handlebars.SafeString( - this.polyglot.t("confirmation.eligibleApplicants.FCFS") - ) + eligibleText = this.polyglot.t("confirmation.eligible.lottery") + preferenceText = this.polyglot.t("confirmation.eligible.lotteryPreference") } + if (listing.reviewOrderType === ListingReviewOrder.waitlist) { + eligibleText = this.polyglot.t("confirmation.eligible.waitlist") + contactText = this.polyglot.t("confirmation.eligible.waitlistContact") + preferenceText = this.polyglot.t("confirmation.eligible.waitlistPreference") + } + const user = { firstName: application.applicant.firstName, middleName: application.applicant.middleName, lastName: application.applicant.lastName, } + const nextStepsUrl = this.polyglot.t("confirmation.nextStepsUrl") + await this.send( application.applicant.emailAddress, jurisdiction.emailFromAddress, @@ -179,7 +179,10 @@ export class EmailService { listing, listingUrl, application, - eligibleApplicantsText, + preferenceText, + interviewText: this.polyglot.t("confirmation.interview"), + eligibleText, + contactText, nextStepsUrl: nextStepsUrl != "confirmation.nextStepsUrl" ? nextStepsUrl : null, user, }) diff --git a/backend/core/src/listings/dto/listing-published-create.dto.ts b/backend/core/src/listings/dto/listing-published-create.dto.ts index 6612e00797..e26e67fe66 100644 --- a/backend/core/src/listings/dto/listing-published-create.dto.ts +++ b/backend/core/src/listings/dto/listing-published-create.dto.ts @@ -20,7 +20,6 @@ import { OmitType } from "@nestjs/swagger" import { UnitCreateDto } from "../../units/dto/unit-create.dto" import { EnforceLowerCase } from "../../shared/decorators/enforceLowerCase.decorator" import { ListingImageUpdateDto } from "./listing-image-update.dto" -import { ListingAvailability } from "../types/listing-availability-enum" export class ListingPublishedCreateDto extends OmitType(ListingCreateDto, [ "assets", @@ -39,7 +38,6 @@ export class ListingPublishedCreateDto extends OmitType(ListingCreateDto, [ "rentalAssistance", "reviewOrderType", "units", - "listingAvailability", ] as const) { @Expose() @ValidateNested({ groups: [ValidationsGroupsEnum.default], each: true }) @@ -77,10 +75,6 @@ export class ListingPublishedCreateDto extends OmitType(ListingCreateDto, [ @Type(() => ListingImageUpdateDto) images: ListingImageUpdateDto[] - @Expose() - @IsEnum(ListingAvailability, { groups: [ValidationsGroupsEnum.default] }) - listingAvailability: ListingAvailability | null - @Expose() @IsEmail({}, { groups: [ValidationsGroupsEnum.default] }) @EnforceLowerCase() diff --git a/backend/core/src/listings/dto/listing-published-update.dto.ts b/backend/core/src/listings/dto/listing-published-update.dto.ts index 0f8430f8fa..768413a8c3 100644 --- a/backend/core/src/listings/dto/listing-published-update.dto.ts +++ b/backend/core/src/listings/dto/listing-published-update.dto.ts @@ -20,7 +20,6 @@ import { AssetUpdateDto } from "../../assets/dto/asset.dto" import { UnitUpdateDto } from "../../units/dto/unit-update.dto" import { EnforceLowerCase } from "../../shared/decorators/enforceLowerCase.decorator" import { ListingImageUpdateDto } from "./listing-image-update.dto" -import { ListingAvailability } from "../types/listing-availability-enum" export class ListingPublishedUpdateDto extends OmitType(ListingUpdateDto, [ "assets", @@ -39,7 +38,6 @@ export class ListingPublishedUpdateDto extends OmitType(ListingUpdateDto, [ "rentalAssistance", "reviewOrderType", "units", - "listingAvailability", ] as const) { @Expose() @ValidateNested({ groups: [ValidationsGroupsEnum.default], each: true }) @@ -77,10 +75,6 @@ export class ListingPublishedUpdateDto extends OmitType(ListingUpdateDto, [ @Type(() => ListingImageUpdateDto) images: ListingImageUpdateDto[] - @Expose() - @IsEnum(ListingAvailability, { groups: [ValidationsGroupsEnum.default] }) - listingAvailability: ListingAvailability | null - @Expose() @IsEmail({}, { groups: [ValidationsGroupsEnum.default] }) @EnforceLowerCase() diff --git a/backend/core/src/listings/entities/listing.entity.ts b/backend/core/src/listings/entities/listing.entity.ts index b707addeff..becf9b3e7d 100644 --- a/backend/core/src/listings/entities/listing.entity.ts +++ b/backend/core/src/listings/entities/listing.entity.ts @@ -43,7 +43,6 @@ import { ApplicationMethod } from "../../application-methods/entities/applicatio import { UnitsSummarized } from "../../units/types/units-summarized" import { UnitsSummary } from "../../units-summary/entities/units-summary.entity" import { ListingReviewOrder } from "../types/listing-review-order-enum" -import { ListingAvailability } from "../types/listing-availability-enum" import { ApplicationMethodDto } from "../../application-methods/dto/application-method.dto" import { ApplicationMethodType } from "../../application-methods/types/application-method-type-enum" import { ListingFeatures } from "./listing-features.entity" @@ -520,16 +519,6 @@ class Listing extends BaseEntity { }) reviewOrderType?: ListingReviewOrder | null - @Column({ type: "enum", enum: ListingAvailability, nullable: true }) - @Expose() - @IsOptional({ groups: [ValidationsGroupsEnum.default] }) - @IsEnum(ListingAvailability, { groups: [ValidationsGroupsEnum.default] }) - @ApiProperty({ - enum: ListingAvailability, - enumName: "ListingAvailability", - }) - listingAvailability?: ListingAvailability | null - @Expose() applicationConfig?: Record diff --git a/backend/core/src/listings/listings-cron.service.ts b/backend/core/src/listings/listings-cron.service.ts new file mode 100644 index 0000000000..5c3fe236f7 --- /dev/null +++ b/backend/core/src/listings/listings-cron.service.ts @@ -0,0 +1,33 @@ +import { Inject, Injectable, Logger } from "@nestjs/common" +import { InjectRepository } from "@nestjs/typeorm" +import { Interval } from "@nestjs/schedule" +import { ListingStatus } from "./types/listing-status-enum" +import { ListingRepository } from "./db/listing.repository" + +@Injectable() +export class ListingsCronService { + constructor( + @InjectRepository(ListingRepository) private readonly listingRepository: ListingRepository, + @Inject(Logger) private readonly logger = new Logger(ListingsCronService.name) + ) {} + + @Interval(1000 * 60 * 60) + public async changeOverdueListingsStatusCron() { + this.logger.warn("changeOverdueListingsStatusCron job running") + const listings = await this.listingRepository + .createQueryBuilder("listings") + .select(["listings.id", "listings.applicationDueDate", "listings.status"]) + .where(`listings.status = '${ListingStatus.active}'`) + .andWhere(`listings.applicationDueDate IS NOT NULL`) + .andWhere(`listings.applicationDueDate < NOW()`) + .getMany() + + for (const listing of listings) { + listing.status = ListingStatus.closed + listing.closedAt = new Date() + } + + await this.listingRepository.save(listings) + this.logger.warn(`Changed the status of ${listings?.length} listings`) + } +} diff --git a/backend/core/src/listings/listings.module.ts b/backend/core/src/listings/listings.module.ts index 8718f85824..cd2f8280ed 100644 --- a/backend/core/src/listings/listings.module.ts +++ b/backend/core/src/listings/listings.module.ts @@ -1,4 +1,4 @@ -import { Module } from "@nestjs/common" +import { Logger, Module } from "@nestjs/common" import { TypeOrmModule } from "@nestjs/typeorm" import { ListingsService } from "./listings.service" import { ListingsController } from "./listings.controller" @@ -14,6 +14,7 @@ import { ActivityLogModule } from "../activity-log/activity-log.module" import { ListingRepository } from "./db/listing.repository" import { ListingUtilities } from "./entities/listing-utilities.entity" import { ApplicationFlaggedSetsModule } from "../application-flagged-sets/application-flagged-sets.module" +import { ListingsCronService } from "./listings-cron.service" @Module({ imports: [ @@ -32,7 +33,7 @@ import { ApplicationFlaggedSetsModule } from "../application-flagged-sets/applic ActivityLogModule, ApplicationFlaggedSetsModule, ], - providers: [ListingsService], + providers: [ListingsService, ListingsCronService, Logger], exports: [ListingsService], controllers: [ListingsController], }) diff --git a/backend/core/src/listings/listings.service.ts b/backend/core/src/listings/listings.service.ts index 92824065ea..5d6332d3c3 100644 --- a/backend/core/src/listings/listings.service.ts +++ b/backend/core/src/listings/listings.service.ts @@ -6,7 +6,7 @@ import { Interval } from "@nestjs/schedule" import { Listing } from "./entities/listing.entity" import { getView } from "./views/view" import { summarizeUnits, summarizeUnitsByTypeAndRent } from "../shared/units-transformations" -import { Language, ListingAvailability } from "../../types" +import { Language, ListingReviewOrder } from "../../types" import { AmiChart } from "../ami-charts/entities/ami-chart.entity" import { ListingCreateDto } from "./dto/listing-create.dto" import { ListingUpdateDto } from "./dto/listing-update.dto" @@ -111,9 +111,7 @@ export class ListingsService { await this.authorizeUserActionForListingId(this.req.user, listing.id, authzActions.update) const availableUnits = - listingDto.listingAvailability === ListingAvailability.availableUnits - ? listingDto.units.length - : 0 + listingDto.reviewOrderType !== ListingReviewOrder.waitlist ? listingDto.units.length : 0 listingDto.units.forEach((unit) => { if (!unit.id) { delete unit.id @@ -166,24 +164,6 @@ export class ListingsService { return result } - @Interval(1000 * 60 * 60) - public async changeOverdueListingsStatusCron() { - const listings = await this.listingRepository - .createQueryBuilder("listings") - .select(["listings.id", "listings.applicationDueDate", "listings.status"]) - .where(`listings.status = '${ListingStatus.active}'`) - .andWhere(`listings.applicationDueDate IS NOT NULL`) - .andWhere(`listings.applicationDueDate < NOW()`) - .getMany() - - for (const listing of listings) { - listing.status = ListingStatus.closed - listing.closedAt = new Date() - } - - await this.listingRepository.save(listings) - } - private async addUnitsSummarized(listing: Listing) { if (Array.isArray(listing.units) && listing.units.length > 0) { const amiCharts = await this.amiChartsRepository.find({ diff --git a/backend/core/src/listings/types/listing-availability-enum.ts b/backend/core/src/listings/types/listing-availability-enum.ts deleted file mode 100644 index 8deb357947..0000000000 --- a/backend/core/src/listings/types/listing-availability-enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ListingAvailability { - availableUnits = "availableUnits", - openWaitlist = "openWaitlist", -} diff --git a/backend/core/src/listings/types/listing-review-order-enum.ts b/backend/core/src/listings/types/listing-review-order-enum.ts index 41c0b46279..b1cbbd181c 100644 --- a/backend/core/src/listings/types/listing-review-order-enum.ts +++ b/backend/core/src/listings/types/listing-review-order-enum.ts @@ -1,4 +1,5 @@ export enum ListingReviewOrder { lottery = "lottery", firstComeFirstServe = "firstComeFirstServe", + waitlist = "waitlist", } diff --git a/backend/core/src/listings/views/config.ts b/backend/core/src/listings/views/config.ts index d7422e28e1..bc9d4a3a7c 100644 --- a/backend/core/src/listings/views/config.ts +++ b/backend/core/src/listings/views/config.ts @@ -52,7 +52,6 @@ const views: Views = { "listingImagesImage.id", "listingImagesImage.fileId", "listingImagesImage.label", - "listings.listingAvailability", "utilities.id", "utilities.water", "utilities.gas", diff --git a/backend/core/src/migration/1663104141106-new-confirmation-email-translations.ts b/backend/core/src/migration/1663104141106-new-confirmation-email-translations.ts new file mode 100644 index 0000000000..74c6031cad --- /dev/null +++ b/backend/core/src/migration/1663104141106-new-confirmation-email-translations.ts @@ -0,0 +1,40 @@ +import { MigrationInterface, QueryRunner } from "typeorm" +import { Language } from "../shared/types/language-enum" + +export class newConfirmationEmailTranslations1663104141106 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + let generalTranslation = await queryRunner.query( + `SELECT translations FROM translations WHERE jurisdiction_id IS NULL AND language = ($1)`, + [Language.en] + ) + + generalTranslation = generalTranslation["0"]["translations"] + + generalTranslation.confirmation = { + ...generalTranslation.confirmation, + eligible: { + fcfs: + "Eligibile applicants will be contacted on a first come first serve basis until vacancies are filled.", + fcfsPreference: + "Housing preferences, if applicable, will affect first come first serve order.", + lottery: + "Once the application period closes, eligible applicants will be placed in order based on lottery rank order.", + lotteryPreference: "Housing preferences, if applicable, will affect lottery rank order.", + waitlist: + "Eligibile applicants will be placed on the waitlist on a first come first serve basis until waitlist spots are filled.", + waitlistPreference: "Housing preferences, if applicable, will affect waitlist order.", + waitlistContact: + "You may be contacted while on the waitlist to confirm that you wish to remain on the waitlist.", + }, + interview: + "If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.", + } + + await queryRunner.query( + `UPDATE "translations" SET translations = ($1) where jurisdiction_id IS NULL and language = ($2)`, + [generalTranslation, Language.en] + ) + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/backend/core/src/migration/1663959354563-new-listing-type-enum.ts b/backend/core/src/migration/1663959354563-new-listing-type-enum.ts new file mode 100644 index 0000000000..33255ec488 --- /dev/null +++ b/backend/core/src/migration/1663959354563-new-listing-type-enum.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class newListingTypeEnum1663959354563 implements MigrationInterface { + name = "newListingTypeEnum1663959354563" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "public"."listings_review_order_type_enum" RENAME TO "listings_review_order_type_enum_old"` + ) + await queryRunner.query( + `CREATE TYPE "public"."listings_review_order_type_enum" AS ENUM('lottery', 'firstComeFirstServe', 'waitlist')` + ) + await queryRunner.query( + `ALTER TABLE "listings" ALTER COLUMN "review_order_type" TYPE "public"."listings_review_order_type_enum" USING "review_order_type"::"text"::"public"."listings_review_order_type_enum"` + ) + await queryRunner.query(`DROP TYPE "public"."listings_review_order_type_enum_old"`) + + const listings = await queryRunner.query(`SELECT id, listing_availability FROM listings`) + + for (const l of listings) { + if (l.listing_availability === "openWaitlist") { + await queryRunner.query(`UPDATE listings SET review_order_type = ($1) WHERE id = ($2)`, [ + "waitlist", + l.id, + ]) + } + } + await queryRunner.query(`ALTER TABLE "listings" DROP COLUMN "listing_availability"`) + await queryRunner.query(`DROP TYPE "public"."listings_listing_availability_enum"`) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "listings" ADD "listing_availability" "public"."listings_listing_availability_enum"` + ) + await queryRunner.query( + `CREATE TYPE "public"."listings_listing_availability_enum" AS ENUM('availableUnits', 'openWaitlist')` + ) + const listings = await queryRunner.query(`SELECT id, review_order_type FROM listings`) + + for (const l of listings) { + if (l.review_order_type === "waitlist") { + await queryRunner.query(`UPDATE listings SET listing_availability = ($1) WHERE id = ($2)`, [ + "openWaitlist", + l.id, + ]) + } + } + + await queryRunner.query( + `CREATE TYPE "public"."listings_review_order_type_enum_old" AS ENUM('lottery', 'firstComeFirstServe')` + ) + await queryRunner.query( + `ALTER TABLE "listings" ALTER COLUMN "review_order_type" TYPE "public"."listings_review_order_type_enum_old" USING "review_order_type"::"text"::"public"."listings_review_order_type_enum_old"` + ) + await queryRunner.query(`DROP TYPE "public"."listings_review_order_type_enum"`) + await queryRunner.query( + `ALTER TYPE "public"."listings_review_order_type_enum_old" RENAME TO "listings_review_order_type_enum"` + ) + } +} diff --git a/backend/core/src/migration/1664882024743-add-thank-you-to-email.ts b/backend/core/src/migration/1664882024743-add-thank-you-to-email.ts new file mode 100644 index 0000000000..7fc7ab17e0 --- /dev/null +++ b/backend/core/src/migration/1664882024743-add-thank-you-to-email.ts @@ -0,0 +1,50 @@ +import { MigrationInterface, QueryRunner } from "typeorm" +import { Language } from "../shared/types/language-enum" + +export class addThankYouToEmail1664882024743 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + const [{ id: alamedaJurisdiction }] = await queryRunner.query( + `SELECT id FROM jurisdictions WHERE name = 'Alameda' LIMIT 1` + ) + + const [{ id: sanMateoJurisdiction }] = await queryRunner.query( + `SELECT id FROM jurisdictions WHERE name = 'San Mateo' LIMIT 1` + ) + + let generalTranslation = await queryRunner.query( + `SELECT translations FROM translations WHERE jurisdiction_id IS NULL AND language = ($1)`, + [Language.en] + ) + generalTranslation = generalTranslation["0"]["translations"] + generalTranslation.footer.thankYou = "Thank you" + + let existingAlamedaTranslations = await queryRunner.query( + `SELECT translations FROM translations WHERE jurisdiction_id = ($1)`, + [alamedaJurisdiction] + ) + existingAlamedaTranslations = existingAlamedaTranslations["0"]["translations"] + existingAlamedaTranslations.footer.thankYou = "Thank you" + + let existingSanMateoTranslations = await queryRunner.query( + `SELECT translations FROM translations WHERE jurisdiction_id = ($1)`, + [sanMateoJurisdiction] + ) + existingSanMateoTranslations = existingSanMateoTranslations["0"]["translations"] + existingSanMateoTranslations.footer.thankYou = "Thank you" + + await queryRunner.query( + `UPDATE "translations" SET translations = ($1) where jurisdiction_id IS NULL and language = ($2)`, + [generalTranslation, Language.en] + ) + await queryRunner.query( + `UPDATE "translations" SET translations = ($1) where jurisdiction_id = ($2) and language = ($3)`, + [existingAlamedaTranslations, alamedaJurisdiction, Language.en] + ) + await queryRunner.query( + `UPDATE "translations" SET translations = ($1) where jurisdiction_id = ($2) and language = ($3)`, + [existingSanMateoTranslations, sanMateoJurisdiction, Language.en] + ) + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/backend/core/src/migration/1665528174645-email-reset-translations.ts b/backend/core/src/migration/1665528174645-email-reset-translations.ts new file mode 100644 index 0000000000..8e204129b2 --- /dev/null +++ b/backend/core/src/migration/1665528174645-email-reset-translations.ts @@ -0,0 +1,40 @@ +import { MigrationInterface, QueryRunner } from "typeorm" +import { Language } from "../shared/types/language-enum" + +export class emailResetTranslations1665528174645 implements MigrationInterface { + name = "emailResetTranslations1665528174645" + + public async up(queryRunner: QueryRunner): Promise { + let generalTranslation = await queryRunner.query( + `SELECT translations FROM translations WHERE jurisdiction_id IS NULL AND language = ($1)`, + [Language.en] + ) + + generalTranslation = generalTranslation["0"]["translations"] + + generalTranslation.confirmation = { + ...generalTranslation.confirmation, + eligible: { + fcfs: + "Eligible applicants will be contacted on a first come first serve basis until vacancies are filled.", + waitlist: + "Eligible applicants will be placed on the waitlist on a first come first serve basis until waitlist spots are filled.", + lottery: + "Once the application period closes, eligible applicants will be placed in order based on lottery rank order.", + fcfsPreference: + "Housing preferences, if applicable, will affect first come first serve order.", + waitlistContact: + "You may be contacted while on the waitlist to confirm that you wish to remain on the waitlist.", + lotteryPreference: "Housing preferences, if applicable, will affect lottery rank order.", + waitlistPreference: "Housing preferences, if applicable, will affect waitlist order.", + }, + } + + await queryRunner.query( + `UPDATE "translations" SET translations = ($1) where jurisdiction_id IS NULL and language = ($2)`, + [generalTranslation, Language.en] + ) + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/backend/core/src/migration/1665762602664-remove-activity-log-user-relationship-on-delete.ts b/backend/core/src/migration/1665762602664-remove-activity-log-user-relationship-on-delete.ts new file mode 100644 index 0000000000..dead42ed0c --- /dev/null +++ b/backend/core/src/migration/1665762602664-remove-activity-log-user-relationship-on-delete.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from "typeorm" + +export class removeActivityLogUserRelationshipOnDelete1665762602664 implements MigrationInterface { + name = "removeActivityLogUserRelationshipOnDelete1665762602664" + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "activity_logs" DROP CONSTRAINT "FK_d54f841fa5478e4734590d44036"` + ) + await queryRunner.query( + `ALTER TABLE "activity_logs" ADD CONSTRAINT "FK_d54f841fa5478e4734590d44036" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("id") ON DELETE SET NULL ON UPDATE NO ACTION` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "activity_logs" DROP CONSTRAINT "FK_d54f841fa5478e4734590d44036"` + ) + await queryRunner.query( + `ALTER TABLE "activity_logs" ADD CONSTRAINT "FK_d54f841fa5478e4734590d44036" FOREIGN KEY ("user_id") REFERENCES "user_accounts"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } +} diff --git a/backend/core/src/seeder/seeds/listings/listing-coliseum-seed.ts b/backend/core/src/seeder/seeds/listings/listing-coliseum-seed.ts index d374ebd90f..0b72427e62 100644 --- a/backend/core/src/seeder/seeds/listings/listing-coliseum-seed.ts +++ b/backend/core/src/seeder/seeds/listings/listing-coliseum-seed.ts @@ -17,7 +17,6 @@ import { ListingReviewOrder } from "../../../listings/types/listing-review-order import { ListingStatus } from "../../../listings/types/listing-status-enum" import { UnitCreateDto } from "../../../units/dto/unit-create.dto" import { Listing } from "../../../listings/entities/listing.entity" -import { ListingAvailability } from "../../../listings/types/listing-availability-enum" const coliseumListing: ListingSeedType = { jurisdictionName: "Alameda", @@ -107,7 +106,6 @@ const coliseumListing: ListingSeedType = { waitlistOpenSpots: 3000, isWaitlistOpen: true, whatToExpect: null, - listingAvailability: ListingAvailability.availableUnits, utilities: { water: false, gas: false, diff --git a/backend/core/src/seeder/seeds/listings/listing-triton-seed.ts b/backend/core/src/seeder/seeds/listings/listing-triton-seed.ts index a81f023fb4..22daef85b9 100644 --- a/backend/core/src/seeder/seeds/listings/listing-triton-seed.ts +++ b/backend/core/src/seeder/seeds/listings/listing-triton-seed.ts @@ -7,7 +7,6 @@ import { ListingReviewOrder } from "../../../listings/types/listing-review-order import { ListingStatus } from "../../../listings/types/listing-status-enum" import { UnitCreateDto } from "../../../units/dto/unit-create.dto" import { Listing } from "../../../listings/entities/listing.entity" -import { ListingAvailability } from "../../../listings/types/listing-availability-enum" import { classToClass } from "class-transformer" import dayjs from "dayjs" @@ -96,7 +95,6 @@ const tritonListing: ListingSeedType = { waitlistOpenSpots: 200, isWaitlistOpen: true, whatToExpect: null, - listingAvailability: ListingAvailability.availableUnits, utilities: { water: true, gas: true, diff --git a/backend/core/src/seeder/seeds/listings/shared.ts b/backend/core/src/seeder/seeds/listings/shared.ts index 23f5c464b0..fa9f37e85e 100644 --- a/backend/core/src/seeder/seeds/listings/shared.ts +++ b/backend/core/src/seeder/seeds/listings/shared.ts @@ -13,7 +13,6 @@ import { UserCreateDto } from "../../../auth/dto/user-create.dto" import { CountyCode } from "../../../shared/types/county-code" import { ListingReviewOrder } from "../../../listings/types/listing-review-order-enum" import { ListingStatus } from "../../../listings/types/listing-status-enum" -import { ListingAvailability } from "../../../listings/types/listing-availability-enum" import { ApplicationSection } from "../../../multiselect-question/types/multiselect-application-section-enum" export const getDate = (days: number) => { const someDate = new Date() @@ -220,7 +219,6 @@ export const defaultListing: ListingSeedType = { isWaitlistOpen: false, waitlistMaxSize: null, whatToExpect: "Custom what to expect text", - listingAvailability: ListingAvailability.availableUnits, } // Preferences diff --git a/backend/core/src/shared/units-transformations.ts b/backend/core/src/shared/units-transformations.ts index 53b088f5fd..b0fdd7a5e8 100644 --- a/backend/core/src/shared/units-transformations.ts +++ b/backend/core/src/shared/units-transformations.ts @@ -10,7 +10,7 @@ import { AmiChart } from "../ami-charts/entities/ami-chart.entity" import { AmiChartItem } from "../ami-charts/entities/ami-chart-item.entity" import { UnitAmiChartOverride } from "../units/entities/unit-ami-chart-override.entity" import { Listing } from "../listings/entities/listing.entity" -import { ListingAvailability } from "../listings/types/listing-availability-enum" +import { ListingReviewOrder } from "../listings/types/listing-review-order-enum" export type AnyDict = { [key: string]: unknown } type Units = Unit[] @@ -335,7 +335,7 @@ export const summarizeUnitsByTypeAndRent = (units: Units, listing: Listing): Uni const finalSummary = unitMap[key].reduce((summary, unit, index) => { return getUnitsSummary(unit, index === 0 ? null : summary) }, {} as UnitSummary) - if (listing.listingAvailability === ListingAvailability.availableUnits) { + if (listing.reviewOrderType !== ListingReviewOrder.waitlist) { finalSummary.totalAvailable = unitMap[key].length } summaries.push(finalSummary) diff --git a/backend/core/src/shared/views/confirmation.hbs b/backend/core/src/shared/views/confirmation.hbs index 7b6ec0bfcf..fb779769de 100644 --- a/backend/core/src/shared/views/confirmation.hbs +++ b/backend/core/src/shared/views/confirmation.hbs @@ -29,9 +29,12 @@

{{t "confirmation.whatHappensNext"}}

    -
  • {{t "confirmation.applicationPeriodCloses"}}
  • -
  • {{eligibleApplicantsText}}
  • -
  • {{t "confirmation.contactedForAnInterview"}}
  • +
  • {{eligibleText}}
  • +
  • {{preferenceText}}
  • +
  • {{interviewText}}
  • + {{#if contactText}} +
  • {{contactText}}
  • + {{/if}}
{{#if nextStepsUrl}} diff --git a/backend/core/types/src/backend-swagger.ts b/backend/core/types/src/backend-swagger.ts index 59e244abe6..fcca925606 100644 --- a/backend/core/types/src/backend-swagger.ts +++ b/backend/core/types/src/backend-swagger.ts @@ -4820,9 +4820,6 @@ export interface Listing { /** */ reviewOrderType?: ListingReviewOrder - /** */ - listingAvailability?: ListingAvailability - /** */ showWaitlist: boolean @@ -5259,9 +5256,6 @@ export interface ListingCreate { /** */ reviewOrderType?: ListingReviewOrder - /** */ - listingAvailability?: ListingAvailability - /** */ applicationMethods: ApplicationMethodCreate[] @@ -5659,9 +5653,6 @@ export interface ListingUpdate { /** */ reviewOrderType?: ListingReviewOrder - /** */ - listingAvailability?: ListingAvailability - /** */ id?: string @@ -6238,11 +6229,7 @@ export enum ListingStatus { export enum ListingReviewOrder { "lottery" = "lottery", "firstComeFirstServe" = "firstComeFirstServe", -} - -export enum ListingAvailability { - "availableUnits" = "availableUnits", - "openWaitlist" = "openWaitlist", + "waitlist" = "waitlist", } export enum ListingEventType { diff --git a/shared-helpers/CHANGELOG.md b/shared-helpers/CHANGELOG.md index 310b90d72a..5f93f5b1a4 100644 --- a/shared-helpers/CHANGELOG.md +++ b/shared-helpers/CHANGELOG.md @@ -3,6 +3,94 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.0.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.9...@bloom-housing/shared-helpers@7.0.1) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.9](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.8...@bloom-housing/shared-helpers@7.0.1-alpha.9) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.8](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.7...@bloom-housing/shared-helpers@7.0.1-alpha.8) (2022-10-25) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.7](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.6...@bloom-housing/shared-helpers@7.0.1-alpha.7) (2022-10-25) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.6](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.5...@bloom-housing/shared-helpers@7.0.1-alpha.6) (2022-10-24) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.5](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.4...@bloom-housing/shared-helpers@7.0.1-alpha.5) (2022-10-21) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.4](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.3...@bloom-housing/shared-helpers@7.0.1-alpha.4) (2022-10-20) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.3](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.2...@bloom-housing/shared-helpers@7.0.1-alpha.3) (2022-10-19) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.2](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.1...@bloom-housing/shared-helpers@7.0.1-alpha.2) (2022-10-13) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@7.0.1-alpha.0...@bloom-housing/shared-helpers@7.0.1-alpha.1) (2022-10-12) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + +## [7.0.1-alpha.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@6.0.1-alpha.4...@bloom-housing/shared-helpers@7.0.1-alpha.0) (2022-10-05) + +**Note:** Version bump only for package @bloom-housing/shared-helpers + + + + + # [7.0.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/shared-helpers@5.1.1-alpha.37...@bloom-housing/shared-helpers@7.0.0) (2022-09-29) diff --git a/shared-helpers/package.json b/shared-helpers/package.json index 384e78e57a..ea90022cea 100644 --- a/shared-helpers/package.json +++ b/shared-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@bloom-housing/shared-helpers", - "version": "7.0.0", + "version": "7.0.1", "description": "Shared helpers for Bloom affordable housing system", "homepage": "https://github.com/bloom-housing/bloom/tree/master/shared-helpers", "main": "index.js", @@ -16,8 +16,8 @@ "prettier": "prettier --write \"**/*.ts\"" }, "dependencies": { - "@bloom-housing/backend-core": "^7.0.0", - "@bloom-housing/ui-components": "^7.0.0" + "@bloom-housing/backend-core": "^7.0.1", + "@bloom-housing/ui-components": "^7.0.1" }, "devDependencies": { "@bloom-housing/ui-components": "^3.0.1-alpha.15", diff --git a/shared-helpers/src/summaryTables.tsx b/shared-helpers/src/summaryTables.tsx index d0086d2986..1af2728576 100644 --- a/shared-helpers/src/summaryTables.tsx +++ b/shared-helpers/src/summaryTables.tsx @@ -7,7 +7,7 @@ import { ContentAccordion, getTranslationWithArguments, } from "@bloom-housing/ui-components" -import { MinMax, UnitSummary, Unit, ListingAvailability } from "@bloom-housing/backend-core/types" +import { MinMax, UnitSummary, Unit, ListingReviewOrder } from "@bloom-housing/backend-core/types" const getTranslationFromCurrencyString = (value: string) => { if (value.startsWith("t.")) return getTranslationWithArguments(value) @@ -16,7 +16,7 @@ const getTranslationFromCurrencyString = (value: string) => { export const unitSummariesTable = ( summaries: UnitSummary[], - listingAvailability: ListingAvailability + listingReviewOrder: ListingReviewOrder ): StandardTableData => { const unitSummaries = summaries?.map((unitSummary) => { const unitPluralization = @@ -63,7 +63,7 @@ export const unitSummariesTable = ( : getRent(unitSummary.rentRange.min, unitSummary.rentRange.max) let availability = null - if (listingAvailability === ListingAvailability.availableUnits) { + if (listingReviewOrder !== ListingReviewOrder.waitlist) { availability = ( {unitSummary.totalAvailable > 0 ? ( @@ -77,7 +77,7 @@ export const unitSummariesTable = ( )} ) - } else if (listingAvailability === ListingAvailability.openWaitlist) { + } else if (listingReviewOrder === ListingReviewOrder.waitlist) { availability = ( {t("listings.waitlist.open")} @@ -104,12 +104,12 @@ export const unitSummariesTable = ( export const getSummariesTable = ( summaries: UnitSummary[], - listingAvailability: ListingAvailability + listingReviewOrder: ListingReviewOrder ): StandardTableData => { let unitSummaries: StandardTableData = [] if (summaries?.length > 0) { - unitSummaries = unitSummariesTable(summaries, listingAvailability) + unitSummaries = unitSummariesTable(summaries, listingReviewOrder) } return unitSummaries } diff --git a/sites/partners/CHANGELOG.md b/sites/partners/CHANGELOG.md index a95c4093e8..1cfd1f36c1 100644 --- a/sites/partners/CHANGELOG.md +++ b/sites/partners/CHANGELOG.md @@ -3,6 +3,94 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.0.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.9...@bloom-housing/partners@7.0.1) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.9](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.8...@bloom-housing/partners@7.0.1-alpha.9) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.8](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.7...@bloom-housing/partners@7.0.1-alpha.8) (2022-10-25) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.7](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.6...@bloom-housing/partners@7.0.1-alpha.7) (2022-10-25) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.6](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.5...@bloom-housing/partners@7.0.1-alpha.6) (2022-10-24) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.5](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.4...@bloom-housing/partners@7.0.1-alpha.5) (2022-10-21) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.4](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.3...@bloom-housing/partners@7.0.1-alpha.4) (2022-10-20) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.3](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.2...@bloom-housing/partners@7.0.1-alpha.3) (2022-10-19) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.2](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.1...@bloom-housing/partners@7.0.1-alpha.2) (2022-10-13) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@7.0.1-alpha.0...@bloom-housing/partners@7.0.1-alpha.1) (2022-10-12) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + +## [7.0.1-alpha.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@6.0.1-alpha.5...@bloom-housing/partners@7.0.1-alpha.0) (2022-10-05) + +**Note:** Version bump only for package @bloom-housing/partners + + + + + # [7.0.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/partners@5.1.1-alpha.39...@bloom-housing/partners@7.0.0) (2022-09-29) diff --git a/sites/partners/__tests__/PaperListingForm/sections/DetailUnits.test.tsx b/sites/partners/__tests__/PaperListingForm/sections/DetailUnits.test.tsx index f8b85b1a82..8a75605b0d 100644 --- a/sites/partners/__tests__/PaperListingForm/sections/DetailUnits.test.tsx +++ b/sites/partners/__tests__/PaperListingForm/sections/DetailUnits.test.tsx @@ -3,12 +3,14 @@ import { fireEvent, render, within } from "@testing-library/react" import { DetailUnits } from "../../../src/listings/PaperListingDetails/sections/DetailUnits" import { ListingContext } from "../../../src/listings/ListingContext" import { listing, unit } from "../../testHelpers" -import { ListingAvailability } from "@bloom-housing/backend-core" +import { ListingReviewOrder } from "@bloom-housing/backend-core" describe("DetailUnits", () => { it("should render the detail units when no units exist", () => { const results = render( - + ) @@ -32,7 +34,7 @@ describe("DetailUnits", () => { diff --git a/sites/partners/__tests__/testHelpers.ts b/sites/partners/__tests__/testHelpers.ts index 3da65c5184..6bce34e86a 100644 --- a/sites/partners/__tests__/testHelpers.ts +++ b/sites/partners/__tests__/testHelpers.ts @@ -1,7 +1,6 @@ import { ApplicationSection, Listing, - ListingAvailability, ListingReviewOrder, ListingStatus, MultiselectQuestion, @@ -98,7 +97,6 @@ export const unit: Unit = { export const listing: Listing = { id: "Uvbk5qurpB2WI9V6WnNdH", - listingAvailability: ListingAvailability.openWaitlist, applicationConfig: undefined, applicationOpenDate: new Date("2019-12-31T15:22:57.000-07:00"), applicationPickUpAddress: undefined, diff --git a/sites/partners/cypress/integration/03-listing.spec.ts b/sites/partners/cypress/integration/03-listing.spec.ts index 6f9ca8bd07..927723802b 100644 --- a/sites/partners/cypress/integration/03-listing.spec.ts +++ b/sites/partners/cypress/integration/03-listing.spec.ts @@ -198,7 +198,6 @@ describe("Listing Management Tests", () => { cy.get("#specialNotes").contains(listing["specialNotes"]) cy.get("#reviewOrderQuestion").contains("First come first serve") cy.get("#dueDateQuestion").contains("No") - cy.getByID("waitlist.openQuestion").contains("No") cy.get("#whatToExpect").contains( "Applicants will be contacted by the property agent in rank order until vacancies are filled. All of the information that you have provided will be verified and your eligibility confirmed. Your application will be removed from the waitlist if you have made any fraudulent statements. If we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized. Should your application be chosen, be prepared to fill out a more detailed application and provide required supporting documents." ) diff --git a/sites/partners/package.json b/sites/partners/package.json index 3b30b8119e..fd47dd23a7 100644 --- a/sites/partners/package.json +++ b/sites/partners/package.json @@ -1,6 +1,6 @@ { "name": "@bloom-housing/partners", - "version": "7.0.0", + "version": "7.0.1", "author": "Sean Albert ", "description": "Partners app reference implementation for the Bloom affordable housing system", "main": "index.js", @@ -26,9 +26,9 @@ "dev:server-wait-cypress": "wait-on \"http-get://localhost:${PORT:-3100}/listings\" --httpTimeout 60000 --tcpTimeout 1500 -v --interval 15000 && yarn build && yarn start" }, "dependencies": { - "@bloom-housing/backend-core": "^7.0.0", - "@bloom-housing/shared-helpers": "^7.0.0", - "@bloom-housing/ui-components": "^7.0.0", + "@bloom-housing/backend-core": "^7.0.1", + "@bloom-housing/shared-helpers": "^7.0.1", + "@bloom-housing/ui-components": "^7.0.1", "@mapbox/mapbox-sdk": "^0.13.0", "ag-grid-community": "^26.0.0", "ag-grid-react": "^26.0.0", diff --git a/sites/partners/src/listings/PaperListingDetails/sections/DetailRankingsAndResults.tsx b/sites/partners/src/listings/PaperListingDetails/sections/DetailRankingsAndResults.tsx index af95545876..e7616e4c77 100644 --- a/sites/partners/src/listings/PaperListingDetails/sections/DetailRankingsAndResults.tsx +++ b/sites/partners/src/listings/PaperListingDetails/sections/DetailRankingsAndResults.tsx @@ -26,13 +26,15 @@ const DetailRankingsAndResults = () => { grid={false} inset > - - - {getReviewOrderType() === ListingReviewOrder.firstComeFirstServe - ? t("listings.firstComeFirstServe") - : t("listings.lotteryTitle")} - - + {listing.reviewOrderType !== ListingReviewOrder.waitlist && ( + + + {getReviewOrderType() === ListingReviewOrder.firstComeFirstServe + ? t("listings.firstComeFirstServe") + : t("listings.lotteryTitle")} + + + )} {lotteryEvent && ( <> @@ -60,18 +62,21 @@ const DetailRankingsAndResults = () => { )} - - - {getDetailBoolean(listing.isWaitlistOpen)} - - - {listing.isWaitlistOpen && ( - - - {getDetailFieldNumber(listing.waitlistOpenSpots)} - - + {listing.reviewOrderType === ListingReviewOrder.waitlist && ( + <> + + + {getDetailBoolean(listing.isWaitlistOpen)} + + + + + {getDetailFieldNumber(listing.waitlistOpenSpots)} + + + )} + diff --git a/sites/partners/src/listings/PaperListingDetails/sections/DetailUnits.tsx b/sites/partners/src/listings/PaperListingDetails/sections/DetailUnits.tsx index b74473da5e..8f019bcae9 100644 --- a/sites/partners/src/listings/PaperListingDetails/sections/DetailUnits.tsx +++ b/sites/partners/src/listings/PaperListingDetails/sections/DetailUnits.tsx @@ -9,7 +9,7 @@ import { } from "@bloom-housing/ui-components" import { ListingContext } from "../../ListingContext" import { UnitDrawer } from "../DetailsUnitDrawer" -import { ListingAvailability } from "@bloom-housing/backend-core" +import { ListingReviewOrder } from "@bloom-housing/backend-core" type DetailUnitsProps = { setUnitDrawer: (unit: UnitDrawer) => void @@ -54,9 +54,9 @@ const DetailUnits = ({ setUnitDrawer }: DetailUnitsProps) => { ) const listingAvailabilityText = useMemo(() => { - if (listing.listingAvailability === ListingAvailability.availableUnits) { + if (listing.reviewOrderType !== ListingReviewOrder.waitlist) { return t("listings.availableUnits") - } else if (listing.listingAvailability === ListingAvailability.openWaitlist) { + } else if (listing.reviewOrderType === ListingReviewOrder.waitlist) { return t("listings.waitlist.open") } return t("t.none") diff --git a/sites/partners/src/listings/PaperListingForm/formTypes.ts b/sites/partners/src/listings/PaperListingForm/formTypes.ts index 11c0e16f70..b586617d3d 100644 --- a/sites/partners/src/listings/PaperListingForm/formTypes.ts +++ b/sites/partners/src/listings/PaperListingForm/formTypes.ts @@ -158,7 +158,6 @@ export const formDefaults: FormListing = { urlSlug: undefined, showWaitlist: false, reviewOrderType: null, - listingAvailability: null, unitsSummary: [], unitsSummarized: { unitTypes: [], diff --git a/sites/partners/src/listings/PaperListingForm/formatters/AdditionalMetadataFormatter.ts b/sites/partners/src/listings/PaperListingForm/formatters/AdditionalMetadataFormatter.ts index c9ca7b8ef7..e8259f6c53 100644 --- a/sites/partners/src/listings/PaperListingForm/formatters/AdditionalMetadataFormatter.ts +++ b/sites/partners/src/listings/PaperListingForm/formatters/AdditionalMetadataFormatter.ts @@ -1,4 +1,4 @@ -import { ListingReviewOrder, ListingAvailability } from "@bloom-housing/backend-core/types" +import { ListingReviewOrder } from "@bloom-housing/backend-core/types" import { listingFeatures, listingUtilities } from "@bloom-housing/shared-helpers" import Formatter from "./Formatter" @@ -50,11 +50,10 @@ export default class AdditionalMetadataFormatter extends Formatter { ? ListingReviewOrder.lottery : ListingReviewOrder.firstComeFirstServe - if (this.data.listingAvailabilityQuestion === "availableUnits") { - this.data.listingAvailability = ListingAvailability.availableUnits - } else if (this.data.listingAvailabilityQuestion === "openWaitlist") { - this.data.listingAvailability = ListingAvailability.openWaitlist + if (this.data.listingAvailabilityQuestion === "openWaitlist") { + this.data.reviewOrderType = ListingReviewOrder.waitlist } + this.data.features = listingFeatures.reduce((acc, current) => { return { ...acc, diff --git a/sites/partners/src/listings/PaperListingForm/sections/RankingsAndResults.tsx b/sites/partners/src/listings/PaperListingForm/sections/RankingsAndResults.tsx index c7d0663695..6a9e74b7cc 100644 --- a/sites/partners/src/listings/PaperListingForm/sections/RankingsAndResults.tsx +++ b/sites/partners/src/listings/PaperListingForm/sections/RankingsAndResults.tsx @@ -72,31 +72,33 @@ const RankingsAndResults = ({ listing }: RankingsAndResultsProps) => { title={t("listings.sections.rankingsResultsTitle")} description={t("listings.sections.rankingsResultsSubtitle")} > - - -

{t("listings.reviewOrderQuestion")}

- -
-
+ {availabilityQuestion !== "openWaitlist" && ( + + +

{t("listings.reviewOrderQuestion")}

+ +
+
+ )} {reviewOrder === "reviewOrderFCFS" && ( diff --git a/sites/partners/src/listings/PaperListingForm/sections/Units.tsx b/sites/partners/src/listings/PaperListingForm/sections/Units.tsx index 59fb17c53f..121cd1a37f 100644 --- a/sites/partners/src/listings/PaperListingForm/sections/Units.tsx +++ b/sites/partners/src/listings/PaperListingForm/sections/Units.tsx @@ -18,7 +18,7 @@ import UnitForm from "../UnitForm" import { useFormContext, useWatch } from "react-hook-form" import { TempUnit } from "../formTypes" import { fieldHasError, fieldMessage } from "../../../../lib/helpers" -import { ListingAvailability } from "@bloom-housing/backend-core/types" +import { ListingReviewOrder } from "@bloom-housing/backend-core" type UnitProps = { units: TempUnit[] @@ -194,15 +194,14 @@ const FormUnits = ({ units, setUnits, disableUnitsAccordion }: UnitProps) => { value: "availableUnits", id: "availableUnits", dataTestId: "listingAvailability.availableUnits", - defaultChecked: - listing?.listingAvailability === ListingAvailability.availableUnits, + defaultChecked: listing?.reviewOrderType !== ListingReviewOrder.waitlist, }, { label: t("listings.waitlist.open"), value: "openWaitlist", id: "openWaitlist", dataTestId: "listingAvailability.openWaitlist", - defaultChecked: listing?.listingAvailability === ListingAvailability.openWaitlist, + defaultChecked: listing?.reviewOrderType === ListingReviewOrder.waitlist, }, ]} /> diff --git a/sites/public/CHANGELOG.md b/sites/public/CHANGELOG.md index bea6b2fc00..f2d89595c0 100644 --- a/sites/public/CHANGELOG.md +++ b/sites/public/CHANGELOG.md @@ -3,6 +3,113 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [7.0.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.10...@bloom-housing/public@7.0.1) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.10](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.9...@bloom-housing/public@7.0.1-alpha.10) (2022-10-26) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.9](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.8...@bloom-housing/public@7.0.1-alpha.9) (2022-10-25) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.8](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.7...@bloom-housing/public@7.0.1-alpha.8) (2022-10-25) + + +### Features + +* accept only numeric values in currency field ([4638808](https://github.com/bloom-housing/bloom/commit/4638808e79d758d28f1f7befd2c77e892673374e)) +* display income currency value with commas ([343e36f](https://github.com/bloom-housing/bloom/commit/343e36f4eced424aa915c91192274c5dda65c25f)) +* submit income as a string ([0e43540](https://github.com/bloom-housing/bloom/commit/0e43540d008dc51a0195eac4c2b0f367b4978957)) + + + + + +## [7.0.1-alpha.7](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.6...@bloom-housing/public@7.0.1-alpha.7) (2022-10-24) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.6](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.5...@bloom-housing/public@7.0.1-alpha.6) (2022-10-21) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.5](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.4...@bloom-housing/public@7.0.1-alpha.5) (2022-10-20) + + +### Bug Fixes + +* switch listing to server side props ([#3145](https://github.com/bloom-housing/bloom/issues/3145)) ([937faa9](https://github.com/bloom-housing/bloom/commit/937faa99dab0249b43bc90fd7cc8bd3dcacf6f38)) + + + + + +## [7.0.1-alpha.4](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.3...@bloom-housing/public@7.0.1-alpha.4) (2022-10-20) + + +### Features + +* **expandablecontent:** remove classname from ExpandableContent ([a1068d9](https://github.com/bloom-housing/bloom/commit/a1068d92a88ec32a3f1108c5592a0c6bf55f31cf)), closes [#3140](https://github.com/bloom-housing/bloom/issues/3140) + + + + + +## [7.0.1-alpha.3](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.2...@bloom-housing/public@7.0.1-alpha.3) (2022-10-19) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.2](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.1...@bloom-housing/public@7.0.1-alpha.2) (2022-10-13) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@7.0.1-alpha.0...@bloom-housing/public@7.0.1-alpha.1) (2022-10-12) + +**Note:** Version bump only for package @bloom-housing/public + + + + + +## [7.0.1-alpha.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@6.0.1-alpha.4...@bloom-housing/public@7.0.1-alpha.0) (2022-10-05) + +**Note:** Version bump only for package @bloom-housing/public + + + + + # [7.0.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/public@5.1.1-alpha.40...@bloom-housing/public@7.0.0) (2022-09-29) diff --git a/sites/public/lib/helpers.tsx b/sites/public/lib/helpers.tsx index 7b8228b14a..e8bd831ee1 100644 --- a/sites/public/lib/helpers.tsx +++ b/sites/public/lib/helpers.tsx @@ -5,7 +5,6 @@ import { ListingReviewOrder, UnitsSummarized, ListingStatus, - ListingAvailability, } from "@bloom-housing/backend-core/types" import { t, @@ -52,10 +51,10 @@ const getListingCardSubtitle = (address: Address) => { const getListingTableData = ( unitsSummarized: UnitsSummarized, - listingAvailability: ListingAvailability + listingReviewOrder: ListingReviewOrder ) => { return unitsSummarized !== undefined - ? getSummariesTable(unitsSummarized.byUnitTypeAndRent, listingAvailability) + ? getSummariesTable(unitsSummarized.byUnitTypeAndRent, listingReviewOrder) : [] } @@ -113,13 +112,13 @@ export const getListings = (listings) => { } const generateTableSubHeader = (listing) => { - if (listing.listingAvailability === ListingAvailability.availableUnits) { + if (listing.reviewOrderType !== ListingReviewOrder.waitlist) { return { content: t("listings.availableUnits"), styleType: AppearanceStyleType.success, isPillType: true, } - } else if (listing.listingAvailability === ListingAvailability.openWaitlist) { + } else if (listing.reviewOrderType === ListingReviewOrder.waitlist) { return { content: t("listings.waitlist.open"), styleType: AppearanceStyleType.primary, @@ -147,7 +146,7 @@ export const getListings = (listings) => { }} tableProps={{ headers: unitSummariesHeaders, - data: getListingTableData(listing.unitsSummarized, listing.listingAvailability), + data: getListingTableData(listing.unitsSummarized, listing.reviewOrderType), responsiveCollapse: true, cellClassName: "px-5 py-3", }} diff --git a/sites/public/package.json b/sites/public/package.json index b01d116536..d9e88597c3 100644 --- a/sites/public/package.json +++ b/sites/public/package.json @@ -1,6 +1,6 @@ { "name": "@bloom-housing/public", - "version": "7.0.0", + "version": "7.0.1", "author": "Sean Albert ", "description": "Public web app reference implementation for the Bloom affordable housing system", "main": "index.js", @@ -24,9 +24,9 @@ "dev:all": "concurrently \"yarn dev:listings\" \"yarn dev:server-wait\"" }, "dependencies": { - "@bloom-housing/backend-core": "^7.0.0", - "@bloom-housing/shared-helpers": "^7.0.0", - "@bloom-housing/ui-components": "^7.0.0", + "@bloom-housing/backend-core": "^7.0.1", + "@bloom-housing/shared-helpers": "^7.0.1", + "@bloom-housing/ui-components": "^7.0.1", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", diff --git a/sites/public/pages/applications/financial/income.tsx b/sites/public/pages/applications/financial/income.tsx index 5e96e4d4b6..1dab697a93 100644 --- a/sites/public/pages/applications/financial/income.tsx +++ b/sites/public/pages/applications/financial/income.tsx @@ -78,14 +78,15 @@ const ApplicationIncome = () => { const onSubmit = (data) => { const { income, incomePeriod } = data + const incomeValue = income.replaceAll(",", "") // Skip validation of total income if the applicant has income vouchers. const validationError = application.incomeVouchers ? null - : verifyIncome(listing, income, incomePeriod) + : verifyIncome(listing, incomeValue, incomePeriod) setIncomeError(validationError) if (!validationError) { - const toSave = { income, incomePeriod } + const toSave = { income: incomeValue, incomePeriod } conductor.completeSection(currentPageSection) conductor.currentStep.save(toSave) diff --git a/sites/public/pages/applications/review/confirmation.tsx b/sites/public/pages/applications/review/confirmation.tsx index 486dc27c2b..cad4fb6cee 100644 --- a/sites/public/pages/applications/review/confirmation.tsx +++ b/sites/public/pages/applications/review/confirmation.tsx @@ -14,7 +14,7 @@ import { FormCard, t, } from "@bloom-housing/ui-components" -import { ListingReviewOrder } from "@bloom-housing/backend-core/types" +import { ListingEvent, ListingReviewOrder } from "@bloom-housing/backend-core/types" import { imageUrlFromListing, PageView, @@ -33,25 +33,22 @@ const ApplicationConfirmation = () => { const imageUrl = imageUrlFromListing(listing, parseInt(process.env.listingPhotoSize)) - const reviewOrder = useMemo(() => { - if (listing) { - if (listing.reviewOrderType == ListingReviewOrder.lottery) { - const lotteryEvent = getLotteryEvent(listing) - const lotteryText = [] - if (lotteryEvent?.startTime) { - lotteryText.push( - t("application.review.confirmation.eligibleApplicants.lotteryDate", { - lotteryDate: dayjs(lotteryEvent?.startTime).format("MMMM D, YYYY"), - }) - ) + const content = useMemo(() => { + switch (listing?.reviewOrderType) { + case ListingReviewOrder.firstComeFirstServe: + return { + text: t("application.review.confirmation.whatHappensNext.fcfs"), } - lotteryText.push(t("application.review.confirmation.eligibleApplicants.lottery")) - return lotteryText.join(" ") - } else { - return t("application.review.confirmation.eligibleApplicants.FCFS") - } - } else { - return "" + case ListingReviewOrder.lottery: + return { + text: t("application.review.confirmation.whatHappensNext.lottery"), + } + case ListingReviewOrder.waitlist: + return { + text: t("application.review.confirmation.whatHappensNext.waitlist"), + } + default: + return { text: "" } } }, [listing, router.locale]) @@ -93,9 +90,7 @@ const ApplicationConfirmation = () => {
- - {t("application.review.confirmation.whatHappensNext", { reviewOrder })} - + {content.text}
diff --git a/sites/public/pages/applications/review/terms.tsx b/sites/public/pages/applications/review/terms.tsx index 2a244a5c91..c1e9fcb389 100644 --- a/sites/public/pages/applications/review/terms.tsx +++ b/sites/public/pages/applications/review/terms.tsx @@ -2,7 +2,7 @@ 5.3 Terms View of application terms with checkbox */ -import React, { useContext, useEffect, useState } from "react" +import React, { useContext, useEffect, useMemo, useState } from "react" import { useRouter } from "next/router" import { AppearanceStyleType, @@ -14,7 +14,7 @@ import { AlertBox, ProgressNav, } from "@bloom-housing/ui-components" -import { ApplicationSection } from "@bloom-housing/backend-core" +import { ApplicationSection, ListingReviewOrder } from "@bloom-housing/backend-core/types" import { useForm } from "react-hook-form" import Markdown from "markdown-to-jsx" import { @@ -87,6 +87,25 @@ const ApplicationTerms = () => { }, ] + const content = useMemo(() => { + switch (listing?.reviewOrderType) { + case ListingReviewOrder.firstComeFirstServe: + return { + text: t("application.review.terms.fcfs.text"), + } + case ListingReviewOrder.lottery: + return { + text: t("application.review.terms.lottery.text"), + } + case ListingReviewOrder.waitlist: + return { + text: t("application.review.terms.waitlist.text"), + } + default: + return { text: "" } + } + }, [listing, router.locale]) + useEffect(() => { pushGtmEvent({ event: "pageView", @@ -122,20 +141,37 @@ const ApplicationTerms = () => { )}
-
+
{listing?.applicationDueDate && ( - - {t("application.review.terms.textSubmissionDate", { - applicationDueDate: applicationDueDate, - })} - + <> + + {t("application.review.terms.textSubmissionDate", { + applicationDueDate: applicationDueDate, + })} + +
+
+ )} - - {t("application.review.terms.text")} + ( +
  • + {children} +
  • + ), + }, + }, + }} + > + {content.text}
    -
    +
    { const { profile } = useContext(AuthContext) const { conductor, application, listing } = useFormConductor("whatToExpect") + const router = useRouter() const currentPageSection = 1 /* Form Handler */ @@ -28,6 +32,28 @@ const ApplicationWhatToExpect = () => { conductor.routeToNextOrReturnUrl() } + const content = useMemo(() => { + switch (listing?.reviewOrderType) { + case ListingReviewOrder.firstComeFirstServe: + return { + steps: t("application.start.whatToExpect.fcfs.steps"), + finePrint: t("application.start.whatToExpect.fcfs.finePrint"), + } + case ListingReviewOrder.lottery: + return { + steps: t("application.start.whatToExpect.lottery.steps"), + finePrint: t("application.start.whatToExpect.lottery.finePrint"), + } + case ListingReviewOrder.waitlist: + return { + steps: t("application.start.whatToExpect.waitlist.steps"), + finePrint: t("application.start.whatToExpect.waitlist.finePrint"), + } + default: + return { steps: "", finePrint: "" } + } + }, [listing, router.locale]) + useEffect(() => { pushGtmEvent({ event: "pageView", @@ -58,9 +84,41 @@ const ApplicationWhatToExpect = () => {
    -

    {t("application.start.whatToExpect.info1")}

    -

    {t("application.start.whatToExpect.info2")}

    -

    {t("application.start.whatToExpect.info3")}

    +
    + ( +
      + {children} +
    + ), + }, + }, + }} + > + {content.steps} +
    + + ( +
  • + {children} +
  • + ), + }, + }, + }} + > + {content.finePrint} +
    +
    diff --git a/sites/public/pages/listings.tsx b/sites/public/pages/listings.tsx index 75284c34f1..c95422815f 100644 --- a/sites/public/pages/listings.tsx +++ b/sites/public/pages/listings.tsx @@ -71,12 +71,11 @@ export default function ListingsPage(props: ListingsProps) { ) } -export async function getStaticProps() { +export async function getServerSideProps() { const openListings = fetchOpenListings() const closedListings = fetchClosedListings() return { props: { openListings: await openListings, closedListings: await closedListings }, - revalidate: process.env.cacheRevalidate, } } diff --git a/sites/public/src/ListingView.tsx b/sites/public/src/ListingView.tsx index 63dc06d453..6eebc81345 100644 --- a/sites/public/src/ListingView.tsx +++ b/sites/public/src/ListingView.tsx @@ -9,9 +9,9 @@ import { ApplicationMethod, ApplicationMethodType, ListingStatus, - ListingAvailability, Jurisdiction, ApplicationSection, + ListingReviewOrder, } from "@bloom-housing/backend-core/types" import { AdditionalFees, @@ -131,7 +131,7 @@ export const ListingView = (props: ListingProps) => { if (amiValues.length == 1) { groupedUnits = getSummariesTable( listing.unitsSummarized.byUnitTypeAndRent, - listing.listingAvailability + listing.reviewOrderType ) } // else condition is handled inline below @@ -411,15 +411,15 @@ export const ListingView = (props: ListingProps) => { return ( { }) groupedUnits = byAMI - ? getSummariesTable(byAMI.byUnitType, listing.listingAvailability) + ? getSummariesTable(byAMI.byUnitType, listing.reviewOrderType) : [] return ( @@ -610,6 +610,7 @@ export const ListingView = (props: ListingProps) => { > { { { ", () => { expect(getByText("Term")).toBeTruthy() expect(getByText("Description")).toBeTruthy() }) + it("renders with markdown and description className", () => { + const { container, getByText } = render( + Description'} + dtClassName={"test-class"} + markdown={true} + markdownProps={{ disableParsingRawHTML: false }} + /> + ) + expect(getByText("Term")).toBeTruthy() + expect(getByText("Description")).toBeTruthy() + expect(container.getElementsByClassName("test-class").length).toBe(1) + expect(screen.getByTitle("Description Title")).toBeTruthy() + }) }) diff --git a/ui-components/package.json b/ui-components/package.json index 721ecaac63..d71029cca8 100644 --- a/ui-components/package.json +++ b/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@bloom-housing/ui-components", - "version": "7.0.0", + "version": "7.0.1", "author": "Sean Albert ", "description": "Shared user interface components for Bloom affordable housing system", "homepage": "https://github.com/bloom-housing/bloom/tree/master/shared/ui-components", diff --git a/ui-components/src/actions/ExpandableText.stories.tsx b/ui-components/src/actions/ExpandableText.stories.tsx index 282ff8323c..7e143f90e8 100644 --- a/ui-components/src/actions/ExpandableText.stories.tsx +++ b/ui-components/src/actions/ExpandableText.stories.tsx @@ -16,18 +16,27 @@ const longText = Lorem Ipsum." export const standard = () => ( - {longText} + + {longText} + ) export const expanded = () => ( - + {longText} ) export const noExpansion = () => ( - Short text + + Short text + ) export const html = () => ( {`Go to Google. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been \ the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley \ @@ -35,6 +44,7 @@ export const html = () => ( ) export const disableRawHtml = () => ( {`Go to Google. Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been \ @@ -46,7 +56,7 @@ export const buttonClassName = () => ( { "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been \ diff --git a/ui-components/src/actions/ExpandableText.tsx b/ui-components/src/actions/ExpandableText.tsx index 6769c223aa..b73a386e68 100644 --- a/ui-components/src/actions/ExpandableText.tsx +++ b/ui-components/src/actions/ExpandableText.tsx @@ -33,7 +33,7 @@ const moreLessButton = ( strings: ExpandableTextProps["strings"], buttonClassName: ExpandableTextProps["buttonClassName"] ) => { - const classes = ["button-toggle ml-4"] + const classes = ["button-toggle"] if (buttonClassName) { classes.push(buttonClassName) } diff --git a/ui-components/src/forms/Field.tsx b/ui-components/src/forms/Field.tsx index df0fae4b69..672fca46bf 100644 --- a/ui-components/src/forms/Field.tsx +++ b/ui-components/src/forms/Field.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react" +import React, { ChangeEvent, useMemo } from "react" import { ErrorMessage } from "../notifications/ErrorMessage" import { UseFormMethods, RegisterOptions } from "react-hook-form" @@ -57,20 +57,41 @@ const Field = (props: FieldProps) => { if (props.bordered && (props.type === "radio" || props.type === "checkbox")) controlClasses.push("field-border") - const formatValue = () => { + const formatValue = (focused = false) => { if (props.getValues && props.setValue) { const currencyValue = props.getValues(props.name) const numericIncome = parseFloat(currencyValue) - if (!isNaN(numericIncome)) { - props.setValue(props.name, numericIncome.toFixed(2)) + + if (focused && currencyValue) { + props.setValue(props.name, parseFloat(currencyValue.replaceAll(",", ""))) + } else if (isNaN(numericIncome)) { + props.setValue(props.name, "") + } else { + props.setValue( + props.name, + numericIncome.toLocaleString("en-US", { minimumFractionDigits: 2 }) + ) } } } + const filterNumbers = (e: ChangeEvent) => { + if (props.setValue) { + props.setValue(props.name, e.target.value.replace(/[a-z]|[A-Z]/g, "").match(/^\d*\.?\d?\d?/g)) + } + } + let inputProps = { ...props.inputProps } - if (props.type === "currency") inputProps = { ...inputProps, step: 0.01, onBlur: formatValue } + if (props.type === "currency") { + inputProps = { + ...inputProps, + onBlur: () => formatValue(), + onFocus: () => formatValue(true), + onChange: filterNumbers, + } + } - const type = (props.type === "currency" && "number") || props.type || "text" + const type = (props.type === "currency" && "text") || props.type || "text" const isRadioOrCheckbox = ["radio", "checkbox"].includes(type) const label = useMemo(() => { diff --git a/ui-components/src/global/markdown.scss b/ui-components/src/global/markdown.scss index f24ce1b339..3567bc677a 100644 --- a/ui-components/src/global/markdown.scss +++ b/ui-components/src/global/markdown.scss @@ -1,53 +1,173 @@ .markdown { + /* Set sizing and gaps for built-in HTML elements */ + h2 { - @apply mt-8; - @apply mb-5; - @apply text-3xl; + margin-block: var(--bloom-s8) var(--bloom-s5); + font-size: var(--bloom-font-size-3xl); - @screen md { - @apply text-xl; + @media (min-width: $screen-md) { + font-size: var(--bloom-font-size-xl); } } h2:first-child { - @apply mt-0; + margin-block-start: 0; } hr { - @apply mt-8; - @apply mb-8; - @apply border-b; - @apply border-solid; - @apply border-gray-450; + margin-block: var(--bloom-s8); + border-block-end: 1px solid var(--bloom-color-gray-450); } p:not(:last-child), ul:not(:last-child) { - @apply mb-4; + margin-block-end: var(--bloom-s4); } ul { list-style: disc; - @apply ml-6; + margin-inline-start: var(--bloom-s6); } + /* Certain text blocks in the application form flow use this extra styling: */ + &.markdown-informational { - font-size: 0.9em; - @apply text-gray-750; + font-size: var(--bloom-font-size-tiny); + color: var(--bloom-color-gray-750); h3 { - @apply font-sans; - @apply font-bold; - @apply text-base; - @apply text-black; + font-family: var(--bloom-font-sans); + font-weight: bold; + font-size: var(--bloom-font-size-base); + color: var(--bloom-color-black); + margin-block-end: var(--bloom-s4); } li { - @apply mb-4; + margin-block-end: var(--bloom-s4); } a { - @apply underline; + text-align: underline; + } + } + + /* Used for a larger lead paragraph: */ + + .markdown__lead { + font-family: var(--bloom-font-sans); + font-size: var(--bloom-font-size-base); + color: var(--bloom-color-gray-700); + margin-block: var(--bloom-s3) var(--bloom-s4); + } + + /* Used for a (1), (2), (3) section on the application form flow: */ + + ol.large-numbers { + counter-reset: list-number; + list-style-type: none; + margin-block-end: var(--bloom-s8); + + li { + display: flex; + align-items: center; + gap: 0.8em; + counter-increment: list-number; + margin-block-end: var(--bloom-s4); + font-weight: 600; + } + + li::before { + content: counter(list-number); + flex-shrink: 0; + width: 32px; + height: 32px; + border: 1px solid var(--bloom-color-gray-600); + padding: 3px; + border-radius: 100px; + text-align: center; + font-weight: normal; + } + } + + /* Used for lengthy (1), (2), (3), etc. such as the How it Works page: */ + + ol.process-list { + font-size: 1.06rem; + line-height: 1.5; + margin-block: 0; + list-style-type: none; + counter-reset: list-number; + padding-block: 20px 0; + padding-inline: var(--bloom-s5) 0; + position: relative; + + & > li { + border-inline-start: 1px solid var(--bloom-color-gray-450); + font-size: 1.06rem; + margin-bottom: 0; + padding-block-end: var(--bloom-s8); + padding-inline-start: var(--bloom-s8); + + @media (min-width: $screen-md) { + padding-inline-start: var(--bloom-s10); + } + + &::before { + content: counter(list-number, decimal); + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + border-radius: var(--bloom-rounded-full); + background-color: var(--bloom-color-white); + border: var(--bloom-border-2) solid var(--bloom-color-gray-750); + box-shadow: 0 0 0 1px var(--bloom-color-white); + color: var(--bloom-color-gray-750); + counter-increment: list-number; + font-size: var(--bloom-font-size-base); + font-weight: normal; + line-height: 0.9; + width: var(--bloom-s10); + height: var(--bloom-s10); + margin-block-start: 0; + position: absolute; + left: 0; + } + + &:last-child { + border-inline-start-color: transparent; + } + } + + /* The homepage How it Works section uses a horizontal layout: */ + + &.has-horizontal-layout { + display: flex; + gap: var(--bloom-s8); + padding-inline-start: 0; + margin-block: var(--bloom-s12); + + @media (max-width: 1100px) { + flex-direction: column; + gap: var(--bloom-s12); + } + + & > li { + position: relative; + border-inline-start-width: 0; + text-align: center; + padding-top: var(--bloom-s8); + background-color: var(--bloom-color-white); + box-shadow: 0px 0px 3px var(--bloom-color-gray-450); + padding-inline: var(--bloom-s4); + + &::before { + inset-block-start: calc(0rem - var(--bloom-s7)); + inset-inline-start: 50%; + margin-inline-start: calc(0rem - var(--bloom-s0_5)); + } + } } } } diff --git a/ui-components/src/locales/general.json b/ui-components/src/locales/general.json index 3982ff32ee..08f5716680 100644 --- a/ui-components/src/locales/general.json +++ b/ui-components/src/locales/general.json @@ -379,7 +379,9 @@ "application.review.confirmation.whatExpectFirstParagraph.refer": "Please refer to the listing for the lottery results date.", "application.review.confirmation.whatExpectSecondparagraph": "Applicants will be contacted in order until vacancies are filled. Should your application be chosen, be prepared to fill out a more detailed application and provide required supporting documents.", "application.review.confirmation.whatExpectTitle": "What to expect next", - "application.review.confirmation.whatHappensNext": "### What happens next?\n\n* After all applications are submitted, the property manager will begin processing applications.\n\n* %{reviewOrder}\n\n* If you are contacted for an interview, you will need to fill out a more detailed application and provide supporting documents.", + "application.review.confirmation.whatHappensNext.fcfs": "### What happens next?\n\n* Eligible applicants will be contacted on a first come first serve basis until vacancies are filled.\n\n* Housing preferences, if applicable, will affect first come, first serve order.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.", + "application.review.confirmation.whatHappensNext.lottery": "### What happens next?\n\n* Once the application period closes, eligible applicants will be placed in order based on lottery rank order.\n\n* Housing preferences, if applicable, will affect lottery rank order.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.", + "application.review.confirmation.whatHappensNext.waitlist": "### What happens next?\n\n* Eligible applicants will be placed on the waitlist on a first come first serve basis until waitlist spots are filled.\n\n* Housing preferences, if applicable, will affect waitlist order.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.\n\n* You may be contacted while on the waitlist to confirm that you wish to remain on the waitlist.", "application.review.demographics.ethnicityLabel": "Which best describes your ethnicity?", "application.review.demographics.ethnicityOptions.hispanicLatino": "Hispanic / Latino", "application.review.demographics.ethnicityOptions.notHispanicLatino": "Not Hispanic / Latino", @@ -439,13 +441,21 @@ "application.review.sameAddressAsApplicant": "Same Address as Applicant", "application.review.takeAMomentToReview": "Take a moment to review your information before submitting your application.", "application.review.terms.confirmCheckboxText": "I agree and understand that I cannot change anything after I submit.", - "application.review.terms.textSubmissionDate": "This application must be submitted by %{applicationDueDate}.

    ", - "application.review.terms.text": "Applicants will be contacted by the leasing agent in lottery and preference order or waitlist order until vacancies are filled. All of the information that you have provided will be verified and your eligibility confirmed. Your application may be removed from the waitlist if you have made any fraudulent statements and duplicate applications from the same household may be removed as only one application per household is permitted. Should your application be chosen for review, be prepared to fill out a more detailed application and provide required supporting documents. For more information, please contact the developer or leasing agent posted in the listing. Please contact the developer/property manager directly if there are any updates to your application.

    If we cannot verify a housing lottery preference that you have claimed, you will not receive the preference but will not be otherwise penalized.

    Completing this housing application does not entitle you to housing or indicate you are eligible for housing; all applicants will be screened as outlined in the property’s Resident Selection Criteria. We offer no guarantees about obtaining housing.

    You cannot change your online application after you submit.

    I declare that the foregoing is true and accurate, and acknowledge that any misstatement fraudulently or negligently made on this application may result in removal from the lottery.

    ", + "application.review.terms.textSubmissionDate": "This application must be submitted by %{applicationDueDate}.", + "application.review.terms.fcfs.text": "* Applicants are applying to currently vacant apartments on a first come, first serve basis.\n\n* Eligible applicants will be contacted on a first come first serve basis until vacancies are filled.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.\n\n* All of the information that you have provided will be verified and your eligibility confirmed.\n\n* Your application may be removed if you have made any fraudulent statements.\n\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.\n\nFor more information, please contact the housing developer or property manager posted in the listing.\n\nCompleting this application does not entitle you to housing or indicate you are eligible for housing. All applicants will be screened as outlined in the property's Resident Selection Criteria.\n\nYou cannot change your online application after you submit.\n\nI declare that the foregoing is true and accurate, and acknowledge that any misstatement fraudulently or negligently made on this application will result in removal from the lottery.", + "application.review.terms.lottery.text": "* Applicants are applying to enter a lottery for currently vacant apartments.\n\n* Once the application period closes, eligible applicants will be placed in lottery rank order.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.\n\n* All of the information that you have provided will be verified and your eligibility confirmed.\n\n* Your application may be removed if you have made any fraudulent statements.\n\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.\n\nFor more information, please contact the housing developer or property manager posted in the listing.\n\nCompleting this application does not entitle you to housing or indicate you are eligible for housing. All applicants will be screened as outlined in the property's Resident Selection Criteria.\n\nYou cannot change your online application after you submit.\n\nI declare that the foregoing is true and accurate, and acknowledge that any misstatement fraudulently or negligently made on this application will result in removal from the lottery.", + "application.review.terms.waitlist.text": "* Applicants are applying for an open waitlist and not a currently vacant apartment.\n\n* When vacancies become available, eligible applicants will be contacted by the property manager on a first come, first serve basis.\n\n* If you are contacted for an interview, you will be asked to fill out a more detailed application and provide supporting documents.\n\n* All of the information that you have provided will be verified and your eligibility confirmed.\n\n* Your application may be removed if you have made any fraudulent statements.\n\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.\n\n* You may be contacted while on the waitlist to confirm that you wish to remain on the waitlist.\n\nFor more information, please contact the housing developer or property manager posted in the listing.\n\nCompleting this application does not entitle you to housing or indicate you are eligible for housing. All applicants will be screened as outlined in the property's Resident Selection Criteria.\n\nYou cannot change your online application after you submit.\n\nI declare that the foregoing is true and accurate, and acknowledge that any misstatement fraudulently or negligently made on this application will result in removal from the lottery.", "application.review.terms.title": "Terms", "application.review.voucherOrSubsidy": "Housing Voucher or Rental Subsidy", - "application.start.whatToExpect.info1": "First we'll ask about you and the people you plan to live with. Then, we'll ask about your income. Finally, we'll see if you qualify for any affordable housing lottery preference.", - "application.start.whatToExpect.info2": "Please be aware that each household member can only appear on one application for each listing.", - "application.start.whatToExpect.info3": "Any fraudulent statements will cause your application to be removed.", + "application.start.whatToExpect.fcfs.steps": "1. First we'll ask about you and the people you plan to live with.\n2. Then, we'll ask about your income.\n3. Finally, we'll see if you qualify for any affordable housing lottery preferences, if applicable.", + "application.start.whatToExpect.fcfs.finePrint": "* Applicants are applying to currently vacant apartments on a first come, first serve basis.\n* Please be aware that each household member can only appear on one application for each listing.\n* Applicants will be contacted by the property manager on a first come, first serve basis, until vacancies are filled.\n* All of the information that you have provided will be verified and your eligibility confirmed.\n* Your application may be removed if you have made any fraudulent statements.\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.", + + "application.start.whatToExpect.lottery.steps": "1. First we'll ask about you and the people you plan to live with.\n2. Then, we'll ask about your income.\n3. Finally, we'll see if you qualify for any affordable housing lottery preferences, if applicable.", + "application.start.whatToExpect.lottery.finePrint": "* Applicants are applying to enter a lottery for currently vacant apartments.\n* Please be aware that each household member can only appear on one application for each listing.\n* Applicants will be contacted by the property agent in lottery rank order until vacancies are filled.\n* All of the information that you have provided will be verified and your eligibility confirmed.\n* Your application may be removed if you have made any fraudulent statements.\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.", + + "application.start.whatToExpect.waitlist.steps": "1. First we'll ask about you and the people you plan to live with.\n2. Then, we'll ask about your income.\n3. Finally, we'll see if you qualify for any affordable housing preferences, if applicable.", + "application.start.whatToExpect.waitlist.finePrint": "* Applicants are applying for an open waitlist and not a currently available apartment.\n* Please be aware that each household member can only appear on one application for each listing.\n* When vacancies become available, eligible applicants will be contacted by the property manager on a first come, first serve basis.\n* All of the information that you have provided will be verified and your eligibility confirmed.\n* Your application may be removed if you have made any fraudulent statements.\n* For properties with housing preferences, if we cannot verify a housing preference that you have claimed, you will not receive the preference but will not be otherwise penalized.", + "application.start.whatToExpect.title": "Here's what to expect from this application.", "application.status": "Status", "application.statuses.inProgress": "In Progress", diff --git a/ui-components/src/sections/InfoCardGrid.stories.tsx b/ui-components/src/sections/InfoCardGrid.stories.tsx index 3fcaa2ff13..8ba99e2e15 100644 --- a/ui-components/src/sections/InfoCardGrid.stories.tsx +++ b/ui-components/src/sections/InfoCardGrid.stories.tsx @@ -45,7 +45,7 @@ More content externalHref="http://google.com" className="is-normal-primary-lighter" > - + {` Text within _another_ component… `} diff --git a/ui-components/src/text/Description.tsx b/ui-components/src/text/Description.tsx index 44d239ab1a..3555f55a90 100644 --- a/ui-components/src/text/Description.tsx +++ b/ui-components/src/text/Description.tsx @@ -1,23 +1,31 @@ import * as React from "react" import "./Description.scss" -import Markdown from "markdown-to-jsx" +import Markdown, { MarkdownOptions } from "markdown-to-jsx" export interface DescriptionProps { term: string description: any + dtClassName?: string markdown?: boolean + markdownProps?: MarkdownOptions } export const Description = (props: DescriptionProps) => { + const dtClasses = ["description__body"] + if (props.dtClassName) dtClasses.push(props.dtClassName) + return ( <>
    {props.term}
    {props.markdown ? ( -
    - +
    +
    ) : ( -
    {props.description}
    +
    {props.description}
    )} )