feat: local friends, full data export/restore & Splitwise Pro import#695
Open
xHecktor wants to merge 53 commits into
Open
feat: local friends, full data export/restore & Splitwise Pro import#695xHecktor wants to merge 53 commits into
xHecktor wants to merge 53 commits into
Conversation
* test: check write access * feat: add local friends (name-only) and rename support - Allow adding friends by name only (without email) in all three flows: expense dialog, group member picker, and friend list - Add RenameFriend component (AlertDialog) on the friend detail page, visible only for local users without an email address - Add updateFriendName tRPC mutation; authorization uses shared ExpenseParticipant records so renaming works even after settle-up or when the friend was just added but no expense exists yet - Server-side guard prevents renaming registered users (linked OAuth account or verified email) * ci: add Docker Hub build and push workflow * refactor: move rename into settings drawer, remove separate pencil button
* feat: merge local friend into registered account * feat: merge local friend into registered account
…R, es-MX, fr, he, hi, hu, it)
…Portuguese locale
…merge in all 21 locales (#3) Co-authored-by: Claude <noreply@anthropic.com>
- Export now includes individual expenses with all participants - New importSplitProData mutation maps users by email, creates local placeholders for unknown users, and skips duplicate expenses by ID - Import page at /import-splitpro with file preview before importing - Auto-match at login: when a new user signs in, any local placeholder with the same email is merged into the new account automatically - All 23 locales updated with new translation keys Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX Co-authored-by: Claude <noreply@anthropic.com>
…match Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
When an expense already exists but a participant was deleted (e.g. local user removed), re-add the missing ExpenseParticipant rows so balances are correctly restored. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
- Detect Splitwise Pro backup format (has repayments in expenses) - Reconstruct expense participants from repayments - Map users by email, create local placeholders for unknown users - Create groups from backup if they don't exist yet - Deterministic UUID from Splitwise expense ID prevents duplicates on re-import - Legacy balance-only import mode still works unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
Large backups (1334 expenses) exceed the tRPC JSON body limit. Instead upload the file directly to a dedicated API route that parses it server-side. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
Users referenced in expenses but not in friends list are now created as local placeholders using their name from the group member data. Re-importing is still safe — existing users are matched by name. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
For regular expenses, 'to' in repayments = who paid the bill. For payment expenses, 'from' = who sent the settlement money. Using wrong direction caused incorrect balances for settled friends. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QEcTdEHzqDa6ubbvaRz1tX
VND, JPY, KRW and other zero-decimal currencies must not be multiplied by 100 when converting amounts to internal storage, as they have no subunits. Also repairs oxlint-corrupted code patterns from previous commit attempts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Splitwise repayments use the same from/to semantics for both regular expenses and payments: from=debtor, to=creditor. The previous change incorrectly swapped these for payment expenses, causing ~10x balance errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Imported users have an email address but no linked account and no emailVerified flag. The previous checks treated any user with an email as a registered user, blocking rename/merge in both UI and backend. - Frontend: show rename/merge options when accounts.length == 0 and emailVerified is null, regardless of email presence - Backend getFriend: include accounts so the frontend can check - Backend mergeLocalFriend: drop the email fallback from the local-user guard; only accounts.length and emailVerified matter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Adds a choice between two import modes on the import page: - Merge (default): new data is added, existing expenses are kept - Restore: all existing expenses and groups of the current user are deleted first, then the backup is imported cleanly The restore function removes all expenses the user is involved in (as payer or participant), cleans up group memberships, and then calls the existing importSplitProData logic. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Replace hardcoded German strings with i18n keys for the SplitPro import mode selection (merge vs restore). Adds translations for all 23 locales. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
When two exported user IDs map to the same DB user ID, createMany would fail with a unique constraint error on (expenseId, userId). Using skipDuplicates silently ignores these conflicts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
When two exported user IDs map to the same DB user ID (e.g. after a user merge), the participants array could contain duplicate userIds, causing a unique constraint violation on (expenseId, userId). Deduplicate by userId using a Map before the expense create call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
When importing with restore mode, group memberships (groupUsers) were deleted but not re-created for groups that already existed (matched by publicId). Now restores missing member entries on existing groups. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Both import flows now stream real-time progress via Server-Sent Events: - New SSE API routes: /api/import-splitpro-stream, /api/import-splitwise-stream - ImportLogWindow component shows timestamped log entries with color coding - useImportStream hook manages SSE stream reading and log state - Service functions accept optional onLog callback for progress/error reporting - Logs show user mapping, group creation, per-100 expense progress, and errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Next.js compress middleware buffers the entire response, preventing SSE events from reaching the client in real-time. Disable compress globally (reverse proxy handles compression in production), add explicit flush() after each write, and set anti-buffering headers (X-Accel-Buffering, Content-Encoding, no-transform). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Extract shared SSE setup into sseHelper. Send an SSE comment (: ping) every 5 seconds to keep connections alive through any reverse proxy (nginx, Apache, Traefik, Synology Web Station etc.) and force buffer flushes even on proxies that ignore X-Accel-Buffering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Remove automatic redirect to /balances after import finishes so users can review the log before navigating away manually. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
Use raw ServerResponse.writeHead() to write SSE headers directly, bypassing the Next.js compression middleware. This keeps gzip compression for all normal pages while allowing SSE to stream unbuffered. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
- Log why each expense was skipped (already exists / unknown payer) - Include expense ID in skip messages for debugging - Warn when multiple exported user IDs map to same DB user (merged accounts) so the deduplication is visible Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
…ut DB access - Show amount, currency, date, and group name for skipped/failed expenses - Show user names instead of raw IDs in dedup warnings - Include DB user IDs in user mapping logs for traceability - Show restored member names in group restoration logs - Upgrade missing-user expense skips from warn to error level - Add human-readable context to Splitwise import logs too Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
User logs now include export IDs and DB IDs, expense logs include UUIDs (SplitPro) or Splitwise IDs with mapped UUIDs, group logs include publicId/SW ID and DB ID. Human-readable names are kept alongside for frontend readability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
When a SplitPro export is uploaded on the Splitwise import page (or vice versa), show a toast explaining which import page to use instead of silently falling through to an incompatible import mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
In merge mode, both import pages now show checkboxes for each group, allowing users to selectively import only specific groups and their expenses. All groups are selected by default with a select/deselect all toggle. Restore mode continues to import everything. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
- Replace single export link with two-option layout on Splitwise import page: option 1 (balance-only via external link) and option 2 (full backup via Splitwise Pro) - Increase wrong-file-format toast error duration to 10s - Add translations for all 6 new keys in all 23 locale files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019wMSFyaUDFVGr31qqm7tk7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Summary
This PR bundles three related additions that make SplitPro usable without
forcing every participant to have an account, and that make moving data
in/out of SplitPro robust:
progress.
Related to #683 (add local/non-registered users)
Related to #307 (Splitwise import 2.0)
Related to #302 (export group data)
1. Local friends
(
AddMembers,UserInput,SelectUserOrGroup).RenameFriendcomponent +updateFriendNamemutation, from the friend balance screen.
MergeLocalFriendcomponent +mergeLocalFriendmutation; moves allexpense participations to the target user and removes the placeholder.
2. Full data export & restore
downloadDatanow returns a complete export (users, groups, expenses,participants) via
getFullExportData.selection.
3. Splitwise Pro import
and per-expense participants reconstructed from Splitwise
repayments.(and vice-versa) shows a clear error toast instead of failing silently.
link vs. full Splitwise Pro backup).
Shared infrastructure
(
import-splitpro-stream,import-splitwise-stream) on a smallsseHelper(keep-alive pings, compression bypass), surfaced through theuseImportStreamhook +ImportLogWindow.auto-matched to the real account by email (
server/auth.ts).Checklist