Skip to content

[Issue #8596] Create Opportunity page layout and components#8991

Draft
Dao-REI wants to merge 1 commit intomainfrom
dnguyen-8596-create-opportunity-pg1
Draft

[Issue #8596] Create Opportunity page layout and components#8991
Dao-REI wants to merge 1 commit intomainfrom
dnguyen-8596-create-opportunity-pg1

Conversation

@Dao-REI
Copy link
Collaborator

@Dao-REI Dao-REI commented Mar 11, 2026

Summary

Work for #8596

Changes proposed

  1. Created the Opportunity page 1 for Grantors to start the "create opportunity listing" process
  2. Created GenericFormFields for consistent look-and-feel and reusability.
  3. NOTE: the "save" action is in progress and is not completed yet.

Context for reviewers

  • ‎frontend/src/app/[locale]/(base)/opportunities/create/[agencyId]/page.tsx - The endpoint for this page. It sets up the page header, calls fetchUserAgencies(), and uses the component CreateOpportunityPage1 to generate the form fields.
  • ‎frontend/src/services/fetch/fetchers/agenciesFetcher.ts - Added getUserAgencies() to call the backend API to get all agencies this user belongs to.
  • frontend/src/components/opportunities/create/CreateOpportunityPage1.tsx - Component that generates the form fields for page 1 in the Create Opportunity process. It uses GenericFormFields components and the handles:
    • Translations (support for international languages)
    • If Category is 'Other' show the Explanation field
    • Enable the Save button only if all required fields have value
    • TODO: on Save, call the backend API, and show errors if any
  • frontend/src/components/GenericFormFields.tsx - Created to provide a consistent look-and-feel, consistent error messages, and reusable input fields.
  • frontend/src/i18n/messages/en/index.ts - Added labels/text for the Create Opportunity page in English
  • frontend/src/constants/defaultFeatureFlags.ts - Added the createOpportunityOff feature flag

Validation steps

Acceptance Criteria:

<>
<GridContainer>
<PageHeader/>
<CreateOpportunityPage1
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should avoid arbitrary variable naming - what about CreateOpportunityForm?

];

// Field level error messages
let oppNbrVldtnError = "";
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should track things like this, if necessary, in component state rather than in module globals. React will handle this better than the module will (as the file may be re-loaded at weird times, and state may be reset as result)


// All labels/text on this page with support for international languages
const t = useTranslations("CreateOpportunity.CreateOpportunityPage1");
const oppNbrLabel = t("opportunityNumber");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since defining variables like this leads to some bloat in the file, I'd rather follow the usual pattern and call t directly where these values are being used

const charsAllowed255 = t("charactersAllowed255");

// Agencies: sort alphabetically, convert to key-value pairs, set the default
const sortedAgencies = [...userAgencies].sort((a, b) =>
Copy link
Collaborator

Choose a reason for hiding this comment

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

my guess is that the agency endpoint supports sorting, will be easier to deal with things coming in pre-sorted

key: agency.agency_id,
value: agency.agency_name,
}));
keyValueList.forEach((item) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

as it is this will run on each render - can you wrap this and the keyValueList mapping in a useEffect?

};

// Custom CSS for the cancel button
const cancelButtonStyle: React.CSSProperties = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

unless there's a really compelling reason we should not use inline styling

const handleCancel = () => {
redirect("/opportunities")
};
const handleSave = () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's use a server action for this - that may change the general approach here a little

export const defaultFeatureFlags: FeatureFlags = {
applyFormPrototypeOff: false,
opportunitiesListOff: false,
createOpportunityOff: false,
Copy link
Collaborator

Choose a reason for hiding this comment

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

if this PR is creating a new feature flag, it should also update the terraform, see https://github.com/HHS/simpler-grants-gov/blob/main/documentation/frontend/featureFlags.md

@@ -0,0 +1,236 @@
// These fields are meant to be reusable and provide a consistent look and feel for all pages.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like the idea here a lot, but I think we should talk about this as a group before jumping in. It probably makes the most sense to use custom / barebones implementation rather than shared / general components at this point,

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.

2 participants