From 135ddea68b887c169bef49c25e722af21c1aa873 Mon Sep 17 00:00:00 2001 From: Mike Joyce Date: Mon, 25 Mar 2019 14:23:15 -0400 Subject: [PATCH] Sprint 6 -> staging (intermediate merge branch) (#125) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Use react-linkify to automatically hyperlink URLs and email addresses in position capsule descriptions * Authorization -> Authentication (#54) * Authorization -> Authentication * Rename import * Update pagination and page size defaults in alignment with designs * Display link, if available, in the glossary * Add ability to edit links from Glossary editor; update styling for glossary links * Reduce code complexity, fix long link styles * Update snapshot * Add bundlesize (#69) * Add bundlsize and command * Refine glob * Refine maxSize target * Chore/linter (#67) * chore: fix linter is scss file * fix: linter issue * End of basic auth (#63) * Delete login form * Update actions * Clean up sagas * Remove isSAML checks * Refactor sagas, CodeClimate fixes * CodeClimate linting * Upate login screen * Simplify index * Change to force external login * get new custom auth working * remove rest of the basic auth refs from server * use env vars for env specific paths * Bidder role - TM-371 (#68) * Create permissions wrapper and conditionally render content based on bidder role * remove default fallback prop * Fix/mock auth (#70) * fix: override some routes for the webpack dev server so auth works for local development * fix: fix the conf so we can use the login.html when running the server in prod mode * Default to true if can_delete property is not found * Update snapshots * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * TM-410 - display favorites list as 4 across above the large break point (#66) * fix: display favorites list as 4 across above the large break point * fix: display favorites list as 4 across above the large break point * fix: remove the auth redirect loop (#73) * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * no conditional about url * dev -> staging (#56) (#75) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Remove use of skill cone/code to skill (#76) * Add Public Profile page - links from CDO portfolio, profile/public/:id route, add Assignments section to public profile, re-use of Profile Dashboard (#71) ## Relies on https://github.com/MetaPhase-Consulting/State-TalentMAP-API/pull/24 * Sort bid cycles alphabetically by name (#78) * feature: download search results in csv format. Resolves TM-439 * chore: use default variable for sort * chore: reformat the date before file creation * Simplified search bar v2 (#81) * Make use of the existing simple search bar throughout site * Offsets for homepage * Redesign compare page (#82) * Redesign compare page * Remove old table styles; handle zero comparisons with route to catch it * feature: move the download button and use the secondary style. resolves TM-511 * fix: set a width on the cards for the favorite positions profile screen (#80) * fix: set a width on the cards for the favorite positions profile screen. resolves TM-410 * fix: no responsiveness for bid count and favorite buttons on position card displays * feature: fixed width for all position cards (homepage, favorites, similar positions). Resolves TM-510 * fix: remove unnecessary class and fix padding on grid for correct wrapping * chore: fixing the wrapping for the larger width * Fix search styles from breaking on the bidder portfolio * Reverting the changes to the glossary card due to the new term dialog breaking * Add link-container class back in * Update snapshots * Trigger circleci build * Code smells (#86) * Update CodeClimate exclusions for local dev * Reduce complexity * Comparison drawer component, add event listeners where needed, remove old comparison UI from results page * Track old compare choices to maintain sorting during an update * Add test coverage, use cancel tokens * Change dropdown menu link name from "Profile" to "Dashboard" * Remove How to Bid section from position details (#89) * Homepage QA (#90) * Remove Inbox icon * Fetch notifications from any screen, since we no longer use a /login route * Remove the BetaHeader * Conditional rendering of Bid Count on Bid Tracker cards, update Results cards data order and style * Break out compare elements into their own rows, add Bid List button to comparisons, use Set() for bidListToggleIsLoading * Remove eslint-disable * feature: include org info for domestic positions (#92) * Remove bidListToggleIsLoading since that is handled in BidListButton container * Display the service needs filter as a pill on the results page (#95) * Add error handling for position details screen (#93) * Add error handling for position details screen * Update call to action * Update based on design feedback * feature: add bid list button to the favorites cards. TM-512 * dev -> staging * Make icons consistent throughout profile pages (#102) * Service Needs -> Featured (#100) * Add hover to dropdown (#103) * Update empty saved search list text (#101) * Add custom filter for including null language positions (#104) * Remove Status component throughout app (#105) * fix: change label. TM-632 (#107) * Adjust elements to grow * fix: allow export on edge to run * Styles, add post to top * Update ordering for data points on Compare page (#110) * Remove post, add grade to bottom section * Updates to Bid Tracker (#115) * Add opacity to on-hold bids * "priority" -> "pending" * Prop to hide the delete button for standby bids * Track favoriting loading state of individual IDs (#108) * Track favoriting loading state of individual IDs * Fix proptypes * Handshake ribbon (#113) * Re-usable Ribbon component, use Ribbon component to display if handshake has been offered on position * Display handshake in condensed card, tests and snapshots * Test coverage (#117) * Test coverage for AccountDropdown, CompareDrawer, Compare * Add tests for SetType * Add toast notifications for favoriting actions (#114) * Create BoxShadow component and use with various cards (#106) * dev -> staging (#98) (#119) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Use react-linkify to automatically hyperlink URLs and email addresses in position capsule descriptions * Authorization -> Authentication (#54) * Authorization -> Authentication * Rename import * Update pagination and page size defaults in alignment with designs * Display link, if available, in the glossary * Add ability to edit links from Glossary editor; update styling for glossary links * Reduce code complexity, fix long link styles * Update snapshot * Add bundlesize (#69) * Add bundlsize and command * Refine glob * Refine maxSize target * Chore/linter (#67) * chore: fix linter is scss file * fix: linter issue * End of basic auth (#63) * Delete login form * Update actions * Clean up sagas * Remove isSAML checks * Refactor sagas, CodeClimate fixes * CodeClimate linting * Upate login screen * Simplify index * Change to force external login * get new custom auth working * remove rest of the basic auth refs from server * use env vars for env specific paths * Bidder role - TM-371 (#68) * Create permissions wrapper and conditionally render content based on bidder role * remove default fallback prop * Fix/mock auth (#70) * fix: override some routes for the webpack dev server so auth works for local development * fix: fix the conf so we can use the login.html when running the server in prod mode * Default to true if can_delete property is not found * Update snapshots * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * TM-410 - display favorites list as 4 across above the large break point (#66) * fix: display favorites list as 4 across above the large break point * fix: display favorites list as 4 across above the large break point * fix: remove the auth redirect loop (#73) * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * no conditional about url * dev -> staging (#56) (#75) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Remove use of skill cone/code to skill (#76) * Add Public Profile page - links from CDO portfolio, profile/public/:id route, add Assignments section to public profile, re-use of Profile Dashboard (#71) ## Relies on https://github.com/MetaPhase-Consulting/State-TalentMAP-API/pull/24 * Sort bid cycles alphabetically by name (#78) * feature: download search results in csv format. Resolves TM-439 * chore: use default variable for sort * chore: reformat the date before file creation * Simplified search bar v2 (#81) * Make use of the existing simple search bar throughout site * Offsets for homepage * Redesign compare page (#82) * Redesign compare page * Remove old table styles; handle zero comparisons with route to catch it * feature: move the download button and use the secondary style. resolves TM-511 * fix: set a width on the cards for the favorite positions profile screen (#80) * fix: set a width on the cards for the favorite positions profile screen. resolves TM-410 * fix: no responsiveness for bid count and favorite buttons on position card displays * feature: fixed width for all position cards (homepage, favorites, similar positions). Resolves TM-510 * fix: remove unnecessary class and fix padding on grid for correct wrapping * chore: fixing the wrapping for the larger width * Fix search styles from breaking on the bidder portfolio * Reverting the changes to the glossary card due to the new term dialog breaking * Add link-container class back in * Update snapshots * Trigger circleci build * Code smells (#86) * Update CodeClimate exclusions for local dev * Reduce complexity * Comparison drawer component, add event listeners where needed, remove old comparison UI from results page * Track old compare choices to maintain sorting during an update * Add test coverage, use cancel tokens * Change dropdown menu link name from "Profile" to "Dashboard" * Remove How to Bid section from position details (#89) * Homepage QA (#90) * Remove Inbox icon * Fetch notifications from any screen, since we no longer use a /login route * Remove the BetaHeader * Conditional rendering of Bid Count on Bid Tracker cards, update Results cards data order and style * Break out compare elements into their own rows, add Bid List button to comparisons, use Set() for bidListToggleIsLoading * Remove eslint-disable * feature: include org info for domestic positions (#92) * Remove bidListToggleIsLoading since that is handled in BidListButton container * Display the service needs filter as a pill on the results page (#95) * Add error handling for position details screen (#93) * Add error handling for position details screen * Update call to action * Update based on design feedback * dev -> staging * Make icons consistent throughout profile pages (#102) * Service Needs -> Featured (#100) * Add hover to dropdown (#103) * Sprint 6 merge conflicts (#120) * dev -> staging (#98) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Use react-linkify to automatically hyperlink URLs and email addresses in position capsule descriptions * Authorization -> Authentication (#54) * Authorization -> Authentication * Rename import * Update pagination and page size defaults in alignment with designs * Display link, if available, in the glossary * Add ability to edit links from Glossary editor; update styling for glossary links * Reduce code complexity, fix long link styles * Update snapshot * Add bundlesize (#69) * Add bundlsize and command * Refine glob * Refine maxSize target * Chore/linter (#67) * chore: fix linter is scss file * fix: linter issue * End of basic auth (#63) * Delete login form * Update actions * Clean up sagas * Remove isSAML checks * Refactor sagas, CodeClimate fixes * CodeClimate linting * Upate login screen * Simplify index * Change to force external login * get new custom auth working * remove rest of the basic auth refs from server * use env vars for env specific paths * Bidder role - TM-371 (#68) * Create permissions wrapper and conditionally render content based on bidder role * remove default fallback prop * Fix/mock auth (#70) * fix: override some routes for the webpack dev server so auth works for local development * fix: fix the conf so we can use the login.html when running the server in prod mode * Default to true if can_delete property is not found * Update snapshots * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * TM-410 - display favorites list as 4 across above the large break point (#66) * fix: display favorites list as 4 across above the large break point * fix: display favorites list as 4 across above the large break point * fix: remove the auth redirect loop (#73) * fix: remove the auth redirect loop and clean up reamining references to the LOGIN_MODE * no conditional about url * dev -> staging (#56) (#75) * chore: remove unnecessary css file from the build output * chore: update docs regarding apache compression config * Update bid list due date to match site-wide format * Use position id instead of position_number to query for position details * chore: remove unused props from components * chore: fix err due to incorrect favicon size in manifest * chore: remove unused props from the Home container * chore: remove unused props from the HomePagePositionsContainer component * fix: update profile page based on qa * Update styles and content in Position Details page based on QA feedback * fix: add logo to saved search title * fix: search page updates from qa * chore: linter fix * chore: fix linter issue * fix: search page updates from qa * chore: linter fix * Remove feedback button site-wide * Use object in state instead of array * Minor edits to Homepage based on QA * fix: make the pagination link clickable area larger * fix: use the correct button style * fix: better accessibility for active pagination tab selection * fix: use correct button design * fix: use lodash get so that non-existent nested property doesn't throw error (#45) * Use lodash get so that non-existent nested property doesn't throw error * Check for details.id so that components don't render with an empty object * Align bid count with data points in ResultsCondensedCard * Show "Available" filter to all users, not just CDOs (#46) * Update dashboard styles and content based on QA * Fix style for bid list container * Move Bid Count in-line with data points on ResultsCard * View More -> View more * Add disabled state for BidListButton based on proposed API updates * Use real properties from API PR, combine strings * Use white for button text color * feature: add remove bid to the bid tracker for draft and submitted bids * add additional status to the canDeleteBid function * Add react-toastify and use with bid list additions/removals (#51) * Add react-toastify and use with bid list additions/removals * More tests for toast-related functionality * Check if bid can be deleted and apply disabled status accordingly; update and optimize utility function * feature: use the can_delete property from the bid rather than calculate client side * Add loading spinner to Bid List button (#52) * Compress us-flag.jpg (#53) * Remove use of skill cone/code to skill (#76) * Add Public Profile page - links from CDO portfolio, profile/public/:id route, add Assignments section to public profile, re-use of Profile Dashboard (#71) ## Relies on https://github.com/MetaPhase-Consulting/State-TalentMAP-API/pull/24 * Sort bid cycles alphabetically by name (#78) * feature: download search results in csv format. Resolves TM-439 * chore: use default variable for sort * chore: reformat the date before file creation * Simplified search bar v2 (#81) * Make use of the existing simple search bar throughout site * Offsets for homepage * Redesign compare page (#82) * Redesign compare page * Remove old table styles; handle zero comparisons with route to catch it * feature: move the download button and use the secondary style. resolves TM-511 * fix: set a width on the cards for the favorite positions profile screen (#80) * fix: set a width on the cards for the favorite positions profile screen. resolves TM-410 * fix: no responsiveness for bid count and favorite buttons on position card displays * feature: fixed width for all position cards (homepage, favorites, similar positions). Resolves TM-510 * fix: remove unnecessary class and fix padding on grid for correct wrapping * chore: fixing the wrapping for the larger width * Fix search styles from breaking on the bidder portfolio * Reverting the changes to the glossary card due to the new term dialog breaking * Add link-container class back in * Update snapshots * Trigger circleci build * Code smells (#86) * Update CodeClimate exclusions for local dev * Reduce complexity * Comparison drawer component, add event listeners where needed, remove old comparison UI from results page * Track old compare choices to maintain sorting during an update * Add test coverage, use cancel tokens * Change dropdown menu link name from "Profile" to "Dashboard" * Remove How to Bid section from position details (#89) * Homepage QA (#90) * Remove Inbox icon * Fetch notifications from any screen, since we no longer use a /login route * Remove the BetaHeader * Conditional rendering of Bid Count on Bid Tracker cards, update Results cards data order and style * Break out compare elements into their own rows, add Bid List button to comparisons, use Set() for bidListToggleIsLoading * Remove eslint-disable * feature: include org info for domestic positions (#92) * Remove bidListToggleIsLoading since that is handled in BidListButton container * Display the service needs filter as a pill on the results page (#95) * Add error handling for position details screen (#93) * Add error handling for position details screen * Update call to action * Update based on design feedback * dev -> staging * Make icons consistent throughout profile pages (#102) * Service Needs -> Featured (#100) * Add hover to dropdown (#103) * Remove duplicates --- package.json | 1 + .../AccountDropdown/AccountDropdown.jsx | 12 +- .../AccountDropdown/AccountDropdown.test.jsx | 18 ++- .../BidTrackerCard/BidTrackerCard.jsx | 5 +- .../BidTrackerCard.test.jsx.snap | 13 +- .../BidTrackerCardTop/BidTrackerCardTop.jsx | 6 +- .../PriorityCards/IsOnStandby/IsOnStandby.jsx | 1 + .../__snapshots__/IsOnStandby.test.jsx.snap | 1 + .../PriorityCards/IsPriority/IsPriority.jsx | 4 +- .../__snapshots__/IsPriority.test.jsx.snap | 6 +- .../BidderPortfolioCard.jsx | 5 +- .../BidderPortfolioCard.test.jsx.snap | 12 +- src/Components/BoxShadow/BoxShadow.jsx | 47 ++++++ src/Components/BoxShadow/BoxShadow.test.jsx | 33 ++++ .../__snapshots__/BoxShadow.test.jsx.snap | 11 ++ src/Components/BoxShadow/index.js | 1 + .../CompareDrawer/CompareDrawerContainer.jsx | 2 +- .../CompareDrawerContainer.test.jsx | 30 +++- src/Components/CompareList/CompareList.jsx | 46 +++--- .../__snapshots__/CompareList.test.jsx.snap | 116 +++++++------- .../CondensedCardData/CondensedCardData.jsx | 16 +- .../CondensedCardData.test.jsx.snap | 34 ++--- .../NoSavedSearches/NoSavedSearches.jsx | 2 +- .../NoSavedSearches.test.jsx.snap | 2 +- src/Components/Favorite/Favorite.jsx | 7 +- src/Components/FavoriteMessages/Success.jsx | 13 ++ .../FavoriteMessages/Success.test.jsx | 11 ++ .../FavoritePositions/FavoritePositions.jsx | 1 + .../FavoritePositions.test.jsx.snap | 1 + .../GlossaryEditorCard/GlossaryEditorCard.jsx | 5 +- .../GlossaryEditorCard.test.jsx.snap | 24 ++- .../HomePagePositionsList.jsx | 5 +- .../HomePagePositionsList.test.jsx.snap | 7 + .../HomePagePositionsSection.test.jsx.snap | 1 + .../PositionDetailsItem.jsx | 12 +- .../PositionDetailsItem.test.jsx.snap | 15 +- .../ExternalUserStatus/ExternalUserStatus.jsx | 4 - .../ExternalUserStatus.test.jsx.snap | 14 -- .../ProfileDashboard/ProfileDashboard.jsx | 29 ++-- .../UserProfileGeneralInformation.jsx | 2 - ...serProfileGeneralInformation.test.jsx.snap | 9 -- src/Components/ResultsCard/ResultsCard.jsx | 75 ++++++---- .../ResultsCondensedCard.jsx | 47 +++--- .../ResultsCondensedCard.test.jsx.snap | 13 +- .../ResultsCondensedCardBottom.jsx | 51 +++++-- .../ResultsCondensedCardBottom.test.jsx | 12 ++ .../ResultsCondensedCardBottom.test.jsx.snap | 141 ++++++++++++++++++ .../ResultsCondensedCardStats.jsx | 2 +- .../ResultsCondensedCardStats.test.jsx.snap | 2 +- .../ResultsCondensedCardTop.jsx | 27 ++-- .../ResultsCondensedCardTop.test.jsx.snap | 82 +++++----- src/Components/Ribbon/Handshake/Handshake.jsx | 8 + .../Ribbon/Handshake/Handshake.test.jsx | 11 ++ src/Components/Ribbon/Handshake/index.js | 1 + src/Components/Ribbon/Ribbon.jsx | 32 ++++ src/Components/Ribbon/Ribbon.test.jsx | 80 ++++++++++ .../Ribbon/__snapshots__/Ribbon.test.jsx.snap | 18 +++ src/Components/Ribbon/index.js | 1 + .../SearchFiltersContainer.jsx | 26 +++- .../SearchResultsExportLink.jsx | 14 +- .../SearchResultsExportLink.test.jsx | 4 +- .../SearchResultsExportLink.test.jsx.snap | 66 +++++++- src/Constants/EndpointParams.js | 3 +- src/Constants/SetType.test.jsx | 45 ++++++ src/Constants/SystemMessages.js | 10 ++ .../BidListButton/BidListButton.jsx | 17 ++- src/Containers/Compare/Compare.jsx | 2 +- src/Containers/Compare/Compare.test.jsx | 25 +++- src/Containers/Favorite/Favorite.jsx | 21 ++- src/Containers/Favorite/Favorite.test.jsx | 2 +- src/Containers/Toast/Toast.jsx | 14 +- src/actions/favoritePositions.js | 2 +- src/actions/filters/filters.js | 4 +- src/actions/filters/helpers.js | 7 +- src/actions/toast.js | 6 +- src/actions/userProfile.js | 50 +++++-- src/reducers/filters/filters.js | 11 +- src/reducers/toast/toast.js | 6 +- src/reducers/userProfile/userProfile.js | 10 +- src/reducers/userProfile/userProfile.test.js | 10 +- src/sass/_bidListButton.scss | 1 - src/sass/_bidTracker.scss | 7 + src/sass/_buttons.scss | 5 +- src/sass/_compare.scss | 4 - src/sass/_condensedCard.scss | 46 +++++- src/sass/_details.scss | 10 ++ src/sass/_ribbon.scss | 60 ++++++++ src/sass/_variables.scss | 3 +- src/sass/styles.scss | 1 + yarn.lock | 5 + 90 files changed, 1313 insertions(+), 371 deletions(-) create mode 100644 src/Components/BoxShadow/BoxShadow.jsx create mode 100644 src/Components/BoxShadow/BoxShadow.test.jsx create mode 100644 src/Components/BoxShadow/__snapshots__/BoxShadow.test.jsx.snap create mode 100644 src/Components/BoxShadow/index.js create mode 100644 src/Components/FavoriteMessages/Success.jsx create mode 100644 src/Components/FavoriteMessages/Success.test.jsx create mode 100644 src/Components/Ribbon/Handshake/Handshake.jsx create mode 100644 src/Components/Ribbon/Handshake/Handshake.test.jsx create mode 100644 src/Components/Ribbon/Handshake/index.js create mode 100644 src/Components/Ribbon/Ribbon.jsx create mode 100644 src/Components/Ribbon/Ribbon.test.jsx create mode 100644 src/Components/Ribbon/__snapshots__/Ribbon.test.jsx.snap create mode 100644 src/Components/Ribbon/index.js create mode 100644 src/Constants/SetType.test.jsx create mode 100644 src/sass/_ribbon.scss diff --git a/package.json b/package.json index 34fd8518d8..7e55f6e84a 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "babel-polyfill": "^6.26.0", "body-parser": "^1.18.2", "bunyan": "^1.8.12", + "css-box-shadow": "^1.0.0-3", "date-fns": "^1.29.0", "draft-js": "^0.10.4", "draft-js-plugins-editor": "^2.0.4", diff --git a/src/Components/AccountDropdown/AccountDropdown.jsx b/src/Components/AccountDropdown/AccountDropdown.jsx index 7c1b838be8..d29ef6d1b0 100644 --- a/src/Components/AccountDropdown/AccountDropdown.jsx +++ b/src/Components/AccountDropdown/AccountDropdown.jsx @@ -11,6 +11,7 @@ export class AccountDropdown extends Component { constructor(props) { super(props); this.hideDropdown = this.hideDropdown.bind(this); + this.showDropdown = this.showDropdown.bind(this); this.logout = this.logout.bind(this); } @@ -23,6 +24,11 @@ export class AccountDropdown extends Component { this.dropdown.hide(); } + showDropdown() { + // Explicitly show the dropdown using the built-in hide() function from react-simple-dropdown + this.dropdown.show(); + } + render() { const { shouldDisplayName, userProfile } = this.props; const displayName = userProfile ? userProfile.display_name : '...'; @@ -42,8 +48,8 @@ export class AccountDropdown extends Component { className="account-dropdown" ref={(dropdown) => { this.dropdown = dropdown; }} removeElement - onMouseEnter={() => this.dropdown.show()} - onMouseLeave={() => this.dropdown.hide()} + onMouseEnter={this.showDropdown} + onMouseLeave={this.hideDropdown} > @@ -53,7 +59,7 @@ export class AccountDropdown extends Component { }
- this.dropdown.show()}> +
Signed in as
{displayName} diff --git a/src/Components/AccountDropdown/AccountDropdown.test.jsx b/src/Components/AccountDropdown/AccountDropdown.test.jsx index ca618b05a8..09bdc3a152 100644 --- a/src/Components/AccountDropdown/AccountDropdown.test.jsx +++ b/src/Components/AccountDropdown/AccountDropdown.test.jsx @@ -54,14 +54,28 @@ describe('AccountDropdown', () => { // define the instance const instance = accountDropdown.instance(); instance.dropdown = { hide: () => {} }; - // spy the logout function + // spy the hideDropdown function const spy = sinon.spy(instance, 'hideDropdown'); - // click to logout + // call function instance.hideDropdown(); // logout function should have been called once sinon.assert.calledOnce(spy); }); + it('can call the showDropdown function', () => { + const accountDropdown = shallow(); + + // define the instance + const instance = accountDropdown.instance(); + instance.dropdown = { show: () => {} }; + // spy the showDropdown function + const spy = sinon.spy(instance, 'showDropdown'); + // click to logout + instance.showDropdown(); + // logout function should have been called once + sinon.assert.calledOnce(spy); + }); + it('does not display the name when shouldDisplayName is false', () => { const accountDropdown = shallow(); expect(accountDropdown.find('#account-username').exists()).toBe(false); diff --git a/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx b/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx index fe38cf1a51..05d50188ae 100644 --- a/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx +++ b/src/Components/BidTracker/BidTrackerCard/BidTrackerCard.jsx @@ -5,6 +5,7 @@ import BidSteps from '../BidStep'; import BidTrackerCardBottom from '../BidTrackerCardBottom'; import BidTrackerCardTop from '../BidTrackerCardTop'; import OverlayAlert from '../OverlayAlert'; +import BoxShadow from '../../BoxShadow'; import { shouldShowAlert } from '../BidHelpers'; import { APPROVED_PROP, @@ -24,7 +25,7 @@ userProfile }) => { // add class to container for draft since we need to apply an overflow:hidden for drafts only const draftClass = bid.status === DRAFT_PROP ? 'bid-tracker-bid-steps-container--draft' : ''; return ( -
+
{
} -
+ ); }; diff --git a/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap b/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap index f99dcce4e4..0d1145021f 100644 --- a/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap +++ b/src/Components/BidTracker/BidTrackerCard/__snapshots__/BidTrackerCard.test.jsx.snap @@ -1,8 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`BidTrackerCardComponent matches snapshot 1`] = ` -
@@ -170,5 +179,5 @@ exports[`BidTrackerCardComponent matches snapshot 1`] = ` />
-
+ `; diff --git a/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx b/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx index 40e5ce92e3..03eb445a2d 100644 --- a/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx +++ b/src/Components/BidTracker/BidTrackerCardTop/BidTrackerCardTop.jsx @@ -21,7 +21,7 @@ class BidTrackerCardTop extends Component { } render() { - const { bid, showBidCount, showQuestion } = this.props; + const { bid, hideDelete, showBidCount, showQuestion } = this.props; const bidStatistics = get(bid, 'position.bid_statistics[0]', {}); const post = get(bid, 'position.post', {}); return ( @@ -45,7 +45,7 @@ class BidTrackerCardTop extends Component { }
- { bid.can_delete && + { bid.can_delete && !hideDelete && { showQuestion={false} bid={bid} deleteBid={deleteBid} + hideDelete />
diff --git a/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap b/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap index 3e44a928cf..fc3071a751 100644 --- a/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap +++ b/src/Components/BidTracker/PriorityCards/IsOnStandby/__snapshots__/IsOnStandby.test.jsx.snap @@ -77,6 +77,7 @@ exports[`IsOnStandbyComponent matches snapshot 1`] = ` } } deleteBid={[Function]} + hideDelete={true} showBidCount={false} showQuestion={false} /> diff --git a/src/Components/BidTracker/PriorityCards/IsPriority/IsPriority.jsx b/src/Components/BidTracker/PriorityCards/IsPriority/IsPriority.jsx index 9a5a0b5688..dd64d3f7af 100644 --- a/src/Components/BidTracker/PriorityCards/IsPriority/IsPriority.jsx +++ b/src/Components/BidTracker/PriorityCards/IsPriority/IsPriority.jsx @@ -7,11 +7,11 @@ const IsPriority = ({ children }) => (
- Priority Assignment + Pending Assignment
- What is a priority Assignment? + What is a Pending Assignment?
diff --git a/src/Components/BidTracker/PriorityCards/IsPriority/__snapshots__/IsPriority.test.jsx.snap b/src/Components/BidTracker/PriorityCards/IsPriority/__snapshots__/IsPriority.test.jsx.snap index babe8364a7..de12847740 100644 --- a/src/Components/BidTracker/PriorityCards/IsPriority/__snapshots__/IsPriority.test.jsx.snap +++ b/src/Components/BidTracker/PriorityCards/IsPriority/__snapshots__/IsPriority.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`IsPriorityComponent matches snapshot 1`] = ` +exports['IsPriorityComponent matches snapshot 1'] = `
@@ -13,7 +13,7 @@ exports[`IsPriorityComponent matches snapshot 1`] = `
- Priority Assignment + Pending Assignment
- What is a priority Assignment? + What is a Pending Assignment?
diff --git a/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx b/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx index 04835744c4..077d1146ca 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx +++ b/src/Components/BidderPortfolio/BidderPortfolioCard/BidderPortfolioCard.jsx @@ -4,9 +4,10 @@ import UserProfileGeneralInformation from '../../ProfileDashboard/UserProfile/Us import UserProfilePersonalInformation from '../../ProfileDashboard/UserProfile/UserProfilePersonalInformation'; import UserProfileBidInformation from '../../ProfileDashboard/UserProfile/UserProfileBidInformation'; import BidderPortfolioViewMore from '../BidderPortfolioViewMore'; +import BoxShadow from '../../BoxShadow'; const BidderPortfolioCard = ({ userProfile }) => ( -
+ ( submitted={userProfile.bid_statistics[0] ? userProfile.bid_statistics[0].submitted : 0} /> -
+ ); BidderPortfolioCard.propTypes = { diff --git a/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap b/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap index 42939a9466..3f212b6a1b 100644 --- a/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap +++ b/src/Components/BidderPortfolio/BidderPortfolioCard/__snapshots__/BidderPortfolioCard.test.jsx.snap @@ -1,8 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`BidderPortfolioCardComponent matches snapshot 1`] = ` -
-
+ `; diff --git a/src/Components/BoxShadow/BoxShadow.jsx b/src/Components/BoxShadow/BoxShadow.jsx new file mode 100644 index 0000000000..9e68d73d99 --- /dev/null +++ b/src/Components/BoxShadow/BoxShadow.jsx @@ -0,0 +1,47 @@ +// forked from https://github.com/lachlanjc/react-box-shadow +import React from 'react'; +import PropTypes from 'prop-types'; +import cssShadow from 'css-box-shadow'; + +const BoxShadow = ({ is, inset, offsetX, offsetY, blurRadius, spreadRadius, color, style, +...props }) => { + const Component = is; + const sx = { + ...style, + boxShadow: cssShadow.stringify([ + { + inset, + offsetX, + offsetY, + blurRadius, + spreadRadius, + color, + }, + ]), + }; + return ; +}; + +BoxShadow.propTypes = { + is: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + inset: PropTypes.bool, + offsetX: PropTypes.number, + offsetY: PropTypes.number, + blurRadius: PropTypes.number, + spreadRadius: PropTypes.number, + color: PropTypes.string, + style: PropTypes.shape({}), +}; + +BoxShadow.defaultProps = { + is: 'div', + inset: false, + offsetX: 3, + offsetY: 2, + blurRadius: 10, + spreadRadius: 1, + color: 'rgba(0,0,0,.15)', + style: {}, +}; + +export default BoxShadow; diff --git a/src/Components/BoxShadow/BoxShadow.test.jsx b/src/Components/BoxShadow/BoxShadow.test.jsx new file mode 100644 index 0000000000..ac672a37fa --- /dev/null +++ b/src/Components/BoxShadow/BoxShadow.test.jsx @@ -0,0 +1,33 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import toJSON from 'enzyme-to-json'; +import BoxShadow from './BoxShadow'; + +describe('BoxShadow', () => { + it('can receive props', () => { + const wrapper = shallow( + , + ); + const output = { + backgroundColor: 'red', + boxShadow: 'inset 23px 25px 27px 29px #f2f2f2', + }; + expect(wrapper.props().style).toEqual(output); + }); + + it('matches snapshot', () => { + const wrapper = shallow( + , + ); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/BoxShadow/__snapshots__/BoxShadow.test.jsx.snap b/src/Components/BoxShadow/__snapshots__/BoxShadow.test.jsx.snap new file mode 100644 index 0000000000..b80b16fa83 --- /dev/null +++ b/src/Components/BoxShadow/__snapshots__/BoxShadow.test.jsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BoxShadow matches snapshot 1`] = ` +
+`; diff --git a/src/Components/BoxShadow/index.js b/src/Components/BoxShadow/index.js new file mode 100644 index 0000000000..f37f1a09b7 --- /dev/null +++ b/src/Components/BoxShadow/index.js @@ -0,0 +1 @@ +export { default } from './BoxShadow'; diff --git a/src/Components/CompareDrawer/CompareDrawerContainer.jsx b/src/Components/CompareDrawer/CompareDrawerContainer.jsx index 23c387c587..01ddb46ce8 100644 --- a/src/Components/CompareDrawer/CompareDrawerContainer.jsx +++ b/src/Components/CompareDrawer/CompareDrawerContainer.jsx @@ -7,7 +7,7 @@ import CompareDrawer from './CompareDrawer'; import { COMPARE_LIST } from '../../Constants/PropTypes'; import { getScrollDistanceFromBottom } from '../../utilities'; -class Compare extends Component { +export class Compare extends Component { constructor(props) { super(props); this.lsListener = this.lsListener.bind(this); diff --git a/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx b/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx index fb97471994..93de808b3a 100644 --- a/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx +++ b/src/Components/CompareDrawer/CompareDrawerContainer.test.jsx @@ -1,11 +1,12 @@ import React from 'react'; import TestUtils from 'react-dom/test-utils'; +import { shallow } from 'enzyme'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { testDispatchFunctions } from '../../testUtilities/testUtilities'; -import CompareDrawerContainer, { mapDispatchToProps } from './CompareDrawerContainer'; +import CompareDrawerContainer, { Compare, mapDispatchToProps } from './CompareDrawerContainer'; import resultsObject from '../../__mocks__/resultsObject'; const middlewares = [thunk]; @@ -13,13 +14,36 @@ const mockStore = configureStore(middlewares); describe('CompareDrawerContainer', () => { it('is defined', () => { - const compare = TestUtils.renderIntoDocument( + const wrapper = TestUtils.renderIntoDocument( {}} comparisons={resultsObject.results} /> ); - expect(compare).toBeDefined(); + expect(wrapper).toBeDefined(); + }); + + it('returns correct values for shouldComponentUpdate', () => { + const props = { + fetchData: () => {}, + comparisons: resultsObject.results, + hasErrored: false, + }; + const wrapper = shallow( + , + ); + // check that new props return true + const shouldReturnTrue = wrapper.instance().shouldComponentUpdate({}, {}); + expect(shouldReturnTrue).toBe(true); + // check that new state values, excluding 'comparisons', returns true + const shouldAlsoReturnTrue = wrapper.instance().shouldComponentUpdate({}, { prevComparisons: ['fake'], + isHidden: true }); + expect(shouldAlsoReturnTrue).toBe(true); + // if everything else is equal but 'comparisons' is new, should return false + const shouldReturnFalse = wrapper.instance().shouldComponentUpdate(props, { ...wrapper.instance().state, comparisons: ['new'] }); + expect(shouldReturnFalse).toBe(false); }); }); diff --git a/src/Components/CompareList/CompareList.jsx b/src/Components/CompareList/CompareList.jsx index f668695be9..0bcbc08f83 100644 --- a/src/Components/CompareList/CompareList.jsx +++ b/src/Components/CompareList/CompareList.jsx @@ -124,10 +124,14 @@ onToggle }) => { } - Skill + + Post + { compareArray.map(c => ( - {c.skill || NO_SKILL} + + {getPostName(c.post, NO_POST)} + )) } { @@ -135,10 +139,12 @@ onToggle }) => { } - Bureau + TED { compareArray.map(c => ( - {c.bureau || NO_BUREAU} + + {propOrDefault(c, 'current_assignment.estimated_end_date') ? formatDate(c.current_assignment.estimated_end_date) : NO_DATE } + )) } { @@ -146,14 +152,21 @@ onToggle }) => { } - - Post - + Skill { compareArray.map(c => ( - - {getPostName(c.post, NO_POST)} - + {c.skill || NO_SKILL} + )) + } + { + emptyArray.map(() => ) + } + + + Bureau + { + compareArray.map(c => ( + {c.bureau || NO_BUREAU} )) } { @@ -216,19 +229,6 @@ onToggle }) => { emptyArray.map(() => ) } - - TED - { - compareArray.map(c => ( - - {propOrDefault(c, 'current_assignment.estimated_end_date') ? formatDate(c.current_assignment.estimated_end_date) : NO_DATE } - - )) - } - { - emptyArray.map(() => ) - } - Favorite { diff --git a/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap b/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap index c9f84468dd..59dc94fca3 100644 --- a/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap +++ b/src/Components/CompareList/__snapshots__/CompareList.test.jsx.snap @@ -265,13 +265,35 @@ exports[`CompareListComponent matches snapshot 1`] = ` - Skill + Post + + + Freetown, Sierra Leone + + + Chicago, IL + + + + + + + + TED None listed - SECURITY (2501) + 03/13/2020 - Bureau + Skill None listed - (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS + SECURITY (2501) - Post + Bureau - Freetown, Sierra Leone + None listed - Chicago, IL + (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS - - - TED - - - None listed - - - 03/13/2020 - - - - - - Skill + Post + + + Freetown, Sierra Leone + + + Chicago, IL + + + + + + + + TED None listed - SECURITY (2501) + 03/13/2020 - Bureau + Skill None listed - (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS + SECURITY (2501) - Post + Bureau - Freetown, Sierra Leone + None listed - Chicago, IL + (WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS - - - TED - - - None listed - - - 03/13/2020 - - - - - { const estimatedEndDate = propOrDefault(position, 'current_assignment.estimated_end_date') ? @@ -11,18 +11,18 @@ const CondensedCardData = ({ position }) => { return (
{ const textLineTwo = `Reuse this job search in the future by clicking "Save this search". - Try naming your search something memorable, like "Summer Positions 2018".`; + Try naming your search something memorable, like "Summer Positions in My Grade".`; return ( Reuse this job search in the future by clicking "Save this search". - Try naming your search something memorable, like "Summer Positions 2018". + Try naming your search something memorable, like "Summer Positions in My Grade". } /> diff --git a/src/Components/Favorite/Favorite.jsx b/src/Components/Favorite/Favorite.jsx index 04bc6d2491..eec01b4441 100644 --- a/src/Components/Favorite/Favorite.jsx +++ b/src/Components/Favorite/Favorite.jsx @@ -67,7 +67,8 @@ class Favorite extends Component { } isUpdate = (oldState !== newState) || - (this.state.loading !== nextState.isLoading); + (this.state.loading !== nextState.isLoading) || + nextProps.hasErrored; return isUpdate; } @@ -190,10 +191,11 @@ Favorite.propTypes = { className: PropTypes.string, as: PropTypes.string.isRequired, onToggle: PropTypes.func.isRequired, - refKey: PropTypes.node.isRequired, + refKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string.isRequired]).isRequired, hideText: PropTypes.bool, compareArray: FAVORITE_POSITIONS_ARRAY.isRequired, isLoading: PropTypes.bool, + hasErrored: PropTypes.bool, hasBorder: PropTypes.bool, useLongText: PropTypes.bool, useButtonClass: PropTypes.bool, @@ -207,6 +209,7 @@ Favorite.defaultProps = { as: 'div', hideText: false, isLoading: false, + hasErrored: false, compareArray: [], hasBorder: false, useLongText: false, diff --git a/src/Components/FavoriteMessages/Success.jsx b/src/Components/FavoriteMessages/Success.jsx new file mode 100644 index 0000000000..b6d10c1d98 --- /dev/null +++ b/src/Components/FavoriteMessages/Success.jsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { POSITION_DETAILS } from '../../Constants/PropTypes'; + +const Success = ({ pos }) => ( + {pos.title} ({pos.position_number}) has been successfully added to Favorites. Go to Favorites. +); + +Success.propTypes = { + pos: POSITION_DETAILS.isRequired, +}; + +export default Success; diff --git a/src/Components/FavoriteMessages/Success.test.jsx b/src/Components/FavoriteMessages/Success.test.jsx new file mode 100644 index 0000000000..b1d296e020 --- /dev/null +++ b/src/Components/FavoriteMessages/Success.test.jsx @@ -0,0 +1,11 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import Success from './Success'; +import detailsObject from '../../__mocks__/detailsObject'; + +describe('Success', () => { + it('is defined', () => { + const wrapper = shallow(); + expect(wrapper).toBeDefined(); + }); +}); diff --git a/src/Components/FavoritePositions/FavoritePositions.jsx b/src/Components/FavoritePositions/FavoritePositions.jsx index a4acdce092..07de306cd4 100644 --- a/src/Components/FavoritePositions/FavoritePositions.jsx +++ b/src/Components/FavoritePositions/FavoritePositions.jsx @@ -40,6 +40,7 @@ bidList, onSortChange }) => ( title="favorites" maxLength={300} refreshFavorites + showBidListButton />
); diff --git a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap index 36f8aa0192..1131c62043 100644 --- a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap +++ b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap @@ -559,6 +559,7 @@ exports[`FavoritePositionsComponent matches snapshot 1`] = ` ] } refreshFavorites={true} + showBidListButton={true} title="favorites" type="default" /> diff --git a/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx b/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx index 97303b9249..dcdc0c66ce 100644 --- a/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx +++ b/src/Components/GlossaryEditor/GlossaryEditorCard/GlossaryEditorCard.jsx @@ -4,6 +4,7 @@ import { GLOSSARY_OBJECT, EMPTY_FUNCTION, GLOSSARY_ERROR_OBJECT } from '../../.. import TextEditor from '../../TextEditor'; import InteractiveElement from '../../InteractiveElement'; import GlossaryEditorCardBottom from '../GlossaryEditorCardBottom'; +import BoxShadow from '../../BoxShadow'; import { isUrl } from '../../../utilities'; const isEmpty = value => (value || '').length === 0; @@ -175,7 +176,7 @@ class GlossaryEditorCard extends Component { } = this.editorClasses; return ( -
+
{ @@ -241,7 +242,7 @@ class GlossaryEditorCard extends Component { id={term.id || null} submitGlossaryTerm={submitGlossaryTerm} /> -
+ ); } } diff --git a/src/Components/GlossaryEditor/GlossaryEditorCard/__snapshots__/GlossaryEditorCard.test.jsx.snap b/src/Components/GlossaryEditor/GlossaryEditorCard/__snapshots__/GlossaryEditorCard.test.jsx.snap index af2775e2b0..cc4e5cee79 100644 --- a/src/Components/GlossaryEditor/GlossaryEditorCard/__snapshots__/GlossaryEditorCard.test.jsx.snap +++ b/src/Components/GlossaryEditor/GlossaryEditorCard/__snapshots__/GlossaryEditorCard.test.jsx.snap @@ -1,8 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`GlossaryEditorCardComponent matches snapshot 1`] = ` -
-
+ `; exports[`GlossaryEditorCardComponent matches snapshot when displayZeroLengthAlert is true 1`] = ` -
-
+ `; diff --git a/src/Components/HomePagePositionsList/HomePagePositionsList.jsx b/src/Components/HomePagePositionsList/HomePagePositionsList.jsx index 9eff7334a0..74c2126812 100644 --- a/src/Components/HomePagePositionsList/HomePagePositionsList.jsx +++ b/src/Components/HomePagePositionsList/HomePagePositionsList.jsx @@ -11,6 +11,7 @@ const propTypes = { type: HOME_PAGE_CARD_TYPE, refreshFavorites: PropTypes.bool, title: PropTypes.string.isRequired, // should be unique per page, since its used a react key + showBidListButton: PropTypes.bool, }; const defaultProps = { @@ -19,10 +20,11 @@ const defaultProps = { isLoading: false, type: 'default', refreshFavorites: false, + showBidListButton: false, }; const HomePagePositionsList = ({ positions, favorites, isLoading, - bidList, type, refreshFavorites, title }) => ( + bidList, type, refreshFavorites, title, showBidListButton }) => (
{positions.map(p => ( @@ -33,6 +35,7 @@ const HomePagePositionsList = ({ positions, favorites, isLoading, bidList={bidList} type={type} refreshFavorites={refreshFavorites} + showBidListButton={showBidListButton} />
))} diff --git a/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap b/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap index d7d22c8e10..c735223942 100644 --- a/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap +++ b/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap @@ -237,6 +237,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -470,6 +471,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -703,6 +705,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -936,6 +939,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -1169,6 +1173,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -1402,6 +1407,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" />
@@ -1635,6 +1641,7 @@ exports[`HomePagePositionsList displays two rows 1`] = ` } } refreshFavorites={false} + showBidListButton={false} type="default" /> diff --git a/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap b/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap index b82a5df204..e247a7f61b 100644 --- a/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap +++ b/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap @@ -343,6 +343,7 @@ exports[`HomePagePositionsSection matches snapshot 1`] = ` ] } refreshFavorites={false} + showBidListButton={false} title="Title" type="default" /> diff --git a/src/Components/PositionDetailsItem/PositionDetailsItem.jsx b/src/Components/PositionDetailsItem/PositionDetailsItem.jsx index cf0e7cdd89..60c50f854e 100644 --- a/src/Components/PositionDetailsItem/PositionDetailsItem.jsx +++ b/src/Components/PositionDetailsItem/PositionDetailsItem.jsx @@ -1,16 +1,19 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { get } from 'lodash'; import LanguageList from '../../Components/LanguageList/LanguageList'; import CondensedCardDataPoint from '../CondensedCardData/CondensedCardDataPoint'; import OBCUrl from '../OBCUrl'; import PositionDetailsDescription from './PositionDetailsDescription'; import PositionDetailsContact from './PositionDetailsContact'; import ServiceNeededToggle from './ServiceNeededToggle'; +import Handshake from '../Ribbon/Handshake'; import { formatDate, propOrDefault, getAccessiblePositionNumber, getDifferentialPercentage, + getBidStatisticsObject, } from '../../utilities'; import { DEFAULT_HIGHLIGHT_POSITION } from '../../Constants/DefaultProps'; @@ -63,8 +66,15 @@ const PositionDetailsItem = (props) => { }; const incumbent = propOrDefault(details, 'current_assignment.user', NO_USER_LISTED); + + const stats = getBidStatisticsObject(details.bid_statistics); return ( -
+
+
+ { + get(stats, 'has_handshake_offered', false) && + } +

About the Position

diff --git a/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap b/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap index 79723afcbd..2e0bd95829 100644 --- a/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap +++ b/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap @@ -2,8 +2,11 @@ exports[`PositionDetailsItem matches snapshot 1`] = `
+
@@ -193,8 +196,11 @@ exports[`PositionDetailsItem matches snapshot 1`] = ` exports[`PositionDetailsItem matches snapshot when there is an obc id 1`] = `
+
@@ -416,8 +422,11 @@ exports[`PositionDetailsItem matches snapshot when there is an obc id 1`] = ` exports[`PositionDetailsItem matches snapshot when various data is missing from the position 1`] = `
+
diff --git a/src/Components/ProfileDashboard/ExternalUserStatus/ExternalUserStatus.jsx b/src/Components/ProfileDashboard/ExternalUserStatus/ExternalUserStatus.jsx index fe322135fd..4f3f35b6cc 100644 --- a/src/Components/ProfileDashboard/ExternalUserStatus/ExternalUserStatus.jsx +++ b/src/Components/ProfileDashboard/ExternalUserStatus/ExternalUserStatus.jsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Avatar from '../../Avatar'; -import Status from '../UserProfile/Status'; import USER_TYPES from '../../../Constants/UserTypes'; import MailToButton from '../../MailToButton'; @@ -10,9 +9,6 @@ const ExternalUserStatus = ({ initials, firstName, lastName, type, showMail, ema
-
- -
diff --git a/src/Components/ProfileDashboard/ExternalUserStatus/__snapshots__/ExternalUserStatus.test.jsx.snap b/src/Components/ProfileDashboard/ExternalUserStatus/__snapshots__/ExternalUserStatus.test.jsx.snap index d3d2bdde0a..6feb051aea 100644 --- a/src/Components/ProfileDashboard/ExternalUserStatus/__snapshots__/ExternalUserStatus.test.jsx.snap +++ b/src/Components/ProfileDashboard/ExternalUserStatus/__snapshots__/ExternalUserStatus.test.jsx.snap @@ -18,13 +18,6 @@ exports[`ExternalUserStatusComponent matches snapshot 1`] = ` onClick={[Function]} small={true} /> -
- -
-
- -
-
+ -
+ {isPublic ?
@@ -52,12 +53,12 @@ const ProfileDashboard = ({ columns={columns[1]} className="user-dashboard-section-container user-dashboard-column-3" > -
+ -
-
+ + -
+
: @@ -66,25 +67,25 @@ const ProfileDashboard = ({ columns={columns[1]} className={'user-dashboard-section-container user-dashboard-column-2'} > -
+ -
-
+ + -
+ -
+ -
+
-
+ -
+
} diff --git a/src/Components/ProfileDashboard/UserProfile/UserProfileGeneralInformation/UserProfileGeneralInformation.jsx b/src/Components/ProfileDashboard/UserProfile/UserProfileGeneralInformation/UserProfileGeneralInformation.jsx index 6a69bec4dd..cf824f6090 100644 --- a/src/Components/ProfileDashboard/UserProfile/UserProfileGeneralInformation/UserProfileGeneralInformation.jsx +++ b/src/Components/ProfileDashboard/UserProfile/UserProfileGeneralInformation/UserProfileGeneralInformation.jsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { USER_PROFILE } from '../../../../Constants/PropTypes'; import SectionTitle from '../../SectionTitle'; import InformationDataPoint from '../../InformationDataPoint'; -import Status from '../Status'; import EditProfile from '../EditProfile'; import Avatar from '../../../Avatar'; import StaticDevContent from '../../../StaticDevContent'; @@ -13,7 +12,6 @@ const UserProfileGeneralInformation = ({ userProfile, showEditLink, useGroup })
- - - - { return ( {() => ( -
- - - -

{title}

- View position + +
+ + + +

{title}

+ View position +
+ +
Post:
{post}
+
- -
Post:
{post}
+ + -
- - - -
- - - - - - - - - - - { + + + + + + + + + + { + get(stats, 'has_handshake_offered', false) && + } + + + + + { !!favorites && } - - - -
- -
-
-
-
+ +
+ +
+ +
+
+
+
+ )}
); diff --git a/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx b/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx index f6f344df6e..fa66486c02 100644 --- a/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx +++ b/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx @@ -3,26 +3,35 @@ import PropTypes from 'prop-types'; import ResultsCondensedCardTop from '../ResultsCondensedCardTop'; import ResultsCondensedCardBottom from '../ResultsCondensedCardBottom'; import ResultsCondensedCardFooter from '../ResultsCondensedCardFooter'; +import BoxShadow from '../BoxShadow'; import { POSITION_DETAILS, FAVORITE_POSITIONS_ARRAY, BID_RESULTS, HOME_PAGE_CARD_TYPE } from '../../Constants/PropTypes'; -const ResultsCondensedCard = ({ position, favorites, bidList, type, refreshFavorites }) => ( - -
- - - -
+const ResultsCondensedCard = ( + { + position, + favorites, + bidList, + type, + refreshFavorites, + showBidListButton, + }) => ( + + + + + ); ResultsCondensedCard.propTypes = { @@ -31,11 +40,13 @@ ResultsCondensedCard.propTypes = { bidList: BID_RESULTS.isRequired, type: HOME_PAGE_CARD_TYPE.isRequired, refreshFavorites: PropTypes.bool, + showBidListButton: PropTypes.bool, }; ResultsCondensedCard.defaultProps = { favorites: [], refreshFavorites: false, + showBidListButton: false, }; export default ResultsCondensedCard; diff --git a/src/Components/ResultsCondensedCard/__snapshots__/ResultsCondensedCard.test.jsx.snap b/src/Components/ResultsCondensedCard/__snapshots__/ResultsCondensedCard.test.jsx.snap index 6c54256bcd..85827a1f70 100644 --- a/src/Components/ResultsCondensedCard/__snapshots__/ResultsCondensedCard.test.jsx.snap +++ b/src/Components/ResultsCondensedCard/__snapshots__/ResultsCondensedCard.test.jsx.snap @@ -1,8 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ResultsCondensedCardComponent matches snapshot 1`] = ` -
-
+ `; diff --git a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.jsx b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.jsx index 3e49970aa1..9d5dbf8985 100644 --- a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.jsx +++ b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.jsx @@ -1,38 +1,59 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { get } from 'lodash'; import CondensedCardData from '../CondensedCardData'; import { POSITION_DETAILS, FAVORITE_POSITIONS_ARRAY } from '../../Constants/PropTypes'; import Favorite from '../../Containers/Favorite'; +import BidListButton from '../../Containers/BidListButton'; +import PermissionsWrapper from '../../Containers/PermissionsWrapper'; import ResultsCondensedCardStats from '../ResultsCondensedCardStats'; -const ResultsCondensedCardBottom = ({ position, favorites, refreshFavorites }) => ( -
-
- - -
- +const ResultsCondensedCardBottom = ( + { position, + favorites, + refreshFavorites, + showBidListButton, + }) => ( +
+
+ + +
+ + { + showBidListButton && + + + + } +
-
); ResultsCondensedCardBottom.propTypes = { position: POSITION_DETAILS.isRequired, favorites: FAVORITE_POSITIONS_ARRAY.isRequired, refreshFavorites: PropTypes.bool, + showBidListButton: PropTypes.bool, }; ResultsCondensedCardBottom.defaultProps = { type: 'default', refreshFavorites: false, + showBidListButton: false, }; export default ResultsCondensedCardBottom; diff --git a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx index 90cb14bd6c..889ba5d526 100644 --- a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx +++ b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx @@ -41,4 +41,16 @@ describe('ResultsCondensedCardBottomComponent', () => { ); expect(toJSON(wrapper)).toMatchSnapshot(); }); + + it('matches snapshot with bidlist button', () => { + const wrapper = shallow( + , + ); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); }); diff --git a/src/Components/ResultsCondensedCardBottom/__snapshots__/ResultsCondensedCardBottom.test.jsx.snap b/src/Components/ResultsCondensedCardBottom/__snapshots__/ResultsCondensedCardBottom.test.jsx.snap index ef32a1f0b3..ac8353d92d 100644 --- a/src/Components/ResultsCondensedCardBottom/__snapshots__/ResultsCondensedCardBottom.test.jsx.snap +++ b/src/Components/ResultsCondensedCardBottom/__snapshots__/ResultsCondensedCardBottom.test.jsx.snap @@ -119,12 +119,153 @@ exports[`ResultsCondensedCardBottomComponent matches snapshot 1`] = ` ] } hasBorder={true} + hideText={false} refKey={6} refresh={false} useButtonClass={true} + useButtonClassSecondary={false} useLongText={true} />
`; + +exports[`ResultsCondensedCardBottomComponent matches snapshot with bidlist button 1`] = ` +
+
+ + +
+ + + + +
+
+
+`; diff --git a/src/Components/ResultsCondensedCardStats/ResultsCondensedCardStats.jsx b/src/Components/ResultsCondensedCardStats/ResultsCondensedCardStats.jsx index 057816c936..b3c69eb183 100644 --- a/src/Components/ResultsCondensedCardStats/ResultsCondensedCardStats.jsx +++ b/src/Components/ResultsCondensedCardStats/ResultsCondensedCardStats.jsx @@ -7,7 +7,7 @@ const ResultsCondensedCardStats = ({ bidStatisticsArray }) => { const bidStatistics = getBidStatisticsObject(bidStatisticsArray); return (
-
+
diff --git a/src/Components/ResultsCondensedCardStats/__snapshots__/ResultsCondensedCardStats.test.jsx.snap b/src/Components/ResultsCondensedCardStats/__snapshots__/ResultsCondensedCardStats.test.jsx.snap index ff0a6b3116..6877659b51 100644 --- a/src/Components/ResultsCondensedCardStats/__snapshots__/ResultsCondensedCardStats.test.jsx.snap +++ b/src/Components/ResultsCondensedCardStats/__snapshots__/ResultsCondensedCardStats.test.jsx.snap @@ -5,7 +5,7 @@ exports[`ResultsCondensedCardStatsComponent matches snapshot 1`] = ` className="condensed-card-footer condensed-card-statistics" >
{ let icon = ''; @@ -12,25 +16,30 @@ const ResultsCondensedCardTop = ({ position, type }) => { cardTopClass = 'card-top-alternate'; useType = true; } + const stats = getBidStatisticsObject(position.bid_statistics); + const hasHandshake = get(stats, 'has_handshake_offered', false); return (
{useType && } -

{position.title}

-
-
-
Grade:
{position.grade}
+

{position.title}

View position
-
- View position +
+
+ Post: {getPostName(position.post, NO_POST)} +
+ { + hasHandshake && +
+ +
+ }
); diff --git a/src/Components/ResultsCondensedCardTop/__snapshots__/ResultsCondensedCardTop.test.jsx.snap b/src/Components/ResultsCondensedCardTop/__snapshots__/ResultsCondensedCardTop.test.jsx.snap index 14f67ae3b2..035b970a7d 100644 --- a/src/Components/ResultsCondensedCardTop/__snapshots__/ResultsCondensedCardTop.test.jsx.snap +++ b/src/Components/ResultsCondensedCardTop/__snapshots__/ResultsCondensedCardTop.test.jsx.snap @@ -8,33 +8,38 @@ exports[`ResultsCondensedCardTopComponent matches snapshot 1`] = ` className="usa-grid-full condensed-card-top-header-container" >

OMS (DCM)

-
-
-
- Grade: -
-
- 06 -
+ + View position +
- - View position - +
+ + + Post: + + + + Freetown, Sierra Leone + + +
`; @@ -47,7 +52,7 @@ exports[`ResultsCondensedCardTopComponent matches snapshot when type is serviceN className="usa-grid-full condensed-card-top-header-container" >
OMS (DCM) -
-
-
- Grade: -
-
- 06 -
+ + View position +
- - View position - +
+ + + Post: + + + + Freetown, Sierra Leone + + +
`; diff --git a/src/Components/Ribbon/Handshake/Handshake.jsx b/src/Components/Ribbon/Handshake/Handshake.jsx new file mode 100644 index 0000000000..9f16b073d2 --- /dev/null +++ b/src/Components/Ribbon/Handshake/Handshake.jsx @@ -0,0 +1,8 @@ +import React from 'react'; +import Ribbon from '../Ribbon'; + +const Handshake = ({ ...props }) => ( + +); + +export default Handshake; diff --git a/src/Components/Ribbon/Handshake/Handshake.test.jsx b/src/Components/Ribbon/Handshake/Handshake.test.jsx new file mode 100644 index 0000000000..ae198c7b85 --- /dev/null +++ b/src/Components/Ribbon/Handshake/Handshake.test.jsx @@ -0,0 +1,11 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import Handshake from './Handshake'; + +describe('HandshakeComponent', () => { + it('is defined', () => { + const wrapper = shallow(); + + expect(wrapper).toBeDefined(); + }); +}); diff --git a/src/Components/Ribbon/Handshake/index.js b/src/Components/Ribbon/Handshake/index.js new file mode 100644 index 0000000000..f67d74d363 --- /dev/null +++ b/src/Components/Ribbon/Handshake/index.js @@ -0,0 +1 @@ +export { default } from './Handshake'; diff --git a/src/Components/Ribbon/Ribbon.jsx b/src/Components/Ribbon/Ribbon.jsx new file mode 100644 index 0000000000..0782784ce1 --- /dev/null +++ b/src/Components/Ribbon/Ribbon.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import FA from 'react-fontawesome'; + +const Ribbon = ({ type, className, icon, text, cutSide, containerProps }) => ( +
+
+ {text} +
+
+); + + +Ribbon.propTypes = { + className: PropTypes.string, + icon: PropTypes.string, + text: PropTypes.string, + containerProps: PropTypes.shape({}), + type: PropTypes.oneOf(['primary', 'secondary']), + cutSide: PropTypes.oneOf(['left', 'right']), +}; + +Ribbon.defaultProps = { + className: '', + icon: 'handshake', + text: '', + containerProps: {}, + type: 'primary', + cutSide: 'left', +}; + +export default Ribbon; diff --git a/src/Components/Ribbon/Ribbon.test.jsx b/src/Components/Ribbon/Ribbon.test.jsx new file mode 100644 index 0000000000..35d694d3c1 --- /dev/null +++ b/src/Components/Ribbon/Ribbon.test.jsx @@ -0,0 +1,80 @@ +import { shallow } from 'enzyme'; +import React from 'react'; +import toJSON from 'enzyme-to-json'; +import Ribbon from './Ribbon'; + +describe('RibbonComponent', () => { + const props = { + type: 'primary', + className: '', + icon: '', + text: '', + cutSide: 'left', + containerProps: {}, + }; + + it('is defined', () => { + const wrapper = shallow(); + + expect(wrapper).toBeDefined(); + }); + + it('creates class names', () => { + const props$ = { + ...props, + className: 'custom-class', + cutSide: 'right', + type: 'primary', + }; + const wrapper = shallow(); + + expect(wrapper.find('div').at(0).props().className) + .toBe(`ribbon-outer-container ribbon-outer-container-cut-${props$.cutSide} ${props$.className}`); + expect(wrapper.find('div').at(1).props().className) + .toBe(`ribbon ribbon-${props$.type} ribbon-cut-${props$.cutSide}`); + }); + + it('passes the correct icon name', () => { + const props$ = { + ...props, + icon: 'message', + }; + const wrapper = shallow(); + + expect(wrapper.find('FontAwesome').at(0).props().name) + .toBe(props$.icon); + }); + + it('passes the correct text', () => { + const props$ = { + ...props, + text: 'Ribbon text', + }; + const wrapper = shallow(); + + expect(wrapper.find('span').text()) + .toBe(props$.text); + }); + + it('spreads the containerProps', () => { + const props$ = { + ...props, + containerProps: { + alt: 'alt text', + tabIndex: 0, + }, + }; + const wrapper = shallow(); + + Object.keys(props$.containerProps).map(m => ( + expect(wrapper.find('div').at(0).props()[m]) + .toBe(props$.containerProps[m]) + )); + }); + + it('matches snapshot', () => { + const wrapper = shallow(); + + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/src/Components/Ribbon/__snapshots__/Ribbon.test.jsx.snap b/src/Components/Ribbon/__snapshots__/Ribbon.test.jsx.snap new file mode 100644 index 0000000000..160eac8b84 --- /dev/null +++ b/src/Components/Ribbon/__snapshots__/Ribbon.test.jsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RibbonComponent matches snapshot 1`] = ` +
+
+ + +
+
+`; diff --git a/src/Components/Ribbon/index.js b/src/Components/Ribbon/index.js new file mode 100644 index 0000000000..96c43933bd --- /dev/null +++ b/src/Components/Ribbon/index.js @@ -0,0 +1 @@ +export { default } from './Ribbon'; diff --git a/src/Components/SearchFilters/SearchFiltersContainer/SearchFiltersContainer.jsx b/src/Components/SearchFilters/SearchFiltersContainer/SearchFiltersContainer.jsx index c6843e2826..341fc2f25d 100644 --- a/src/Components/SearchFilters/SearchFiltersContainer/SearchFiltersContainer.jsx +++ b/src/Components/SearchFilters/SearchFiltersContainer/SearchFiltersContainer.jsx @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { includes, sortBy } from 'lodash'; import MultiSelectFilterContainer from '../MultiSelectFilterContainer/MultiSelectFilterContainer'; import MultiSelectFilter from '../MultiSelectFilter/MultiSelectFilter'; import BooleanFilterContainer from '../BooleanFilterContainer/BooleanFilterContainer'; @@ -9,7 +10,7 @@ import PostFilter from '../PostFilter'; import SkillFilter from '../SkillFilter'; import { FILTER_ITEMS_ARRAY, POST_DETAILS_ARRAY } from '../../../Constants/PropTypes'; import { propSort, sortGrades, getPostName, propOrDefault } from '../../../utilities'; -import { ENDPOINT_PARAMS } from '../../../Constants/EndpointParams'; +import { ENDPOINT_PARAMS, COMMON_PROPERTIES } from '../../../Constants/EndpointParams'; class SearchFiltersContainer extends Component { @@ -62,7 +63,9 @@ class SearchFiltersContainer extends Component { }); // get our normal multi-select filters - const multiSelectFilterNames = ['bidCycle', 'skill', 'grade', 'region', 'post', 'tod', 'language', 'postDiff', 'dangerPay']; + const multiSelectFilterNames = ['bidCycle', 'skill', 'grade', 'region', 'post', 'tod', 'language', + 'postDiff', 'dangerPay']; + const blackList = []; // don't create accordions for these // create map const multiSelectFilterMap = new Map(); @@ -77,6 +80,10 @@ class SearchFiltersContainer extends Component { f.data.sort(sortGrades); } else if (f.item.description === 'language' && f.data) { f.data.sort(propSort('custom_description')); + // Push the "NONE" code choice to the bottom. We're already sorting + // data, and this is readable, so the next line is eslint-disabled. + // eslint-disable-next-line + f.data = sortBy(f.data, item => item.code === COMMON_PROPERTIES.NULL_LANGUAGE ? 1 : 0); } // add to Map multiSelectFilterMap.set(f.item.description, f); @@ -158,6 +165,19 @@ class SearchFiltersContainer extends Component { skillCones={skillCones} /> ); + case 'language': + return ( +
+ +
+ ); + case includes(blackList, type) ? type : null: + return null; default: return (
@@ -173,7 +193,7 @@ class SearchFiltersContainer extends Component { } }; - if (item) { + if (item && !includes(blackList, n)) { sortedFilters.push( { content: getFilter(n), title: item.item.title, diff --git a/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx b/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx index 2bd4bf7e0e..be6708936b 100644 --- a/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx +++ b/src/Components/SearchResultsExportLink/SearchResultsExportLink.jsx @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { CSVDownload } from 'react-csv'; +import { CSVLink } from 'react-csv'; import queryString from 'query-string'; import { POSITION_SEARCH_SORTS } from '../../Constants/Sort'; import { fetchResultData } from '../../actions/results'; @@ -52,7 +52,11 @@ class SearchResultsExportLink extends Component { }; fetchResultData(queryString.stringify(query)).then((results) => { const data = processData(results.results); - this.setState({ data }); + this.setState({ data }, () => { + // click the CSVLink component to trigger the CSV download + // This is needed for the download to work in Edge. + this.csvLink.link.click(); + }); }); } @@ -60,10 +64,8 @@ class SearchResultsExportLink extends Component { const { data } = this.state; return (
- - { - data && - } + + { this.csvLink = x; }} target="_blank" filename={this.props.filename} data={data} headers={HEADERS} />
); } diff --git a/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx index 7958269e88..2c0fb49a2a 100644 --- a/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx +++ b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx @@ -24,11 +24,11 @@ describe('SearchResultsExportLink', () => { expect(fetchResultDataStub.calledOnce).toBe(true); }); - it('shows download component when state has data', () => { + it('shows link component when state has data', () => { const wrapper = shallow(); wrapper.setState({ data: 'test' }); wrapper.update(); - expect(wrapper.find('CSVDownload').prop('data')).toBeTruthy(); + expect(wrapper.find('CSVLink').prop('data')).toBeTruthy(); }); it('matches snapshot', () => { diff --git a/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap index fb3d0cc497..8e4de7dc4e 100644 --- a/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap +++ b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap @@ -6,7 +6,71 @@ exports[`SearchResultsExportLink matches snapshot 1`] = ` className="usa-button-secondary" onClick={[Function]} > - Download + Export +
`; diff --git a/src/Constants/EndpointParams.js b/src/Constants/EndpointParams.js index d8f9905d31..95ea2aee08 100644 --- a/src/Constants/EndpointParams.js +++ b/src/Constants/EndpointParams.js @@ -2,7 +2,7 @@ export const ENDPOINT_PARAMS = { skill: 'skill__code__in', - language: 'languages__language__code__in', + language: 'language_codes', grade: 'grade__code__in', tod: 'post__tour_of_duty__code__in', org: 'bureau__code__in', @@ -21,6 +21,7 @@ export const ENDPOINT_PARAMS = { // any properties that we want to abstract to a common name export const COMMON_PROPERTIES = { posted: 'posted_date', + NULL_LANGUAGE: 'NONE', }; // Take our custom query param from the Bidder Portfolio navigation and convert them to queries diff --git a/src/Constants/SetType.test.jsx b/src/Constants/SetType.test.jsx new file mode 100644 index 0000000000..32ce8d9981 --- /dev/null +++ b/src/Constants/SetType.test.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { omit, pick } from 'lodash'; +import SetType from './SetType'; + +describe('PropTypes', () => { + const props = { + a: new Set(), + b: 1, + c: () => {}, + d: {}, + e: [], + f: null, + g: undefined, + h: 'h', + i:
, + j: 0, + k: { method: 1 /* some non-function */ }, + }; + + const shouldReturnNull = ['a', 'f', 'g']; + + Object.keys(pick(props, shouldReturnNull)).map(k => ( + it(`should return null for Set, null, and undefined (key = ${k}: ${JSON.stringify(props[k])})`, () => { + const output = SetType(props, k, 'component'); + expect(output).toBeNull(); + }) + )); + + Object.keys(omit(props, shouldReturnNull)).map(k => ( + it(`should return an error if type !== Set (key = ${k}: ${JSON.stringify(props[k])})`, () => { + const output = SetType(props, k, 'component'); + expect(output.toString()).toEqual((expect.stringMatching(/^(Error)/))); + }) + )); + + it('return an error if the prop is required and a null value is provided', () => { + const output = SetType.isRequired(props, 'f', 'component'); + expect(output.toString()).toEqual((expect.stringMatching(/^(Error)/))); + }); + + it('return a null if the prop is required and a valid value is provided', () => { + const output = SetType.isRequired(props, 'a', 'component'); + expect(output).toBeNull(); + }); +}); diff --git a/src/Constants/SystemMessages.js b/src/Constants/SystemMessages.js index 483723e2e5..feffb73604 100644 --- a/src/Constants/SystemMessages.js +++ b/src/Constants/SystemMessages.js @@ -1,3 +1,5 @@ +import FavoriteSuccess from '../Components/FavoriteMessages/Success'; + export const DEFAULT_TEXT = 'None listed'; export const NO_ASSIGNMENT_DATE = DEFAULT_TEXT; @@ -37,6 +39,14 @@ export const DELETE_BID_ITEM_ERROR = 'Error trying to delete this bid.'; export const ADD_BID_ITEM_SUCCESS = 'Bid successfully added.'; export const ADD_BID_ITEM_ERROR = 'Error trying to add this bid.'; +export const ADD_FAVORITE_TITLE = 'Favorite Added'; +export const DELETE_FAVORITE_TITLE = 'Favorite Removed'; +export const ERROR_FAVORITE_TITLE = 'Favorite Error'; +export const DELETE_FAVORITE_SUCCESS = pos => `${pos.title} (${pos.position_number}) has been successfully removed from favorites.`; +export const DELETE_FAVORITE_ERROR = () => "We're experiencing an error attemtping to remove this position to your Favorites. Please try again."; +export const ADD_FAVORITE_SUCCESS = pos => FavoriteSuccess({ pos }); +export const ADD_FAVORITE_ERROR = () => "We're experiencing an error attemtping to add this position to your Favorites. Please try again."; + export const ACCEPT_BID_SUCCESS = 'Bid successfully accepted.'; export const ACCEPT_BID_ERROR = 'Error trying to accept this bid.'; export const DECLINE_BID_SUCCESS = 'Bid successfully declined.'; diff --git a/src/Containers/BidListButton/BidListButton.jsx b/src/Containers/BidListButton/BidListButton.jsx index e2f076bd48..f3ea1f7f25 100644 --- a/src/Containers/BidListButton/BidListButton.jsx +++ b/src/Containers/BidListButton/BidListButton.jsx @@ -1,26 +1,35 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { SetType, BID_LIST } from '../../Constants/PropTypes'; import { toggleBidPosition } from '../../actions/bidList'; import BidListButton from '../../Components/BidListButton'; -import { SetType } from '../../Constants/PropTypes'; -const BidListButtonContainer = ({ toggleBid, isLoading, id, ...rest }) => ( - - ); +const BidListButtonContainer = ({ toggleBid, isLoading, id, compareArray, ...rest }) => ( + +); BidListButtonContainer.propTypes = { toggleBid: PropTypes.func.isRequired, isLoading: SetType, id: PropTypes.number.isRequired, + compareArray: BID_LIST.isRequired, }; BidListButtonContainer.defaultProps = { isLoading: new Set(), + compareArray: { results: [] }, }; export const mapStateToProps = state => ({ isLoading: state.bidListToggleIsLoading, + compareArray: state.bidListFetchDataSuccess, }); export const mapDispatchToProps = dispatch => ({ diff --git a/src/Containers/Compare/Compare.jsx b/src/Containers/Compare/Compare.jsx index 6a46ceb08d..246796925f 100644 --- a/src/Containers/Compare/Compare.jsx +++ b/src/Containers/Compare/Compare.jsx @@ -12,7 +12,7 @@ import { COMPARE_LIST, POSITION_SEARCH_RESULTS, BID_LIST, SetType } from '../../ import { POSITION_RESULTS_OBJECT } from '../../Constants/DefaultProps'; import { LOGIN_REDIRECT } from '../../login/routes'; -class Compare extends Component { +export class Compare extends Component { constructor(props) { super(props); this.onToggle = this.onToggle.bind(this); diff --git a/src/Containers/Compare/Compare.test.jsx b/src/Containers/Compare/Compare.test.jsx index 4956bd6cd1..2e1cd20962 100644 --- a/src/Containers/Compare/Compare.test.jsx +++ b/src/Containers/Compare/Compare.test.jsx @@ -1,11 +1,13 @@ import React from 'react'; +import sinon from 'sinon'; +import { shallow } from 'enzyme'; import TestUtils from 'react-dom/test-utils'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import { testDispatchFunctions } from '../../testUtilities/testUtilities'; -import Compare, { mapDispatchToProps } from './Compare'; +import Compare, { Compare as CompareComponent, mapDispatchToProps } from './Compare'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); @@ -21,6 +23,27 @@ describe('Main', () => { expect(compare).toBeDefined(); }); + it('passes the correct value to getComparisons when onToggle is called', () => { + const compare = shallow( + true} + onNavigateTo={() => {}} + match={{ params: { ids: '1,2,3' } }} + fetchData={() => {}} + hasErrored={false} + isLoading={false} + fetchFavorites={() => {}} + fetchBidList={() => {}} + />, + ); + const instance = compare.instance(); + const input = '1'; + const getComparisonsSpy = sinon.spy(instance, 'getComparisons'); + instance.onToggle(input); + const exp = '2,3'; + expect(getComparisonsSpy.getCall(0).args[0]).toBe(exp); + }); + it('can handle authentication redirects', () => { const compare = TestUtils.renderIntoDocument( ( - -); + + ); FavoriteContainer.propTypes = { onToggle: PropTypes.func.isRequired, - isLoading: PropTypes.bool.isRequired, + isLoading: SetType, hasErrored: PropTypes.bool.isRequired, + refKey: PropTypes.oneOfType([PropTypes.number, PropTypes.string.isRequired]).isRequired, +}; + +FavoriteContainer.defaultProps = { + isLoading: new Set(), }; export const mapStateToProps = state => ({ - isLoading: state.userProfileFavoritePositionIsLoading || false, + isLoading: state.userProfileFavoritePositionIsLoading, hasErrored: state.userProfileFavoritePositionHasErrored || false, }); diff --git a/src/Containers/Favorite/Favorite.test.jsx b/src/Containers/Favorite/Favorite.test.jsx index 68906af49c..02cadbdcdd 100644 --- a/src/Containers/Favorite/Favorite.test.jsx +++ b/src/Containers/Favorite/Favorite.test.jsx @@ -6,7 +6,7 @@ import Favorite, { mapDispatchToProps } from './Favorite'; describe('Favorite', () => { const props = { onToggle: () => {}, - isLoading: false, + isLoading: new Set(), hasErrored: false, refKey: 'key', compareArray: [], diff --git a/src/Containers/Toast/Toast.jsx b/src/Containers/Toast/Toast.jsx index 264645cb62..838851cf77 100644 --- a/src/Containers/Toast/Toast.jsx +++ b/src/Containers/Toast/Toast.jsx @@ -17,12 +17,13 @@ export class Toast extends Component { } } - notify({ type = 'success', message = 'Message' }) { // eslint-disable-line - let title; - if (type === 'success') { title = 'Success'; } - if (type === 'error') { title = 'Error'; } + notify({ type = 'success', message = 'Message', title = '' }) { // eslint-disable-line + let title$; + if (type === 'success') { title$ = 'Success'; } + if (type === 'error') { title$ = 'Error'; } + if (title) { title$ = title; } toast[type]( - , + , ); } @@ -36,7 +37,8 @@ export class Toast extends Component { Toast.propTypes = { toastData: PropTypes.shape({ type: PropTypes.string, - message: PropTypes.string, + message: PropTypes.node, + title: PropTypes.string, }), }; diff --git a/src/actions/favoritePositions.js b/src/actions/favoritePositions.js index 2ac22d8bc5..e3c716b9fc 100644 --- a/src/actions/favoritePositions.js +++ b/src/actions/favoritePositions.js @@ -31,9 +31,9 @@ export function favoritePositionsFetchData(sortType) { api.get(url) .then(response => response.data) .then((results) => { + dispatch(favoritePositionsFetchDataSuccess(results)); dispatch(favoritePositionsHasErrored(false)); dispatch(favoritePositionsIsLoading(false)); - dispatch(favoritePositionsFetchDataSuccess(results)); }) .catch(() => { dispatch(favoritePositionsHasErrored(true)); diff --git a/src/actions/filters/filters.js b/src/actions/filters/filters.js index a77752f78d..3f7d37c4d1 100644 --- a/src/actions/filters/filters.js +++ b/src/actions/filters/filters.js @@ -1,3 +1,4 @@ +import { union } from 'lodash'; import api from '../../api'; import { ASYNC_PARAMS, ENDPOINT_PARAMS } from '../../Constants/EndpointParams'; import { removeDuplicates } from '../../utilities'; @@ -218,7 +219,8 @@ export function filtersFetchData(items = { filters: [] }, queryParams = {}, save api.get(`/${item.item.endpoint}`) .then((response) => { const itemFilter = Object.assign({}, item); - itemFilter.data = response.data.results; + // We have a mix of server-supplied and hard-coded data, so we combine them with union. + itemFilter.data = union(response.data.results, item.initialData); return itemFilter; }) ), diff --git a/src/actions/filters/helpers.js b/src/actions/filters/helpers.js index 2565d6b5a3..9bf0e27391 100644 --- a/src/actions/filters/helpers.js +++ b/src/actions/filters/helpers.js @@ -1,4 +1,5 @@ import { getPostName } from '../../utilities'; +import { COMMON_PROPERTIES } from '../../Constants/EndpointParams'; // Attempt to map the non-numeric grade codes to a full description. // If no match is found, return the unmodified code. @@ -29,7 +30,11 @@ export function getFilterCustomDescription(filterItem, filterItemObject) { case 'bidCycle': return filterItemObject.name; case 'language': - return `${filterItemObject.formal_description} (${filterItemObject.code})`; + // language code NONE gets displayed differently + return filterItemObject.code === COMMON_PROPERTIES.NULL_LANGUAGE ? + filterItemObject.customDescription + : + `${filterItemObject.formal_description} (${filterItemObject.code})`; case 'grade': return getCustomGradeDescription(filterItemObject.code); case 'postDiff': diff --git a/src/actions/toast.js b/src/actions/toast.js index 749325200e..8e234b8d54 100644 --- a/src/actions/toast.js +++ b/src/actions/toast.js @@ -1,13 +1,15 @@ -export function toastSuccess(toast) { +export function toastSuccess(toast, title) { return { type: 'TOAST_NOTIFICATION_SUCCESS', toast, + title, }; } -export function toastError(toast) { +export function toastError(toast, title) { return { type: 'TOAST_NOTIFICATION_ERROR', toast, + title, }; } diff --git a/src/actions/userProfile.js b/src/actions/userProfile.js index 28aa5a7bf3..24e2eb0e43 100644 --- a/src/actions/userProfile.js +++ b/src/actions/userProfile.js @@ -3,6 +3,8 @@ import { indexOf } from 'lodash'; import api from '../api'; import { favoritePositionsFetchData } from './favoritePositions'; +import { toastSuccess, toastError } from './toast'; +import * as SystemMessages from '../Constants/SystemMessages'; export function userProfileHasErrored(bool) { return { @@ -26,10 +28,10 @@ export function userProfileFetchDataSuccess(userProfile) { } // when adding or removing a favorite -export function userProfileFavoritePositionIsLoading(bool) { +export function userProfileFavoritePositionIsLoading(bool, id) { return { type: 'USER_PROFILE_FAVORITE_POSITION_IS_LOADING', - userProfileFavoritePositionIsLoading: bool, + userProfileFavoritePositionIsLoading: { bool, id }, }; } @@ -48,10 +50,9 @@ export function unsetUserProfile() { } // include an optional bypass for when we want to silently update the profile -export function userProfileFetchData(bypass) { +export function userProfileFetchData(bypass, cb) { return (dispatch) => { if (!bypass) { - dispatch(userProfileIsLoading(true)); dispatch(userProfileHasErrored(false)); } @@ -77,16 +78,20 @@ export function userProfileFetchData(bypass) { }; // then perform dispatches + if (cb) { + dispatch(cb()); + } dispatch(userProfileFetchDataSuccess(newProfileObject)); dispatch(userProfileIsLoading(false)); dispatch(userProfileHasErrored(false)); dispatch(userProfileFavoritePositionHasErrored(false)); - dispatch(userProfileFavoritePositionIsLoading(false)); })) .catch(() => { + if (cb) { + dispatch(cb()); + } dispatch(userProfileHasErrored(true)); dispatch(userProfileIsLoading(false)); - dispatch(userProfileFavoritePositionIsLoading(false)); }); }; } @@ -107,21 +112,40 @@ export function userProfileToggleFavoritePosition(id, remove, refreshFavorites = url: `/position/${idString}/favorite/`, }; - dispatch(userProfileFavoritePositionIsLoading(true)); + /** + * create functions for creating the action and fetching position data to supply to message + */ + // action + const getAction = () => api(config); + + // position + const getPosition = () => api.get(`/position/${id}/`); + + dispatch(userProfileFavoritePositionIsLoading(true, id)); dispatch(userProfileFavoritePositionHasErrored(false)); - api(config) - .then(() => { - dispatch(userProfileFetchData(true)); - dispatch(userProfileFavoritePositionIsLoading(false)); + axios.all([getAction(), getPosition()]) + .then(axios.spread((action, position) => { + const pos = position.data; + const message = remove ? + SystemMessages.DELETE_FAVORITE_SUCCESS(pos) : SystemMessages.ADD_FAVORITE_SUCCESS(pos); + const title = remove ? SystemMessages.DELETE_FAVORITE_TITLE + : SystemMessages.ADD_FAVORITE_TITLE; + const cb = () => userProfileFavoritePositionIsLoading(false, id); + dispatch(userProfileFetchData(true, cb)); dispatch(userProfileFavoritePositionHasErrored(false)); + dispatch(toastSuccess(message, title)); if (refreshFavorites) { dispatch(favoritePositionsFetchData()); } - }) + })) .catch(() => { + const message = remove ? + SystemMessages.DELETE_FAVORITE_ERROR() : SystemMessages.ADD_FAVORITE_ERROR(); + const title = SystemMessages.ERROR_FAVORITE_TITLE; + dispatch(userProfileFavoritePositionIsLoading(false, id)); dispatch(userProfileFavoritePositionHasErrored(true)); - dispatch(userProfileFavoritePositionIsLoading(false)); + dispatch(toastError(message, title)); }); }; } diff --git a/src/reducers/filters/filters.js b/src/reducers/filters/filters.js index d71d11be5b..1f0a993479 100644 --- a/src/reducers/filters/filters.js +++ b/src/reducers/filters/filters.js @@ -1,4 +1,4 @@ -import { ENDPOINT_PARAMS } from '../../Constants/EndpointParams'; +import { COMMON_PROPERTIES, ENDPOINT_PARAMS } from '../../Constants/EndpointParams'; // Set what filters we want to fetch const items = @@ -50,6 +50,15 @@ const items = }, data: [ ], + // Allow users to include languages with no code. This option is not supplied from + // the endpoint, so we define it here. + initialData: [ + { + code: COMMON_PROPERTIES.NULL_LANGUAGE, + short_description: 'No language requirement', + custom_description: 'No language requirement', + }, + ], }, { item: { diff --git a/src/reducers/toast/toast.js b/src/reducers/toast/toast.js index 6ef75182c5..ae92449461 100644 --- a/src/reducers/toast/toast.js +++ b/src/reducers/toast/toast.js @@ -1,9 +1,9 @@ -export default function toast(state = { type: 'success', message: '' }, action) { +export default function toast(state = { type: 'success', message: '', title: '' }, action) { switch (action.type) { case 'TOAST_NOTIFICATION_SUCCESS': - return { type: 'success', message: action.toast }; + return { type: 'success', message: action.toast, title: action.title }; case 'TOAST_NOTIFICATION_ERROR': - return { type: 'error', message: action.toast }; + return { type: 'error', message: action.toast, title: action.title }; default: return state; } diff --git a/src/reducers/userProfile/userProfile.js b/src/reducers/userProfile/userProfile.js index cfc9cc3703..01863f3da3 100644 --- a/src/reducers/userProfile/userProfile.js +++ b/src/reducers/userProfile/userProfile.js @@ -32,10 +32,16 @@ export function userProfileFavoritePositionHasErrored(state = false, action) { return state; } } -export function userProfileFavoritePositionIsLoading(state = false, action) { +export function userProfileFavoritePositionIsLoading(state = new Set(), action) { + const newSet = new Set(state); switch (action.type) { case 'USER_PROFILE_FAVORITE_POSITION_IS_LOADING': - return action.userProfileFavoritePositionIsLoading; + if (action.userProfileFavoritePositionIsLoading.bool) { + newSet.add(action.userProfileFavoritePositionIsLoading.id); + return newSet; + } + newSet.delete(action.userProfileFavoritePositionIsLoading.id); + return newSet; default: return state; } diff --git a/src/reducers/userProfile/userProfile.test.js b/src/reducers/userProfile/userProfile.test.js index 20afea3552..828acf2c92 100644 --- a/src/reducers/userProfile/userProfile.test.js +++ b/src/reducers/userProfile/userProfile.test.js @@ -14,10 +14,14 @@ describe('reducers', () => { }); it('can set reducer USER_PROFILE_FAVORITE_POSITION_HAS_ERRORED', () => { - expect(reducers.userProfileFavoritePositionHasErrored(false, { type: 'USER_PROFILE_FAVORITE_POSITION_HAS_ERRORED', userProfileFavoritePositionHasErrored: true })).toBe(true); + expect(reducers.userProfileFavoritePositionHasErrored(new Set(), { type: 'USER_PROFILE_FAVORITE_POSITION_HAS_ERRORED', userProfileFavoritePositionHasErrored: true })).toBe(true); }); - it('can set reducer USER_PROFILE_FAVORITE_POSITION_IS_LOADING', () => { - expect(reducers.userProfileFavoritePositionIsLoading(false, { type: 'USER_PROFILE_FAVORITE_POSITION_IS_LOADING', userProfileFavoritePositionIsLoading: true })).toBe(true); + it('can set reducer USER_PROFILE_FAVORITE_POSITION_IS_LOADING to add an id', () => { + expect(reducers.userProfileFavoritePositionIsLoading(new Set(), { type: 'USER_PROFILE_FAVORITE_POSITION_IS_LOADING', userProfileFavoritePositionIsLoading: { bool: true, id: 1 } })).toEqual(new Set([1])); + }); + + it('can set reducer USER_PROFILE_FAVORITE_POSITION_IS_LOADING to remove an id', () => { + expect(reducers.userProfileFavoritePositionIsLoading(new Set([1]), { type: 'USER_PROFILE_FAVORITE_POSITION_IS_LOADING', userProfileFavoritePositionIsLoading: { bool: false, id: 1 } })).toEqual(new Set()); }); }); diff --git a/src/sass/_bidListButton.scss b/src/sass/_bidListButton.scss index f599c23191..46b13495f6 100644 --- a/src/sass/_bidListButton.scss +++ b/src/sass/_bidListButton.scss @@ -1,5 +1,4 @@ .bid-list-button { - padding: 1.5rem 4rem; .button-icon { color: $color-white; diff --git a/src/sass/_bidTracker.scss b/src/sass/_bidTracker.scss index 4c98d85f19..20a62bce02 100644 --- a/src/sass/_bidTracker.scss +++ b/src/sass/_bidTracker.scss @@ -593,7 +593,14 @@ $draft-icon-offset: 120px; } .bid-tracker-standby-container { + $link-color: #2B4559; + display: flex; + opacity: .75; // lowest opacity without violating 4.5:1 contrast ratio + + a:link { + color: $link-color; // opacity lightens normal link color below the required 4.5:1 ratio + } .bid-tracker-standby-inner-container { display: flex; diff --git a/src/sass/_buttons.scss b/src/sass/_buttons.scss index 134dc00143..c39ccac233 100644 --- a/src/sass/_buttons.scss +++ b/src/sass/_buttons.scss @@ -144,8 +144,8 @@ &.button-text-hidden { margin-top: 0; padding-bottom: 11.5px; - padding-left: 15px; - padding-right: 15px; + padding-left: 14px; + padding-right: 14px; padding-top: 11.5px; .spinner-white, @@ -155,6 +155,7 @@ .fa { margin-right: 0; + padding-right: 0; } } diff --git a/src/sass/_compare.scss b/src/sass/_compare.scss index 8fbf3322a3..b3107840a7 100644 --- a/src/sass/_compare.scss +++ b/src/sass/_compare.scss @@ -10,10 +10,6 @@ .bid-list-button { min-width: 100%; padding: .75em 1em; - - .button-icon { - display: none; - } } .post-data-button { diff --git a/src/sass/_condensedCard.scss b/src/sass/_condensedCard.scss index dc22327777..b5d54c35ab 100644 --- a/src/sass/_condensedCard.scss +++ b/src/sass/_condensedCard.scss @@ -67,15 +67,27 @@ $condensed-card-data-padding: 15px 20px 9px; background-color: $blue-primary; border-bottom: 1px solid $color-gray-lightest; color: $color-white; - overflow: hidden; padding: $condensed-card-padding; position: relative; + width: 100%; + + a, + .data { + font-weight: 300; + } a { color: $color-white; } } + .post-ribbon-container { + display: flex; + + div:nth-of-type(1) { flex: 3; } + div:nth-of-type(2) { flex: 2; } + } + .card-top-alternate { background-color: $tm-navy; } @@ -96,10 +108,14 @@ $condensed-card-data-padding: 15px 20px 9px; .condensed-card-top-header-left { float: left; + a { + display: inline-block; + } + h3 { display: inline; font-family: inherit; - font-size: inherit; + font-size: 1.1em; font-weight: inherit; line-height: inherit; margin-bottom: inherit; @@ -132,26 +148,48 @@ $condensed-card-data-padding: 15px 20px 9px; .condensed-card-bottom-container { background-color: $color-white; + flex-grow: 1; padding: $condensed-card-data-padding; padding-bottom: 5px; position: relative; } .condensed-card-inner { + display: flex; + flex-direction: column; height: 100%; position: relative; } .condensed-card-bottom { + display: flex; + flex-direction: column; font-size: 16px; + height: 100%; } .condensed-card-buttons-section { margin-bottom: 10px; + margin-left: 0; + margin-right: 0; + + button { + height: 44px; + margin-top: 0; + } + } + + .condensed-card-statistics-inner { + margin-left: 0; + margin-right: 0; } .condensed-card-statistics { border-top: 0; + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: flex-end; position: relative; .bid-count-list-item { @@ -182,8 +220,11 @@ $condensed-card-data-padding: 15px 20px 9px; } .condensed-card-data { + flex-grow: 1; line-height: 1.4em; margin-bottom: 20px; + margin-left: 0; + margin-right: 0; a { color: $blue-primary; @@ -302,4 +343,3 @@ $condensed-card-data-padding: 15px 20px 9px; display: flex; flex-wrap: wrap; } - diff --git a/src/sass/_details.scss b/src/sass/_details.scss index 0ed78f9452..27296267f4 100644 --- a/src/sass/_details.scss +++ b/src/sass/_details.scss @@ -1,3 +1,13 @@ +.position-details-outer-container { + position: relative; + + .handshake-offset-container { + left: -3px; + position: absolute; + top: -37px; + } +} + .position-details-header-container { color: $color-white; position: relative; diff --git a/src/sass/_ribbon.scss b/src/sass/_ribbon.scss new file mode 100644 index 0000000000..ddaa3b4c14 --- /dev/null +++ b/src/sass/_ribbon.scss @@ -0,0 +1,60 @@ +$ribbon-shadow-color: rgba(112, 112, 112, .5); + +.ribbon { + color: $color-black; + display: flex; + font-family: 'Merriweather'; + font-size: 1.3rem; + font-weight: bold; + padding-bottom: 5px; + padding-top: 6px; + + .fa { + font-size: 1.8rem; + margin-right: .5em; + } + + .text { + margin-top: .12em; + } +} + +.ribbon-primary { + background-color: $tertiary-gold-lighter; +} + +.ribbon-secondary { + background-color: $blue-primary-darkest; + color: $color-white; +} + +.ribbon-outer-container-cut-left { + box-shadow: 3px 4px 4px -4px $ribbon-shadow-color; +} + +.ribbon-outer-container-cut-right { + box-shadow: -3px 4px 4px -4px $ribbon-shadow-color; +} + +.ribbon-cut-left { + clip-path: polygon(9% 0, 100% 0, 100% 100%, 0 100%, 0 100%); + padding-left: 1em; + padding-right: 1em; +} + +.ribbon-cut-right { + clip-path: polygon(0 0, 100% 0, 91% 100%, 0 100%, 0 100%); + padding-left: .5em; + padding-right: 1em; +} + +.ribbon-results-card { + position: absolute; + right: -1px; +} + +.ribbon-condensed-card { + position: absolute; + right: 0; + z-index: $ribbon-z; +} diff --git a/src/sass/_variables.scss b/src/sass/_variables.scss index 027a206732..0c708ad074 100644 --- a/src/sass/_variables.scss +++ b/src/sass/_variables.scss @@ -161,13 +161,14 @@ $tm-focus-outline-offset-default: 3px; //* z-index //* // we'll use this section to keep track of and order them -$autosuggest-suggestions-container-open-z: 2; +$autosuggest-suggestions-container-open-z: 20; $dropdown-content-z: 15000; $feedback-z: 11000; $feedback-button-z: 10000; $scrollbar-z: 0; $skill-code-dropdown-z: 10500; $spinner-z: 100; +$ribbon-z: 10; //** // other variables diff --git a/src/sass/styles.scss b/src/sass/styles.scss index f45bd109a2..c81858d9ff 100644 --- a/src/sass/styles.scss +++ b/src/sass/styles.scss @@ -82,5 +82,6 @@ @import 'compareDrawer'; @import 'inputs'; @import 'toast'; +@import 'ribbon'; @import 'overrides'; @import 'accessibility'; diff --git a/yarn.lock b/yarn.lock index 5c61eef9e2..5dadd06ea7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2310,6 +2310,11 @@ crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" +css-box-shadow@^1.0.0-3: + version "1.0.0-3" + resolved "https://registry.yarnpkg.com/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz#9eaeb7140947bf5d649fc49a19e4bbaa5f602713" + integrity sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg== + css-color-names@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"