nicolium: groups migrations

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-22 20:08:16 +01:00
parent be44696640
commit fbbcbdce3f
34 changed files with 446 additions and 499 deletions

View File

@ -2,16 +2,15 @@ import { GroupRoles } from 'pl-api';
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useJoinGroup } from '@/api/hooks/groups/use-join-group';
import { useLeaveGroup } from '@/api/hooks/groups/use-leave-group';
import Button from '@/components/ui/button';
import { importEntities } from '@/entity-store/actions';
import { Entities } from '@/entity-store/entities';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import {
useJoinGroupMutation,
useLeaveGroupMutation,
} from '@/queries/groups/use-group-relationship';
import { useModalsActions } from '@/stores/modals';
import toast from '@/toast';
import type { Group, GroupRelationship } from 'pl-api';
import type { Group } from 'pl-api';
interface IGroupActionButton {
group: Pick<Group, 'id' | 'locked' | 'relationship'>;
@ -33,12 +32,11 @@ const messages = defineMessages({
});
const GroupActionButton = ({ group }: IGroupActionButton) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const { openModal } = useModalsActions();
const joinGroup = useJoinGroup(group);
const leaveGroup = useLeaveGroup(group);
const { mutate: joinGroup, isPending: isJoiningGroup } = useJoinGroupMutation(group.id);
const { mutate: leaveGroup, isPending: isLeavingGroup } = useLeaveGroupMutation(group.id);
const isRequested = group.relationship?.requested;
const isNonMember = !group.relationship?.member && !isRequested;
@ -46,26 +44,21 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
const onJoinGroup = () =>
joinGroup.mutate(
{},
{
onSuccess() {
joinGroup.invalidate();
toast.success(
group.locked
? intl.formatMessage(messages.joinRequestSuccess)
: intl.formatMessage(messages.joinSuccess),
);
},
onError(error) {
const message = error.response?.json?.error;
if (message) {
toast.error(message);
}
},
joinGroup(undefined, {
onSuccess: () => {
toast.success(
group.locked
? intl.formatMessage(messages.joinRequestSuccess)
: intl.formatMessage(messages.joinSuccess),
);
},
);
// onError: (error) => {
// const message = error.response?.json?.error;
// if (message) {
// toast.error(message);
// }
// },
});
const onLeaveGroup = () => {
openModal('CONFIRM', {
@ -73,25 +66,15 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
message: intl.formatMessage(messages.confirmationMessage),
confirm: intl.formatMessage(messages.confirmationConfirm),
onConfirm: () =>
leaveGroup.mutate(group.relationship?.id as string, {
onSuccess() {
leaveGroup.invalidate();
leaveGroup(undefined, {
onSuccess: () => {
toast.success(intl.formatMessage(messages.leaveSuccess));
},
}),
});
};
const onCancelRequest = () =>
leaveGroup.mutate(group.relationship?.id as string, {
onSuccess() {
const entity = {
...(group.relationship as GroupRelationship),
requested: false,
};
dispatch(importEntities([entity], Entities.GROUP_RELATIONSHIPS));
},
});
const onCancelRequest = () => leaveGroup();
if (isOwner || isAdmin) {
return (
@ -103,7 +86,7 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
if (isNonMember) {
return (
<Button theme='primary' onClick={onJoinGroup} disabled={joinGroup.isSubmitting}>
<Button theme='primary' onClick={onJoinGroup} disabled={isJoiningGroup || isLeavingGroup}>
{group.locked ? (
<FormattedMessage id='group.join.private' defaultMessage='Request access' />
) : (
@ -115,14 +98,18 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
if (isRequested) {
return (
<Button theme='secondary' onClick={onCancelRequest} disabled={leaveGroup.isSubmitting}>
<Button
theme='secondary'
onClick={onCancelRequest}
disabled={isJoiningGroup || isLeavingGroup}
>
<FormattedMessage id='group.cancel_request' defaultMessage='Cancel request' />
</Button>
);
}
return (
<Button theme='secondary' onClick={onLeaveGroup} disabled={leaveGroup.isSubmitting}>
<Button theme='secondary' onClick={onLeaveGroup} disabled={isJoiningGroup || isLeavingGroup}>
<FormattedMessage id='group.leave' defaultMessage='Leave group' />
</Button>
);

View File

@ -4,8 +4,6 @@ import React, { useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useAccount } from '@/api/hooks/accounts/use-account';
import { useDemoteGroupMember } from '@/api/hooks/groups/use-demote-group-member';
import { usePromoteGroupMember } from '@/api/hooks/groups/use-promote-group-member';
import Account from '@/components/account';
import DropdownMenu from '@/components/dropdown-menu/dropdown-menu';
import HStack from '@/components/ui/hstack';
@ -15,7 +13,9 @@ import PlaceholderAccount from '@/features/placeholder/components/placeholder-ac
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useBlockGroupUserMutation } from '@/queries/groups/use-group-blocks';
import {
useDemoteGroupMemberMutation,
useKickGroupMemberMutation,
usePromoteGroupMemberMutation,
type MinifiedGroupMember,
} from '@/queries/groups/use-group-members';
import { useModalsActions } from '@/stores/modals';
@ -79,8 +79,8 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
const { mutate: blockGroupMember } = useBlockGroupUserMutation(group.id, member.account_id);
const { mutate: kickGroupMember } = useKickGroupMemberMutation(group.id, member.account_id);
const promoteGroupMember = usePromoteGroupMember(group, member);
const demoteGroupMember = useDemoteGroupMember(group, member);
const { mutate: promoteGroupMember } = usePromoteGroupMemberMutation(group.id);
const { mutate: demoteGroupMember } = useDemoteGroupMemberMutation(group.id);
const { account, isLoading } = useAccount(member.account_id);
@ -91,6 +91,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
// Member role
const isMemberOwner = member.role === GroupRoles.OWNER;
const isMemberAdmin = member.role === GroupRoles.ADMIN;
// const isMemberModerator = membisMemberModeratorer.role === GroupRoles.MODERATOR;
const isMemberUser = member.role === GroupRoles.USER;
const handleKickFromGroup = () => {
@ -131,7 +132,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
confirm: intl.formatMessage(messages.promoteConfirm),
onConfirm: () => {
promoteGroupMember(
{ role: GroupRoles.ADMIN, account_ids: [member.account_id] },
{ accountId: member.account_id, role: GroupRoles.ADMIN },
{
onSuccess() {
toast.success(intl.formatMessage(messages.promotedToAdmin, { name: account?.acct }));
@ -144,7 +145,7 @@ const GroupMemberListItem = ({ member, group }: IGroupMemberListItem) => {
const handleUserAssignment = () => {
demoteGroupMember(
{ role: GroupRoles.USER, account_ids: [member.account_id] },
{ accountId: member.account_id, role: GroupRoles.USER },
{
onSuccess() {
toast.success(intl.formatMessage(messages.demotedToUser, { name: account?.acct }));

View File

@ -2,9 +2,9 @@ import { GroupRoles, type Group } from 'pl-api';
import React, { useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useLeaveGroup } from '@/api/hooks/groups/use-leave-group';
import DropdownMenu, { Menu } from '@/components/dropdown-menu';
import IconButton from '@/components/ui/icon-button';
import { useLeaveGroupMutation } from '@/queries/groups/use-group-relationship';
import { useModalsActions } from '@/stores/modals';
import toast from '@/toast';
@ -28,7 +28,7 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
const { openModal } = useModalsActions();
const intl = useIntl();
const leaveGroup = useLeaveGroup(group);
const { mutate: leaveGroup } = useLeaveGroupMutation(group.id);
const isMember = group.relationship?.role === GroupRoles.USER;
const isAdmin = group.relationship?.role === GroupRoles.ADMIN;
@ -51,9 +51,8 @@ const GroupOptionsButton = ({ group }: IGroupActionButton) => {
message: intl.formatMessage(messages.confirmationMessage),
confirm: intl.formatMessage(messages.confirmationConfirm),
onConfirm: () =>
leaveGroup.mutate(group.relationship?.id as string, {
onSuccess() {
leaveGroup.invalidate();
leaveGroup(undefined, {
onSuccess: () => {
toast.success(intl.formatMessage(messages.leaveSuccess));
},
}),

View File

@ -9,27 +9,18 @@ import Stack from '@/components/ui/stack';
import Text from '@/components/ui/text';
import Emojify from '@/features/emoji/emojify';
import GroupActionButton from '@/features/group/components/group-action-button';
import { useGroupQuery } from '@/queries/groups/use-group';
import { shortNumberFormat } from '@/utils/numbers';
import type { Group } from 'pl-api';
interface IGroupListItem {
group: Pick<
Group,
| 'id'
| 'avatar'
| 'avatar_description'
| 'display_name'
| 'emojis'
| 'locked'
| 'members_count'
| 'relationship'
>;
groupId: string;
withJoinAction?: boolean;
}
const GroupListItem = (props: IGroupListItem) => {
const { group, withJoinAction = true } = props;
const GroupListItem: React.FC<IGroupListItem> = ({ groupId, withJoinAction = true }) => {
const { data: group } = useGroupQuery(groupId, true);
if (!group) return null;
return (
<HStack alignItems='center' justifyContent='between' data-testid='group-list-item'>

View File

@ -3,11 +3,11 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import { groupComposeModal } from '@/actions/compose';
import { useGroup } from '@/api/hooks/groups/use-group';
import Avatar from '@/components/ui/avatar';
import HStack from '@/components/ui/hstack';
import Icon from '@/components/ui/icon';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useGroupQuery } from '@/queries/groups/use-group';
import { useModalsActions } from '@/stores/modals';
import { layouts } from '../router';
@ -19,7 +19,7 @@ interface IComposeButton {
const ComposeButton: React.FC<IComposeButton> = ({ shrink }) => {
const match = useMatch({ from: layouts.group.id, shouldThrow: false });
const { group } = useGroup(match?.params.groupId ?? '');
const { data: group } = useGroupQuery(match?.params.groupId);
const isGroupMember = !!group?.relationship?.member;
if (match && isGroupMember) {
@ -49,7 +49,7 @@ const HomeComposeButton: React.FC<IComposeButton> = ({ shrink }) => {
const GroupComposeButton: React.FC<IComposeButton> = ({ shrink }) => {
const dispatch = useAppDispatch();
const match = useMatch({ from: layouts.group.id, shouldThrow: false });
const { group } = useGroup(match?.params.groupId ?? '');
const { data: group } = useGroupQuery(match?.params.groupId);
if (!group) return null;

View File

@ -1,14 +1,14 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { useGroups } from '@/api/hooks/groups/use-groups';
import Widget from '@/components/ui/widget';
import GroupListItem from '@/features/groups/components/discover/group-list-item';
import PlaceholderGroupSearch from '@/features/placeholder/components/placeholder-group-search';
import { useGroupsQuery } from '@/queries/groups/use-groups';
const MyGroupsPanel = () => {
const { groups, isFetching, isFetched, isError } = useGroups();
const isEmpty = (isFetched && groups.length === 0) ?? isError;
const { data: groupIds = [], isFetching, isError } = useGroupsQuery();
const isEmpty = (!isFetching && groupIds.length === 0) ?? isError;
if (isEmpty) {
return null;
@ -20,9 +20,11 @@ const MyGroupsPanel = () => {
? new Array(3)
.fill(0)
.map((_, idx) => <PlaceholderGroupSearch key={idx} withJoinAction={false} />)
: groups
: groupIds
.slice(0, 3)
.map((group) => <GroupListItem group={group} withJoinAction={false} key={group.id} />)}
.map((groupId) => (
<GroupListItem groupId={groupId} withJoinAction={false} key={groupId} />
))}
</Widget>
);
};