Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b9e2d24
feat: init refactor and documents page
cliu02 Apr 8, 2026
a9a089a
feat: cleanup
cliu02 Apr 8, 2026
04cc54e
feat: update routing
cliu02 Apr 8, 2026
d6584a7
chore: old token
cliu02 Apr 9, 2026
de09189
test: tests
cliu02 Apr 9, 2026
e96ce0a
chore: more refactors
cliu02 Apr 9, 2026
f114d1a
chore: cleanup
cliu02 Apr 9, 2026
38a1938
chore: cleanup and placeholder
cliu02 Apr 9, 2026
778c99a
chore: cleanup
cliu02 Apr 9, 2026
91c94b7
chore: cleanup
cliu02 Apr 9, 2026
e8d2d29
chore: renaming
cliu02 Apr 9, 2026
6527593
chore: main merge conflict
cliu02 Apr 9, 2026
2637ab7
fix: backwards compatible
cliu02 Apr 9, 2026
a076d5e
feat: update record response
cliu02 Apr 9, 2026
69e6f79
chore: cleanup
cliu02 Apr 9, 2026
7b3c7f4
chore: refactor
cliu02 Apr 9, 2026
4731ffb
fix: old email param
cliu02 Apr 9, 2026
59c91a7
test: update tests
cliu02 Apr 9, 2026
03ecf9f
test: update tests
cliu02 Apr 9, 2026
2100d8a
Merge branch 'main' into DAH-4006-i2i-next-steps
cliu02 Apr 13, 2026
0dce7be
feat: next steps page
cliu02 Apr 15, 2026
32b16be
chore: refactor
cliu02 Apr 15, 2026
bfe2f69
Merge branch 'main' into DAH-4006-i2i-next-steps
cliu02 Apr 15, 2026
27bac01
feat: use scheduling url
cliu02 Apr 15, 2026
7446e9c
chore: refactor and fallbacks
cliu02 Apr 15, 2026
10629cc
Merge branch 'main' into DAH-4006-i2i-next-steps
cliu02 Apr 15, 2026
789a7c0
fix: merge conflict
cliu02 Apr 15, 2026
fb7aa2c
test: add test
cliu02 Apr 15, 2026
0b2cff3
feat: response pages
cliu02 Apr 16, 2026
78a446a
Merge branch 'main' into DAH-4037-i2i-response
cliu02 Apr 20, 2026
2f2fa3f
chore: cleanup
cliu02 Apr 20, 2026
9876c34
chore: cleanup
cliu02 Apr 20, 2026
55c2047
fix: merge conflict
cliu02 Apr 20, 2026
23ace8b
fix: merge conflict
cliu02 Apr 20, 2026
63111e1
chore: updated token
cliu02 Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions app/controllers/invite_to_controller.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# Invite to X controller
class InviteToController < ApplicationController
def index
# TODO: uncomment when backend token updated
# decoded_params = decode_token(params[:t])
# if decoded_params.is_a?(String)
# redirect_to decoded_params
# return
# end
decoded_params = decode_token(params[:t])
if decoded_params.is_a?(String)
redirect_to decoded_params
return
end
decoded_params ||= params
@invite_to_props = props(decoded_params)
# Get file upload URL for application
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { post } from "../../api/apiService"
import { recordResponse } from "../../api/inviteToApiService"
import { INVITE_TO_X } from "../../modules/constants"

