Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

News List #66

Merged
merged 30 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
05be489
initial client side query params
Woozl Nov 4, 2023
c15f72b
add optional abort controller to fetchStrapiGraphQL fn
Woozl Sep 27, 2023
c6784ca
initial layout
Woozl Nov 7, 2023
f2e5fbb
add loading states
Woozl Nov 8, 2023
bf83b5c
change link prop from `href` to `to`
Woozl Nov 8, 2023
46bd5eb
integrate new /posts REST endpoint for article fetch
Woozl Nov 8, 2023
1d045ae
add pagination and fix filter operators
Woozl Nov 8, 2023
e8fed55
hide clear all button when there are no filters
Woozl Nov 8, 2023
f247d51
fix hydration mismatch error
Woozl Nov 8, 2023
b6b9e66
remove duplicated jsx for skeleton
Woozl Nov 8, 2023
cd6bf67
add swr to news article fetch
Woozl Nov 9, 2023
5d553ac
remove unused import
Woozl Nov 9, 2023
8063f17
add `isTagSelected` function for highlighting selected tags in articl…
Woozl Nov 9, 2023
72f8115
add tag add/delete to article preview tag list
Woozl Nov 10, 2023
1b5d5cf
highlight freeSearch text in title
Woozl Nov 10, 2023
9900d85
fix typo causing add/delete tag bug
Woozl Nov 10, 2023
128a7d4
add filter menu modal
Woozl Nov 10, 2023
ba8ffb1
fix skeleton on mobile
Woozl Nov 10, 2023
0aa6ea0
put tags under title on mobile
Woozl Nov 10, 2023
024734f
add free search tag preview and instructional text
Woozl Nov 10, 2023
6085852
add error component
Woozl Nov 10, 2023
4fc124a
fix react key error if user submitted same custom text as a slug/name
Woozl Nov 10, 2023
71849a0
add new tags endpoint + swr caching
Woozl Nov 16, 2023
22f93e5
swap to new /post-list endpoint, update swr cache
Woozl Nov 16, 2023
95f759c
fix deleteTag function bug
Woozl Nov 17, 2023
32144ed
increase excerpt length
Woozl Nov 17, 2023
593ee1b
consolidate article type toggle styles
Woozl Nov 17, 2023
90778c3
add missing doc text
Woozl Nov 17, 2023
c1847dc
add tags error ui
Woozl Nov 17, 2023
021626b
change from news/feature to blog/feature
Woozl Nov 17, 2023
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
90 changes: 90 additions & 0 deletions components/news/article-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useNewsArticles } from "@/lib/strapi/newsSWR";
import { Box, Pagination, Skeleton, Stack, Typography } from "@mui/material"
import { ArticlePreview } from "./article-preview";
import { useMemo } from "react";
import { Error } from "./error";

export const ArticleList = ({
selectedTags,
isTagSelected,
deleteTag,
addTag,
blogOrFeature,
page,
setPage,
}) => {
const {
data,
isLoading,
error,
} = useNewsArticles({
filters: {
collaborations: selectedTags.collaborations.map(({ slug }) => slug),
organizations: selectedTags.organizations.map(({ slug }) => slug),
people: selectedTags.people.map(({ slug }) => slug),
projects: selectedTags.projects.map(({ slug }) => slug),
researchGroups: selectedTags.researchGroups.map(({ slug }) => slug),
postTags: selectedTags.postTags.map(({ name }) => name),
freeSearch: selectedTags.freeSearch,
newsOrBlog:
blogOrFeature === 'blog'
? 'blog'
: blogOrFeature === 'feature'
? 'news'
: undefined,
},
page,
});

const freeSearch = useMemo(() => (selectedTags.freeSearch), [selectedTags]);

if(error) {
return <Error
message="We're sorry, something went wrong. Ensure your device has internet access. Refresh or go back a page."
sx={{ my: 4 }}
/>
}

if (isLoading || !data) return <Stack><ArticleListSkeleton /></Stack>

const { results: articles, pagination } = data;

if (Array.isArray(articles) && articles.length === 0) return <NoArticlesText />

return <Stack>
<Stack direction='column' gap={4} paddingY={4}>
{articles.map((article, i) => (
<ArticlePreview
key={i}
article={article}
isTagSelected={isTagSelected}
deleteTag={deleteTag}
addTag={addTag}
freeSearch={freeSearch}
/>
))}
</Stack>

<Pagination count={pagination?.pageCount} page={page} onChange={(_, p) => page === p ? null : setPage(p)} sx={{ alignSelf: 'center' }} />
</Stack>
}

