lint frontend, fix drag and drop bug

This commit is contained in:
david 2026-02-27 10:53:36 +03:00
parent 1bcad71f23
commit 1628677222
13 changed files with 382 additions and 52 deletions

View file

@ -0,0 +1,120 @@
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { ModalContentProps } from '../../types';
import { useToast } from '../../context/toasts/useToast';
const cardSchema = z.object({
name: z
.string()
.min(1, 'Card name is required')
.max(100, 'Card name must be less than 100 characters'),
description: z.string().max(2000, 'Description must be less than 2000 characters').optional(),
});
type CardFormData = z.infer<typeof cardSchema>;
interface CreateCardModalProps extends ModalContentProps {
onCreate: (data: { name: string; description?: string }) => Promise<void>;
}
export function CreateCardModal({ onClose, onCreate }: CreateCardModalProps) {
const { addNotification } = useToast();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<CardFormData>({
resolver: zodResolver(cardSchema),
});
const onSubmit = async (data: CardFormData) => {
try {
await onCreate(data);
onClose();
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to create card';
addNotification({
type: 'error',
title: 'Error Creating Card',
message: errorMessage,
duration: 5000,
});
}
};
return (
<div className="bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-700">
<div className="flex justify-between items-center p-6 border-b border-gray-700">
<h2 id="modal-title" className="text-xl font-bold text-white">
Create Card
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-white transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="p-6">
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
Card Name <span className="text-red-400">*</span>
</label>
<input
id="name"
type="text"
{...register('name')}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Enter card name..."
autoFocus
/>
{errors.name && <p className="mt-1 text-sm text-red-400">{errors.name.message}</p>}
</div>
<div className="mb-6">
<label htmlFor="description" className="block text-sm font-medium text-gray-300 mb-2">
Description
</label>
<textarea
id="description"
rows={4}
{...register('description')}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
placeholder="Optional card description..."
/>
{errors.description && (
<p className="mt-1 text-sm text-red-400">{errors.description.message}</p>
)}
</div>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-6 py-3 text-gray-300 hover:text-white transition-colors"
>
Cancel
</button>
<button
type="submit"
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
>
Create Card
</button>
</div>
</form>
</div>
);
}

View file

@ -0,0 +1,106 @@
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { ModalContentProps } from '../../types';
import { useToast } from '@/context/toasts';
const listSchema = z.object({
name: z
.string()
.min(1, 'List name is required')
.max(100, 'List name must be less than 100 characters'),
});
type ListFormData = z.infer<typeof listSchema>;
interface CreateListModalProps extends ModalContentProps {
onCreate: (name: string) => Promise<void>;
}
export function CreateListModal({ onClose, onCreate }: CreateListModalProps) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<ListFormData>({
resolver: zodResolver(listSchema),
});
const { addNotification } = useToast();
const onSubmit = async (data: ListFormData) => {
try {
await onCreate(data.name);
onClose();
} catch (err) {
// Error is handled by caller
const errorMessage = err instanceof Error ? err.message : 'Failed to create card';
addNotification({
type: 'error',
title: 'Error Register',
message: errorMessage,
duration: 5000,
});
}
};
return (
<div className="bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-700">
<div className="flex justify-between items-center p-6 border-b border-gray-700">
<h2 id="modal-title" className="text-xl font-bold text-white">
Create List
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-white transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="p-6">
<div className="mb-6">
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
List Name <span className="text-red-400">*</span>
</label>
<input
id="name"
type="text"
{...register('name')}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., To Do, In Progress, Done"
autoFocus
/>
{errors.name && <p className="mt-1 text-sm text-red-400">{errors.name.message}</p>}
</div>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-6 py-3 text-gray-300 hover:text-white transition-colors"
>
Cancel
</button>
<button
type="submit"
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
>
Create List
</button>
</div>
</form>
</div>
);
}

View file

