Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions src/common/AuthWrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,26 @@ import { describe, expect, it, vi } from 'vitest';

import { AuthWrapper } from './AuthWrapper';

vi.mock('src/stores/Profile/profile.store', () => ({
useProfileStore: () => ({
profile: FactoryUser({
roles: [UserRole.BETA_TESTER],
}),
}),
ProfileStoreProvider: ({ children }: { children: React.ReactNode }) => children,
}));
import type { Profile } from 'oa-shared';

const { ProfileStore } = await vi.hoisted(async () => {
const actual = await import('src/stores/Profile/profile.store');
return { ProfileStore: actual.ProfileStore };
});

const mockStore = new ProfileStore();
mockStore.profile = FactoryUser({
roles: [UserRole.BETA_TESTER],
}) as Profile;

vi.mock('src/stores/Profile/profile.store', async (importOriginal) => {
const actual: any = await importOriginal();
return {
...actual,
useProfileStore: () => mockStore,
ProfileStoreProvider: ({ children }: { children: React.ReactNode }) => children,
};
});

describe('AuthWrapper', () => {
it('renders fallback when user is not authorized', () => {
Expand Down
25 changes: 3 additions & 22 deletions src/common/AuthWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { observer } from 'mobx-react';
import { useProfileStore } from 'src/stores/Profile/profile.store';

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

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

export const AuthWrapper = observer((props: IProps) => {
const { borderLess, children, roleRequired } = props;
const { profile } = useProfileStore();

const isAuthorized = isUserAuthorized(profile, roleRequired);
const { isUserAuthorized } = useProfileStore();
const isAuthorized = isUserAuthorized(roleRequired);

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

return <>{isAuthorized ? childElements : props.fallback || <></>}</>;
});

export const isUserAuthorized = (user?: Profile | null, roleRequired?: UserRole | UserRole[]) => {
const userRoles = user?.roles || [];

// If no role required just check if user is logged in
if (!roleRequired || roleRequired.length === 0) {
return user ? true : false;
}

const rolesRequired = Array.isArray(roleRequired) ? roleRequired : [roleRequired];

// otherwise use logged in user profile values
if (user && roleRequired) {
return userRoles.some((role) => rolesRequired.includes(role as UserRole));
}

return false;
};
2 changes: 1 addition & 1 deletion src/pages/Library/Content/List/LibraryListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const LibraryListHeader = (props: IProps) => {

return (
<ListHeader
itemCount={showDrafts ? draftCount : itemCount}
itemCount={showDrafts ? draftCount : itemCount || 0}
actionComponents={actionComponents}
showDrafts={showDrafts}
headingTitle={headingTitle}
Expand Down
222 changes: 56 additions & 166 deletions src/pages/News/NewsListHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
// import { useCallback, useEffect, useState } from 'react'
import { Link } from 'react-router';
// import debounce from 'debounce'
import { Tooltip } from 'oa-components';
import { UserRole } from 'oa-shared';
import { AuthWrapper } from 'src/common/AuthWrapper';
// import { FieldContainer } from 'src/common/Form/FieldContainer'
import { UserAction } from 'src/common/UserAction';
// import {
// NewsSearchParams,
// } from 'src/pages/News/newsContent.service'
import { Button } from 'theme-ui';
import { useProfileStore } from 'src/stores/Profile/profile.store';
import { Button, Flex, Heading } from 'theme-ui';

import DraftButton from '../common/Drafts/DraftButton';
import { ListHeader } from '../common/Layout/ListHeader';
import { headings, listing } from './labels';
// import { NewsSortOptions } from './NewsSortOptions'

// import type { Category } from 'oa-shared'

interface IProps {
draftCount: number;
Expand All @@ -26,159 +14,61 @@ interface IProps {

export const NewsListHeader = (props: IProps) => {
const { draftCount, handleShowDrafts, showDrafts } = props;

// const [categories, setCategories] = useState<Category[]>([])
// const [searchString, setSearchString] = useState<string>('')

// const [searchParams, setSearchParams] = useSearchParams()
// const categoryParam = searchParams.get(NewsSearchParams.category)
// const category =
// (categoryParam && categories?.find((x) => x.id === +categoryParam)) ?? null
// const q = searchParams.get(NewsSearchParams.q)
// const sort = searchParams.get(NewsSearchParams.sort) as NewsSortOption

// useEffect(() => {
// const initCategories = async () => {
// const categories = (await newsContentService.getCategories()) || []
// setCategories(categories)
// }

// initCategories()
// }, [])

// useEffect(() => {
// setSearchString(q || '')
// }, [q])

// const updateFilter = useCallback(
// (key: NewsSearchParams, value: string) => {
// const params = new URLSearchParams(searchParams.toString())
// if (value) {
// params.set(key, value)
// } else {
// params.delete(key)
// }
// setSearchParams(params)
// },
// [searchParams],
// )

// const onSearchInputChange = useCallback(
// debounce((value: string) => {
// searchValue(value)
// }, 500),
// [searchParams],
// )

// const searchValue = (value: string) => {
// const params = new URLSearchParams(searchParams.toString())
// params.set('q', value)

// if (value.length > 0 && sort !== 'MostRelevant') {
// params.set('sort', 'MostRelevant')
// }

// if (value.length === 0 || !value) {
// params.set('sort', 'Newest')
// }

// setSearchParams(params)
// }

const actionComponents = (
<UserAction
incompleteProfile={
<AuthWrapper roleRequired={UserRole.ADMIN}>
<Link
to="/settings"
data-tooltip-id="tooltip"
data-tooltip-content={listing.incompleteProfile}
>
<Button type="button" data-cy="complete-profile-news" variant="disabled">
{listing.create}
</Button>
</Link>
<Tooltip id="tooltip" />
</AuthWrapper>
}
loggedIn={
<AuthWrapper roleRequired={UserRole.ADMIN}>
<DraftButton
showDrafts={showDrafts}
draftCount={draftCount}
handleShowDrafts={handleShowDrafts}
/>
<Link to="/news/create">
<Button type="button" data-cy="create-news" variant="primary">
{listing.create}
</Button>
</Link>
</AuthWrapper>
}
loggedOut={<></>}
/>
);

// const categoryComponent = (
// <CategoryHorizonalList
// allCategories={categories}
// activeCategory={category !== '' ? category : null}
// setActiveCategory={(updatedCategory) =>
// updateFilter(
// NewsSearchParams.category,
// updatedCategory ? (updatedCategory as Category).id.toString() : '',
// )
// }
// />
// )

// const filteringComponents = (
// <Flex
// sx={{
// gap: 2,
// flexDirection: ['column', 'column', 'row'],
// flexWrap: 'wrap',
// }}
// >
// <Flex sx={{ width: ['100%', '100%', '230px'] }}>
// <FieldContainer>
// <Select
// options={NewsSortOptions.toArray(!!q)}
// placeholder={listing.sort}
// value={{ label: NewsSortOptions.get(sort) }}
// onChange={(sortBy) =>
// updateFilter(NewsSearchParams.sort, sortBy.value)
// }
// />
// </FieldContainer>
// </Flex>
// <Flex sx={{ width: ['100%', '100%', '300px'] }}>
// <SearchField
// dataCy="news-search-box"
// placeHolder={listing.search}
// value={searchString}
// onChange={(value) => {
// setSearchString(value)
// onSearchInputChange(value)
// }}
// onClickDelete={() => {
// setSearchString('')
// searchValue('')
// }}
// onClickSearch={() => searchValue(searchString)}
// />
// </Flex>
// </Flex>
// )
const { isUserAuthorized } = useProfileStore();

return (
<ListHeader
actionComponents={actionComponents}
actionComponentsMaxWidth="650px"
showDrafts={showDrafts}
headingTitle={headings.list}
categoryComponent={<></>}
filteringComponents={<></>}
/>
<Flex
sx={{
flexDirection: 'column',
alignItems: 'center',
paddingTop: [6, 12],
paddingBottom: [4, 8],
gap: [4, 8],
}}
>
<Flex>
<Heading
as="h1"
sx={{
marginX: 'auto',
textAlign: 'center',
fontWeight: 'bold',
fontSize: 5,
}}
>
{headings.list}
</Heading>
</Flex>
{isUserAuthorized(UserRole.ADMIN) && (
<Flex
sx={{
justifyContent: 'space-between',
flexDirection: ['column', 'column', 'row'],
gap: [2, 2, 2],
paddingX: [2, 0],
maxWidth: '100%',
}}
>
<Flex
sx={{
gap: 2,
alignSelf: ['flex-start', 'flex-start', 'flex-end'],
display: ['none', 'none', 'flex'],
}}
>
<DraftButton
showDrafts={showDrafts}
draftCount={draftCount}
handleShowDrafts={handleShowDrafts}
/>
<Link to="/news/create">
<Button type="button" data-cy="create-news" variant="primary">
{listing.create}
</Button>
</Link>
</Flex>
</Flex>
)}
</Flex>
);
};
3 changes: 1 addition & 2 deletions src/pages/News/NewsListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const NewsListing = () => {

useEffect(() => {
if (!sort) {
// ensure sort is set
const params = new URLSearchParams(searchParams.toString());

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

return (
<Flex sx={{ flexDirection: 'column', gap: [2, 4], alignItems: 'center' }}>
<Flex sx={{ flexDirection: 'column', alignItems: 'center' }}>
<NewsListHeader
draftCount={draftCount}
handleShowDrafts={handleShowDrafts}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Question/QuestionListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export const QuestionListHeader = (props: IProps) => {

return (
<ListHeader
itemCount={showDrafts ? draftCount : itemCount}
itemCount={showDrafts ? draftCount : itemCount || 0}
actionComponents={actionComponents}
showDrafts={false}
headingTitle={headings.list}
Expand Down
Loading