jest.mock("../../api/apiService", () => ({
post: jest.fn(),
Expand All @@ -16,7 +17,7 @@ describe("inviteToApiService", () => {
deadline: "2099-01-01",
action: "submit",
response: "submit",
type: "I2A",
type: INVITE_TO_X.APPLY,
}
await recordResponse(record)
expect(post).toHaveBeenCalled()
Expand Down
19 changes: 10 additions & 9 deletions app/javascript/__tests__/pages/invite-to-apply.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { localizedFormat } from "../../util/languageUtil"
import { getListing } from "../../api/listingApiService"
import { recordResponse } from "../../api/inviteToApiService"
import { ConfigContext } from "../../lib/ConfigContext"
import { INVITE_TO_X } from "../../modules/constants"

jest.mock("../../api/listingApiService")
jest.mock("../../api/inviteToApiService", () => ({
Expand Down Expand Up @@ -98,7 +99,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
act: "contact",
deadline: mockPastDeadline,
}}
Expand All @@ -117,7 +118,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "no",
appId: "0000",
Expand Down Expand Up @@ -145,7 +146,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "contact",
appId: "0000",
Expand Down Expand Up @@ -182,7 +183,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockPastDeadline,
act: "yes",
appId: "0000",
Expand All @@ -197,7 +198,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "yes",
}}
Expand All @@ -217,7 +218,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "yes",
appId: "a0o123",
Expand All @@ -242,7 +243,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "yes",
appId: "a0o123",
Expand All @@ -264,7 +265,7 @@ describe("Invite to Apply", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
deadline: mockFutureDeadline,
act: "yes",
}}
Expand All @@ -285,7 +286,7 @@ describe("Invite to Apply", () => {
assetPaths={"/"}
documentsPath={true}
urlParams={{
type: "I2A",
type: INVITE_TO_X.APPLY,
}}
/>
)
Expand Down
54 changes: 49 additions & 5 deletions app/javascript/__tests__/pages/invite-to-interview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { renderAndLoadAsync } from "../__util__/renderUtils"
import { ConfigContext } from "../../lib/ConfigContext"
import { getListing } from "../../api/listingApiService"
import { recordResponse } from "../../api/inviteToApiService"
import { INVITE_TO_X } from "../../modules/constants"

jest.mock("../../api/listingApiService")
jest.mock("../../api/inviteToApiService", () => ({
Expand Down Expand Up @@ -89,7 +90,7 @@ describe("Invite to Interview", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
type: INVITE_TO_X.INTERVIEW,
}}
documentsPath={true}
/>
Expand All @@ -104,7 +105,7 @@ describe("Invite to Interview", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
type: INVITE_TO_X.INTERVIEW,
act: "yes",
deadline: "2030-10-10",
appId: "test-id",
Expand All @@ -120,7 +121,7 @@ describe("Invite to Interview", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
type: INVITE_TO_X.INTERVIEW,
act: "yes",
deadline: "2020-10-10",
appId: "test-id",
Expand All @@ -135,7 +136,7 @@ describe("Invite to Interview", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
type: INVITE_TO_X.INTERVIEW,
act: "yes",
deadline: "2030-10-10",
appId: "test-id",
Expand All @@ -150,7 +151,7 @@ describe("Invite to Interview", () => {
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
type: INVITE_TO_X.INTERVIEW,
deadline: "2030-10-10",
act: "yes",
appId: "test-id",
Expand All @@ -163,4 +164,47 @@ describe("Invite to Interview", () => {
await userEvent.click(button)
expect(recordResponse).toHaveBeenCalled()
})

it("shows the withdrawn page when the action is no", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: INVITE_TO_X.INTERVIEW,
act: "no",
deadline: "2030-10-10",
appId: "test-id",
}}
/>
)
expect(screen.getByText(t("inviteToInterviewPage.withdrawn.title"))).toBeInTheDocument()
})
it("shows the withdrawn page without the body when the deadline is passed", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: INVITE_TO_X.INTERVIEW,
act: "no",
deadline: "2020-10-10",
appId: "test-id",
}}
/>
)
expect(screen.queryByText(t("inviteToInterviewPage.withdrawn.body"))).not.toBeInTheDocument()
})
it("shows the waitlist page when the action is contact", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: INVITE_TO_X.INTERVIEW,
act: "contact",
deadline: "2030-10-10",
appId: "test-id",
}}
/>
)
expect(screen.getByText(t("inviteToInterviewPage.waitlist.title"))).toBeInTheDocument()
})
})
5 changes: 5 additions & 0 deletions app/javascript/modules/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,8 @@ export const RELATIONSHIP_OPTIONS = [
{ value: "Friend", label: "label.friend" },
{ value: "Other", label: "label.Other" },
]

