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
35 changes: 35 additions & 0 deletions packages/shared/src/components/BookmarkEmptyScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { ReactElement } from 'react';
import Link from 'next/link';
import BookmarkIcon from '../../icons/bookmark.svg';
import { headerHeight } from '../styles/sizes';
import sizeN from '../../macros/sizeN.macro';
import { Button } from './buttons/Button';

export default function BookmarkEmptyScreen(): ReactElement {
return (
<main
className="flex fixed inset-0 flex-col justify-center items-center px-6 withNavBar text-theme-label-secondary"
style={{ marginTop: headerHeight }}
>
<BookmarkIcon
className="m-0 icon text-theme-label-tertiary"
style={{ fontSize: sizeN(20) }}
/>
<h1
className="my-4 text-center text-theme-label-primary typo-title1"
style={{ maxWidth: '32.5rem' }}
>
Your bookmark list is empty.
</h1>
<p className="mb-10 text-center" style={{ maxWidth: '32.5rem' }}>
Go back to your feed and bookmark posts you’d like to keep or read
later. Each post you bookmark will be stored here.
</p>
<Link href="/" passHref>
<Button className="btn-primary" tag="a" buttonSize="large">
Back to feed
</Button>
</Link>
</main>
);
}
79 changes: 79 additions & 0 deletions packages/shared/src/components/BookmarkFeedLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, {
ReactElement,
ReactNode,
useContext,
useMemo,
useState,
} from 'react';
import Link from 'next/link';
import MagnifyingIcon from '../../icons/magnifying.svg';
import { BOOKMARKS_FEED_QUERY, SEARCH_BOOKMARKS_QUERY } from '../graphql/feed';
import AuthContext from '../contexts/AuthContext';
import { CustomFeedHeader, FeedPage } from './utilities';
import SearchEmptyScreen from './SearchEmptyScreen';
import Feed, { FeedProps } from './Feed';
import BookmarkEmptyScreen from './BookmarkEmptyScreen';
import { Button } from './buttons/Button';

export type BookmarkFeedLayoutProps = {
isSearchOn: boolean;
searchQuery?: string;
children?: ReactNode;
searchChildren: ReactNode;
onSearchButtonClick?: () => unknown;
};

export default function BookmarkFeedLayout({
searchQuery,
isSearchOn,
searchChildren,
children,
}: BookmarkFeedLayoutProps): ReactElement {
const { user, tokenRefreshed } = useContext(AuthContext);
const [showEmptyScreen, setShowEmptyScreen] = useState(false);

const feedProps = useMemo<FeedProps<unknown>>(() => {
if (isSearchOn && searchQuery) {
return {
feedQueryKey: ['bookmarks', user?.id ?? 'anonymous', searchQuery],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the bookmarks while not logging in should not be possible.
If the user is not logged in maybe we should disable the query

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually can't get the bookmarks feed if not logged in.
The BookmarkFeedPage will redirect you if the token is refreshed.

I assumed this was a second fallback in the split second.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and fully tested locally yes

query: SEARCH_BOOKMARKS_QUERY,
variables: { query: searchQuery },
emptyScreen: <SearchEmptyScreen />,
className: 'my-3',
};
}
return {
feedQueryKey: ['bookmarks', user?.id ?? 'anonymous'],
query: BOOKMARKS_FEED_QUERY,
className: 'my-3',
onEmptyFeed: () => setShowEmptyScreen(true),
};
}, [isSearchOn && searchQuery, user]);

if (showEmptyScreen) {
return <BookmarkEmptyScreen />;
}

return (
<FeedPage>
{children}
<CustomFeedHeader className="relative">
{!isSearchOn && (
<>
<Link href="/bookmarks/search">
<Button
href="/bookmarks/search"
aria-label="Search bookmarks"
icon={<MagnifyingIcon />}
/>
</Link>
<div className="mx-4 w-px h-full bg-theme-bg-tertiary">&nbsp;</div>
<span className="font-bold typo-callout">Bookmarks</span>
</>
)}
{isSearchOn ? searchChildren : undefined}
</CustomFeedHeader>
{tokenRefreshed && <Feed {...feedProps} />}
</FeedPage>
);
}
19 changes: 13 additions & 6 deletions packages/shared/src/components/PostsSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { SearchField } from './fields/SearchField';
import { useAutoComplete } from '../hooks/useAutoComplete';
import { apiUrl } from '../lib/config';
import { SEARCH_POST_SUGGESTIONS } from '../graphql/search';
import { SEARCH_BOOKMARKS_SUGGESTIONS } from '../graphql/feed';

