Merge branch 'group-fixes' into 'develop'
Various Group fixes & improvements See merge request soapbox-pub/soapbox!2510
This commit is contained in:
commit
0acbbc3445
16 changed files with 117 additions and 44 deletions
|
@ -3,15 +3,24 @@ import { useEntityLookup } from 'soapbox/entity-store/hooks';
|
||||||
import { useApi } from 'soapbox/hooks/useApi';
|
import { useApi } from 'soapbox/hooks/useApi';
|
||||||
import { groupSchema } from 'soapbox/schemas';
|
import { groupSchema } from 'soapbox/schemas';
|
||||||
|
|
||||||
|
import { useGroupRelationship } from './useGroupRelationship';
|
||||||
|
|
||||||
function useGroupLookup(slug: string) {
|
function useGroupLookup(slug: string) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
return useEntityLookup(
|
const { entity: group, ...result } = useEntityLookup(
|
||||||
Entities.GROUPS,
|
Entities.GROUPS,
|
||||||
(group) => group.slug === slug,
|
(group) => group.slug === slug,
|
||||||
() => api.get(`/api/v1/groups/lookup?name=${slug}`),
|
() => api.get(`/api/v1/groups/lookup?name=${slug}`),
|
||||||
{ schema: groupSchema },
|
{ schema: groupSchema },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { entity: relationship } = useGroupRelationship(group?.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
entity: group ? { ...group, relationship: relationship || null } : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useGroupLookup };
|
export { useGroupLookup };
|
|
@ -7,14 +7,17 @@ import { useEntity } from 'soapbox/entity-store/hooks';
|
||||||
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
import { useApi, useAppDispatch } from 'soapbox/hooks';
|
||||||
import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas';
|
import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas';
|
||||||
|
|
||||||
function useGroupRelationship(groupId: string) {
|
function useGroupRelationship(groupId: string | undefined) {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { entity: groupRelationship, ...result } = useEntity<GroupRelationship>(
|
const { entity: groupRelationship, ...result } = useEntity<GroupRelationship>(
|
||||||
[Entities.GROUP_RELATIONSHIPS, groupId],
|
[Entities.GROUP_RELATIONSHIPS, groupId as string],
|
||||||
() => api.get(`/api/v1/groups/relationships?id[]=${groupId}`),
|
() => api.get(`/api/v1/groups/relationships?id[]=${groupId}`),
|
||||||
{ schema: z.array(groupRelationshipSchema).transform(arr => arr[0]) },
|
{
|
||||||
|
enabled: !!groupId,
|
||||||
|
schema: z.array(groupRelationshipSchema).transform(arr => arr[0]),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -17,11 +17,16 @@ interface IIcon extends Pick<React.SVGAttributes<SVGAElement>, 'strokeWidth'> {
|
||||||
src: string
|
src: string
|
||||||
/** Width and height of the icon in pixels. */
|
/** Width and height of the icon in pixels. */
|
||||||
size?: number
|
size?: number
|
||||||
|
/** Override the data-testid */
|
||||||
|
'data-testid'?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders and SVG icon with optional counter. */
|
/** Renders and SVG icon with optional counter. */
|
||||||
const Icon: React.FC<IIcon> = ({ src, alt, count, size, countMax, ...filteredProps }): JSX.Element => (
|
const Icon: React.FC<IIcon> = ({ src, alt, count, size, countMax, ...filteredProps }): JSX.Element => (
|
||||||
<div className='relative flex shrink-0 flex-col' data-testid='icon'>
|
<div
|
||||||
|
className='relative flex shrink-0 flex-col'
|
||||||
|
data-testid={filteredProps['data-testid'] || 'icon'}
|
||||||
|
>
|
||||||
{count ? (
|
{count ? (
|
||||||
<span className='absolute -right-3 -top-2 flex h-5 min-w-[20px] shrink-0 items-center justify-center whitespace-nowrap break-words'>
|
<span className='absolute -right-3 -top-2 flex h-5 min-w-[20px] shrink-0 items-center justify-center whitespace-nowrap break-words'>
|
||||||
<Counter count={count} countMax={countMax} />
|
<Counter count={count} countMax={countMax} />
|
||||||
|
|
|
@ -14,6 +14,8 @@ interface UseEntityOpts<TEntity extends Entity> {
|
||||||
schema?: EntitySchema<TEntity>
|
schema?: EntitySchema<TEntity>
|
||||||
/** Whether to refetch this entity every time the hook mounts, even if it's already in the store. */
|
/** Whether to refetch this entity every time the hook mounts, even if it's already in the store. */
|
||||||
refetch?: boolean
|
refetch?: boolean
|
||||||
|
/** A flag to potentially disable sending requests to the API. */
|
||||||
|
enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function useEntity<TEntity extends Entity>(
|
function useEntity<TEntity extends Entity>(
|
||||||
|
@ -31,6 +33,7 @@ function useEntity<TEntity extends Entity>(
|
||||||
|
|
||||||
const entity = useAppSelector(state => state.entities[entityType]?.store[entityId] as TEntity | undefined);
|
const entity = useAppSelector(state => state.entities[entityType]?.store[entityId] as TEntity | undefined);
|
||||||
|
|
||||||
|
const isEnabled = opts.enabled ?? true;
|
||||||
const isLoading = isFetching && !entity;
|
const isLoading = isFetching && !entity;
|
||||||
|
|
||||||
const fetchEntity = async () => {
|
const fetchEntity = async () => {
|
||||||
|
@ -44,10 +47,11 @@ function useEntity<TEntity extends Entity>(
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isEnabled) return;
|
||||||
if (!entity || opts.refetch) {
|
if (!entity || opts.refetch) {
|
||||||
fetchEntity();
|
fetchEntity();
|
||||||
}
|
}
|
||||||
}, []);
|
}, [isEnabled]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entity,
|
entity,
|
||||||
|
|
|
@ -91,31 +91,32 @@ describe('<GroupTagListItem />', () => {
|
||||||
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('as a non-owner', () => {
|
describe('as a non-owner', () => {
|
||||||
const group = buildGroup({
|
const group = buildGroup({
|
||||||
relationship: buildGroupRelationship({
|
relationship: buildGroupRelationship({
|
||||||
role: GroupRoles.ADMIN,
|
role: GroupRoles.ADMIN,
|
||||||
member: true,
|
member: true,
|
||||||
}),
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the tag is pinned', () => {
|
||||||
|
const tag = buildGroupTag({ pinned: true, visible: true });
|
||||||
|
|
||||||
|
it('does render the pin icon', () => {
|
||||||
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
|
screen.debug();
|
||||||
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when the tag is visible', () => {
|
describe('when the tag is not pinned', () => {
|
||||||
const tag = buildGroupTag({ visible: true });
|
const tag = buildGroupTag({ pinned: false, visible: true });
|
||||||
|
|
||||||
it('does not render the pin icon', () => {
|
it('does not render the pin icon', () => {
|
||||||
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
||||||
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the tag is not visible', () => {
|
|
||||||
const tag = buildGroupTag({ visible: false });
|
|
||||||
|
|
||||||
it('does not render the pin icon', () => {
|
|
||||||
render(<GroupTagListItem group={group} tag={tag} isPinnable />);
|
|
||||||
expect(screen.queryAllByTestId('pin-icon')).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -55,6 +55,12 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
|
||||||
: intl.formatMessage(messages.joinSuccess),
|
: intl.formatMessage(messages.joinSuccess),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onError(error) {
|
||||||
|
const message = (error.response?.data as any).error;
|
||||||
|
if (message) {
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const onLeaveGroup = () =>
|
const onLeaveGroup = () =>
|
||||||
|
|
|
@ -99,7 +99,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
|
|
||||||
if (!isDefaultHeader(group.header)) {
|
if (!isDefaultHeader(group.header)) {
|
||||||
header = (
|
header = (
|
||||||
<a href={group.header} onClick={handleHeaderClick} target='_blank' className='relative'>
|
<a href={group.header} onClick={handleHeaderClick} target='_blank' className='relative w-full'>
|
||||||
{header}
|
{header}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
@ -155,6 +155,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
|
||||||
theme='muted'
|
theme='muted'
|
||||||
align='center'
|
align='center'
|
||||||
dangerouslySetInnerHTML={{ __html: group.note_emojified }}
|
dangerouslySetInnerHTML={{ __html: group.note_emojified }}
|
||||||
|
className='[&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue'
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ const messages = defineMessages({
|
||||||
leave: { id: 'group.leave.label', defaultMessage: 'Leave' },
|
leave: { id: 'group.leave.label', defaultMessage: 'Leave' },
|
||||||
leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' },
|
leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' },
|
||||||
report: { id: 'group.report.label', defaultMessage: 'Report' },
|
report: { id: 'group.report.label', defaultMessage: 'Report' },
|
||||||
|
share: { id: 'group.share.label', defaultMessage: 'Share' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IGroupActionButton {
|
interface IGroupActionButton {
|
||||||
|
@ -35,6 +36,15 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
|
||||||
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
|
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
|
||||||
const isBlocked = group.relationship?.blocked_by;
|
const isBlocked = group.relationship?.blocked_by;
|
||||||
|
|
||||||
|
const handleShare = () => {
|
||||||
|
navigator.share({
|
||||||
|
text: group.display_name,
|
||||||
|
url: group.url,
|
||||||
|
}).catch((e) => {
|
||||||
|
if (e.name !== 'AbortError') console.error(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const onLeaveGroup = () =>
|
const onLeaveGroup = () =>
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
heading: intl.formatMessage(messages.confirmationHeading),
|
heading: intl.formatMessage(messages.confirmationHeading),
|
||||||
|
@ -49,6 +59,7 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const menu: Menu = useMemo(() => {
|
const menu: Menu = useMemo(() => {
|
||||||
|
const canShare = 'share' in navigator;
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
if (isMember || isAdmin) {
|
if (isMember || isAdmin) {
|
||||||
|
@ -59,6 +70,14 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canShare) {
|
||||||
|
items.push({
|
||||||
|
text: intl.formatMessage(messages.share),
|
||||||
|
icon: require('@tabler/icons/share.svg'),
|
||||||
|
action: handleShare,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
items.push({
|
items.push({
|
||||||
text: intl.formatMessage(messages.leave),
|
text: intl.formatMessage(messages.leave),
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useUpdateGroupTag } from 'soapbox/api/hooks';
|
import { useUpdateGroupTag } from 'soapbox/api/hooks';
|
||||||
import { HStack, IconButton, Stack, Text, Tooltip } from 'soapbox/components/ui';
|
import { HStack, Icon, IconButton, Stack, Text, Tooltip } from 'soapbox/components/ui';
|
||||||
import { importEntities } from 'soapbox/entity-store/actions';
|
import { importEntities } from 'soapbox/entity-store/actions';
|
||||||
import { Entities } from 'soapbox/entity-store/entities';
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
@ -84,6 +84,20 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPinIcon = () => {
|
const renderPinIcon = () => {
|
||||||
|
if (!isOwner && tag.pinned) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
src={require('@tabler/icons/pin-filled.svg')}
|
||||||
|
className='h-5 w-5 text-gray-600'
|
||||||
|
data-testid='pin-icon'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOwner) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (isPinnable) {
|
if (isPinnable) {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -149,12 +163,12 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
</Stack>
|
</Stack>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{isOwner ? (
|
<HStack alignItems='center' space={2}>
|
||||||
<HStack alignItems='center' space={2}>
|
{tag.visible ? (
|
||||||
{tag.visible ? (
|
renderPinIcon()
|
||||||
renderPinIcon()
|
) : null}
|
||||||
) : null}
|
|
||||||
|
|
||||||
|
{isOwner ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
text={
|
text={
|
||||||
tag.visible ?
|
tag.visible ?
|
||||||
|
@ -173,8 +187,8 @@ const GroupTagListItem = (props: IGroupMemberListItem) => {
|
||||||
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
iconClassName='h-5 w-5 text-primary-500 dark:text-accent-blue'
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HStack>
|
) : null}
|
||||||
) : null}
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { Input, Streamfield } from 'soapbox/components/ui';
|
import { Input, Streamfield } from 'soapbox/components/ui';
|
||||||
|
@ -36,15 +36,19 @@ const GroupTagsField: React.FC<IGroupTagsField> = ({ tags, onChange, onAddItem,
|
||||||
const HashtagField: StreamfieldComponent<string> = ({ value, onChange, autoFocus = false }) => {
|
const HashtagField: StreamfieldComponent<string> = ({ value, onChange, autoFocus = false }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const formattedValue = useMemo(() => {
|
||||||
|
return `#${value}`;
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
const handleChange: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
const handleChange: React.ChangeEventHandler<HTMLInputElement> = ({ target }) => {
|
||||||
onChange(target.value);
|
onChange(target.value.replace('#', ''));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
outerClassName='w-full'
|
outerClassName='w-full'
|
||||||
type='text'
|
type='text'
|
||||||
value={value}
|
value={formattedValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={intl.formatMessage(messages.hashtagPlaceholder)}
|
placeholder={intl.formatMessage(messages.hashtagPlaceholder)}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { useGroup, useUpdateGroup } from 'soapbox/api/hooks';
|
import { useGroup, useGroupTags, useUpdateGroup } from 'soapbox/api/hooks';
|
||||||
import { Button, Column, Form, FormActions, FormGroup, Icon, Input, Spinner, Textarea } from 'soapbox/components/ui';
|
import { Button, Column, Form, FormActions, FormGroup, Icon, Input, Spinner, Textarea } from 'soapbox/components/ui';
|
||||||
import { useAppSelector, useInstance } from 'soapbox/hooks';
|
import { useAppSelector, useInstance } from 'soapbox/hooks';
|
||||||
import { useImageField, useTextField } from 'soapbox/hooks/forms';
|
import { useImageField, useTextField } from 'soapbox/hooks/forms';
|
||||||
|
@ -36,6 +36,7 @@ const EditGroup: React.FC<IEditGroup> = ({ params: { groupId } }) => {
|
||||||
|
|
||||||
const { group, isLoading } = useGroup(groupId);
|
const { group, isLoading } = useGroup(groupId);
|
||||||
const { updateGroup } = useUpdateGroup(groupId);
|
const { updateGroup } = useUpdateGroup(groupId);
|
||||||
|
const { invalidate } = useGroupTags(groupId);
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [tags, setTags] = useState<string[]>(['']);
|
const [tags, setTags] = useState<string[]>(['']);
|
||||||
|
@ -64,6 +65,7 @@ const EditGroup: React.FC<IEditGroup> = ({ params: { groupId } }) => {
|
||||||
tags,
|
tags,
|
||||||
}, {
|
}, {
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
|
invalidate();
|
||||||
toast.success(intl.formatMessage(messages.groupSaved));
|
toast.success(intl.formatMessage(messages.groupSaved));
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ const GroupTopics: React.FC<IGroupTopics> = (props) => {
|
||||||
showLoading={!group || isLoading && tags.length === 0}
|
showLoading={!group || isLoading && tags.length === 0}
|
||||||
placeholderComponent={PlaceholderAccount}
|
placeholderComponent={PlaceholderAccount}
|
||||||
placeholderCount={3}
|
placeholderCount={3}
|
||||||
className='divide-y divide-solid divide-gray-300'
|
className='divide-y divide-solid divide-gray-300 dark:divide-gray-800'
|
||||||
itemClassName='py-3 last:pb-0'
|
itemClassName='py-3 last:pb-0'
|
||||||
emptyMessage={
|
emptyMessage={
|
||||||
<Stack space={4} className='pt-6' justifyContent='center' alignItems='center'>
|
<Stack space={4} className='pt-6' justifyContent='center' alignItems='center'>
|
||||||
|
|
|
@ -19,7 +19,7 @@ const GroupLinkPreview: React.FC<IGroupLinkPreview> = ({ card }) => {
|
||||||
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'>
|
||||||
<div
|
<div
|
||||||
className='-mb-8 h-32 w-full bg-center'
|
className='-mb-8 h-32 w-full bg-cover bg-center'
|
||||||
style={{ backgroundImage: `url(${group.header})` }}
|
style={{ backgroundImage: `url(${group.header})` }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,12 @@ import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
const ComposeButton = () => {
|
const ComposeButton = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const isOnGroupPage = location.pathname.startsWith('/group/');
|
||||||
|
const match = useRouteMatch<{ groupSlug: string }>('/group/:groupSlug');
|
||||||
|
const { entity: group } = useGroupLookup(match?.params.groupSlug || '');
|
||||||
|
const isGroupMember = !!group?.relationship?.member;
|
||||||
|
|
||||||
if (location.pathname.startsWith('/group/')) {
|
if (isOnGroupPage && isGroupMember) {
|
||||||
return <GroupComposeButton />;
|
return <GroupComposeButton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ const ConfirmationStep: React.FC<IConfirmationStep> = ({ group }) => {
|
||||||
<Text size='2xl' weight='bold' align='center'>{group.display_name}</Text>
|
<Text size='2xl' weight='bold' align='center'>{group.display_name}</Text>
|
||||||
<Text
|
<Text
|
||||||
size='md'
|
size='md'
|
||||||
className='mx-auto max-w-sm'
|
className='mx-auto max-w-sm [&_a]:text-primary-600 [&_a]:hover:underline [&_a]:dark:text-accent-blue'
|
||||||
dangerouslySetInnerHTML={{ __html: group.note_emojified }}
|
dangerouslySetInnerHTML={{ __html: group.note_emojified }}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
@ -807,6 +807,7 @@
|
||||||
"group.report.label": "Report",
|
"group.report.label": "Report",
|
||||||
"group.role.admin": "Admin",
|
"group.role.admin": "Admin",
|
||||||
"group.role.owner": "Owner",
|
"group.role.owner": "Owner",
|
||||||
|
"group.share.label": "Share",
|
||||||
"group.tabs.all": "All",
|
"group.tabs.all": "All",
|
||||||
"group.tabs.media": "Media",
|
"group.tabs.media": "Media",
|
||||||
"group.tabs.members": "Members",
|
"group.tabs.members": "Members",
|
||||||
|
|
Loading…
Reference in a new issue