Skip to content

Commit fc84ee2

Browse files
authored
fix: news pagination & header UI (#4592)
1 parent de34c69 commit fc84ee2

File tree

10 files changed

+138
-266
lines changed

10 files changed

+138
-266
lines changed

src/common/AuthWrapper.test.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,26 @@ import { describe, expect, it, vi } from 'vitest';
66

77
import { AuthWrapper } from './AuthWrapper';
88

9-
vi.mock('src/stores/Profile/profile.store', () => ({
10-
useProfileStore: () => ({
11-
profile: FactoryUser({
12-
roles: [UserRole.BETA_TESTER],
13-
}),
14-
}),
15-
ProfileStoreProvider: ({ children }: { children: React.ReactNode }) => children,
16-
}));
9+
import type { Profile } from 'oa-shared';
10+
11+
const { ProfileStore } = await vi.hoisted(async () => {
12+
const actual = await import('src/stores/Profile/profile.store');
13+
return { ProfileStore: actual.ProfileStore };
14+
});
15+
16+
const mockStore = new ProfileStore();
17+
mockStore.profile = FactoryUser({
18+
roles: [UserRole.BETA_TESTER],
19+
}) as Profile;
20+
21+
vi.mock('src/stores/Profile/profile.store', async (importOriginal) => {
22+
const actual: any = await importOriginal();
23+
return {
24+
...actual,
25+
useProfileStore: () => mockStore,
26+
ProfileStoreProvider: ({ children }: { children: React.ReactNode }) => children,
27+
};
28+
});
1729

1830
describe('AuthWrapper', () => {
1931
it('renders fallback when user is not authorized', () => {

src/common/AuthWrapper.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import { observer } from 'mobx-react';
33
import { useProfileStore } from 'src/stores/Profile/profile.store';
44

5-
import type { Profile, UserRole } from 'oa-shared';
5+
import type { UserRole } from 'oa-shared';
66

77
/*
88
Simple wrapper to only render a component if the user is logged in (plus optional user role required)
@@ -17,9 +17,8 @@ interface IProps {
1717

1818
export const AuthWrapper = observer((props: IProps) => {
1919
const { borderLess, children, roleRequired } = props;
20-
const { profile } = useProfileStore();
21-
22-
const isAuthorized = isUserAuthorized(profile, roleRequired);
20+
const { isUserAuthorized } = useProfileStore();
21+
const isAuthorized = isUserAuthorized(roleRequired);
2322

2423
const childElements =
2524
roleRequired === 'beta-tester' && !borderLess ? (
@@ -30,21 +29,3 @@ export const AuthWrapper = observer((props: IProps) => {
3029

3130
return <>{isAuthorized ? childElements : props.fallback || <></>}</>;
3231
});
33-
34-
export const isUserAuthorized = (user?: Profile | null, roleRequired?: UserRole | UserRole[]) => {
35-
const userRoles = user?.roles || [];
36-
37-
// If no role required just check if user is logged in
38-
if (!roleRequired || roleRequired.length === 0) {
39-
return user ? true : false;
40-
}
41-
42-
const rolesRequired = Array.isArray(roleRequired) ? roleRequired : [roleRequired];
43-
44-
// otherwise use logged in user profile values
45-
if (user && roleRequired) {
46-
return userRoles.some((role) => rolesRequired.includes(role as UserRole));
47-
}
48-
49-
return false;
50-
};

src/pages/Library/Content/List/LibraryListHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export const LibraryListHeader = (props: IProps) => {
166166

167167
return (
168168
<ListHeader
169-
itemCount={showDrafts ? draftCount : itemCount}
169+
itemCount={showDrafts ? draftCount : itemCount || 0}
170170
actionComponents={actionComponents}
171171
showDrafts={showDrafts}
172172
headingTitle={headingTitle}

src/pages/News/NewsListHeader.tsx

Lines changed: 56 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
1-
// import { useCallback, useEffect, useState } from 'react'
21
import { Link } from 'react-router';
3-
// import debounce from 'debounce'
4-
import { Tooltip } from 'oa-components';
52
import { UserRole } from 'oa-shared';
6-
import { AuthWrapper } from 'src/common/AuthWrapper';
7-
// import { FieldContainer } from 'src/common/Form/FieldContainer'
8-
import { UserAction } from 'src/common/UserAction';
9-
// import {
10-
// NewsSearchParams,
11-
// } from 'src/pages/News/newsContent.service'
12-
import { Button } from 'theme-ui';
3+
import { useProfileStore } from 'src/stores/Profile/profile.store';
4+
import { Button, Flex, Heading } from 'theme-ui';
135

146
import DraftButton from '../common/Drafts/DraftButton';
15-
import { ListHeader } from '../common/Layout/ListHeader';
167
import { headings, listing } from './labels';
17-
// import { NewsSortOptions } from './NewsSortOptions'
18-
19-
// import type { Category } from 'oa-shared'
208

219
interface IProps {
2210
draftCount: number;
@@ -26,159 +14,61 @@ interface IProps {
2614

2715
export const NewsListHeader = (props: IProps) => {
2816
const { draftCount, handleShowDrafts, showDrafts } = props;
29-
30-
// const [categories, setCategories] = useState<Category[]>([])
31-
// const [searchString, setSearchString] = useState<string>('')
32-
33-
// const [searchParams, setSearchParams] = useSearchParams()
34-
// const categoryParam = searchParams.get(NewsSearchParams.category)
35-
// const category =
36-
// (categoryParam && categories?.find((x) => x.id === +categoryParam)) ?? null
37-
// const q = searchParams.get(NewsSearchParams.q)
38-
// const sort = searchParams.get(NewsSearchParams.sort) as NewsSortOption
39-
40-
// useEffect(() => {
41-
// const initCategories = async () => {
42-
// const categories = (await newsContentService.getCategories()) || []
43-
// setCategories(categories)
44-
// }
45-
46-
// initCategories()
47-
// }, [])
48-
49-
// useEffect(() => {
50-
// setSearchString(q || '')
51-
// }, [q])
52-
53-
// const updateFilter = useCallback(
54-
// (key: NewsSearchParams, value: string) => {
55-
// const params = new URLSearchParams(searchParams.toString())
56-
// if (value) {
57-
// params.set(key, value)
58-
// } else {
59-
// params.delete(key)
60-
// }
61-
// setSearchParams(params)
62-
// },
63-
// [searchParams],
64-
// )
65-
66-
// const onSearchInputChange = useCallback(
67-
// debounce((value: string) => {
68-
// searchValue(value)
69-
// }, 500),
70-
// [searchParams],
71-
// )
72-
73-
// const searchValue = (value: string) => {
74-
// const params = new URLSearchParams(searchParams.toString())
75-
// params.set('q', value)
76-
77-
// if (value.length > 0 && sort !== 'MostRelevant') {
78-
// params.set('sort', 'MostRelevant')
79-
// }
80-
81-
// if (value.length === 0 || !value) {
82-
// params.set('sort', 'Newest')
83-
// }
84-
85-
// setSearchParams(params)
86-
// }
87-
88-
const actionComponents = (
89-
<UserAction
90-
incompleteProfile={
91-
<AuthWrapper roleRequired={UserRole.ADMIN}>
92-
<Link
93-
to="/settings"
94-
data-tooltip-id="tooltip"
95-
data-tooltip-content={listing.incompleteProfile}
96-
>
97-
<Button type="button" data-cy="complete-profile-news" variant="disabled">
98-
{listing.create}
99-
</Button>
100-
</Link>
101-
<Tooltip id="tooltip" />
102-
</AuthWrapper>
103-
}
104-
loggedIn={
105-
<AuthWrapper roleRequired={UserRole.ADMIN}>
106-
<DraftButton
107-
showDrafts={showDrafts}
108-
draftCount={draftCount}
109-
handleShowDrafts={handleShowDrafts}
110-
/>
111-
<Link to="/news/create">
112-
<Button type="button" data-cy="create-news" variant="primary">
113-
{listing.create}
114-
</Button>
115-
</Link>
116-
</AuthWrapper>
117-
}
118-
loggedOut={<></>}
119-
/>
120-
);
121-
122-
// const categoryComponent = (
123-
// <CategoryHorizonalList
124-
// allCategories={categories}
125-
// activeCategory={category !== '' ? category : null}
126-
// setActiveCategory={(updatedCategory) =>
127-
// updateFilter(
128-
// NewsSearchParams.category,
129-
// updatedCategory ? (updatedCategory as Category).id.toString() : '',
130-
// )
131-
// }
132-
// />
133-
// )
134-
135-
// const filteringComponents = (
136-
// <Flex
137-
// sx={{
138-
// gap: 2,
139-
// flexDirection: ['column', 'column', 'row'],
140-
// flexWrap: 'wrap',
141-
// }}
142-
// >
143-
// <Flex sx={{ width: ['100%', '100%', '230px'] }}>
144-
// <FieldContainer>
145-
// <Select
146-
// options={NewsSortOptions.toArray(!!q)}
147-
// placeholder={listing.sort}
148-
// value={{ label: NewsSortOptions.get(sort) }}
149-
// onChange={(sortBy) =>
150-
// updateFilter(NewsSearchParams.sort, sortBy.value)
151-
// }
152-
// />
153-
// </FieldContainer>
154-
// </Flex>
155-
// <Flex sx={{ width: ['100%', '100%', '300px'] }}>
156-
// <SearchField
157-
// dataCy="news-search-box"
158-
// placeHolder={listing.search}
159-
// value={searchString}
160-
// onChange={(value) => {
161-
// setSearchString(value)
162-
// onSearchInputChange(value)
163-
// }}
164-
// onClickDelete={() => {
165-
// setSearchString('')
166-
// searchValue('')
167-
// }}
168-
// onClickSearch={() => searchValue(searchString)}
169-
// />
170-
// </Flex>
171-
// </Flex>
172-
// )
17+
const { isUserAuthorized } = useProfileStore();
17318

17419
return (
175-
<ListHeader
176-
actionComponents={actionComponents}
177-
actionComponentsMaxWidth="650px"
178-
showDrafts={showDrafts}
179-
headingTitle={headings.list}
180-
categoryComponent={<></>}
181-
filteringComponents={<></>}
182-
/>
20+
<Flex
21+
sx={{
22+
flexDirection: 'column',
23+
alignItems: 'center',
24+
paddingTop: [6, 12],
25+
paddingBottom: [4, 8],
26+
gap: [4, 8],
27+
}}
28+
>
29+
<Flex>
30+
<Heading
31+
as="h1"
32+
sx={{
33+
marginX: 'auto',
34+
textAlign: 'center',
35+
fontWeight: 'bold',
36+
fontSize: 5,
37+
}}
38+
>
39+
{headings.list}
40+
</Heading>
41+
</Flex>
42+
{isUserAuthorized(UserRole.ADMIN) && (
43+
<Flex
44+
sx={{
45+
justifyContent: 'space-between',
46+
flexDirection: ['column', 'column', 'row'],
47+
gap: [2, 2, 2],
48+
paddingX: [2, 0],
49+
maxWidth: '100%',
50+
}}
51+
>
52+
<Flex
53+
sx={{
54+
gap: 2,
55+
alignSelf: ['flex-start', 'flex-start', 'flex-end'],
56+
display: ['none', 'none', 'flex'],
57+
}}
58+
>
59+
<DraftButton
60+
showDrafts={showDrafts}
61+
draftCount={draftCount}
62+
handleShowDrafts={handleShowDrafts}
63+
/>
64+
<Link to="/news/create">
65+
<Button type="button" data-cy="create-news" variant="primary">
66+
{listing.create}
67+
</Button>
68+
</Link>
69+
</Flex>
70+
</Flex>
71+
)}
72+
</Flex>
18373
);
18474
};

src/pages/News/NewsListing.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export const NewsListing = () => {
2828

2929
useEffect(() => {
3030
if (!sort) {
31-
// ensure sort is set
3231
const params = new URLSearchParams(searchParams.toString());
3332

3433
if (q) {
@@ -70,7 +69,7 @@ export const NewsListing = () => {
7069
const newsList = showDrafts ? drafts : news;
7170

7271
return (
73-
<Flex sx={{ flexDirection: 'column', gap: [2, 4], alignItems: 'center' }}>
72+
<Flex sx={{ flexDirection: 'column', alignItems: 'center' }}>
7473
<NewsListHeader
7574
draftCount={draftCount}
7675
handleShowDrafts={handleShowDrafts}

src/pages/Question/QuestionListHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export const QuestionListHeader = (props: IProps) => {
171171

172172
return (
173173
<ListHeader
174-
itemCount={showDrafts ? draftCount : itemCount}
174+
itemCount={showDrafts ? draftCount : itemCount || 0}
175175
actionComponents={actionComponents}
176176
showDrafts={false}
177177
headingTitle={headings.list}

0 commit comments

Comments
 (0)