@ -9,7 +9,7 @@ interface KanbanCardProps {
export function KanbanCard({ card, onClick }: KanbanCardProps) { export function KanbanCard({ card, onClick }: KanbanCardProps) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id: card.id.toString(), id: `CARD_${card.id}`,
}); });
const style = { const style = {

View file

@ -2,20 +2,29 @@ import { useDroppable } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { ListWithCards, Card as CardType } from '../../types/kanban'; import { ListWithCards, Card as CardType } from '../../types/kanban';
import { KanbanCard } from './KanbanCard'; import { KanbanCard } from './KanbanCard';
import { CreateCardModal } from './CreateCardModal';
import { useModal } from '../../context/modals/useModal';
interface KanbanColumnProps { interface KanbanColumnProps {
list: ListWithCards; list: ListWithCards;
cards: CardType[]; cards: CardType[];
onCardClick: (card: CardType) => void; onCardClick: (card: CardType) => void;
onCardCreate: (data: { name: string; description?: string }) => Promise<void>;
} }
export function KanbanColumn({ list, cards, onCardClick }: KanbanColumnProps) { export function KanbanColumn({ list, cards, onCardClick, onCardCreate }: KanbanColumnProps) {
const { setNodeRef, isOver } = useDroppable({ const { setNodeRef, isOver } = useDroppable({
id: list.id.toString(), id: `LIST_${list.id}`,
}); });
const { openModal } = useModal();
const handleAddCard = () => {
openModal((props) => <CreateCardModal {...props} onCreate={onCardCreate} />);
};
return ( return (
<div className="bg-gray-800 rounded-lg p-4 min-w-[300px] max-w-[300px] border border-gray-700"> <div className="bg-gray-800 rounded-lg p-4 min-w-[300px] max-w-[300px] border border-gray-700 flex flex-col">
<h2 className="text-white font-bold text-lg mb-4 flex items-center justify-between"> <h2 className="text-white font-bold text-lg mb-4 flex items-center justify-between">
{list.name} {list.name}
<span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full"> <span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full">
@ -30,13 +39,34 @@ export function KanbanColumn({ list, cards, onCardClick }: KanbanColumnProps) {
> >
<div <div
ref={setNodeRef} ref={setNodeRef}
className={`min-h-[200px] transition-colors ${isOver ? 'bg-gray-750' : ''}`} className={`min-h-[200px] flex-1 transition-colors ${isOver ? 'bg-gray-750' : ''}`}
> >
{cards.map((card) => ( {cards.map((card) => (
<KanbanCard key={card.id} card={card} onClick={() => onCardClick(card)} /> <KanbanCard key={card.id} card={card} onClick={() => onCardClick(card)} />
))} ))}
</div> </div>
</SortableContext> </SortableContext>
<button
onClick={handleAddCard}
className="mt-3 w-full py-2 px-4 bg-gray-700 hover:bg-gray-600 text-gray-300 hover:text-white rounded-lg transition-colors flex items-center justify-center gap-2 text-sm font-medium"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Add Card
</button>
</div> </div>
); );
} }

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApi } from './useApi'; import { useApi } from './useApi';
import { useLoader } from '../context/loaders/useLoader'; import { useLoader } from '../context/loaders/useLoader';
import { useToast } from '../context/toasts/useToast'; import { useToast } from '../context/toasts/useToast';
@ -12,7 +12,7 @@ export function useBoard(boardId: number) {
const { withLoader } = useLoader(); const { withLoader } = useLoader();
const { addNotification } = useToast(); const { addNotification } = useToast();
const fetchBoard = async () => { const fetchBoard = useCallback(async () => {
try { try {
setError(null); setError(null);
const data = await withLoader(() => getBoard(boardId), 'Loading board...'); const data = await withLoader(() => getBoard(boardId), 'Loading board...');
@ -29,7 +29,7 @@ export function useBoard(boardId: number) {
}); });
return null; return null;
} }
}; }, [boardId, getBoard, withLoader, addNotification]);
const updateBoardData = (updatedBoard: BoardWithDetails) => { const updateBoardData = (updatedBoard: BoardWithDetails) => {
setBoard(updatedBoard); setBoard(updatedBoard);
@ -37,7 +37,7 @@ export function useBoard(boardId: number) {
useEffect(() => { useEffect(() => {
fetchBoard(); fetchBoard();
}, [boardId]); }, [boardId, fetchBoard]);
return { return {
board, board,

View file

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApi } from './useApi'; import { useApi } from './useApi';
import { useLoader } from '../context/loaders/useLoader'; import { useLoader } from '../context/loaders/useLoader';
import { useToast } from '../context/toasts/useToast'; import { useToast } from '../context/toasts/useToast';
@ -12,7 +12,7 @@ export function useBoards() {
const { withLoader } = useLoader(); const { withLoader } = useLoader();
const { addNotification } = useToast(); const { addNotification } = useToast();
const fetchBoards = async () => { const fetchBoards = useCallback(async () => {
try { try {
setError(null); setError(null);
const data = await withLoader(() => getBoards(), 'Loading boards...'); const data = await withLoader(() => getBoards(), 'Loading boards...');
@ -29,7 +29,7 @@ export function useBoards() {
}); });
return []; return [];
} }
}; }, [getBoards, withLoader, addNotification]);
const createNewBoard = async (boardData: { name: string; description?: string }) => { const createNewBoard = async (boardData: { name: string; description?: string }) => {
try { try {
@ -110,7 +110,7 @@ export function useBoards() {
useEffect(() => { useEffect(() => {
fetchBoards(); fetchBoards();
}, []); }, [fetchBoards]);
return { return {
boards, boards,

View file

@ -1,9 +1,9 @@
import { useApi } from './useApi'; import { useApi } from './useApi';
import { useLoader } from '../context/loaders/useLoader'; import { useLoader } from '../context/loaders/useLoader';
import { useToast } from '../context/toasts/useToast'; import { useToast } from '../context/toasts/useToast';
import { BoardWithDetails, Card, List } from '../types/kanban'; import { Card } from '../types/kanban';
export function useCardMutations(boardId: number, onUpdate: (board: BoardWithDetails) => void) { export function useCardMutations(boardId: number, onUpdate: () => void) {
const { createCard, updateCard, deleteCard } = useApi(); const { createCard, updateCard, deleteCard } = useApi();
const { withLoader } = useLoader(); const { withLoader } = useLoader();
const { addNotification } = useToast(); const { addNotification } = useToast();
@ -68,18 +68,20 @@ export function useCardMutations(boardId: number, onUpdate: (board: BoardWithDet
}; };
const moveCard = async ( const moveCard = async (
cardId: number, card: Card,
fromListId: number, fromListId: number,
toListId: number, toListId: number,
newPosition: number newPosition: number
) => { ) => {
try { try {
// Optimistic update - don't show loader for drag operations // Optimistic update - don't show loader for drag operations
await updateCard(cardId, { await updateCard(card.id, {
name: '', // Placeholder, will be filled by server name: card.name, // Placeholder, will be filled by server
pos: newPosition, pos: newPosition,
list_id: toListId, list_id: toListId,
}); });
onUpdate();
addNotification({ addNotification({
type: 'success', type: 'success',
title: 'Card Moved', title: 'Card Moved',

View file

@ -1,9 +1,8 @@
import { useApi } from './useApi'; import { useApi } from './useApi';
import { useLoader } from '../context/loaders/useLoader'; import { useLoader } from '../context/loaders/useLoader';
import { useToast } from '../context/toasts/useToast'; import { useToast } from '../context/toasts/useToast';
import { BoardWithDetails } from '../types/kanban';
export function useListMutations(boardId: number, onUpdate: (board: BoardWithDetails) => void) { export function useListMutations(boardId: number, onUpdate: () => void) {
const { createList, updateList, deleteList } = useApi(); const { createList, updateList, deleteList } = useApi();
const { withLoader } = useLoader(); const { withLoader } = useLoader();
const { addNotification } = useToast(); const { addNotification } = useToast();
@ -11,6 +10,7 @@ export function useListMutations(boardId: number, onUpdate: (board: BoardWithDet
const createNewList = async (name: string, pos: number) => { const createNewList = async (name: string, pos: number) => {
try { try {
const data = await withLoader(() => createList(boardId, { name, pos }), 'Creating list...'); const data = await withLoader(() => createList(boardId, { name, pos }), 'Creating list...');
onUpdate();
addNotification({ addNotification({
type: 'success', type: 'success',
title: 'List Created', title: 'List Created',

View file

@ -3,6 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { useBoards } from '../hooks/useBoards'; import { useBoards } from '../hooks/useBoards';
import { useToast } from '@/context/toasts';
const boardSchema = z.object({ const boardSchema = z.object({
name: z name: z
@ -17,6 +18,7 @@ type BoardFormData = z.infer<typeof boardSchema>;
export function BoardCreate() { export function BoardCreate() {
const navigate = useNavigate(); const navigate = useNavigate();
const { createBoard } = useBoards(); const { createBoard } = useBoards();
const { addNotification } = useToast();
const { const {
register, register,
@ -28,10 +30,22 @@ export function BoardCreate() {
const onSubmit = async (data: BoardFormData) => { const onSubmit = async (data: BoardFormData) => {
try { try {
await createBoard(data); const newBoard = await createBoard(data);
navigate('/boards'); addNotification({
type: 'success',
title: 'Board Created',
message: `Board "${newBoard.name}" created successfully.`,
duration: 3000,
});
navigate(`/boards/${newBoard.id}`);
} catch (err) { } catch (err) {
// Error is handled by the hook const errorMessage = err instanceof Error ? err.message : 'Failed to create board';
addNotification({
type: 'error',
title: 'Error Creating Board',
message: errorMessage,
duration: 5000,
});
} }
}; };

View file

@ -3,6 +3,8 @@ import { useBoard } from '../hooks/useBoard';
import { useCardMutations } from '../hooks/useCardMutations'; import { useCardMutations } from '../hooks/useCardMutations';
import { useListMutations } from '../hooks/useListMutations'; import { useListMutations } from '../hooks/useListMutations';
import { KanbanColumn } from '../components/kanban/KanbanColumn'; import { KanbanColumn } from '../components/kanban/KanbanColumn';
import { CreateListModal } from '../components/kanban/CreateListModal';
import { useModal } from '../context/modals/useModal';
import { import {
DndContext, DndContext,
DragEndEvent, DragEndEvent,
@ -14,15 +16,16 @@ import {
useSensors, useSensors,
closestCenter, closestCenter,
} from '@dnd-kit/core'; } from '@dnd-kit/core';
import { Card as CardType, BoardWithDetails } from '../types/kanban'; import { Card as CardType } from '../types/kanban';
import { useState } from 'react'; import { useState } from 'react';
export function BoardDetail() { export function BoardDetail() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const { board, fetchBoard } = useBoard(parseInt(id || '0')); const { board, fetchBoard } = useBoard(parseInt(id || '0'));
const { moveCard } = useCardMutations(parseInt(id || '0'), fetchBoard); const { createCard, moveCard } = useCardMutations(parseInt(id || '0'), fetchBoard);
const { createList } = useListMutations(parseInt(id || '0'), fetchBoard); const { createList } = useListMutations(parseInt(id || '0'), fetchBoard);
const { openModal } = useModal();
const [activeCard, setActiveCard] = useState<CardType | null>(null); const [activeCard, setActiveCard] = useState<CardType | null>(null);
@ -36,11 +39,16 @@ export function BoardDetail() {
const handleDragStart = (event: DragStartEvent) => { const handleDragStart = (event: DragStartEvent) => {
const { active } = event; const { active } = event;
const cardId = parseInt(active.id as string);
const activeIdStr = (active.id as string).split('_')[1];
const cardId = parseInt(activeIdStr as string);
if (board) { if (board) {
const card = board.lists.flatMap((list) => list.cards).find((c) => c.id === cardId); const card = board.lists.flatMap((list) => list.cards).find((c) => c.id === cardId);
// console.log('---handleDragStart', event, card)
// console.log('---handleDragStart.board', board)
if (card) { if (card) {
setActiveCard(card); setActiveCard(card);
} }
@ -50,19 +58,24 @@ export function BoardDetail() {
const handleDragOver = (event: DragOverEvent) => { const handleDragOver = (event: DragOverEvent) => {
const { active, over } = event; const { active, over } = event;
console.log('---handleDragOver', event);
if (!over) return; if (!over) return;
const activeId = parseInt(active.id as string); // const activeId = parseInt(active.id as string);
const overId = parseInt(over.id as string); // const overId = parseInt(over.id as string);
const overIdStr = (over.id as string).split('_')[1];
const overId = parseInt(overIdStr, 10);
const activeIdStr = (active.id as string).split('_')[1];
const activeId = parseInt(activeIdStr, 10);
if (activeId === overId) return; if (activeId === overId) return;
// Find the active card and its current list // Find active card and its current list
if (!board) return; if (!board) return;
const activeList = board.lists.find((list) => list.cards.some((card) => card.id === activeId)); const activeList = board.lists.find((list) => list.cards.some((card) => card.id === activeId));
// If we're hovering over a card in the same list, do nothing // If we're hovering over a card in same list, do nothing
if (activeList) { if (activeList) {
const overCard = activeList.cards.find((card) => card.id === overId); const overCard = activeList.cards.find((card) => card.id === overId);
if (overCard) return; if (overCard) return;
@ -75,12 +88,17 @@ export function BoardDetail() {
if (!over || !board) return; if (!over || !board) return;
const activeId = parseInt(active.id as string); // console.log('--------------over', over)
const overId = parseInt(over.id as string); // console.log('--------------board', board)
const [overType, overIdStr] = (over.id as string).split('_');
const overId = parseInt(overIdStr, 10);
const activeIdStr = (active.id as string).split('_')[1];
const activeId = parseInt(activeIdStr, 10);
// debugger
if (activeId === overId) return; if (active.id === over.id) return;
// Find the active card // Find active card
let activeCard: CardType | undefined; let activeCard: CardType | undefined;
let activeList: (typeof board.lists)[0] | undefined; let activeList: (typeof board.lists)[0] | undefined;
@ -96,13 +114,14 @@ export function BoardDetail() {
if (!activeCard || !activeList) return; if (!activeCard || !activeList) return;
// Check if we're dropping on a list or a card // Check if we're dropping on a list or a card
// debugger
if (overType.toLocaleLowerCase() === 'list') {
const overList = board.lists.find((list) => list.id === overId); const overList = board.lists.find((list) => list.id === overId);
// Dropping on a list - append to end
if (overList) { if (!overList || overList.id === activeList.id) return; // Same list, do nothing
// Dropping on a list - append to the end
if (overList.id === activeList.id) return; // Same list, do nothing
await moveCard(activeCard.id, activeList.id, overList.id, overList.cards.length); await moveCard(activeCard, activeList.id, overList.id, overList.cards.length);
return; return;
} }
@ -124,7 +143,7 @@ export function BoardDetail() {
// Calculate new position // Calculate new position
const overCardIndex = overListContainingCard.cards.findIndex((c) => c.id === overId); const overCardIndex = overListContainingCard.cards.findIndex((c) => c.id === overId);
// If dropping on the same list and after the same card, do nothing // If dropping on to same list and after of same card, do nothing
if ( if (
overListContainingCard.id === activeList.id && overListContainingCard.id === activeList.id &&
overCardIndex === activeList.cards.findIndex((c) => c.id === activeId) + 1 overCardIndex === activeList.cards.findIndex((c) => c.id === activeId) + 1
@ -132,22 +151,33 @@ export function BoardDetail() {
return; return;
} }
await moveCard(activeCard.id, activeList.id, overListContainingCard.id, overCardIndex); await moveCard(activeCard, activeList.id, overListContainingCard.id, overCardIndex);
}; };
const handleCardClick = (card: CardType) => { const handleCardClick = (card: CardType) => {
navigate(`/boards/${id}/cards/${card.id}`); navigate(`/boards/${id}/cards/${card.id}`);
}; };
const handleAddList = async () => { const handleAddList = () => {
const listName = prompt('Enter list name:'); openModal((props) => (
if (listName && listName.trim()) { <CreateListModal
try { {...props}
const newList = await createList(listName.trim(), board ? board.lists.length : 0); onCreate={async (name) => {
} catch (err) { await createList(name, board ? board.lists.length : 0);
// Error handled by hook fetchBoard(); // Refresh board data
} }}
} />
));
};
const handleAddCard =
(listId: number) => async (data: { name: string; description?: string }) => {
await createCard(listId, {
name: data.name,
description: data.description,
pos: board ? board.lists.find((list) => list.id === listId)?.cards.length || 0 : 0,
});
fetchBoard(); // Refresh board data
}; };
if (!board) { if (!board) {
@ -194,6 +224,7 @@ export function BoardDetail() {
list={list} list={list}
cards={list.cards} cards={list.cards}
onCardClick={handleCardClick} onCardClick={handleCardClick}
onCardCreate={handleAddCard(list.id)}
/> />
))} ))}
</div> </div>

View file

@ -5,6 +5,7 @@ import { useNavigate, Link, useParams } from 'react-router-dom';
import { useBoard } from '../hooks/useBoard'; import { useBoard } from '../hooks/useBoard';
import { useBoards } from '../hooks/useBoards'; import { useBoards } from '../hooks/useBoards';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useToast } from '@/context/toasts';
const boardSchema = z.object({ const boardSchema = z.object({
name: z name: z
@ -22,6 +23,7 @@ export function BoardEdit() {
const { board } = useBoard(parseInt(id || '0')); const { board } = useBoard(parseInt(id || '0'));
const { updateBoard } = useBoards(); const { updateBoard } = useBoards();
const { addNotification } = useToast();
const { const {
register, register,
handleSubmit, handleSubmit,
@ -46,6 +48,13 @@ export function BoardEdit() {
navigate(`/boards/${id}`); navigate(`/boards/${id}`);
} catch (err) { } catch (err) {
// Error is handled by the hook // Error is handled by the hook
const errorMessage = err instanceof Error ? err.message : 'Failed to create card';
addNotification({
type: 'error',
title: 'Error Login',
message: errorMessage,
duration: 5000,
});
} }
}; };

View file

@ -1,18 +1,28 @@
import { useState } from 'react'; import { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth'; import { useAuth } from '../hooks/useAuth';
import { useToast } from '@/context/toasts';
export default function Login() { export default function Login() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const { login: handleLogin } = useAuth(); const { login: handleLogin } = useAuth();
const { addNotification } = useToast();
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
try { try {
await handleLogin(email, password); await handleLogin(email, password);
} catch (err) { } catch (err) {
// Error is handled by the hook (toast shown) // Error is handled by the hook (toast shown)
const errorMessage = err instanceof Error ? err.message : 'Failed to create card';
addNotification({
type: 'error',
title: 'Error Login',
message: errorMessage,
duration: 5000,
});
} }
}; };
@ -58,7 +68,7 @@ export default function Login() {
</form> </form>
<p className="mt-6 text-center text-gray-400"> <p className="mt-6 text-center text-gray-400">
Don't have an account? Don&apos;t have an account?
<Link to="/register" className="ml-2 text-blue-400 hover:text-blue-300"> <Link to="/register" className="ml-2 text-blue-400 hover:text-blue-300">
Register Register
</Link> </Link>

View file

@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth'; import { useAuth } from '../hooks/useAuth';
import { useToast } from '../context/toasts/useToast';
interface FormData { interface FormData {
email: string; email: string;
@ -22,6 +23,7 @@ export function Register() {
}); });
const { register: handleRegister } = useAuth(); const { register: handleRegister } = useAuth();
const { addNotification } = useToast();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ setFormData({
@ -50,7 +52,13 @@ export function Register() {
last_name: formData.last_name, last_name: formData.last_name,
}); });
} catch (err) { } catch (err) {
// Error is handled by the hook (toast shown) const errorMessage = err instanceof Error ? err.message : 'Failed to register';
addNotification({
type: 'error',
title: 'Registration Error',
message: errorMessage,
duration: 5000,
});
} }
}; };