export enum INVITE_TO_X {
APPLY = "I2A",
INTERVIEW = "I2I",
}
82 changes: 82 additions & 0 deletions app/javascript/pages/inviteTo/InviteToContactMeLater.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from "react"
import { t, LoadingOverlay } from "@bloom-housing/ui-components"
import { Card, Heading } from "@bloom-housing/ui-seeds"
import styles from "./invite-to.module.scss"
import { renderMarkup, localizedFormat } from "../../util/languageUtil"
import RailsSaleListing from "../../api/types/rails/listings/RailsSaleListing"
import FormLayout from "../../layouts/FormLayout"
import InviteToApplyHeader from "./InviteToHeader"
import InviteToLeasingAgentInfo from "./InviteToLeasingAgentInfo"
import { INVITE_TO_X } from "../../modules/constants"

interface InviteToContactMeLaterProps {
Comment thread
cliu02 marked this conversation as resolved.
type: INVITE_TO_X
listing: RailsSaleListing | null
deadline: string
submitPreviewLink: string
}

const InviteToContactMeLater = ({
type,
listing,
deadline,
submitPreviewLink,
}: InviteToContactMeLaterProps) => {
return (
<FormLayout>
<LoadingOverlay isLoading={!listing}>
<InviteToApplyHeader listing={listing} />
<Card className={styles.responseCard}>
<Card.Header className={styles.responseHeader} divider="flush">
<Heading priority={2} size="2xl" className={styles.responseHeading}>
{type === INVITE_TO_X.APPLY
? t("inviteToApplyPage.contact.title", {
listingName: listing?.Building_Name_for_Process,
})
: t("inviteToInterviewPage.waitlist.title")}
</Heading>
<p className={styles.responseSubtitle}>
{type === INVITE_TO_X.APPLY && t("inviteToApplyPage.contact.subtitle")}
{type === INVITE_TO_X.INTERVIEW &&
renderMarkup(
`${t("inviteToInterviewPage.waitlist.subtitle", {
listingName: listing?.Building_Name_for_Process,
link: `/listings/${listing?.Id}`,
})}\n\n${t("inviteToInterviewPage.waitlist.p1")}`,
"<strong></strong><a></a>"
)}
</p>
</Card.Header>
<Card.Section className={styles.responseSection}>
<Heading priority={3} size="xl" className={styles.responseHeading}>
{t("inviteToApplyPage.leasingAgent.p1")}
</Heading>
<p>{t("inviteToApplyPage.leasingAgent.p2")}</p>
<p>{t("inviteToApplyPage.leasingAgent.p3")}</p>
<InviteToLeasingAgentInfo listing={listing} />
{type === INVITE_TO_X.APPLY &&
renderMarkup(
t("inviteToApplyPage.submitYourInfo", {
listingName: listing?.Building_Name_for_Process,
link: submitPreviewLink,
deadline: localizedFormat(deadline, "ll"),
}),
"<strong></strong><a></a>"
)}
{type === INVITE_TO_X.INTERVIEW &&
renderMarkup(
t("inviteToInterviewPage.waitlist.body", {
listingName: listing?.Building_Name_for_Process,
link: submitPreviewLink,
deadline: localizedFormat(deadline, "ll"),
}),
"<strong></strong><a></a>"
)}
</Card.Section>
</Card>
</LoadingOverlay>
</FormLayout>
)
}

export default InviteToContactMeLater
7 changes: 4 additions & 3 deletions app/javascript/pages/inviteTo/InviteToLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import styles from "./invite-to.module.scss"
import InviteToLeasingAgentInfo from "./InviteToLeasingAgentInfo"
import Layout from "../../layouts/Layout"
import { INVITE_TO_X } from "../../modules/constants"

const InviteToHeader = ({
listing,
Expand Down Expand Up @@ -40,9 +41,9 @@ const DeadlineBanner = ({
}: {
deadline: string
listing: RailsSaleListing
type: string
type: INVITE_TO_X
}) => {
if (type === "I2A") {
if (type === INVITE_TO_X.APPLY) {
return (
<Message
fullwidth
Expand Down Expand Up @@ -110,7 +111,7 @@ const InviteToSidebarBlock = ({

interface InviteToLayoutProps {
listing: RailsSaleListing
type: "I2I" | "I2A"
type: INVITE_TO_X
title?: string
subtitle?: string
children: React.ReactNode
Expand Down
Loading
Loading