const AutoCompleteMenu = dynamic(() => import('./fields/AutoCompleteMenu'), {
ssr: false,
Expand All @@ -15,12 +16,14 @@ export type PostsSearchProps = {
initialQuery?: string;
onSubmitQuery: (query: string) => Promise<unknown>;
closeSearch: () => unknown;
suggestionType?: string;
};

export default function PostsSearch({
initialQuery: initialQueryProp,
onSubmitQuery,
closeSearch,
suggestionType = 'searchPostSuggestions',
}: PostsSearchProps): ReactElement {
const searchBoxRef = useRef<HTMLDivElement>();
const [initialQuery, setInitialQuery] = useState<string>();
Expand All @@ -33,11 +36,16 @@ export default function PostsSearch({
}>(null);
const [items, setItems] = useState<string[]>([]);

const SEARCH_URL =
suggestionType === 'searchPostSuggestions'
? SEARCH_POST_SUGGESTIONS
: SEARCH_BOOKMARKS_SUGGESTIONS;

const { data: searchResults, isLoading } = useQuery<{
searchPostSuggestions: { hits: { title: string }[] };
[suggestionType: string]: { hits: { title: string }[] };
}>(
['searchPostSuggestions', query],
() => request(`${apiUrl}/graphql`, SEARCH_POST_SUGGESTIONS, { query }),
[suggestionType, query],
() => request(`${apiUrl}/graphql`, SEARCH_URL, { query }),
{
enabled: !!query,
},
Expand All @@ -62,12 +70,11 @@ export default function PostsSearch({

useEffect(() => {
if (!isLoading) {
if (!items?.length && searchResults?.searchPostSuggestions?.hits.length) {
if (!items?.length && searchResults?.[suggestionType]?.hits.length) {
showSuggestions();
}
setItems(
searchResults?.searchPostSuggestions?.hits.map((hit) => hit.title) ??
[],
searchResults?.[suggestionType]?.hits.map((hit) => hit.title) ?? [],
);
}
}, [searchResults, isLoading]);
Expand Down
24 changes: 24 additions & 0 deletions packages/shared/src/graphql/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ export const BOOKMARKS_FEED_QUERY = gql`
${FEED_POST_CONNECTION_FRAGMENT}
`;

export const SEARCH_BOOKMARKS_QUERY = gql`
query SearchBookmarks(
$loggedIn: Boolean! = false
$first: Int
$after: String
$query: String!
) {
page: searchBookmarks(first: $first, after: $after, query: $query) {
...FeedPostConnection
}
}
${FEED_POST_CONNECTION_FRAGMENT}
`;

export const SEARCH_BOOKMARKS_SUGGESTIONS = gql`
query SearchBookmarksSuggestions($query: String!) {
searchBookmarksSuggestions(query: $query) {
hits {
title
}
}
}
`;

export const SEARCH_POSTS_QUERY = gql`
query SearchPosts(
$loggedIn: Boolean! = false
Expand Down
15 changes: 14 additions & 1 deletion packages/webapp/__tests__/BookmarksPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ const renderComponent = (
}}
>
<SettingsContext.Provider value={settingsContext}>
<BookmarksPage />
{BookmarksPage.getLayout(
<BookmarksPage />,
{},
BookmarksPage.layoutProps,
)}
</SettingsContext.Provider>
</OnboardingContext.Provider>
</AuthContext.Provider>
Expand Down Expand Up @@ -143,3 +147,12 @@ it('should show empty screen when feed is empty', async () => {
expect(elements.length).toBeFalsy();
});
});

it('should set href to the search permalink', async () => {
renderComponent();
await waitForNock();
await waitFor(async () => {
const searchBtn = await screen.findByLabelText('Search bookmarks');
expect(searchBtn).toHaveAttribute('href', '/bookmarks/search');
});
});
Loading