Skip to content

Commit d5aff8e

Browse files
committed
Fix light account/backup notification banner display behavior
1 parent 46f8f21 commit d5aff8e

4 files changed

Lines changed: 61 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- changed: ramps: Infinite buy support according to new API
1212
- changed: Optimize login performance.
1313
- changed: Update Monero LWS server name to "Edge LWS"
14+
- fixed: Light account/backup reminder notification banner sometimes missing on login
1415
- fixed: ramps: Various Infinite UI/UX issues
1516

1617
## 4.41.1 (2025-12-29)

src/actions/LocalSettingsActions.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ watchAccountSettings(s => {
2525
})
2626

2727
let readSettingsFromDisk = false
28+
29+
/**
30+
* Resets the local account settings cache. Must be called on logout to prevent
31+
* one account's settings from persisting to a subsequent account's session.
32+
*/
33+
export const resetLocalAccountSettingsCache = (): void => {
34+
readSettingsFromDisk = false
35+
localAccountSettings = asLocalAccountSettings({})
36+
}
37+
2838
export const getLocalAccountSettings = async (
2939
account: EdgeAccount
3040
): Promise<LocalAccountSettings> => {
@@ -214,7 +224,12 @@ const writeAccountNotifState = async (
214224
const localSettings = await getLocalAccountSettings(account)
215225
return await writeLocalAccountSettings(account, {
216226
...localSettings,
217-
notifState
227+
// Merge with existing notifState to prevent concurrent writes from
228+
// overwriting each other's keys
229+
notifState: {
230+
...localSettings.notifState,
231+
...notifState
232+
}
218233
})
219234
}
220235

@@ -261,6 +276,13 @@ export const writeTokenWarningsShown = async (
261276
export const readLocalAccountSettings = async (
262277
account: EdgeAccount
263278
): Promise<LocalAccountSettings> => {
279+
// If we've already read from disk, return the cached settings.
280+
// This prevents stale disk reads from overwriting newer in-memory writes
281+
// that may not have been persisted to disk yet.
282+
if (readSettingsFromDisk) {
283+
return localAccountSettings
284+
}
285+
264286
try {
265287
const text = await account.localDisklet.getText(LOCAL_SETTINGS_FILENAME)
266288
const json = JSON.parse(text)
@@ -273,6 +295,7 @@ export const readLocalAccountSettings = async (
273295
// Defaults can be derived from cleaners. Only write when values change.
274296
const defaults = asLocalAccountSettings({})
275297
emitAccountSettings(defaults)
298+
readSettingsFromDisk = true
276299
return defaults
277300
}
278301
}

src/actions/LoginActions.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ import {
3939
getDeviceSettings,
4040
writeIsSurveyDiscoverShown
4141
} from './DeviceSettingsActions'
42-
import { readLocalAccountSettings } from './LocalSettingsActions'
42+
import {
43+
readLocalAccountSettings,
44+
resetLocalAccountSettingsCache
45+
} from './LocalSettingsActions'
4346
import {
4447
registerNotificationsV2,
4548
updateNotificationSettings
@@ -333,6 +336,7 @@ export function logoutRequest(
333336
const { account } = state.core
334337
Keyboard.dismiss()
335338
Airship.clear()
339+
resetLocalAccountSettingsCache()
336340

337341
dispatch({ type: 'LOGOUT' })
338342
if (typeof account.logout === 'function') await account.logout()

src/components/services/NotificationService.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
107107

108108
const wallets = useWatch(account, 'currencyWallets')
109109
const otpKey = useWatch(account, 'otpKey')
110+
const username = useWatch(account, 'username')
110111

111112
const detectedTokensRedux = useSelector(
112113
state => state.core.enabledDetectedTokens
@@ -124,7 +125,7 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
124125
// we only need referral-based promoId targeting.
125126
const accountFunded = useIsAccountFunded()
126127

127-
const isLightAccountReminder = account.id != null && account.username == null
128+
const isLightAccountReminder = account.id != null && username == null
128129

129130
const isOtpReminder =
130131
otpKey == null &&
@@ -146,6 +147,8 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
146147
// Update notification info with
147148
// 1. Date last received if transitioning from incomplete to complete
148149
// 2. Reset `isBannerHidden` if it's a new notification
150+
// All notification writes are in a single effect with sequential awaits to
151+
// prevent race conditions between concurrent writes.
149152
useAsyncEffect(
150153
async () => {
151154
// New token(s) detected
@@ -165,11 +168,6 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
165168
}
166169

167170
await updateNotificationInfo(account, 'ip2FaReminder', isIp2faReminder)
168-
await updateNotificationInfo(
169-
account,
170-
'lightAccountReminder',
171-
isLightAccountReminder
172-
)
173171
await updateNotificationInfo(
174172
account,
175173
'otpReminder',
@@ -198,6 +196,33 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
198196
)
199197
}
200198
}
199+
200+
// Handle lightAccountReminder separately using writeAccountNotifInfo
201+
// directly (not updateNotificationInfo) to ensure isBannerHidden is
202+
// always reset to false on login. This is placed last to avoid race
203+
// conditions with other notification writes above.
204+
if (isLightAccountReminder) {
205+
// Light account: always show the backup reminder banner on login
206+
await writeAccountNotifInfo(account, 'lightAccountReminder', {
207+
isPriority: true,
208+
isBannerHidden: false,
209+
isCompleted: false
210+
})
211+
} else {
212+
// Non-light account: mark complete if notification exists and isn't
213+
// already complete (handles upgrade from light account)
214+
const { notifState: currentNotifState } = await getLocalAccountSettings(
215+
account
216+
)
217+
const existing = currentNotifState.lightAccountReminder
218+
if (existing != null && !existing.isCompleted) {
219+
await writeAccountNotifInfo(account, 'lightAccountReminder', {
220+
isPriority: true,
221+
isBannerHidden: true,
222+
isCompleted: true
223+
})
224+
}
225+
}
201226
},
202227
[
203228
isIp2faReminder,
@@ -206,7 +231,6 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
206231
isPwReminder,
207232
wallets,
208233
detectedTokensRedux,
209-
notifState,
210234
accountReferral,
211235
accountReferralLoaded,
212236
countryCode,
@@ -215,21 +239,5 @@ export const NotificationService: React.FC<Props> = (props: Props) => {
215239
'NotificationServices'
216240
)
217241

218-
// Make sure the backup banner is always shown on login if needed. We do this
219-
// separately in this effect so that we can hide the banner during current
220-
// login session if they so choose.
221-
useAsyncEffect(
222-
async () => {
223-
if (!isLightAccountReminder) return
224-
225-
await writeAccountNotifInfo(account, 'lightAccountReminder', {
226-
isBannerHidden: false,
227-
isCompleted: false
228-
})
229-
},
230-
[isLightAccountReminder],
231-
'NotificationServices'
232-
)
233-
234242
return null
235243
}

0 commit comments

Comments
 (0)