feat(profile): confirm a profile change before it is written - #688
Merged
Conversation
Saving a display name, username, profile picture, or minimum tip wrote straight through on the first tap. Each of the four is an overwrite with no undo, and the username is worse than the rest: changing a handle releases the old one, so someone else can claim it. Save now raises a confirmation first. `confirmProfileChange` builds all four from one template — "Change X?" / "Are you sure you want to permanently change your X?" / "Change X" / "Cancel" — with the username carrying an extra sentence about not getting the old handle back. Built on `.alert` rather than `.error`: the banner is red either way, but a user changing their own display name is not an error worth an analytics event. The prompt only fires on a change. "Permanently change your X" doesn't fit a first claim, and all four screens double as the profile checklist's setup step, where the button says Next rather than Save. Each gate reads the stored value and goes straight to the write when there isn't one. Minimum tip reads `minDmChatInitFee` off the profile rather than the screen's `existingFee`, which is scoped to the currency being entered — a fee set in another currency is still a fee being replaced. Its confirmation sits after the $1 floor check, so an amount the server would reject still gets the minimum-tip dialog instead of a confirm that then fails. Username is ordered the same way, behind the Too Short / Too Long / Invalid Characters rejections. Each screen's `errorDialog` state becomes `dialog`, since it now hosts a confirmation as well. Matches code-android-app#1363, which arrived at the same copy and the same change-only gate.
`EnterAmountView.Subtitle` gained a `hidden` case in cb79aa6, for screens that pass a header in place of the subtitle block. Two exhaustive switches in `WithdrawViewModelTests` were never updated, so the whole FlipcashTests target failed to compile and no test in it could run. Both switches already have an arm that records an unexpected subtitle; `hidden` joins it.
Collaborator
Author
|
Android side of this is code-payments/code-android-app#1363. Same four gates and the same copy, including the extra sentence on username. |
bmc08gt
added a commit
to code-payments/code-android-app
that referenced
this pull request
Aug 28, 2026
Saving a display name, username, profile picture, or minimum tip wrote straight through on the first tap. Each of those four is an overwrite with no undo, and changing a username also releases the old handle for anyone to claim. Each screen's Save button now dispatches a confirm event rather than the save event. The ViewModel gate shows a destructive alert whose single action dispatches the original save; Cancel leaves the entry as it is. The save pipelines themselves are unchanged. The prompt is only shown when there is a stored value to replace. A first claim overwrites nothing, and the name step is mandatory during onboarding, where a confirmation would sit on a screen that cannot be backed out of. Where a gate has something local to check, it checks first, so an entry that can never be saved says why instead of asking the user to confirm saving it. Username checks length ahead of the confirmation, matching iOS. Minimum tip asks after the below-minimum check, and its write moved to a CommitRequested event so it runs on viewModelScope rather than from the dialog callback. iOS equivalent: code-payments/code-ios-app#688
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.
Saving a display name, username, profile picture, or minimum tip wrote straight through on the first tap. Each of the four is an overwrite with no undo, and the username is worse than the rest: changing a handle releases the old one, so someone else can claim it.
Save now raises a confirmation first.
DialogItem.confirmProfileChangebuilds all four from one template — "Change X?" / "Are you sure you want to permanently change your X?" / "Change X" / "Cancel" — with the username carrying an extra sentence: "You might not be able to get your old username back".It is built on
.alert, not.error. The banner is red either way, but a user deliberately changing their own display name is not an error worth an analytics event.Two judgment calls worth a second opinion:
The prompt only fires on a change. "Permanently change your X" doesn't fit a first claim, and all four screens double as the profile checklist's setup step, where the button reads Next rather than Save. Each gate gets the stored value first —
currentUsername != nil, a non-emptydisplayName,profilePicture != nil,minDmChatInitFee != nil— and goes straight to the write when there isn't one."Profile Picture", not "Profile Photo". That matches the My Account row and the
Set Profile Picturescreen title.Minimum tip reads
minDmChatInitFeeoff the profile rather than the screen'sexistingFee, which is scoped to the currency being entered: a fee set in another currency is still a fee being replaced. Its confirmation also sits after the $1 floor check, so an amount the server would reject still gets the minimum-tip dialog instead of a confirm that then fails. Username is ordered the same way, behind Too Short / Too Long / Invalid Characters — that screen deliberately leaves Save live while the input is bad, so without the ordering a rejected handle would ask for confirmation first.Each screen's
errorDialogstate is renameddialog, since it now hosts a confirmation as well. The save pipelines are otherwise untouched; each screen just splits itssubmit()into a gate and the existing write.ProfileChangeDialogTestscovers the factory: the field naming across all four, the destructive-over-Cancel action order, that the handler runs on confirm and not on cancel, that only the username warns about losing the old value, and that the item is destructive but untracked.Parity
Android reached the same design independently in code-payments/code-android-app#1363 — same copy, same
showAlert-with-cancel styling, same change-only gate, same "Profile Picture" wording, and the same after-validation ordering for minimum tip.Second commit is unrelated
EnterAmountView.Subtitlegained ahiddencase in cb79aa6 and two exhaustive switches inWithdrawViewModelTestswere never updated, so the whole FlipcashTests target failed to compile and nothing in it could run.hiddenjoins the arm that already records an unexpected subtitle. Happy to split it out if you'd rather it landed on its own.