From 5279a1076fe059460a9dc67fa83fc0b371dee74d Mon Sep 17 00:00:00 2001 From: Roy Johnson Date: Thu, 5 Dec 2024 11:27:44 -0600 Subject: [PATCH] Add sidebar for startup partners [CORE-609] --- src/app/pages/partners/results/results.scss | 31 +++++ src/app/pages/partners/results/results.tsx | 123 ++++++++++++++------ test/src/pages/partners/partners.test.tsx | 26 ++++- 3 files changed, 142 insertions(+), 38 deletions(-) diff --git a/src/app/pages/partners/results/results.scss b/src/app/pages/partners/results/results.scss index 210eef226..b95868e48 100644 --- a/src/app/pages/partners/results/results.scss +++ b/src/app/pages/partners/results/results.scss @@ -104,4 +104,35 @@ } } } + + .with-sidebar { + display: flex; + flex-direction: row; + gap: 3rem; + max-width: 120rem; + margin: 0 auto; + + > .boxed { + grid-gap: 3rem; + } + + .grid { + max-width: unset; + } + + > .sidebar { + min-width: 20rem; + border: thin solid black; + height: max-content; + + h2 { + background-color: white; + text-align: center; + } + + .grid { + gap: 0.2rem; + } + } + } } diff --git a/src/app/pages/partners/results/results.tsx b/src/app/pages/partners/results/results.tsx index 706a51ee4..bb5f2b15e 100644 --- a/src/app/pages/partners/results/results.tsx +++ b/src/app/pages/partners/results/results.tsx @@ -6,8 +6,9 @@ import {useDataFromPromise} from '~/helpers/page-data-utils'; import SelectedPartnerDialog from './selected-partner-dialog'; import shuffle from 'lodash/shuffle'; import orderBy from 'lodash/orderBy'; -import './results.scss'; +import partition from 'lodash/partition'; import {differenceInYears} from 'date-fns'; +import './results.scss'; export const costOptions = ['$0 - $10', '$11 - $25', '$26 - $40', '> $40'].map( (label) => ({ @@ -112,10 +113,7 @@ function filterBy( // eslint-disable-next-line complexity function useFilteredEntries(entries: PartnerEntry[]) { const {books, types, advanced, sort, resultCount} = useSearchContext(); - const unfilteredResults = React.useMemo( - () => shuffle(entries), - [entries] - ); + const unfilteredResults = React.useMemo(() => shuffle(entries), [entries]); const finalResult = React.useMemo(() => { let result = filterByBooks(unfilteredResults, books); @@ -160,8 +158,8 @@ function useFilteredEntries(entries: PartnerEntry[]) { } function advancedFilterKeys(partnerEntry: PartnerData) { - return (Object.keys(partnerEntry) as Array).filter( - (k) => ([false, true] as unknown[]).includes(partnerEntry[k]) + return (Object.keys(partnerEntry) as Array).filter((k) => + ([false, true] as unknown[]).includes(partnerEntry[k]) ); } @@ -204,11 +202,49 @@ function resultEntry(pd: PartnerData) { ratingCount: pd.rating_count, partnershipLevel: pd.partnership_level, yearsAsPartner: pd.partner_anniversary_date - ? differenceInYears(Date.now(), new Date(pd.partner_anniversary_date)) + ? differenceInYears( + Date.now(), + new Date(pd.partner_anniversary_date) + ) : null }; } +function Sidebar({entries}: {entries: PartnerEntry[]}) { + return ( +
+
+

Startups

+ +
+
+ ); +} + +const headings: Record = { + '10': '10+ years as Technology Partners', + '7': '7-10 years as partners', + '4': '4-7 years as partners', + '1': '1-3 years as partners', + new: 'New partners' +}; +const ages: Ages[] = ['10', '7', '4', '1', 'new']; + +function HeadingAndResultGrid({ + age, + entries +}: { + age: Ages; + entries: PartnerEntry[]; +}) { + return ( + +

{headings[age as Ages]}

+ +
+ ); +} + type Ages = '10' | '7' | '4' | '1' | 'new'; function ResultGridLoader({ @@ -223,15 +259,11 @@ function ResultGridLoader({ [partnerData] ); const filteredEntries = useFilteredEntries(entries); - const ages: Ages[] = ['10', '7', '4', '1']; - const headings: Record = { - '10': '10+ years as Technology Partners', - '7': '7-10 years as partners', - '4': '4-7 years as partners', - '1': '1-3 years as partners', - new: 'New partners' - }; - const partnersByAge = filteredEntries.reduce((a, b) => { + const [startups, nonStartups] = partition( + filteredEntries, + (e) => e.partnershipLevel?.toLowerCase() === 'startup' + ); + const partnersByAge = nonStartups.reduce((a, b) => { const bucket = ages.find((age) => (b.yearsAsPartner ?? 0) >= Number(age)) ?? 'new'; @@ -241,22 +273,52 @@ function ResultGridLoader({ a[bucket].push(b); return a; }, {} as Record); + const foundAges = ages.filter((a) => partnersByAge[a]); + + if (startups.length > 0) { + const [firstAge, ...otherAges] = foundAges; + return ( +
+
+ +
+ +
+
+ {otherAges.map((age) => ( + + ))} +
+ +
+
+ ); + } return ( - - {([...ages, 'new'] as Ages[]) - .filter((age) => age in partnersByAge) - .map((age) => ( - -

{headings[age as Ages]}

- -
- ))} +
+ {foundAges.map((age) => ( + + ))} - +
); } @@ -269,14 +331,9 @@ export default function Results({linkTexts}: {linkTexts: LinkTexts}) { [partnerData] ); - if (!partnerData) { return null; } - return ( -
- -
- ); + return ; } diff --git a/test/src/pages/partners/partners.test.tsx b/test/src/pages/partners/partners.test.tsx index d584a9f87..8e62deb72 100644 --- a/test/src/pages/partners/partners.test.tsx +++ b/test/src/pages/partners/partners.test.tsx @@ -62,7 +62,7 @@ describe('partners/results', () => { }); }); -describe('full page', () => { +describe('partners full page', () => { const user = userEvent.setup(); function Component() { @@ -74,12 +74,11 @@ describe('full page', () => { ); } - beforeEach(() => { - mockSfPartners.mockResolvedValue(sfPartners); - render(); - }); + jest.setTimeout(12000); it('displays grid that filters by type', async () => { + mockSfPartners.mockResolvedValue(sfPartners); + render(); const buttons = await screen.findAllByRole('button'); expect(buttons).toHaveLength(6); @@ -93,6 +92,8 @@ describe('full page', () => { expect(screen.getAllByRole('link')).toHaveLength(4); }); it('filters by book', async () => { + mockSfPartners.mockResolvedValue(sfPartners); + render(); const bookButton = await screen.findByRole('button', {name: 'Books'}); await user.click(bookButton); @@ -106,6 +107,8 @@ describe('full page', () => { expect(checkboxes).toHaveLength(24); }); it('filters by advanced filter', async () => { + mockSfPartners.mockResolvedValue(sfPartners); + render(); const filterButton = await screen.findByRole('button', { name: 'Advanced Filters' }); @@ -122,6 +125,8 @@ describe('full page', () => { expect(screen.getAllByRole('link')).toHaveLength(5); }); it('sorts', async () => { + mockSfPartners.mockResolvedValue(sfPartners); + render(); const sortButtons = await screen.findAllByRole('button', { name: 'Sort' }); @@ -170,6 +175,8 @@ describe('full page', () => { ]); }); it('shows details in dialog', async () => { + mockSfPartners.mockResolvedValue(sfPartners); + render(); const partnerLink = await screen.findByRole('link', {name: 'Rice Online Learning'}); await user.click(partnerLink); @@ -177,4 +184,13 @@ describe('full page', () => { screen.getByText('through the edX platform', {exact: false}); await user.click(screen.getByRole('button', {name: 'close'})); }); + it('displays sidebar of startups', async () => { + sfPartners[0].partnership_level = 'startup'; // eslint-disable-line + mockSfPartners.mockResolvedValue(sfPartners); + render(); + const startupHeading = await screen.findByRole('heading', {level: 2, name: 'Startups'}); + + expect(startupHeading.parentNode?.textContent).toContain(sfPartners[0].partner_name); + sfPartners[0].partnership_level = 'Full partner'; // eslint-disable-line + }); });