Skip to content

Commit

Permalink
feat: Add canvas access maganer (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
matevszm authored Dec 5, 2024
1 parent 97a8249 commit f2b715d
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 4 deletions.
14 changes: 13 additions & 1 deletion src/components/ContentTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default function ContentTable({
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>
{t('components.contentTable.createdAt')}
{t('components.contentTable.actions')}
</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => {
Expand Down Expand Up @@ -199,6 +199,18 @@ export default function ContentTable({
{t('components.contentTable.buttons.delete')}
</DropdownMenuItem>
)}
{value.isOwner && (
<DropdownMenuItem
onClick={() => {
openModal({
modalState: 'EDIT_CANVAS_ACCESS',
params: { selectedId: value.id },
});
}}
>
{t('components.contentTable.buttons.access')}
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
Expand Down
113 changes: 113 additions & 0 deletions src/components/EditCanvasAccessDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { useModalStore } from '@/store/modalStore';
import { useTranslation } from 'react-i18next';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { canvasAccessByIdDTO, ExcaliApi } from '@/lib/api/excali-api';
import { XIcon } from 'lucide-react';
import { toast } from '@/components/ui/use-toast';

interface EditCanvasDialogProps {
isOpen: boolean;
onClose: () => void;
}

export default function EditCanvasAccessDialog({
isOpen,
onClose,
}: EditCanvasDialogProps) {
const { t } = useTranslation();
const { modalProps } = useModalStore();

const queryClient = useQueryClient();

const currentCanvasId = modalProps?.selectedId;

const currentCanvasQueryKey = ['canvas', currentCanvasId];

const { data: canvasData } = useQuery({
queryKey: currentCanvasQueryKey,
queryFn: () => ExcaliApi.getCanvasById(`${currentCanvasId}`),
enabled: Boolean(currentCanvasId),
});

const { mutate: removeAccessById } = useMutation({
mutationFn: (data: canvasAccessByIdDTO) => ExcaliApi.removeAccessById(data),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: currentCanvasQueryKey });
toast({
title: 'Access remove successfully',
});
},
});

const { resetState } = useModalStore();

const canvasAccesses =
canvasData?.canvasAccesses.sort((a, b) =>
a.isOwner && b.isOwner ? 0 : a.isOwner ? -1 : 1
) ?? [];

function removeAccess(userId: string) {
if (!currentCanvasId) {
onClose();
return toast({
variant: 'destructive',
title: 'Invalid canvasId',
});
}

removeAccessById({
canvasId: currentCanvasId,
personId: userId,
});
}

return (
<Dialog
open={isOpen}
onOpenChange={() => {
onClose();
resetState();
}}
>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>
{t('components.editCanvasAccessDialog.title')}
</DialogTitle>
<DialogDescription>
{t('components.editCanvasAccessDialog.description')}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
{canvasAccesses.map(({ user, isOwner }) => (
<div key={user.id} className="flex items-center">
{!isOwner && (
<XIcon
className="h-4 w-4 cursor-pointer text-red-500"
onClick={() => removeAccess(user.id)}
/>
)}
<p className={isOwner ? 'text-neutral-500' : ''}>
{user.displayName}
</p>
</div>
))}
</div>
<DialogFooter>
<Button onClick={onClose}>
{t('components.editCanvasAccessDialog.close')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
5 changes: 5 additions & 0 deletions src/components/UserDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { TagsFilterStoreProvider } from '@/providers/TagsFilterProvider/TagsFilt
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { ShareCanvasByIdDialog } from '@/components/ShareCanvasByIdDialog';
import { useModalStore } from '@/store/modalStore';
import EditCanvasAccessDialog from '@/components/EditCanvasAccessDialog';

export default function UserDashboard() {
const { data } = useUserAuth();
Expand Down Expand Up @@ -72,6 +73,10 @@ export default function UserDashboard() {
/>
)}

{modalState === 'EDIT_CANVAS_ACCESS' && (
<EditCanvasAccessDialog isOpen={isModalOpen} onClose={closeModal} />
)}

<Toaster />
<ContentWrapper pagePaths={['Dashboard', 'Canvases']}>
<Tabs defaultValue="all">
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useEditCanvasForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useEffect } from 'react';
import { useEditCanvas } from '@/hooks/useEditCanvas';

export function useEditCanvasForm(
canvasId: string | undefined,
canvasId: string | undefined | null,
onClose: () => void
) {
const { data: tagsData = [] } = useQuery({
Expand Down
9 changes: 8 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"edit": "Edit",
"share": "Share",
"delete": "Delete",
"load": "Load"
"load": "Load",
"access": "Access"
}
},
"createCanvasDialog": {
Expand Down Expand Up @@ -90,6 +91,12 @@
},
"saveCanvasButton": "Save canvas"
},
"editCanvasAccessDialog": {
"title": "Access to dialog",
"description": "Edit accesses to canvas",
"toast": "Access has been saved.",
"close": "Close"
},
"errorPage": {
"title": "Uh-oh!",
"description": " Something went wrong.",
Expand Down
17 changes: 17 additions & 0 deletions src/lib/api/excali-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ export class ExcaliApi {
return this.instance.post(`/canvas/${canvasId}/access`, { personId });
};

public static removeAccessById = async ({
personId,
canvasId,
}: canvasAccessByIdDTO) => {
return this.instance(`/canvas/${canvasId}/access`, {
method: 'DELETE',
data: { personId },
});
};

public static deleteCanvasById = async (canvasId: Uuid) => {
return this.instance.delete(`/canvas/${canvasId}`);
};
Expand Down Expand Up @@ -163,6 +173,12 @@ export interface CanvasDTO {
tags: CanvasTagDTO[];
isOwner: boolean;
owner: string;
canvasAccesses: CanvasAccessesDTO[];
}

export interface CanvasAccessesDTO {
isOwner: boolean;
user: UserDTO;
}

export interface ApiPageInfo {
Expand Down Expand Up @@ -214,6 +230,7 @@ export interface UpdateCanvasMetaDTO {
export interface UserDTO {
id: string;
email: string;
displayName: string;
}

export interface canvasAccessDTO {
Expand Down
4 changes: 3 additions & 1 deletion src/store/modalStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface ModalState {
export const MODAL_STATE = {
ADD_TAG: 'ADD_TAG',
EDIT_TAG: 'EDIT_TAG',
EDIT_CANVAS_ACCESS: 'EDIT_CANVAS_ACCESS',
REMOVE_TAG: 'REMOVE_TAG',
ADD_CANVAS: 'ADD_CANVAS',
EDIT_CANVAS: 'EDIT_CANVAS',
Expand All @@ -42,7 +43,8 @@ export type ModalPayload =
| typeof MODAL_STATE.REMOVE_TAG
| typeof MODAL_STATE.EDIT_CANVAS
| typeof MODAL_STATE.REMOVE_CANVAS
| typeof MODAL_STATE.SHARE_CANVAS_BY_ID;
| typeof MODAL_STATE.SHARE_CANVAS_BY_ID
| typeof MODAL_STATE.EDIT_CANVAS_ACCESS;
params: ModalProps;
}
| {
Expand Down

0 comments on commit f2b715d

Please sign in to comment.