diff --git a/packages/api/src/resolvers/subscriptions/index.ts b/packages/api/src/resolvers/subscriptions/index.ts index 95fc2abc0..beb246649 100644 --- a/packages/api/src/resolvers/subscriptions/index.ts +++ b/packages/api/src/resolvers/subscriptions/index.ts @@ -31,6 +31,7 @@ import { Merge } from '../../util' import { analytics } from '../../utils/analytics' import { enqueueRssFeedFetch } from '../../utils/createTask' import { authorized } from '../../utils/helpers' +import { Brackets } from 'typeorm' type PartialSubscription = Omit @@ -76,20 +77,26 @@ export const subscriptionsResolver = authorized< user: { id: uid }, }) - // Show all RSS, but only active newsletters - queryBuilder - .where({ - type: SubscriptionType.Newsletter, - status: SubscriptionStatus.Active, - }) - .orWhere({ - type: SubscriptionType.Rss, - }) - - if (type) { + if (type && type == SubscriptionType.Newsletter) { queryBuilder.andWhere({ type, + status: SubscriptionStatus.Active, }) + } else if (type && type == SubscriptionType.Rss) { + queryBuilder.where({ + type, + }) + } else { + queryBuilder.andWhere( + new Brackets((qb) => { + qb.where({ + type: SubscriptionType.Newsletter, + status: SubscriptionStatus.Active, + }).orWhere({ + type: SubscriptionType.Rss, + }) + }) + ) } const subscriptions = await queryBuilder diff --git a/packages/api/test/resolvers/subscriptions.test.ts b/packages/api/test/resolvers/subscriptions.test.ts index 6eda49441..71d412bec 100644 --- a/packages/api/test/resolvers/subscriptions.test.ts +++ b/packages/api/test/resolvers/subscriptions.test.ts @@ -10,7 +10,10 @@ import { SubscriptionStatus, SubscriptionType, } from '../../src/generated/graphql' -import { UNSUBSCRIBE_EMAIL_TEXT } from '../../src/services/subscriptions' +import { + UNSUBSCRIBE_EMAIL_TEXT, + unsubscribe, +} from '../../src/services/subscriptions' import * as sendEmail from '../../src/utils/sendEmail' import { createTestSubscription, createTestUser, deleteTestUser } from '../db' import { graphqlRequest, request } from '../util' @@ -134,44 +137,52 @@ describe('Subscriptions API', () => { undefined, SubscriptionType.Rss ) - await createTestSubscription( - user, - 'sub_6', - undefined, - SubscriptionStatus.Unsubscribed, - undefined, - SubscriptionType.Newsletter - ) - const allSubscriptions = [sub5, ...subscriptions] - const res = await graphqlRequest(query, authToken).expect(200) - expect(res.body.data.subscriptions.subscriptions).to.eql( - allSubscriptions.map((sub) => ({ - id: sub.id, - name: sub.name, - })) - ) + try { + await createTestSubscription( + user, + 'sub_6', + undefined, + SubscriptionStatus.Unsubscribed, + undefined, + SubscriptionType.Newsletter + ) + const allSubscriptions = [sub5, ...subscriptions] + const res = await graphqlRequest(query, authToken).expect(200) + + expect(res.body.data.subscriptions.subscriptions).to.eql( + allSubscriptions.map((sub) => ({ + id: sub.id, + name: sub.name, + })) + ) + } finally { + unsubscribe(sub5) + } }) it('should not return other users subscriptions', async () => { // create test user and login - const user2 = await createTestUser('fakeUser') - await createTestSubscription( - user2, - 'sub_other', - undefined, - SubscriptionStatus.Unsubscribed, - undefined, - SubscriptionType.Rss - ) - const res = await graphqlRequest(query, authToken).expect(200) - - expect(res.body.data.subscriptions.subscriptions).to.eql( - subscriptions.map((sub) => ({ - id: sub.id, - name: sub.name, - })) - ) + const user2 = await createTestUser('fakeUser2') + try { + await createTestSubscription( + user2, + 'sub_other', + undefined, + SubscriptionStatus.Unsubscribed, + undefined, + SubscriptionType.Rss + ) + const res = await graphqlRequest(query, authToken).expect(200) + expect(res.body.data.subscriptions.subscriptions).to.eql( + subscriptions.map((sub) => ({ + id: sub.id, + name: sub.name, + })) + ) + } finally { + deleteTestUser(user2.id) + } }) it('responds status code 400 when invalid query', async () => { diff --git a/packages/web/components/elements/icons/ToggleCaretDownIcon.tsx b/packages/web/components/elements/icons/ToggleCaretDownIcon.tsx new file mode 100644 index 000000000..946ea3a86 --- /dev/null +++ b/packages/web/components/elements/icons/ToggleCaretDownIcon.tsx @@ -0,0 +1,32 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class ToggleCaretDownIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + ) + } +} diff --git a/packages/web/components/elements/icons/ToggleCaretLeftIcon.tsx b/packages/web/components/elements/icons/ToggleCaretLeftIcon.tsx new file mode 100644 index 000000000..85796ecab --- /dev/null +++ b/packages/web/components/elements/icons/ToggleCaretLeftIcon.tsx @@ -0,0 +1,32 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class ToggleCaretLeftIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + ) + } +} diff --git a/packages/web/components/elements/icons/ToggleCaretRightIcon.tsx b/packages/web/components/elements/icons/ToggleCaretRightIcon.tsx new file mode 100644 index 000000000..c8ba41e59 --- /dev/null +++ b/packages/web/components/elements/icons/ToggleCaretRightIcon.tsx @@ -0,0 +1,32 @@ +/* eslint-disable functional/no-class */ +/* eslint-disable functional/no-this-expression */ +import { IconProps } from './IconProps' + +import React from 'react' + +export class ToggleCaretRightIcon extends React.Component { + render() { + const size = (this.props.size || 26).toString() + const color = (this.props.color || '#2A2A2A').toString() + + return ( + + + + + + ) + } +} diff --git a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx index 79fb71724..a363f9a54 100644 --- a/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx +++ b/packages/web/components/templates/homeFeed/LibraryFilterMenu.tsx @@ -1,9 +1,8 @@ -import { ReactNode, useMemo, useState } from 'react' +import { ReactNode, useMemo } from 'react' import { StyledText } from '../../elements/StyledText' import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives' -import { Dropdown, DropdownOption } from '../../elements/DropdownElements' import { Button } from '../../elements/Button' -import { CaretRight, Circle, DotsThree, Plus } from 'phosphor-react' +import { CaretRight, Circle } from 'phosphor-react' import { useGetSubscriptionsQuery } from '../../../lib/networking/queries/useGetSubscriptionsQuery' import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery' import { Label } from '../../../lib/networking/fragments/labelFragment' @@ -11,6 +10,11 @@ import { theme } from '../../tokens/stitches.config' import { useRegisterActions } from 'kbar' import { LogoBox } from '../../elements/LogoBox' import { usePersistedState } from '../../../lib/hooks/usePersistedState' +import { ToggleCaretDownIcon } from '../../elements/icons/ToggleCaretDownIcon' +import { ToggleCaretLeftIcon } from '../../elements/icons/ToggleCaretLeftIcon' +import Link from 'next/link' +import { ArrowRightIcon } from '../../elements/icons/ArrowRightIcon' +import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon' export const LIBRARY_LEFT_MENU_WIDTH = '233px' @@ -103,6 +107,10 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element { name: 'Unlabeled', term: 'no:label', }, + { + name: 'Oldest First', + term: 'sort:saved-desc', + }, { name: 'Files', term: 'type:file', @@ -130,16 +138,26 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element { [] ) + const [collapsed, setCollapsed] = usePersistedState({ + key: `--saved-searches-collapsed`, + initialValue: false, + }) + return ( - - {items.map((item) => ( - - ))} + + {!collapsed && + items.map((item) => ( + + ))} @@ -148,8 +166,8 @@ function SavedSearches(props: LibraryFilterMenuProps): JSX.Element { function Subscriptions(props: LibraryFilterMenuProps): JSX.Element { const { subscriptions } = useGetSubscriptionsQuery() - const [viewAll, setViewAll] = usePersistedState({ - key: `--subscriptions-view-all`, + const [collapsed, setCollapsed] = usePersistedState({ + key: `--subscriptions-collapsed`, initialValue: false, }) @@ -173,15 +191,10 @@ function Subscriptions(props: LibraryFilterMenuProps): JSX.Element { return ( { - window.location.href = '/settings/subscriptions' - }} - viewAll={() => { - setViewAll(true) - }} + collapsed={collapsed} + setCollapsed={setCollapsed} > - {viewAll ? ( + {!collapsed ? ( <> ) })} - + ) : ( @@ -210,8 +226,8 @@ function Subscriptions(props: LibraryFilterMenuProps): JSX.Element { function Labels(props: LibraryFilterMenuProps): JSX.Element { const { labels } = useGetLabelsQuery() - const [viewAll, setViewAll] = usePersistedState({ - key: `--labels-view-all`, + const [collapsed, setCollapsed] = usePersistedState({ + key: `--labels-collapsed`, initialValue: false, }) @@ -224,16 +240,18 @@ function Labels(props: LibraryFilterMenuProps): JSX.Element { return ( { - window.location.href = '/settings/labels' - }} + collapsed={collapsed} + setCollapsed={setCollapsed} > - {sortedLabels.slice(0, viewAll ? undefined : 4).map((item) => { - return - })} - + {!collapsed && ( + <> + {sortedLabels.map((item) => { + return + })} + + + )} ) } @@ -241,10 +259,11 @@ function Labels(props: LibraryFilterMenuProps): JSX.Element { type MenuPanelProps = { title: string children: ReactNode - editFunc?: () => void - editTitle?: string + hideBottomBorder?: boolean - viewAll?: () => void + + collapsed: boolean + setCollapsed: (collapsed: boolean) => void } function MenuPanel(props: MenuPanelProps): JSX.Element { @@ -261,7 +280,7 @@ function MenuPanel(props: MenuPanelProps): JSX.Element { alignment="start" distribution="start" > - + - {props.editTitle && props.editFunc && ( - - - - } - > - {props.viewAll && ( - { - if (props.viewAll) { - props.viewAll() - } - }} - /> - )} - { - if (props.editFunc) { - props.editFunc() - } - }} + {props.children} @@ -492,34 +486,43 @@ function LabelButton(props: LabelButtonProps): JSX.Element { ) } -type ViewAllButtonProps = { - state: boolean - setState: (state: boolean) => void +type EditButtonProps = { + title: string + destination: string } -function ViewAllButton(props: ViewAllButtonProps): JSX.Element { +function EditButton(props: EditButtonProps): JSX.Element { return ( - + + + {props.title} + + ) }