diff --git a/actions/copy-card/index.ts b/actions/copy-card/index.ts index 5f44e5d..ce54f0b 100644 --- a/actions/copy-card/index.ts +++ b/actions/copy-card/index.ts @@ -29,6 +29,13 @@ const handler = async (data: InputType): Promise => { }, }, }, + select: { + id: true, + title: true, + description: true, + order: true, + listId: true, + }, }); if (!cardToCopy) return { error: 'Card not found' }; diff --git a/actions/copy-list/index.ts b/actions/copy-list/index.ts index a4892a6..164c683 100644 --- a/actions/copy-list/index.ts +++ b/actions/copy-list/index.ts @@ -28,8 +28,18 @@ const handler = async (data: InputType): Promise => { orgId, }, }, - include: { - cards: true, + select: { + id: true, + title: true, + order: true, + boardId: true, + cards: { + select: { + title: true, + description: true, + order: true, + }, + }, }, }); diff --git a/actions/create-board/index.ts b/actions/create-board/index.ts index 9e667b5..3510c79 100644 --- a/actions/create-board/index.ts +++ b/actions/create-board/index.ts @@ -77,6 +77,17 @@ const handler = async (data: InputType): Promise => { imageLinkHTML, imageDownloadUrl, }, + select: { + id: true, + title: true, + orgId: true, + imageId: true, + imageThumbUrl: true, + imageFullUrl: true, + imageUserName: true, + imageLinkHTML: true, + imageDownloadUrl: true, + }, }); if (!isPro) { diff --git a/actions/create-card/index.ts b/actions/create-card/index.ts index 698d6a1..8af4aeb 100644 --- a/actions/create-card/index.ts +++ b/actions/create-card/index.ts @@ -27,6 +27,9 @@ const handler = async (data: InputType): Promise => { orgId, }, }, + select: { + id: true, + }, }); if (!list) return { error: 'List not found' }; diff --git a/actions/create-list/index.ts b/actions/create-list/index.ts index f52082f..ece4164 100644 --- a/actions/create-list/index.ts +++ b/actions/create-list/index.ts @@ -25,6 +25,9 @@ const handler = async (data: InputType): Promise => { id: boardId, orgId, }, + select: { + id: true, + }, }); if (!board) return { error: 'Board not found' }; diff --git a/actions/delete-board/index.ts b/actions/delete-board/index.ts index 9a09943..7569d14 100644 --- a/actions/delete-board/index.ts +++ b/actions/delete-board/index.ts @@ -30,6 +30,10 @@ const handler = async (data: InputType): Promise => { id, orgId, }, + select: { + id: true, + title: true, + }, }); if (!isPro) { diff --git a/actions/delete-card/index.ts b/actions/delete-card/index.ts index 4b4913a..d53e82f 100644 --- a/actions/delete-card/index.ts +++ b/actions/delete-card/index.ts @@ -29,6 +29,10 @@ const handler = async (data: InputType): Promise => { }, }, }, + select: { + id: true, + title: true, + }, }); await createAuditLog({ diff --git a/actions/delete-list/index.ts b/actions/delete-list/index.ts index b29d5fb..037ef3b 100644 --- a/actions/delete-list/index.ts +++ b/actions/delete-list/index.ts @@ -28,6 +28,10 @@ const handler = async (data: InputType): Promise => { orgId, }, }, + select: { + id: true, + title: true, + }, }); await createAuditLog({ diff --git a/actions/stripe-redirect/index.ts b/actions/stripe-redirect/index.ts index 47fbcab..c3395a3 100644 --- a/actions/stripe-redirect/index.ts +++ b/actions/stripe-redirect/index.ts @@ -24,6 +24,9 @@ const handler = async (data: InputType): Promise => { try { const orgSubscription = await db.orgSubscription.findUnique({ where: { orgId }, + select: { + stripeCustomerId: true, + }, }); if (orgSubscription?.stripeCustomerId) { diff --git a/actions/update-board/index.ts b/actions/update-board/index.ts index 7220098..56138f5 100644 --- a/actions/update-board/index.ts +++ b/actions/update-board/index.ts @@ -60,6 +60,17 @@ const handler = async (data: InputType): Promise => { imageUserName, imageDownloadUrl, }, + select: { + id: true, + title: true, + orgId: true, + imageId: true, + imageThumbUrl: true, + imageFullUrl: true, + imageUserName: true, + imageLinkHTML: true, + imageDownloadUrl: true, + }, }); await createAuditLog({ diff --git a/actions/update-card-order/index.ts b/actions/update-card-order/index.ts index 65b67d0..880a561 100644 --- a/actions/update-card-order/index.ts +++ b/actions/update-card-order/index.ts @@ -32,6 +32,11 @@ const handler = async (data: InputType): Promise => { order: card.order, listId: card.listId, }, + select: { + id: true, + order: true, + listId: true, + }, }) ); diff --git a/actions/update-card/index.ts b/actions/update-card/index.ts index d2560dc..27aee27 100644 --- a/actions/update-card/index.ts +++ b/actions/update-card/index.ts @@ -34,6 +34,15 @@ const handler = async (data: InputType): Promise => { dueDate: dueDate, startedAt: startedAt, }, + select: { + id: true, + title: true, + description: true, + order: true, + listId: true, + dueDate: true, + startedAt: true, + }, }); await createAuditLog({ diff --git a/actions/update-list/index.ts b/actions/update-list/index.ts index 684ab2c..a6ffc52 100644 --- a/actions/update-list/index.ts +++ b/actions/update-list/index.ts @@ -31,6 +31,11 @@ const handler = async (data: InputType): Promise => { data: { title, }, + select: { + id: true, + title: true, + boardId: true, + }, }); await createAuditLog({ diff --git a/app/(platform)/(dashboard)/board/[boardId]/_components/board-update-image.tsx b/app/(platform)/(dashboard)/board/[boardId]/_components/board-update-image.tsx index 6c092d1..0902c41 100644 --- a/app/(platform)/(dashboard)/board/[boardId]/_components/board-update-image.tsx +++ b/app/(platform)/(dashboard)/board/[boardId]/_components/board-update-image.tsx @@ -1,78 +1,98 @@ -import { X } from 'lucide-react'; -import { toast } from 'sonner'; -import { useRef } from 'react'; -import Link from 'next/link'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; -import { Button } from '@/components/ui/button'; -import { - Popover, - PopoverClose, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/popover'; -import { FormPicker } from '@/components/form/form-picker'; -import { FormSubmit } from '@/components/form/form-submit'; -import { useAction } from '@/hooks/use-action'; import { updateBoard } from '@/actions/update-board'; +import { Button } from '@/components/ui/button'; +import { Form, FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useToast } from '@/components/ui/use-toast'; +import { cn } from '@/lib/utils'; +import { images } from '@/constants/images'; + +const formSchema = z.object({ + image: z.string().min(1, { + message: 'Image is required', + }), +}); interface BoardUpdateImageProps { boardId: string; + image: string; } -export const BoardUpdateImage = ({ boardId }: BoardUpdateImageProps) => { - const closeRef = useRef(null); +export function BoardUpdateImage({ boardId, image }: BoardUpdateImageProps) { + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + const router = useRouter(); - const { execute, fieldErrors } = useAction(updateBoard, { - onSuccess: (data) => { - toast.success('Board image updated'); - closeRef.current?.click(); - }, - onError: (error) => { - toast.error(error); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + image, }, }); - const onSubmit = (formData: FormData) => { - const image = formData.get('image') as string; + async function onSubmit(values: z.infer) { + setIsLoading(true); - execute({ id: boardId, image }); - }; + const response = await updateBoard({ + id: boardId, + image: values.image, + }); + + setIsLoading(false); + + if (response?.error) { + return toast({ + title: 'Something went wrong.', + description: response.error, + variant: 'destructive', + }); + } + + toast({ + description: 'Board image updated.', + }); + + router.refresh(); + } return ( - - - - - - - - - -
-

- Images Provided by{' '} - - Unsplash - -

- -
- Update - -
-
+ + ); -}; +} diff --git a/app/(platform)/(dashboard)/board/[boardId]/_components/list-item.tsx b/app/(platform)/(dashboard)/board/[boardId]/_components/list-item.tsx index c65dc72..7f4c555 100644 --- a/app/(platform)/(dashboard)/board/[boardId]/_components/list-item.tsx +++ b/app/(platform)/(dashboard)/board/[boardId]/_components/list-item.tsx @@ -1,76 +1,138 @@ -'use client'; +import { Card } from '@prisma/client'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { LazyLoad } from 'react-lazyload'; -import { useRef, useState } from 'react'; -import { Draggable, Droppable } from '@hello-pangea/dnd'; - -import { ListWithCards } from '@/types'; +import { updateCard } from '@/actions/update-card'; +import { deleteCard } from '@/actions/delete-card'; +import { Button } from '@/components/ui/button'; +import { Form, FormControl, FormField, FormItem, FormLabel } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useToast } from '@/components/ui/use-toast'; import { cn } from '@/lib/utils'; -import { ListHeader } from './list-header'; -import { CardForm } from './card-form'; -import { CardItem } from './card-item'; +const formSchema = z.object({ + title: z.string().min(1, { + message: 'Title is required', + }), +}); interface ListItemProps { - data: ListWithCards; - index: number; + card: Card; } -export const ListItem = ({ index, data }: ListItemProps) => { - const textareaRef = useRef(null); - +export function ListItem({ card }: ListItemProps) { const [isEditing, setIsEditing] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { toast } = useToast(); + const router = useRouter(); - const disableEditing = () => { - setIsEditing(false); - }; + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: card.title, + }, + }); - const enableEditing = () => { - setIsEditing(true); - setTimeout(() => { - textareaRef.current?.focus(); + async function onSubmit(values: z.infer) { + setIsLoading(true); + + const response = await updateCard({ + id: card.id, + title: values.title, + boardId: card.boardId, }); - }; + + setIsLoading(false); + + if (response?.error) { + return toast({ + title: 'Something went wrong.', + description: response.error, + variant: 'destructive', + }); + } + + toast({ + description: 'Card updated.', + }); + + setIsEditing(false); + router.refresh(); + } + + async function onDelete() { + setIsLoading(true); + + const response = await deleteCard({ + id: card.id, + boardId: card.boardId, + }); + + setIsLoading(false); + + if (response?.error) { + return toast({ + title: 'Something went wrong.', + description: response.error, + variant: 'destructive', + }); + } + + toast({ + description: 'Card deleted.', + }); + + router.refresh(); + } return ( - - {(provided) => ( -
  • -
    - - - {(provided) => ( -
      0 ? 'mt-2' : 'mt-0' - )} - > - {data.cards.map((card, index) => ( - - ))} - {provided.placeholder} -
    - )} -
    - + +
    + {isEditing ? ( +
    + + ( + + Title + + + + + )} + /> +
    + + +
    + + + ) : ( +
    + {card.title} +
    + + +
    -
  • - )} -
    + )} + + ); -}; +} diff --git a/next.config.ts b/next.config.ts index bc531b0..f890f99 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,6 @@ import { withSentryConfig } from '@sentry/nextjs'; import type { NextConfig } from 'next'; +import compression from 'compression'; import { withContentCollections } from '@content-collections/next'; import createMDX from '@next/mdx'; @@ -23,6 +24,10 @@ const nextConfig: NextConfig = { ], }, pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'], + compress: true, + serverMiddleware: [ + compression() + ], }; const withMDX = createMDX({}); diff --git a/package.json b/package.json index 60dd646..d783a6d 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@vercel/speed-insights": "^1.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "compression": "^1.7.4", "date-fns": "^4.1.0", "dompurify": "^3.2.3", "lodash": "^4.17.21", @@ -48,6 +49,7 @@ "react": "^19.0.0", "react-day-picker": "^9.4.4", "react-dom": "^19.0.0", + "react-lazyload": "^3.2.0", "sharp": "^0.33.5", "sonner": "^1.7.1", "stripe": "^17.4.0",