auth + groups invite
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
EyeOpen,
|
||||
} from '../../../assets/icons/groups';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { GroupUpdate } from './Groups';
|
||||
import { GroupInvite, GroupUpdate } from './Groups';
|
||||
|
||||
export interface GroupItemProps {
|
||||
id: number;
|
||||
@@ -17,6 +17,9 @@ export interface GroupItemProps {
|
||||
description: string;
|
||||
setUpdateActive: (value: any) => void;
|
||||
setUpdateGroup: (value: GroupUpdate) => void;
|
||||
setInviteActive: (value: any) => void;
|
||||
setInviteGroup: (value: GroupInvite) => void;
|
||||
type: 'manage' | 'member';
|
||||
}
|
||||
|
||||
interface IconComponentProps {
|
||||
@@ -45,6 +48,9 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
||||
description,
|
||||
setUpdateGroup,
|
||||
setUpdateActive,
|
||||
setInviteActive,
|
||||
setInviteGroup,
|
||||
type,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -63,10 +69,16 @@ const GroupItem: React.FC<GroupItemProps> = ({
|
||||
<div className="grid grid-flow-row grid-rows-[1fr,24px]">
|
||||
<div className="text-[18px] font-bold">{name}</div>
|
||||
<div className=" flex gap-[10px]">
|
||||
{(role == 'menager' || role == 'owner') && (
|
||||
<IconComponent src={UserAdd} />
|
||||
{type == 'manage' && (
|
||||
<IconComponent
|
||||
src={UserAdd}
|
||||
onClick={() => {
|
||||
setInviteActive(true);
|
||||
setInviteGroup({ id, name });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(role == 'menager' || role == 'owner') && (
|
||||
{type == 'manage' && (
|
||||
<IconComponent
|
||||
src={Edit}
|
||||
onClick={() => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { fetchMyGroups } from '../../../redux/slices/groups';
|
||||
import ModalCreate from './ModalCreate';
|
||||
import ModalUpdate from './ModalUpdate';
|
||||
import Filters from './Filter';
|
||||
import ModalInvite from './ModalInvite';
|
||||
|
||||
export interface GroupUpdate {
|
||||
id: number;
|
||||
@@ -15,19 +16,35 @@ export interface GroupUpdate {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface GroupInvite {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const Groups = () => {
|
||||
const [modalActive, setModalActive] = useState<boolean>(false);
|
||||
const [modelUpdateActive, setModalUpdateActive] = useState<boolean>(false);
|
||||
const [modalActive, setModalActive] = useState(false);
|
||||
const [modalUpdateActive, setModalUpdateActive] = useState(false);
|
||||
const [updateGroup, setUpdateGroup] = useState<GroupUpdate>({
|
||||
id: 0,
|
||||
name: '',
|
||||
description: '',
|
||||
});
|
||||
const [modalInviteActive, setModalInviteActive] = useState(false);
|
||||
const [inviteGroup, setInviteGroup] = useState<GroupInvite>({
|
||||
id: 0,
|
||||
name: '',
|
||||
});
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
// Берём группы из стора
|
||||
const groups = useAppSelector((store) => store.groups.groups);
|
||||
// ✅ Берём группы и статус из нового слайса
|
||||
const groups = useAppSelector((store) => store.groups.fetchMyGroups.groups);
|
||||
const groupsStatus = useAppSelector(
|
||||
(store) => store.groups.fetchMyGroups.status,
|
||||
);
|
||||
const groupsError = useAppSelector(
|
||||
(store) => store.groups.fetchMyGroups.error,
|
||||
);
|
||||
|
||||
// Берём текущего пользователя
|
||||
const currentUserName = useAppSelector((store) => store.auth.username);
|
||||
@@ -52,8 +69,8 @@ const Groups = () => {
|
||||
(m) => m.username === currentUserName,
|
||||
);
|
||||
if (!me) return;
|
||||
|
||||
if (me.role === 'Administrator') {
|
||||
const roles = me.role.split(',').map((r) => r.trim());
|
||||
if (roles.includes('Administrator')) {
|
||||
managed.push(group);
|
||||
} else {
|
||||
current.push(group);
|
||||
@@ -68,7 +85,7 @@ const Groups = () => {
|
||||
}, [groups, currentUserName]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20p]">
|
||||
<div className="h-full w-[calc(100%+250px)] box-border p-[20px] pt-[20px]">
|
||||
<div className="h-full box-border">
|
||||
<div className="relative flex items-center mb-[20px]">
|
||||
<div
|
||||
@@ -79,9 +96,7 @@ const Groups = () => {
|
||||
Группы
|
||||
</div>
|
||||
<SecondaryButton
|
||||
onClick={() => {
|
||||
setModalActive(true);
|
||||
}}
|
||||
onClick={() => setModalActive(true)}
|
||||
text="Создать группу"
|
||||
className="absolute right-0"
|
||||
/>
|
||||
@@ -89,37 +104,67 @@ const Groups = () => {
|
||||
|
||||
<Filters />
|
||||
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Управляемые"
|
||||
groups={managedGroups}
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
/>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Текущие"
|
||||
groups={currentGroups}
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
/>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Скрытые"
|
||||
groups={hiddenGroups} // пока пусто
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
/>
|
||||
{groupsStatus === 'loading' && (
|
||||
<div className="text-liquid-white mt-4">
|
||||
Загрузка групп...
|
||||
</div>
|
||||
)}
|
||||
{groupsStatus === 'failed' && (
|
||||
<div className="text-red-400 mt-4">
|
||||
Ошибка: {groupsError || 'Не удалось загрузить группы'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{groupsStatus === 'successful' && (
|
||||
<>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Управляемые"
|
||||
groups={managedGroups}
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
setInviteActive={setModalInviteActive}
|
||||
setInviteGroup={setInviteGroup}
|
||||
type="manage"
|
||||
/>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Текущие"
|
||||
groups={currentGroups}
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
setInviteActive={setModalInviteActive}
|
||||
setInviteGroup={setInviteGroup}
|
||||
type="member"
|
||||
/>
|
||||
<GroupsBlock
|
||||
className="mb-[20px]"
|
||||
title="Скрытые"
|
||||
groups={hiddenGroups} // пока пусто
|
||||
setUpdateActive={setModalUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
setInviteActive={setModalInviteActive}
|
||||
setInviteGroup={setInviteGroup}
|
||||
type="member"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ModalCreate setActive={setModalActive} active={modalActive} />
|
||||
<ModalUpdate
|
||||
setActive={setModalUpdateActive}
|
||||
active={modelUpdateActive}
|
||||
active={modalUpdateActive}
|
||||
groupId={updateGroup.id}
|
||||
groupName={updateGroup.name}
|
||||
groupDescription={updateGroup.description}
|
||||
/>
|
||||
<ModalInvite
|
||||
setActive={setModalInviteActive}
|
||||
active={modalInviteActive}
|
||||
groupId={inviteGroup.id}
|
||||
groupName={inviteGroup.name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import GroupItem from './GroupItem';
|
||||
import { cn } from '../../../lib/cn';
|
||||
import { ChevroneDown } from '../../../assets/icons/groups';
|
||||
import { Group } from '../../../redux/slices/groups';
|
||||
import { GroupUpdate } from './Groups';
|
||||
import { GroupInvite, GroupUpdate } from './Groups';
|
||||
|
||||
interface GroupsBlockProps {
|
||||
groups: Group[];
|
||||
@@ -11,6 +11,9 @@ interface GroupsBlockProps {
|
||||
className?: string;
|
||||
setUpdateActive: (value: any) => void;
|
||||
setUpdateGroup: (value: GroupUpdate) => void;
|
||||
setInviteActive: (value: any) => void;
|
||||
setInviteGroup: (value: GroupInvite) => void;
|
||||
type: 'manage' | 'member';
|
||||
}
|
||||
|
||||
const GroupsBlock: FC<GroupsBlockProps> = ({
|
||||
@@ -19,6 +22,9 @@ const GroupsBlock: FC<GroupsBlockProps> = ({
|
||||
className,
|
||||
setUpdateActive,
|
||||
setUpdateGroup,
|
||||
setInviteActive,
|
||||
setInviteGroup,
|
||||
type,
|
||||
}) => {
|
||||
const [active, setActive] = useState<boolean>(title != 'Скрытые');
|
||||
|
||||
@@ -63,8 +69,11 @@ const GroupsBlock: FC<GroupsBlockProps> = ({
|
||||
description={v.description}
|
||||
setUpdateActive={setUpdateActive}
|
||||
setUpdateGroup={setUpdateGroup}
|
||||
setInviteActive={setInviteActive}
|
||||
setInviteGroup={setInviteGroup}
|
||||
role={'owner'}
|
||||
name={v.name}
|
||||
type={type}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ interface ModalCreateProps {
|
||||
const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
||||
const [name, setName] = useState<string>('');
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const status = useAppSelector((state) => state.groups.statuses.create);
|
||||
const status = useAppSelector((state) => state.groups.createGroup.status);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
102
src/views/home/groups/ModalInvite.tsx
Normal file
102
src/views/home/groups/ModalInvite.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { FC, useEffect, useMemo } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { fetchGroupJoinLink } from '../../../redux/slices/groups';
|
||||
import { Modal } from '../../../components/modal/Modal';
|
||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||
import { SecondaryButton } from '../../../components/button/SecondaryButton';
|
||||
import { Input } from '../../../components/input/Input';
|
||||
|
||||
interface ModalInviteProps {
|
||||
active: boolean;
|
||||
setActive: (value: boolean) => void;
|
||||
groupId: number;
|
||||
groupName: string;
|
||||
}
|
||||
|
||||
const ModalInvite: FC<ModalInviteProps> = ({
|
||||
active,
|
||||
setActive,
|
||||
groupId,
|
||||
groupName,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const baseUrl = window.location.origin;
|
||||
|
||||
// Получаем токен и дату из Redux
|
||||
const { joinLink, status } = useAppSelector(
|
||||
(state) => state.groups.fetchGroupJoinLink,
|
||||
);
|
||||
|
||||
// При открытии модалки запрашиваем join link
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
dispatch(fetchGroupJoinLink(groupId));
|
||||
}
|
||||
}, [active, groupId, dispatch]);
|
||||
|
||||
// Генерация полной ссылки с query параметрами
|
||||
const inviteLink = useMemo(() => {
|
||||
if (!joinLink) return '';
|
||||
const params = new URLSearchParams({
|
||||
token: joinLink.token,
|
||||
expiresAt: joinLink.expiresAt,
|
||||
groupName,
|
||||
groupId: `${groupId}`,
|
||||
});
|
||||
return `${baseUrl}/home/group-invite?${params.toString()}`;
|
||||
}, [joinLink, groupName, baseUrl, groupId]);
|
||||
|
||||
// Копирование и закрытие модалки
|
||||
const handleCopy = async () => {
|
||||
if (!inviteLink) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(inviteLink);
|
||||
setActive(false);
|
||||
} catch (err) {
|
||||
console.error('Не удалось скопировать ссылку:', err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
|
||||
onOpenChange={setActive}
|
||||
open={active}
|
||||
backdrop="blur"
|
||||
>
|
||||
<div className="w-[500px]">
|
||||
<div className="font-bold text-[30px] mb-[20px]">
|
||||
Приглашение в группу "{groupName}"
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<div className="font-bold text-[18px] mb-[5px]">
|
||||
Ссылка для приглашения
|
||||
</div>
|
||||
<div
|
||||
className=" break-all break-words text-[#5d96ff] hover:underline cursor-pointer"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{inviteLink}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row w-full items-center justify-end mt-[30px] gap-[20px]">
|
||||
<PrimaryButton
|
||||
onClick={handleCopy}
|
||||
text={
|
||||
status === 'loading' ? 'Загрузка...' : 'Скопировать'
|
||||
}
|
||||
disabled={status === 'loading' || !inviteLink}
|
||||
/>
|
||||
<SecondaryButton
|
||||
onClick={() => setActive(false)}
|
||||
text="Отмена"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalInvite;
|
||||
@@ -24,10 +24,10 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
|
||||
const [name, setName] = useState<string>('');
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const statusUpdate = useAppSelector(
|
||||
(state) => state.groups.statuses.update,
|
||||
(state) => state.groups.updateGroup.status,
|
||||
);
|
||||
const statusDelete = useAppSelector(
|
||||
(state) => state.groups.statuses.delete,
|
||||
(state) => state.groups.deleteGroup.status,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user