diff --git a/.changeset/afraid-houses-grin.md b/.changeset/afraid-houses-grin.md new file mode 100644 index 000000000000..55b0beb1b1d0 --- /dev/null +++ b/.changeset/afraid-houses-grin.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/coin-xrp": minor +--- + +Xrp listOp returns only Payment method to avoid Destination value undefined diff --git a/.changeset/beige-baboons-share.md b/.changeset/beige-baboons-share.md new file mode 100644 index 000000000000..666debf5589c --- /dev/null +++ b/.changeset/beige-baboons-share.md @@ -0,0 +1,5 @@ +--- +"ledger-live-desktop": minor +--- + +tests: adding new e2e test diff --git a/.changeset/famous-tables-mate.md b/.changeset/famous-tables-mate.md new file mode 100644 index 000000000000..da426d1b22c3 --- /dev/null +++ b/.changeset/famous-tables-mate.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/types-live": minor +"live-mobile": minor +"@ledgerhq/live-common": minor +--- + +Add llm Reborn II feature flag for A/B Test + analytics diff --git a/.changeset/orange-ducks-do.md b/.changeset/orange-ducks-do.md new file mode 100644 index 000000000000..db47b97b6be2 --- /dev/null +++ b/.changeset/orange-ducks-do.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/react-ui": minor +"@ledgerhq/ui-shared": minor +--- + +Update the react-ui Carousel based on the portfolio content cards design diff --git a/.changeset/silent-planes-count.md b/.changeset/silent-planes-count.md new file mode 100644 index 000000000000..7e822e4f44a1 --- /dev/null +++ b/.changeset/silent-planes-count.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +Wait for fetchData from braze before open the app. The braze landing page weren't able to render anything if a user coming from a notification hadn't lockpin diff --git a/.changeset/two-apricots-prove.md b/.changeset/two-apricots-prove.md new file mode 100644 index 000000000000..0d59064c96fa --- /dev/null +++ b/.changeset/two-apricots-prove.md @@ -0,0 +1,7 @@ +--- +"@ledgerhq/coin-stacks": minor +"ledger-live-desktop": minor +"@ledgerhq/live-common": minor +--- + +fix: import from stacks, playwright test selecting 7d timerange diff --git a/.github/workflows/test-mobile-e2e-reusable.yml b/.github/workflows/test-mobile-e2e-reusable.yml index 12865d226dd4..b16a451db50f 100644 --- a/.github/workflows/test-mobile-e2e-reusable.yml +++ b/.github/workflows/test-mobile-e2e-reusable.yml @@ -180,7 +180,7 @@ jobs: AVD_CORES: 4 AVD_RAM_SIZE: 4096M AVD_OPTIONS: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.9.5 + SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.11 COINAPPS: ${{ github.workspace }}/coin-apps outputs: status: ${{ steps.detox.outcome }} diff --git a/.github/workflows/test-ui-e2e-only-desktop.yml b/.github/workflows/test-ui-e2e-only-desktop.yml index 709c02c77093..d800be14f6cd 100644 --- a/.github/workflows/test-ui-e2e-only-desktop.yml +++ b/.github/workflows/test-ui-e2e-only-desktop.yml @@ -60,7 +60,7 @@ jobs: FORCE_COLOR: 3 CI_OS: "ubuntu-latest" PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.9.5 + SPECULOS_IMAGE_TAG: ghcr.io/ledgerhq/speculos:0.11 ENABLE_BROADCAST_TEST: ${{ inputs.enable_send_test }} runs-on: [ledger-live-4xlarge] strategy: diff --git a/apps/ledger-live-desktop/src/renderer/drawers/NFTViewerDrawer/index.tsx b/apps/ledger-live-desktop/src/renderer/drawers/NFTViewerDrawer/index.tsx index 6aaeab0dd39c..0ea3e4f5cba9 100644 --- a/apps/ledger-live-desktop/src/renderer/drawers/NFTViewerDrawer/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/drawers/NFTViewerDrawer/index.tsx @@ -239,6 +239,7 @@ const NFTViewerDrawer = ({ account, nftId, height }: NFTViewerDrawerProps) => { { - diff --git a/apps/ledger-live-desktop/src/renderer/screens/nft/Send/Summary.tsx b/apps/ledger-live-desktop/src/renderer/screens/nft/Send/Summary.tsx index 0be6ab0f2e80..c8394e568101 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/nft/Send/Summary.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/nft/Send/Summary.tsx @@ -54,7 +54,12 @@ const Summary = ({ transaction }: Props) => { - + {(nftName as string) || "-"} diff --git a/apps/ledger-live-desktop/tests/enum/Account.ts b/apps/ledger-live-desktop/tests/enum/Account.ts index 11d6dc8324b9..33d7d183c5d0 100644 --- a/apps/ledger-live-desktop/tests/enum/Account.ts +++ b/apps/ledger-live-desktop/tests/enum/Account.ts @@ -8,6 +8,7 @@ export class Account { public readonly address: string, public readonly accountType?: AccountType, public readonly index?: number, + public readonly ensName?: string, ) {} static readonly BTC_NATIVE_SEGWIT_1 = new Account( @@ -122,6 +123,15 @@ export class Account { 1, ); + static readonly ETH_MC = new Account( + Currency.ETH, + "Ethereum MC", + "0x4258A05DBA420A398bcdFB389E4250759b4223ea", + undefined, + undefined, + "ldgrqamco.eth", + ); + static readonly ETH_3 = new Account( Currency.ETH, "Ethereum 3", @@ -409,6 +419,13 @@ export class Account { 1, ); + static readonly ETH_USDC_1 = new Account( + Currency.ETH_USDC, + "Ethereum 1", + "0xB9051f83AC6e147924377BBEebd1Aa7aB43a67F6", + AccountType.ERC20, + ); + static readonly ETH_LIDO = new Account( Currency.ETH_LIDO, "Ethereum 1", diff --git a/apps/ledger-live-desktop/tests/enum/Currency.ts b/apps/ledger-live-desktop/tests/enum/Currency.ts index c4d7c3b57a46..11a50b415729 100644 --- a/apps/ledger-live-desktop/tests/enum/Currency.ts +++ b/apps/ledger-live-desktop/tests/enum/Currency.ts @@ -55,6 +55,7 @@ export class Currency { ); static readonly TON = new Currency("Ton", "TON", "ton", AppInfos.TON); static readonly ETH_USDT = new Currency("Tether USD", "USDT", "ethereum", AppInfos.ETHEREUM); + static readonly ETH_USDC = new Currency("USD Coin", "USDC", "ethereum", AppInfos.ETHEREUM); static readonly ETH_LIDO = new Currency( "LIDO Staked ETH", "STETH", diff --git a/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts b/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts index aca88be4e025..ea2868183ed3 100644 --- a/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts +++ b/apps/ledger-live-desktop/tests/enum/DeviceLabels.ts @@ -41,4 +41,5 @@ export enum DeviceLabels { NEW_ORDINARY_TRANSACTION = "New ordinary transaction", SEND_TO_ADDRESS_2 = "Send to address (2/2)", CONFIRM_TRANSACTION = "Confirm transaction", + REVIEW_TRANSACTION = "Review transaction", } diff --git a/apps/ledger-live-desktop/tests/families/evm.ts b/apps/ledger-live-desktop/tests/families/evm.ts index 314f09373f78..714d98a70607 100644 --- a/apps/ledger-live-desktop/tests/families/evm.ts +++ b/apps/ledger-live-desktop/tests/families/evm.ts @@ -1,9 +1,10 @@ import { expect } from "@playwright/test"; -import { Transaction } from "tests/models/Transaction"; +import { NFTTransaction, Transaction } from "tests/models/Transaction"; import { pressBoth, pressUntilTextFound, containsSubstringInEvent, + waitFor, } from "@ledgerhq/live-common/e2e/speculos"; import { DeviceLabels } from "tests/enum/DeviceLabels"; @@ -16,3 +17,11 @@ export async function sendEVM(tx: Transaction) { await pressBoth(); } + +export async function sendEvmNFT(tx: NFTTransaction) { + await waitFor(DeviceLabels.REVIEW_TRANSACTION); + const events = await pressUntilTextFound(DeviceLabels.ACCEPT); + const isAddressCorrect = containsSubstringInEvent(tx.accountToCredit.address, events); + expect(isAddressCorrect).toBeTruthy(); + await pressBoth(); +} diff --git a/apps/ledger-live-desktop/tests/models/Transaction.ts b/apps/ledger-live-desktop/tests/models/Transaction.ts index 42ad80fd4733..ce6a39e1c37e 100644 --- a/apps/ledger-live-desktop/tests/models/Transaction.ts +++ b/apps/ledger-live-desktop/tests/models/Transaction.ts @@ -10,3 +10,15 @@ export class Transaction { public memoTag?: string, ) {} } + +export class NFTTransaction extends Transaction { + constructor( + accountToDebit: Account, + accountToCredit: Account, + public nftName: string, + speed?: Fee, + memoTag?: string, + ) { + super(accountToDebit, accountToCredit, "0", speed, memoTag); + } +} diff --git a/apps/ledger-live-desktop/tests/page/account.page.ts b/apps/ledger-live-desktop/tests/page/account.page.ts index af0b864f1d30..69af224484aa 100644 --- a/apps/ledger-live-desktop/tests/page/account.page.ts +++ b/apps/ledger-live-desktop/tests/page/account.page.ts @@ -31,6 +31,9 @@ export class AccountPage extends AppPage { private tokenRow = (tokenTicker: string) => this.page.getByTestId(`token-row-${tokenTicker}`); private addTokenButton = this.page.getByRole("button", { name: "Add token" }); private viewDetailsButton = this.page.getByText("View details"); + private seeGalleryButton = this.page.getByTestId("see-gallery-button"); + private nft = (nftName: string) => this.page.locator(`text=${nftName}`); + private nftOperation = this.page.getByText("NFT Sent"); @step("Navigate to token") async navigateToToken(SubAccount: Account) { @@ -159,4 +162,19 @@ export class AccountPage extends AppPage { async navigateToTokenInAccount(SubAccount: Account) { await this.tokenRow(SubAccount.currency.ticker).click(); } + + @step("Navigate to NFT gallery") + async navigateToNFTGallery() { + await this.seeGalleryButton.click(); + } + + @step("Select NFT $0") + async selectNFT(nftName: string) { + await this.nft(nftName).click(); + } + + @step("Navigate to NFT operation") + async navigateToNFTOperation() { + await this.nftOperation.click(); + } } diff --git a/apps/ledger-live-desktop/tests/page/drawer/nft.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/nft.drawer.ts new file mode 100644 index 000000000000..fbcc90579a25 --- /dev/null +++ b/apps/ledger-live-desktop/tests/page/drawer/nft.drawer.ts @@ -0,0 +1,18 @@ +import { step } from "tests/misc/reporters/step"; +import { Drawer } from "tests/component/drawer.component"; +import { expect } from "@playwright/test"; + +export class NFTDrawer extends Drawer { + private nftName = this.page.getByTestId("nft-name-sendDrawer"); + private sendButton = this.page.getByTestId("nft-send-button-sendDrawer"); + + @step("Verify nft name is visible") + async expectNftNameIsVisible(nft: string) { + await expect(this.nftName).toHaveText(nft); + } + + @step("click on send button") + async clickSend() { + await this.sendButton.click(); + } +} diff --git a/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts b/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts index 7d94ac086d32..2641a95bcf0a 100644 --- a/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts +++ b/apps/ledger-live-desktop/tests/page/drawer/send.drawer.ts @@ -1,12 +1,14 @@ import { step } from "tests/misc/reporters/step"; import { Drawer } from "tests/component/drawer.component"; import { expect } from "@playwright/test"; -import { Transaction } from "tests/models/Transaction"; +import { NFTTransaction, Transaction } from "tests/models/Transaction"; export class SendDrawer extends Drawer { private addressValue = (address: string) => this.page.locator('[data-testid="drawer-content"]').locator(`text=${address}`); private amountValue = this.page.getByTestId("amountReceived-drawer"); + private transactionType = this.page.getByTestId("transaction-type"); + private nftName = this.page.getByTestId("nft-name-operationDrawer"); @step("Verify address is visible") async addressValueIsVisible(address: string) { @@ -18,4 +20,14 @@ export class SendDrawer extends Drawer { await expect(this.addressValue(tx.accountToCredit.address)).toBeVisible(); await expect(this.amountValue).toBeVisible(); } + + @step("Verify Send NFT information") + async expectNftInfos(tx: NFTTransaction) { + const transactionType = await this.transactionType.textContent(); + expect(transactionType).toMatch("Sending"); + const NFTName = await this.nftName.textContent(); + expect(NFTName).toBe(tx.nftName); + const address = await this.addressValue(tx.accountToCredit.address).textContent(); + expect(address).toBe(tx.accountToCredit.address); + } } diff --git a/apps/ledger-live-desktop/tests/page/index.ts b/apps/ledger-live-desktop/tests/page/index.ts index 43a11c45cf66..4068d1b2bb63 100644 --- a/apps/ledger-live-desktop/tests/page/index.ts +++ b/apps/ledger-live-desktop/tests/page/index.ts @@ -20,6 +20,7 @@ import { SendDrawer } from "./drawer/send.drawer"; import { AssetDrawer } from "./drawer/asset.drawer"; import { PasswordlockModal } from "./modal/passwordlock.modal"; import { LockscreenPage } from "tests/page/lockscreen.page"; +import { NFTDrawer } from "./drawer/nft.drawer"; export class Application extends PageHolder { public account = new AccountPage(this.page); @@ -43,4 +44,5 @@ export class Application extends PageHolder { public assetDrawer = new AssetDrawer(this.page); public password = new PasswordlockModal(this.page); public LockscreenPage = new LockscreenPage(this.page); + public nftDrawer = new NFTDrawer(this.page); } diff --git a/apps/ledger-live-desktop/tests/page/market.page.ts b/apps/ledger-live-desktop/tests/page/market.page.ts index c1403684e980..f097c4e9f42c 100644 --- a/apps/ledger-live-desktop/tests/page/market.page.ts +++ b/apps/ledger-live-desktop/tests/page/market.page.ts @@ -29,8 +29,9 @@ export class MarketPage extends AppPage { @step("Switch market range for $0") async switchMarketRange(range: string) { await this.marketRangeSelect.click(); - // TODO: For some reason need to hack selects like that - await this.page.click(`text=${range}`); + await this.page.click(`#react-select-3-listbox >> text=${range}`); + // NOTE: this.page.click(`text=${range}`); + // won't work on 7th row if the coin starts with d (e.g. "dogecoin") } @step("Toggle star filter") diff --git a/apps/ledger-live-desktop/tests/page/modal/send.modal.ts b/apps/ledger-live-desktop/tests/page/modal/send.modal.ts index ea7bfcb94296..b51ee208e248 100644 --- a/apps/ledger-live-desktop/tests/page/modal/send.modal.ts +++ b/apps/ledger-live-desktop/tests/page/modal/send.modal.ts @@ -1,7 +1,7 @@ import { expect } from "@playwright/test"; import { Modal } from "../../component/modal.component"; import { step } from "tests/misc/reporters/step"; -import { Transaction } from "../../models/Transaction"; +import { NFTTransaction, Transaction } from "../../models/Transaction"; export class SendModal extends Modal { private drowdownAccount = this.page.locator('[data-testid="modal-content"] svg').nth(1); @@ -13,9 +13,12 @@ export class SendModal extends Modal { ); private checkTransactionbroadcastLabel = this.page.locator("text=Transaction sent"); private recipientAddressDisplayedValue = this.page.getByTestId("recipient-address"); + private recipientEnsDisplayed = this.page.getByTestId("transaction-recipient-ens"); private amountDisplayedValue = this.page.getByTestId("transaction-amount"); + private nftNameDisplayed = this.page.getByTestId("transaction-nft-name"); private feeStrategy = (fee: string) => this.page.getByText(fee); private noTagButton = this.page.getByRole("button", { name: "Don’t add Tag" }); + private ENSAddressLabel = this.page.getByTestId("ens-address-sendModal"); async selectAccount(name: string) { await this.drowdownAccount.click(); @@ -38,6 +41,13 @@ export class SendModal extends Modal { await this.recipientInput.fill(recipient); } + @step("choose fee startegy") + async chooseFeeStrategy(fee: string | undefined) { + if (fee) { + await this.feeStrategy(fee).click(); + } + } + @step("Enter recipient and tag") async fillRecipientInfo(transaction: Transaction) { await this.fillRecipient(transaction.accountToCredit.address); @@ -47,6 +57,16 @@ export class SendModal extends Modal { } } + @step("Craft NFT tx") + async craftNFTTx(tx: NFTTransaction) { + await this.fillRecipient(tx.accountToCredit.ensName || tx.accountToCredit.address); + const displayedAddress = await this.ENSAddressLabel.innerText(); + expect(displayedAddress).toEqual(tx.accountToCredit.address); + await this.continueButton.click(); + await this.chooseFeeStrategy(tx.speed); + await this.continueButton.click(); + } + @step("Fill tx information") async craftTx(tx: Transaction) { await this.fillRecipientInfo(tx); @@ -59,12 +79,25 @@ export class SendModal extends Modal { await this.cryptoAmountField.fill(tx.amount); if (tx.speed !== undefined) { - await this.feeStrategy(tx.speed).click(); + await this.chooseFeeStrategy(tx.speed); } await this.countinueSendAmount(); } + @step("Verify tx information before confirming") + async expectNFTTxInfoValidity(tx: NFTTransaction) { + const displayedEns = await this.recipientEnsDisplayed.innerText(); + expect(displayedEns).toEqual(tx.accountToCredit.ensName); + + const displayedReceiveAddress = await this.recipientAddressDisplayedValue.innerText(); + expect(displayedReceiveAddress).toEqual(tx.accountToCredit.address); + + const displayedNftName = await this.nftNameDisplayed.innerText(); + expect(displayedNftName).toEqual(expect.stringContaining(tx.nftName)); + await this.continueButton.click(); + } + @step("Verify tx information before confirming") async expectTxInfoValidity(tx: Transaction) { const displayedReceiveAddress = await this.recipientAddressDisplayedValue.innerText(); diff --git a/apps/ledger-live-desktop/tests/page/speculos.page.ts b/apps/ledger-live-desktop/tests/page/speculos.page.ts index 4681469b3b1d..64b0e95a76b3 100644 --- a/apps/ledger-live-desktop/tests/page/speculos.page.ts +++ b/apps/ledger-live-desktop/tests/page/speculos.page.ts @@ -8,14 +8,14 @@ import { } from "@ledgerhq/live-common/e2e/speculos"; import { Account } from "../enum/Account"; import { expect } from "@playwright/test"; -import { Transaction } from "tests/models/Transaction"; +import { NFTTransaction, Transaction } from "tests/models/Transaction"; import { Delegate } from "tests/models/Delegate"; import { DeviceLabels } from "tests/enum/DeviceLabels"; import { Currency } from "tests/enum/Currency"; import { Swap } from "tests/models/Swap"; import { extractNumberFromString } from "tests/utils/textParserUtils"; import { sendBTCBasedCoin } from "tests/families/bitcoin"; -import { sendEVM } from "tests/families/evm"; +import { sendEVM, sendEvmNFT } from "tests/families/evm"; import { sendPolkadot } from "tests/families/polkadot"; import { sendAlgorand } from "tests/families/algorand"; import { sendTron } from "tests/families/tron"; @@ -60,6 +60,18 @@ export class SpeculosPage extends AppPage { await pressBoth(); } + @step("Sign Send NFT Transaction") + async signSendNFTTransaction(tx: NFTTransaction) { + const currencyName = tx.accountToDebit.currency; + switch (currencyName) { + case Currency.ETH: + await sendEvmNFT(tx); + break; + default: + throw new Error(`Unsupported currency: ${currencyName}`); + } + } + @step("Sign Send Transaction") async signSendTransaction(tx: Transaction) { const currencyName = tx.accountToDebit.currency; diff --git a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts index 292ee758d0dd..5d016c9f23a5 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/send.tx.spec.ts @@ -1,7 +1,7 @@ import { test } from "../../fixtures/common"; import { Account } from "../../enum/Account"; import { Fee } from "../../enum/Fee"; -import { Transaction } from "../../models/Transaction"; +import { Transaction, NFTTransaction } from "../../models/Transaction"; import { addTmsLink } from "tests/utils/allureUtils"; import { getDescription } from "../../utils/customJsonReporter"; import { commandCLI } from "tests/utils/cliUtils"; @@ -575,3 +575,55 @@ for (const transaction of transactionsAddressInvalid) { ); }); } + +test.describe("send NFT to ENS address", () => { + const transaction = new NFTTransaction(Account.ETH_1, Account.ETH_MC, "NY la muse", Fee.SLOW); + test.beforeAll(async () => { + process.env.DISABLE_TRANSACTION_BROADCAST = "true"; + }); + test.afterAll(async () => { + delete process.env.DISABLE_TRANSACTION_BROADCAST; + }); + test.use({ + userdata: "skip-onboarding", + cliCommands: [ + { + command: commandCLI.liveData, + args: { + currency: transaction.accountToDebit.currency.currencyId, + index: transaction.accountToDebit.index, + appjson: "", + add: true, + }, + }, + ], + speculosApp: transaction.accountToDebit.currency.speculosApp, + }); + + test( + "Send NFT to ENS address", + { + annotation: { + type: "TMS", + description: "B2CQA-2203", + }, + }, + async ({ app }) => { + await addTmsLink(getDescription(test.info().annotations).split(", ")); + await app.layout.goToAccounts(); + await app.accounts.navigateToAccountByName(transaction.accountToDebit.accountName); + await app.account.navigateToNFTGallery(); + await app.account.selectNFT(transaction.nftName); + await app.nftDrawer.expectNftNameIsVisible(transaction.nftName); + await app.nftDrawer.clickSend(); + await app.send.craftNFTTx(transaction); + await app.send.expectNFTTxInfoValidity(transaction); + await app.speculos.signSendNFTTransaction(transaction); + await app.send.expectTxSent(); + await app.account.navigateToViewDetails(); + await app.drawer.close(); + await app.account.navigateToNFTOperation(); + await app.sendDrawer.expectNftInfos(transaction); + }, + ); +}); diff --git a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts index 45c66c0de83e..812c0a27c1c4 100644 --- a/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts +++ b/apps/ledger-live-desktop/tests/specs/speculos/swap.spec.ts @@ -112,6 +112,73 @@ const swaps = [ ), xrayTicket: "B2CQA-2751", }, + //todo: flaky balance retrieval, reactivate after LIVE-14410 + /*{ + swap: new Swap( + Account.SOL_1, + Account.ETH_1, + "0.25", + Fee.MEDIUM, + Provider.CHANGELLY, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2828", + }, + { + swap: new Swap( + Account.SOL_1, + Account.BTC_NATIVE_SEGWIT_1, + "0.25", + Fee.MEDIUM, + Provider.CHANGELLY, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2827", + }, + { + swap: new Swap( + Account.SOL_1, + Account.ETH_USDT_1, + "0.25", + Fee.MEDIUM, + Provider.CHANGELLY, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2829", + }, + { + swap: new Swap( + Account.ETH_USDC_1, + Account.ETH_1, + "45", + Fee.MEDIUM, + Provider.EXODUS, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2830", + }, + { + swap: new Swap( + Account.ETH_USDC_1, + Account.SOL_1, + "45", + Fee.MEDIUM, + Provider.EXODUS, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2831", + }, + { + swap: new Swap( + Account.ETH_USDC_1, + Account.BTC_NATIVE_SEGWIT_1, + "45", + Fee.MEDIUM, + Provider.EXODUS, + Rate.FLOAT, + ), + xrayTicket: "B2CQA-2832", + },*/ ]; for (const { swap, xrayTicket } of swaps) { diff --git a/apps/ledger-live-mobile/src/actions/dynamicContent.ts b/apps/ledger-live-mobile/src/actions/dynamicContent.ts index f863a247352d..2be7ebeb92c7 100755 --- a/apps/ledger-live-mobile/src/actions/dynamicContent.ts +++ b/apps/ledger-live-mobile/src/actions/dynamicContent.ts @@ -60,3 +60,10 @@ const setDynamicContentNotificationCardsAction = export const setDynamicContentNotificationCards = (notificationCards: NotificationContentCard[]) => setDynamicContentNotificationCardsAction(notificationCards); + +const setIsDynamicContentLoadingAction = createAction( + DynamicContentActionTypes.DYNAMIC_CONTENT_IS_LOADING, +); + +export const setIsDynamicContentLoading = (isLoading: boolean) => + setIsDynamicContentLoadingAction(isLoading); diff --git a/apps/ledger-live-mobile/src/actions/types.ts b/apps/ledger-live-mobile/src/actions/types.ts index 0b201827af65..a351875d625e 100644 --- a/apps/ledger-live-mobile/src/actions/types.ts +++ b/apps/ledger-live-mobile/src/actions/types.ts @@ -166,6 +166,7 @@ export enum DynamicContentActionTypes { DYNAMIC_CONTENT_SET_NOTIFICATION_CARDS = "DYNAMIC_CONTENT_SET_NOTIFICATION_CARDS", DYNAMIC_CONTENT_SET_CATEGORIES_CARDS = "DYNAMIC_CONTENT_SET_CATEGORIES_CARDS", DYNAMIC_CONTENT_SET_MOBILE_CARDS = "DYNAMIC_CONTENT_SET_MOBILE_CARDS", + DYNAMIC_CONTENT_IS_LOADING = "DYNAMIC_CONTENT_IS_LOADING", } export type DynamicContentSetWalletCardsPayload = DynamicContentState["walletCards"]; diff --git a/apps/ledger-live-mobile/src/analytics/segment.ts b/apps/ledger-live-mobile/src/analytics/segment.ts index 26b9b56b7c2a..cdb395e99023 100644 --- a/apps/ledger-live-mobile/src/analytics/segment.ts +++ b/apps/ledger-live-mobile/src/analytics/segment.ts @@ -16,7 +16,7 @@ import { } from "@react-navigation/native"; import snakeCase from "lodash/snakeCase"; import React, { MutableRefObject, useCallback } from "react"; -import { FeatureId, Features, idsToLanguage } from "@ledgerhq/types-live"; +import { ABTestingVariants, FeatureId, Features, idsToLanguage } from "@ledgerhq/types-live"; import { hasNftInAccounts, GENESIS_PASS_COLLECTION_CONTRACT, @@ -107,6 +107,16 @@ const getLedgerSyncAttributes = (state: State) => { }; }; +const getRebornAttributes = () => { + if (!analyticsFeatureFlagMethod) return false; + const reborn = analyticsFeatureFlagMethod("llmRebornLP"); + + return { + llmRebornLP_A: reborn?.params?.variant === ABTestingVariants.variantA, + llmRebornLP_B: reborn?.params?.variant === ABTestingVariants.variantB, + }; +}; + const getMandatoryProperties = async (store: AppStore) => { const state: State = store.getState(); const { user } = await getOrCreateUser(); @@ -186,6 +196,7 @@ const extraProperties = async (store: AppStore) => { stakingProviders?.enabled && stakingProviders?.params?.listProvider.length; const ledgerSyncAtributes = getLedgerSyncAttributes(state); + const rebornAttributes = getRebornAttributes(); return { ...mandatoryProperties, @@ -222,6 +233,7 @@ const extraProperties = async (store: AppStore) => { nps, stakingProvidersEnabled: stakingProvidersCount || "flag not loaded", ...ledgerSyncAtributes, + ...rebornAttributes, }; }; diff --git a/apps/ledger-live-mobile/src/dynamicContent/useContentCards.ts b/apps/ledger-live-mobile/src/dynamicContent/useContentCards.ts index 2997ea58eef6..00209088d83a 100644 --- a/apps/ledger-live-mobile/src/dynamicContent/useContentCards.ts +++ b/apps/ledger-live-mobile/src/dynamicContent/useContentCards.ts @@ -1,15 +1,19 @@ -import { useEffect } from "react"; +import { useCallback, useEffect } from "react"; import { useDynamicContentLogic } from "./useDynamicContentLogic"; const HookDynamicContentCards = () => { const { refreshDynamicContent, fetchData, clearOldDismissedContentCards } = useDynamicContentLogic(); - useEffect(() => { + const fetchContentCardsData = useCallback(async () => { clearOldDismissedContentCards(); refreshDynamicContent(); - fetchData(); - }, [fetchData, refreshDynamicContent, clearOldDismissedContentCards]); + await fetchData(); + }, [refreshDynamicContent, fetchData, clearOldDismissedContentCards]); + + useEffect(() => { + fetchContentCardsData(); + }, [fetchContentCardsData]); return null; }; diff --git a/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts b/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts index 6741cd366046..d5bd4f57815e 100644 --- a/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts +++ b/apps/ledger-live-mobile/src/dynamicContent/useDynamicContentLogic.ts @@ -7,6 +7,7 @@ import { setDynamicContentLearnCards, setDynamicContentCategoriesCards, setDynamicContentMobileCards, + setIsDynamicContentLoading, } from "../actions/dynamicContent"; import { useBrazeContentCard } from "./brazeContentCard"; import { @@ -33,8 +34,16 @@ export const useDynamicContentLogic = () => { const dismissedContentCardsIds = Object.keys(dismissedContentCards); const fetchData = useCallback(async () => { + dispatch(setIsDynamicContentLoading(true)); + // Fetch data from Braze - const contentCards: BrazeContentCard[] = await Braze.getContentCards(); + let contentCards: BrazeContentCard[] = []; + try { + contentCards = await Braze.getContentCards(); + } catch (error) { + console.error("Error fetching dynamic content", error); + } + const filteredContentCards = filterCardsThatHaveBeenDismissed( contentCards, dismissedContentCardsIds, @@ -70,6 +79,7 @@ export const useDynamicContentLogic = () => { dispatch(setDynamicContentAssetsCards(assetCards)); dispatch(setDynamicContentNotificationCards(notificationCards)); dispatch(setDynamicContentLearnCards(learnCards)); + dispatch(setIsDynamicContentLoading(false)); }, [Braze, dismissedContentCardsIds, dispatch]); const clearOldDismissedContentCards = () => { diff --git a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/GenericLandingPage/index.tsx b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/GenericLandingPage/index.tsx index 73d3445078c1..d47238325aac 100644 --- a/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/GenericLandingPage/index.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/LandingPages/screens/GenericLandingPage/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import { Flex } from "@ledgerhq/native-ui"; +import { Flex, InfiniteLoader } from "@ledgerhq/native-ui"; import { LandingPagesNavigatorParamList } from "~/components/RootNavigator/types/LandingPagesNavigator"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; import { ScreenName } from "~/const"; @@ -8,6 +8,8 @@ import { TrackScreen } from "~/analytics"; import { LandingPageUseCase } from "~/dynamicContent/types"; import useDynamicContent from "~/dynamicContent/useDynamicContent"; import { filterCategoriesByLocation } from "~/dynamicContent/utils"; +import { useSelector } from "react-redux"; +import { isDynamicContentLoadingSelector } from "~/reducers/dynamicContent"; type NavigationProps = BaseComposite< StackNavigatorProps @@ -15,6 +17,7 @@ type NavigationProps = BaseComposite< const GenericLandingPage = (props: NavigationProps) => { const useCase = props.route.params?.useCase; + const isLoading = useSelector(isDynamicContentLoadingSelector); const { categoriesCards } = useDynamicContent(); useEffect(() => { @@ -22,15 +25,19 @@ const GenericLandingPage = (props: NavigationProps) => { props.navigation.goBack(); } const categoriesToDisplay = filterCategoriesByLocation(categoriesCards, useCase); - if (categoriesToDisplay.length === 0) { + if (categoriesToDisplay.length === 0 && !isLoading) { props.navigation.goBack(); } - }, [categoriesCards, props.navigation, useCase]); + }, [categoriesCards, isLoading, props.navigation, useCase]); return ( - + - + {isLoading ? ( + + ) : ( + + )} ); }; diff --git a/apps/ledger-live-mobile/src/reducers/dynamicContent.ts b/apps/ledger-live-mobile/src/reducers/dynamicContent.ts index ac5b88aee8c3..c2ba58436799 100644 --- a/apps/ledger-live-mobile/src/reducers/dynamicContent.ts +++ b/apps/ledger-live-mobile/src/reducers/dynamicContent.ts @@ -19,6 +19,7 @@ export const INITIAL_STATE: DynamicContentState = { notificationCards: [], categoriesCards: [], mobileCards: [], + isLoading: true, }; const handlers: ReducerMap = { @@ -46,6 +47,10 @@ const handlers: ReducerMap = { ...state, mobileCards: (action as Action).payload, }), + [DynamicContentActionTypes.DYNAMIC_CONTENT_IS_LOADING]: (state, action) => ({ + ...state, + isLoading: (action as unknown as Action).payload, + }), }; // Selectors @@ -61,4 +66,7 @@ export const categoriesCardsSelector = (s: State) => s.dynamicContent.categories export const mobileCardsSelector = (s: State) => s.dynamicContent.mobileCards; +export const isDynamicContentLoadingSelector: (s: State) => boolean = (s: State) => + s.dynamicContent.isLoading; + export default handleActions(handlers, INITIAL_STATE); diff --git a/apps/ledger-live-mobile/src/reducers/types.ts b/apps/ledger-live-mobile/src/reducers/types.ts index 55eea19fb567..06226fff8e01 100644 --- a/apps/ledger-live-mobile/src/reducers/types.ts +++ b/apps/ledger-live-mobile/src/reducers/types.ts @@ -129,6 +129,8 @@ export type DynamicContentState = { categoriesCards: CategoryContentCard[]; /** Dynamic content cards for Ledger Live Mobile */ mobileCards: BrazeContentCard[]; + /** Check if CC are loading */ + isLoading: boolean; }; // === RATINGS STATE === diff --git a/libs/coin-modules/coin-stacks/package.json b/libs/coin-modules/coin-stacks/package.json index 7e8efde95089..5ef8f4b01fce 100644 --- a/libs/coin-modules/coin-stacks/package.json +++ b/libs/coin-modules/coin-stacks/package.json @@ -46,10 +46,6 @@ "exports": { "./lib/*": "./lib/*.js", "./lib-es/*": "./lib-es/*.js", - "./api": { - "require": "./lib/api/index.js", - "default": "./lib-es/api/index.js" - }, "./deviceTransactionConfig": { "require": "./lib/bridge/deviceTransactionConfig.js", "default": "./lib-es/bridge/deviceTransactionConfig.js" diff --git a/libs/coin-modules/coin-stacks/src/bridge/index.ts b/libs/coin-modules/coin-stacks/src/bridge/index.ts index b86b691d6bb6..07e79b6362e8 100644 --- a/libs/coin-modules/coin-stacks/src/bridge/index.ts +++ b/libs/coin-modules/coin-stacks/src/bridge/index.ts @@ -61,4 +61,3 @@ export function createBridges(signerContext: SignerContext) { accountBridge: buildAccountBridge(signerContext), }; } -export { prepareTransaction, estimateMaxSpendable }; diff --git a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts index e396d5b27f3b..7deef8c64c6f 100644 --- a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts +++ b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts @@ -65,10 +65,9 @@ async function filterOperations( ): Promise { const operations = await listOperations(address, startAt); - return operations - .filter(op => op.type === "Payment") - .map(op => { - return { + return operations.map( + op => + ({ id: encodeOperationId(accountId, op.hash, op.simpleType), hash: op.hash, accountId, @@ -82,8 +81,8 @@ async function filterOperations( date: op.date, transactionSequenceNumber: op.transactionSequenceNumber, extra: {}, - } satisfies Operation; - }); + }) satisfies Operation, + ); } async function calculateSpendableBalance( diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts index 10f9015e8bef..9b7a570ac271 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts @@ -1,10 +1,10 @@ import { getServerInfos, getTransactions } from "../network"; import type { XrplOperation } from "../network/types"; -import { XrpOperation } from "../types"; +import { XrpMemo, XrpOperation } from "../types"; import { RIPPLE_EPOCH } from "./utils"; /** - * Returns list of operations associated to an account. + * Returns list of "Payment" Operations associated to an account. * @param address Account address * @param blockHeight Height to start searching for operations * @returns @@ -26,7 +26,9 @@ export async function listOperations( ledger_index_max: maxLedgerVersion, }); - return transactions.map(convertToCoreOperation(address)); + return transactions + .filter(op => op.tx.TransactionType === "Payment") + .map(convertToCoreOperation(address)); } const convertToCoreOperation = @@ -71,7 +73,7 @@ const convertToCoreOperation = }; } - const memos = Memos?.map(m => { + const memos: XrpMemo[] | undefined = Memos?.map(m => { const memo = { data: m?.Memo?.MemoData, format: m?.Memo?.MemoFormat, diff --git a/libs/coin-modules/coin-xrp/src/types/model.ts b/libs/coin-modules/coin-xrp/src/types/model.ts index 1a77064f7043..66a9042c5eb0 100644 --- a/libs/coin-modules/coin-xrp/src/types/model.ts +++ b/libs/coin-modules/coin-xrp/src/types/model.ts @@ -5,6 +5,11 @@ export type AccountInfo = { sequence: number; }; +export type XrpMemo = { + data?: string; + format?: string; + type?: string; +}; export type XrpOperation = { hash: string; address: string; @@ -19,10 +24,6 @@ export type XrpOperation = { transactionSequenceNumber: number; details?: { destinationTag?: number; - memos?: { - data?: string; - format?: string; - type?: string; - }; + memos?: XrpMemo[]; }; }; diff --git a/libs/ledger-live-common/src/families/stacks/setup.ts b/libs/ledger-live-common/src/families/stacks/setup.ts index 4cf8dfff70f5..15784daceae7 100644 --- a/libs/ledger-live-common/src/families/stacks/setup.ts +++ b/libs/ledger-live-common/src/families/stacks/setup.ts @@ -2,7 +2,7 @@ import { createBridges } from "@ledgerhq/coin-stacks"; import stacksResolver, { signMessage } from "@ledgerhq/coin-stacks/signer/index"; -import makeCliTools from "@ledgerhq/coin-xrp/test/cli"; +import makeCliTools from "@ledgerhq/coin-stacks/test/cli"; import Transport from "@ledgerhq/hw-transport"; import { Bridge } from "@ledgerhq/types-live"; import BlockstackApp from "@zondax/ledger-stacks"; diff --git a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts index 760d63b7b13c..029e2c664990 100644 --- a/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts +++ b/libs/ledger-live-common/src/featureFlags/defaultFeatures.ts @@ -526,6 +526,7 @@ export const DEFAULT_FEATURES: Features = { hourly: 2 * 24 * 60 * 60 * 1000, }, }, + llmRebornLP: { ...DEFAULT_FEATURE, params: { variant: ABTestingVariants.variantA } }, }; // Firebase SDK treat JSON values as strings diff --git a/libs/ledgerjs/packages/types-live/src/feature.ts b/libs/ledgerjs/packages/types-live/src/feature.ts index 1aeafe79608f..54960fc27da4 100644 --- a/libs/ledgerjs/packages/types-live/src/feature.ts +++ b/libs/ledgerjs/packages/types-live/src/feature.ts @@ -202,6 +202,7 @@ export type Features = CurrencyFeatures & { llMevProtection: DefaultFeature; llmNetworkBasedAddAccountFlow: DefaultFeature; llCounterValueGranularitiesRates: Feature_LlCounterValueGranularitiesRates; + llmRebornLP: Feature_LlmRebornLP; }; /** @@ -569,6 +570,9 @@ export type Feature_SpamFilteringTx = DefaultFeature; export type Feature_MemoTag = DefaultFeature; export type Feature_RecoverUpsellRedirection = DefaultFeature; +export type Feature_LlmRebornLP = Feature<{ + variant: ABTestingVariants; +}>; /** * Utils types. */ diff --git a/libs/ui/packages/react/.storybook/preview.tsx b/libs/ui/packages/react/.storybook/preview.tsx index 68921f1d6776..95f3efcbf4bd 100644 --- a/libs/ui/packages/react/.storybook/preview.tsx +++ b/libs/ui/packages/react/.storybook/preview.tsx @@ -6,7 +6,7 @@ import { StyleProvider } from "../src/styles"; export const decorators = [ (Story, { globals }) => { const backgrounds = globals?.backgrounds ?? {}; - const theme = backgrounds?.value === palettes.dark.background.main ? "dark" : "light"; + const theme = backgrounds?.value === palettes.dark.background.default ? "dark" : "light"; return ( @@ -29,11 +29,11 @@ export const parameters = { values: [ { name: "light", - value: palettes.light.background.main, + value: palettes.light.background.default, }, { name: "dark", - value: palettes.dark.background.main, + value: palettes.dark.background.default, }, ], }, diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx index fe055f4d9e2e..f5a50a0a9bc4 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx @@ -1,14 +1,15 @@ +import type { Meta, StoryObj } from "@storybook/react"; import React from "react"; import Carousel from "./"; import { Props } from "./types"; -const CarouselStory = (args: Props) => { - const slides = [...Array(5)].map((_, index) => ( +const CarouselStory = (args: Omit & { children: number }) => { + const slides = Array.from({ length: args.children }, (_, index) => (
@@ -24,16 +25,18 @@ export default { argTypes: { children: { description: "The elements to be displayed.", + control: { type: "range", min: 1, max: 10, step: 1 }, }, variant: { description: "Variant for the carousel.", options: ["default", "content-card"], defaultValue: "default", - control: { type: "select" }, + control: "inline-radio", }, }, args: { variant: "default", + children: 5, }, parameters: { docs: { @@ -42,6 +45,7 @@ export default { }, }, }, -}; + render: CarouselStory, +} satisfies Meta; -export const Default = CarouselStory.bind({}); +export const Default: StoryObj = {}; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx b/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx new file mode 100644 index 000000000000..ed2f51322e54 --- /dev/null +++ b/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import styled from "styled-components"; +import { Icons } from "../../../assets"; + +type Props = ContainerProps & { + onClick: () => void; +}; + +export function ChevronArrow(props: Props) { + return ( + + + + ); +} + +type ContainerProps = { + direction: "left" | "right"; +}; + +const ChevronArrowContainer = styled.button` + display: inline-flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + + position: absolute; + top: 50%; + left: ${({ direction }) => (direction === "left" ? "0" : "auto")}; + right: ${({ direction }) => (direction === "left" ? "auto" : "0")}; + z-index: 1; + + --dir: ${({ direction }) => (direction === "left" ? "1" : "-1")}; + scale: var(--dir) 1; + translate: calc(-50% * var(--dir)) -50%; + + background-color: ${({ theme }) => theme.colors.background.default}; // Fake the transparent clip + border-radius: 100%; + border: none; + outline: none; + + ::before { + content: ""; + display: block; + position: absolute; + z-index: -1; + inset: 3px; + background-color: ${({ theme }) => theme.colors.opacityDefault.c05}; + border-color: ${({ theme }) => theme.colors.opacityDefault.c05}; + border-radius: 100%; + border-style: solid; + border-width: 1px; + } + svg { + color: ${({ theme }) => theme.colors.primary.c100}; + } + ::before, + svg { + cursor: pointer; + } + + transition: opacity 0.2s ease-in-out; + opacity: var(--hover-transition); + ::before, + svg { + transition: translate 0.2s ease-in-out; + translate: calc(-50% + 50% * var(--hover-transition)) 0; + } +`; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx index db5019e6bab8..62f40352ac8b 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx @@ -13,7 +13,7 @@ const Pagination = ({ children, currentIndex }: SubProps) => { return ( {children.map((child, index) => ( - + ))} ); diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts index 1920f750ffc3..763a629a47c3 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts @@ -3,17 +3,17 @@ import { ItemStatus } from "./types"; /** * Returns the status of an indexed item from the carousel index. */ -export const getItemStatus = (itemIndex: number, activeIndex: number) => { - const itemDistanceFromActiveIndex = Math.abs(itemIndex - activeIndex); +export const getItemStatus = (itemIndex: number, activeIndex: number, itemCount: number) => { + const isActive = itemIndex === activeIndex; + if (isActive) { + return ItemStatus.active; + } - switch (itemDistanceFromActiveIndex) { - case 0: - return ItemStatus.active; - case 1: - return ItemStatus.nearby; - case 2: - return ItemStatus.far; - default: - return ItemStatus.none; + const isAdjacent = Math.abs(itemIndex - activeIndex) === 1; + if (isAdjacent) { + return ItemStatus.nearby; } + + const isEdge = itemIndex === 0 || itemIndex === itemCount - 1; + return isEdge ? ItemStatus.far : ItemStatus.nearby; }; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx index fa04654914a4..f9c809076a7d 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx @@ -9,6 +9,8 @@ const Footers: { [key in Variant]: FC } = { }; const Footer = (props: SubProps) => { + if (props.children.length === 1) return null; + const Component = Footers[props.variant]; return ; }; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx index 879384a7e1ca..a809b5c1674b 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx @@ -25,8 +25,6 @@ const FooterArrowContainer = styled.div` `; const FooterContentCard = ({ children, emblaApi, currentIndex, variant }: SubProps) => { - if (children.length === 1) return null; - return ( >` + position: relative; + + --hover-transition: 0; + &:hover { + --hover-transition: 1; + } +`; + /** * This component uses the https://github.com/davidjerleke/embla-carousel library. */ @@ -45,15 +55,27 @@ const Carousel = ({ children, variant = "default" }: Props) => { emblaApi.on("reInit", updateIndex); }, [emblaApi, updateIndex]); + const handleGotoPrevSlide = () => emblaApi?.scrollPrev(); + const handleGotoNextSlide = () => emblaApi?.scrollNext(); + return (
- - - {children.map(child => ( - {child} - ))} - - + + {variant === "default" && children.length > 1 && ( + <> + + + + )} + + + + {children.map(child => ( + {child} + ))} + + +
); }; + export default Carousel; diff --git a/libs/ui/packages/shared/palettes/dark.ts b/libs/ui/packages/shared/palettes/dark.ts index 4d798a8fdc72..8977e93cd00d 100644 --- a/libs/ui/packages/shared/palettes/dark.ts +++ b/libs/ui/packages/shared/palettes/dark.ts @@ -107,8 +107,10 @@ export default { black: "#000000", }, background: { + default: "#131214", main: "#131214", drawer: "#1D1C1F", + card: "#1C1D1F", }, effects: { dropShadow: "rgba(0, 0, 0, 0.48)", diff --git a/libs/ui/packages/shared/palettes/light.ts b/libs/ui/packages/shared/palettes/light.ts index 81a7e796c331..6c78cdad9397 100644 --- a/libs/ui/packages/shared/palettes/light.ts +++ b/libs/ui/packages/shared/palettes/light.ts @@ -107,8 +107,10 @@ export default { black: "#000000", }, background: { + default: "#F9F9F9", main: "#FFFFFF", drawer: "#FFFFFF", + card: "#FFFFFF", }, effects: { dropShadow: "rgba(0, 0, 0, 0.16)",