Skip to content
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

[Issue #3308] View Saved Opportunities on Saved grants page #3992

Merged
merged 16 commits into from
Feb 26, 2025

Conversation

acouch
Copy link
Collaborator

@acouch acouch commented Feb 24, 2025

Summary

Fixes #3308

Time to review: 15 mins

Changes proposed

Add saved opps to Saved grants page:

Desktop

image

Mobile

image

Desktop (no saved opps, no change)

image

Mobile (no saved opps, no change)

image

Context for reviewers

To test:

  1. Start API locally
  2. Sign in
  3. Save an opportunity
  4. View opportunity and "Saved grants" page

@acouch acouch force-pushed the acouch/issue-3308-view-saved-opps branch from 39a096b to 7113ea5 Compare February 24, 2025 15:22
Copy link
Collaborator

@doug-s-nava doug-s-nava left a comment

Choose a reason for hiding this comment

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

a couple of nit picks and one edge case that I'm not sure we want to cover here or handle later.

If you're viewing a search result that contains a saved opportunity, then you log out, the opportunity still shows up as saved. It gets removed on refresh, so maybe we want to just refresh the page on logout?

Screenshot 2025-02-24 at 11 42 39 AM

SavedOpportunity[]
> => {
try {
const session = await getSession();
Copy link
Collaborator

Choose a reason for hiding this comment

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

It may be overkill, but since the session will only be available on the server side (until now it's only been used in route handlers) maybe we should put a "server only" on this file?

Copy link
Collaborator Author

@acouch acouch Feb 25, 2025

Choose a reason for hiding this comment

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

The client component would immediately break with a notice if you tried to import the file, so doesn't seem like a risk that someone would use this incorrectly. server-only designates it is a server function, but also that it should be able to be used directly by the client, which isn't true in this case. Do we want to add server-only to every file that is server side?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agree there's no big risk here since this would break if we tried to use it in the client, but potentially would add some dev time if someone didn't realize that. If we pop "server-only" in here, since it's a utilities file rather than anything react specific, I think we'd gain the ability to break faster and provide an annotation for devs so that the use case for the file is immediately clear. https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#keeping-server-only-code-out-of-the-client-environment

Not entirely necessary, but worth maybe discussing further and thinking about documenting as we make a decision one way or another about how we want to use these designations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I was conflating server-only and use server, the former poisoning clients from importing while the latter endorsing. I added server-only.

return (
<ul className="usa-prose usa-list--unstyled">
{opportunities.map((opportunity) => (
<li key={opportunity?.opportunity_id}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

doesn't seem like this would probably be possible, but if we could have undefined opportunities coming through that could mean non-unique keys here. We could either just assert that the opportunity will always be defined or add a unique fallback value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't like the idea of validating things like that in a client component, and instead enforce that on the data fetching side. I updated it for this case.

export default async function SavedGrants({ params }: LocalizedPageProps) {
const { locale } = await params;
const t = await getTranslations({ locale });
const savedOpportunities = await fetchSavedOpportunities();
Copy link
Collaborator

Choose a reason for hiding this comment

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

[a note for later] is there a way we could move this complexity to the API side so that we don't have to make so many round trips here, or will caching make that more or less a moot point?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would be ideal if the saved opportunity returned enough detail to create the results summary. Since that is changing with the new design we shouldn't make a change there right now.

export default async function SavedGrants({ params }: LocalizedPageProps) {
const { locale } = await params;
const t = await getTranslations({ locale });
const savedOpportunities = await fetchSavedOpportunities();
const opportunities = savedOpportunities.map(async (savedOpportunity) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

something like "opportunityDetailPromises" would be clearer to me here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The getOpportunityDetails returns:

interface OpportunityApiResponse extends APIResponse {
  data: Opportunity;
}

For me it makes sense to describe the type of data we are returning, vs the function used to retrieve it. Does opportuniyPromises make more sense?

Copy link
Collaborator

Choose a reason for hiding this comment

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

tldr; yep, opportunityPromises is great.

I'm willing to go either way, since the promise piece of this is in the type, and explaining the type in the variable name is also generally a no-no, but in cases like this where a synchronous map function returns the promises, and then you await them later, I find it's often confusing at what point we're dealing with unresolved promises and at what point we're dealing with actual data. Somehow making it clear that this variable doesn't yet represent opportunities, but will in the future, seems helpful to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I updated this and added docs so we are clear in the future.

@acouch acouch requested a review from freddieyebra as a code owner February 26, 2025 14:43
Copy link
Collaborator

@doug-s-nava doug-s-nava left a comment

Choose a reason for hiding this comment

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

✅ ✅ ✅ ✅

@acouch
Copy link
Collaborator Author

acouch commented Feb 26, 2025

I created a separate ticket for the logout #4022

@acouch acouch merged commit 863a6c5 into main Feb 26, 2025
10 checks passed
@acouch acouch deleted the acouch/issue-3308-view-saved-opps branch February 26, 2025 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implementation for viewing saved opportunities
2 participants