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).
This commit is contained in:
2026-04-01 18:36:29 +11:00
parent 0a1f6b48a2
commit 7491e70a15
5 changed files with 90 additions and 15 deletions
+52 -2
View File
@@ -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<number[]>([]);
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({
/>
</div>
{/* Tags */}
{allTags.length > 0 && (
<div>
<label className="block text-xs text-zinc-500 mb-1">Tags (optional)</label>
<div className="flex flex-wrap gap-1.5">
{allTags.map((tag) => {
const selected = selectedTagIds.includes(tag.id);
return (
<button
key={tag.id}
type="button"
onClick={() => toggleTag(tag.id)}
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded text-xs border transition-colors ${
selected
? "border-transparent text-white"
: "border-zinc-600 text-zinc-400 hover:border-zinc-500 hover:text-zinc-300"
}`}
style={selected ? { backgroundColor: tag.color + "cc", borderColor: tag.color } : {}}
>
<span
className="w-1.5 h-1.5 rounded-full flex-shrink-0"
style={{ backgroundColor: tag.color }}
/>
{tag.name}
</button>
);
})}
</div>
</div>
)}
{/* Splits */}
<div>
<div className="flex items-center justify-between mb-1">