├── .env.example
├── .eslintrc.cjs
├── .gitignore
├── .husky
├── pre-commit
└── pre-push
├── .nvmrc
├── .swcrc
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── index.html
├── jest.config.cjs
├── jest
├── CSSStub.ts
├── setupTests.ts
└── svgTransformer.ts
├── package.json
├── public
├── css
│ ├── hirocoool.webflow.css
│ ├── normalize.css
│ └── webflow.css
├── favicon.png
├── favicon.png:Zone.Identifier
├── favicon.svg
├── fonts
│ ├── Bueno-Black.ttf
│ ├── Bueno-Bold.ttf
│ ├── Bueno-Light.otf
│ ├── Bueno-Light.ttf
│ ├── Bueno-Medium.otf
│ ├── Bueno-Regular.otf
│ ├── TWKEverett-Bold.otf
│ ├── TWKEverett-Extrabold-web.ttf
│ ├── TWKEverett-Light.otf
│ ├── TWKEverett-LightItalic.otf
│ ├── TWKEverett-Medium.otf
│ ├── TWKEverett-Regular.otf
│ ├── TWKEverett-Super.otf
│ ├── TWKEverett-Thin.otf
│ └── TWKEverett-UltralightItalic.otf
├── images
│ ├── 2D-Shape-p-500.png
│ ├── 2D-Shape.png
│ ├── Abraham-Cobos-p-500.jpeg
│ ├── Abraham-Cobos-p-800.jpeg
│ ├── Abraham-Cobos.jpeg
│ ├── BITCOIN-WRAPPED.png
│ ├── Background-p-1080.png
│ ├── Background-p-500.png
│ ├── Background-p-800.png
│ ├── Background.png
│ ├── Base-p-1080.jpeg
│ ├── Base-p-500.jpeg
│ ├── Base-p-800.jpeg
│ ├── Base.jpeg
│ ├── CheckGreen.svg
│ ├── Eduardo-Rios-p-500.jpeg
│ ├── Eduardo-Rios.jpeg
│ ├── Ellipse-2069.png
│ ├── Ellipse-2070.png
│ ├── Ellipse-2071.png
│ ├── Hiro-logo.png
│ ├── Imagen-deposito-p-500.png
│ ├── Imagen-deposito.png
│ ├── Luis-Gabriel.jpeg
│ ├── Model-Cone.png
│ ├── Model-Sphere.png
│ ├── Model-Tank.png
│ ├── OG.png
│ ├── Portafolio-p-1080.png
│ ├── Portafolio-p-500.png
│ ├── Portafolio-p-800.png
│ ├── Portafolio.png
│ ├── Safe-p-1080.png
│ ├── Safe-p-1600.png
│ ├── Safe-p-500.png
│ ├── Safe-p-800.png
│ ├── Safe.png
│ ├── T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512-p-500.png
│ ├── T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512.png
│ ├── USDM.png
│ ├── Vector-1.png
│ ├── Vector-10.png
│ ├── Vector-2.png
│ ├── Vector.png
│ ├── Vector_1.png
│ ├── WhatsApp-Image-2023-08-28-at-19.51.31-p-1080.jpeg
│ ├── WhatsApp-Image-2023-08-28-at-19.51.31-p-500.jpeg
│ ├── WhatsApp-Image-2023-08-28-at-19.51.31-p-800.jpeg
│ ├── WhatsApp-Image-2023-08-28-at-19.51.31.jpeg
│ ├── bandoSwap.png
│ ├── bando_full_green.png
│ ├── bgKyc.png
│ ├── bgKyc2.png
│ ├── ezgif-2-8ea0987634.gif
│ ├── favicon.png
│ ├── gorduki-nounish-512-p-500.png
│ ├── gorduki-nounish-512.png
│ ├── image-58.png
│ ├── image-58_1.png
│ ├── image-60.png
│ ├── image-60_1.png
│ ├── image-61.png
│ ├── image-61_1.png
│ ├── image-62.png
│ ├── ramp.gif
│ ├── shiba.webp
│ ├── steth-steth-logo.png
│ └── webclip.png
└── js
│ └── webflow.js
├── src
├── assets
│ ├── ArrowDown.svg
│ ├── CaretDown.svg
│ ├── CaretGreen.svg
│ ├── CaretWhite.svg
│ ├── CheckGreen.svg
│ ├── CopyToClipboard.svg
│ ├── Cross.svg
│ ├── Mexico-01.svg
│ ├── Mexico.svg
│ ├── TokenPlaceholder.svg
│ ├── TokenPlaceholderGray.svg
│ ├── TokenPlaceholderGray.svg:Zone.Identifier
│ ├── TransactionArrow.svg
│ ├── UpDownArrow.svg
│ ├── arbitrum.svg
│ ├── bg-drawer.png
│ ├── chains
│ │ ├── arbitrum.png
│ │ ├── bnb.png
│ │ ├── brett.png
│ │ ├── celo.png
│ │ ├── ceur.png
│ │ ├── cusd.png
│ │ ├── degen.png
│ │ ├── eth.png
│ │ ├── maga.png
│ │ ├── matic.png
│ │ ├── mpeth.png
│ │ ├── optimism.png
│ │ ├── usdb.png
│ │ ├── usdc.png
│ │ ├── usdt.png
│ │ └── weth.png
│ ├── currency.svg
│ ├── hielocos.png
│ ├── image-58_1.png
│ ├── ine_ref.png
│ ├── killb-logo.svg
│ ├── kycBg.svg
│ ├── logo-bando.svg
│ ├── logo.svg
│ ├── logo_white.svg
│ ├── networks
│ │ ├── arbitrum.png
│ │ ├── base.png
│ │ ├── binance-smart-chain.png
│ │ ├── binance-smart-chain.svg
│ │ ├── blast.png
│ │ ├── celo.png
│ │ ├── ethereum.png
│ │ ├── optimism.png
│ │ ├── polygon.png
│ │ ├── scroll.png
│ │ └── solana.svg
│ ├── telegram.svg
│ ├── transactions.svg
│ └── usdt.svg
├── components
│ ├── Alert
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── BoxContainer
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── Button
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── CurrencyImg
│ │ └── index.tsx
│ ├── DialogDrawer
│ │ └── index.tsx
│ ├── Footer
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── Hr
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── I18nSwitcher
│ │ └── index.tsx
│ ├── Jumbotron
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── KycBulletPoints
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── LimitUsage
│ │ └── index.tsx
│ ├── MarkDownContainer
│ │ └── index.tsx
│ ├── Navbar
│ │ ├── DrawerLink.tsx
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── PageTitle
│ │ └── index.tsx
│ ├── ProgressBar
│ │ └── index.tsx
│ ├── RampDirectionTabs
│ │ └── index.tsx
│ ├── SimpleFooter
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── SiteSpinner
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── StatusBadge
│ │ └── index.tsx
│ ├── StatusCircle
│ │ └── index.tsx
│ ├── Svgs
│ │ ├── ArrowCircle.tsx
│ │ └── Logout.tsx
│ ├── TokensWidget
│ │ ├── NetworkTiles.tsx
│ │ ├── TokensList.tsx
│ │ ├── alerts.tsx
│ │ ├── components.tsx
│ │ ├── helpers.ts
│ │ └── index.tsx
│ ├── TransactionDetails
│ │ ├── RateText.tsx
│ │ ├── TransactionCopyText.tsx
│ │ ├── index.tsx
│ │ └── mapProviderStatus.ts
│ ├── TransactionsTable
│ │ ├── CellDetailWithIcon.tsx
│ │ ├── TableComponents.tsx
│ │ ├── TransactionRow.tsx
│ │ └── index.tsx
│ ├── UserCard
│ │ └── index.tsx
│ ├── UserMenu
│ │ └── index.tsx
│ └── forms
│ │ ├── ErrorBox
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ │ ├── GetQuoteForm
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ ├── schema.ts
│ │ └── v2
│ │ │ └── index.tsx
│ │ ├── Input
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ │ ├── KycForm
│ │ ├── index.tsx
│ │ └── schema.ts
│ │ ├── MuiInput
│ │ ├── MuiPhoneInput.tsx
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ └── styles.ts
│ │ ├── MuiSelect
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ │ ├── OnboardingForm
│ │ └── index.tsx
│ │ ├── PlacesAutocomplete
│ │ └── index.tsx
│ │ ├── RampForm
│ │ ├── CurrencyPill.tsx
│ │ ├── RampTitle.tsx
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ └── schema.ts
│ │ └── Select
│ │ ├── index.test.tsx
│ │ └── index.tsx
├── config
│ ├── axios.ts
│ ├── constants
│ │ ├── chains.ts
│ │ ├── countries.ts
│ │ ├── currencies.test.tsx
│ │ ├── currencies.tsx
│ │ ├── identification.ts
│ │ ├── links.ts
│ │ └── networks.ts
│ ├── endpoints.ts
│ ├── env.test.ts
│ ├── env.ts
│ ├── firebase
│ │ ├── index.ts
│ │ ├── remoteConfig.test.ts
│ │ └── remoteConfig.ts
│ ├── sentry.ts
│ ├── tapfiliate.ts
│ └── theme.ts
├── global.d.ts
├── helpers
│ ├── TestProvider.tsx
│ ├── formatNumber.ts
│ ├── formatWalletNumber.ts
│ ├── getStorageQuote.ts
│ ├── getUserLanguage.ts
│ ├── input.test.ts
│ ├── inputs.ts
│ ├── phoneValidation.ts
│ ├── regexValidators.ts
│ └── validateSolanaAddress.ts
├── hooks
│ ├── useAuthCookie
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── useKyc
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ └── requests.ts
│ ├── useMagic
│ │ └── index.ts
│ ├── useMagicLinkAuth
│ │ ├── MagicProvider.tsx
│ │ ├── index.tsx
│ │ └── requests.ts
│ ├── useNetworks
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ ├── mock.ts
│ │ └── requests.ts
│ ├── useQuote
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ └── requests.ts
│ ├── useRecipient
│ │ ├── index.test.tsx
│ │ ├── index.ts
│ │ └── requests.ts
│ ├── useRemoteConfig
│ │ ├── RemoteConfigProvider.test.tsx
│ │ ├── RemoteConfigProvider.tsx
│ │ └── index.ts
│ ├── useTokens
│ │ ├── index.test.tsx
│ │ ├── index.ts
│ │ ├── mock.ts
│ │ └── requests.ts
│ ├── useTransaction
│ │ ├── index.test.tsx
│ │ ├── index.ts
│ │ └── requests.ts
│ ├── useTransactions
│ │ ├── index.test.tsx
│ │ ├── index.ts
│ │ └── requests.ts
│ └── useUser
│ │ ├── IntercomProvider.tsx
│ │ ├── MagicUserProvider.tsx
│ │ ├── index.tsx
│ │ ├── requests.ts
│ │ └── types.ts
├── index.css
├── layouts
│ ├── CleanLayout
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── ColumnLayout
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ ├── EmptyLayout
│ │ ├── index.test.tsx
│ │ └── index.tsx
│ └── LandingLayout
│ │ ├── index.test.tsx
│ │ └── index.tsx
├── main.tsx
├── mui.d.ts
├── pages
│ ├── FAQ
│ │ └── index.tsx
│ ├── Kyc
│ │ └── index.tsx
│ ├── Landing
│ │ └── index.tsx
│ ├── PrivacyNotice
│ │ └── index.tsx
│ ├── ProtectedRamp
│ │ └── index.tsx
│ ├── Ramp
│ │ └── index.tsx
│ ├── SignIn
│ │ ├── index.tsx
│ │ └── schema.ts
│ ├── Signup
│ │ └── index.tsx
│ ├── Terms
│ │ └── index.tsx
│ └── Transactions
│ │ ├── Detail
│ │ └── index.tsx
│ │ ├── History
│ │ └── index.tsx
│ │ └── KycDetail
│ │ └── index.tsx
├── routes
│ ├── ExposedWrapper.tsx
│ ├── OnlyGuestWrapper.tsx
│ ├── ProtectedWrapper.tsx
│ └── index.tsx
├── tapfiliate.d.ts
├── translations
│ ├── en
│ │ ├── faq.ts
│ │ ├── footer.ts
│ │ ├── form.ts
│ │ ├── index.ts
│ │ ├── kycPoints.ts
│ │ ├── landing.ts
│ │ ├── quote.ts
│ │ ├── ramp.ts
│ │ ├── transactionDetail.ts
│ │ ├── transactions.ts
│ │ └── userMenu.ts
│ ├── es
│ │ ├── faq.ts
│ │ ├── footer.ts
│ │ ├── form.ts
│ │ ├── index.ts
│ │ ├── kycPoints.ts
│ │ ├── landing.ts
│ │ ├── quote.ts
│ │ ├── ramp.ts
│ │ ├── transactionDetail.ts
│ │ ├── transactions.ts
│ │ └── userMenu.ts
│ ├── index.test.tsx
│ └── index.ts
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
├── vercel.json
├── vite.config.ts
└── yarn.lock
/.env.example:
--------------------------------------------------------------------------------
1 | VITE_APP_TITLE=Bando
2 | VITE_APP_DESCRIPTION=Bando - Tu cuenta inteligente de cripto
3 | VITE_HEAP_ID=1913562785
4 | VITE_SENTRY_DSN="https://te.st"
5 | TAPFILIATE_ACCOUNT_ID=
6 |
7 | AUTH_COOKIE_NAME=bando_local
8 | API=http://localhost:8000
9 | GOOGLE_MAPS_API_KEY=
10 |
11 | MAGIC_LINK_SECRET=
12 | MAGIC_LINK_RPC_URL=
13 | MAGIC_LINK_CHAIN_ID=11155111
14 |
15 | INTERCOM_APP_ID=
16 |
17 | # Firebase
18 | FIREBASE_API_KEY=
19 | FIREBASE_AUTH_DOMAIN=
20 | FIREBASE_PROJECT_ID=
21 | FIREBASE_STORAGE_BUCKET=
22 | FIREBASE_MESSAGING_SENDER_ID=
23 | FIREBASE_APP_ID=
24 | FIREBASE_MEASUREMENT_ID=
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | coverage
12 | test_reports
13 | dist
14 | dist-ssr
15 | *.local
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 | .env
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn lint
5 |
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | yarn test-ci
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.12.2
2 |
--------------------------------------------------------------------------------
/.swcrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "jsc": {
4 | "target": "es2020",
5 | "externalHelpers": false,
6 | "parser": {
7 | "syntax": "typescript",
8 | "tsx": true,
9 | "decorators": false,
10 | "dynamicImport": false
11 | },
12 | "transform": {
13 | "react": {
14 | "pragma": "React.createElement",
15 | "pragmaFrag": "React.Fragment",
16 | "throwIfNamespace": true,
17 | "development": false,
18 | "useBuiltins": false,
19 | "runtime": "automatic"
20 | },
21 | "hidden": {
22 | "jest": true
23 | }
24 | }
25 | },
26 | "module": {
27 | "type": "commonjs",
28 | "strict": false,
29 | "strictMode": true,
30 | "noInterop": false,
31 | "lazy": false
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributing to React Bando
3 |
4 | 🎉 Thank you for your interest in contributing to React Bando! 🎉
5 |
6 | We welcome contributions from everyone. By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md).
7 |
8 | ## How to Contribute
9 |
10 | ### Reporting Bugs
11 |
12 | If you encounter a bug or issue with the project, please follow these steps:
13 |
14 | 1. Check the [existing issues](https://github.com/bandohq/react-bando/issues) to see if the issue has already been reported.
15 | 2. If it hasn't, [create a new issue](https://github.com/bandohq/react-bando/issues/new) with a descriptive title and detailed description of the problem, including steps to reproduce if possible.
16 |
17 | ### Suggesting Enhancements
18 |
19 | We welcome suggestions for how we can improve React Bando. If you have an idea for an enhancement, follow these steps:
20 |
21 | 1. Check the [existing issues](https://github.com/bandohq/react-bando/issues) to see if the enhancement has already been suggested.
22 | 2. If it hasn't, [create a new issue](https://github.com/bandohq/react-bando/issues/new) with a clear title and description of your enhancement idea.
23 |
24 | ### Pull Requests
25 |
26 | We actively welcome your pull requests! To contribute code to React Bando, follow these steps:
27 |
28 | 1. Fork the repository and create your own branch off the `main` branch.
29 | 2. Make your changes and ensure the code is well-documented and tested.
30 | 3. Open a pull request (PR) to the `main` branch of the original repository, describing the changes you've made.
31 |
32 | ### Code Style
33 |
34 | - Follow the coding style and conventions used throughout the project.
35 | - Ensure any new code adheres to existing patterns and practices.
36 | - Write clear commit messages and PR descriptions.
37 |
38 | ### Code of Conduct
39 |
40 | This project and everyone participating in it is governed by our [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [maintainer's email].
41 |
42 | ### License
43 |
44 | By contributing to React Bando, you agree that your contributions will be licensed under the [LICENSE](LICENSE) file of the project.
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Bando
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 1.0.0 | :white_check_mark: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | To report vulnerabilities please email to platform@bando.cool with the following:
15 | - Description of the finding
16 | - How to replicate the vulnerability
17 |
18 | We'll review your submission and contact about its acceptance or rejection.
19 |
--------------------------------------------------------------------------------
/jest.config.cjs:
--------------------------------------------------------------------------------
1 | const defaultCoverage = {
2 | branches: 80,
3 | functions: 85,
4 | lines: 95,
5 | statements: 90,
6 | };
7 |
8 | module.exports = {
9 | roots: ['/src'],
10 | collectCoverage: true,
11 | collectCoverageFrom: [
12 | '**/*.{js,jsx,ts,tsx}',
13 | '!**/*.d.ts',
14 | '!__mocks__/**',
15 | '!.next/**',
16 | '!coverage/**',
17 | '!.eslintrc.js',
18 | '!jest/**',
19 | '!src/helpers/**',
20 | '!src/config/axios.ts',
21 | ],
22 | coverageThreshold: {
23 | global: defaultCoverage,
24 | 'src/components/**': defaultCoverage,
25 | 'src/hooks/**': defaultCoverage,
26 | // 'src/store/**': defaultCoverage,
27 | // 'src/layouts/**': defaultCoverage,
28 | // 'src/pages/**': defaultCoverage,
29 | 'src/config/**': defaultCoverage,
30 | },
31 | setupFiles: ['fake-indexeddb/auto'],
32 | setupFilesAfterEnv: ['jest-extended/all', './jest/setupTests.ts'],
33 | testEnvironment: 'jsdom',
34 | transform: {
35 | '^.+\\.(ts|js|tsx|jsx|mjs)$': ['@swc/jest'],
36 | '^.+\\.svg$': '/jest/svgTransformer.ts',
37 | },
38 | transformIgnorePatterns: [
39 | '[/\\\\]node_modules[/\\\\](?!(@uppy|nanoid|preact|exifr|camelize-ts)).+\\.(js|jsx|mjs|cjs|ts|tsx)$',
40 | '^.+\\.module\\.(css|sass|scss)$',
41 | ],
42 | modulePaths: ['/src'],
43 | moduleNameMapper: {
44 | '^.+\\.(css|scss|png)$': '/jest/CSSStub.ts',
45 | '@config/(.*)': '/src/config/$1',
46 | '@pages/(.*)': '/src/pages/$1',
47 | '@layouts/(.*)': '/src/layouts/$1',
48 | '@components/(.*)': '/src/components/$1',
49 | '@translations/(.*)': '/src/translations/$1',
50 | '@assets/(.*)': '/src/assets/$1',
51 | '@styles/(.*)': '/src/styles/$1',
52 | '@hooks/(.*)': '/src/hooks/$1',
53 | '@store/(.*)': '/src/store/$1',
54 | '@helpers/(.*)': '/src/helpers/$1',
55 | },
56 | moduleFileExtensions: ['tsx', 'ts', 'js', 'json', 'jsx', 'node'],
57 | watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],
58 | resetMocks: true,
59 | testEnvironmentOptions: {
60 | customExportConditions: [''],
61 | },
62 | };
63 |
--------------------------------------------------------------------------------
/jest/CSSStub.ts:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/jest/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 |
6 | import '@testing-library/jest-dom';
7 | import 'jest-localstorage-mock';
8 |
9 | import { TextEncoder, TextDecoder } from 'util';
10 | import { Response } from 'cross-fetch';
11 |
12 | jest.mock('@intercom/messenger-js-sdk');
13 |
14 | process.env.API = 'https://api.com';
15 | process.env.AUTH_COOKIE_NAME = 'bando_test';
16 |
17 | console.warn = jest.fn();
18 | console.error = jest.fn();
19 | global.window = Object.create(window);
20 |
21 | HTMLElement.prototype.scrollIntoView = jest.fn();
22 | Object.defineProperty(window, 'location', {
23 | value: {
24 | href: '',
25 | replace: jest.fn(),
26 | origin: 'https://te.st',
27 | pathname: '/',
28 | },
29 | });
30 |
31 | global.ResizeObserver = require('resize-observer-polyfill');
32 | Object.assign(global, { TextDecoder, TextEncoder, Response });
33 |
34 | const asString = jest.fn().mockImplementation(() => '{}');
35 | const asBoolean = jest.fn().mockImplementation(() => true);
36 | const asNumber = jest.fn().mockImplementation(() => 1);
37 | const remoteConfigValue = {
38 | asString,
39 | asBoolean,
40 | asNumber,
41 | };
42 |
43 | jest.mock('firebase/remote-config', () => ({
44 | getAll: jest.fn().mockReturnValue({
45 | USE_GOOGLE_AUTOCOMPLETE: remoteConfigValue,
46 | }),
47 | getValue: jest.fn().mockReturnValue(remoteConfigValue),
48 | getRemoteConfig: jest.fn().mockImplementation(() => ({})),
49 | fetchAndActivate: jest.fn().mockResolvedValue(true),
50 | }));
51 |
--------------------------------------------------------------------------------
/jest/svgTransformer.ts:
--------------------------------------------------------------------------------
1 | // See https://stackoverflow.com/questions/58603201/jest-cannot-load-svg-file
2 | module.exports = {
3 | process() {
4 | return {
5 | code: 'module.exports = {};',
6 | };
7 | },
8 | getCacheKey() {
9 | // The output is always the same.
10 | return 'svgTransform';
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/favicon.png
--------------------------------------------------------------------------------
/public/favicon.png:Zone.Identifier:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/favicon.png:Zone.Identifier
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/fonts/Bueno-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Black.ttf
--------------------------------------------------------------------------------
/public/fonts/Bueno-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Bold.ttf
--------------------------------------------------------------------------------
/public/fonts/Bueno-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Light.otf
--------------------------------------------------------------------------------
/public/fonts/Bueno-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Light.ttf
--------------------------------------------------------------------------------
/public/fonts/Bueno-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Medium.otf
--------------------------------------------------------------------------------
/public/fonts/Bueno-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/Bueno-Regular.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Bold.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Extrabold-web.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Extrabold-web.ttf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Light.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-LightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-LightItalic.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Medium.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Regular.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Super.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Super.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-Thin.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-Thin.otf
--------------------------------------------------------------------------------
/public/fonts/TWKEverett-UltralightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/fonts/TWKEverett-UltralightItalic.otf
--------------------------------------------------------------------------------
/public/images/2D-Shape-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/2D-Shape-p-500.png
--------------------------------------------------------------------------------
/public/images/2D-Shape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/2D-Shape.png
--------------------------------------------------------------------------------
/public/images/Abraham-Cobos-p-500.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Abraham-Cobos-p-500.jpeg
--------------------------------------------------------------------------------
/public/images/Abraham-Cobos-p-800.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Abraham-Cobos-p-800.jpeg
--------------------------------------------------------------------------------
/public/images/Abraham-Cobos.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Abraham-Cobos.jpeg
--------------------------------------------------------------------------------
/public/images/BITCOIN-WRAPPED.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/BITCOIN-WRAPPED.png
--------------------------------------------------------------------------------
/public/images/Background-p-1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Background-p-1080.png
--------------------------------------------------------------------------------
/public/images/Background-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Background-p-500.png
--------------------------------------------------------------------------------
/public/images/Background-p-800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Background-p-800.png
--------------------------------------------------------------------------------
/public/images/Background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Background.png
--------------------------------------------------------------------------------
/public/images/Base-p-1080.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Base-p-1080.jpeg
--------------------------------------------------------------------------------
/public/images/Base-p-500.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Base-p-500.jpeg
--------------------------------------------------------------------------------
/public/images/Base-p-800.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Base-p-800.jpeg
--------------------------------------------------------------------------------
/public/images/Base.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Base.jpeg
--------------------------------------------------------------------------------
/public/images/CheckGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/images/Eduardo-Rios-p-500.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Eduardo-Rios-p-500.jpeg
--------------------------------------------------------------------------------
/public/images/Eduardo-Rios.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Eduardo-Rios.jpeg
--------------------------------------------------------------------------------
/public/images/Ellipse-2069.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Ellipse-2069.png
--------------------------------------------------------------------------------
/public/images/Ellipse-2070.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Ellipse-2070.png
--------------------------------------------------------------------------------
/public/images/Ellipse-2071.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Ellipse-2071.png
--------------------------------------------------------------------------------
/public/images/Hiro-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Hiro-logo.png
--------------------------------------------------------------------------------
/public/images/Imagen-deposito-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Imagen-deposito-p-500.png
--------------------------------------------------------------------------------
/public/images/Imagen-deposito.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Imagen-deposito.png
--------------------------------------------------------------------------------
/public/images/Luis-Gabriel.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Luis-Gabriel.jpeg
--------------------------------------------------------------------------------
/public/images/Model-Cone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Model-Cone.png
--------------------------------------------------------------------------------
/public/images/Model-Sphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Model-Sphere.png
--------------------------------------------------------------------------------
/public/images/Model-Tank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Model-Tank.png
--------------------------------------------------------------------------------
/public/images/OG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/OG.png
--------------------------------------------------------------------------------
/public/images/Portafolio-p-1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Portafolio-p-1080.png
--------------------------------------------------------------------------------
/public/images/Portafolio-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Portafolio-p-500.png
--------------------------------------------------------------------------------
/public/images/Portafolio-p-800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Portafolio-p-800.png
--------------------------------------------------------------------------------
/public/images/Portafolio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Portafolio.png
--------------------------------------------------------------------------------
/public/images/Safe-p-1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Safe-p-1080.png
--------------------------------------------------------------------------------
/public/images/Safe-p-1600.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Safe-p-1600.png
--------------------------------------------------------------------------------
/public/images/Safe-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Safe-p-500.png
--------------------------------------------------------------------------------
/public/images/Safe-p-800.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Safe-p-800.png
--------------------------------------------------------------------------------
/public/images/Safe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Safe.png
--------------------------------------------------------------------------------
/public/images/T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512-p-500.png
--------------------------------------------------------------------------------
/public/images/T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/T04Q2NUKE93-U054FKGVAM6-2478d51583e0-512.png
--------------------------------------------------------------------------------
/public/images/USDM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/USDM.png
--------------------------------------------------------------------------------
/public/images/Vector-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Vector-1.png
--------------------------------------------------------------------------------
/public/images/Vector-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Vector-10.png
--------------------------------------------------------------------------------
/public/images/Vector-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Vector-2.png
--------------------------------------------------------------------------------
/public/images/Vector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Vector.png
--------------------------------------------------------------------------------
/public/images/Vector_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/Vector_1.png
--------------------------------------------------------------------------------
/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-1080.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-1080.jpeg
--------------------------------------------------------------------------------
/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-500.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-500.jpeg
--------------------------------------------------------------------------------
/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-800.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/WhatsApp-Image-2023-08-28-at-19.51.31-p-800.jpeg
--------------------------------------------------------------------------------
/public/images/WhatsApp-Image-2023-08-28-at-19.51.31.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/WhatsApp-Image-2023-08-28-at-19.51.31.jpeg
--------------------------------------------------------------------------------
/public/images/bandoSwap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/bandoSwap.png
--------------------------------------------------------------------------------
/public/images/bando_full_green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/bando_full_green.png
--------------------------------------------------------------------------------
/public/images/bgKyc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/bgKyc.png
--------------------------------------------------------------------------------
/public/images/bgKyc2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/bgKyc2.png
--------------------------------------------------------------------------------
/public/images/ezgif-2-8ea0987634.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/ezgif-2-8ea0987634.gif
--------------------------------------------------------------------------------
/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/favicon.png
--------------------------------------------------------------------------------
/public/images/gorduki-nounish-512-p-500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/gorduki-nounish-512-p-500.png
--------------------------------------------------------------------------------
/public/images/gorduki-nounish-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/gorduki-nounish-512.png
--------------------------------------------------------------------------------
/public/images/image-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-58.png
--------------------------------------------------------------------------------
/public/images/image-58_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-58_1.png
--------------------------------------------------------------------------------
/public/images/image-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-60.png
--------------------------------------------------------------------------------
/public/images/image-60_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-60_1.png
--------------------------------------------------------------------------------
/public/images/image-61.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-61.png
--------------------------------------------------------------------------------
/public/images/image-61_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-61_1.png
--------------------------------------------------------------------------------
/public/images/image-62.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/image-62.png
--------------------------------------------------------------------------------
/public/images/ramp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/ramp.gif
--------------------------------------------------------------------------------
/public/images/shiba.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/shiba.webp
--------------------------------------------------------------------------------
/public/images/steth-steth-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/steth-steth-logo.png
--------------------------------------------------------------------------------
/public/images/webclip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/public/images/webclip.png
--------------------------------------------------------------------------------
/src/assets/ArrowDown.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/CaretDown.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/CaretGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/CaretWhite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/CheckGreen.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/CopyToClipboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/Cross.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/Mexico.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/assets/TokenPlaceholder.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/TokenPlaceholderGray.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/assets/TokenPlaceholderGray.svg:Zone.Identifier:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/TokenPlaceholderGray.svg:Zone.Identifier
--------------------------------------------------------------------------------
/src/assets/TransactionArrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/UpDownArrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/bg-drawer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/bg-drawer.png
--------------------------------------------------------------------------------
/src/assets/chains/arbitrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/arbitrum.png
--------------------------------------------------------------------------------
/src/assets/chains/bnb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/bnb.png
--------------------------------------------------------------------------------
/src/assets/chains/brett.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/brett.png
--------------------------------------------------------------------------------
/src/assets/chains/celo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/celo.png
--------------------------------------------------------------------------------
/src/assets/chains/ceur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/ceur.png
--------------------------------------------------------------------------------
/src/assets/chains/cusd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/cusd.png
--------------------------------------------------------------------------------
/src/assets/chains/degen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/degen.png
--------------------------------------------------------------------------------
/src/assets/chains/eth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/eth.png
--------------------------------------------------------------------------------
/src/assets/chains/maga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/maga.png
--------------------------------------------------------------------------------
/src/assets/chains/matic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/matic.png
--------------------------------------------------------------------------------
/src/assets/chains/mpeth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/mpeth.png
--------------------------------------------------------------------------------
/src/assets/chains/optimism.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/optimism.png
--------------------------------------------------------------------------------
/src/assets/chains/usdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/usdb.png
--------------------------------------------------------------------------------
/src/assets/chains/usdc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/usdc.png
--------------------------------------------------------------------------------
/src/assets/chains/usdt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/usdt.png
--------------------------------------------------------------------------------
/src/assets/chains/weth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/chains/weth.png
--------------------------------------------------------------------------------
/src/assets/hielocos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/hielocos.png
--------------------------------------------------------------------------------
/src/assets/image-58_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/image-58_1.png
--------------------------------------------------------------------------------
/src/assets/ine_ref.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/ine_ref.png
--------------------------------------------------------------------------------
/src/assets/networks/arbitrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/arbitrum.png
--------------------------------------------------------------------------------
/src/assets/networks/base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/base.png
--------------------------------------------------------------------------------
/src/assets/networks/binance-smart-chain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/binance-smart-chain.png
--------------------------------------------------------------------------------
/src/assets/networks/binance-smart-chain.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/networks/blast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/blast.png
--------------------------------------------------------------------------------
/src/assets/networks/celo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/celo.png
--------------------------------------------------------------------------------
/src/assets/networks/ethereum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/ethereum.png
--------------------------------------------------------------------------------
/src/assets/networks/optimism.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/optimism.png
--------------------------------------------------------------------------------
/src/assets/networks/polygon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/polygon.png
--------------------------------------------------------------------------------
/src/assets/networks/scroll.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/assets/networks/scroll.png
--------------------------------------------------------------------------------
/src/assets/telegram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/transactions.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Alert/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import BandoAlert from '.';
3 |
4 | describe('BandoAlert', () => {
5 | it('should render BandoInfoAlert', async () => {
6 | render(
7 | ,
13 | );
14 | screen.getByText('This is an outlined info Alert.');
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/components/Alert/index.tsx:
--------------------------------------------------------------------------------
1 | import Alert, { AlertProps } from '@mui/material/Alert';
2 | import AlertTitle from '@mui/material/AlertTitle';
3 | import { styled } from '@mui/material/styles';
4 |
5 | export type BandoAlertProps = AlertProps & {
6 | title?: string;
7 | text: string;
8 | };
9 |
10 | const StyledAlert = styled(Alert)({
11 | margin: '0 8px',
12 | });
13 |
14 | export default function BandoAlert({ title, text, severity, variant, icon }: BandoAlertProps) {
15 | return (
16 |
17 | {title && {title} }
18 | {text}
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/BoxContainer/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import BoxContainer from '.';
3 |
4 | describe('BoxContainer', () => {
5 | it('should render BoxContainer ', async () => {
6 | render(Page content );
7 | screen.getByLabelText('container-box');
8 | screen.getByText('Page content');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/BoxContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const BoxContainer = styled(Box)(({ theme }) => ({
5 | padding: theme.spacing(2),
6 | backgroundColor: theme.palette.primary.contrastText,
7 | display: 'flex',
8 | flexDirection: 'column',
9 | gap: theme.spacing(2),
10 | borderRadius: theme.spacing(1),
11 | boxShadow: '0 4px 4px rgb(0 0 0 / 25%)',
12 | }));
13 |
14 | export default BoxContainer;
15 |
--------------------------------------------------------------------------------
/src/components/Button/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 |
4 | import BandoButton from '.';
5 |
6 | describe('BandoButton', () => {
7 | const onClick = jest.fn();
8 |
9 | it('should render button and trigger onClick action', async () => {
10 | render(Click me );
11 | const btn = screen.getByRole('button', { name: /Click me/i });
12 |
13 | await userEvent.click(btn);
14 | expect(onClick).toHaveBeenCalledTimes(1);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/src/components/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import ButtonBase, { ButtonProps } from '@mui/material/Button';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const BandoButton = styled(ButtonBase)(({ theme }) => ({
5 | borderRadius: theme.spacing(1),
6 | boxShadow: 'none',
7 | textTransform: 'none',
8 | padding: '12px 24px',
9 | fontSize: '1rem',
10 | lineHeight: '1.25rem',
11 | fontFamily: 'Kanit',
12 | fontWeight: 'bold',
13 | '&.rounded': { borderRadius: '24px' },
14 | '&:hover:not(.MuiButton-text)': {
15 | backgroundColor: theme.palette.primary.main,
16 | backgroundImage: 'linear-gradient(rgb(0 0 0/20%) 0 0)',
17 | boxShadow: 'none',
18 | },
19 | '&.MuiButton-sizeSmall': {
20 | padding: '10px 18px',
21 | },
22 | }));
23 |
24 | export default BandoButton;
25 |
--------------------------------------------------------------------------------
/src/components/CurrencyImg/index.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | const CurrencyImg = styled('img')(({ theme }) => ({
4 | marginTop: '-10px',
5 | marginBottom: '-10px',
6 | padding: 0,
7 | marginRight: theme.spacing(1),
8 | width: 37,
9 | height: 37,
10 | }));
11 |
12 | export default CurrencyImg;
13 |
--------------------------------------------------------------------------------
/src/components/Footer/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import Footer from '@components/Footer';
3 |
4 | test('renders Footer component', () => {
5 | render();
6 |
7 | // Assert that the FooterLogo is rendered
8 | const footerLogo = screen.getByLabelText('Bando footer logo');
9 | expect(footerLogo).toBeInTheDocument();
10 |
11 | // Assert that the FooterLink for Preguntas Frecuentes is rendered
12 | const preguntasFrecuentesLink = screen.getByText('Preguntas Frecuentes');
13 | expect(preguntasFrecuentesLink).toBeInTheDocument();
14 |
15 | // Assert that the FooterLink for Bando Academy is rendered
16 | const bandoAcademyLink = screen.getByText('Bando Academy');
17 | expect(bandoAcademyLink).toBeInTheDocument();
18 |
19 | // Assert that the FooterLink for Política de Privacidad is rendered
20 | const politicaPrivacidadLink = screen.getByText('Política de Privacidad');
21 | expect(politicaPrivacidadLink).toBeInTheDocument();
22 |
23 | // Assert that the FooterLink for Términos y Condiciones is rendered
24 | const terminosCondicionesLink = screen.getByText('Términos y Condiciones');
25 | expect(terminosCondicionesLink).toBeInTheDocument();
26 |
27 | // Assert that the FooterLink for Contacto is rendered
28 | const contactoLink = screen.getByText('Contacto');
29 | expect(contactoLink).toBeInTheDocument();
30 |
31 | // Assert that the IconButton for Twitter is rendered
32 | const twitterIconButton = screen.getByLabelText('Twitter');
33 | expect(twitterIconButton).toBeInTheDocument();
34 | });
35 |
--------------------------------------------------------------------------------
/src/components/Hr/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 | import Hr from '.';
4 |
5 | describe('Hr', () => {
6 | it('should render Hr', () => {
7 | render( , { wrapper });
8 | screen.getByLabelText('HR element');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/Hr/index.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | const Hr = styled('hr')(({ theme }) => ({
4 | backgroundColor: theme.palette.ink.i300,
5 | height: 1,
6 | width: '100%',
7 | border: 0,
8 | }));
9 |
10 | export default Hr;
11 |
--------------------------------------------------------------------------------
/src/components/Jumbotron/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import Jumbotron from '.';
5 |
6 | describe('Jumbotron', () => {
7 | it('should render ', () => {
8 | render( , { wrapper });
9 | screen.getByText('Intercambia entre pesos y cripto en una transferencia bancaria', {
10 | exact: false,
11 | });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/KycBulletPoints/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import KycBulletPoints from '.';
5 |
6 | describe('KycBulletPoints', () => {
7 | it('should render ', () => {
8 | render( , { wrapper });
9 |
10 | const listItems = screen.getAllByRole('listitem');
11 | expect(listItems).toHaveLength(4);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/src/components/KycBulletPoints/index.tsx:
--------------------------------------------------------------------------------
1 | import Typography from '@mui/material/Typography';
2 | import { useTranslation } from 'react-i18next';
3 |
4 | import { styled } from '@mui/material/styles';
5 | import SimpleFooter from '../SimpleFooter';
6 |
7 | const Container = styled('div')(() => ({
8 | width: '100%',
9 | height: '100%',
10 | display: 'flex',
11 | flexDirection: 'column',
12 | alignItems: 'flex-end',
13 | position: 'relative',
14 | }));
15 |
16 | const BulletContainer = styled('div')(({ theme }) => ({
17 | maxWidth: '480px',
18 | width: '100%',
19 | margin: 'auto',
20 | padding: theme.spacing(3, 0),
21 | }));
22 |
23 | const Title = styled(Typography)(({ theme }) => ({
24 | color: theme.palette.ink.i900,
25 | fontSize: `${theme.typography.pxToRem(22)} !important`,
26 | lineHeight: '1.3em',
27 | textAlign: 'center',
28 | fontFamily: 'Kanit',
29 | fontWeight: 'normal',
30 | }));
31 |
32 | const BulletList = styled('ul')(({ theme }) => ({
33 | fontFamily: 'TWK Everett',
34 | color: theme.palette.ink.i900,
35 | fontSize: `${theme.typography.pxToRem(14)} !important`,
36 | paddingTop: theme.spacing(2),
37 | '& li': {
38 | listStyleImage: 'url(/images/CheckGreen.svg)',
39 | padding: theme.spacing(1, 0),
40 | paddingLeft: theme.spacing(1),
41 | },
42 | }));
43 |
44 | export default function KycBulletPoints() {
45 | const { t } = useTranslation('kycPoints');
46 | const points = t('points', { returnObjects: true });
47 |
48 | return (
49 |
50 |
51 | {t('title')}
52 |
53 |
54 | {points.map((point, idx) => (
55 |
56 | {point}
57 |
58 | ))}
59 |
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/MarkDownContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import BoxContainer from '@components/BoxContainer';
2 | import Markdown from 'react-markdown';
3 | import { ComponentProps } from 'react';
4 |
5 | type MarkDownContainerProps = {
6 | content: string;
7 | sx?: ComponentProps['sx'];
8 | };
9 |
10 | export default function MarkDownContainer({ content, sx }: MarkDownContainerProps) {
11 | return (
12 |
13 | {content}
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Navbar/DrawerLink.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { ListItemButton, ListItemIcon, ListItemText, ListItemButtonProps } from '@mui/material';
4 | import { styled } from '@mui/material/styles';
5 | import useUser from '../../hooks/useUser';
6 |
7 | type DrawerLinkProps = {
8 | to: string;
9 | icon: React.ReactElement;
10 | text: string;
11 | };
12 |
13 | const DrawerButton = styled(ListItemButton)(({ theme }) => ({
14 | color: theme.palette.ink.i900,
15 | '&:hover': {
16 | backgroundColor: theme.palette.action.hover,
17 | },
18 | }));
19 |
20 | function DrawerLink({ to, icon, text }: DrawerLinkProps) {
21 | const navigate = useNavigate();
22 | const { removeSessionStorage } = useUser();
23 | const logoutUser = async () => {
24 | removeSessionStorage();
25 | navigate('/');
26 | };
27 | return (
28 | (to === 'logout' ? logoutUser() : navigate(to))}
31 | >
32 | {icon}
33 |
34 |
35 | );
36 | }
37 |
38 | export default DrawerLink;
39 |
--------------------------------------------------------------------------------
/src/components/Navbar/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, fireEvent } from '@testing-library/react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { PropsWithChildren } from 'react';
4 | import Wrapper from '@helpers/TestProvider';
5 |
6 | import Navbar from '.';
7 |
8 | jest.mock('react-router-dom', () => ({
9 | ...jest.requireActual('react-router-dom'),
10 | useNavigate: jest.fn(),
11 | }));
12 |
13 | const wrapper = ({ children }: PropsWithChildren) => (
14 |
15 | {children}
16 |
17 | );
18 |
19 | describe('Navbar', () => {
20 | const navigate = jest.fn();
21 |
22 | beforeEach(() => {
23 | (useNavigate as jest.Mock).mockReturnValue(navigate);
24 | });
25 |
26 | it('should render the navbar and change the navbar classname based on the scroll position', async () => {
27 | render( , { wrapper });
28 |
29 | screen.getByLabelText('Bando logo');
30 | screen.getByLabelText('scrollTop');
31 |
32 | fireEvent.scroll(window, { target: { scrollY: 2000 } });
33 | screen.getByLabelText('scrolled');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/components/PageTitle/index.tsx:
--------------------------------------------------------------------------------
1 | import Typography from '@mui/material/Typography';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const PageTitle = styled(Typography)(({ theme }) => ({
5 | fontSize: '25px !important',
6 | fontFamily: 'Kanit',
7 | fontWeight: 700,
8 | color: theme.palette.ink.i600,
9 | marginBottom: theme.spacing(2),
10 | }));
11 |
12 | export default PageTitle;
13 |
--------------------------------------------------------------------------------
/src/components/ProgressBar/index.tsx:
--------------------------------------------------------------------------------
1 | import LinearProgress, { linearProgressClasses } from '@mui/material/LinearProgress';
2 |
3 | import { styled } from '@mui/material/styles';
4 |
5 | type ProgressBarProps = {
6 | progressHeight?: number;
7 | };
8 |
9 | const ProgressBar = styled(LinearProgress)(({ theme, progressHeight = 12 }) => ({
10 | height: progressHeight,
11 | borderRadius: progressHeight / 2,
12 | [`&.${linearProgressClasses.colorPrimary}`]: {
13 | backgroundColor: theme.palette.ink.i200,
14 | },
15 | [`& .${linearProgressClasses.bar}`]: {
16 | borderRadius: progressHeight / 2,
17 | backgroundColor: theme.palette.primary.main,
18 | },
19 | }));
20 |
21 | export default ProgressBar;
22 |
--------------------------------------------------------------------------------
/src/components/RampDirectionTabs/index.tsx:
--------------------------------------------------------------------------------
1 | import { SyntheticEvent, useState } from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import Box from '@mui/material/Box';
4 | import Tabs from '@mui/material/Tabs';
5 | import Tab from '@mui/material/Tab';
6 |
7 | type RampDirectionTabsProps = {
8 | handleChange: () => void;
9 | };
10 |
11 | export default function RampDirectionTabs({ handleChange }: RampDirectionTabsProps) {
12 | const [value, setValue] = useState(0);
13 | const { t } = useTranslation('quote');
14 |
15 | const a11yProps = (index: number) => {
16 | return {
17 | id: `full-width-tab-${index}`,
18 | 'aria-controls': `full-width-tabpanel-${index}`,
19 | };
20 | };
21 |
22 | const onChange = (_: SyntheticEvent, newValue: number) => {
23 | setValue(newValue);
24 | handleChange();
25 | };
26 |
27 | return (
28 |
29 |
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/SimpleFooter/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import SimpleFooter from '@components/SimpleFooter';
3 | import wrapper from '@helpers/TestProvider';
4 |
5 | describe('SimpleFooter', () => {
6 | it('renders with default props', () => {
7 | render( , { wrapper });
8 |
9 | screen.getByText(/Bando. Todos los derechos reservados./i);
10 | });
11 |
12 | it('renders with custom background color', () => {
13 | const { container } = render( , { wrapper });
14 |
15 | expect(container.firstChild).toHaveStyle('background-color: red');
16 | });
17 |
18 | it('renders with custom text color', () => {
19 | const { getByText } = render( , { wrapper });
20 | const footerText = getByText(/Bando. Todos los derechos reservados./i);
21 | expect(footerText).toHaveStyle('color: blue');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/components/SimpleFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import { Typography, Container } from '@mui/material';
2 | import { useTranslation } from 'react-i18next';
3 |
4 | type SimpleFooterProps = {
5 | bgColor?: string;
6 | textColor?: string;
7 | };
8 |
9 | export default function SimpleFooter({ bgColor, textColor }: SimpleFooterProps) {
10 | const { t } = useTranslation('footer');
11 |
12 | return (
13 |
22 |
23 | {t('disclaimer')}
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/SiteSpinner/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import SiteSpinner from '.';
5 |
6 | describe('SiteSpinner', () => {
7 | it('should render ', () => {
8 | render( , { wrapper });
9 |
10 | screen.getByRole('progressbar');
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/src/components/SiteSpinner/index.tsx:
--------------------------------------------------------------------------------
1 | import CircularProgress from '@mui/material/CircularProgress';
2 | import Box from '@mui/material/Box';
3 | import { styled } from '@mui/material/styles';
4 |
5 | const SiteSpinnerContainer = styled(Box)(() => ({
6 | width: '100%',
7 | height: '100%',
8 | display: 'flex',
9 | justifyContent: 'center',
10 | alignItems: 'center',
11 | alignSelf: 'stretch',
12 | margin: 'auto',
13 | }));
14 |
15 | export default function SiteSpinner() {
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Svgs/ArrowCircle.tsx:
--------------------------------------------------------------------------------
1 | import { SVGAttributes } from 'react';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const ArrowCircle = (props?: SVGAttributes) => {
5 | return (
6 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | const ArrowCircleIcon = styled(ArrowCircle)(({ theme }) => ({
21 | transform: 'rotate(90deg)',
22 | color: theme.palette.ink.i800,
23 | fontSize: 16,
24 | '& rect': {
25 | color: 'inherit',
26 | stroke: theme.palette.ink.i800,
27 | },
28 | '& path, & d': {
29 | color: 'inherit',
30 | fill: theme.palette.ink.i800,
31 | },
32 | }));
33 |
34 | export default ArrowCircleIcon;
35 |
--------------------------------------------------------------------------------
/src/components/Svgs/Logout.tsx:
--------------------------------------------------------------------------------
1 | import { SVGAttributes } from 'react';
2 | import { styled } from '@mui/material/styles';
3 |
4 | const Logout = (props?: SVGAttributes) => {
5 | return (
6 |
16 |
21 |
22 | );
23 | };
24 |
25 | const LogoutIcon = styled(Logout)(() => ({}));
26 | export default LogoutIcon;
27 |
--------------------------------------------------------------------------------
/src/components/TokensWidget/alerts.tsx:
--------------------------------------------------------------------------------
1 | import { AlertProps, createSvgIcon } from '@mui/material';
2 | import BandoAlert from '@components/Alert';
3 | import { JSX } from 'react/jsx-runtime';
4 |
5 | const RouteIcon = createSvgIcon(
6 |
7 |
12 | ,
13 | 'RouteIcon',
14 | );
15 |
16 | export const RouteAlert = (
17 | props: JSX.IntrinsicAttributes & AlertProps & { title?: string; text: string },
18 | ) => } {...props} />;
19 |
--------------------------------------------------------------------------------
/src/components/TokensWidget/helpers.ts:
--------------------------------------------------------------------------------
1 | export function fillArray(mod = 5, arr: T[], maxLength = 10) {
2 | if (arr.length >= maxLength) return [...arr].splice(0, maxLength);
3 | let newArr: (T | null)[] = [];
4 | newArr = [...arr];
5 | while (newArr?.length % mod !== 0) {
6 | newArr?.push(null);
7 | }
8 | return [...newArr];
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/TransactionDetails/RateText.tsx:
--------------------------------------------------------------------------------
1 | import { OperationType, Transaction } from '@hooks/useTransaction/requests';
2 | import Typography from '@mui/material/Typography';
3 | import { styled } from '@mui/material/styles';
4 | import formatNumber from '@helpers/formatNumber';
5 |
6 | type RateTextProps = {
7 | operationType?: OperationType;
8 | transaction?: Transaction;
9 | rate: number;
10 | };
11 |
12 | export const Amount = styled(Typography)(({ theme }) => ({
13 | fontSize: theme.typography.pxToRem(14),
14 | fontWeight: 'normal',
15 | lineHeight: 'normal',
16 | color: theme.palette.ink.i500,
17 | textAlign: 'left',
18 | margin: '0 6px',
19 | }));
20 |
21 | export default function RateText({ operationType, transaction, rate = 0 }: RateTextProps) {
22 | if (operationType === 'deposit') {
23 | return (
24 | <>
25 | 1 {transaction?.quoteCurrency} ≈{' '}
26 |
27 | {formatNumber(rate, 2, 18)}
28 | {' '}
29 | {transaction?.baseCurrency}
30 | >
31 | );
32 | }
33 | return (
34 | <>
35 | 1 {transaction?.baseCurrency} ≈{' '}
36 |
37 | {formatNumber(rate, 2, 18)}
38 | {' '}
39 | {transaction?.quoteCurrency}
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/TransactionDetails/TransactionCopyText.tsx:
--------------------------------------------------------------------------------
1 | import { CopyToClipboard } from 'react-copy-to-clipboard';
2 | import { CircularButton } from '@components/forms/RampForm/RampTitle';
3 | import Tooltip from '@mui/material/Tooltip';
4 | import Typography from '@mui/material/Typography';
5 | import Box from '@mui/material/Box';
6 |
7 | import CopyImg from '../../assets/CopyToClipboard.svg';
8 | import { styled } from '@mui/material/styles';
9 | import { ComponentProps, useState } from 'react';
10 | import { useTranslation } from 'react-i18next';
11 |
12 | export const DetailText = styled(Typography)(({ theme }) => {
13 | return {
14 | fontFamily: 'TWK Everett',
15 | fontSize: theme.typography.pxToRem(14),
16 | color: theme.palette.ink.i700,
17 | textAlign: 'left',
18 | width: 'fit-content',
19 | lineHeight: 'normal',
20 | display: 'flex',
21 | alignItems: 'center',
22 | '&.ellipse': {
23 | display: 'inline-block',
24 | paddingTop: theme.spacing(0.7),
25 | overflow: 'hidden',
26 | whiteSpace: 'nowrap',
27 | textOverflow: 'ellipsis',
28 | [theme.breakpoints.down('sm')]: {
29 | width: '100%',
30 | },
31 | },
32 | };
33 | });
34 |
35 | type TransactionCopyTextProps = ComponentProps & {
36 | text: string;
37 | ellipse?: boolean;
38 | };
39 |
40 | export default function TransactionCopyText({
41 | text = '',
42 | ellipse = false,
43 | ...props
44 | }: TransactionCopyTextProps) {
45 | const { t } = useTranslation('transactions');
46 | const [tooltipOpen, setTooltipOpen] = useState(false);
47 | const handleCopy = () => {
48 | setTooltipOpen(true);
49 | setTimeout(() => {
50 | setTooltipOpen(false);
51 | }, 700);
52 | };
53 | return (
54 |
55 |
56 | {text}
57 |
58 |
66 |
67 | handleCopy()}>
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/TransactionDetails/mapProviderStatus.ts:
--------------------------------------------------------------------------------
1 | export default function mapProviderStatus(status: string) {
2 | switch (status) {
3 | case 'CONVERSION_REQUEST':
4 | case 'CONVERSION_PROCESSING':
5 | case 'CONVERSION_COMPLETED':
6 | case 'MANUAL_REVISION':
7 | case 'REVIEW_NEEDED':
8 | case 'CASH_OUT_REQUEST':
9 | case 'CASH_OUT_PENDING':
10 | case 'CASH_OUT_REQUESTED':
11 | case 'CASH_OUT_PROCESSING':
12 | case 'CASH_OUT_COMPLETED':
13 | case 'WAIT_SOURCE_CONFIRMATIONS':
14 | case 'WAIT_DESTINATION_TRANSACTION':
15 | case 'BRIDGE_NOT_AVAILABLE':
16 | case 'CHAIN_NOT_AVAILABLE':
17 | case 'REFUND_IN_PROGRESS':
18 | case 'REFUNDED':
19 | case 'FIAT_COMPLETED':
20 | case 'PENDING':
21 | case 'FAILED':
22 | case 'TXN_REVERTED':
23 | case 'APPROVE_REVERTED':
24 | return {
25 | text: 'Procesando',
26 | color: 'info',
27 | };
28 | case 'REJECTED':
29 | case 'ERROR':
30 | case 'NOT_PROCESSABLE_REFUND_NEEDED':
31 | case 'OUT_OF_GAS':
32 | case 'SLIPPAGE_EXCEEDED':
33 | case 'INSUFFICIENT_ALLOWANCE':
34 | case 'INSUFFICIENT_BALANCE':
35 | case 'UNKNOWN_ERROR':
36 | return {
37 | text: 'Fallida ',
38 | subtitle:
39 | 'Lo sentimos, tu transacción no pudo ser completada. Tu dinero será devuelto a tu cuenta en las próximas horas.',
40 | color: 'error',
41 | };
42 | case 'EXPIRED':
43 | return {
44 | text: 'Expirada ',
45 | subtitle:
46 | 'No pudimos detectar tu transacción durante 60 mins. Cualquier aclaración, por favor contacta a soporte.',
47 | color: 'error',
48 | };
49 | case 'CANCELED':
50 | return {
51 | text: 'Cancelada ',
52 | subtitle:
53 | 'Lo sentimos, tu transacción no pudo ser completada. Tu dinero será devuelto a tu cuenta en las próximas horas.',
54 | color: 'error',
55 | };
56 | case 'COMPLETED':
57 | case 'DONE':
58 | return {
59 | text: 'Completada ',
60 | color: 'success',
61 | };
62 | case 'CASH_IN_PROCESSING':
63 | case 'CASH_IN_REQUESTED':
64 | case 'CREATED':
65 | return {
66 | text: 'Esperando depósito',
67 | color: 'pending',
68 | };
69 | default:
70 | return { text: '', color: '' };
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/TransactionsTable/CellDetailWithIcon.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { TableRowDetail, TableRowDetail as Container } from './TableComponents';
3 | import { SxProps, styled } from '@mui/material/styles';
4 |
5 | type CellDetailWithIconProps = {
6 | mainIcon?: ReactNode;
7 | mainIconComp?: ReactNode;
8 | subIcon?: ReactNode;
9 | headerText: string;
10 | subText?: string;
11 | network?: string;
12 | sx?: SxProps;
13 | };
14 |
15 | export const TransactionTypeIcon = styled(TableRowDetail)(() => ({
16 | position: 'relative',
17 | borderRadius: '50%',
18 | '& img': {
19 | width: 'auto',
20 | height: '100%',
21 | objectFit: 'cover',
22 | borderRadius: '50%',
23 | },
24 | '& span.network-img': {
25 | fontSize: 'inherit',
26 | position: 'absolute',
27 | width: 14,
28 | height: 14,
29 | display: 'flex',
30 | alignItems: 'center',
31 | bottom: '-3px',
32 | right: '-1px',
33 | overflow: 'hidden',
34 | padding: 0,
35 | borderRadius: '50%',
36 | '& img': {
37 | width: 'auto',
38 | height: '100%',
39 | objectFit: 'cover',
40 | },
41 | },
42 | }));
43 |
44 | export default function CellDetailWithIcon({
45 | mainIcon,
46 | mainIconComp,
47 | headerText,
48 | subText,
49 | network,
50 | subIcon,
51 | sx,
52 | }: CellDetailWithIconProps) {
53 | const subIconComp = typeof subIcon === 'string' ? : subIcon;
54 |
55 | return (
56 |
57 | {mainIcon ? (
58 |
59 | {mainIcon}
60 | {subIcon && {subIconComp} }
61 |
62 | ) : (
63 | mainIconComp
64 | )}
65 |
73 | {headerText}
74 | {subText && {subText} }
75 |
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/TransactionsTable/index.tsx:
--------------------------------------------------------------------------------
1 | import { Fragment, useState } from 'react';
2 | import { format } from 'date-fns';
3 |
4 | import Table from '@mui/material/Table';
5 | import TableContainer from '@mui/material/TableContainer';
6 | import TransactionRow from './TransactionRow';
7 | import { TableBody, StyledTableRow, HeaderCell } from './TableComponents';
8 |
9 | import useTransactions from '@hooks/useTransactions';
10 |
11 | export default function TransactionsTable() {
12 | const [selectedRow, setSelectedRow] = useState('');
13 | const { transactions } = useTransactions();
14 |
15 | const onRowClick = (rowId: string) => {
16 | setSelectedRow((currentRow) => (rowId === currentRow ? '' : rowId));
17 | };
18 |
19 | return (
20 |
21 |
25 |
26 | {!!transactions &&
27 | Object.keys(transactions).map((key) => {
28 | const txns = transactions[key];
29 |
30 | return (
31 |
32 |
33 |
34 | {format(new Date(key ?? ''), 'MMM d, y')}
35 |
36 |
37 | {txns.map((txn, idx) => (
38 |
45 | ))}
46 |
47 | );
48 | })}
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/UserCard/index.tsx:
--------------------------------------------------------------------------------
1 | import Typography from '@mui/material/Typography';
2 | import Box, { BoxProps } from '@mui/material/Box';
3 | import LimitUsage, { LimitUsageProps } from '@components/LimitUsage';
4 |
5 | import { User } from '../../hooks/useUser/types';
6 | import { styled } from '@mui/material/styles';
7 |
8 | type UserCardProps = {
9 | user: Partial | null;
10 | };
11 |
12 | const UserCardBox = styled(Box)(({ theme }) => ({
13 | '& h5': {
14 | fontWeight: 700,
15 | },
16 | '& p': {
17 | color: theme.palette.ink.i600,
18 | },
19 | }));
20 |
21 | function UserCard(props: UserCardProps) {
22 | return (
23 |
24 | {`${props.user?.firstName} ${props.user?.lastName}`}
25 | {props.user?.email}
26 |
27 |
33 |
34 | );
35 | }
36 |
37 | export default UserCard;
38 |
--------------------------------------------------------------------------------
/src/components/forms/ErrorBox/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import ErrorBox from '.';
3 |
4 | describe('ErrorBox', () => {
5 | it('should render ErrorBox ', async () => {
6 | render(Error message );
7 | screen.getByLabelText('error-box');
8 | screen.getByText('Error message');
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/forms/ErrorBox/index.tsx:
--------------------------------------------------------------------------------
1 | import Box, { BoxProps } from '@mui/material/Box';
2 | import { styled } from '@mui/material/styles';
3 |
4 | type ErrorBoxProps = BoxProps & {
5 | mode?: 'error' | 'alert';
6 | align?: 'center' | 'left' | 'right';
7 | };
8 |
9 | const ErrorBox = styled(Box)(({ theme, mode, align }) => {
10 | if (align) align = align === 'center' ? 'center' : align === 'left' ? 'left' : 'right';
11 | else align = 'center';
12 | let color = theme.palette.error.main;
13 | if (mode) color = mode === 'error' ? theme.palette.error.main : theme.palette.info.main;
14 | return {
15 | padding: theme.spacing(2),
16 | marginTop: theme.spacing(2),
17 | display: 'flex',
18 | flexDirection: 'column',
19 | gap: theme.spacing(2),
20 | borderRadius: '6px',
21 | color: color,
22 | backgroundColor: theme.palette.error.contrastText,
23 | border: `1px solid ${color}`,
24 | textAlign: align,
25 | };
26 | });
27 |
28 | export default ErrorBox;
29 |
--------------------------------------------------------------------------------
/src/components/forms/Input/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import wrapper from '@helpers/TestProvider';
4 |
5 | import Input, { HelpText } from '.';
6 |
7 | describe('Input', () => {
8 | it('should render input with label and help text', async () => {
9 | render(
10 |
16 | Tipo de cambio (USDT/MXN): 16.83
17 | >
18 | }
19 | disabled
20 | />,
21 | { wrapper },
22 | );
23 | screen.getByLabelText('Recibes');
24 | screen.getByText('Tipo de cambio (USDT/MXN):');
25 | });
26 |
27 | it('should change the value of the input', async () => {
28 | render( , {
29 | wrapper,
30 | });
31 |
32 | const input = screen.getByPlaceholderText('textInput') as HTMLInputElement;
33 | await userEvent.type(input, 'new text value');
34 | expect(input.value).toBe('new text value');
35 | });
36 |
37 | it('mantainLabel should hide label element when false', async () => {
38 | render(
39 | ,
46 | {
47 | wrapper,
48 | },
49 | );
50 |
51 | expect(() => screen.getByText('LabelText')).toThrow();
52 | });
53 |
54 | describe('HelpText component', () => {
55 | it('should render HelpText component', () => {
56 | render(Help text , { wrapper });
57 | screen.getByText('Help text');
58 | });
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/src/components/forms/MuiInput/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import wrapper from '@helpers/TestProvider';
4 |
5 | import MuiInput from '.';
6 |
7 | describe('MuiInput', () => {
8 | it('should render input with label', async () => {
9 | render( , { wrapper });
10 | screen.getByLabelText('moneyReceived');
11 | });
12 |
13 | it('should render input with label and be identifiebla by name', async () => {
14 | render( , {
15 | wrapper,
16 | });
17 | screen.getByLabelText('moneyReceived');
18 | });
19 |
20 | it('should change the value of the input', async () => {
21 | render( , {
22 | wrapper,
23 | });
24 |
25 | const input = screen.getByPlaceholderText('textInput') as HTMLInputElement;
26 | await userEvent.type(input, 'new text value');
27 | expect(input.value).toBe('new text value');
28 | });
29 |
30 | it('mantainLabel should hide label element when false', async () => {
31 | render(
32 | ,
39 | {
40 | wrapper,
41 | },
42 | );
43 |
44 | expect(() => screen.getByText('LabelText')).toThrow();
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/components/forms/MuiInput/index.tsx:
--------------------------------------------------------------------------------
1 | import TextField, { TextFieldProps } from '@mui/material/TextField';
2 | import { ForwardedRef, forwardRef } from 'react';
3 | import { styled } from '@mui/material/styles';
4 | import inputStyles from './styles';
5 |
6 | const TextFieldBase = styled(TextField)(() => inputStyles);
7 |
8 | export type MuiInputProps = TextFieldProps & {
9 | mantainLabel?: boolean;
10 | };
11 |
12 | const MuiInput = forwardRef((inputProps: MuiInputProps, ref: ForwardedRef) => {
13 | const { mantainLabel = false, fullWidth = true, className, ...props } = inputProps;
14 | const classNames = mantainLabel ? 'label-top' : '';
15 | const labelText = props.id ?? props.name;
16 |
17 | return (
18 |
26 | );
27 | });
28 |
29 | export default MuiInput;
30 |
--------------------------------------------------------------------------------
/src/components/forms/MuiInput/styles.ts:
--------------------------------------------------------------------------------
1 | const inputStyles = {
2 | fontFamily: 'TWK Everett',
3 | fontSize: 16,
4 | '& .MuiFormLabel-root': {
5 | fontFamily: 'inherit',
6 | fontSize: 'inherit',
7 | },
8 | '&.label-top': {
9 | marginTop: 28,
10 | },
11 | '& .MuiInputBase-input, & .MuiSelect-select': {
12 | fontFamily: 'Kanit',
13 | fontSize: '16px !important',
14 | lineHeight: 'normal',
15 | padding: '17px 16px',
16 | display: 'flex',
17 | justifyContent: 'start',
18 | alignItems: 'center',
19 | },
20 | '& .MuiSelect-select': { paddingRight: '32px !important' },
21 | };
22 |
23 | export default inputStyles;
24 |
--------------------------------------------------------------------------------
/src/components/forms/MuiSelect/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, within } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import wrapper from '@helpers/TestProvider';
4 |
5 | import MuiSelect from '.';
6 | import { CurrencyImg } from '../GetQuoteForm';
7 | import Currency from '../../../assets/currency.svg';
8 |
9 | const items = [
10 | {
11 | label: 'MXN',
12 | value: 'mxn',
13 | startComponent: ,
14 | },
15 | {
16 | label: 'USD',
17 | value: 'usd',
18 | startComponent: ,
19 | },
20 | ];
21 |
22 | describe('MuiSelect', () => {
23 | it('should render select with label', async () => {
24 | render( , {
25 | wrapper,
26 | });
27 | screen.getByLabelText('moneyReceived');
28 | });
29 |
30 | it('should render select with label and be identifiebla by name', async () => {
31 | render( , {
32 | wrapper,
33 | });
34 | screen.getByLabelText('moneyReceived');
35 | });
36 |
37 | it('changes value from select', async () => {
38 | const { container } = render(
39 | ,
46 | { wrapper },
47 | );
48 |
49 | const selectBox = screen.getByRole('combobox');
50 | const selectInput = within(container).getByDisplayValue('mxn') as HTMLInputElement;
51 | expect(selectInput.value).toBe('mxn');
52 |
53 | await userEvent.click(selectBox);
54 | const options = screen.getAllByRole('option');
55 |
56 | await userEvent.click(options[1]);
57 | expect(selectInput.value).toBe('usd');
58 | });
59 | });
60 |
--------------------------------------------------------------------------------
/src/components/forms/MuiSelect/index.tsx:
--------------------------------------------------------------------------------
1 | import { CustomSelectProps } from '../Select';
2 |
3 | import TextField, { TextFieldProps } from '@mui/material/TextField';
4 | import MenuItemBase from '@mui/material/MenuItem';
5 |
6 | import { ForwardedRef, forwardRef } from 'react';
7 | import { styled } from '@mui/material/styles';
8 | import CaretDown from '../../../assets/CaretDown.svg';
9 | import inputStyles from '../MuiInput/styles';
10 |
11 | const TextFieldBase = styled(TextField)(() => inputStyles);
12 | const MenuItem = styled(MenuItemBase)(() => ({
13 | fontSize: '16px !important',
14 | padding: '16px',
15 | }));
16 |
17 | const CaretImg = styled('img')(() => ({
18 | userSelect: 'none',
19 | width: '18px',
20 | height: '10px',
21 | display: 'inline-block',
22 | fill: 'currentColor',
23 | position: 'absolute',
24 | right: '12px',
25 | top: 'calc(50% - 4px)',
26 | pointerEvents: 'none',
27 | color: 'rgba(0, 0, 0, 0.54)',
28 | flexShrink: 0,
29 | }));
30 |
31 | export type MuiSelectProps = TextFieldProps & CustomSelectProps;
32 |
33 | const MuiSelect = forwardRef((inputProps: MuiSelectProps, ref: ForwardedRef) => {
34 | const { mantainLabel = false, fullWidth = true, className, items, ...props } = inputProps;
35 | const classNames = mantainLabel ? 'label-top' : '';
36 | const labelText = props.id ?? props.name;
37 |
38 | return (
39 | ,
48 | }}
49 | select
50 | >
51 | {items?.map((item) => (
52 |
57 | <>
58 | {item.startComponent}
59 | {item.label}
60 | {item.endComponent}
61 | >
62 |
63 | ))}
64 |
65 | );
66 | });
67 |
68 | export default MuiSelect;
69 |
--------------------------------------------------------------------------------
/src/components/forms/RampForm/CurrencyPill.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 |
3 | const Container = styled('div')(({ theme }) => ({
4 | borderRadius: '100px',
5 | fontSize: theme.typography.pxToRem(20),
6 | border: `1px solid ${theme.palette.ink.i300}`,
7 | color: theme.palette.ink.i900,
8 | textAlign: 'left',
9 | width: 'fit-content',
10 | lineHeight: 'normal',
11 | padding: theme.spacing(2),
12 | display: 'flex',
13 | alignItems: 'center',
14 | justifyContent: 'center',
15 | fontFamily: 'Kanit',
16 | fontWeight: 'bold',
17 | '& img': {
18 | width: '37px',
19 | height: '37px',
20 | borderRadius: '50%',
21 | objectFit: 'cover',
22 | marginRight: theme.spacing(1),
23 | },
24 | [theme.breakpoints.between('xs', 'sm')]: {
25 | fontSize: theme.typography.pxToRem(14),
26 | '& img': {
27 | width: '22px',
28 | height: '22px',
29 | },
30 | },
31 | }));
32 |
33 | type CurrencyPillProps = {
34 | currency: string;
35 | imgUrl?: string;
36 | };
37 |
38 | export default function CurrencyPill({ currency, imgUrl = '' }: Readonly) {
39 | return (
40 |
41 | {currency}
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/forms/RampForm/RampTitle.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigate } from 'react-router-dom';
2 | import { styled } from '@mui/material/styles';
3 | import { useTranslation } from 'react-i18next';
4 | import Typography from '@mui/material/Typography';
5 | import Grid from '@mui/material/Unstable_Grid2';
6 | import Cross from '../../../assets/Cross.svg';
7 | import Button from '@mui/material/Button';
8 |
9 | import env from '@config/env';
10 | import { ReactNode } from 'react';
11 |
12 | export const CircularButton = styled(Button)(() => ({
13 | borderRadius: '50%',
14 | aspectRatio: '1/1',
15 | width: 'fit-content',
16 | minWidth: 'fit-content',
17 | }));
18 |
19 | type RampTitleProps = {
20 | title?: string;
21 | success?: boolean;
22 | subtitle?: string;
23 | noArrow?: boolean;
24 | leftContent?: ReactNode;
25 | };
26 | export default function RampTitle({
27 | leftContent,
28 | title = '',
29 | success = false,
30 | subtitle = '',
31 | noArrow = false,
32 | }: RampTitleProps) {
33 | const navigate = useNavigate();
34 | const { t } = useTranslation('ramp');
35 | const closeRamp = () => {
36 | localStorage.removeItem(env.rampDataLocalStorage);
37 | navigate('/');
38 | };
39 |
40 | return (
41 | <>
42 |
52 |
61 | {title || (success ? t('inProgressTitle') : t('confirmTitle'))}
62 |
63 | {!noArrow && (
64 |
65 |
66 |
67 | )}
68 | {leftContent}
69 |
70 |
71 | {subtitle && (
72 |
73 |
74 | {subtitle}
75 |
76 |
77 | )}
78 | >
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/src/components/forms/RampForm/schema.ts:
--------------------------------------------------------------------------------
1 | import * as yup from 'yup';
2 | import { isAddress } from 'web3-validator';
3 | import { isValidSolanaAddress } from '@helpers/validateSolanaAddress';
4 | import { OperationType } from '@hooks/useTransaction/requests';
5 |
6 | export type ConfirmRampFormValues = {
7 | operationType: OperationType;
8 | address?: string;
9 | firstName?: string;
10 | lastName?: string;
11 | clabe?: string;
12 | };
13 |
14 | const schema = (network: string) =>
15 | yup.object().shape({
16 | operationType: yup.string().oneOf(['deposit', 'withdraw']).required(),
17 | address: yup.string().when(['operationType'], {
18 | is: 'deposit',
19 | then: (schema) =>
20 | schema
21 | .test(
22 | 'is-valid-address',
23 | () => 'La dirección es incorrecta',
24 | (value) =>
25 | network == 'sol' ? isValidSolanaAddress(value ?? '') : isAddress(value ?? ''),
26 | )
27 | .required(),
28 | otherwise: (schema) => schema.optional(),
29 | }),
30 | firstName: yup.string().when(['operationType'], {
31 | is: 'withdraw',
32 | then: (schema) => schema.required(),
33 | otherwise: (schema) => schema.optional(),
34 | }),
35 | lastName: yup.string().when(['operationType'], {
36 | is: 'withdraw',
37 | then: (schema) => schema.required(),
38 | otherwise: (schema) => schema.optional(),
39 | }),
40 | clabe: yup.string().when(['operationType'], {
41 | is: 'withdraw',
42 | then: (schema) => schema.min(18).max(18).required(),
43 | otherwise: (schema) => schema.optional(),
44 | }),
45 | });
46 |
47 | export default schema;
48 |
--------------------------------------------------------------------------------
/src/components/forms/Select/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, within } from '@testing-library/react';
2 | import userEvent from '@testing-library/user-event';
3 | import wrapper from '@helpers/TestProvider';
4 |
5 | import Select from '.';
6 | import { CurrencyImg } from '../GetQuoteForm';
7 | import Currency from '../../../assets/currency.svg';
8 |
9 | const items = [
10 | {
11 | label: 'MXN',
12 | value: 'mxn',
13 | startComponent: ,
14 | },
15 | {
16 | label: 'USD',
17 | value: 'usd',
18 | startComponent: ,
19 | },
20 | ];
21 |
22 | describe('Select', () => {
23 | it('should render select with label and help text', async () => {
24 | render(
25 |
32 | Tipo de cambio (USDT/MXN): 16.83
33 | >
34 | }
35 | />,
36 | { wrapper },
37 | );
38 | screen.getByLabelText('moneyReceived');
39 | screen.getByText('Tipo de cambio (USDT/MXN):');
40 | });
41 |
42 | it('changes value from select', async () => {
43 | const { container } = render(
44 |
52 | Tipo de cambio (USDT/MXN): 16.83
53 | >
54 | }
55 | />,
56 | { wrapper },
57 | );
58 |
59 | const selectBox = screen.getByRole('combobox');
60 | const selectInput = within(container).getByDisplayValue('mxn') as HTMLInputElement;
61 | expect(selectInput.value).toBe('mxn');
62 |
63 | await userEvent.click(selectBox);
64 | const options = screen.getAllByRole('option');
65 |
66 | await userEvent.click(options[1]);
67 | expect(selectInput.value).toBe('usd');
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/src/config/axios.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosHeaders } from 'axios';
2 | import Cookies from 'js-cookie';
3 | import env from '@config/env.ts';
4 |
5 | axios.defaults.baseURL = env.api;
6 | axios.interceptors.request.use((config) => {
7 | const token = Cookies.get(env.authCookieName);
8 |
9 | if (token) {
10 | config.headers = {
11 | Authorization: `Bearer ${token}`,
12 | ...config.headers,
13 | } as unknown as AxiosHeaders;
14 | }
15 |
16 | return config;
17 | });
18 |
19 | axios.interceptors.response.use(
20 | (response) => {
21 | // Added this interceptor to avoid using data key multiple times in response.
22 | if (response.data.data) {
23 | response.data = response.data.data;
24 | }
25 | return response;
26 | },
27 | (error) => {
28 | if (error.response?.status === 401) {
29 | Cookies.remove(env.authCookieName, { domain: window.location.hostname });
30 | }
31 |
32 | return Promise.reject(error);
33 | },
34 | );
35 |
--------------------------------------------------------------------------------
/src/config/constants/chains.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bandohq/react-bando/589c6bd25d0d888695f8ceee6ab18b099df30556/src/config/constants/chains.ts
--------------------------------------------------------------------------------
/src/config/constants/currencies.test.tsx:
--------------------------------------------------------------------------------
1 | import currencies from './currencies';
2 |
3 | describe('currencies constants', () => {
4 | it('should return arrays for sendCurrency and depositCurrency', () => {
5 | expect(currencies.sendCurrency).toBeArray();
6 | expect(currencies.depositCurrency).toBeArray();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/config/constants/identification.ts:
--------------------------------------------------------------------------------
1 | import { TFunction } from 'i18next';
2 |
3 | export enum Identifications {
4 | NATIONAL_IDENTITY_CARD = 'NATIONAL_IDENTITY_CARD',
5 | IFE = 'IFE',
6 | PASSPORT = 'PASSPORT',
7 | DRIVER_LICENSE = 'DRIVER_LICENSE',
8 | }
9 |
10 | export const identificationOptions = (t: TFunction<'form', undefined>) => [
11 | { value: Identifications.NATIONAL_IDENTITY_CARD, label: 'INE' },
12 | { value: Identifications.IFE, label: 'IFE' },
13 | { value: Identifications.PASSPORT, label: t('identification.passport') },
14 | { value: Identifications.DRIVER_LICENSE, label: t('identification.driverLicense') },
15 | ];
16 |
--------------------------------------------------------------------------------
/src/config/constants/links.ts:
--------------------------------------------------------------------------------
1 | export const bandoAcademy = 'https://academy.bando.cool';
2 |
--------------------------------------------------------------------------------
/src/config/endpoints.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | getQuote: '/api/v1/ramps/quote/',
3 | postAuth: '/api/v1/authn/did/token/',
4 | userKyc: '/api/v1/ramps/kyc/user/',
5 | postRecipient: '/api/v1/ramps/recipient/',
6 | transaction: '/api/v1/ramps/transaction/',
7 | transactionHistory: '/api/v1/ramps/transaction/history/',
8 | tokens: '/api/v1/ramps/token/',
9 | networks: '/api/v1/ramps/network/',
10 | };
11 |
--------------------------------------------------------------------------------
/src/config/env.test.ts:
--------------------------------------------------------------------------------
1 | describe('enviroment variables', () => {
2 | const OLD_ENV = process.env;
3 |
4 | beforeEach(() => {
5 | jest.resetModules();
6 | process.env = {
7 | ...OLD_ENV,
8 | };
9 | });
10 |
11 | afterAll(() => {
12 | process.env = OLD_ENV;
13 | });
14 |
15 | it('should set an empty string when env variables are not present', () => {
16 | process.env.API = undefined;
17 | process.env.AUTH_COOKIE_NAME = undefined;
18 |
19 | import('./env').then(({ default: env }) => {
20 | expect(env.api).toBe('');
21 | expect(env.authCookieName).toBe('');
22 | });
23 | });
24 |
25 | it('should set the env variables when they are present', () => {
26 | import('./env').then(({ default: env }) => {
27 | expect(env.api).toBe('https://api.com');
28 | expect(env.authCookieName).toBe('bando_test');
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/config/env.ts:
--------------------------------------------------------------------------------
1 | const api = process.env.API || '';
2 | const authCookieName = process.env.AUTH_COOKIE_NAME || '';
3 | const rampDataLocalStorage = 'bando_ramp_data';
4 | const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY ?? '';
5 | const intercomAppId = process.env.INTERCOM_APP_ID ?? '';
6 | const tapfiliateAccountId = process.env.TAPFILIATE_ACCOUNT_ID ?? '';
7 |
8 | const magicLink = {
9 | secret: process.env.MAGIC_LINK_SECRET || '',
10 | rpcUrl: process.env.MAGIC_LINK_RPC_URL || ' ',
11 | chainID: parseInt(process.env.MAGIC_LINK_CHAIN_ID || '11155111', 10),
12 | };
13 |
14 | export default {
15 | api,
16 | authCookieName,
17 | magicLink,
18 | rampDataLocalStorage,
19 | googleMapsApiKey,
20 | intercomAppId,
21 | tapfiliateAccountId,
22 | };
23 |
--------------------------------------------------------------------------------
/src/config/firebase/index.ts:
--------------------------------------------------------------------------------
1 | import { initializeApp } from 'firebase/app';
2 |
3 | const apiKey = process.env.FIREBASE_API_KEY ?? '';
4 | const authDomain = process.env.FIREBASE_AUTH_DOMAIN ?? '';
5 | const projectId = process.env.FIREBASE_PROJECT_ID ?? '';
6 | const storageBucket = process.env.FIREBASE_STORAGE_BUCKET ?? '';
7 | const messagingSenderId = process.env.FIREBASE_MESSAGING_SENDER_ID ?? '';
8 | const appId = process.env.FIREBASE_APP_ID ?? '';
9 | const measurementId = process.env.FIREBASE_MEASUREMENT_ID ?? '';
10 |
11 | const firebaseConfig = {
12 | apiKey,
13 | authDomain,
14 | projectId,
15 | storageBucket,
16 | messagingSenderId,
17 | appId,
18 | measurementId,
19 | };
20 |
21 | export default function initFirebase() {
22 | initializeApp(firebaseConfig);
23 | }
24 |
--------------------------------------------------------------------------------
/src/config/firebase/remoteConfig.test.ts:
--------------------------------------------------------------------------------
1 | import { fetchAndActivate, getAll, getRemoteConfig } from 'firebase/remote-config';
2 |
3 | import getConfigsFromFirebase from './remoteConfig';
4 |
5 | describe('remoteConfig', () => {
6 | beforeEach(() => {
7 | (getRemoteConfig as jest.Mock).mockReturnValue({ settings: {} });
8 | (fetchAndActivate as jest.Mock).mockResolvedValue(true);
9 | (getAll as jest.Mock).mockReturnValue({
10 | USE_GOOGLE_AUTOCOMPLETE: { asBoolean: jest.fn().mockImplementation(() => true) },
11 | });
12 | });
13 |
14 | it('should fetch firebase remote config values', async () => {
15 | const configs = await getConfigsFromFirebase();
16 | expect(configs.useGoogleAutocomplete).toBeTrue();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/config/firebase/remoteConfig.ts:
--------------------------------------------------------------------------------
1 | import { getRemoteConfig, fetchAndActivate, getAll } from 'firebase/remote-config';
2 | import { minutesToMilliseconds } from 'date-fns';
3 |
4 | export interface AppConfig {
5 | useGoogleAutocomplete: boolean;
6 | }
7 |
8 | const fiveMinInMilis = minutesToMilliseconds(5);
9 |
10 | const getConfigsFromFirebase = async () => {
11 | const remoteConfig = getRemoteConfig();
12 | remoteConfig.settings = {
13 | ...remoteConfig?.settings,
14 | minimumFetchIntervalMillis: fiveMinInMilis,
15 | };
16 |
17 | await fetchAndActivate(remoteConfig);
18 | const values = getAll(remoteConfig);
19 |
20 | return {
21 | useGoogleAutocomplete: values.USE_GOOGLE_AUTOCOMPLETE.asBoolean(),
22 | } as AppConfig;
23 | };
24 |
25 | export default getConfigsFromFirebase;
26 |
--------------------------------------------------------------------------------
/src/config/sentry.ts:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/react';
2 |
3 | Sentry.init({
4 | dsn: process.env.VITE_SENTRY_DSN,
5 | integrations: [
6 | Sentry.browserTracingIntegration(),
7 | Sentry.replayIntegration({
8 | maskAllText: false,
9 | blockAllMedia: false,
10 | }),
11 | ],
12 | // Performance Monitoring
13 | tracesSampleRate: 1.0, // Capture 100% of the transactions
14 | // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
15 | tracePropagationTargets: ['localhost', /^https:\/\/alpha\.bando\.cool\/api/],
16 | // Session Replay
17 | replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
18 | replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
19 | });
20 |
--------------------------------------------------------------------------------
/src/config/tapfiliate.ts:
--------------------------------------------------------------------------------
1 | import Tap from '@tapfiliate/tapfiliate-js';
2 | import * as Sentry from '@sentry/react';
3 |
4 | import env from './env';
5 |
6 | export default function initTapfiliate() {
7 | try {
8 | Tap.init(env.tapfiliateAccountId);
9 | Tap.detect();
10 | } catch (err) {
11 | // NOTE: Notify Sentry of error
12 | Sentry.captureException(err);
13 | }
14 | }
15 |
16 | export function tapfiliateConversion(
17 | id: string | number,
18 | amount: number | string,
19 | options?: Record,
20 | ) {
21 | Tap.conversion(id, amount, options);
22 | }
23 |
--------------------------------------------------------------------------------
/src/config/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material';
2 | import { responsiveFontSizes } from '@mui/material/styles';
3 |
4 | const theme = createTheme({
5 | palette: {
6 | primary: {
7 | main: '#40B494',
8 | light: '#E0FEF6',
9 | dark: '#393F44',
10 | contrastText: '#FFFFFF',
11 | },
12 | secondary: { main: '#59c6a8' },
13 | ink: {
14 | i950: '#000000',
15 | i900: '#212121',
16 | i800: '#3A3E3D',
17 | i700: '#393F44',
18 | i600: '#3D3D3D',
19 | i500: '#686F73',
20 | i400: '#9A9A9A',
21 | i300: '#BFC3C7',
22 | i250: '#D9D9D9',
23 | i200: '#E6E7E9',
24 | i150: '#F6F7F9',
25 | i100: '#F2F2F2',
26 | i000: '#FFFFFF',
27 | },
28 | },
29 | breakpoints: {
30 | values: {
31 | xs: 0,
32 | sm: 600,
33 | md: 900,
34 | lg: 1280,
35 | xl: 1440,
36 | },
37 | },
38 | typography: {
39 | htmlFontSize: 16,
40 | fontSize: 16,
41 | fontFamily: [
42 | 'Kanit',
43 | 'TWK Everett',
44 | '-apple-system',
45 | 'Arial',
46 | 'sans-serif',
47 | '"Apple Color Emoji"',
48 | '"Segoe UI Emoji"',
49 | '"Segoe UI Symbol"',
50 | ].join(','),
51 | },
52 | transitions: {
53 | duration: {
54 | enteringScreen: 400,
55 | leavingScreen: 350,
56 | },
57 | },
58 | });
59 |
60 | export default responsiveFontSizes(theme);
61 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | import 'jest-extended';
2 |
--------------------------------------------------------------------------------
/src/helpers/TestProvider.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from '@mui/material';
2 | import { PropsWithChildren } from 'react';
3 | import { BrowserRouter } from 'react-router-dom';
4 | import { I18nextProvider } from 'react-i18next';
5 | import MagicProvider from '@hooks/useMagicLinkAuth/MagicProvider';
6 | import MagicUserProvider from '@hooks/useUser/MagicUserProvider';
7 |
8 | import i18n from '@translations/index';
9 |
10 | import theme from '@config/theme';
11 |
12 | export default function TestProvider({ children }: Readonly) {
13 | return (
14 |
15 |
16 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/helpers/formatNumber.ts:
--------------------------------------------------------------------------------
1 | export default function formatNumber(nb = 0, minimumFractionDigits = 2, maximumFractionDigits = 2) {
2 | return nb.toLocaleString('us', {
3 | minimumFractionDigits,
4 | maximumFractionDigits,
5 | });
6 | }
7 |
--------------------------------------------------------------------------------
/src/helpers/formatWalletNumber.ts:
--------------------------------------------------------------------------------
1 | // '0x3a05...9115'
2 |
3 | export default function formatWalletNumber(walletNumber: string) {
4 | return `${walletNumber.slice(0, 6)}...${walletNumber.slice(-4)}`;
5 | }
6 |
--------------------------------------------------------------------------------
/src/helpers/getStorageQuote.ts:
--------------------------------------------------------------------------------
1 | import { Quote } from '@hooks/useQuote/requests';
2 | import env from '@config/env';
3 | import { OperationType } from '@hooks/useTransaction/requests';
4 | import { Network } from '@hooks/useNetworks/requests';
5 | import { Token } from '@hooks/useTokens/requests';
6 |
7 | type StorageQuote = {
8 | quote: Quote | null;
9 | networkObj: Network | null;
10 | tokenObj: Token | null;
11 | operationType: OperationType | null;
12 | network?: string | null;
13 | };
14 |
15 | export default function getStorageQuote() {
16 | try {
17 | const quote: StorageQuote = JSON.parse(
18 | localStorage.getItem(env.rampDataLocalStorage) ?? '',
19 | ) as StorageQuote;
20 | return quote;
21 | } catch {
22 | return {
23 | quote: null,
24 | network: null,
25 | networkObj: null,
26 | tokenObj: null,
27 | operationType: null,
28 | } as StorageQuote;
29 | }
30 | }
31 |
32 | export const deleteStorageQuote = () => {
33 | localStorage.removeItem(env.rampDataLocalStorage);
34 | };
35 |
--------------------------------------------------------------------------------
/src/helpers/getUserLanguage.ts:
--------------------------------------------------------------------------------
1 | import { es, enUS } from 'date-fns/locale';
2 | import { setDefaultOptions } from 'date-fns';
3 |
4 | export default function getUserLanguage() {
5 | return navigator.language;
6 | }
7 |
8 | export function getDateLocale(locale: string) {
9 | if (locale === 'es') return es;
10 | return enUS;
11 | }
12 |
13 | export const userLocale = getDateLocale(getUserLanguage());
14 |
15 | export const setupDateLocale = () => {
16 | setDefaultOptions({ locale: userLocale });
17 | };
18 |
--------------------------------------------------------------------------------
/src/helpers/input.test.ts:
--------------------------------------------------------------------------------
1 | import { FormEvent } from 'react';
2 | import { addZero, numberInputOnWheelPreventChange, removeZero } from './inputs';
3 |
4 | describe('inputs function', () => {
5 | it('should add zero', () => {
6 | const event = {
7 | currentTarget: {
8 | value: '',
9 | },
10 | };
11 | addZero(event as FormEvent);
12 | expect(event.currentTarget.value).toBe('0');
13 | });
14 |
15 | it('should remove zero', () => {
16 | const event = {
17 | currentTarget: {
18 | value: '0',
19 | },
20 | };
21 | removeZero(event as unknown as FormEvent);
22 | expect(event.currentTarget.value).toBe('');
23 | });
24 |
25 | it('should blur onwheel', () => {
26 | const event = {
27 | currentTarget: {
28 | blur: jest.fn(),
29 | },
30 | };
31 | numberInputOnWheelPreventChange(event as unknown as React.WheelEvent);
32 | expect(event.currentTarget.blur).toHaveBeenCalled();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/src/helpers/inputs.ts:
--------------------------------------------------------------------------------
1 | import { FormEvent } from 'react';
2 |
3 | export function checkNumberLength(e: FormEvent, maxLength: number) {
4 | if (e.currentTarget.value.length <= maxLength) return;
5 | e.currentTarget.value = e.currentTarget.value.slice(0, maxLength);
6 | }
7 |
8 | export function clearOnCaptureMaskedValue(e: FormEvent) {
9 | if (!e.currentTarget.value.includes('*')) return;
10 | e.currentTarget.value = '';
11 | }
12 |
13 | export function toUpperCase(e: FormEvent) {
14 | e.currentTarget.value = e.currentTarget.value.toUpperCase();
15 | }
16 |
17 | export function allowOnlyNumbers(e: FormEvent) {
18 | e.currentTarget.value = e.currentTarget.value.replace(/\D/g, '');
19 | }
20 |
21 | export function removeZero(e: FormEvent) {
22 | if (e.currentTarget.value === '0') {
23 | e.currentTarget.value = '';
24 | }
25 | }
26 |
27 | export function addZero(e: FormEvent) {
28 | if (e.currentTarget.value === '') {
29 | e.currentTarget.value = '0';
30 | }
31 | }
32 |
33 | export function numberInputOnWheelPreventChange(e: React.WheelEvent) {
34 | // Prevent the input value change
35 | e.currentTarget.blur();
36 | }
37 |
--------------------------------------------------------------------------------
/src/helpers/phoneValidation.ts:
--------------------------------------------------------------------------------
1 | import { PhoneNumberUtil } from 'google-libphonenumber';
2 |
3 | const phoneUtil = PhoneNumberUtil.getInstance();
4 |
5 | export const isPhoneValid = (phone: string) => {
6 | try {
7 | return phoneUtil.isValidNumber(phoneUtil.parseAndKeepRawInput(phone));
8 | } catch (error) {
9 | return false;
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/src/helpers/regexValidators.ts:
--------------------------------------------------------------------------------
1 | export const rfcRegex =
2 | /^([A-ZÑ&]{3,4}) ?(?:- ?)?(\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])) ?(?:- ?)?([A-Z\d]{2})([A\d])$/;
3 |
4 | export const phoneRegExp =
5 | /^((\\+[1-9]{1,4}[ \\-]*)|(\\([0-9]{2,3}\\)[ \\-]*)|([0-9]{2,4})[ \\-]*)*?[0-9]{3,4}?[ \\-]*[0-9]{3,4}?$/;
6 |
--------------------------------------------------------------------------------
/src/helpers/validateSolanaAddress.ts:
--------------------------------------------------------------------------------
1 | import { PublicKey } from '@solana/web3.js';
2 |
3 | export const isValidSolanaAddress = (address: string): boolean => {
4 | try {
5 | const pubkey = new PublicKey(address);
6 | return PublicKey.isOnCurve(pubkey.toBuffer());
7 | } catch (e) {
8 | return false;
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/hooks/useAuthCookie/index.test.tsx:
--------------------------------------------------------------------------------
1 | import '@config/axios';
2 | import { renderHook, waitFor } from '@testing-library/react';
3 | import Cookies from 'js-cookie';
4 |
5 | import useAuthCookie from '.';
6 |
7 | jest.mock('js-cookie');
8 |
9 | describe('useAuthCookie', () => {
10 | it('returns the jwt token and isAuthenticated flag if token is present', async () => {
11 | (Cookies.get as jest.Mock).mockImplementation(() => 'jwt');
12 |
13 | const { result } = renderHook(useAuthCookie);
14 |
15 | await waitFor(() => {
16 | expect(result.current.isAuthenticated).toBeTrue();
17 | expect(result.current.token).toBeString();
18 | });
19 | });
20 |
21 | it('returns jwt values when token is not present', async () => {
22 | (Cookies.get as jest.Mock).mockImplementation(() => undefined);
23 |
24 | const { result } = renderHook(useAuthCookie);
25 |
26 | await waitFor(() => {
27 | expect(result.current.isAuthenticated).toBeFalse();
28 | expect(result.current.token).toBeUndefined();
29 | });
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/hooks/useAuthCookie/index.tsx:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie';
2 | import env from '@config/env.ts';
3 |
4 | export default function useAuthCookie() {
5 | const token = Cookies.get(env.authCookieName);
6 |
7 | return { token, isAuthenticated: !!token };
8 | }
9 |
--------------------------------------------------------------------------------
/src/hooks/useKyc/index.test.tsx:
--------------------------------------------------------------------------------
1 | import '@config/axios';
2 | import axios from 'axios';
3 | import { renderHook } from '@testing-library/react';
4 |
5 | import wrapper from '@helpers/TestProvider';
6 |
7 | jest.mock('axios');
8 |
9 | import useKyc from '.';
10 |
11 | const mockTransactionResponse = {
12 | id: 46,
13 | transaction_id: 555,
14 | status: 'passed',
15 | base_amount: 1000,
16 | base_currency: 'MXN',
17 | quote_currency: 'USDC',
18 | quote_amount: '58.47',
19 | rate: '1',
20 | fee: '2',
21 | is_expired: false,
22 | cash_in_network: 'cash_in_network',
23 | provider_status: 'provider_status',
24 | end_network: 'end_network',
25 | cash_in_details: {
26 | network: 'network',
27 | bank: 'bank',
28 | beneficiary: 'beneficiary',
29 | clabe: 'clabe',
30 | concepto: 'concepto',
31 | },
32 | };
33 |
34 | describe('useKyc', () => {
35 | beforeEach(() => {
36 | (axios.post as jest.Mock).mockResolvedValue({
37 | data: mockTransactionResponse,
38 | });
39 |
40 | (axios.get as jest.Mock).mockResolvedValue({
41 | data: mockTransactionResponse,
42 | });
43 | });
44 |
45 | it('makes a request with correct payload for a user kyc', async () => {
46 | const { result } = renderHook(() => useKyc(), { wrapper });
47 |
48 | await result.current.postUserKyc({
49 | email: 'email',
50 | type: 'type',
51 | firstName: 'firstName',
52 | lastName: 'firstName',
53 | phone: 'phone',
54 | dateOfBirth: 'dateOfBirth',
55 | address: {
56 | street: 'street',
57 | state: 'state',
58 | city: 'city',
59 | zip: 'zip',
60 | country: 'country',
61 | },
62 | nationalIdNumber: 'nationalIdNumber',
63 | document: {
64 | type: 'type',
65 | number: 'number',
66 | country: 'country',
67 | },
68 | });
69 |
70 | expect(axios.post).toHaveBeenCalledWith('/api/v1/ramps/kyc/user/', {
71 | address: {
72 | city: 'state',
73 | country: 'country',
74 | street: 'street',
75 | zip: 'zip',
76 | },
77 | date_of_birth: 'dateOfBirth',
78 | document: {
79 | country: 'country',
80 | number: 'number',
81 | type: 'type',
82 | },
83 | email: 'email',
84 | first_name: 'firstName',
85 | last_name: 'firstName',
86 | national_id_number: 'nationalIdNumber',
87 | phone: 'phone',
88 | type: 'type',
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/src/hooks/useKyc/index.tsx:
--------------------------------------------------------------------------------
1 | import useSWRMutation from 'swr/mutation';
2 |
3 | import { postUserKyc } from './requests';
4 | import endpoints from '@config/endpoints';
5 |
6 | export default function useKyc() {
7 | const { trigger, isMutating, data, error } = useSWRMutation(endpoints.userKyc, postUserKyc);
8 |
9 | return {
10 | postUserKyc: trigger,
11 | isMutating,
12 | data,
13 | error,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/src/hooks/useKyc/requests.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosResponse } from 'axios';
2 |
3 | type PostUserKycArgs = {
4 | email: string;
5 | type: string;
6 | firstName: string;
7 | lastName: string;
8 | phone: string;
9 | dateOfBirth?: string;
10 | address: {
11 | street: string;
12 | state: string;
13 | city: string;
14 | zip: string;
15 | country: string;
16 | };
17 | nationalIdNumber: string;
18 | document: {
19 | type: string;
20 | number: string;
21 | country: string;
22 | cic?: string | undefined;
23 | identificadorCiudadano?: string | undefined;
24 | ocr?: string | undefined;
25 | numeroEmision?: string | undefined;
26 | };
27 | acceptedNotifications?: boolean | undefined;
28 | };
29 | type PostUserKycRequest = (
30 | endpoint: string,
31 | data: { arg: PostUserKycArgs },
32 | ) => Promise;
33 |
34 | type DocumentPayload = {
35 | type: string;
36 | number: string;
37 | issued_country_code: string;
38 | cic?: string | undefined;
39 | identificadorCiudadano?: string | undefined;
40 | ocr?: string | undefined;
41 | numeroEmision?: string | undefined;
42 | };
43 |
44 | export const postUserKyc: PostUserKycRequest = (endpoint, { arg }) => {
45 | const documentArgs: DocumentPayload = {
46 | type: arg.document.type,
47 | number: arg.document.number,
48 | issued_country_code: arg.document.country,
49 | };
50 | if (arg.document.cic) documentArgs.cic = arg.document.cic;
51 | if (arg.document.identificadorCiudadano)
52 | documentArgs.identificadorCiudadano = arg.document.identificadorCiudadano;
53 | if (arg.document.ocr) documentArgs.ocr = arg.document.ocr;
54 | if (arg.document.numeroEmision) documentArgs.numeroEmision = arg.document.numeroEmision;
55 | return axios.post(endpoint, {
56 | type: arg.type,
57 | email: arg.email,
58 | first_name: arg.firstName,
59 | last_name: arg.lastName,
60 | phone: arg.phone,
61 | date_of_birth: arg.dateOfBirth ?? '1990-12-14',
62 | address: {
63 | street: arg.address.street,
64 | state: arg.address.state,
65 | city: arg.address.city,
66 | zip: arg.address.zip,
67 | country: arg.address.country,
68 | },
69 | national_id_number: arg.nationalIdNumber,
70 | document: documentArgs,
71 | accepted_notifications: arg.acceptedNotifications,
72 | });
73 | };
74 |
--------------------------------------------------------------------------------
/src/hooks/useMagic/index.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 |
3 | import { MagicContext } from '@hooks/useMagicLinkAuth/MagicProvider';
4 |
5 | export default function useMagic() {
6 | const magic = useContext(MagicContext);
7 | return magic;
8 | }
9 |
--------------------------------------------------------------------------------
/src/hooks/useMagicLinkAuth/MagicProvider.tsx:
--------------------------------------------------------------------------------
1 | import { Magic } from 'magic-sdk';
2 | import { ReactNode, createContext, useEffect, useMemo, useState } from 'react';
3 | import env from '@config/env';
4 |
5 | type MagicContextType = {
6 | magic: Magic | null;
7 | };
8 |
9 | export const MagicContext = createContext({
10 | magic: null,
11 | });
12 |
13 | const MagicProvider = ({ children }: { children: ReactNode }) => {
14 | const [magic, setMagic] = useState(null);
15 |
16 | useEffect(() => {
17 | console.log('env.magicLink.secret', env.magicLink.secret);
18 | if (env.magicLink.secret) {
19 | const magic = new Magic(env.magicLink.secret);
20 |
21 | setMagic(magic);
22 | }
23 | }, []);
24 |
25 | const value = useMemo(() => {
26 | return {
27 | magic,
28 | };
29 | }, [magic]);
30 |
31 | return {children} ;
32 | };
33 |
34 | export default MagicProvider;
35 |
--------------------------------------------------------------------------------
/src/hooks/useMagicLinkAuth/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import useSWRMutation from 'swr/mutation';
3 | import useMagic from '@hooks/useMagic';
4 |
5 | import Cookies from 'js-cookie';
6 | import endpoints from '@config/endpoints';
7 | import { postAuthentication } from './requests';
8 | import env from '@config/env';
9 |
10 | export default function useMagicLinkAuth() {
11 | const [isAuthenticatingMagicLink, setIsAuthenticatingMagicLink] = useState(false);
12 | const { magic } = useMagic();
13 | const { trigger, isMutating, data, error } = useSWRMutation(
14 | endpoints.postAuth,
15 | postAuthentication,
16 | );
17 |
18 | const login = async ({ email = '' }) => {
19 | setIsAuthenticatingMagicLink(true);
20 | try {
21 | if (magic) {
22 | const did = await magic.auth.loginWithEmailOTP({ email, showUI: true });
23 | const userInfo = await magic.user.getInfo();
24 |
25 | const rsp = await trigger({ username: userInfo.email ?? '', password: did ?? '' });
26 | Cookies.set(env.authCookieName, rsp.token, { domain: window.location.hostname });
27 | localStorage.setItem('bnd', userInfo.email ?? '');
28 | return rsp;
29 | }
30 | } finally {
31 | setIsAuthenticatingMagicLink(false);
32 | }
33 | };
34 |
35 | return {
36 | magic,
37 | login,
38 | data,
39 | error,
40 | isMutating: isMutating || isAuthenticatingMagicLink,
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/src/hooks/useMagicLinkAuth/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export type PostAuthenticationResponse = {
4 | refresh: string;
5 | token: string;
6 | kycLevel: number;
7 | };
8 | type PostAuthenticationRequest = (
9 | endpoint: string,
10 | data: { arg: { username: string; password: string } },
11 | ) => Promise;
12 | export const postAuthentication: PostAuthenticationRequest = (
13 | endpoint,
14 | { arg: { username, password } },
15 | ) =>
16 | axios
17 | .post(endpoint, { username, password }, { headers: { Authorization: '' } })
18 | .then(({ data }) => ({
19 | refresh: data.refresh,
20 | token: data.token,
21 | kycLevel: data.kyc_level,
22 | }));
23 |
--------------------------------------------------------------------------------
/src/hooks/useNetworks/index.test.tsx:
--------------------------------------------------------------------------------
1 | import '@config/axios';
2 | import axios from 'axios';
3 | import { renderHook, waitFor } from '@testing-library/react';
4 |
5 | import wrapper from '@helpers/TestProvider';
6 | import mockNetworks from './mock';
7 |
8 | jest.mock('axios');
9 |
10 | import useNetworks from '.';
11 |
12 | describe('useNetworks', () => {
13 | beforeEach(() => {
14 | (axios.get as jest.Mock).mockResolvedValue({
15 | data: mockNetworks,
16 | });
17 | });
18 |
19 | it('gets the networks list', async () => {
20 | const { result } = renderHook(() => useNetworks(), { wrapper });
21 |
22 | expect(result.current.networks).toBeUndefined();
23 | expect(result.current.totalNetworks).toBe(0);
24 |
25 | await waitFor(() => {
26 | expect(result.current.totalNetworks).toBe(3);
27 | expect(result.current.networks).toStrictEqual([
28 | {
29 | chainId: 1,
30 | explorerUrl: 'https://etherscan.io/',
31 | isActive: true,
32 | isTestnet: false,
33 | key: 'eth',
34 | logoUrl:
35 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/ethereum.svg',
36 | name: 'Ethereum',
37 | networkType: 'EVM',
38 | rpcUrl: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
39 | },
40 | {
41 | chainId: 42161,
42 | explorerUrl: 'https://arbiscan.io/',
43 | isActive: true,
44 | isTestnet: false,
45 | key: 'arb',
46 | logoUrl:
47 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/arbitrum.svg',
48 | name: 'Arbitrum',
49 | networkType: 'EVM',
50 | rpcUrl: 'https://arb1.arbitrum.io/rpc',
51 | },
52 | {
53 | chainId: 137,
54 | explorerUrl: 'https://polygonscan.com/',
55 | isActive: true,
56 | isTestnet: false,
57 | key: 'pol',
58 | logoUrl:
59 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/polygon.svg',
60 | name: 'Polygon',
61 | networkType: 'EVM',
62 | rpcUrl: 'https://polygon-rpc.com/',
63 | },
64 | ]);
65 | });
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/src/hooks/useNetworks/index.tsx:
--------------------------------------------------------------------------------
1 | import useSWR, { useSWRConfig } from 'swr';
2 |
3 | import { getNetworks } from './requests';
4 | import endpoints from '@config/endpoints';
5 | import { useCallback } from 'react';
6 |
7 | export default function useNetworks(direction: 'deposit' | 'withdraw' = 'deposit') {
8 | const { mutate } = useSWRConfig();
9 | const { data: networks, ...queryReturn } = useSWR(
10 | endpoints.networks,
11 | () => getNetworks(endpoints.networks, direction),
12 | {
13 | revalidateOnFocus: false,
14 | },
15 | );
16 |
17 | const refetchNetworks = useCallback(() => {
18 | mutate(endpoints.networks);
19 | }, [mutate]);
20 |
21 | return {
22 | refetchNetworks,
23 | networks,
24 | totalNetworks: networks?.length ?? 0,
25 | ...queryReturn,
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/hooks/useNetworks/mock.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | name: 'Ethereum',
4 | key: 'eth',
5 | logo_url:
6 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/ethereum.svg',
7 | chain_id: 1,
8 | rpc_url: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
9 | explorer_url: 'https://etherscan.io/',
10 | is_testnet: false,
11 | network_type: 'EVM',
12 | is_active: true,
13 | },
14 | {
15 | name: 'Arbitrum',
16 | key: 'arb',
17 | logo_url:
18 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/arbitrum.svg',
19 | chain_id: 42161,
20 | rpc_url: 'https://arb1.arbitrum.io/rpc',
21 | explorer_url: 'https://arbiscan.io/',
22 | is_testnet: false,
23 | network_type: 'EVM',
24 | is_active: true,
25 | },
26 | {
27 | name: 'Polygon',
28 | key: 'pol',
29 | logo_url:
30 | 'https://raw.githubusercontent.com/lifinance/types/main/src/assets/icons/chains/polygon.svg',
31 | chain_id: 137,
32 | rpc_url: 'https://polygon-rpc.com/',
33 | explorer_url: 'https://polygonscan.com/',
34 | is_testnet: false,
35 | network_type: 'EVM',
36 | is_active: true,
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/src/hooks/useNetworks/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export type Network = {
4 | name: string;
5 | key: string;
6 | logoUrl: string;
7 | chainId: number;
8 | rpcUrl: string;
9 | explorerUrl: string;
10 | isTestnet: boolean;
11 | networkType: string;
12 | isActive: boolean;
13 | showNetworkList: never;
14 | };
15 |
16 | type NetworkResponse = Network[];
17 | type GetNetworksRequest = (
18 | endpoint: string,
19 | direction: 'deposit' | 'withdraw',
20 | ) => Promise;
21 |
22 | export const getNetworks: GetNetworksRequest = (endpoint, direction = 'deposit') => {
23 | const dir = direction === 'deposit' ? 'ON' : 'OFF';
24 | const bnd = localStorage.getItem('bnd') || 'not_set';
25 | localStorage.setItem('direction', dir);
26 | return axios
27 | .get(endpoint, {
28 | params: { direction: dir },
29 | headers: { Authorization: '', Bnd: bnd },
30 | })
31 | .then(({ data }) => {
32 | return (data ?? []).map((network: Record) => ({
33 | name: network.name,
34 | key: network.key,
35 | logoUrl: network.logo_url,
36 | chainId: network.chain_id,
37 | rpcUrl: network.rpc_url,
38 | explorerUrl: network.explorer_url,
39 | isTestnet: network.is_testnet,
40 | networkType: network.network_type,
41 | isActive: network.is_active,
42 | }));
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/src/hooks/useQuote/index.test.tsx:
--------------------------------------------------------------------------------
1 | import '@config/axios';
2 | import axios from 'axios';
3 | import { renderHook, waitFor } from '@testing-library/react';
4 |
5 | import wrapper from '@helpers/TestProvider';
6 |
7 | jest.mock('axios');
8 |
9 | import useQuote from '.';
10 |
11 | describe('useQuote', () => {
12 | beforeEach(() => {
13 | (axios.post as jest.Mock).mockResolvedValue({
14 | data: {
15 | id: 46,
16 | base_currency: 'MXN',
17 | base_amount: '1000.00',
18 | quote_currency: 'USDC',
19 | quote_amount: '58.47',
20 | is_expired: false,
21 | expires_at: '2024-01-10T23:06:08.388000Z',
22 | quote_rate: '5.05',
23 | quote_rate_reciprocal: '16.85',
24 | network: 'network',
25 | },
26 | });
27 | });
28 |
29 | it('returns expected values', async () => {
30 | const { result } = renderHook(useQuote, { wrapper });
31 |
32 | expect(result.current.data).toBeUndefined();
33 | await result.current.getQuote({
34 | baseAmount: 1000,
35 | baseCurrency: 'MXN',
36 | quoteCurrency: 'USDC',
37 | network: 'polygon',
38 | });
39 |
40 | await waitFor(() => {
41 | expect(result.current.data).toStrictEqual({
42 | baseAmount: 1000,
43 | baseCurrency: 'MXN',
44 | expiresAt: '2024-01-10T23:06:08.388000Z',
45 | id: 46,
46 | isExpired: false,
47 | quoteAmount: 58.47,
48 | quoteCurrency: 'USDC',
49 | quoteRate: 5.05,
50 | quoteRateInverse: 16.85,
51 | network: 'network',
52 | });
53 | });
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/src/hooks/useQuote/index.tsx:
--------------------------------------------------------------------------------
1 | import useSWRMutation from 'swr/mutation';
2 | import { useEffect, useState } from 'react';
3 |
4 | import { getQuoteRequest, Quote } from './requests';
5 | import endpoints from '@config/endpoints';
6 |
7 | export default function useQuote() {
8 | const [quote, setQuote] = useState(null);
9 | const { trigger, isMutating, data, error } = useSWRMutation(endpoints.getQuote, getQuoteRequest);
10 |
11 | const resetQuote = () => setQuote(null);
12 |
13 | useEffect(() => {
14 | if (data) {
15 | setQuote(data);
16 | }
17 | }, [data]);
18 |
19 | return {
20 | quote,
21 | resetQuote,
22 | getQuote: trigger,
23 | isMutating,
24 | data,
25 | error,
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/hooks/useQuote/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export type RequestQuoteArgs = {
4 | baseAmount: number;
5 | quoteCurrency: string;
6 | baseCurrency: string;
7 | network: string;
8 | };
9 |
10 | export type Quote = RequestQuoteArgs & {
11 | id: number;
12 | quoteRate: number;
13 | quoteAmount: number;
14 | quoteRateInverse: number;
15 | isExpired: boolean;
16 | expiresAt: string;
17 | };
18 |
19 | type GetQuoteRequest = (endpoint: string, data: { arg: RequestQuoteArgs }) => Promise;
20 | export const getQuoteRequest: GetQuoteRequest = (endpoint, { arg }) =>
21 | axios
22 | .post(
23 | endpoint,
24 | {
25 | base_amount: String(arg.baseAmount),
26 | quote_currency: arg.quoteCurrency,
27 | base_currency: arg.baseCurrency,
28 | network: arg.network,
29 | },
30 | {
31 | headers: { Authorization: '' },
32 | },
33 | )
34 | .then(({ data }) => ({
35 | id: data.id,
36 | baseCurrency: data.base_currency,
37 | baseAmount: parseFloat(data.base_amount),
38 | quoteCurrency: data.quote_currency,
39 | quoteAmount: parseFloat(data.quote_amount),
40 | quoteRate: parseFloat(data.quote_rate),
41 | quoteRateInverse: parseFloat(data.quote_rate_reciprocal),
42 | isExpired: data.is_expired,
43 | expiresAt: data.expires_at,
44 | network: data.network,
45 | }));
46 |
--------------------------------------------------------------------------------
/src/hooks/useRecipient/index.ts:
--------------------------------------------------------------------------------
1 | import useSWRMutation from 'swr/mutation';
2 |
3 | import { postRecipient } from './requests';
4 | import endpoints from '@config/endpoints';
5 |
6 | export default function useRecipient() {
7 | const { trigger, isMutating, data, error } = useSWRMutation(
8 | endpoints.postRecipient,
9 | postRecipient,
10 | );
11 |
12 | return {
13 | postRecipient: trigger,
14 | isMutating,
15 | data,
16 | error,
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/hooks/useRecipient/requests.ts:
--------------------------------------------------------------------------------
1 | import axios, { AxiosResponse } from 'axios';
2 | import { OperationType } from '@hooks/useTransaction/requests';
3 |
4 | export type PostRecipientArgs = {
5 | asset: string;
6 | email: string;
7 | walletName?: string;
8 | network?: string;
9 | address?: string;
10 | firstName?: string;
11 | lastName?: string;
12 | operationType: OperationType;
13 | clabe?: string;
14 | };
15 |
16 | type PostRecipientRequest = (
17 | endpoint: string,
18 | data: { arg: PostRecipientArgs },
19 | ) => Promise;
20 | export const postRecipient: PostRecipientRequest = (endpoint, { arg }) => {
21 | const payload =
22 | arg.operationType === 'deposit'
23 | ? {
24 | account_type: 'WALLET_ACCOUNT',
25 | asset: arg.asset,
26 | network: (arg.network ?? 'polygon').toUpperCase(),
27 | data: {
28 | address: arg.address,
29 | wallet_name: arg.walletName ?? 'test',
30 | },
31 | }
32 | : {
33 | account_type: 'SPEI',
34 | asset: 'MXN',
35 | network: 'SPEI',
36 | data: {
37 | address: arg.clabe,
38 | first_name: arg.firstName,
39 | last_name: arg.lastName,
40 | },
41 | };
42 |
43 | return axios.post(endpoint, payload);
44 | };
45 |
--------------------------------------------------------------------------------
/src/hooks/useRemoteConfig/RemoteConfigProvider.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen, waitFor } from '@testing-library/react';
2 |
3 | import { default as getConfigs } from '@config/firebase/remoteConfig';
4 | import { PropsWithChildren } from 'react';
5 |
6 | import RemoteConfigProvider from './RemoteConfigProvider';
7 | import { ThemeProvider } from '@mui/material';
8 | import theme from '@config/theme.ts';
9 |
10 | const ChildComp = () => App has rendered!
;
11 |
12 | const wrapper = ({ children }: PropsWithChildren) => (
13 | {children}
14 | );
15 |
16 | jest.mock('@config/firebase/remoteConfig');
17 |
18 | describe('RemoteConfigProvider', () => {
19 | beforeEach(() => {
20 | (getConfigs as jest.Mock).mockResolvedValue({ api: 'api' });
21 | });
22 |
23 | it('should show a loader when remote configs have not loaded', () => {
24 | render(
25 |
26 |
27 | ,
28 | { wrapper },
29 | );
30 |
31 | expect(() => screen.getByText('App has rendered!')).toThrow();
32 | expect(screen.getByTestId('configs-loader')).toBeInTheDocument();
33 | });
34 |
35 | it('should show app when configs have loaded', async () => {
36 | render(
37 |
38 |
39 | ,
40 | { wrapper },
41 | );
42 |
43 | await waitFor(() => {
44 | screen.getByText('App has rendered!');
45 | expect(() => screen.getByTestId('configs-loader')).toThrow();
46 | });
47 | });
48 |
49 | it('should mantain loader when getting the configs fails', async () => {
50 | (getConfigs as jest.Mock).mockRejectedValue('oops');
51 |
52 | render(
53 |
54 |
55 | ,
56 | { wrapper },
57 | );
58 |
59 | expect(() => screen.getByText('App has rendered!')).toThrow();
60 | expect(screen.getByTestId('configs-loader')).toBeInTheDocument();
61 |
62 | await waitFor(() => {
63 | expect(() => screen.getByText('App has rendered!')).toThrow();
64 | expect(screen.getByTestId('configs-loader')).toBeInTheDocument();
65 | });
66 | });
67 | });
68 |
--------------------------------------------------------------------------------
/src/hooks/useRemoteConfig/RemoteConfigProvider.tsx:
--------------------------------------------------------------------------------
1 | import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
2 | import { default as getConfigs, AppConfig } from '@config/firebase/remoteConfig';
3 | import Box from '@mui/material/Box';
4 | import CircularProgress from '@mui/material/CircularProgress';
5 |
6 | import { Provider } from '.';
7 |
8 | const RemoteConfigProvider = ({ children }: PropsWithChildren) => {
9 | const [configs, setConfigs] = useState(null);
10 |
11 | const fetchConfigs = useCallback(async () => {
12 | try {
13 | const fetchedConfigs = await getConfigs();
14 | setConfigs(fetchedConfigs);
15 | } catch {
16 | setConfigs(null);
17 | }
18 | }, []);
19 |
20 | useEffect(() => {
21 | fetchConfigs();
22 | }, [fetchConfigs]);
23 |
24 | if (!configs) {
25 | return (
26 |
39 |
44 |
45 | );
46 | }
47 |
48 | return {children} ;
49 | };
50 |
51 | export default RemoteConfigProvider;
52 |
--------------------------------------------------------------------------------
/src/hooks/useRemoteConfig/index.ts:
--------------------------------------------------------------------------------
1 | import { useContext, createContext } from 'react';
2 | import { AppConfig } from '@config/firebase/remoteConfig';
3 |
4 | export const remoteConfigContext = createContext(null);
5 | export const { Provider } = remoteConfigContext;
6 |
7 | export default function useRemoteConfig() {
8 | const configs = useContext(remoteConfigContext) as AppConfig;
9 | return { configs };
10 | }
11 |
--------------------------------------------------------------------------------
/src/hooks/useTokens/index.ts:
--------------------------------------------------------------------------------
1 | import useSWR, { useSWRConfig } from 'swr';
2 | import { useCallback, useEffect, useMemo, useState } from 'react';
3 | import { matchSorter } from 'match-sorter';
4 |
5 | import { getTokens, Token } from './requests';
6 | import endpoints from '@config/endpoints';
7 | import { OperationType } from '@hooks/useTransaction/requests';
8 |
9 | const TOKEN_QUERYSTR = '/?all=true';
10 |
11 | type UseTokensArgs = {
12 | chainKey?: string;
13 | operationType?: OperationType;
14 | };
15 |
16 | export default function useTokens({ chainKey = '', operationType }: UseTokensArgs) {
17 | const { mutate } = useSWRConfig();
18 |
19 | const [rspTokens, setRspTokens] = useState([]);
20 | const [tokens, setTokens] = useState([]);
21 |
22 | const { data, ...queryReturn } = useSWR(
23 | `${endpoints.tokens}${chainKey}${TOKEN_QUERYSTR}`,
24 | chainKey ? getTokens : null,
25 | {
26 | revalidateOnFocus: false,
27 | },
28 | );
29 |
30 | const refetchTokens = useCallback(() => {
31 | mutate(`${endpoints.tokens}${chainKey}${TOKEN_QUERYSTR}`);
32 | }, [mutate, chainKey]);
33 |
34 | const resetTokens = () =>
35 | !!data?.tokens && setTokens(data.tokens.filter((token) => !!token.symbol));
36 |
37 | const filteredTokensByOperationType = useMemo(
38 | () =>
39 | operationType == 'deposit'
40 | ? data?.tokens?.filter((token) => token?.isOnrampActive && !!token.symbol)
41 | : data?.tokens?.filter((token) => token?.isOfframpActive && !!token.symbol),
42 | [data, operationType],
43 | );
44 |
45 | const filterTokens = useCallback(
46 | (search: string) => {
47 | if (!search && rspTokens.length) {
48 | setTokens(rspTokens);
49 | return;
50 | }
51 | if (rspTokens) {
52 | const sort = matchSorter(rspTokens, search, { keys: ['key', 'name'] });
53 | setTokens(sort);
54 | return sort;
55 | }
56 | },
57 | [rspTokens],
58 | );
59 |
60 | useEffect(() => {
61 | if (filteredTokensByOperationType) {
62 | setTokens(filteredTokensByOperationType);
63 | setRspTokens(filteredTokensByOperationType);
64 | }
65 | }, [filteredTokensByOperationType]);
66 |
67 | return {
68 | refetchTokens,
69 | data,
70 | tokens,
71 | resetTokens,
72 | filterTokens,
73 | totalTokens: data?.tokens?.length ?? 0,
74 | ...queryReturn,
75 | };
76 | }
77 |
--------------------------------------------------------------------------------
/src/hooks/useTokens/mock.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | count: 1348,
3 | next: 'http://localhost:8000/api/v1/ramps/token/pol/?page=2',
4 | previous: null,
5 | results: [
6 | {
7 | id: 33065,
8 | name: 'MetaTrace Utility Token',
9 | symbol: 'ACE',
10 | key: 'ACE',
11 | address: '0x9627a3d6872bE48410fCEce9b1dDD344Bf08c53e',
12 | decimals: 2,
13 | is_onramp_verified: true,
14 | is_offramp_verified: true,
15 | is_onramp_active: false,
16 | is_offramp_active: false,
17 | image_url: null,
18 | min_allowance: null,
19 | max_allowance: null,
20 | },
21 | {
22 | id: 33000,
23 | name: 'Krasnalcoin',
24 | symbol: 'KC',
25 | key: 'KC',
26 | address: '0x784665471bB8B945b57A76a9200B109Ee214E789',
27 | decimals: 6,
28 | is_onramp_verified: true,
29 | is_offramp_verified: true,
30 | is_onramp_active: false,
31 | is_offramp_active: false,
32 | image_url: null,
33 | min_allowance: null,
34 | max_allowance: null,
35 | },
36 | {
37 | id: 33410,
38 | name: 'Medieus Token',
39 | symbol: 'MDUS',
40 | key: 'MDUS',
41 | address: '0xAb9CB20A28f97e189ca0B666B8087803Ad636b3C',
42 | decimals: 18,
43 | is_onramp_verified: true,
44 | is_offramp_verified: true,
45 | is_onramp_active: false,
46 | is_offramp_active: false,
47 | image_url: null,
48 | min_allowance: null,
49 | max_allowance: null,
50 | },
51 | ],
52 | };
53 |
--------------------------------------------------------------------------------
/src/hooks/useTokens/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export type Token = {
4 | id: number;
5 | name: string;
6 | symbol: string;
7 | key: string;
8 | address: string;
9 | decimals: number;
10 | isOnrampVerified: boolean;
11 | isOfframpVerified: boolean;
12 | isOnrampActive: boolean;
13 | isOfframpActive: boolean;
14 | imageUrl: string;
15 | minAllowance: number;
16 | maxAllowance: number;
17 | };
18 |
19 | type TokenResponse = {
20 | count?: number;
21 | next?: string;
22 | previous?: string;
23 | tokens?: Token[];
24 | };
25 |
26 | type GetTokensRequest = (endpoint: string) => Promise;
27 |
28 | const mapToken = (token: Record) => ({
29 | id: token?.id,
30 | name: token?.name,
31 | symbol: token?.symbol,
32 | key: token?.key,
33 | address: token?.address,
34 | decimals: token?.decimals,
35 | isOnrampVerified: token?.is_onramp_verified,
36 | isOfframpVerified: token?.is_offramp_verified,
37 | isOnrampActive: token?.is_onramp_active,
38 | isOfframpActive: token?.is_offramp_active,
39 | imageUrl: token?.image_url,
40 | minAllowance: parseFloat((token?.min_allowance as string) ?? '2'), // Set default of 2 when there is no min_allowance
41 | maxAllowance: parseFloat((token?.max_allowance as string) ?? '20'), // Set default of 20 when there is no max_allowance
42 | });
43 |
44 | export const getTokens: GetTokensRequest = (endpoint) => {
45 | const bnd = localStorage.getItem('bnd') || '';
46 | const direction = localStorage.getItem('direction') || '';
47 | return axios
48 | .get(endpoint, {
49 | params: { direction: direction },
50 | headers: { Authorization: '', Bnd: bnd },
51 | })
52 | .then(({ data }) => {
53 | const tokenArr = (data.length ? data : data.results) ?? [];
54 | return {
55 | count: data?.count,
56 | next: data?.next,
57 | previous: data?.previous,
58 | tokens: tokenArr.map((token: Record) => mapToken(token)),
59 | };
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/src/hooks/useTransaction/index.ts:
--------------------------------------------------------------------------------
1 | import useSWRMutation from 'swr/mutation';
2 | import useSWR, { useSWRConfig } from 'swr';
3 | import { secondsToMilliseconds } from 'date-fns';
4 |
5 | import { postTransaction, getTransaction } from './requests';
6 | import endpoints from '@config/endpoints';
7 | import { useCallback, useEffect, useRef } from 'react';
8 |
9 | type SetInterval = ReturnType;
10 |
11 | type UseTransactionArgs = {
12 | transactionId?: string;
13 | };
14 |
15 | const TRANSACTION_CHECK_INTERVAL_SECS = 20;
16 |
17 | export default function useTransaction({ transactionId = '' }: UseTransactionArgs) {
18 | const { mutate } = useSWRConfig();
19 | const timerRef = useRef(0);
20 | const statusCheckInterval = secondsToMilliseconds(TRANSACTION_CHECK_INTERVAL_SECS);
21 |
22 | const { trigger, isMutating, data, error } = useSWRMutation(
23 | endpoints.transaction,
24 | postTransaction,
25 | );
26 |
27 | const { data: transaction, isLoading } = useSWR(
28 | `${endpoints.transaction}${transactionId}/`,
29 | transactionId ? getTransaction : null,
30 | {
31 | revalidateOnFocus: false,
32 | },
33 | );
34 |
35 | const refetchTransaction = useCallback(() => {
36 | mutate(`${endpoints.transaction}${transactionId}/`);
37 | }, [mutate, transactionId]);
38 |
39 | const transactionStatus = transaction?.providerStatus ?? '';
40 | const isTransactionInProgress = ![
41 | 'COMPLETED',
42 | 'FAILED',
43 | 'EXPIRED',
44 | 'REJECTED',
45 | 'CANCELED',
46 | ].includes(transactionStatus);
47 |
48 | // Effect will refetch transaction every TRANSACTION_CHECK_INTERVAL_SECS seconds
49 | // until status is either completed or failed
50 | useEffect(() => {
51 | if (isTransactionInProgress) {
52 | timerRef.current = setInterval(() => {
53 | refetchTransaction();
54 | }, statusCheckInterval);
55 | }
56 |
57 | return () => {
58 | clearTimeout(timerRef.current);
59 | };
60 | }, [isTransactionInProgress, statusCheckInterval, refetchTransaction]);
61 |
62 | return {
63 | transaction,
64 | refetchTransaction,
65 | isLoading,
66 | postTransaction: trigger,
67 | isMutating,
68 | isTransactionInProgress,
69 | data,
70 | error,
71 | };
72 | }
73 |
--------------------------------------------------------------------------------
/src/hooks/useTransactions/index.ts:
--------------------------------------------------------------------------------
1 | import useSWR, { useSWRConfig } from 'swr';
2 |
3 | import { getTransactions } from './requests';
4 | import endpoints from '@config/endpoints';
5 | import { useCallback } from 'react';
6 |
7 | export default function useTransactions() {
8 | const { mutate } = useSWRConfig();
9 | const { data: transactions, ...queryReturn } = useSWR(
10 | endpoints.transactionHistory,
11 | getTransactions,
12 | {
13 | revalidateOnFocus: false,
14 | },
15 | );
16 |
17 | const refetchTransactions = useCallback(() => {
18 | mutate(endpoints.transactionHistory);
19 | }, [mutate]);
20 |
21 | return {
22 | refetchTransactions,
23 | transactions,
24 | ...queryReturn,
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/useTransactions/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import {
3 | mapTransactionData,
4 | Transaction,
5 | TransactionRequest,
6 | } from '@hooks/useTransaction/requests';
7 |
8 | type GroupedTransactions = {
9 | [key: string]: Transaction[];
10 | };
11 | type GetTransactionsRequest = (endpoint: string) => Promise;
12 |
13 | export const getTransactions: GetTransactionsRequest = (endpoint) =>
14 | axios.get(endpoint).then(({ data }) => {
15 | const parsedData: GroupedTransactions = Object.keys(data).reduce(
16 | (acc: GroupedTransactions, key) => {
17 | acc[key] = data[key]?.map((transaction: TransactionRequest) =>
18 | mapTransactionData(transaction),
19 | );
20 | return acc;
21 | },
22 | {},
23 | );
24 | return parsedData;
25 | });
26 |
--------------------------------------------------------------------------------
/src/hooks/useUser/IntercomProvider.tsx:
--------------------------------------------------------------------------------
1 | import Intercom from '@intercom/messenger-js-sdk';
2 | import { PropsWithChildren, useEffect } from 'react';
3 | import { User } from './types';
4 | import env from '@config/env';
5 |
6 | type IntercomProviderProps = PropsWithChildren & {
7 | user: Partial | null;
8 | };
9 |
10 | export default function IntercomProvider({ children, user }: IntercomProviderProps) {
11 | // NOTE: Boot Intercom on app start
12 | useEffect(() => {
13 | Intercom({ app_id: env.intercomAppId });
14 | }, []);
15 |
16 | // NOTE: Set or unset User data on Intercom
17 | useEffect(() => {
18 | if (!user) {
19 | Intercom({ app_id: env.intercomAppId });
20 | } else {
21 | Intercom({
22 | app_id: env.intercomAppId,
23 | ...(user.email && { email: user.email }),
24 | ...(user.id && { user_id: String(user.id) }),
25 | ...(user.firstName && { name: [user?.firstName, user.lastName].join(' ') }),
26 | // created_at: // TODO: Define what to send here
27 | });
28 | }
29 | }, [user]);
30 |
31 | return <>{children}>;
32 | }
33 |
--------------------------------------------------------------------------------
/src/hooks/useUser/requests.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { User } from '@hooks/useUser/types';
3 |
4 | type GetUserDataRequest = (endpoint: string) => Promise>;
5 | export const getUserData: GetUserDataRequest = (endpoint) =>
6 | axios
7 | .get(endpoint)
8 | .then(({ data }) => ({
9 | id: data.id,
10 | kycLevel: data.kyc_level,
11 | externalId: data.external_id,
12 | firstName: data.first_name,
13 | lastName: data.last_name,
14 | killbId: data.killb_id,
15 | dateOfBirth: data.date_of_birth,
16 | phone: data.phone,
17 | nationalIdNumber: data.national_id_number,
18 | email: data.email,
19 | currentDepositUsageUsd: Math.abs(parseFloat(data.current_deposit_usage_usd)),
20 | currentWithdrawalUsageUsd: Math.abs(parseFloat(data.current_withdrawal_usage_usd)),
21 | resetAt: data.reset_at,
22 | complianceUrl: data.compliance_url,
23 | active: data.active,
24 | onboardingStatus: data.status,
25 | }))
26 | .catch((error) => {
27 | if (error.response.data.success) return Promise.resolve({ kycLevel: 0 });
28 | return Promise.reject(error);
29 | });
30 |
--------------------------------------------------------------------------------
/src/hooks/useUser/types.ts:
--------------------------------------------------------------------------------
1 | export type User = {
2 | id: number;
3 | kycLevel: number;
4 | email: string;
5 | publicAddress: string;
6 | externalId: string;
7 | firstName: string;
8 | lastName: string;
9 | killbId: string;
10 | dateOfBirth: string;
11 | phone: string;
12 | nationalIdNumber: string;
13 | currentDepositUsageUsd: number;
14 | currentWithdrawalUsageUsd: number;
15 | resetAt: string;
16 | complianceUrl: string;
17 | onboardingStatus: string;
18 | active: boolean;
19 | };
20 |
21 | export type UserContextType = {
22 | user: Partial | null;
23 | fetchUser: () => Promise;
24 | logoutUser: () => Promise;
25 | setUser: (userData: Partial) => void;
26 | resetUser: () => void;
27 | isLoading: boolean;
28 | dataLoaded: boolean;
29 | };
30 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | color-scheme: light;
3 | font-synthesis: none;
4 | text-rendering: optimizeLegibility;
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 | }
8 |
9 | *, *:before, *:after {
10 | box-sizing: border-box;
11 | }
12 |
13 |
14 | body {
15 | margin: 0;
16 | font-size: 16px;
17 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
18 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
19 | sans-serif;
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | overflow-x: hidden;
23 | }
24 |
25 | code {
26 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
27 | monospace;
28 | }
29 |
30 | #root {
31 | min-height: 100vh;
32 | width: 100%;
33 | display: flex;
34 | flex-direction: column;
35 | }
36 |
37 | .landing-page {
38 | min-height: 100vh;
39 | width: 100%;
40 | background: url('/images/Background.png');
41 | background-repeat: no-repeat;
42 | background-attachment: fixed;
43 | background-size: cover;
44 | }
45 |
46 |
47 |
48 | .PhoneInput {
49 | display: flex;
50 | align-items: center;
51 | flex-direction: row;
52 | width: 100%;
53 | gap: 16px;
54 |
55 | & > div:first-child {
56 | width: 30%;
57 | }
58 |
59 | & > div:last-child {
60 | width: 70%;
61 | }
62 | }
63 |
64 | .navbar-box {
65 | max-width: 1280px !important;
66 | }
67 |
68 | @keyframes inAnimation {
69 | 0% {
70 | opacity: 0;
71 | height: 0;
72 | visibility: hidden;
73 | }
74 | 100% {
75 | opacity: 1;
76 | height: auto;
77 | visibility: visible;
78 | }
79 | }
80 |
81 | @keyframes outAnimation {
82 | 0% {
83 | opacity: 1;
84 | height: auto;
85 |
86 | }
87 | 100% {
88 | opacity: 0;
89 | height: 0;
90 | visibility: hidden;
91 | }
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/src/layouts/CleanLayout/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import CleanLayout from '.';
5 |
6 | describe('CleanLayout', () => {
7 | it('should render ', () => {
8 | render(
9 |
10 | Layout container
11 | ,
12 | { wrapper },
13 | );
14 |
15 | screen.getByText('Layout container');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/layouts/CleanLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import Box from '@mui/material/Box';
3 | import { PropsWithChildren } from 'react';
4 | import Navbar from '@components/Navbar';
5 | import SimpleFooter from '@components/SimpleFooter';
6 | import theme from '@config/theme';
7 | const TOP_PADDING = '78px';
8 |
9 | const LayoutContainer = styled('div')(() => ({
10 | width: '100%',
11 | height: 'auto',
12 | minHeight: '100vh',
13 | background: "url('/images/Background.png')",
14 | backgroundRepeat: 'no-repeat',
15 | backgroundAttachment: 'fixed',
16 | backgroundSize: 'cover',
17 | }));
18 |
19 | const Container = styled('div')(() => ({
20 | width: '100%',
21 | margin: '0 auto',
22 | height: 'auto',
23 | display: 'flex',
24 | padding: theme.spacing(2),
25 | paddingTop: TOP_PADDING,
26 | }));
27 |
28 | const ContentContainer = styled(Box)(() => ({
29 | display: 'flex',
30 | flexDirection: 'column',
31 | width: '100%',
32 | maxWidth: '1280px',
33 | margin: '0 auto',
34 | padding: 0,
35 | }));
36 |
37 | export type CleanLayoutProps = PropsWithChildren;
38 |
39 | export default function CleanLayout({ children }: CleanLayoutProps) {
40 | return (
41 |
42 |
43 |
44 | {children}
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/src/layouts/ColumnLayout/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import ColumnLayout from '.';
5 |
6 | describe('ColumnLayout', () => {
7 | it('should render ', () => {
8 | render(
9 | Right content}
11 | leftContent={Left content
}
12 | />,
13 | { wrapper },
14 | );
15 |
16 | screen.getByText('Right content');
17 | screen.getByText('Left content');
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/layouts/ColumnLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import Box from '@mui/material/Box';
3 | import { ReactNode } from 'react';
4 | import Navbar from '@components/Navbar';
5 |
6 | const TOP_PADDING = '78px';
7 |
8 | const LayoutContainer = styled('div')(() => ({
9 | width: '100%',
10 | height: 'auto',
11 | minHeight: '100vh',
12 | background: "url('/images/Background.png')",
13 | backgroundRepeat: 'no-repeat',
14 | backgroundAttachment: 'fixed',
15 | backgroundSize: 'cover',
16 | }));
17 |
18 | const Container = styled('div')(() => ({
19 | width: '100%',
20 | margin: '0 auto',
21 | height: 'auto',
22 | minHeight: '100vh',
23 | display: 'flex',
24 | alignItems: 'stretch',
25 | }));
26 |
27 | const ContentContainer = styled(Box)(() => ({
28 | display: 'flex',
29 | width: '100%',
30 | }));
31 |
32 | const LeftGrid = styled(Box)(({ theme }) => ({
33 | display: 'flex',
34 | maxWidth: '557px',
35 | flexGrow: 1,
36 | justifyContent: 'center',
37 | padding: theme.spacing(3),
38 | paddingBottom: '12%',
39 | paddingTop: TOP_PADDING,
40 | flexDirection: 'column',
41 | backgroundColor: '#fff',
42 | zIndex: 201,
43 |
44 | [theme.breakpoints.down('md')]: {
45 | maxWidth: '100%',
46 | },
47 | }));
48 |
49 | const RightGrid = styled(Box)(({ theme }) => ({
50 | display: 'flex',
51 | flexGrow: 1,
52 | padding: theme.spacing(3),
53 | paddingTop: TOP_PADDING,
54 | [theme.breakpoints.down('md')]: {
55 | display: 'none',
56 | },
57 | }));
58 |
59 | export type ExposedLayoutProps = {
60 | rightContent?: ReactNode;
61 | leftContent?: ReactNode;
62 | alignTop?: boolean;
63 | };
64 |
65 | export default function ColumnLayout({
66 | rightContent,
67 | leftContent,
68 | alignTop = false,
69 | }: ExposedLayoutProps) {
70 | const leftSideSx = alignTop ? { justifyContent: 'flex-start' } : {};
71 |
72 | return (
73 |
74 |
75 |
76 |
77 | {leftContent}
78 | {rightContent}
79 |
80 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/src/layouts/EmptyLayout/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import EmptyLayout from '.';
5 |
6 | describe('EmptyLayout', () => {
7 | it('should render ', () => {
8 | render(
9 |
10 | Layout container
11 | ,
12 | { wrapper },
13 | );
14 |
15 | screen.getByText('Layout container');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/layouts/EmptyLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import { styled } from '@mui/material/styles';
2 | import Box from '@mui/material/Box';
3 | import { PropsWithChildren } from 'react';
4 | import SimpleFooter from '@components/SimpleFooter';
5 | import theme from '@config/theme';
6 | import BLogo from '../../assets/logo.svg';
7 | const TOP_PADDING = '38px';
8 |
9 | const LayoutContainer = styled('div')(() => ({
10 | width: '100%',
11 | height: 'auto',
12 | minHeight: '100vh',
13 | background:
14 | 'linear-gradient(45deg, rgba(247,251,252,1) 0%,rgba(220,244,235,0.8) 40%,rgba(64,180,148,0.5) 100%);',
15 | backgroundRepeat: 'no-repeat',
16 | backgroundAttachment: 'fixed',
17 | backgroundSize: 'cover',
18 | }));
19 |
20 | const Container = styled('div')(() => ({
21 | width: '100%',
22 | margin: '0 auto',
23 | height: 'auto',
24 | minHeight: '10vh',
25 | display: 'flex',
26 | padding: theme.spacing(2),
27 | paddingTop: TOP_PADDING,
28 | }));
29 |
30 | const ContentContainer = styled(Box)(() => ({
31 | display: 'flex',
32 | flexDirection: 'column',
33 | width: '100%',
34 | maxWidth: '1200px',
35 | margin: '0 auto',
36 | padding: 0,
37 | }));
38 |
39 | const Logo = styled('img')(() => ({
40 | width: '120px',
41 | margin: '0 auto',
42 | }));
43 |
44 | export type EmptyLayoutProps = PropsWithChildren;
45 |
46 | export default function EmptyLayout({ children }: EmptyLayoutProps) {
47 | return (
48 |
49 |
50 |
51 |
52 |
53 | {children}
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/layouts/LandingLayout/index.test.tsx:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import wrapper from '@helpers/TestProvider';
3 |
4 | import LandingLayout from '.';
5 |
6 | describe('LandingLayout', () => {
7 | it('should render ', () => {
8 | render(
9 |
10 | Layout content
11 | ,
12 | { wrapper },
13 | );
14 |
15 | screen.getByText('Layout content');
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import { ThemeProvider } from '@mui/material';
4 | import { RouterProvider } from 'react-router-dom';
5 | import MagicProvider from '@hooks/useMagicLinkAuth/MagicProvider';
6 | import MagicUserProvider from '@hooks/useUser/MagicUserProvider';
7 | import RemoteConfigProvider from '@hooks/useRemoteConfig/RemoteConfigProvider';
8 |
9 | import initFirebase from '@config/firebase';
10 | import initTapfiliate from '@config/tapfiliate';
11 |
12 | import router from 'routes/index.tsx';
13 | import theme from '@config/theme.ts';
14 | import { setupDateLocale } from '@helpers/getUserLanguage';
15 |
16 | import '@config/axios';
17 | import '@config/sentry';
18 | import './translations';
19 | import './index.css';
20 |
21 | setupDateLocale();
22 | initFirebase();
23 | initTapfiliate();
24 |
25 | ReactDOM.createRoot(document.getElementById('root')!).render(
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ,
37 | );
38 |
--------------------------------------------------------------------------------
/src/mui.d.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
2 | import { Palette } from '@mui/material/styles';
3 |
4 | declare module '@mui/material/styles' {
5 | interface Palette {
6 | ink: {
7 | i950: string;
8 | i900: string;
9 | i800: string;
10 | i700: string;
11 | i600: string;
12 | i500: string;
13 | i400: string;
14 | i300: string;
15 | i250: string;
16 | i200: string;
17 | i150: string;
18 | i100: string;
19 | i000: string;
20 | };
21 | }
22 |
23 | interface PaletteOptions {
24 | ink: {
25 | i950: string;
26 | i900: string;
27 | i800: string;
28 | i700: string;
29 | i600: string;
30 | i500: string;
31 | i400: string;
32 | i300: string;
33 | i250: string;
34 | i200: string;
35 | i150: string;
36 | i100: string;
37 | i000: string;
38 | };
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/pages/FAQ/index.tsx:
--------------------------------------------------------------------------------
1 | import CleanLayout from '@layouts/CleanLayout';
2 | import MarkDownContainer from '@components/MarkDownContainer';
3 | import { useTranslation } from 'react-i18next';
4 | export default function FAQ() {
5 | const { t } = useTranslation('faq');
6 |
7 | const markdown = `
8 | # ${t('title')}
9 |
10 | ### **${t('q1.title')}**
11 |
12 | ${t('q1.info')}
13 |
14 | ### **${t('q2.title')}**
15 |
16 | ${t('q2.info')}
17 |
18 | ### **${t('q3.title')}**
19 |
20 | ${t('q3.info')}
21 |
22 | ### **${t('q4.title')}**
23 |
24 | ${t('q4.info')}
25 |
26 | ### **${t('q5.title')}**
27 |
28 | ${t('q5.info')}
29 |
30 | ### **${t('q6.title')}**
31 |
32 | ${t('q6.info')}
33 |
34 | ${t('q6.info2')}
35 |
36 | ### **${t('q7.title')}**
37 |
38 | ${t('q7.info')}`;
39 |
40 | return (
41 |
42 |
46 |
47 | );
48 | }
49 |
--------------------------------------------------------------------------------
/src/pages/Kyc/index.tsx:
--------------------------------------------------------------------------------
1 | import EmptyLayout from '@layouts/EmptyLayout';
2 | import KycForm from '@components/forms/KycForm';
3 |
4 | export default function Kyc() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/Landing/index.tsx:
--------------------------------------------------------------------------------
1 | import CleanLayout from '@layouts/CleanLayout';
2 | import GetQuoteForm from '@components/forms/GetQuoteForm/v2';
3 |
4 | export default function Landing() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/ProtectedRamp/index.tsx:
--------------------------------------------------------------------------------
1 | import ColumnLayout from '@layouts/ColumnLayout';
2 | import RampForm from '@components/forms/RampForm';
3 | import KycBulletPoints from '@components/KycBulletPoints';
4 |
5 | export default function ProtectedRamp() {
6 | return (
7 | }
9 | rightContent={ }
10 | alignTop
11 | />
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/pages/Ramp/index.tsx:
--------------------------------------------------------------------------------
1 | import CleanLayout from '@layouts/CleanLayout';
2 | import RampForm from '@components/forms/RampForm';
3 |
4 | export default function Ramp() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/SignIn/schema.ts:
--------------------------------------------------------------------------------
1 | import * as yup from 'yup';
2 |
3 | export type SignInFormValues = {
4 | email: string;
5 | };
6 |
7 | const schema = yup.object().shape({
8 | email: yup.string().email().required(),
9 | });
10 |
11 | export default schema;
12 |
--------------------------------------------------------------------------------
/src/pages/Transactions/Detail/index.tsx:
--------------------------------------------------------------------------------
1 | import CleanLayout from '@layouts/CleanLayout';
2 | import TransactionDetailComponent from '@components/TransactionDetails';
3 |
4 | import useTransaction from '@hooks/useTransaction';
5 | import { useParams } from 'react-router-dom';
6 | import { Transaction } from '@hooks/useTransaction/requests';
7 |
8 | export default function TransactionDetail() {
9 | const { txnId: transactionId } = useParams();
10 | const { transaction } = useTransaction({ transactionId });
11 |
12 | return (
13 |
14 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/pages/Transactions/History/index.tsx:
--------------------------------------------------------------------------------
1 | import CleanLayout from '@layouts/CleanLayout';
2 | import BoxContainer from '@components/BoxContainer';
3 | import Typography from '@mui/material/Typography';
4 |
5 | import TransactionsTable from '@components/TransactionsTable';
6 | import { useTranslation } from 'react-i18next';
7 |
8 | export default function TransactionHistory() {
9 | const { t } = useTranslation('transactions');
10 |
11 | return (
12 |
13 |
23 |
32 | {t('history')}
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/src/pages/Transactions/KycDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import ColumnLayout from '@layouts/ColumnLayout';
2 | import KycBulletPoints from '@components/KycBulletPoints';
3 | import TransactionDetailComponent from '@components/TransactionDetails';
4 | import useTransaction from '@hooks/useTransaction';
5 | import { useParams } from 'react-router-dom';
6 | import { Transaction } from '@hooks/useTransaction/requests';
7 |
8 | export default function TransactionKycDetail() {
9 | const { txnId: transactionId } = useParams();
10 | const { transaction } = useTransaction({ transactionId });
11 |
12 | return (
13 |
25 | }
26 | rightContent={ }
27 | alignTop
28 | />
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/src/routes/ExposedWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { useOutlet } from 'react-router-dom';
2 |
3 | export default function ExposedWrapper() {
4 | const outlet = useOutlet();
5 | return <>{outlet}>;
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/OnlyGuestWrapper.tsx:
--------------------------------------------------------------------------------
1 | /*import { useOutlet, useNavigate } from 'react-router-dom';
2 | import useUser from '@hooks/useUser';
3 | import { useEffect } from 'react';
4 | import SiteSpinner from '@components/SiteSpinner';
5 |
6 | export default function OnlyGuestWrapper() {
7 | const outlet = useOutlet();
8 | const navigate = useNavigate();
9 | const { isUnauthorized, isLoading } = useUser();
10 |
11 | useEffect(() => {
12 | if (!isLoading && !isUnauthorized) {
13 | navigate('/', { replace: true });
14 | }
15 | }, [isUnauthorized, navigate, isLoading]);
16 |
17 | if (isLoading) {
18 | return ;
19 | }
20 |
21 | return <>{outlet}>;
22 | }*/
23 |
--------------------------------------------------------------------------------
/src/routes/ProtectedWrapper.tsx:
--------------------------------------------------------------------------------
1 | import { useOutlet, useNavigate } from 'react-router-dom';
2 | import useUser from '@hooks/useUser';
3 | import { useEffect } from 'react';
4 | import SiteSpinner from '@components/SiteSpinner';
5 |
6 | export default function ProtectedWrapper() {
7 | const outlet = useOutlet();
8 | const navigate = useNavigate();
9 | const { isUnauthorized, isLoading } = useUser();
10 |
11 | useEffect(() => {
12 | if (isUnauthorized && !isLoading) {
13 | navigate('/signin', { replace: true });
14 | }
15 | }, [isUnauthorized, isLoading, navigate]);
16 |
17 | if (isLoading) {
18 | return ;
19 | }
20 |
21 | return <>{outlet}>;
22 | }
23 |
--------------------------------------------------------------------------------
/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import { Route, createBrowserRouter, createRoutesFromElements } from 'react-router-dom';
2 |
3 | import Landing from '@pages/Landing';
4 | import SignIn from '@pages/SignIn';
5 | import SignUp from '@pages/Signup';
6 | import Kyc from '@pages/Kyc';
7 | import Ramp from '@pages/Ramp';
8 | import ProtectedRamp from '@pages/ProtectedRamp';
9 |
10 | import TransactionHistory from '@pages/Transactions/History';
11 | import TransactionKycDetail from '@pages/Transactions/KycDetail';
12 | import TransactionDetail from '@pages/Transactions/Detail';
13 | import Terms from '@pages/Terms';
14 | import PrivacyNotice from '@pages/PrivacyNotice';
15 | import Faq from '@pages/FAQ';
16 |
17 | import ExposedWrapper from './ExposedWrapper';
18 | import ProtectedWrapper from './ProtectedWrapper';
19 |
20 | const router = createBrowserRouter(
21 | createRoutesFromElements(
22 | <>
23 | }>
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 |
31 | }>
32 | } />
33 | } />
34 | } />
35 | } />
36 | } />
37 | } />
38 |
39 | >,
40 | ),
41 | );
42 |
43 | export default router;
44 |
--------------------------------------------------------------------------------
/src/tapfiliate.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unused-vars */
2 | declare module '@tapfiliate/tapfiliate-js' {
3 | export function init(accountId: string) {}
4 |
5 | export function detect() {}
6 |
7 | export function conversion(
8 | id: string | number,
9 | amount: string | number,
10 | options?: Record,
11 | ) {}
12 | }
13 |
--------------------------------------------------------------------------------
/src/translations/en/faq.ts:
--------------------------------------------------------------------------------
1 | import { bandoAcademy } from '@config/constants/links';
2 |
3 | export default {
4 | title: 'Frequently Asked Questions',
5 | q1: {
6 | title: 'What is Bando?',
7 | info: 'Bando is a cryptocurrency platform that provides a simple and secure way for both new and experienced users to buy, sell, and manage their digital assets.',
8 | },
9 | q2: {
10 | title: 'How do I start using Bando?',
11 | info: 'To get started, create an account on our platform, complete the identity verification process (KYC), and "you\'ll" be able to begin transactions immediately, both buying and selling cryptocurrencies',
12 | },
13 | q3: {
14 | title: 'Is it safe to buy and sell on Bando?',
15 | info: 'Yes, Safety is our top priority. We use advanced security technologies to protect your transactions and maintain your privacy.',
16 | },
17 | q4: {
18 | title: 'Can I use bando on my smartphone?',
19 | info: 'Sí, nuestra plataforma es accesible y optimizada para dispositivos móviles, permitiéndote usar Bando desde cualquier dispositivo',
20 | },
21 | q5: {
22 | title: 'Does Bando custody my assets?',
23 | info: 'No, Bando does not custody your assets. The platform facilitates the buying and selling of cryptocurrencies, but you maintain direct control over your digital assets through your personal wallet.',
24 | },
25 | q6: {
26 | title: 'Where can I learn about cryptocurrencies from zero?',
27 | info: `Bando Academy is your ideal starting point! We offer a comprehensive beginner's course on cryptocurrencies at [Bando Academy](${bandoAcademy})`,
28 | info2:
29 | 'There, "you\'ll" learn all the basics, from what cryptocurrencies are to how they work and how you can start trading with them.',
30 | },
31 | q7: {
32 | title: 'What do I do if I have a problem or a question?',
33 | info: 'Our support team is available to assist you with any issues or questions. You can contact us by sending an email to our support team at correo soporte@bando.cool',
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/translations/en/footer.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | disclaimer: `© ${new Date().getFullYear()} Bando. All rights reserved.`,
3 | productTitle: 'Product',
4 | companyTitle: 'About Us',
5 | followUs: 'Follow Us',
6 | productFAQ: 'FAQ',
7 | academyLink: 'Bando Academy',
8 | statusLink: 'Status',
9 | terms: 'Terms and Conditions',
10 | privacy: 'Privacy Policy',
11 | contact: 'Contact',
12 | };
13 |
--------------------------------------------------------------------------------
/src/translations/en/index.ts:
--------------------------------------------------------------------------------
1 | import landing from './landing';
2 | import faq from './faq';
3 | import transactionDetail from './transactionDetail';
4 | import kycPoints from './kycPoints';
5 | import userMenu from './userMenu';
6 | import ramp from './ramp';
7 | import transactions from './transactions';
8 | import form from './form';
9 | import quote from './quote';
10 | import footer from './footer';
11 |
12 | export default {
13 | footer,
14 | landing,
15 | faq,
16 | ramp,
17 | transactionDetail,
18 | transactions,
19 | kycPoints,
20 | userMenu,
21 | form,
22 | quote,
23 | };
24 |
--------------------------------------------------------------------------------
/src/translations/en/kycPoints.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'The easiest and safest way to send and receive crypto to any wallet.',
3 | points: [
4 | 'Deposit with SPEI',
5 | 'Receive directly to your wallet in seconds.',
6 | 'Choose to receive ETH, USDC, USDT or more tokens',
7 | 'No gas fees',
8 | ],
9 | };
10 |
--------------------------------------------------------------------------------
/src/translations/en/landing.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | mainTitle: 'Exchange between MXN and crypto in one SPEI',
3 | subtitle:
4 | 'With Bando enter and exit the crypto world in seconds with SPEI. You can deposit or withdraw to your wallet or exchange to Polygon, Arbitrum, Optimism, and many more.',
5 | section1: {
6 | title: 'Control your cryptos and explore safely!',
7 | info: 'Bando is your gateway to crypto as it should be: your keys, your tokens, with low transaction costs and with the DeFi connection you are looking for!',
8 | },
9 | section2: {
10 | title: 'Deposit pesos, get crypto',
11 | info: 'Deposit with pesos or your local currency and obtain cryptocurrency directly to your preferred wallet or in your Bando smart account.',
12 | },
13 | section3: {
14 | title: 'Put your cryptos to work for you.',
15 | info: 'Get the returns that this industry has available today.',
16 | },
17 | section4: {
18 | title: 'Access to more than 800 cryptocurrencys',
19 | info: 'Acquire Ether, USDC, and all cryptocurrencies available in the ecosystem at the best prices in the industry',
20 | cta: 'Coming soon!',
21 | },
22 | section5: {
23 | title: 'Coming soon!',
24 | list: {
25 | point1: {
26 | title: '1. Smart self-custody account with no risk of losing access to your assets',
27 | info: 'If you lose access to your account, Bando will help you to get it back thanks to our technology, Bando NEVER has the control of your account or your assets.',
28 | },
29 | point2: {
30 | title: '2. Trade with the lowest fees.',
31 | info: 'Direct integration with layer 2 for the highest transaction speed at the lowest cost.',
32 | },
33 | point3: {
34 | title: '3. Access to the best opportunities in crypto',
35 | info: 'Direct connection with the best decentralized protocols so you can buy, sell, borrow, and put your assets to work.',
36 | },
37 | },
38 | },
39 | coins: {
40 | eth: {
41 | title: 'st ETH',
42 | amount: '3.6%',
43 | },
44 | usdm: {
45 | title: 'USDM',
46 | amount: '5%',
47 | },
48 | },
49 | video: {
50 | title: 'Using Bando is super easy',
51 | subtitle:
52 | 'Check our co-founder Lalo explaining how to seamlessly buy tokens from mexican pesos.',
53 | },
54 | };
55 |
--------------------------------------------------------------------------------
/src/translations/en/quote.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | buy: 'Buy',
3 | sell: 'Sell',
4 | title: 'Welcome to crypto.',
5 | selectNetwork: 'Select chain and token',
6 | on: 'You pay',
7 | off: 'You get',
8 | recipient: 'You get',
9 | noData: 'No tokens found.',
10 | notFoundText: `
11 | Reasons for that could be: low liquidity, amount selected is too low, gas costs are too high or there are no routes for the selected combination.
12 | `,
13 | notFoundTitle: 'No routes available',
14 | quoteButton: "Let's Start",
15 | };
16 |
--------------------------------------------------------------------------------
/src/translations/en/ramp.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | address: 'Your wallet address',
3 | lastName: 'Last Name',
4 | name: 'Names',
5 | clabe: 'Interbank code',
6 | confirm: 'Confirm',
7 | confirmTitle: 'Confirm your transaction',
8 | inProgressTitle: 'Transaction in Progress',
9 | errors: {
10 | recipient: 'This account has been rejected by Bando. Try with another account.',
11 | forbidden:
12 | 'Bando is in private beta. To become one of our first users, send an email to soporte@bando.cool',
13 | txn: 'There was an error processing your transaction. Please try again',
14 | limit: `
15 | You have reached your monthly limit!
16 |
17 | How do I increase my limit at Bando?
18 |
19 | With the initial level, the purchase limit is up to $500 USD per month.
20 | To increase your limit to $9,999 USD per month you need to:
21 |
22 |
23 | Open Bando's support chat and request an increase.
24 | Send a photo of your INE (front and back) or passport.
25 | Proof of address
26 | The address of your wallet from which you will send assets to Bando
27 |
28 | Once your documents are submitted, verification can take up to 24 hours.
29 | `,
30 | accountNames: 'The account name does not match your registered name.',
31 | },
32 | };
33 |
--------------------------------------------------------------------------------
/src/translations/en/transactionDetail.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | address: 'Address:',
3 | bank: 'Bank:',
4 | name: 'Name:',
5 | clabe: 'CLABE:',
6 | concepto: 'Concepto:',
7 | footer: {
8 | newTransaction: 'Make another transtacion',
9 | academy: 'Learn crypto with Bando',
10 | },
11 | speiAlert: 'Make sure the "concepto" field is exactly as shown below. 👇',
12 | speiAlertTitle: 'Before you transfer:',
13 | chain: 'Chain:',
14 | };
15 |
--------------------------------------------------------------------------------
/src/translations/en/transactions.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | history: 'Transaction History',
3 | table: {
4 | deposit: 'Deposit',
5 | withdraw: 'Withdraw',
6 | rate: 'Rate',
7 | fee: 'Fee',
8 | noFee: 'N/A',
9 | address: 'Destination account',
10 | copied: 'Copied. 🫡',
11 | reference: 'Payment Reference',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/translations/en/userMenu.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | signin: 'Sign In',
3 | signup: 'Get Started',
4 | joinTg: 'Join Us',
5 | signout: 'Sign Out',
6 | viewTxnHistory: 'My Transactions',
7 | limitUsage: {
8 | title: 'Current usage',
9 | total: '${{usage}} out of ${{limit}} USD ',
10 | revalidateLink: 'Verify your identity',
11 | pendingMessage: 'Identity verification pending',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/translations/es/faq.ts:
--------------------------------------------------------------------------------
1 | import { bandoAcademy } from '@config/constants/links';
2 |
3 | export default {
4 | title: 'Preguntas Frecuentes',
5 | q1: {
6 | title: '¿Qué es Bando?',
7 | info: 'Bando es una plataforma de criptomonedas que proporciona una manera sencilla y segura para que usuarios nuevos y experimentados compren, vendan y gestionen sus activos digitales.',
8 | },
9 | q2: {
10 | title: '¿Cómo empiezo a usar Bando?',
11 | info: 'Para comenzar, crea una cuenta en nuestra plataforma, completa el proceso de verificación de identidad (KYC), y podrás empezar a realizar transacciones de inmediato, tanto compra como venta de criptomonedas.',
12 | },
13 | q3: {
14 | title: '¿Es seguro comprar y vender en Bando?',
15 | info: 'Sí, la seguridad es nuestra máxima prioridad. Utilizamos tecnologías avanzadas de seguridad para proteger tus transacciones y mantener tu privacidad.',
16 | },
17 | q4: {
18 | title: '¿Puedo usar Bando en mi celular?',
19 | info: 'Sí, nuestra plataforma es accesible y optimizada para dispositivos móviles, permitiéndote usar Bando desde cualquier dispositivo',
20 | },
21 | q5: {
22 | title: '¿Bando custodia mis activos?',
23 | info: 'No, Bando no custodia tus activos. La plataforma facilita la compra y venta de criptomonedas, pero eres tú quien mantiene el control directo sobre tus activos digitales a través de tu cartera personal',
24 | },
25 | q6: {
26 | title: '¿Dónde puedo aprender sobre criptomonedas desde cero?',
27 | info: `¡Bando Academy es tu punto de partida ideal! Ofrecemos un curso completo para principiantes sobre criptomonedas en [Bando Academy](${bandoAcademy})`,
28 | info2:
29 | 'Allí, aprenderás todo lo básico, desde qué son las criptomonedas hasta cómo funcionan y cómo puedes comenzar a operar con ellas.',
30 | },
31 | q7: {
32 | title: '¿Qué hago si tengo un problema o una pregunta?',
33 | info: 'Nuestro equipo de soporte está disponible para ayudarte con cualquier problema o duda. Puedes contactarnos enviando un correo electrónico a nuestro equipo de soporte al correo soporte@bando.cool',
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/translations/es/footer.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | disclaimer: `© ${new Date().getFullYear()} Bando. Todos los derechos reservados.`,
3 | productTitle: 'Producto',
4 | companyTitle: 'Nosotros',
5 | followUs: 'Síguenos',
6 | productFAQ: 'Preguntas Frecuentes',
7 | academyLink: 'Bando Academy',
8 | statusLink: 'Status',
9 | terms: 'Términos y Condiciones',
10 | privacy: 'Política de Privacidad',
11 | contact: 'Contacto',
12 | };
13 |
--------------------------------------------------------------------------------
/src/translations/es/index.ts:
--------------------------------------------------------------------------------
1 | import landing from './landing';
2 | import faq from './faq';
3 | import transactionDetail from './transactionDetail';
4 | import kycPoints from './kycPoints';
5 | import userMenu from './userMenu';
6 | import ramp from './ramp';
7 | import transactions from './transactions';
8 | import form from './form';
9 | import quote from './quote';
10 | import footer from './footer';
11 |
12 | export default {
13 | footer,
14 | landing,
15 | faq,
16 | ramp,
17 | transactionDetail,
18 | transactions,
19 | kycPoints,
20 | userMenu,
21 | quote,
22 | form,
23 | };
24 |
--------------------------------------------------------------------------------
/src/translations/es/kycPoints.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'La forma más fácil y segura de enviar y recibir cripto a cualquier wallet.',
3 | points: [
4 | 'Deposita con SPEI',
5 | 'Recibe directo a tu wallet en segundos',
6 | 'Elije recibir ETH, USDC, USDT o más tokens',
7 | 'Sin gas fees',
8 | ],
9 | };
10 |
--------------------------------------------------------------------------------
/src/translations/es/landing.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | mainTitle: 'Intercambia entre pesos y cripto en una transferencia bancaria',
3 | subtitle:
4 | 'Con Bando entra y sal del mundo cripto en segundos. Puedes depositar o retirar a tu wallet o exchange en la red de Polygon y Arbitrum, Optimism, Scroll y más.',
5 | section1: {
6 | title: 'Controla tus criptos y explora con seguridad',
7 | info: 'Bando es tu puerta a cripto como debe ser: tus llaves, tus tokens, con bajos costos de transacción y con la conexión a DeFi que buscas.',
8 | },
9 | section2: {
10 | title: 'Deposita con pesos, obtén cripto',
11 | info: 'Deposita con pesos mexicanos, y obtén criptomonedas directamente en tu wallet preferida.',
12 | },
13 | section3: {
14 | title: 'Pon a trabajar tus criptos por ti',
15 | info: 'Obtén los rendimientos que esta industria tiene disponibles',
16 | },
17 | section4: {
18 | title: 'Accede a más de 800 criptomonedas',
19 | info: 'Adquiere Ether, USDC y todas las criptos disponibles en el ecosistema con los mejores precios de la industria',
20 | cta: '¡Muy Pronto!',
21 | },
22 | section5: {
23 | title: 'Muy Pronto',
24 | list: {
25 | point1: {
26 | title: '1. Cuenta inteligente de custodia propia sin riesgo a perder acceso a tus activos',
27 | info: 'Si pierdes acceso a tu cuenta, Bando te ayudará a obtenerlo de nuevo gracias a nuestra tecnología, Bando NUNCA tiene control de tu cuenta o de tus activos',
28 | },
29 | point2: {
30 | title: '2. Transacciona con las comisiones más baratas',
31 | info: 'Integración directa con capas 2 para que tengas la más alta velocidad en tus transacciones, al menor costo',
32 | },
33 | point3: {
34 | title: '3. Accede a las mejores oportunidades en cripto',
35 | info: 'Conexión directa con los mejores protocolos descentralizados para que compres, vendas, pidas préstamos y pongas a tus activos a trabajar',
36 | },
37 | },
38 | },
39 | coins: {
40 | eth: {
41 | title: 'st ETH',
42 | amount: '3.6%',
43 | },
44 | usdm: {
45 | title: 'USDM',
46 | amount: '5%',
47 | },
48 | },
49 | video: {
50 | title: 'Usar Bando es muy fácil',
51 | subtitle: 'Mira a nuestro co-founder Lalo explicando como funciona.',
52 | },
53 | };
54 |
--------------------------------------------------------------------------------
/src/translations/es/quote.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | buy: 'Compra',
3 | sell: 'Vende',
4 | title: 'Entra al mundo cripto',
5 | selectNetwork: 'Selecciona una red y token',
6 | on: 'Envías',
7 | off: 'Recibes',
8 | recipient: 'Recibes',
9 | noData: 'No se encontraron tokens',
10 | notFoundText: `
11 | Esto es temporal.
12 | Las razones más comunes son: baja liquidez, el monto es muy bajo, los costos de la red son muy altos o el token no está disponible en la red seleccionada.
13 | `,
14 | notFoundTitle: 'No se encontraron rutas',
15 | quoteButton: 'Comenzar',
16 | };
17 |
--------------------------------------------------------------------------------
/src/translations/es/ramp.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | address: 'Dirección de tu billetera',
3 | confirmTitle: 'Confirma',
4 | inProgressTitle: 'Transacción en proceso',
5 | lastName: 'Apellidos',
6 | name: 'Nombres',
7 | clabe: 'Clabe',
8 | confirm: 'Confirmar',
9 | errors: {
10 | recipient: 'Esta cuenta ha sido rechazada por Bando. Intenta con otra.',
11 | forbidden:
12 | 'Bando está en beta privado. Para poder ser de nuestros primeros usuarios envía un correo a soporte@bando.cool',
13 | txn: 'Hubo un error al procesar tu transacción. Por favor intenta de nuevo.',
14 | limit: `
15 | ¡Has alcanzado tu límite mensual!
16 | ¿Cómo hago para aumentar mi límite en Bando?
17 |
18 | Con el nivel inicial, el límite de compra es de hasta $500 USD mensuales.
19 | Para aumentar tu límite hasta $9,999 USD al mes necesitas:
20 |
21 |
22 | Abre el chat de soporte de bando y solicita un aumento.
23 | Envía foto de tu INE (Frente y vuelta) o pasaporte
24 | Envía tu Comprobante de Domicilio
25 | Envía la dirección de tu billetera con la enviarás activos a Bando
26 |
27 | Una vez con tus documentos, la verificación puede tardar hasta 24 horas.
28 | `,
29 | accountNames: 'El nombre de la cuenta no coincide con tu nombre registrado.',
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/src/translations/es/transactionDetail.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | address: 'Dirección:',
3 | bank: 'Banco:',
4 | name: 'Nombre:',
5 | clabe: 'CLABE:',
6 | concepto: 'Concepto:',
7 | footer: {
8 | newTransaction: 'Haz otra transacción',
9 | academy: 'Aprende cripto con Bando',
10 | },
11 | speiAlert: 'Asegurate de que el campo "concepto" sea exactamente como se muestra aquí debajo 👇',
12 | speiAlertTitle: 'Antes de transferir:',
13 | chain: 'Red:',
14 | };
15 |
--------------------------------------------------------------------------------
/src/translations/es/transactions.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | history: 'Historial de Transacciones',
3 | table: {
4 | deposit: 'Deposito',
5 | withdraw: 'Retiro',
6 | rate: 'Tasa',
7 | fee: 'Comisión',
8 | noFee: 'N/A',
9 | address: 'Cuenta Destino',
10 | copied: 'Copiado. 🫡',
11 | reference: 'Concepto',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/translations/es/userMenu.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | signin: 'Inicia Sesión',
3 | signup: 'Empieza Ya',
4 | joinTg: 'Únete',
5 | signout: 'Cerrar Sesión',
6 | viewTxnHistory: 'Mis Transacciones',
7 | limitUsage: {
8 | title: 'Uso actual',
9 | total: '${{usage}} de ${{limit}} USD ',
10 | revalidateLink: 'Verifica tu identidad',
11 | pendingMessage: 'Verificación de identidad pendiente',
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/src/translations/index.test.tsx:
--------------------------------------------------------------------------------
1 | import i18n from '.';
2 |
3 | describe('i18n', () => {
4 | it('Is i18n instance', () => {
5 | expect(i18n).toBeObject();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/translations/index.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 | import es from './es';
4 | import en from './en';
5 |
6 | declare module 'i18next' {
7 | interface CustomTypeOptions {
8 | resources: typeof es | typeof en;
9 | }
10 | }
11 |
12 | const resources = {
13 | es,
14 | en,
15 | };
16 |
17 | i18n.use(initReactI18next).init({
18 | resources,
19 | lng: 'en',
20 | interpolation: {
21 | escapeValue: false,
22 | },
23 | returnObjects: true,
24 | react: {
25 | transSupportBasicHtmlNodes: true,
26 | transKeepBasicHtmlNodesFor: ['br', 'strong', 'i'],
27 | bindI18n: 'languageChanged loaded',
28 | bindI18nStore: 'added removed',
29 | nsMode: 'default',
30 | },
31 | });
32 |
33 | i18n.services.formatter?.add('capitalize', (value) => {
34 | return value.charAt(0).toUpperCase() + value.slice(1);
35 | });
36 |
37 | export default i18n;
38 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 | "esModuleInterop": false,
10 | "allowSyntheticDefaultImports": true,
11 | "types": ["@testing-library/jest-dom"],
12 |
13 | /* Bundler mode */
14 | "allowJs": true,
15 | "moduleResolution": "bundler",
16 | "allowImportingTsExtensions": true,
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react-jsx",
21 |
22 | /* Linting */
23 | "strict": true,
24 | "forceConsistentCasingInFileNames": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 | "noFallthroughCasesInSwitch": true,
28 | "paths": {
29 | "@config/*": [
30 | "config/*"
31 | ],
32 | "@pages/*": [
33 | "pages/*"
34 | ],
35 | "@layouts/*": [
36 | "layouts/*"
37 | ],
38 | "@components/*": [
39 | "components/*"
40 | ],
41 | "@translations/*": [
42 | "translations/*"
43 | ],
44 | "@assets/*": [
45 | "assets/*"
46 | ],
47 | "@styles/*": [
48 | "styles/*"
49 | ],
50 | "@hooks/*": [
51 | "hooks/*"
52 | ],
53 | "@store/*": [
54 | "store/*"
55 | ],
56 | "@helpers/*": [
57 | "helpers/*"
58 | ]
59 | }
60 | },
61 | "include": ["src"],
62 | "references": [{ "path": "./tsconfig.node.json" }]
63 | }
64 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | {
4 | "source": "/(.*)",
5 | "destination": "/"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, loadEnv } from 'vite';
2 | import { createHtmlPlugin } from 'vite-plugin-html';
3 |
4 | import path from 'path';
5 | import tsconfigPaths from 'vite-tsconfig-paths';
6 | import env from 'vite-plugin-environment';
7 | import check from 'vite-plugin-checker';
8 | import react from '@vitejs/plugin-react-swc';
9 |
10 | export default ({ mode }) => {
11 | const envVars = loadEnv(mode, process.cwd());
12 | return defineConfig({
13 | resolve: {
14 | alias: {
15 | '@config': path.resolve(__dirname, 'src/config'),
16 | '@pages': path.resolve(__dirname, 'src/pages'),
17 | '@layouts': path.resolve(__dirname, 'src/layouts'),
18 | '@components': path.resolve(__dirname, 'src/components'),
19 | '@translations': path.resolve(__dirname, 'src/translations'),
20 | '@assets': path.resolve(__dirname, 'src/assets'),
21 | '@styles': path.resolve(__dirname, 'src/styles'),
22 | '@hooks': path.resolve(__dirname, 'src/hooks'),
23 | '@store': path.resolve(__dirname, 'src/store'),
24 | '@helpers': path.resolve(__dirname, 'src/helpers'),
25 | },
26 | },
27 | plugins: [
28 | react(),
29 | tsconfigPaths(),
30 | check({ typescript: true }),
31 | env('all'),
32 | createHtmlPlugin({
33 | viteNext: true,
34 | minify: true,
35 | inject: {
36 | data: {
37 | title: envVars.VITE_APP_TITLE,
38 | description: envVars.VITE_APP_DESCRIPTION,
39 | heapId: envVars.VITE_HEAP_ID,
40 | googleMapsApiKey: envVars.VITE_GOOGLE_MAPS_API_KEY,
41 | },
42 | },
43 | }),
44 | ],
45 | build: {
46 | sourcemap: true,
47 | },
48 | optimizeDeps: {
49 | // This is a temporary fix for the Grid2 issue
50 | // https://github.com/mui/material-ui/issues/32727#issuecomment-1697253782
51 | include: [
52 | '@mui/material/Unstable_Grid2',
53 | '@emotion/react',
54 | '@emotion/styled',
55 | '@mui/material/Tooltip',
56 | ],
57 | },
58 | });
59 | };
60 |
--------------------------------------------------------------------------------