const NoArticlesText = () => (
<Stack sx={{ minHeight: '300px' }} alignItems='center' justifyContent='center' gap={1}>
<Typography variant="h3" fontWeight='bold'>No results</Typography>
<Typography variant="subtitle1" maxWidth='35ch' textAlign='center'>
We couldn&apos;t find any articles matching your filters. Please remove some and try again.
</Typography>
</Stack>
)

export const ArticleListSkeleton = () => (
<Stack direction='column' gap={4} paddingY={4}>
{new Array(25).fill(0).map((_, i) => <Box key={i}>
<Stack gap={1}>
<Skeleton variant="rectangular" sx={{ borderRadius: '8px', maxWidth: '60%' }} height="2rem" />
<Skeleton variant="rectangular" sx={{ borderRadius: '8px' }} height="6rem" />
</Stack>
</Box>)}
</Stack>
)
114 changes: 114 additions & 0 deletions components/news/article-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Box, Skeleton, Stack, Typography } from "@mui/material"
import { Link } from "../link"
import { Tag } from "./tag"
import match from "autosuggest-highlight/match";
import parse from "autosuggest-highlight/parse";

export const ArticlePreview = ({
article,
isTagSelected,
deleteTag,
addTag,
freeSearch,
skeleton = false,
}) => {
const date = new Date(article.publishDate)
const [day, month, year] = [
date.getUTCDate(),
date.getUTCMonth() + 1,
date.getUTCFullYear(),
]
const dateString = date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });

const articleLink = `news/${year}/${month}/${day}/${article.slug}`;

const tags = [
article.projects.map((x) => ({ ...x, type: 'projects' })),
article.people.map((x) => ({ ...x, type: 'people' })),
article.collaborations.map((x) => ({ ...x, type: 'collaborations' })),
article.researchGroups.map((x) => ({ ...x, type: 'researchGroups' })),
article.organizations.map((x) => ({ ...x, type: 'organizations' })),
article.tags.map((x) => ({ ...x, type: 'postTags' }))
].flat();

const titleFreeSearchMatches = match(article.title, freeSearch.join(' '), {
findAllOccurrences: true,
insideWords: true,
});
const titleHighlightSections = parse(article.title, titleFreeSearchMatches);

if (skeleton) return <Box>
<Skeleton />
</Box>

return <Stack gap={1}>
<Box>
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'column', md: 'row' },
alignItems: { xs: 'initial', sm: 'initial', md: 'baseline' },
gap: { xs: 1, sm: 1, md: 2 },
mb: { xs: 1, sm: 1, md: 0 },
}}>
<Stack direction='row' alignItems='center' gap={1}>
<Typography variant="subtitle2" whiteSpace='nowrap'>{dateString}</Typography>
<Box sx={{ width: '0.3em', height: '0.3em', backgroundColor: '#b6b6b6', borderRadius: '50%', flex: '0 0 auto' }} />
<Typography variant="subtitle2" textTransform='uppercase'>{
article.newsOrBlog === 'blog' ? 'Blog' : 'Feature'
}</Typography>
</Stack>

<Stack direction='row' flex="1" gap={1} sx={{ overflowX: 'auto', direction: { xs: 'ltr', sm: 'ltr', md: 'rtl'} }}>
{tags.map(({ name, slug, type }, i) => {
const id = type === 'postTags' ? name : slug;
const isSelected = isTagSelected(id, type);
return <Tag
key={i}
type={type}
contents={name}
inverted={isSelected}
onClick={!isSelected ? () => { addTag(id, type) } : undefined}
onDelete={isSelected ? () => { deleteTag(id, type) } : undefined}
sx={{ minWidth: 'fit-content' }}
/>
})}
</Stack>
</Box>
<Typography variant="h3" sx={{ '& a': { textDecoration: 'none' }}}>
<Link to={articleLink}>{
titleHighlightSections.map((part, i) => (
<span key={i} style={{
fontWeight: part.highlight ? 'bold' : 'initial'
}}>{part.text}</span>
))
}</Link>
</Typography>
</Box>


<Typography paragraph className="excerpt" sx={{
'--maxHeight': 'calc(4rem * 1.5)',
'&:before': {
content: "''",
width: '100%',
height: '100%',
position: 'absolute',
left: 0,
top: 0,
pointerEvents: 'none',
background: 'linear-gradient(transparent 0px, white calc(var(--maxHeight) - 4px ))'
},
'& > .hover-link': {
position: 'absolute',
bottom: 0,
right: 0,
},
position: 'relative',
maxHeight: 'var(--maxHeight)',
overflow: 'hidden',
}}>
{article.excerpt}
<Link to={articleLink} className='hover-link'>Read more →</Link>
</Typography>
</Stack>
}
Loading