From 7491e70a1584c5d7dbc5283017e252632e309534 Mon Sep 17 00:00:00 2001 From: siddharthd Date: Wed, 1 Apr 2026 18:36:29 +1100 Subject: [PATCH] fix(participants): show Me contextually per logged-in user Participant id=1 was named "Me" in the DB, causing Sonu and other users to see "Me" referring to Siddharth when viewing splits and shared expenses. - Rename participant id=1 from "Me" to "Siddharth" in the DB - /api/participants now substitutes "Me" for whichever participant matches the current user, so the label is always relative to the viewer - split-modal: default split uses currentUser.id instead of name === "Me" - transactions/page: filter and display logic uses participant ID not name - shared/page: split chips show "Me" when participant_id === current user Also includes add-transaction-modal tags support (pre-existing staged change). --- src/app/api/participants/route.ts | 10 ++++- src/app/shared/page.tsx | 20 ++++++++- src/app/transactions/page.tsx | 7 +-- src/components/add-transaction-modal.tsx | 54 +++++++++++++++++++++++- src/components/split-modal.tsx | 14 +++--- 5 files changed, 90 insertions(+), 15 deletions(-) diff --git a/src/app/api/participants/route.ts b/src/app/api/participants/route.ts index 48f43c2..ee96bca 100644 --- a/src/app/api/participants/route.ts +++ b/src/app/api/participants/route.ts @@ -1,11 +1,17 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; -import { queryRaw } from "@/lib/db"; +import { getCurrentUser } from "@/lib/auth"; -export async function GET() { +export async function GET(req: NextRequest) { + const user = await getCurrentUser(req); const participants = await prisma.participants.findMany({ orderBy: { name: "asc" }, }); + if (user) { + return NextResponse.json( + participants.map((p) => (p.id === user.id ? { ...p, name: "Me" } : p)) + ); + } return NextResponse.json(participants); } diff --git a/src/app/shared/page.tsx b/src/app/shared/page.tsx index 1ecea8d..c377b8d 100644 --- a/src/app/shared/page.tsx +++ b/src/app/shared/page.tsx @@ -13,6 +13,7 @@ import { type SplitPayment, } from "@/lib/hooks"; import type { SharedTransactionRow } from "@/lib/queries"; +import { EditTransactionModal } from "@/components/edit-transaction-modal"; function formatDate(d: string) { return new Date(d).toLocaleDateString("en-AU", { day: "numeric", month: "short", year: "numeric" }); @@ -264,6 +265,7 @@ export default function SharedPage() { const [addingParticipant, setAddingParticipant] = useState(false); const [paymentModal, setPaymentModal] = useState<{ id: number; name: string; balance: number } | null>(null); const [showHistory, setShowHistory] = useState(null); + const [editModal, setEditModal] = useState(null); return (
@@ -352,6 +354,7 @@ export default function SharedPage() { Description Amount Splits + @@ -377,11 +380,19 @@ export default function SharedPage() { {splits.map((s) => ( - {s.name} {s.share_percent}% + {s.participant_id === me?.id ? "Me" : s.name} {s.share_percent}% ))}
+ + + ); })} @@ -399,6 +410,13 @@ export default function SharedPage() { onClose={() => setPaymentModal(null)} /> )} + + {editModal && ( + setEditModal(null)} + /> + )} ); } diff --git a/src/app/transactions/page.tsx b/src/app/transactions/page.tsx index 5d0e195..9826bdd 100644 --- a/src/app/transactions/page.tsx +++ b/src/app/transactions/page.tsx @@ -218,7 +218,7 @@ function MarkAsPaymentModal({ const { data: me } = useCurrentUser(); const record = useRecordPayment(); - const others = participants.filter((p) => p.name !== "Me"); + const others = participants.filter((p) => p.id !== me?.id); const [participantId, setParticipantId] = useState(others[0]?.id ?? ""); @@ -517,6 +517,7 @@ function TransactionsContent() { const { data, isLoading } = useTransactions(filters); const { data: banks } = useBanks(); const { data: tags } = useTags(); + const { data: me } = useCurrentUser(); const { data: statementInfo } = useStatement(parseInt(filters.statement_id) || 0); const updateTxn = useUpdateTransaction(); const bulkAction = useBulkAction(); @@ -851,7 +852,7 @@ function TransactionsContent() {
- {t.splits?.filter((s) => s.name !== "Me").map((s) => ( + {t.splits?.filter((s) => s.participant_id !== me?.id).map((s) => ( setSplitModal({ transactionId: t.id, amount: t.amount, description: t.description, merchant: t.effective_merchant || undefined, transactionIds: undefined })} className={`text-xs px-2 py-0.5 rounded transition-colors ${ - t.splits?.some((s) => s.name !== "Me") + t.splits?.some((s) => s.participant_id !== me?.id) ? "text-amber-400 hover:text-amber-200 hover:bg-zinc-800" : "text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800" }`} diff --git a/src/components/add-transaction-modal.tsx b/src/components/add-transaction-modal.tsx index 1a7cb00..3bc2be9 100644 --- a/src/components/add-transaction-modal.tsx +++ b/src/components/add-transaction-modal.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { useCreateTransaction, useParticipants } from "@/lib/hooks"; +import { useCreateTransaction, useParticipants, useTags } from "@/lib/hooks"; import { CATEGORIES, formatCategory } from "@/lib/categories"; const TRANSACTION_TYPES = ["debit", "credit", "payment", "refund", "fee", "interest", "transfer"]; @@ -27,6 +27,7 @@ export function AddTransactionModal({ }) { const createTransaction = useCreateTransaction(); const { data: participants = [] } = useParticipants(); + const { data: allTags = [] } = useTags(); const [date, setDate] = useState(prefill?.date ?? new Date().toISOString().slice(0, 10)); const [description, setDescription] = useState(prefill?.description ?? ""); @@ -34,6 +35,7 @@ export function AddTransactionModal({ const [type, setType] = useState(prefill?.transaction_type ?? "debit"); const [merchant, setMerchant] = useState(prefill?.merchant_normalized ?? ""); const [category, setCategory] = useState(prefill?.category ?? ""); + const [selectedTagIds, setSelectedTagIds] = useState([]); const [splits, setSplits] = useState<{ participant_id: number; share_percent: number }[]>( prefill?.splits ?? [] ); @@ -51,9 +53,15 @@ export function AddTransactionModal({ setSplits(splits.filter((_, idx) => idx !== i)); } + function toggleTag(id: number) { + setSelectedTagIds((prev) => + prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id] + ); + } + async function handleSubmit(e: React.FormEvent) { e.preventDefault(); - await createTransaction.mutateAsync({ + const result = await createTransaction.mutateAsync({ date, description, amount: parseFloat(amount), @@ -62,6 +70,17 @@ export function AddTransactionModal({ category: category || undefined, splits: splits.length ? splits : undefined, }); + if (selectedTagIds.length && result?.id) { + await Promise.all( + selectedTagIds.map((tagId) => + fetch(`/api/transactions/${result.id}/tags`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ tag_id: tagId }), + }) + ) + ); + } onClose(); } @@ -150,6 +169,37 @@ export function AddTransactionModal({ />
+ {/* Tags */} + {allTags.length > 0 && ( +
+ +
+ {allTags.map((tag) => { + const selected = selectedTagIds.includes(tag.id); + return ( + + ); + })} +
+
+ )} + {/* Splits */}
diff --git a/src/components/split-modal.tsx b/src/components/split-modal.tsx index 69ad892..b2f925c 100644 --- a/src/components/split-modal.tsx +++ b/src/components/split-modal.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; -import { useParticipants, useSetSplits, useTransactionSplits, useBulkAction, useCreateRule } from "@/lib/hooks"; +import { useParticipants, useSetSplits, useTransactionSplits, useBulkAction, useCreateRule, useCurrentUser } from "@/lib/hooks"; interface Split { participant_id: number; @@ -22,6 +22,7 @@ export function SplitModal({ transactionId, transactionIds, amount, description, const singleId = transactionId ?? 0; const { data: participants } = useParticipants(); + const { data: currentUser } = useCurrentUser(); const { data: existingSplits } = useTransactionSplits(isBulk ? 0 : singleId); const setSplits = useSetSplits(); const bulkAction = useBulkAction(); @@ -34,10 +35,9 @@ export function SplitModal({ transactionId, transactionIds, amount, description, // Initialise: bulk always defaults to 100% Me; single loads existing splits useEffect(() => { - if (!participants || participants.length === 0) return; - const me = participants.find((p) => p.name === "Me"); + if (!participants || participants.length === 0 || !currentUser) return; if (isBulk) { - if (me) setSplitsState([{ participant_id: me.id, share_percent: 100 }]); + setSplitsState([{ participant_id: currentUser.id, share_percent: 100 }]); } else if (existingSplits && existingSplits.length > 0) { setSplitsState( existingSplits.map((s: { participant_id: number; share_percent: number }) => ({ @@ -45,10 +45,10 @@ export function SplitModal({ transactionId, transactionIds, amount, description, share_percent: Number(s.share_percent), })) ); - } else if (me) { - setSplitsState([{ participant_id: me.id, share_percent: 100 }]); + } else { + setSplitsState([{ participant_id: currentUser.id, share_percent: 100 }]); } - }, [existingSplits, participants, isBulk]); + }, [existingSplits, participants, isBulk, currentUser]); const total = splits.reduce((sum, s) => sum + s.share_percent, 0);