added delete board delete column fuctionality and so on
This commit is contained in:
parent
69b5b0376a
commit
1619a7ff7b
8 changed files with 163 additions and 15 deletions
|
@ -35,4 +35,22 @@ export async function addEmailtoBoard(boardId: string, email: string){
|
||||||
usersAccesses[email] = ['room:write'];
|
usersAccesses[email] = ['room:write'];
|
||||||
await liveblocksClient.updateRoom(boardId, {usersAccesses});
|
await liveblocksClient.updateRoom(boardId, {usersAccesses});
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateBoard(boardId:string, updateData:any){
|
||||||
|
const result = await liveblocksClient.updateRoom(boardId, updateData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeEmailFromBoard(boardId:string, email:string){
|
||||||
|
const room = await liveblocksClient.getRoom(boardId);
|
||||||
|
const usersAccesses:any = room.usersAccesses;
|
||||||
|
usersAccesses[email] = null
|
||||||
|
await liveblocksClient.updateRoom(boardId, {usersAccesses});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBoard(boardId:string){
|
||||||
|
await liveblocksClient.deleteRoom(boardId)
|
||||||
|
return true
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ const BoardPage = async (props: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Board name={boardInfo.metadata.boardName} id={boardId}/>
|
<Board name={boardInfo.metadata.boardName.toString()} id={boardId}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"use server"
|
"use server"
|
||||||
|
import BoardDelete from "@/components/BoardDelete";
|
||||||
|
import EmailAccessList from "@/components/EmailAccessList";
|
||||||
import NewUserAccess from "@/components/forms/NewUserAccess";
|
import NewUserAccess from "@/components/forms/NewUserAccess";
|
||||||
import { liveblocksClient } from "@/lib/liveblocksClient";
|
import { liveblocksClient } from "@/lib/liveblocksClient";
|
||||||
import { getUserEmail } from "@/lib/userClient";
|
import { getUserEmail } from "@/lib/userClient";
|
||||||
|
@ -23,14 +25,13 @@ const page = async ({params}: PageProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Link href={`/boards/${boardId}`} className="flex items-center gap-1 text-[#164863] font-semibold hover:text-[#427D9D] hover:duration-300"><ImBackward2/>Go back</Link>
|
<div className="flex justify-between">
|
||||||
<h1 className="text-2xl">Access to {boardInfo.metadata.boardName}:</h1>
|
<Link href={`/boards/${boardId}`} className="flex items-center gap-1 text-[#164863] font-semibold hover:text-[#427D9D] hover:duration-300"><ImBackward2/>Go back</Link>
|
||||||
|
<BoardDelete boardId={boardId}/>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl mb-2">Access to {boardInfo.metadata.boardName}:</h1>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
{Object.keys(boardInfo.usersAccesses).map(email => (
|
<EmailAccessList boardId={boardId} usersAccesses={boardInfo.usersAccesses}/>
|
||||||
<div>
|
|
||||||
{email}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<NewUserAccess boardId = {boardId}/>
|
<NewUserAccess boardId = {boardId}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,9 +5,29 @@ import { ClientSideSuspense } from '@liveblocks/react';
|
||||||
import Columns from './Columns';
|
import Columns from './Columns';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { IoMdSettings } from "react-icons/io";
|
import { IoMdSettings } from "react-icons/io";
|
||||||
|
import { FormEvent, useState } from 'react';
|
||||||
|
import { updateBoard } from '@/app/actions/boardActions';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
|
const Board = ({id, name}: {id:string, name:string}) => {
|
||||||
|
const [renameBoard, setRenameBoard] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
async function handleRenameSubmit(ev:FormEvent){
|
||||||
|
ev.preventDefault();
|
||||||
|
const input = (ev.target as HTMLFormElement).querySelector('input');
|
||||||
|
if(input){
|
||||||
|
const newBoardName = input?.value;
|
||||||
|
await updateBoard(id, {metadata: {boardName: newBoardName}});
|
||||||
|
input.value = '';
|
||||||
|
setRenameBoard(false);
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const Board = ({id, name}: {id: string, name: string}) => {
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoomProvider id={id} initialPresence={{}} initialStorage={
|
<RoomProvider id={id} initialPresence={{}} initialStorage={
|
||||||
|
@ -19,7 +39,14 @@ const Board = ({id, name}: {id: string, name: string}) => {
|
||||||
<>
|
<>
|
||||||
<div className='flex justify-between gap-2 items-center mb-4'>
|
<div className='flex justify-between gap-2 items-center mb-4'>
|
||||||
<div>
|
<div>
|
||||||
<h1 className='text-2xl'>Board: {name}</h1>
|
{!renameBoard && (
|
||||||
|
<h1 onClick={() => setRenameBoard(true)} className='text-2xl'>Board: {name}</h1>
|
||||||
|
)}
|
||||||
|
{renameBoard && (
|
||||||
|
<form onSubmit={handleRenameSubmit}>
|
||||||
|
<input type="text" defaultValue={name} className='border p-2'/>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link href={`/boards/${id}/settings`} className='flex items-center gap-1 bg-[#427D9D] p-2 rounded-full'>
|
<Link href={`/boards/${id}/settings`} className='flex items-center gap-1 bg-[#427D9D] p-2 rounded-full'>
|
||||||
<IoMdSettings className='text-white text-[18px]'/>
|
<IoMdSettings className='text-white text-[18px]'/>
|
||||||
|
|
23
task_management_app/src/components/BoardDelete.tsx
Normal file
23
task_management_app/src/components/BoardDelete.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"use client"
|
||||||
|
import { deleteBoard } from "@/app/actions/boardActions"
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
|
||||||
|
const BoardDelete = ({boardId}:{boardId:string}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
async function handleDeleteBoard(){
|
||||||
|
await deleteBoard(boardId);
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => handleDeleteBoard()} className="bg-[tomato] p-2 text-white rounded-md font-semibold hover:opacity-90">Delete board</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BoardDelete
|
|
@ -2,6 +2,8 @@ import { Card, useMutation, useStorage } from '@/app/liveblocks.config';
|
||||||
import { shallow } from '@liveblocks/client';
|
import { shallow } from '@liveblocks/client';
|
||||||
import { ReactSortable } from 'react-sortablejs';
|
import { ReactSortable } from 'react-sortablejs';
|
||||||
import NewCardForm from './forms/NewCardForm';
|
import NewCardForm from './forms/NewCardForm';
|
||||||
|
import { FormEvent, useState } from 'react';
|
||||||
|
import { BsThreeDots } from "react-icons/bs";
|
||||||
|
|
||||||
|
|
||||||
type ColumnProps = {
|
type ColumnProps = {
|
||||||
|
@ -35,13 +37,48 @@ const Column = ({id, name}: ColumnProps) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
},[])
|
}, []);
|
||||||
|
|
||||||
|
const updateColumn = useMutation(({storage}, id, newName) => {
|
||||||
|
const columns = storage.get('columns');
|
||||||
|
columns.find(c => c.toObject().id === id)?.set('name', newName)
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [renameColumn, setRenameColumn] = useState(false);
|
||||||
|
function handleRenameColumn(ev: FormEvent){
|
||||||
|
ev.preventDefault();
|
||||||
|
const input = (ev.target as HTMLFormElement).querySelector('input')
|
||||||
|
if(input){
|
||||||
|
const newColumnName = input.value;
|
||||||
|
updateColumn(id, newColumnName)
|
||||||
|
setRenameColumn(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-50 shadow-md rounded-md p-2 bg-gray-100'>
|
<div className='w-50 shadow-md rounded-md p-2 bg-gray-100'>
|
||||||
<h3>{name}</h3>
|
{!renameColumn && (
|
||||||
{columnCards && (
|
<div className='flex justify-between'>
|
||||||
|
<h3>{name}</h3>
|
||||||
|
<button onClick={() => setRenameColumn(true)}><BsThreeDots className='text-[#164863]'/></button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{renameColumn && (
|
||||||
|
<div className='mb-8'>
|
||||||
|
Edit name:
|
||||||
|
<form onSubmit={handleRenameColumn} className='mb-2'>
|
||||||
|
<input type="text" defaultValue={name} className='border p-2 w-full'/>
|
||||||
|
<button type="submit" className='w-full mt-2'>Save</button>
|
||||||
|
</form>
|
||||||
|
<button className='w-full p-2 bg-[tomato] text-white rounded-md font-semibold text-sm'> Delete column</button>
|
||||||
|
<button onClick={() => setRenameColumn(false)} className='w-full mt-4 text-xs uppercase font-bold text-gray-500 tracking-wider'>Cancel</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!renameColumn && columnCards && (
|
||||||
<>
|
<>
|
||||||
<ReactSortable list={columnCards} setList={items => setTasksOrderForColumn(items, id)} group="cards" ghostClass='opacity-40' className='min-h-12'>
|
<ReactSortable list={columnCards} setList={items => setTasksOrderForColumn(items, id)} group="cards" ghostClass='opacity-40' className='min-h-12'>
|
||||||
{columnCards.map(card => (
|
{columnCards.map(card => (
|
||||||
|
@ -52,7 +89,10 @@ const Column = ({id, name}: ColumnProps) => {
|
||||||
</ReactSortable>
|
</ReactSortable>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<NewCardForm columnId={id}/>
|
{!renameColumn && (
|
||||||
|
<NewCardForm columnId={id}/>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
33
task_management_app/src/components/EmailAccessList.tsx
Normal file
33
task_management_app/src/components/EmailAccessList.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
"use client"
|
||||||
|
import { deleteBoard, removeEmailFromBoard } from "@/app/actions/boardActions";
|
||||||
|
import { RoomAccesses } from "@liveblocks/node";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { IoTrashOutline } from "react-icons/io5";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const EmailAccessList = ({boardId, usersAccesses} : {boardId:string ,usersAccesses:RoomAccesses}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
async function handleDelete(emailToDelete: string){
|
||||||
|
await removeEmailFromBoard(boardId, emailToDelete)
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-xs">
|
||||||
|
{Object.keys(usersAccesses).map(email => (
|
||||||
|
<div key={email} className="flex gap-2 my-2 items-center max-w-xs justify-between border pl-2">
|
||||||
|
{email}
|
||||||
|
<button onClick={() => handleDelete(email)} className="p-2 bg-gray-100">
|
||||||
|
<IoTrashOutline className="text-[tomato] text-lg"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmailAccessList
|
|
@ -2,4 +2,10 @@ import { Liveblocks } from "@liveblocks/node";
|
||||||
|
|
||||||
export const liveblocksClient = new Liveblocks({
|
export const liveblocksClient = new Liveblocks({
|
||||||
secret: process.env.LIVEBLOCKS_SECRET_KEY || '',
|
secret: process.env.LIVEBLOCKS_SECRET_KEY || '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function getLiveblocksClient(){
|
||||||
|
return new Liveblocks({
|
||||||
|
secret: process.env.LIVEBLOCKS_SECRET_KEY || '',
|
||||||
|
});
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue