Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
45323d4
Feature/move history fetch to bg (#2273)
piyalbasu Oct 7, 2025
b61ec65
check for updated appdata before showing password modal (#2300)
piyalbasu Oct 8, 2025
91a49a5
stringify errors rather than using `cause` (#2302)
piyalbasu Oct 9, 2025
cc130fc
Feature/move icons to own hook (#2308)
piyalbasu Oct 15, 2025
c50cbc1
skip blockaid scan on first fetch of account-balances (#2310)
piyalbasu Oct 17, 2025
89b00b0
Merge branch 'master' into release/5.36.0
piyalbasu Oct 23, 2025
47203df
Dropdown menu option to copy wallet address (#2316)
leofelix077 Oct 27, 2025
3da9b6b
scroll on long strings; pretty print json (#2320)
piyalbasu Oct 27, 2025
9218e3f
Merge branch 'master' into release/5.36.0
aristidesstaffieri Oct 29, 2025
077117e
re-searching so should abort any in flight API requests (#2323)
piyalbasu Oct 30, 2025
45f8bd6
[FEATURE] new send/swap navigation flow (#2353)
aristidesstaffieri Nov 5, 2025
1779c51
[FEATURE] adds send and swap buttons to asset detail view (#2351)
aristidesstaffieri Nov 7, 2025
5199754
only fetch asset list data if needed (#2369)
piyalbasu Nov 7, 2025
cdafa95
[BUG] SAC token management improvements (#2374)
aristidesstaffieri Nov 10, 2025
85014fb
Merge branch 'master' into release/5.36.0
piyalbasu Nov 11, 2025
5e591ce
Feature/cache token prices (#2373)
piyalbasu Nov 11, 2025
6268562
load backend settings async on Account view (#2381)
piyalbasu Nov 13, 2025
4f2b5c6
Feature/use ledger key for home domains (#2363)
piyalbasu Nov 14, 2025
9a0dd5e
update version numbers for release
piyalbasu Nov 17, 2025
6817e58
add v1 of dismissable app promo banner
leofelix077 Nov 19, 2025
0aed2a4
rm unnecessary calls to make flows even faster (#2391)
piyalbasu Nov 19, 2025
1e8117a
makes send swap buttons stay in the container in full screen mode (#2…
piyalbasu Nov 19, 2025
bd6034d
Merge branch 'master' into release/5.36.0
piyalbasu Nov 21, 2025
c559337
send app promotion store messages to background
leofelix077 Nov 21, 2025
1f3710d
use redux selector for allAccounts to properly update rename (#2403)
piyalbasu Nov 21, 2025
192ce88
merge 5.36 into branch
leofelix077 Nov 21, 2025
924d488
adjust unified package imports
leofelix077 Nov 21, 2025
1e98b16
Merge branch 'release/5.37.0' of github.com:stellar/freighter into fe…
leofelix077 Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions @shared/api/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,30 @@ export const getTokenIds = async ({
return tokenIdList;
};

export const getMobileAppBannerDismissed = async (): Promise<boolean> => {
const { isDismissed, error } = await sendMessageToBackground({
activePublicKey: null,
type: SERVICE_TYPES.GET_MOBILE_APP_BANNER_DISMISSED,
});

if (error) {
return false;
}

return !!isDismissed;
};

export const dismissMobileAppBanner = async (): Promise<void> => {
const { error } = await sendMessageToBackground({
activePublicKey: null,
type: SERVICE_TYPES.DISMISS_MOBILE_APP_BANNER,
});

if (error) {
throw new Error(error);
}
};

export const removeTokenId = async ({
activePublicKey,
contractId,
Expand Down
12 changes: 11 additions & 1 deletion @shared/api/types/message-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ export interface GetHiddenAssetsMessage extends BaseMessage {
type: SERVICE_TYPES.GET_HIDDEN_ASSETS;
}

export interface GetMobileAppBannerDismissedMessage extends BaseMessage {
type: SERVICE_TYPES.GET_MOBILE_APP_BANNER_DISMISSED;
}

export interface DismissMobileAppBannerMessage extends BaseMessage {
type: SERVICE_TYPES.DISMISS_MOBILE_APP_BANNER;
}

export type ServiceMessageRequest =
| FundAccountMessage
| CreateAccountMessage
Expand Down Expand Up @@ -407,4 +415,6 @@ export type ServiceMessageRequest =
| ModifyAssetsListMessage
| GetIsAccountMismatchMessage
| ChangeAssetVisibilityMessage
| GetHiddenAssetsMessage;
| GetHiddenAssetsMessage
| GetMobileAppBannerDismissedMessage
| DismissMobileAppBannerMessage;
1 change: 1 addition & 0 deletions @shared/api/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface Response {
};
hiddenAssets: Record<IssuerKey, AssetVisibility>;
isOverwritingAccount: boolean;
isDismissed: boolean;
}

export interface MemoRequiredAccount {
Expand Down
2 changes: 2 additions & 0 deletions @shared/constants/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export enum SERVICE_TYPES {
CHANGE_ASSET_VISIBILITY = "CHANGE_ASSET_VISIBILITY",
GET_HIDDEN_ASSETS = "GET_HIDDEN_ASSETS",
GET_IS_ACCOUNT_MISMATCH = "GET_IS_ACCOUNT_MISMATCH",
GET_MOBILE_APP_BANNER_DISMISSED = "GET_MOBILE_APP_BANNER_DISMISSED",
DISMISS_MOBILE_APP_BANNER = "DISMISS_MOBILE_APP_BANNER",
}

export enum EXTERNAL_SERVICE_TYPES {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DataStorageAccess } from "background/helpers/dataStorageAccess";

const STORAGE_KEY = "mobileAppBannerDismissed";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually store this in /extension/src/constants/localStorageTypes. Then, you can reuse this const in getMobileAppBannerDismissed, as well


export const dismissMobileAppBanner = async ({
localStore,
}: {
localStore: DataStorageAccess;
}): Promise<void> => {
await localStore.setItem(STORAGE_KEY, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd be nice to have this function return {isDismissed: localStore.getItem(STORAGE_KEY)}. Then, in the UI, you can use the return value from this function to set the UI state. This makes sure that the value was correctly set in localStore and we don't accidentally have a mismatch in the UI state and the localStore

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DataStorageAccess } from "background/helpers/dataStorageAccess";

const STORAGE_KEY = "mobileAppBannerDismissed";

export const getMobileAppBannerDismissed = async ({
localStore,
}: {
localStore: DataStorageAccess;
}): Promise<{ isDismissed: boolean }> => {
const dismissed = await localStore.getItem(STORAGE_KEY);
return { isDismissed: !!dismissed };
};
12 changes: 12 additions & 0 deletions extension/src/background/messageListener/popupMessageListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import { modifyAssetsList } from "./handlers/modifyAssetsList";
import { getIsAccountMismatch } from "./handlers/getIsAccountMismatch";
import { changeAssetVisibility } from "./handlers/changeAssetVisibility";
import { getHiddenAssets } from "./handlers/getHiddenAssets";
import { getMobileAppBannerDismissed } from "./handlers/getMobileAppBannerDismissed";
import { dismissMobileAppBanner } from "./handlers/dismissMobileAppBanner";
import { loadBackendSettings } from "./handlers/loadBackendSettings";

const numOfPublicKeysToCheck = 5;
Expand Down Expand Up @@ -470,6 +472,16 @@ export const popupMessageListener = (
localStore,
});
}
case SERVICE_TYPES.GET_MOBILE_APP_BANNER_DISMISSED: {
return getMobileAppBannerDismissed({
localStore,
});
}
case SERVICE_TYPES.DISMISS_MOBILE_APP_BANNER: {
return dismissMobileAppBanner({
localStore,
});
}
default:
return { error: "Message type not supported" };
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { signOut } from "popup/ducks/accountServices";
import { AccountHeaderModal } from "popup/components/account/AccountHeaderModal";
import { NetworkIcon } from "popup/components/manageNetwork/NetworkIcon";
import { NetworkDetails } from "@shared/constants/stellar";
import { MobileAppBanner } from "popup/components/account/MobileAppBanner";

import "./styles.scss";

Expand Down Expand Up @@ -380,7 +381,7 @@ export const AccountHeader = ({
</div>
</NavLink>
</div>

<MobileAppBanner />
{isBackgroundActive
? createPortal(
<LoadingBackground
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
display: flex;
margin-top: pxToRem(8px);
gap: pxToRem(12px);
justify-content: center;

&__column {
align-items: center;
Expand Down
105 changes: 105 additions & 0 deletions extension/src/popup/components/account/MobileAppBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState, useEffect } from "react";
import { Icon, Text } from "@stellar/design-system";
import { useTranslation } from "react-i18next";
import browser from "webextension-polyfill";

import {
getMobileAppBannerDismissed,
dismissMobileAppBanner,
} from "@shared/api/internal";

import { openTab } from "popup/helpers/navigate";
import FreighterLogo from "popup/assets/logo-freighter-shadow.png";

import "./styles.scss";

// Check if browser storage is available (extension context, not fullscreen)
const isStorageAvailable = (): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check is a holdover from the last version of this PR, right? You shouldn't need to check this anymore. Whether you're in extension mode or in fullscreen mode, you should have access to the background storage now.

You should be able to safely remove this

return !!browser?.storage?.local;
};

export const MobileAppBanner = () => {
const { t } = useTranslation();
const [isDismissed, setIsDismissed] = useState<boolean | null>(null);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const checkDismissedStatus = async () => {
try {
const dismissed = await getMobileAppBannerDismissed();
setIsDismissed(dismissed);
} catch (error) {
console.error("Error checking banner dismissal status:", error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use captureException here to report this error to Sentry so we can have visibility if there's an issue

setIsDismissed(false);
} finally {
setIsLoading(false);
}
};

if (isStorageAvailable()) {
checkDismissedStatus();
} else {
setIsLoading(false);
}
}, []);

const handleDismiss = async (e: React.MouseEvent) => {
e.stopPropagation();
try {
await dismissMobileAppBanner();
setIsDismissed(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to this comment: https://github.com/stellar/freighter/pull/2394/files#r2571903580

dismissMobileAppeBanner should return {isDismissed: true} and you can use that value in setIsDismissed to make sure there isn't a possible mismatch between the UI and the background storage

} catch (error) {
console.error("Error dismissing banner:", error);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to above comment - let's use captureException

}
};

const handleBannerClick = () => {
openTab("https://www.freighter.app/#download");
};

// Don't show banner if storage is not available (e.g., fullscreen mode) or if dismissed
if (!isStorageAvailable() || isLoading || isDismissed) {
return null;
}

return (
<div
className="MobileAppBanner"
data-testid="mobile-app-banner"
onClick={handleBannerClick}
>
<div className="MobileAppBanner__content">
<div className="MobileAppBanner__text">
<Text
as="div"
size="sm"
weight="medium"
className="MobileAppBanner__title"
>
{t("Introducing Freighter Mobile")}
</Text>
<Text
as="div"
size="xs"
weight="medium"
className="MobileAppBanner__subtitle"
>
{t("Download on iOS or Android today")}
</Text>
</div>
<div className="MobileAppBanner__logo">
<img src={FreighterLogo} alt="Freighter logo" />
</div>
</div>
<button
type="button"
className="MobileAppBanner__dismiss"
onClick={handleDismiss}
aria-label="Dismiss banner"
data-testid="mobile-app-banner-dismiss"
>
<Icon.X />
</button>
</div>
);
};
89 changes: 89 additions & 0 deletions extension/src/popup/components/account/MobileAppBanner/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@use "../../../styles/utils.scss" as *;

.MobileAppBanner {
position: relative;
background: var(--sds-clr-lilac-03);
border-radius: pxToRem(12px);
padding: pxToRem(16px);
margin: pxToRem(12px) 0 pxToRem(0) pxToRem(-16px);
width: calc(100% + pxToRem(32px));
display: flex;
align-items: center;
justify-content: space-between;
gap: pxToRem(12px);
cursor: pointer;
transition: opacity 0.2s ease;

&:hover {
opacity: 0.9;
}

&__content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
gap: pxToRem(16px);
max-width: 100%;
margin: 0 auto;
}

&__text {
display: flex;
flex-direction: column;
gap: pxToRem(8px);
flex: 1;
}

&__title {
color: var(--sds-clr-base-01);
margin: 0;
font-size: pxToRem(14px);
}

&__subtitle {
color: var(--sds-clr-gray-11);
margin: 0;
font-size: pxToRem(12px);
}

&__logo {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;

img {
width: pxToRem(40px);
height: pxToRem(40px);
object-fit: contain;
}
}

&__dismiss {
position: absolute;
top: pxToRem(4px);
right: pxToRem(4px);
background: none;
border: none;
cursor: pointer;
padding: pxToRem(4px);
display: flex;
align-items: center;
justify-content: center;
color: var(--sds-clr-base-01);
border-radius: pxToRem(16px);
z-index: 10;
pointer-events: auto;

&:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.1);
}

svg {
width: pxToRem(16px);
height: pxToRem(16px);
}
}
}
2 changes: 2 additions & 0 deletions extension/src/popup/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"Discover": "Discover",
"Do this later": "Do this later",
"Done": "Done",
"Download on iOS or Android today": "Download on iOS or Android today",
"Enable Blind Signing on Ledger": "Enable Blind Signing on Ledger",
"Enabled": "Enabled",
"Enter a Stellar Asset List compatible URL": "Enter a Stellar Asset List compatible URL",
Expand Down Expand Up @@ -206,6 +207,7 @@
"Insufficient Balance": "Insufficient Balance",
"Insufficient Fee": "Insufficient Fee",
"INSUFFICIENT FUNDS FOR FEE": "INSUFFICIENT FUNDS FOR FEE",
"Introducing Freighter Mobile": "Introducing Freighter Mobile",
"invalid destination address": "invalid destination address",
"Invalid mnemonic phrase": "Invalid mnemonic phrase",
"INVALID STELLAR ADDRESS": "INVALID STELLAR ADDRESS",
Expand Down
2 changes: 2 additions & 0 deletions extension/src/popup/locales/pt/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
"Discover": "Discover",
"Do this later": "Do this later",
"Done": "Done",
"Download on iOS or Android today": "Baixe no iOS ou Android hoje",
"Enable Blind Signing on Ledger": "Enable Blind Signing on Ledger",
"Enabled": "Enabled",
"Enter a Stellar Asset List compatible URL": "Enter a Stellar Asset List compatible URL",
Expand Down Expand Up @@ -206,6 +207,7 @@
"Insufficient Balance": "Insufficient Balance",
"Insufficient Fee": "Insufficient Fee",
"INSUFFICIENT FUNDS FOR FEE": "INSUFFICIENT FUNDS FOR FEE",
"Introducing Freighter Mobile": "Apresentando o Freighter Mobile",
"invalid destination address": "invalid destination address",
"Invalid mnemonic phrase": "Invalid mnemonic phrase",
"INVALID STELLAR ADDRESS": "INVALID STELLAR ADDRESS",
Expand Down