From 12f380868f57e773e5296da95b0376bb17eb3a4d Mon Sep 17 00:00:00 2001 From: Emily Jablonski <65367387+emilyjablonski@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:32:08 -0600 Subject: [PATCH] feat: run lottery modals (#4168) --- api/prisma/seed-helpers/address-factory.ts | 2 +- shared-helpers/__tests__/testHelpers.ts | 2 +- .../pages/applications/index.test.tsx | 2 +- .../__tests__/pages/listings/lottery.test.tsx | 98 ++++++++++++++++++- .../__tests__/pages/settings/index.test.tsx | 4 +- .../locale_overrides/general.json | 12 ++- .../components/shared/NavigationHeader.tsx | 2 +- .../src/pages/listings/[id]/lottery.tsx | 96 ++++++++++++++++-- 8 files changed, 198 insertions(+), 20 deletions(-) diff --git a/api/prisma/seed-helpers/address-factory.ts b/api/prisma/seed-helpers/address-factory.ts index 02ea1b3bdd..68bf790c17 100644 --- a/api/prisma/seed-helpers/address-factory.ts +++ b/api/prisma/seed-helpers/address-factory.ts @@ -37,7 +37,7 @@ export const yosemiteAddress = { export const rockyMountainAddress = { placeName: 'Rocky Mountain National Park', city: 'Estes Park', - state: 'C0', + state: 'CO', street: '1000 US-36', zipCode: '80517', latitude: 40.3800984, diff --git a/shared-helpers/__tests__/testHelpers.ts b/shared-helpers/__tests__/testHelpers.ts index 20ee193672..bac17864fe 100644 --- a/shared-helpers/__tests__/testHelpers.ts +++ b/shared-helpers/__tests__/testHelpers.ts @@ -145,7 +145,7 @@ export const application: Application = { updatedAt: new Date(), placeName: "Rocky Mountain National Park", city: "Estes Park", - state: "C0", + state: "CO", street: "1000 US-36", zipCode: "80517", latitude: 40.3800984, diff --git a/sites/partners/__tests__/pages/applications/index.test.tsx b/sites/partners/__tests__/pages/applications/index.test.tsx index 30a53cdec6..477f5699dc 100644 --- a/sites/partners/__tests__/pages/applications/index.test.tsx +++ b/sites/partners/__tests__/pages/applications/index.test.tsx @@ -101,7 +101,7 @@ describe("applications", () => { expect(getByText("Mailing City")).toBeInTheDocument() expect(getByText("Estes Park")).toBeInTheDocument() expect(getByText("Mailing State")).toBeInTheDocument() - expect(getByText("C0")).toBeInTheDocument() + expect(getByText("CO")).toBeInTheDocument() expect(getByText("Mailing Zip")).toBeInTheDocument() expect(getByText("80517")).toBeInTheDocument() expect(getByText("Work Street Address")).toBeInTheDocument() diff --git a/sites/partners/__tests__/pages/listings/lottery.test.tsx b/sites/partners/__tests__/pages/listings/lottery.test.tsx index 9ee88ec359..b426d67ba8 100644 --- a/sites/partners/__tests__/pages/listings/lottery.test.tsx +++ b/sites/partners/__tests__/pages/listings/lottery.test.tsx @@ -31,6 +31,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -49,6 +52,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -69,6 +75,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -95,6 +104,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -121,6 +133,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -145,6 +160,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -175,6 +193,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -207,10 +228,13 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) - const { getByText, findByText } = render( + const { getByText, findByText, findAllByText } = render( ) @@ -219,7 +243,7 @@ describe("lottery", () => { fireEvent.click(getByText("Re-run lottery")) expect(await findByText("Are you sure?")).toBeInTheDocument() - expect(await findByText("I understand, re-run the lottery")).toBeInTheDocument() + expect(await findAllByText("Re-run lottery")).toHaveLength(2) }) it("should show release modal if user clicks on release", async () => { @@ -236,6 +260,9 @@ describe("lottery", () => { }), rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) }) ) @@ -247,11 +274,74 @@ describe("lottery", () => { expect(header).toBeInTheDocument() fireEvent.click(getByText("Release lottery")) - expect(await findByText("Confirmation needed")).toBeInTheDocument() + expect(await findByText("Are you sure?")).toBeInTheDocument() expect( await findByText( - "Releasing the lottery will give Partner users access to the lottery data, including the ability to publicize anonymous results to applicants." + "Releasing the lottery will give Partner users access to the lottery data, including the ability to publish results to applicants." ) ).toBeInTheDocument() }) + + it("should show confirm modal if user clicks on run lottery with no unresolved duplicates", async () => { + mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" }) + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res( + ctx.json({ + id: "user1", + userRoles: { isAdmin: true }, + }) + ) + }), + rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { + return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 0 })) + }) + ) + + const { getByText, findByText } = render() + + const header = await findByText("Partners Portal") + expect(header).toBeInTheDocument() + + fireEvent.click(getByText("Run lottery")) + expect(await findByText("Confirmation needed")).toBeInTheDocument() + expect( + await findByText("Make sure to add all paper applications before running the lottery.") + ).toBeInTheDocument() + }) + + it("should show confirm with duplicates modal if user clicks on run lottery and listing does have unresolved duplicates", async () => { + mockNextRouter({ id: "Uvbk5qurpB2WI9V6WnNdH" }) + document.cookie = "access-token-available=True" + server.use( + rest.get("http://localhost/api/adapter/user", (_req, res, ctx) => { + return res( + ctx.json({ + id: "user1", + userRoles: { isAdmin: true }, + }) + ) + }), + rest.post("http://localhost:3100/auth/token", (_req, res, ctx) => { + return res(ctx.json("")) + }), + rest.get("http://localhost/api/adapter/applicationFlaggedSets/meta", (_req, res, ctx) => { + return res(ctx.json({ totalCount: 5, totalPendingCount: 5 })) + }) + ) + + const { getByText, findByText, findAllByText } = render() + + const header = await findByText("Partners Portal") + expect(header).toBeInTheDocument() + + fireEvent.click(getByText("Run lottery")) + expect(await findByText("Confirmation needed")).toBeInTheDocument() + expect(await findByText("5 unresolved duplicate sets.")).toBeInTheDocument() + expect(await findAllByText("Run lottery")).toHaveLength(2) + }) }) diff --git a/sites/partners/__tests__/pages/settings/index.test.tsx b/sites/partners/__tests__/pages/settings/index.test.tsx index 4a871bbdaa..802a89d33d 100644 --- a/sites/partners/__tests__/pages/settings/index.test.tsx +++ b/sites/partners/__tests__/pages/settings/index.test.tsx @@ -86,7 +86,7 @@ describe("settings", () => { expect(headAndBody).toHaveLength(2) const [head, body] = headAndBody expect(within(head).getAllByRole("columnheader")).toHaveLength(4) - const rows = within(body).getAllByRole("row") + const rows = await within(body).findAllByRole("row") expect(rows).toHaveLength(1) const [name, jurisdiction, updated, actions] = within(rows[0]).getAllByRole("cell") expect(name).toHaveTextContent(multiselectQuestionPreference.text) @@ -207,7 +207,7 @@ describe("settings", () => { `This preference is currently added to listings and needs to be removed before being deleted.` ) ) - expect(getByText(listing.name)) + expect(await findByText(listing.name)) // verify delete button is not there expect(queryAllByText("Delete")).toHaveLength(0) diff --git a/sites/partners/page_content/locale_overrides/general.json b/sites/partners/page_content/locale_overrides/general.json index c1d502ec28..6afc16c8fb 100644 --- a/sites/partners/page_content/locale_overrides/general.json +++ b/sites/partners/page_content/locale_overrides/general.json @@ -57,7 +57,7 @@ "applications.addConfirmModalAddApplicationPostLotteryConfirm": "I understand, add application", "applications.addConfirmModalAddApplicationPostLotteryTitle": "Are you sure?", "applications.addConfirmModalAddApplicationPostLotteryWeighted": "Adding a new application will require the lottery to be re-run.", - "applications.addConfirmModalContent": "This listing has closed, are you sure you want to add an application?", + "applications.addConfirmModalContent": "This listing has closed. Are you sure you want to add a paper application?", "applications.addConfirmModalHeader": "Confirmation needed", "applications.allApplications": "All Applications", "applications.duplicate": "Duplicate", @@ -216,6 +216,10 @@ "listings.listingStatusText": "Listing Status", "listings.listingSubmitted": "Listing Submitted", "listings.longitude": "Longitude", + "listings.lottery.duplicateContent": "This listing has", + "listings.lottery.duplicateString": "%{sets} unresolved duplicate set.", + "listings.lottery.duplicateStringPlural": "%{sets} unresolved duplicate sets.", + "listings.lottery.duplicatesConfirm": "You should resolve any duplicates before running the lottery.", "listings.lottery.export": "Export lottery data", "listings.lottery.exportFile": "File includes randomized general pool and preference data.", "listings.lottery.history": "History", @@ -223,13 +227,13 @@ "listings.lottery.noDataDescription": "It looks like you haven't run a lottery for this listing yet.", "listings.lottery.release": "Release lottery", "listings.lottery.releaseButton": "Release lottery", - "listings.lottery.releaseContent": "Releasing the lottery will give Partner users access to the lottery data, including the ability to publicize anonymous results to applicants.", + "listings.lottery.releaseContent": "Releasing the lottery will give Partner users access to the lottery data, including the ability to publish results to applicants.", "listings.lottery.reRun": "Re-run lottery", - "listings.lottery.reRunButton": "I understand, re-run the lottery", - "listings.lottery.reRunContent": "Re-running the lottery will reset current preference and general pool rankings.", + "listings.lottery.reRunContent": "Re-running the lottery will give all applications a new raw rank, which will impact the order in which they are processed.", "listings.lottery.reRunHistory": "Re-running a lottery will also be tracked in the history log.", "listings.lottery.reRunCannotBeUndone": "This action cannot be undone.", "listings.lottery.runLottery": "Run lottery", + "listings.lottery.runLotteryContent": "Make sure to add all paper applications before running the lottery.", "listings.lotteryDateNotes": "Lottery Date Notes", "listings.lotteryDateQuestion": "When will the lottery be run?", "listings.lotteryEndTime": "Lottery End Time", diff --git a/sites/partners/src/components/shared/NavigationHeader.tsx b/sites/partners/src/components/shared/NavigationHeader.tsx index 38be926777..2ef0dd1e75 100644 --- a/sites/partners/src/components/shared/NavigationHeader.tsx +++ b/sites/partners/src/components/shared/NavigationHeader.tsx @@ -71,7 +71,7 @@ const NavigationHeader = ({ label: tabs.lotteryLabel, path: `/listings/${listingId}/lottery`, activePaths: [`/listings/${listingId}/lottery`], - content: t("t.new"), + content: undefined, }) } diff --git a/sites/partners/src/pages/listings/[id]/lottery.tsx b/sites/partners/src/pages/listings/[id]/lottery.tsx index 227aa6e7ef..c8a8b4ae63 100644 --- a/sites/partners/src/pages/listings/[id]/lottery.tsx +++ b/sites/partners/src/pages/listings/[id]/lottery.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react" +import React, { useState, useContext } from "react" import Head from "next/head" import axios from "axios" import Ticket from "@heroicons/react/24/solid/TicketIcon" @@ -6,10 +6,12 @@ import Download from "@heroicons/react/24/solid/ArrowDownTrayIcon" import { t, Breadcrumbs, BreadcrumbLink } from "@bloom-housing/ui-components" import { Button, Card, Heading, Icon, Dialog } from "@bloom-housing/ui-seeds" import { CardHeader, CardSection } from "@bloom-housing/ui-seeds/src/blocks/Card" +import { AuthContext } from "@bloom-housing/shared-helpers" import { Listing, ListingsStatusEnum, ReviewOrderTypeEnum, + ListingUpdate, } from "@bloom-housing/shared-helpers/src/types/backend-swagger" import Layout from "../../../layouts" import { ListingContext } from "../../../components/listings/ListingContext" @@ -17,6 +19,7 @@ import { MetaTags } from "../../../components/shared/MetaTags" import ListingGuard from "../../../components/shared/ListingGuard" import { NavigationHeader } from "../../../components/shared/NavigationHeader" import { ListingStatusBar } from "../../../components/listings/ListingStatusBar" +import { useFlaggedApplicationsMeta } from "../../../lib/hooks" import styles from "../../../../styles/lottery.module.scss" @@ -26,8 +29,14 @@ const Lottery = (props: { listing: Listing }) => { const { listing } = props + const [runModal, setRunModal] = useState(false) const [reRunModal, setReRunModal] = useState(false) const [releaseModal, setReleaseModal] = useState(false) + const [loading, setLoading] = useState(false) + + const { listingsService } = useContext(AuthContext) + const { data } = useFlaggedApplicationsMeta(listing?.id) + const duplicatesExist = data?.totalPendingCount > 0 if (!listing) return
{t("t.errorOccurred")}
@@ -70,7 +79,13 @@ const Lottery = (props: { listing: Listing }) => { {t("listings.lottery.noDataDescription")}
- +
) @@ -174,6 +189,77 @@ const Lottery = (props: { listing: Listing }) => { + setRunModal(false)} + > + + {t("applications.addConfirmModalHeader")} + + + {duplicatesExist ? ( +

+ {t("listings.lottery.duplicateContent")}{" "} + + {t( + data?.totalPendingCount === 1 + ? "listings.lottery.duplicateString" + : "listings.lottery.duplicateStringPlural", + { + sets: data?.totalPendingCount?.toString(), + } + )} + {" "} + {t("listings.lottery.duplicatesConfirm")} +

+ ) : ( +

{t("listings.lottery.runLotteryContent")}

+ )} +

{t("applications.addConfirmModalAddApplicationPostLotteryAreYouSure")}

+
+ + + + +
{ }} size="sm" > - {t("listings.lottery.reRunButton")} + {t("listings.lottery.reRun")}