Merge branch 'group-lookup' into 'develop'
Add useEntityLookup hook See merge request soapbox-pub/soapbox!2439
This commit is contained in:
commit
992e75f28e
|
@ -80,7 +80,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<div className='px-4 pb-4'>
|
<div className='px-4 pb-4'>
|
||||||
<Link to={`/groups/${group.id}`}>
|
<Link to={`/group/${group.slug}`}>
|
||||||
<Button type='button' theme='secondary' block>
|
<Button type='button' theme='secondary' block>
|
||||||
{intl.formatMessage(messages.action)}
|
{intl.formatMessage(messages.action)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import ColumnLoading from 'soapbox/features/ui/components/column-loading';
|
||||||
|
import { useGroupLookup } from 'soapbox/hooks/api/groups/useGroupLookup';
|
||||||
|
|
||||||
|
import { Layout } from '../ui';
|
||||||
|
|
||||||
|
interface IGroupLookup {
|
||||||
|
params: {
|
||||||
|
groupSlug: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMaybeGroupLookup {
|
||||||
|
params?: {
|
||||||
|
groupSlug?: string
|
||||||
|
groupId?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupLookupHoc(Component: React.ComponentType<{ params: { groupId: string } }>) {
|
||||||
|
const GroupLookup: React.FC<IGroupLookup> = (props) => {
|
||||||
|
const { entity: group } = useGroupLookup(props.params.groupSlug);
|
||||||
|
|
||||||
|
if (!group) return (
|
||||||
|
<>
|
||||||
|
<Layout.Main>
|
||||||
|
<ColumnLoading />
|
||||||
|
</Layout.Main>
|
||||||
|
|
||||||
|
<Layout.Aside />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const newProps = {
|
||||||
|
...props,
|
||||||
|
params: {
|
||||||
|
...props.params,
|
||||||
|
id: group.id,
|
||||||
|
groupId: group.id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Component {...newProps} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MaybeGroupLookup: React.FC<IMaybeGroupLookup> = (props) => {
|
||||||
|
const { params } = props;
|
||||||
|
|
||||||
|
if (params?.groupId) {
|
||||||
|
return <Component {...props} params={{ ...params, groupId: params.groupId }} />;
|
||||||
|
} else {
|
||||||
|
return <GroupLookup {...props} params={{ ...params, groupSlug: params?.groupSlug || '' }} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return MaybeGroupLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupLookupHoc;
|
|
@ -0,0 +1,11 @@
|
||||||
|
type HOC<P, R> = (Component: React.ComponentType<P>) => React.ComponentType<R>
|
||||||
|
type AsyncComponent<P> = () => Promise<{ default: React.ComponentType<P> }>
|
||||||
|
|
||||||
|
const withHoc = <P, R>(asyncComponent: AsyncComponent<P>, hoc: HOC<P, R>) => {
|
||||||
|
return async () => {
|
||||||
|
const { default: component } = await asyncComponent();
|
||||||
|
return { default: hoc(component) };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withHoc;
|
|
@ -253,7 +253,7 @@ const Status: React.FC<IStatus> = (props) => {
|
||||||
return (
|
return (
|
||||||
<StatusInfo
|
<StatusInfo
|
||||||
avatarSize={avatarSize}
|
avatarSize={avatarSize}
|
||||||
to={`/groups/${group.id}`}
|
to={`/group/${group.slug}`}
|
||||||
icon={<Icon src={require('@tabler/icons/circles.svg')} className='h-4 w-4 text-primary-600 dark:text-accent-blue' />}
|
icon={<Icon src={require('@tabler/icons/circles.svg')} className='h-4 w-4 text-primary-600 dark:text-accent-blue' />}
|
||||||
text={
|
text={
|
||||||
<Text size='xs' theme='muted' weight='medium'>
|
<Text size='xs' theme='muted' weight='medium'>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
export { useEntities } from './useEntities';
|
export { useEntities } from './useEntities';
|
||||||
export { useEntity } from './useEntity';
|
export { useEntity } from './useEntity';
|
||||||
export { useEntityActions } from './useEntityActions';
|
export { useEntityActions } from './useEntityActions';
|
||||||
|
export { useEntityLookup } from './useEntityLookup';
|
||||||
export { useCreateEntity } from './useCreateEntity';
|
export { useCreateEntity } from './useCreateEntity';
|
||||||
export { useDeleteEntity } from './useDeleteEntity';
|
export { useDeleteEntity } from './useDeleteEntity';
|
||||||
export { useDismissEntity } from './useDismissEntity';
|
export { useDismissEntity } from './useDismissEntity';
|
||||||
|
|
|
@ -59,4 +59,5 @@ function useEntity<TEntity extends Entity>(
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useEntity,
|
useEntity,
|
||||||
|
type UseEntityOpts,
|
||||||
};
|
};
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks';
|
||||||
|
import { type RootState } from 'soapbox/store';
|
||||||
|
|
||||||
|
import { importEntities } from '../actions';
|
||||||
|
import { Entity } from '../types';
|
||||||
|
|
||||||
|
import { EntityFn } from './types';
|
||||||
|
import { type UseEntityOpts } from './useEntity';
|
||||||
|
|
||||||
|
/** Entities will be filtered through this function until it returns true. */
|
||||||
|
type LookupFn<TEntity extends Entity> = (entity: TEntity) => boolean
|
||||||
|
|
||||||
|
function useEntityLookup<TEntity extends Entity>(
|
||||||
|
entityType: string,
|
||||||
|
lookupFn: LookupFn<TEntity>,
|
||||||
|
entityFn: EntityFn<void>,
|
||||||
|
opts: UseEntityOpts<TEntity> = {},
|
||||||
|
) {
|
||||||
|
const { schema = z.custom<TEntity>() } = opts;
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [isFetching, setPromise] = useLoading(true);
|
||||||
|
|
||||||
|
const entity = useAppSelector(state => findEntity(state, entityType, lookupFn));
|
||||||
|
const isLoading = isFetching && !entity;
|
||||||
|
|
||||||
|
const fetchEntity = async () => {
|
||||||
|
try {
|
||||||
|
const response = await setPromise(entityFn());
|
||||||
|
const entity = schema.parse(response.data);
|
||||||
|
dispatch(importEntities([entity], entityType));
|
||||||
|
} catch (e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!entity || opts.refetch) {
|
||||||
|
fetchEntity();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
entity,
|
||||||
|
fetchEntity,
|
||||||
|
isFetching,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findEntity<TEntity extends Entity>(
|
||||||
|
state: RootState,
|
||||||
|
entityType: string,
|
||||||
|
lookupFn: LookupFn<TEntity>,
|
||||||
|
) {
|
||||||
|
const cache = state.entities[entityType];
|
||||||
|
|
||||||
|
if (cache) {
|
||||||
|
return (Object.values(cache.store) as TEntity[]).find(lookupFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useEntityLookup };
|
|
@ -82,7 +82,7 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
theme='secondary'
|
theme='secondary'
|
||||||
to={`/groups/${group.id}/manage`}
|
to={`/group/${group.slug}/manage`}
|
||||||
>
|
>
|
||||||
<FormattedMessage id='group.manage' defaultMessage='Manage Group' />
|
<FormattedMessage id='group.manage' defaultMessage='Manage Group' />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -26,11 +26,11 @@ const messages = defineMessages({
|
||||||
|
|
||||||
interface IEditGroup {
|
interface IEditGroup {
|
||||||
params: {
|
params: {
|
||||||
id: string
|
groupId: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditGroup: React.FC<IEditGroup> = ({ params: { id: groupId } }) => {
|
const EditGroup: React.FC<IEditGroup> = ({ params: { groupId } }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import toast from 'soapbox/toast';
|
||||||
|
|
||||||
import ColumnForbidden from '../ui/components/column-forbidden';
|
import ColumnForbidden from '../ui/components/column-forbidden';
|
||||||
|
|
||||||
type RouteParams = { id: string };
|
type RouteParams = { groupId: string };
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.group_blocked_members', defaultMessage: 'Banned Members' },
|
heading: { id: 'column.group_blocked_members', defaultMessage: 'Banned Members' },
|
||||||
|
@ -62,7 +62,7 @@ const GroupBlockedMembers: React.FC<IGroupBlockedMembers> = ({ params }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const id = params?.id;
|
const id = params?.groupId;
|
||||||
|
|
||||||
const { group } = useGroup(id);
|
const { group } = useGroup(id);
|
||||||
const accountIds = useAppSelector((state) => state.user_lists.group_blocks.get(id)?.items);
|
const accountIds = useAppSelector((state) => state.user_lists.group_blocks.get(id)?.items);
|
||||||
|
@ -86,7 +86,7 @@ const GroupBlockedMembers: React.FC<IGroupBlockedMembers> = ({ params }) => {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.group_blocks' defaultMessage="The group hasn't banned any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.group_blocks' defaultMessage="The group hasn't banned any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${id}/manage`}>
|
<Column label={intl.formatMessage(messages.heading)} backHref={`/group/${group.slug}/manage`}>
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='group_blocks'
|
scrollKey='group_blocks'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { Attachment, Status } from 'soapbox/types/entities';
|
||||||
const GroupGallery = () => {
|
const GroupGallery = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { id: groupId } = useParams<{ id: string }>();
|
const { groupId } = useParams<{ groupId: string }>();
|
||||||
|
|
||||||
const { group, isLoading: groupIsLoading } = useGroup(groupId);
|
const { group, isLoading: groupIsLoading } = useGroup(groupId);
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,13 @@ import GroupMemberListItem from './components/group-member-list-item';
|
||||||
import type { Group } from 'soapbox/types/entities';
|
import type { Group } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IGroupMembers {
|
interface IGroupMembers {
|
||||||
params: { id: string }
|
params: { groupId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_ADMIN_COUNT = 5;
|
export const MAX_ADMIN_COUNT = 5;
|
||||||
|
|
||||||
const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
||||||
const groupId = props.params.id;
|
const { groupId } = props.params;
|
||||||
|
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
|
|
||||||
|
@ -58,7 +58,10 @@ const GroupMembers: React.FC<IGroupMembers> = (props) => {
|
||||||
itemClassName='py-3 last:pb-0'
|
itemClassName='py-3 last:pb-0'
|
||||||
prepend={(pendingCount > 0) && (
|
prepend={(pendingCount > 0) && (
|
||||||
<div className={clsx('py-3', { 'border-b border-gray-200 dark:border-gray-800': members.length })}>
|
<div className={clsx('py-3', { 'border-b border-gray-200 dark:border-gray-800': members.length })}>
|
||||||
<PendingItemsRow to={`/groups/${groupId}/manage/requests`} count={pendingCount} />
|
<PendingItemsRow
|
||||||
|
to={`/group/${group?.slug}/manage/requests`}
|
||||||
|
count={pendingCount}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import ColumnForbidden from '../ui/components/column-forbidden';
|
||||||
|
|
||||||
import type { Account as AccountEntity } from 'soapbox/schemas';
|
import type { Account as AccountEntity } from 'soapbox/schemas';
|
||||||
|
|
||||||
type RouteParams = { id: string };
|
type RouteParams = { groupId: string };
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' },
|
heading: { id: 'column.group_pending_requests', defaultMessage: 'Pending requests' },
|
||||||
|
@ -54,7 +54,7 @@ interface IGroupMembershipRequests {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupMembershipRequests: React.FC<IGroupMembershipRequests> = ({ params }) => {
|
const GroupMembershipRequests: React.FC<IGroupMembershipRequests> = ({ params }) => {
|
||||||
const id = params?.id;
|
const id = params?.groupId;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { group } = useGroup(id);
|
const { group } = useGroup(id);
|
||||||
|
|
|
@ -13,11 +13,11 @@ import GroupTagListItem from './components/group-tag-list-item';
|
||||||
import type { Group } from 'soapbox/types/entities';
|
import type { Group } from 'soapbox/types/entities';
|
||||||
|
|
||||||
interface IGroupTopics {
|
interface IGroupTopics {
|
||||||
params: { id: string }
|
params: { groupId: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
const GroupTopics: React.FC<IGroupTopics> = (props) => {
|
const GroupTopics: React.FC<IGroupTopics> = (props) => {
|
||||||
const groupId = props.params.id;
|
const { groupId } = props.params;
|
||||||
|
|
||||||
const { group, isFetching: isFetchingGroup } = useGroup(groupId);
|
const { group, isFetching: isFetchingGroup } = useGroup(groupId);
|
||||||
const { tags, isFetching: isFetchingTags, hasNextPage, fetchNextPage } = useGroupTags(groupId);
|
const { tags, isFetching: isFetchingTags, hasNextPage, fetchNextPage } = useGroupTags(groupId);
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { useGroup } from 'soapbox/hooks/api';
|
||||||
|
|
||||||
import Timeline from '../ui/components/timeline';
|
import Timeline from '../ui/components/timeline';
|
||||||
|
|
||||||
type RouteParams = { id: string };
|
type RouteParams = { groupId: string };
|
||||||
|
|
||||||
interface IGroupTimeline {
|
interface IGroupTimeline {
|
||||||
params: RouteParams
|
params: RouteParams
|
||||||
|
@ -22,7 +22,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const groupId = props.params.id;
|
const { groupId } = props.params;
|
||||||
|
|
||||||
const { group } = useGroup(groupId);
|
const { group } = useGroup(groupId);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { TRUTHSOCIAL } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import ColumnForbidden from '../ui/components/column-forbidden';
|
import ColumnForbidden from '../ui/components/column-forbidden';
|
||||||
|
|
||||||
type RouteParams = { id: string };
|
type RouteParams = { groupId: string };
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.manage_group', defaultMessage: 'Manage group' },
|
heading: { id: 'column.manage_group', defaultMessage: 'Manage group' },
|
||||||
|
@ -34,7 +34,7 @@ interface IManageGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||||
const { id } = params;
|
const { groupId: id } = params;
|
||||||
|
|
||||||
const backend = useBackend();
|
const backend = useBackend();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -76,12 +76,12 @@ const ManageGroup: React.FC<IManageGroup> = ({ params }) => {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const navigateToEdit = () => history.push(`/groups/${id}/manage/edit`);
|
const navigateToEdit = () => history.push(`/group/${group.slug}/manage/edit`);
|
||||||
const navigateToPending = () => history.push(`/groups/${id}/manage/requests`);
|
const navigateToPending = () => history.push(`/group/${group.slug}/manage/requests`);
|
||||||
const navigateToBlocks = () => history.push(`/groups/${id}/manage/blocks`);
|
const navigateToBlocks = () => history.push(`/group/${group.slug}/manage/blocks`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.heading)} backHref={`/groups/${id}`}>
|
<Column label={intl.formatMessage(messages.heading)} backHref={`/group/${group.slug}`}>
|
||||||
<CardBody className='space-y-4'>
|
<CardBody className='space-y-4'>
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -25,7 +25,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link to={`/groups/${group.id}`}>
|
<Link to={`/group/${group.slug}`}>
|
||||||
<Stack
|
<Stack
|
||||||
className='aspect-h-7 aspect-w-10 h-full w-full overflow-hidden rounded-lg'
|
className='aspect-h-7 aspect-w-10 h-full w-full overflow-hidden rounded-lg'
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|
|
@ -21,7 +21,7 @@ const GroupListItem = (props: IGroup) => {
|
||||||
alignItems='center'
|
alignItems='center'
|
||||||
justifyContent='between'
|
justifyContent='between'
|
||||||
>
|
>
|
||||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
<Link key={group.id} to={`/group/${group.slug}`}>
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2}>
|
||||||
<GroupAvatar
|
<GroupAvatar
|
||||||
group={group}
|
group={group}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
||||||
const { group } = card;
|
const { group } = card;
|
||||||
if (!group) return null;
|
if (!group) return null;
|
||||||
|
|
||||||
const navigateToGroup = () => history.push(`/groups/${group.id}`);
|
const navigateToGroup = () => history.push(`/group/${group.slug}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className='cursor-default overflow-hidden rounded-lg border border-gray-300 text-center dark:border-gray-800'>
|
<Stack className='cursor-default overflow-hidden rounded-lg border border-gray-300 text-center dark:border-gray-800'>
|
||||||
|
|
|
@ -106,7 +106,7 @@ const Groups: React.FC = () => {
|
||||||
placeholderCount={3}
|
placeholderCount={3}
|
||||||
>
|
>
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
<Link key={group.id} to={`/group/${group.slug}`}>
|
||||||
<GroupCard group={group} />
|
<GroupCard group={group} />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default () => {
|
||||||
showLoading={isLoading && groups.length === 0}
|
showLoading={isLoading && groups.length === 0}
|
||||||
>
|
>
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<Link key={group.id} to={`/groups/${group.id}`}>
|
<Link key={group.id} to={`/group/${group.slug}`}>
|
||||||
<GroupCard group={group} />
|
<GroupCard group={group} />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -118,6 +118,7 @@ type DisplayMedia = 'default' | 'hide_all' | 'show_all';
|
||||||
type RouteParams = {
|
type RouteParams = {
|
||||||
statusId: string
|
statusId: string
|
||||||
groupId?: string
|
groupId?: string
|
||||||
|
groupSlug?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IThread {
|
interface IThread {
|
||||||
|
@ -516,8 +517,10 @@ const Thread: React.FC<IThread> = (props) => {
|
||||||
children.push(...renderChildren(descendantsIds).toArray());
|
children.push(...renderChildren(descendantsIds).toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.group && typeof status.group === 'object' && !props.params.groupId) {
|
if (status.group && typeof status.group === 'object') {
|
||||||
return <Redirect to={`/groups/${status.group.id}/posts/${props.params.statusId}`} />;
|
if (status.group.slug && !props.params.groupSlug) {
|
||||||
|
return <Redirect to={`/group/${status.group.slug}/posts/${props.params.statusId}`} />;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleMessage = () => {
|
const titleMessage = () => {
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses';
|
||||||
import { connectUserStream } from 'soapbox/actions/streaming';
|
import { connectUserStream } from 'soapbox/actions/streaming';
|
||||||
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
||||||
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||||
|
import GroupLookupHoc from 'soapbox/components/hoc/group-lookup-hoc';
|
||||||
|
import withHoc from 'soapbox/components/hoc/with-hoc';
|
||||||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||||
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
||||||
import { Layout } from 'soapbox/components/ui';
|
import { Layout } from 'soapbox/components/ui';
|
||||||
|
@ -141,6 +143,16 @@ import { WrappedRoute } from './util/react-router-helpers';
|
||||||
// Without this it ends up in ~8 very commonly used bundles.
|
// Without this it ends up in ~8 very commonly used bundles.
|
||||||
import 'soapbox/components/status';
|
import 'soapbox/components/status';
|
||||||
|
|
||||||
|
const GroupTagsSlug = withHoc(GroupTags as any, GroupLookupHoc);
|
||||||
|
const GroupTagTimelineSlug = withHoc(GroupTagTimeline as any, GroupLookupHoc);
|
||||||
|
const GroupTimelineSlug = withHoc(GroupTimeline as any, GroupLookupHoc);
|
||||||
|
const GroupMembersSlug = withHoc(GroupMembers as any, GroupLookupHoc);
|
||||||
|
const GroupGallerySlug = withHoc(GroupGallery as any, GroupLookupHoc);
|
||||||
|
const ManageGroupSlug = withHoc(ManageGroup as any, GroupLookupHoc);
|
||||||
|
const EditGroupSlug = withHoc(EditGroup as any, GroupLookupHoc);
|
||||||
|
const GroupBlockedMembersSlug = withHoc(GroupBlockedMembers as any, GroupLookupHoc);
|
||||||
|
const GroupMembershipRequestsSlug = withHoc(GroupMembershipRequests as any, GroupLookupHoc);
|
||||||
|
|
||||||
const EmptyPage = HomePage;
|
const EmptyPage = HomePage;
|
||||||
|
|
||||||
const keyMap = {
|
const keyMap = {
|
||||||
|
@ -303,17 +315,28 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
{features.groupsDiscovery && <WrappedRoute path='/groups/tags' exact page={GroupsPendingPage} component={GroupsTags} content={children} />}
|
{features.groupsDiscovery && <WrappedRoute path='/groups/tags' exact page={GroupsPendingPage} component={GroupsTags} content={children} />}
|
||||||
{features.groupsDiscovery && <WrappedRoute path='/groups/discover/tags/:id' exact page={GroupsPendingPage} component={GroupsTag} content={children} />}
|
{features.groupsDiscovery && <WrappedRoute path='/groups/discover/tags/:id' exact page={GroupsPendingPage} component={GroupsTag} content={children} />}
|
||||||
{features.groupsPending && <WrappedRoute path='/groups/pending-requests' exact page={GroupsPendingPage} component={PendingGroupRequests} content={children} />}
|
{features.groupsPending && <WrappedRoute path='/groups/pending-requests' exact page={GroupsPendingPage} component={PendingGroupRequests} content={children} />}
|
||||||
{features.groupsTags && <WrappedRoute path='/groups/:id/tags' exact page={GroupPage} component={GroupTags} content={children} />}
|
{features.groupsTags && <WrappedRoute path='/groups/:groupId/tags' exact page={GroupPage} component={GroupTags} content={children} />}
|
||||||
{features.groupsTags && <WrappedRoute path='/groups/:groupId/tag/:id' exact page={GroupsPendingPage} component={GroupTagTimeline} content={children} />}
|
{features.groupsTags && <WrappedRoute path='/groups/:groupId/tag/:id' exact page={GroupsPendingPage} component={GroupTagTimeline} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id' exact page={GroupPage} component={GroupTimeline} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId' exact page={GroupPage} component={GroupTimeline} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/members' exact page={GroupPage} component={GroupMembers} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/members' exact page={GroupPage} component={GroupMembers} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/media' publicRoute={!authenticatedProfile} component={GroupGallery} page={GroupPage} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/media' publicRoute={!authenticatedProfile} component={GroupGallery} page={GroupPage} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage' exact page={ManageGroupsPage} component={ManageGroup} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/manage' exact page={ManageGroupsPage} component={ManageGroup} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage/edit' exact page={ManageGroupsPage} component={EditGroup} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/manage/edit' exact page={ManageGroupsPage} component={EditGroup} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage/blocks' exact page={ManageGroupsPage} component={GroupBlockedMembers} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/manage/blocks' exact page={ManageGroupsPage} component={GroupBlockedMembers} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage/requests' exact page={ManageGroupsPage} component={GroupMembershipRequests} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/manage/requests' exact page={ManageGroupsPage} component={GroupMembershipRequests} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:groupId/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
|
||||||
|
|
||||||
|
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tags' exact page={GroupPage} component={GroupTagsSlug} content={children} />}
|
||||||
|
{features.groupsTags && <WrappedRoute path='/group/:groupSlug/tag/:id' exact page={GroupsPendingPage} component={GroupTagTimelineSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug' exact page={GroupPage} component={GroupTimelineSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/members' exact page={GroupPage} component={GroupMembersSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/media' publicRoute={!authenticatedProfile} component={GroupGallerySlug} page={GroupPage} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/manage' exact page={ManageGroupsPage} component={ManageGroupSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/manage/edit' exact page={ManageGroupsPage} component={EditGroupSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/manage/blocks' exact page={ManageGroupsPage} component={GroupBlockedMembersSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/manage/requests' exact page={ManageGroupsPage} component={GroupMembershipRequestsSlug} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/group/:groupSlug/posts/:statusId' exact page={StatusPage} component={Status} content={children} />}
|
||||||
|
|
||||||
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
|
<WrappedRoute path='/statuses/new' page={DefaultPage} component={NewStatus} content={children} exact />
|
||||||
<WrappedRoute path='/statuses/:statusId' exact page={StatusPage} component={Status} content={children} />
|
<WrappedRoute path='/statuses/:statusId' exact page={StatusPage} component={Status} content={children} />
|
||||||
{features.scheduledStatuses && <WrappedRoute path='/scheduled_statuses' page={DefaultPage} component={ScheduledStatuses} content={children} />}
|
{features.scheduledStatuses && <WrappedRoute path='/scheduled_statuses' page={DefaultPage} component={ScheduledStatuses} content={children} />}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
|
import { useEntityLookup } from 'soapbox/entity-store/hooks';
|
||||||
|
import { useApi } from 'soapbox/hooks/useApi';
|
||||||
|
import { groupSchema } from 'soapbox/schemas';
|
||||||
|
|
||||||
|
function useGroupLookup(slug: string) {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
return useEntityLookup(
|
||||||
|
Entities.GROUPS,
|
||||||
|
(group) => group.slug === slug,
|
||||||
|
() => api.get(`/api/v1/groups/lookup?name=${slug}`),
|
||||||
|
{ schema: groupSchema },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useGroupLookup };
|
|
@ -33,6 +33,7 @@ export const GroupRecord = ImmutableRecord({
|
||||||
members_count: 0,
|
members_count: 0,
|
||||||
note: '',
|
note: '',
|
||||||
statuses_visibility: 'public',
|
statuses_visibility: 'public',
|
||||||
|
slug: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
uri: '',
|
uri: '',
|
||||||
url: '',
|
url: '',
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { useMemo } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
import { useRouteMatch } from 'react-router-dom';
|
||||||
|
|
||||||
|
import GroupLookupHoc from 'soapbox/components/hoc/group-lookup-hoc';
|
||||||
import { Column, Icon, Layout, Stack, Text } from 'soapbox/components/ui';
|
import { Column, Icon, Layout, Stack, Text } from 'soapbox/components/ui';
|
||||||
import GroupHeader from 'soapbox/features/group/components/group-header';
|
import GroupHeader from 'soapbox/features/group/components/group-header';
|
||||||
import LinkFooter from 'soapbox/features/ui/components/link-footer';
|
import LinkFooter from 'soapbox/features/ui/components/link-footer';
|
||||||
|
@ -28,7 +29,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
interface IGroupPage {
|
interface IGroupPage {
|
||||||
params?: {
|
params?: {
|
||||||
id?: string
|
groupId?: string
|
||||||
}
|
}
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}
|
}
|
||||||
|
@ -66,7 +67,7 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
const me = useOwnAccount();
|
const me = useOwnAccount();
|
||||||
|
|
||||||
const id = params?.id || '';
|
const id = params?.groupId || '';
|
||||||
|
|
||||||
const { group } = useGroup(id);
|
const { group } = useGroup(id);
|
||||||
const { accounts: pending } = useGroupMembershipRequests(id);
|
const { accounts: pending } = useGroupMembershipRequests(id);
|
||||||
|
@ -85,28 +86,28 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
||||||
const items = [];
|
const items = [];
|
||||||
items.push({
|
items.push({
|
||||||
text: intl.formatMessage(messages.all),
|
text: intl.formatMessage(messages.all),
|
||||||
to: `/groups/${group?.id}`,
|
to: `/group/${group?.slug}`,
|
||||||
name: '/groups/:id',
|
name: '/group/:groupSlug',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (features.groupsTags) {
|
if (features.groupsTags) {
|
||||||
items.push({
|
items.push({
|
||||||
text: intl.formatMessage(messages.tags),
|
text: intl.formatMessage(messages.tags),
|
||||||
to: `/groups/${group?.id}/tags`,
|
to: `/group/${group?.slug}/tags`,
|
||||||
name: '/groups/:id/tags',
|
name: '/group/:groupSlug/tags',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
text: intl.formatMessage(messages.members),
|
text: intl.formatMessage(messages.members),
|
||||||
to: `/groups/${group?.id}/members`,
|
to: `/group/${group?.slug}/members`,
|
||||||
name: '/groups/:id/members',
|
name: '/group/:groupSlug/members',
|
||||||
count: pending.length,
|
count: pending.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(messages.media),
|
text: intl.formatMessage(messages.media),
|
||||||
to: `/groups/${group?.id}/media`,
|
to: `/group/${group?.slug}/media`,
|
||||||
name: '/groups/:id/media',
|
name: '/group/:groupSlug/media',
|
||||||
});
|
});
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
|
@ -161,4 +162,4 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GroupPage;
|
export default GroupLookupHoc(GroupPage as any) as any;
|
||||||
|
|
|
@ -28,6 +28,7 @@ const groupSchema = z.object({
|
||||||
members_count: z.number().catch(0),
|
members_count: z.number().catch(0),
|
||||||
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
|
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
|
||||||
relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
|
relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
|
||||||
|
slug: z.string().catch(''), // TruthSocial
|
||||||
statuses_visibility: z.string().catch('public'),
|
statuses_visibility: z.string().catch('public'),
|
||||||
tags: z.array(groupTagSchema).catch([]),
|
tags: z.array(groupTagSchema).catch([]),
|
||||||
uri: z.string().catch(''),
|
uri: z.string().catch(''),
|
||||||
|
|
Loading…
Reference in New Issue