Skip to content

feat(profile): confirm a profile change before it is written - #688

Merged
bmc08gt merged 2 commits into
mainfrom
feat/profile-change-confirmation
Aug 28, 2026
Merged

feat(profile): confirm a profile change before it is written#688
bmc08gt merged 2 commits into
mainfrom
feat/profile-change-confirmation

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

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.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: "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-empty displayName, 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 Picture screen title.

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 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 errorDialog state is renamed dialog, since it now hosts a confirmation as well. The save pipelines are otherwise untouched; each screen just splits its submit() into a gate and the existing write.

ProfileChangeDialogTests covers 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.Subtitle gained a hidden case in cb79aa6 and two exhaustive switches in WithdrawViewModelTests were never updated, so the whole FlipcashTests target failed to compile and nothing in it could run. hidden joins the arm that already records an unexpected subtitle. Happy to split it out if you'd rather it landed on its own.

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.
@bmc08gt

bmc08gt commented Aug 28, 2026

Copy link
Copy Markdown
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
@bmc08gt
bmc08gt merged commit 665ec96 into main Aug 28, 2026
@bmc08gt
bmc08gt deleted the feat/profile-change-confirmation branch August 28, 2026 21:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant