From 1cdee92a7b64b39a4b24b0c43bddf7e677453ba2 Mon Sep 17 00:00:00 2001 From: Tom Unger Date: Sat, 5 Sep 2026 17:44:06 -0600 Subject: [PATCH 1/2] fix: explain why the people list is empty when adding someone The list of people to add rendered nothing at all when empty, under a box labelled "Enter name or email". The box looks like a search field but is a filter, so a new account -- which has no contacts, because contacts are derived from shared expenses -- saw a blank dialog, typed a name, watched nothing happen, and had no way to tell whether a list existed at all. Say why the list is empty instead, distinguishing the three reasons so each message is true: no contacts yet, the filter matched nobody, or everyone is already in the group. The reasoning is one shared function so the two dialogs cannot drift apart, and it stays silent while the query is loading rather than briefly claiming you have no contacts. The email button was the way out of the empty state, but it read "Add to Split Pro", which sounds like creating a new account and so gets skipped when the person already has one. Name it after what it does: "Add to Group" and "Add to Expense". Both changes apply to the group dialog and the add-expense picker, which had the same empty state -- a picture with no words -- and the same button. Translation keys are unchanged and only the English values move, since no fallbackLng is configured and a renamed key would surface as a raw key string in the other locales. Co-Authored-By: Claude Opus 5 --- public/locales/en/common.json | 13 ++++-- .../AddExpense/SelectUserOrGroup.tsx | 15 ++++++- src/components/group/AddMembers.tsx | 15 +++++++ src/lib/peopleList.test.ts | 40 +++++++++++++++++++ src/lib/peopleList.ts | 32 +++++++++++++++ 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 src/lib/peopleList.test.ts create mode 100644 src/lib/peopleList.ts diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 5ec3f196f..1e8fe5216 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -184,11 +184,11 @@ "description_placeholder": "Enter description", "pick_a_date": "Pick a date", "select_user_or_group": { - "add_to_split_pro": "Add to Split Pro", + "add_to_split_pro": "Add to Expense", "note": "Note: sending invite is disabled for now because of spam", "only_one_group_time": "You can have only one group at a time", "send_invite": "Send invite to user", - "warning": "Warning: Don't use send invite if it's invalid email. use add to Split Pro instead. Your account will be blocked if this feature is misused" + "warning": "Warning: Don't use send invite if it's invalid email. Use add to expense instead. Your account will be blocked if this feature is misused" }, "split_type_section": { "direction": { @@ -324,12 +324,12 @@ "no_members": { "add_members": "Add members", "add_members_details": { - "add_to_split_pro": "Add to Split Pro", + "add_to_split_pro": "Add to Group", "note": "Note: sending email invites is disabled", "placeholder": "Enter name or email", "send_invite": "Send invite to user", "title": "Add members", - "warning": "Warning: Don't use send invite if it's invalid email. use add to Split Pro instead. Your account will be blocked if this feature is misused" + "warning": "Warning: Don't use send invite if it's invalid email. Use add to group instead. Your account will be blocked if this feature is misused" }, "invite_link": "Invite link", "no_members": "No members in the group yet." @@ -441,6 +441,11 @@ "on": "on", "or": "or", "outstanding_balances": "Outstanding balances", + "people_list_empty": { + "no_contacts": "You have not shared expenses with anyone", + "no_matches": "No one matches that name", + "all_added": "Everyone you share expenses with is already in this group" + }, "total_balance": "Total balance", "select_currency": "Select currency", "select_balance": "Select balance", diff --git a/src/components/AddExpense/SelectUserOrGroup.tsx b/src/components/AddExpense/SelectUserOrGroup.tsx index 85ef2265b..df1c16c36 100644 --- a/src/components/AddExpense/SelectUserOrGroup.tsx +++ b/src/components/AddExpense/SelectUserOrGroup.tsx @@ -10,6 +10,7 @@ import { z } from 'zod'; import { useAddExpenseStore } from '~/store/addStore'; import { api } from '~/utils/api'; import { deserializeDefaultSplit } from '~/lib/defaultSplit'; +import { peopleListEmptyReason } from '~/lib/peopleList'; import { EntityAvatar } from '../ui/avatar'; import { Button } from '../ui/button'; @@ -45,6 +46,13 @@ export const SelectUserOrGroup: React.FC<{ (f.name ?? f.email)?.toLowerCase().includes(nameOrEmail.toLowerCase()), ); + /* Both friends and groups populate this list, so either one counts as having contacts. */ + const emptyReason = peopleListEmptyReason({ + isLoading: friendsQuery.isPending || groupsQuery.isPending, + hasAnyContacts: 0 < (friendsQuery.data?.length ?? 0) || 0 < (groupsQuery.data?.length ?? 0), + isFiltering: '' !== nameOrEmail.trim(), + }); + const onAddEmailClick = useCallback( (invite = false) => { if (isEmail.success) { @@ -212,8 +220,13 @@ export const SelectUserOrGroup: React.FC<{ ) : null} {0 === filteredFriends?.length && 0 === filteredGroups?.length ? ( -
+
empty user image + {emptyReason ? ( +

+ {t(`ui.people_list_empty.${emptyReason}`)} +

+ ) : null}
) : null}
diff --git a/src/components/group/AddMembers.tsx b/src/components/group/AddMembers.tsx index 0a101537d..f6a617b26 100644 --- a/src/components/group/AddMembers.tsx +++ b/src/components/group/AddMembers.tsx @@ -9,6 +9,7 @@ import { z } from 'zod'; import { Button } from '~/components/ui/button'; import { AppDrawer } from '~/components/ui/drawer'; +import { peopleListEmptyReason } from '~/lib/peopleList'; import { api } from '~/utils/api'; import { EntityAvatar } from '../ui/avatar'; @@ -45,6 +46,15 @@ const AddMembers: React.FC<{ (friend.name ?? friend.email)?.toLowerCase().includes(inputValue.toLowerCase()), ); + /* Says why the list is blank instead of rendering nothing, which reads as a broken dialog. */ + const emptyReason = filteredUsers?.length + ? null + : peopleListEmptyReason({ + isLoading: friendsQuery.isPending, + hasAnyContacts: 0 < (friendsQuery.data?.length ?? 0), + isFiltering: '' !== inputValue.trim(), + }); + function onUserSelect(userId: number) { setUserIds((prev) => ({ ...prev, [userId]: !prev[userId] })); } @@ -160,6 +170,11 @@ const AddMembers: React.FC<{
+ {emptyReason ? ( +

+ {t(`ui.people_list_empty.${emptyReason}`)} +

+ ) : null} {filteredUsers?.map((friend) => (