diff --git a/package-lock.json b/package-lock.json index 70bb59c..59906c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@prisma/client": "5.6.0", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", @@ -755,6 +756,37 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.7.tgz", + "integrity": "sha512-OcUN2FU0YpmajD/qkph3XzMcK/NmSk9hGWnjV68p6QiZMgILugusgQwnLSDs3oFSJYGKf3Y49zgFedhGh04k9A==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", diff --git a/package.json b/package.json index 409aa0c..d488774 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@prisma/client": "5.6.0", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", diff --git a/src/app/groups/[groupId]/edit/page.tsx b/src/app/groups/[groupId]/edit/page.tsx index 1fcf225..be1a5a8 100644 --- a/src/app/groups/[groupId]/edit/page.tsx +++ b/src/app/groups/[groupId]/edit/page.tsx @@ -1,5 +1,5 @@ import { GroupForm } from '@/components/group-form' -import { getGroup, updateGroup } from '@/lib/api' +import { getGroup, getGroupExpensesParticipants, updateGroup } from '@/lib/api' import { groupFormSchema } from '@/lib/schemas' import { Metadata } from 'next' import { notFound, redirect } from 'next/navigation' @@ -23,5 +23,12 @@ export default async function EditGroupPage({ redirect(`/groups/${group.id}`) } - return + const protectedParticipantIds = await getGroupExpensesParticipants(groupId) + return ( + + ) } diff --git a/src/components/group-form.tsx b/src/components/group-form.tsx index d2140f0..7bf0f8e 100644 --- a/src/components/group-form.tsx +++ b/src/components/group-form.tsx @@ -18,6 +18,11 @@ import { FormLabel, FormMessage, } from '@/components/ui/form' +import { + HoverCard, + HoverCardContent, + HoverCardTrigger, +} from '@/components/ui/hover-card' import { Input } from '@/components/ui/input' import { getGroup } from '@/lib/api' import { GroupFormValues, groupFormSchema } from '@/lib/schemas' @@ -28,9 +33,14 @@ import { useFieldArray, useForm } from 'react-hook-form' export type Props = { group?: NonNullable>> onSubmit: (groupFormValues: GroupFormValues) => Promise + protectedParticipantIds?: string[] } -export function GroupForm({ group, onSubmit }: Props) { +export function GroupForm({ + group, + onSubmit, + protectedParticipantIds = [], +}: Props) { const form = useForm({ resolver: zodResolver(groupFormSchema), defaultValues: group @@ -48,6 +58,7 @@ export function GroupForm({ group, onSubmit }: Props) { const { fields, append, remove } = useFieldArray({ control: form.control, name: 'participants', + keyName: 'key', }) return ( @@ -115,7 +126,7 @@ export function GroupForm({ group, onSubmit }: Props) { -
    +
      {fields.map((item, index) => (
    • - + {item.id && + protectedParticipantIds.includes(item.id) ? ( + + + + + + This participant is part of expenses, and can + not be removed. + + + ) : ( + + )}
      diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx new file mode 100644 index 0000000..e54d91c --- /dev/null +++ b/src/components/ui/hover-card.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as HoverCardPrimitive from "@radix-ui/react-hover-card" + +import { cn } from "@/lib/utils" + +const HoverCard = HoverCardPrimitive.Root + +const HoverCardTrigger = HoverCardPrimitive.Trigger + +const HoverCardContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + +)) +HoverCardContent.displayName = HoverCardPrimitive.Content.displayName + +export { HoverCard, HoverCardTrigger, HoverCardContent } diff --git a/src/lib/api.ts b/src/lib/api.ts index 4ae88ef..2968cae 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -66,6 +66,18 @@ export async function deleteExpense(expenseId: string) { }) } +export async function getGroupExpensesParticipants(groupId: string) { + const expenses = await getGroupExpenses(groupId) + return Array.from( + new Set( + expenses.flatMap((e) => [ + e.paidById, + ...e.paidFor.map((pf) => pf.participantId), + ]), + ), + ) +} + export async function updateExpense( groupId: string, expenseId: string,