From bb70c1ea3c6b36d100e442149a2853fc35112a64 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 13 Apr 2023 09:28:40 -0400 Subject: [PATCH] Add topic pages --- app/soapbox/features/groups/tag.tsx | 117 ++++++++++++++++++ app/soapbox/features/groups/tags.tsx | 104 ++++------------ app/soapbox/features/ui/index.tsx | 4 +- .../features/ui/util/async-components.ts | 4 + app/soapbox/hooks/api/groups/useGroupTag.ts | 21 ++++ .../hooks/api/groups/useGroupsFromTag.ts | 12 +- .../hooks/api/groups/usePopularTags.ts | 2 +- app/soapbox/hooks/api/index.ts | 1 + app/soapbox/locales/en.json | 5 + 9 files changed, 189 insertions(+), 81 deletions(-) create mode 100644 app/soapbox/features/groups/tag.tsx create mode 100644 app/soapbox/hooks/api/groups/useGroupTag.ts diff --git a/app/soapbox/features/groups/tag.tsx b/app/soapbox/features/groups/tag.tsx new file mode 100644 index 000000000..4774a4700 --- /dev/null +++ b/app/soapbox/features/groups/tag.tsx @@ -0,0 +1,117 @@ +import clsx from 'clsx'; +import React, { useCallback, useState } from 'react'; +import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; + +import { Column, HStack, Icon } from 'soapbox/components/ui'; +import { useGroupTag, useGroupsFromTag } from 'soapbox/hooks/api'; + +import GroupGridItem from './components/discover/group-grid-item'; +import GroupListItem from './components/discover/group-list-item'; + +import type { Group } from 'soapbox/schemas'; + +enum Layout { + LIST = 'LIST', + GRID = 'GRID' +} + +const GridList: Components['List'] = React.forwardRef((props, ref) => { + const { context, ...rest } = props; + return
; +}); + +interface ITag { + params: { id: string } +} + +const Tag: React.FC = (props) => { + const tagId = props.params.id; + + const [layout, setLayout] = useState(Layout.LIST); + + const { tag, isLoading } = useGroupTag(tagId); + const { groups, hasNextPage, fetchNextPage } = useGroupsFromTag(tagId); + + const handleLoadMore = () => { + if (hasNextPage) { + fetchNextPage(); + } + }; + + const renderGroupList = useCallback((group: Group, index: number) => ( +
+ +
+ ), []); + + const renderGroupGrid = useCallback((group: Group, index: number) => ( +
+ +
+ ), []); + + if (isLoading || !tag) { + return null; + } + + return ( + + + + + + } + > + {layout === Layout.LIST ? ( + renderGroupList(group, index)} + endReached={handleLoadMore} + /> + ) : ( + renderGroupGrid(group, index)} + components={{ + Item: (props) => ( +
+ ), + List: GridList, + }} + endReached={handleLoadMore} + /> + )} + + ); +}; + +export default Tag; diff --git a/app/soapbox/features/groups/tags.tsx b/app/soapbox/features/groups/tags.tsx index 052f5a6e3..1484665e4 100644 --- a/app/soapbox/features/groups/tags.tsx +++ b/app/soapbox/features/groups/tags.tsx @@ -1,35 +1,24 @@ import clsx from 'clsx'; -import React, { useCallback, useState } from 'react'; -import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; +import React from 'react'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; +import { Virtuoso } from 'react-virtuoso'; -import { Column, HStack, Icon } from 'soapbox/components/ui'; -import { useGroupsFromTag } from 'soapbox/hooks/api'; +import { Column, Text } from 'soapbox/components/ui'; +import { usePopularTags } from 'soapbox/hooks/api'; -import GroupGridItem from './components/discover/group-grid-item'; -import GroupListItem from './components/discover/group-list-item'; +import TagListItem from './components/discover/tag-list-item'; -import type { Group } from 'soapbox/schemas'; +import type { GroupTag } from 'soapbox/schemas'; -enum Layout { - LIST = 'LIST', - GRID = 'GRID' -} - -const GridList: Components['List'] = React.forwardRef((props, ref) => { - const { context, ...rest } = props; - return
; +const messages = defineMessages({ + title: { id: 'groups.tags.title', defaultMessage: 'Browse Topics' }, }); -interface ITags { - params: { id: string } -} +const Tags: React.FC = () => { + const intl = useIntl(); -const Tags: React.FC = (props) => { - const tagId = props.params.id; - - const [layout, setLayout] = useState(Layout.LIST); - - const { groups, hasNextPage, fetchNextPage } = useGroupsFromTag(tagId); + const { tags, isFetched, isError, hasNextPage, fetchNextPage } = usePopularTags(); + const isEmpty = (isFetched && tags.length === 0) || isError; const handleLoadMore = () => { if (hasNextPage) { @@ -37,7 +26,7 @@ const Tags: React.FC = (props) => { } }; - const renderGroupList = useCallback((group: Group, index: number) => ( + const renderItem = (index: number, tag: GroupTag) => (
= (props) => { }) } > - +
- ), []); - - const renderGroupGrid = useCallback((group: Group, index: number) => ( -
- -
- ), []); + ); return ( - - - - - - } - > - {layout === Layout.LIST ? ( + + {isEmpty ? ( + + + + ) : ( renderGroupList(group, index)} - endReached={handleLoadMore} - /> - ) : ( - renderGroupGrid(group, index)} - components={{ - Item: (props) => ( -
- ), - List: GridList, - }} + data={tags} + itemContent={renderItem} endReached={handleLoadMore} /> )} diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 15cdc8033..b967f27de 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -122,6 +122,7 @@ import { GroupsDiscover, GroupsPopular, GroupsSuggested, + GroupsTag, GroupsTags, PendingGroupRequests, GroupMembers, @@ -297,7 +298,8 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {features.groupsDiscovery && } {features.groupsDiscovery && } {features.groupsDiscovery && } - {features.groupsDiscovery && } + {features.groupsDiscovery && } + {features.groupsDiscovery && } {features.groupsPending && } {features.groups && } {features.groups && } diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 01e44ca0a..654cab76d 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -562,6 +562,10 @@ export function GroupsSuggested() { return import(/* webpackChunkName: "features/groups" */'../../groups/suggested'); } +export function GroupsTag() { + return import(/* webpackChunkName: "features/groups" */'../../groups/tag'); +} + export function GroupsTags() { return import(/* webpackChunkName: "features/groups" */'../../groups/tags'); } diff --git a/app/soapbox/hooks/api/groups/useGroupTag.ts b/app/soapbox/hooks/api/groups/useGroupTag.ts new file mode 100644 index 000000000..d0e63d74d --- /dev/null +++ b/app/soapbox/hooks/api/groups/useGroupTag.ts @@ -0,0 +1,21 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useEntity } from 'soapbox/entity-store/hooks'; +import { useApi } from 'soapbox/hooks'; +import { type GroupTag, groupTagSchema } from 'soapbox/schemas'; + +function useGroupTag(tagId: string) { + const api = useApi(); + + const { entity: tag, ...result } = useEntity( + [Entities.GROUP_TAGS, tagId], + () => api.get(`/api/v1/tags/${tagId }`), + { schema: groupTagSchema }, + ); + + return { + ...result, + tag, + }; +} + +export { useGroupTag }; \ No newline at end of file diff --git a/app/soapbox/hooks/api/groups/useGroupsFromTag.ts b/app/soapbox/hooks/api/groups/useGroupsFromTag.ts index 7ebf871f4..a6b8540dc 100644 --- a/app/soapbox/hooks/api/groups/useGroupsFromTag.ts +++ b/app/soapbox/hooks/api/groups/useGroupsFromTag.ts @@ -5,6 +5,8 @@ import { groupSchema } from 'soapbox/schemas'; import { useApi } from '../../useApi'; import { useFeatures } from '../../useFeatures'; +import { useGroupRelationships } from './useGroups'; + import type { Group } from 'soapbox/schemas'; function useGroupsFromTag(tagId: string) { @@ -13,16 +15,22 @@ function useGroupsFromTag(tagId: string) { const { entities, ...result } = useEntities( [Entities.GROUPS, 'tags', tagId], - () => api.get(`/api/mock/tags/${tagId}/groups`), + () => api.get(`/api/v1/tags/${tagId}/groups`), { schema: groupSchema, enabled: features.groupsDiscovery, }, ); + const { relationships } = useGroupRelationships(entities.map(entity => entity.id)); + + const groups = entities.map((group) => ({ + ...group, + relationship: relationships[group.id] || null, + })); return { ...result, - groups: entities, + groups, }; } diff --git a/app/soapbox/hooks/api/groups/usePopularTags.ts b/app/soapbox/hooks/api/groups/usePopularTags.ts index d6fe5e0af..0bd272a2d 100644 --- a/app/soapbox/hooks/api/groups/usePopularTags.ts +++ b/app/soapbox/hooks/api/groups/usePopularTags.ts @@ -11,7 +11,7 @@ function usePopularTags() { const { entities, ...result } = useEntities( [Entities.GROUP_TAGS], - () => api.get('/api/mock/groups/tags'), + () => api.get('/api/v1/groups/tags'), { schema: groupTagSchema, enabled: features.groupsDiscovery, diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/hooks/api/index.ts index f28c6a5a5..800ea5857 100644 --- a/app/soapbox/hooks/api/index.ts +++ b/app/soapbox/hooks/api/index.ts @@ -15,6 +15,7 @@ export { useGroupMedia } from './groups/useGroupMedia'; export { useGroup, useGroups } from './groups/useGroups'; export { useGroupMembershipRequests } from './groups/useGroupMembershipRequests'; export { useGroupSearch } from './groups/useGroupSearch'; +export { useGroupTag } from './groups/useGroupTag'; export { useGroupValidation } from './groups/useGroupValidation'; export { useGroupsFromTag } from './groups/useGroupsFromTag'; export { useJoinGroup } from './groups/useJoinGroup'; diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 89608c4d5..2d0c10823 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -830,6 +830,10 @@ "groups.discover.suggested.empty": "Unable to fetch suggested groups at this time. Please check back later.", "groups.discover.suggested.show_more": "Show More", "groups.discover.suggested.title": "Suggested For You", + "groups.discover.tags.empty": "Unable to fetch popular topics at this time. Please check back later.", + "groups.discover.tags.show_more": "Show More", + "groups.discover.tags.title": "Browse Topics", + "groups.discovery.tags.no_of_groups": "Number of groups", "groups.empty.subtitle": "Start discovering groups to join or create your own.", "groups.empty.title": "No Groups yet", "groups.pending.count": "{number, plural, one {# pending request} other {# pending requests}}", @@ -838,6 +842,7 @@ "groups.pending.label": "Pending Requests", "groups.popular.label": "Suggested Groups", "groups.search.placeholder": "Search My Groups", + "groups.tags.title": "Browse Topics", "hashtag.column_header.tag_mode.all": "and {additional}", "hashtag.column_header.tag_mode.any": "or {additional}", "hashtag.column_header.tag_mode.none": "without {additional}",