feat: update components for multi-user support
- Add sharing indicator to ListCard showing collaborator count - Add permission badge and share button to ListDetailView - Disable editing in ItemForm for viewers with visual feedback - Add permission checks to item detail and edit pages - Hide edit/delete buttons for viewers on item detail page - Pass permission and collaborator data through component hierarchy - Update navigation to include profile and invitations links (already done in previous tasks) Implements task 15 from user-auth-and-sharing spec
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getItem } from '@/lib/actions'
|
||||
import { canEditList } from '@/lib/permissions'
|
||||
import { auth } from '@/auth'
|
||||
import ItemForm from '@/components/ItemForm'
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
@@ -14,12 +16,20 @@ interface PageProps {
|
||||
}
|
||||
|
||||
export default async function EditItemPage({ params }: PageProps) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const item = await getItem(params.itemId)
|
||||
|
||||
if (!item || item.listId !== params.id) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
// Check if user can edit this list
|
||||
const canEdit = await canEditList(session.user.id, params.id)
|
||||
|
||||
return (
|
||||
<main className="container mx-auto px-4 sm:px-6 py-6 sm:py-8 max-w-3xl">
|
||||
{/* Header */}
|
||||
@@ -36,7 +46,7 @@ export default async function EditItemPage({ params }: PageProps) {
|
||||
|
||||
{/* Form */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-5 sm:p-6 lg:p-8">
|
||||
<ItemForm listId={params.id} item={item} mode="edit" />
|
||||
<ItemForm listId={params.id} item={item} mode="edit" canEdit={canEdit} />
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getItem } from '@/lib/actions'
|
||||
import { canEditList } from '@/lib/permissions'
|
||||
import { auth } from '@/auth'
|
||||
import ItemDetail from '@/components/ItemDetail'
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft, Edit } from 'lucide-react'
|
||||
@@ -16,12 +18,29 @@ interface PageProps {
|
||||
}
|
||||
|
||||
export default async function ItemDetailPage({ params }: PageProps) {
|
||||
const item = await getItem(params.itemId)
|
||||
|
||||
if (!item || item.listId !== params.id) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const itemData = await getItem(params.itemId)
|
||||
|
||||
if (!itemData || itemData.listId !== params.id) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
// Type assertion for the item with list relation
|
||||
const item = itemData as typeof itemData & {
|
||||
list: {
|
||||
title: string
|
||||
color: string
|
||||
ownerId: string
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user can edit this list
|
||||
const canEdit = await canEditList(session.user.id, params.id)
|
||||
|
||||
return (
|
||||
<main className="container mx-auto px-4 sm:px-6 py-6 sm:py-8 max-w-4xl">
|
||||
{/* Header Actions */}
|
||||
@@ -35,21 +54,40 @@ export default async function ItemDetailPage({ params }: PageProps) {
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-2 w-full sm:w-auto">
|
||||
<ToggleItemButton itemId={item.id} completed={item.completed} />
|
||||
<Link
|
||||
href={`/list/${params.id}/item/${params.itemId}/edit`}
|
||||
className="flex items-center gap-2 px-4 sm:px-5 py-2.5 sm:py-3 bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white rounded-lg transition-colors tap-target text-sm sm:text-base font-medium"
|
||||
>
|
||||
<Edit size={18} className="flex-shrink-0" />
|
||||
<span>Edit</span>
|
||||
</Link>
|
||||
<DeleteItemButton itemId={item.id} listId={params.id} />
|
||||
{canEdit && (
|
||||
<>
|
||||
<ToggleItemButton itemId={item.id} completed={item.completed} />
|
||||
<Link
|
||||
href={`/list/${params.id}/item/${params.itemId}/edit`}
|
||||
className="flex items-center gap-2 px-4 sm:px-5 py-2.5 sm:py-3 bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white rounded-lg transition-colors tap-target text-sm sm:text-base font-medium"
|
||||
>
|
||||
<Edit size={18} className="flex-shrink-0" />
|
||||
<span>Edit</span>
|
||||
</Link>
|
||||
<DeleteItemButton itemId={item.id} listId={params.id} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Item Detail */}
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-5 sm:p-6 lg:p-8">
|
||||
<ItemDetail item={item} listTitle={item.list.title} />
|
||||
<ItemDetail
|
||||
item={{
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
completed: item.completed,
|
||||
priority: item.priority,
|
||||
dueDate: item.dueDate,
|
||||
tags: item.tags,
|
||||
notes: item.notes,
|
||||
createdAt: item.createdAt,
|
||||
updatedAt: item.updatedAt,
|
||||
listId: item.listId,
|
||||
}}
|
||||
listTitle={item.list.title}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { getListWithItems } from '@/lib/actions'
|
||||
import { getPermission } from '@/lib/permissions'
|
||||
import { auth } from '@/auth'
|
||||
import { notFound } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
@@ -8,12 +10,25 @@ import ListDetailView from '@/components/ListDetailView'
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function ListDetailPage({ params }: { params: { id: string } }) {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const list = await getListWithItems(params.id)
|
||||
|
||||
if (!list) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
// Get user's permission for this list
|
||||
const permission = await getPermission(session.user.id, params.id)
|
||||
if (!permission) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const canShare = permission === 'owner'
|
||||
const canEdit = permission === 'owner' || permission === 'editor'
|
||||
const completedItems = list.items.filter(item => item.completed)
|
||||
|
||||
return (
|
||||
@@ -41,13 +56,28 @@ export default async function ListDetailPage({ params }: { params: { id: string
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Inline Item Creation */}
|
||||
<div className="mb-6 sm:mb-8">
|
||||
<InlineItemCreate listId={list.id} />
|
||||
</div>
|
||||
{/* Inline Item Creation - only show if user can edit */}
|
||||
{canEdit && (
|
||||
<div className="mb-6 sm:mb-8">
|
||||
<InlineItemCreate listId={list.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search, Filter, and Items List */}
|
||||
<ListDetailView listId={list.id} initialItems={list.items} />
|
||||
<ListDetailView
|
||||
listId={list.id}
|
||||
initialItems={list.items}
|
||||
permission={permission}
|
||||
canShare={canShare}
|
||||
listTitle={list.title}
|
||||
ownerId={list.ownerId}
|
||||
ownerName={list.owner?.name}
|
||||
ownerEmail={list.owner?.email || ''}
|
||||
collaborators={list.collaborators.map(c => ({
|
||||
...c,
|
||||
permission: c.permission as 'editor' | 'viewer'
|
||||
}))}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user