-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat/my applications #1079
Merged
Merged
feat/my applications #1079
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
a2ce2c6
First pass at my applications
emilyjablonski c10c622
Displaying individual applications
emilyjablonski 7ca8ee0
Reenabl edit mode
emilyjablonski 674a6d8
Cypress test
emilyjablonski 07d0a20
PR style feedback
emilyjablonski 20b11c5
Fix unit test
emilyjablonski 99e98d5
Visually handle a loss of access
emilyjablonski 57ce5da
adjusted dash block and status block type sizes
2560ad8
PR Feedback
emilyjablonski ad94b00
Merge branch 'feat/my-applications' of https://github.com/bloom-housi…
emilyjablonski File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
describe("My applications page", function () { | ||
it("renders the my applications page", function () { | ||
cy.visit("/account/applications") | ||
cy.getByID("email").type("admin@example.com") | ||
cy.getByID("password").type("abcdef") | ||
cy.get("button").contains("Sign In").click() | ||
cy.visit("/account/applications") | ||
cy.contains("My Applications") | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import React, { useContext, useEffect, useState } from "react" | ||
import { Application, Listing } from "@bloom-housing/backend-core/types" | ||
import { AppStatusItem, ApiClientContext } from "@bloom-housing/ui-components" | ||
|
||
interface AppStatusItemWrapperProps { | ||
application: Application | ||
} | ||
|
||
const AppStatusItemWrapper = (props: AppStatusItemWrapperProps) => { | ||
const { listingsService } = useContext(ApiClientContext) | ||
const [listing, setListing] = useState<Listing>() | ||
|
||
useEffect(() => { | ||
listingsService | ||
?.retrieve({ listingId: props.application.listing.id }) | ||
.then((retrievedListing) => { | ||
setListing(retrievedListing) | ||
}) | ||
.catch((err) => console.error(`Error fetching listing: ${err}`)) | ||
}, [listingsService, props.application]) | ||
|
||
return listing ? ( | ||
<AppStatusItem application={props.application} listing={listing} key={props.application.id} /> | ||
) : ( | ||
// Potential for a loading state here | ||
<></> | ||
) | ||
} | ||
|
||
export { AppStatusItemWrapper as default, AppStatusItemWrapper } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import React, { useEffect, useState, useContext } from "react" | ||
import { | ||
ApiClientContext, | ||
RequireLogin, | ||
t, | ||
UserContext, | ||
FormCard, | ||
dateToString, | ||
} from "@bloom-housing/ui-components" | ||
import Link from "next/link" | ||
import FormSummaryDetails from "../../src/forms/applications/FormSummaryDetails" | ||
import FormsLayout from "../../layouts/forms" | ||
import { Application, Listing } from "@bloom-housing/backend-core/types" | ||
import { useRouter } from "next/router" | ||
|
||
export default () => { | ||
const router = useRouter() | ||
const applicationId = router.query.id as string | ||
const { applicationsService, listingsService } = useContext(ApiClientContext) | ||
const { profile } = useContext(UserContext) | ||
const [application, setApplication] = useState<Application>() | ||
const [listing, setListing] = useState<Listing>() | ||
const [unauthorized, setUnauthorized] = useState(false) | ||
const [noApplication, setNoApplication] = useState(false) | ||
useEffect(() => { | ||
if (profile) { | ||
applicationsService | ||
.retrieve({ applicationId }) | ||
.then((app) => { | ||
setApplication(app) | ||
listingsService | ||
?.retrieve({ listingId: app.listing.id }) | ||
.then((retrievedListing) => { | ||
setListing(retrievedListing) | ||
}) | ||
.catch((err) => { | ||
console.error(`Error fetching listing: ${err}`) | ||
}) | ||
}) | ||
.catch((err) => { | ||
console.error(`Error fetching application: ${err}`) | ||
const { status } = err.response || {} | ||
if (status === 404) { | ||
setNoApplication(true) | ||
} | ||
if (status === 403) { | ||
setUnauthorized(true) | ||
} | ||
}) | ||
} | ||
}, [profile, applicationId, applicationsService, listingsService]) | ||
|
||
return ( | ||
<> | ||
<RequireLogin signInPath="/sign-in" signInMessage={t("t.loginIsRequired")}> | ||
<FormsLayout> | ||
{noApplication && ( | ||
<FormCard header={t("account.application.error")}> | ||
<p className="field-note mb-5">{t("account.application.noApplicationError")}</p> | ||
<a href={`applications`} className="button is-small"> | ||
{t("account.application.return")} | ||
</a> | ||
</FormCard> | ||
)} | ||
{unauthorized && ( | ||
<FormCard header={t("account.application.error")}> | ||
<p className="field-note mb-5">{t("account.application.noAccessError")}</p> | ||
<a href={`applications`} className="button is-small"> | ||
{t("account.application.return")} | ||
</a> | ||
</FormCard> | ||
)} | ||
{application && ( | ||
<> | ||
<FormCard header={t("account.application.confirmation")}> | ||
<div className="py-2"> | ||
{listing && ( | ||
<Link | ||
href={`listing/id=${listing.id}`} | ||
as={`${origin}/listing/${listing.id}/${listing.urlSlug}`} | ||
> | ||
<a className="lined text-tiny"> | ||
{t("application.confirmation.viewOriginalListing")} | ||
</a> | ||
</Link> | ||
)} | ||
</div> | ||
</FormCard> | ||
|
||
<FormCard> | ||
<div className="form-card__lead border-b"> | ||
<h2 className="form-card__title is-borderless"> | ||
{t("application.confirmation.informationSubmittedTitle")} | ||
</h2> | ||
<p className="field-note mt-4 text-center"> | ||
{t("application.confirmation.submitted")} | ||
{dateToString(application.submissionDate)} | ||
</p> | ||
</div> | ||
<div className="form-card__group text-center"> | ||
<h3 className="form-card__paragraph-title"> | ||
{t("application.confirmation.lotteryNumber")} | ||
</h3> | ||
|
||
<p className="font-serif text-3xl my-0">{application.id}</p> | ||
</div> | ||
|
||
<FormSummaryDetails application={application} /> | ||
|
||
<div className="form-card__pager hide-for-print"> | ||
<div className="form-card__pager-row py-6"> | ||
<a href="#" className="lined text-tiny" onClick={() => window.print()}> | ||
{t("application.confirmation.printCopy")} | ||
</a> | ||
</div> | ||
</div> | ||
</FormCard> | ||
</> | ||
)} | ||
</FormsLayout> | ||
</RequireLogin> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,56 @@ | ||
import React, { useEffect, useState, Fragment } from "react" | ||
import React, { useEffect, useState, Fragment, useContext } from "react" | ||
import Head from "next/head" | ||
import { | ||
AppearanceBorderType, | ||
AppearanceStyleType, | ||
AppStatusItem, | ||
Button, | ||
ApiClientContext, | ||
DashBlock, | ||
DashBlocks, | ||
HeaderBadge, | ||
LinkButton, | ||
MetaTags, | ||
Modal, | ||
RequireLogin, | ||
t, | ||
UserContext, | ||
} from "@bloom-housing/ui-components" | ||
import Layout from "../../layouts/application" | ||
import moment from "moment" | ||
import { Application, ArcherListing } from "@bloom-housing/backend-core/types" | ||
import { PaginatedApplication } from "@bloom-housing/backend-core/types" | ||
import { AppStatusItemWrapper } from "./AppStatusItemWrapper" | ||
|
||
export default () => { | ||
const [applications, setApplications] = useState([]) | ||
const [deletingApplication, setDeletingApplication] = useState(null) | ||
const listing = Object.assign({}, ArcherListing) | ||
const { applicationsService } = useContext(ApiClientContext) | ||
const { profile } = useContext(UserContext) | ||
const [applications, setApplications] = useState<PaginatedApplication>() | ||
const [error, setError] = useState(null) | ||
|
||
useEffect(() => { | ||
// applicationsService.list().then((apps) => { | ||
// setApplications(apps) | ||
// }) | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const listing = {} as any | ||
const application = {} as Application | ||
listing.applicationDueDate = moment().add(10, "days").format() | ||
application.listing = listing | ||
// TODO: Fix the types here (and probably this shouldn't come from the frontend anyway) | ||
// application.updatedAt = moment().toDate() | ||
setApplications([application]) | ||
}, []) | ||
if (profile) { | ||
applicationsService | ||
.list({ userId: profile.id }) | ||
.then((apps) => { | ||
setApplications(apps) | ||
}) | ||
.catch((err) => { | ||
console.error(`Error fetching applications: ${err}`) | ||
setError(`${err}`) | ||
}) | ||
} | ||
}, [profile, applicationsService]) | ||
|
||
const noApplicationsSection = () => { | ||
return error ? ( | ||
<div className="p-8"> | ||
<h2 className="pb-4">{`${t("account.errorFetchingApplications")}`}</h2> | ||
</div> | ||
) : ( | ||
<div className="p-8"> | ||
<h2 className="pb-4">{t("account.noApplications")}</h2> | ||
<LinkButton href="/listings">{t("listings.browseListings")}</LinkButton> | ||
</div> | ||
) | ||
} | ||
|
||
const noApplicationsSection = ( | ||
<div className="p-8"> | ||
<h2 className="pb-4">It looks like you haven't applied to any listings yet.</h2> | ||
<LinkButton href="/listings">{t("listings.browseListings")}</LinkButton> | ||
</div> | ||
) | ||
const modalActions = [ | ||
<Button | ||
styleType={AppearanceStyleType.primary} | ||
onClick={() => { | ||
// applicationsService.delete(deletingApplication.id).then(() => { | ||
const newApplications = [...applications] | ||
const deletedAppIndex = applications.indexOf(deletingApplication, 0) | ||
delete newApplications[deletedAppIndex] | ||
setDeletingApplication(null) | ||
setApplications(newApplications) | ||
// }) | ||
}} | ||
> | ||
{t("t.delete")} | ||
</Button>, | ||
<Button | ||
styleType={AppearanceStyleType.secondary} | ||
border={AppearanceBorderType.borderless} | ||
onClick={() => { | ||
setDeletingApplication(null) | ||
}} | ||
> | ||
{t("t.cancel")} | ||
</Button>, | ||
] | ||
return ( | ||
<> | ||
<RequireLogin signInPath="/sign-in" signInMessage={t("t.loginIsRequired")}> | ||
<Modal | ||
open={deletingApplication} | ||
title={t("application.deleteThisApplication")} | ||
ariaDescription={t("application.deleteThisApplication")} | ||
actions={modalActions} | ||
hideCloseIcon | ||
/> | ||
<Layout> | ||
<Head> | ||
<title>{t("nav.myApplications")}</title> | ||
|
@@ -88,17 +61,13 @@ export default () => { | |
<DashBlocks> | ||
<DashBlock title={t("account.myApplications")} icon={<HeaderBadge />}> | ||
<Fragment> | ||
{applications.map((application) => ( | ||
<AppStatusItem | ||
key={application.id} | ||
status="inProgress" | ||
listing={listing} | ||
application={application} | ||
setDeletingApplication={setDeletingApplication} | ||
/> | ||
))} | ||
{applications.length == 0 && noApplicationsSection} | ||
{applications && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could probably use optionals here to condense it a bit? |
||
applications.items.length > 0 && | ||
applications.items.map((application, index) => ( | ||
<AppStatusItemWrapper key={index} application={application} /> | ||
))} | ||
</Fragment> | ||
{!applications && noApplicationsSection()} | ||
</DashBlock> | ||
</DashBlocks> | ||
</div> | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 403/404 state hooks are neat. I wonder if we could make a standard hook that would provide this and set the appropriate state on either 404 or 403, so we could use it on other pages. This might be good to review as well if we move towards using SWR everywhere: https://swr.vercel.app/docs/error-handling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored this to be a little cleaner, but would hope we could accomplish this in another ticket?