Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 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
8714824
chore: renaming
cliu02 Apr 16, 2026
99eb4a2
Merge branch 'main' into DAH-4006-i2i-next-steps
cliu02 Apr 20, 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
2 changes: 1 addition & 1 deletion app/assets/json/translations/react/en.json
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Info Sensitive Data Finding

PII

More Details
Attribute Value
Data Classifier PII/Name
Data Classifier ID BUILTIN-125

Sampled Examples

Key Value
assistance.contact.helpLine.title1 Frc*o
assistance.contact.questionsAboutListings.title1 D****A

Rule ID: BUILTIN-125


To ignore this finding as an exception, reply to this conversation with #wiz_ignore reason

If you'd like to ignore this finding in all future scans, add an exception in the .wiz file (learn more) or create an Ignore Rule (learn more).


To get more details on how to remediate this issue using AI, reply to this conversation with #wiz remediate

Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@
"inviteToInterviewPage.leasingAgent.p2": "We sent you an email confirming your answer. Follow the link in your email if you need to change it.",
"inviteToInterviewPage.leasingAgent.p3": "Or, contact the leasing agent:",
"inviteToInterviewPage.submitYourInfo.deadline": "%{day} at 11:59 pm Pacific Time",
"inviteToInterviewPage.submitYourInfo.deadlineInfo": "<strong>The deadline to schedule your appointment has passed:</strong> %{day} at 11:59 pm Pacific Time. If you are still interested in an apartment at %{listingName}, contact the leasing agent.",
"inviteToInterviewPage.submitYourInfo.deadlineInfo": "<strong>The deadline to schedule your appointment has passed:</strong> %{day} at 11:59 pm Pacific Time.\n\nIf you are still interested in an apartment at %{listingName}, contact the leasing agent.",
"inviteToInterviewPage.submitYourInfo.deadlinePassed": "The deadline to schedule your appointment has passed: ",
"inviteToInterviewPage.submitYourInfo.prepare.p1": "If you already work with a housing counselor, contact them for help with your application.",
"inviteToInterviewPage.submitYourInfo.prepare.p2": "Otherwise, <a target='_blank' href='https://www.homesanfrancisco.org'>HomeSF</a> can connect you with someone who can help.",
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/api/v1/invite_to_response_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def record_response
"action=#{action}",
)
else
DahliaBackend::MessageService.send_invite_to_response(
DahliaBackend::MessageService.send_invite_to_apply_response(
deadline,
application_id,
application_number,
Expand Down
33 changes: 17 additions & 16 deletions app/controllers/invite_to_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
# Invite to X controller
class InviteToController < ApplicationController
def index
decoded_params = decode_token(params[:t])
if decoded_params.is_a?(String)
redirect_to decoded_params
return
end
# 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 ||= params
@invite_to_props = props(decoded_params)
# Get file upload URL for application
if decoded_params['appId'].present?
application = Force::ShortFormService.get(decoded_params['appId'])
@invite_to_props = @invite_to_props.merge(
fileUploadUrl: application['uploadURL'],
url: application['uploadURL'],
)
end
if decoded_params['applicationNumber'].present?
application = Force::ShortFormService.get(decoded_params['applicationNumber'])
@invite_to_props = @invite_to_props.merge(
fileUploadUrl: application['uploadURL'],
url: application['uploadURL'],
)
end
record_response(decoded_params)
Expand All @@ -37,31 +38,31 @@ def props(decoded_params = params)
url_params = {
type: decoded_params['type'],
deadline: decoded_params['deadline'],
action: decoded_params['action'] || decoded_params['response'],
inviteAction: decoded_params['inviteAction'] || decoded_params['response'],
Comment thread
cliu02 marked this conversation as resolved.
Outdated
appId: decoded_params['appId'] || decoded_params['applicationNumber'],
}

{
assetPaths: static_asset_paths,
urlParams: url_params,
submitPreviewLinkTokenParam: encode_token(url_params.except(:action, :response)),
submitPreviewLinkTokenParam: encode_token(url_params.except(:inviteAction, :response)),
}.compact
end

def record_response(decoded_params)
deadline = decoded_params['deadline']
response = decoded_params['response']
application_number = decoded_params['applicationNumber']
action = decoded_params['action']
invite_action = decoded_params['inviteAction']
app_id = decoded_params['appId']

if (action.blank? && response.blank?) || (deadline && deadline_has_passed?(deadline)) || language_change?
if (invite_action.blank? && response.blank?) || (deadline && deadline_has_passed?(deadline)) || language_change?
Rails.logger.info(
'InviteToController#record_response: *NOT* recording ' \
"deadline=#{deadline}, " \
"app_id=#{app_id}, " \
"application_number=#{application_number}, " \
"action=#{action.inspect}, " \
"inviteAction=#{invite_action.inspect}, " \
"response=#{response.inspect}",
)
return
Expand All @@ -72,16 +73,16 @@ def record_response(decoded_params)
"deadline=#{deadline}, " \
"app_id=#{app_id}, " \
"application_number=#{application_number}, " \
"action=#{action}, " \
"inviteAction=#{invite_action}, " \
"response=#{response}",
)

DahliaBackend::MessageService.send_invite_to_response(
DahliaBackend::MessageService.send_invite_to_apply_response(
deadline,
app_id,
application_number,
response,
action,
invite_action,
params['id'], # listing_id
)
end
Expand All @@ -99,7 +100,7 @@ def decode_token(token)
# "data" => {
# "type" => "I2I",
# "deadline" => "1999-12-31",
# "action" => "yes",
# "inviteAction" => "yes",
# "appId" => "12345678"
# },
# "iat" => 946512000
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/__tests__/api/inviteToApplyApiService.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { post } from "../../api/apiService"
import { recordResponse } from "../../api/inviteToApplyApiService"
import { recordResponse } from "../../api/inviteToApiService"

jest.mock("../../api/apiService", () => ({
post: jest.fn(),
}))

describe("inviteToApplyApiService", () => {
describe("inviteToApiService", () => {
describe("recordResponse", () => {
it("calls apiService post", async () => {
post as jest.Mock
Expand Down
24 changes: 12 additions & 12 deletions app/javascript/__tests__/pages/invite-to-apply.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import InviteToPage from "../../pages/inviteTo/invite-to"
import { renderAndLoadAsync } from "../__util__/renderUtils"
import { localizedFormat } from "../../util/languageUtil"
import { getListing } from "../../api/listingApiService"
import { recordResponse } from "../../api/inviteToApplyApiService"
import { recordResponse } from "../../api/inviteToApiService"
import { ConfigContext } from "../../lib/ConfigContext"

jest.mock("../../api/listingApiService")
jest.mock("../../api/inviteToApplyApiService", () => ({
jest.mock("../../api/inviteToApiService", () => ({
recordResponse: jest.fn(),
}))
jest.mock("../../hooks/useFeatureFlag", () => ({
Expand Down Expand Up @@ -99,7 +99,7 @@ describe("Invite to Apply", () => {
assetPaths={"/"}
urlParams={{
type: "I2A",
action: "contact",
inviteAction: "contact",
deadline: mockPastDeadline,
}}
Comment thread
cliu02 marked this conversation as resolved.
/>
Expand All @@ -119,12 +119,12 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "no",
inviteAction: "no",
appId: "0000",
}}
/>
)
const submitPreviewLink = `/en/listings/${mockListing.Id}/next-steps?appId=0000&deadline=${mockFutureDeadline}`
const submitPreviewLink = `/en/listings/${mockListing.Id}/next-steps?appId=0000&deadline=${mockFutureDeadline}&type=I2A`

expect(screen.getByText(t("inviteToApplyPage.withdrawn.title"))).toBeInTheDocument()
expect(screen.getByText(mockListing.Building_Name_for_Process)).toBeInTheDocument()
Expand All @@ -147,13 +147,13 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "contact",
inviteAction: "contact",
appId: "0000",
}}
/>
)

const submitPreviewLink = `/en/listings/${mockListing.Id}/next-steps?appId=0000&deadline=${mockFutureDeadline}`
const submitPreviewLink = `/en/listings/${mockListing.Id}/next-steps?appId=0000&deadline=${mockFutureDeadline}&type=I2A`

expect(
screen.getByText(
Expand Down Expand Up @@ -184,7 +184,7 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockPastDeadline,
action: "yes",
inviteAction: "yes",
appId: "0000",
}}
/>
Expand All @@ -199,7 +199,7 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "yes",
inviteAction: "yes",
}}
/>
)
Expand All @@ -219,7 +219,7 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "yes",
inviteAction: "yes",
appId: "a0o123",
}}
/>
Expand All @@ -244,7 +244,7 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "yes",
inviteAction: "yes",
appId: "a0o123",
}}
/>
Expand All @@ -266,7 +266,7 @@ describe("Invite to Apply", () => {
urlParams={{
type: "I2A",
deadline: mockFutureDeadline,
action: "yes",
inviteAction: "yes",
}}
/>
)
Expand Down
73 changes: 63 additions & 10 deletions app/javascript/__tests__/pages/invite-to-interview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import React from "react"
import { screen } from "@testing-library/react"
import "@testing-library/jest-dom"
import { t } from "@bloom-housing/ui-components"
import { userEvent } from "@testing-library/user-event"
import InviteToPage from "../../pages/inviteTo/invite-to"
import { renderAndLoadAsync } from "../__util__/renderUtils"
import { ConfigContext } from "../../lib/ConfigContext"
import { getListing } from "../../api/listingApiService"
import { recordResponse } from "../../api/inviteToApiService"

jest.mock("../../api/listingApiService")
jest.mock("../../api/inviteToApplyApiService", () => ({
jest.mock("../../api/inviteToApiService", () => ({
recordResponse: jest.fn(),
}))
jest.mock("../../hooks/useFeatureFlag", () => ({
Expand All @@ -23,7 +25,7 @@ jest.mock("../../hooks/useFeatureFlag", () => ({
unleashFlag: true,
variant: {
payload: {
value: "listing-id",
value: JSON.stringify({ enabled_listings: ["listing-id"] }),
},
},
}),
Expand All @@ -33,16 +35,16 @@ const mockListing = {
Id: "listing-id",
Name: "Test Listing",
Building_Name_for_Process: "Test Building",
Leaseup_Appointment_Scheduling_URL: "test-link",
Leasing_Agent_Name: "test-agent",
Leasing_Agent_Phone: "123-456-7890",
Leasing_Agent_Email: "test-agent@test-agent.com",
Office_Hours: "9-5 M-F",
File_Upload_URL: "https://example.com/upload",
translations: {},
Listing_Images: [
{
Image_URL: "example-image-url",
Image_Description: "example-image-alt",
Image_URL: "image-url",
Image_Description: "image-alt",
},
],
}
Expand Down Expand Up @@ -82,7 +84,7 @@ describe("Invite to Interview", () => {
afterEach(() => {
jest.restoreAllMocks()
})
it("renders the documents page when documentsPath is true", async () => {
it("renders the documents page", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
Expand All @@ -93,21 +95,72 @@ describe("Invite to Interview", () => {
/>
)
expect(
screen.getByText(t("inviteToInterviewPage.documents.checkWhatYouNeed.bringDocuments.title"))
screen.getByText(t("inviteToInterviewPage.documents.checkWhatYouNeed.title"))
).toBeInTheDocument()
})

it("renders the deadline passed page if the deadline is passed", async () => {
it("renders the next steps page", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
deadline: "2020-10-10",
inviteAction: "yes",
deadline: "2030-10-10",
appId: "test-id",
}}
documentsPath={false}
/>
)
expect(screen.getByText(t("inviteToInterviewPage.deadlinePassed.title"))).toBeInTheDocument()
expect(screen.getByText(t("inviteToInterviewPage.submitYourInfo.subtitle"))).toBeInTheDocument()
})

it("shows the deadline passed banner if deadline is passed", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
inviteAction: "yes",
deadline: "2020-10-10",
appId: "test-id",
}}
/>
)
expect(screen.getByTestId("deadline-passed-banner")).toBeInTheDocument()
})

it("shows the deadline banner when deadline is not passed", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
inviteAction: "yes",
deadline: "2030-10-10",
appId: "test-id",
}}
/>
)
expect(screen.getByTestId("deadline-not-passed-banner")).toBeInTheDocument()
})

it("records the response when the scheduling button is clicked", async () => {
await renderWithContext(
<InviteToPage
assetPaths={"/"}
urlParams={{
type: "I2I",
deadline: "2030-10-10",
inviteAction: "yes",
appId: "test-id",
}}
/>
)
const button = screen.getByRole("button", {
name: t("inviteToInterviewPage.submitYourInfo.whatToDo.step1.p2"),
})
await userEvent.click(button)
expect(recordResponse).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { post } from "./apiService"
export const recordResponse = async (record: {
listingId: string
appId: string
// Deprecated I2A pilot - remove in DAH-4045
applicationNumber: string
deadline: string
action: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type BaseRailsListing = {
In_Lottery: number
Information_Sessions?: ListingEvent[]
LastModifiedDate: string
Leaseup_Appointment_Scheduling_URL?: string
Leasing_Agent_City?: string
Leasing_Agent_Email?: string
Leasing_Agent_Name?: string
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/pages/inviteTo/InviteToDeadlinePassed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Card, Heading } from "@bloom-housing/ui-seeds"
import styles from "./invite-to.module.scss"
import RailsSaleListing from "../../api/types/rails/listings/RailsSaleListing"
import FormLayout from "../../layouts/FormLayout"
import InviteToApplyLeasingAgentInfo from "./InviteToLeasingAgentInfo"
import InviteToLeasingAgentInfo from "./InviteToLeasingAgentInfo"
import InviteToApplyHeader from "./InviteToHeader"

interface InviteToDeadlinePassedProps {
Expand All @@ -31,7 +31,7 @@ const InviteToDeadlinePassed = ({ listing }: InviteToDeadlinePassedProps) => {
listingName: listing?.Building_Name_for_Process,
})}
</Heading>
<InviteToApplyLeasingAgentInfo listing={listing} />
<InviteToLeasingAgentInfo listing={listing} />
</Card.Section>
</Card>
</LoadingOverlay>
Expand Down
Loading
Loading