Skip to content

Commit

Permalink
Render React LOC content from session data. (#1535)
Browse files Browse the repository at this point in the history
This builds upon #1534 by rendering the React version of the LOC content from the user's session data and exposing it at `/loc/react-letter.pdf` (the `react-` prefix means it won't conflict with the Django-based renderer, which is still what's used in production).
  • Loading branch information
toolness authored Jun 10, 2020
1 parent 1d42be3 commit c372fed
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 22 deletions.
7 changes: 7 additions & 0 deletions frontend/lib/justfix-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@ function createLetterOfComplaintRouteInfo(prefix: string) {
sampleLetterContent: createLetterStaticPageRouteInfo(
`${prefix}/sample-letter`
),
/** Letter content for the user (HTML and PDF versions). */
letterContent: createLetterStaticPageRouteInfo(
// We're calling this 'react-letter' for now because we
// don't want to collide with the Django-rendered version
// of the letter.
`${prefix}/react-letter`
),
splash: `${prefix}/splash`,
welcome: `${prefix}/welcome`,
...createJustfixCrossSiteVisitorRoutes(prefix),
Expand Down
64 changes: 63 additions & 1 deletion frontend/lib/loc/letter-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ import {
BaseLetterContentProps,
letter,
baseSampleLetterProps,
getBaseLetterContentPropsFromSession,
TransformSession,
} from "../util/letter-content-util";
import { createLetterStaticPageWithQuery } from "../static-page/letter-static-page";
import {
IssueAreaChoice,
getIssueAreaChoiceLabels,
IssueAreaChoices,
} from "../../../common-data/issue-area-choices";
import {
IssueChoice,
getIssueChoiceLabels,
} from "../../../common-data/issue-choices";
import { friendlyUTCDate } from "../util/date-util";
import { AllSessionInfo } from "../queries/AllSessionInfo";
import { issuesForArea, customIssuesForArea } from "../issues/issues";

const HEAT_ISSUE_CHOICES = new Set<IssueChoice>([
"HOME__NO_HEAT",
Expand All @@ -34,7 +39,7 @@ type AreaIssues = {
type LocContentProps = BaseLetterContentProps & {
issues: AreaIssues[];
accessDates: GraphQLDate[];
hasCalled311: boolean;
hasCalled311: boolean | null;
};

const LetterTitle: React.FC<LocContentProps> = (props) => (
Expand Down Expand Up @@ -203,6 +208,63 @@ export const LocContent: React.FC<LocContentProps> = (props) => (

const LocStaticPage = createLetterStaticPageWithQuery(LocContent);

function getIssuesFromSession(session: AllSessionInfo): AreaIssues[] {
const result: AreaIssues[] = [];

for (let area of IssueAreaChoices) {
const issueChoices: Issue[] = issuesForArea(
area,
session.issues as IssueChoice[]
).map((choice) => ({
kind: "choice",
choice,
}));
const customIssues: Issue[] = customIssuesForArea(
area,
session.customIssuesV2 || []
).map((ci) => ({
kind: "custom",
value: ci.description,
}));
const issues: Issue[] = [...issueChoices, ...customIssues];
if (issues.length) {
result.push({ area, issues });
}
}

return result;
}

export function getLocContentPropsFromSession(
session: AllSessionInfo
): LocContentProps | null {
const baseProps = getBaseLetterContentPropsFromSession(session);
const onb = session.onboardingInfo;

if (!(baseProps && onb)) {
return null;
}

return {
...baseProps,
issues: getIssuesFromSession(session),
accessDates: session.accessDates,
hasCalled311: onb.hasCalled311,
};
}

export const LocForUserPage: React.FC<{ isPdf: boolean }> = ({ isPdf }) => (
<TransformSession transformer={getLocContentPropsFromSession}>
{(props) => (
<LocStaticPage
{...props}
isPdf={isPdf}
title="Your Letter of Complaint"
/>
)}
</TransformSession>
);

export const locSampleProps: LocContentProps = {
...baseSampleLetterProps,
issues: [
Expand Down
6 changes: 5 additions & 1 deletion frontend/lib/loc/letter-of-complaint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { createJustfixCrossSiteVisitorSteps } from "../justfix-cross-site-visito
import { ProgressStepProps } from "../progress/progress-step-route";
import { assertNotNull } from "../util/util";
import { Switch, Route } from "react-router-dom";
import { LocSamplePage } from "./letter-content";
import { LocSamplePage, LocForUserPage } from "./letter-content";
import { createLetterStaticPageRoutes } from "../static-page/routes";

export const Welcome: React.FC<ProgressStepProps> = (props) => {
Expand Down Expand Up @@ -149,6 +149,10 @@ const LetterOfComplaintRoutes: React.FC<{}> = () => (
JustfixRoutes.locale.loc.sampleLetterContent,
LocSamplePage
)}
{createLetterStaticPageRoutes(
JustfixRoutes.locale.loc.letterContent,
LocForUserPage
)}
<Route component={LetterOfComplaintProgressRoutes} />
</Switch>
);
Expand Down
35 changes: 35 additions & 0 deletions frontend/lib/loc/tests/__snapshots__/letter-content.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,38 @@ exports[`<LocContent> works 1`] = `
</div>
</div>
`;
exports[`getLocContentPropsFromSession() returns expected value when user is logged in 1`] = `
Object {
"accessDates": Array [],
"aptNumber": "",
"city": "Brooklyn",
"email": "boop@jones.net",
"firstName": "Boop",
"hasCalled311": null,
"issues": Array [
Object {
"area": "HOME",
"issues": Array [
Object {
"choice": "HOME__RATS",
"kind": "choice",
},
Object {
"kind": "custom",
"value": "Rain enters through roof",
},
],
},
],
"landlordAddress": "123 Cloud City Drive
Bespin, NY 12345",
"landlordEmail": "landlordo@calrissian.net",
"landlordName": "Landlordo Calrissian",
"lastName": "Jones",
"phoneNumber": "5551234567",
"state": "NY",
"street": "150 Court St",
"zipCode": "11201",
}
`;
23 changes: 22 additions & 1 deletion frontend/lib/loc/tests/letter-content.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import React from "react";
import ReactTestingLibraryPal from "../../tests/rtl-pal";
import { LocContent, locSampleProps } from "../letter-content";
import {
LocContent,
locSampleProps,
getLocContentPropsFromSession,
} from "../letter-content";
import { newSb } from "../../tests/session-builder";

describe("<LocContent>", () => {
it("works", () => {
const pal = new ReactTestingLibraryPal(<LocContent {...locSampleProps} />);
expect(pal.rr.container).toMatchSnapshot();
});
});

const filledUser = newSb()
.withLoggedInJustfixUser()
.withLandlordDetails()
.withCustomIssue()
.withIssues();

describe("getLocContentPropsFromSession()", () => {
it("returns null when user is logged out", () => {
expect(getLocContentPropsFromSession(newSb().value)).toBe(null);
});

it("returns expected value when user is logged in", () => {
expect(getLocContentPropsFromSession(filledUser.value)).toMatchSnapshot();
});
});
24 changes: 7 additions & 17 deletions frontend/lib/norent/letter-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
letter,
baseSampleLetterProps,
getBaseLetterContentPropsFromSession,
TransformSession,
} from "../util/letter-content-util";
import { makeStringHelperFC } from "../util/string-helper";

Expand Down Expand Up @@ -71,7 +72,7 @@ export const NorentLetterTranslation: React.FC<{}> = () => {
return (
<article className="message jf-letter-translation">
<div className="message-body has-background-grey-lighter has-text-left has-text-weight-light">
<LetterContentPropsFromSession>
<TransformSession transformer={getNorentLetterContentPropsFromSession}>
{(props) => (
<>
<letter.DearLandlord {...props} />
Expand All @@ -82,25 +83,12 @@ export const NorentLetterTranslation: React.FC<{}> = () => {
</p>
</>
)}
</LetterContentPropsFromSession>
</TransformSession>
</div>
</article>
);
};

const LetterContentPropsFromSession: React.FC<{
children: (lcProps: NorentLetterContentProps) => JSX.Element;
}> = ({ children }) => {
const { session } = useContext(AppContext);
const lcProps = getNorentLetterContentPropsFromSession(session);

if (!lcProps) {
return <p>We don't have enough information to generate a letter yet.</p>;
}

return children(lcProps);
};

export const NorentLetterEmailToLandlord: React.FC<NorentLetterContentProps> = (
props
) => (
Expand Down Expand Up @@ -134,7 +122,8 @@ export const NorentLetterEmailToLandlord: React.FC<NorentLetterContentProps> = (
);

export const NorentLetterEmailToLandlordForUser: React.FC<{}> = () => (
<LetterContentPropsFromSession
<TransformSession
transformer={getNorentLetterContentPropsFromSession}
children={(lcProps) => <NorentLetterEmailToLandlord {...lcProps} />}
/>
);
Expand Down Expand Up @@ -252,7 +241,8 @@ function getNorentLetterContentPropsFromSession(
export const NorentLetterForUserStaticPage: React.FC<{ isPdf: boolean }> = ({
isPdf,
}) => (
<LetterContentPropsFromSession
<TransformSession
transformer={getNorentLetterContentPropsFromSession}
children={(lcProps) => (
<NorentLetterStaticPage
{...lcProps}
Expand Down
42 changes: 41 additions & 1 deletion frontend/lib/tests/session-builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import {
BlankNorentScaffolding,
} from "../queries/NorentScaffolding";
import { BlankOnboardingInfo, OnboardingInfo } from "../queries/OnboardingInfo";
import { PhoneNumberAccountStatus, Borough } from "../queries/globalTypes";
import {
PhoneNumberAccountStatus,
Borough,
CustomIssueArea,
} from "../queries/globalTypes";
import { IssueChoice } from "../../../common-data/issue-choices";
import { IssueAreaChoice } from "../../../common-data/issue-area-choices";

/**
* An attempt to encapsulate the creation of a GraphQL session object
Expand Down Expand Up @@ -54,12 +60,46 @@ export class SessionBuilder {
return this.withLoggedInUser().withOnboardingInfo({
address: "150 Court St",
borough: Borough.BROOKLYN,
city: "Brooklyn",
state: "NY",
zipcode: "11201",
agreedToJustfixTerms: true,
});
}

withIssues(issues: IssueChoice[] = ["HOME__RATS"]): SessionBuilder {
return this.with({ issues });
}

withCustomIssue(
area: IssueAreaChoice = "HOME",
description: string = "Rain enters through roof"
): SessionBuilder {
const prevIssues = this.value.customIssuesV2 || [];
return this.with({
customIssuesV2: [
...prevIssues,
{ area: area as CustomIssueArea, description, id: area + description },
],
});
}

withLandlordDetails(): SessionBuilder {
return this.with({
landlordDetails: {
name: "Landlordo Calrissian",
address: "123 Cloud City Drive\nBespin, NY 12345",
primaryLine: "123 Cloud City Drive",
city: "Bespin",
state: "NY",
zipCode: "12345",
email: "landlordo@calrissian.net",
phoneNumber: "5551234567",
isLookedUp: false,
},
});
}

withLoggedInNationalUser(): SessionBuilder {
return this.withLoggedInUser().withOnboardingInfo({
address: "152 W. 32nd St",
Expand Down
17 changes: 16 additions & 1 deletion frontend/lib/util/letter-content-util.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react";
import React, { useContext } from "react";
import { BreaksBetweenLines } from "../ui/breaks-between-lines";
import { formatPhoneNumber } from "../forms/phone-number-form-field";
import { Trans } from "@lingui/macro";
import { friendlyUTCDate, friendlyDate } from "./date-util";
import { AllSessionInfo } from "../queries/AllSessionInfo";
import { assertNotNull } from "./util";
import { makeStringHelperFC } from "./string-helper";
import { AppContext } from "../app-context";

export type BaseLetterContentProps = {
firstName: string;
Expand Down Expand Up @@ -123,6 +124,20 @@ const Title: React.FC<{ children: React.ReactNode }> = (props) => (
</h1>
);

export function TransformSession<T>(props: {
transformer: (session: AllSessionInfo) => T | null;
children: (props: T) => JSX.Element;
}) {
const { session } = useContext(AppContext);
const transformedProps = props.transformer(session);

if (!transformedProps) {
return <p>We don't have enough information to generate this content.</p>;
}

return props.children(transformedProps);
}

export const baseSampleLetterProps: BaseLetterContentProps = {
firstName: "Boop",
lastName: "Jones",
Expand Down

0 comments on commit c372fed

Please sign in to comment.