Thrifty Wallet UI kit - Documentation

Thriftywallet is a ReactJs UI kit for Crypto Wallet ( Cryptocurrency), rewards points, and FIAT Currency. It can be used for multiple purposes.

It is built with a very professional color and font theme. It has neat & clean code with good documentation thus making it easy to customize. Thriftywallet supports light and dark themes, responsive for all screen sizes. Thriftywallet is a PWA Application that can be installed in the mobile or desktop and can be used as a native app.

It has 25+ Uniques screens & widgets, Amazing animations, PWA enabled, Light & Dark Theme supported. Using this Theme, one can create a Custodial Wallet( Delegated wallet) for Cryptocurrency, noncustodial wallet for Crypto coin, Wallet for Reward points(loyalty) , wallet for FIAT currency, and closed wallet.


  • Version: 1.5
  • Author: Thriftysoft
  • Created: 01 March, 2022
  • Updated: 21st July, 2023

Thank you for purchasing our theme. If you have any questions that are beyond the scope of this help file, please feel free to email us at admin@thriftysoft.tech. You can also contact us via Skype Thanks so much!


Features

This project is Build with React, a JavaScript frameworks for building user interfaces. Besides, Material UI was used as a UI component library. These are the main tools that are used in this project and the other tools and libraries are listed below.


Main Tools

Here is the list of all dependancies
  
"dependencies": {
  "@emotion/react": "^11.7.1",
  "@emotion/styled": "^11.6.0",
  "@mui/icons-material": "^5.3.1",
  "@mui/lab": "^5.0.0-alpha.67",
  "@mui/material": "^5.3.1",
  "@soywod/pin-field": "^0.2.2",
  "@testing-library/jest-dom": "^5.14.1",
  "@testing-library/react": "^12.0.0",
  "@testing-library/user-event": "^13.2.1",
  "add": "^2.0.6",
  "axios": "^0.25.0",
  "cra-template-pwa": "^1.0.3",
  "date-fns": "^2.28.0",
  "firebase": "^9.6.5",
  "react": "^17.0.2",
  "react-confetti": "^6.0.1",
  "react-copy-to-clipboard": "^5.0.4",
  "react-dom": "^17.0.2",
  "react-icons": "^4.3.1",
  "react-pin-field": "^2.0.0-beta.3",
  "react-router-dom": "^6.2.1",
  "react-scripts": "5.0.0",
  "react-swipeable-views": "^0.14.0",
  "react-use": "^17.3.2",
  "react-webcam": "^6.0.1",
  "web-vitals": "^2.1.0",
  "yarn": "^1.22.17"
},

            
          

Core Features

  • 25 + Unique Screens & Widgets
  • Clean, neat, and well-documneted source code
  • Reusable Code Components
  • User-Friendly Interface
  • Fully Responsive, Compatible with All Screen Size
  • Major Browser Compatibility
  • For multiple use cases ( Crypto Wallet, loyalty point wallet & Fiat Wallet)
  • Built with React Js 17.0.2
  • Dark and Light Theme
  • PWA Enabled
  • Clean Animation
  • Support skeleton Screens
  • Easy to customize colors
  • Easy to customize font
  • Material UI & CSS Lib
  • Google Font Used
  • Icon library - Material Icons

Getting Started

It is a complete React Project, so we assume that you have the latest version of Node.js pre-installed. The Node Package Manager or NPM came with the Node.js bundle so you don't need to install it separately.

Our recommendation is to use the YARN package manager.

                
npm install --global yarn
                
              

Then check the version with:

                
yarn --version
                
              

Installation

To complete the installation please follow the steps below:

  • First, change directory to the project folder by running:

    
    cd thrifty-wallet-react-app
                  

  • Then you have to install all the dependacies that came with the project. We recommend to use YARN, as there exist a .lock file.

    TL;DR - it will make your life easier 😎

    
    yarn # or npm install
                  
  • After installing all required dependancies, now we are good to start our project by running:

    
    yarn start # or npm start
                  
  • That's it. Now you can view the entire project on your local server.


How to use

So, the basic usage of the application is really simple. We divided this section with:

The initial index.html and other publicly seen assets like dummy data can be found in the /public directory.


Main App.js File

The App.js file is the root of all the Pages and Components that will described below.

All the pages and components that were used in the main App.js file are lazy imported with its correspondent loader and they were wrapped with three Contexts Provider which are

  • AuthProvider - For providing all the user centric authentication information all over the app
  • ColoModeContext Provider - This provider mainly used for the changing the theme mode between dark and light. We will talk more about it in theme section
  • ThemeProvider - This provider is the main provider for distributing all the color and theme information in the entire app

All the pages on this app except the authentication pages are being used with a private/protected route. So, we have to create an account first in order to have the full access in the app. It should be rememberred that, as this project is nothing but a UI kit only, we used minimal authentication with firebase, so it gives the feelings of real flow of an application. Nothing more, nothing less.

Folder Structure

The project contains 15 reusable and independent components with 10 sub-components and 12 Pages. Within these pages, approximately70 other components are presented. These components are mainly dependent on their correspondent pages.

Here is the full src folders and files tree view


│   App.css
│   App.js
│   App.test.js
│   index.css
│   index.js
│   logo.svg
│   reportWebVitals.js
│   service-worker.js
│   serviceWorkerRegistration.js
│   setupTests.js
│
├───assets
│   │   bitCoinIcon.svg
│   │   bronzeBadgeIcon.svg
│   │   buyCryptoCurrencyCardImg.svg
│   │   buyCryptoCurrencyCardImgLight.svg
│   │   CardanoVectorLogo.svg
│   │   contactImageDark.svg
│   │   copyIconDark.svg
│   │   diamondBadgeIcon.svg
│   │   dominosPizza.svg
│   │   EthereumVectorLogo.svg
│   │   facebookFlatColorIcon.svg
│   │   goldBadgeIcon.svg
│   │   googleFlatColorIcon.svg
│   │   initialLoginRegistrationImage.png
│   │   kycImage.png
│   │   LiteCoinVectorLogo.svg
│   │   mainLogo.svg
│   │   moonIcon.svg
│   │   onboardingBGImage.svg
│   │   onboardingLogo.svg
│   │   onboardingStepFiveImage.svg
│   │   onboardingStepFourImage.svg
│   │   onboardingStepOneImage.svg
│   │   onboardingStepThreeImage.svg
│   │   onboardingStepTwoImage.svg
│   │   profileAvatar.svg
│   │   progressCheckIcon.svg
│   │   progressUnCheckIcon.svg
│   │   qrCode.svg
│   │   qrIconDark.svg
│   │   qrImageScanner.svg
│   │   receiveCopyIcon.svg
│   │   receiveQRIcon.svg
│   │   reusableCardLogo.svg
│   │   sendIconMobile.svg
│   │   signUpImageDark.svg
│   │   silverBadgeIcon.svg
│   │   stepperIconEmpty.svg
│   │   stepperIconPending.svg
│   │   sunIcon.svg
│   │   tableDetailsIcon.svg
│   │   totalFundValueImage.svg
│   │   totalFundValueImageLight.svg
│   │   twitterFlatColorIcon.svg
│   │   twoFAPinBG.svg
│   │   twoFAPinBGLight.svg
│   │
│   └───authenticationImages
│           accountSetupStep.svg
│           BankStepDark.svg
│           BankStepLight.svg
│           ForgotPassDark.svg
│           ForgotPassLight.svg
│           kycStep.svg
│           kycStepLight.svg
│           loginDark.svg
│           loginLight.svg
│           otpDark.svg
│           otpLight.svg
│           ResetPassDark.svg
│           ResetPassLight.svg
│           signUpDark.svg
│           SignUpLight.svg
│
├───components
│   ├───AuthProgress
│   │       AuthProgress.js
│   │       AuthProgress.module.css
│   │
│   ├───CustomStepper
│   │       CustomStepper.js
│   │
│   ├───CustomSwitch
│   │       CustomSwitch.js
│   │
│   ├───CustomToolTip
│   │       CustomToolTip.js
│   │
│   ├───DatePickerTextField
│   │       DatePickerTextField.js
│   │
│   ├───GrowwBar
│   │       GrowwBar.js
│   │
│   ├───InstallationModal
│   │       InstallationModal.js
│   │
│   ├───Layout
│   │       CustomAppbar.js
│   │       CustomDrawer.js
│   │       Layout.js
│   │       MobileNavDrawer.js
│   │       MobileNavDrawerPermanent.js
│   │       SettingsMenu.js
│   │
│   ├───LazyImageComponent
│   │       LazyImageComponent.js
│   │
│   ├───ProgressLoader
│   │       ComponentLoader.js
│   │       CustomProgress.js
│   │       LoaderStyle.module.css
│   │       ProgressLoader.js
│   │
│   ├───Routes
│   │       CoinDetailsRoutes.js
│   │       LayoutRoutes.js
│   │
│   ├───Skeletons
│   │       ComponentSkeletons.js
│   │
│   ├───StyledTable
│   │       StyledTable.js
│   │
│   ├───TableDetailsModal
│   │       TableDetailsModal.js
│   │       TableDetailsModal.module.css
│   │
│   └───TabPanel
│           TabPanel.js
│
├───contexts
│       AuthProvider.js
│
├───Firebase
│       firebase.config.js
│       firebase.init.js
│
├───hooks
│       useAuth.js
│       useFirebase.js
│
├───Pages
│   ├───AccountSetup
│   │   │   AccountSetup.js
│   │   │   AccountSetup.module.css
│   │   │
│   │   ├───AccountSetupStep
│   │   │       AccountSetupStep.js
│   │   │       AccountSetupStep.module.css
│   │   │
│   │   ├───BankStep
│   │   │       BankStep.js
│   │   │       BankStep.module.css
│   │   │
│   │   └───KYCStep
│   │           KYCStep.js
│   │           KYCStep.module.css
│   │
│   ├───CoinDetails
│   │   │   BalanceArea.js
│   │   │   CoinDetails.js
│   │   │   CoinDetailsMobile.js
│   │   │   CoinDetailsStyle.module.css
│   │   │   CoinFilterArea.js
│   │   │   CoinTransactionTable.js
│   │   │   CoinTransactionTableMobile.js
│   │   │   QRCodeScannerModal.js
│   │   │   SendConfirmationModal.js
│   │   │
│   │   └───CoinDetailsChildrenMobile
│   │       │   CoinDetailsChildrenMobile.module.css
│   │       │   ReceiveBoxMobile.js
│   │       │   SendBoxMobile.js
│   │       │   TransactionBoxMobile.js
│   │       │
│   │       └───TableCoinDetails
│   ├───CryptoWallet
│   │   │   CryptoWalletInterface.js
│   │   │
│   │   ├───CryptoWalletTopCards
│   │   │       CryptoWalletTopCards.js
│   │   │       CryptoWalletTopCards.module.css
│   │   │       CryptoWalletTopCardsMobile.js
│   │   │
│   │   ├───DataArea
│   │   │       TableArea.js
│   │   │       TableArea.module.css
│   │   │       TableAreaMobile.js
│   │   │       TransactionArea.js
│   │   │       TransactionAreaMobile.js
│   │   │
│   │   └───FundsAndTransferArea
│   │           FundsAndTransferArea.js
│   │           FundsAndTransferArea.module.css
│   │           FundsAndTransferAreaMobile.js
│   │
│   ├───FiatWallet
│   │   │   FiatWalletInterface.js
│   │   │
│   │   ├───CurrencyBalanceCard
│   │   │       CurrencyBalanceCard.js
│   │   │       CurrencyBalanceCard.module.css
│   │   │
│   │   ├───DepositFunds
│   │   │       DepositFunds.js
│   │   │       DepositFunds.module.css
│   │   │
│   │   ├───PaymentAuthorizationModal
│   │   │       PaymentAuthorizationModal.js
│   │   │       PaymentAuthorizationModal.module.css
│   │   │
│   │   ├───TransactionDetailsArea
│   │   │       FiatTableDetailsModal.js
│   │   │       TransactionDetailsArea.js
│   │   │       TransactionDetailsArea.module.css
│   │   │       TransactionDetailsAreaMobile.js
│   │   │
│   │   ├───TransactionDrawer
│   │   │       TransactionDrawer.js
│   │   │       TransactionDrawerMobile.js
│   │   │
│   │   └───WithdrawFunds
│   │           WithdrawFunds.js
│   │
│   ├───Login
│   │   │   Login.js
│   │   │   Login.module.css
│   │   │
│   │   ├───ForgotPass
│   │   │       ForgotPass.js
│   │   │       ForgotPass.module.css
│   │   │
│   │   ├───OTPVerification
│   │   │       OTPVerification.js
│   │   │       OTPVerification.module.css
│   │   │
│   │   ├───ResetPass
│   │   │       ResetPass.js
│   │   │       ResetPass.module.css
│   │   │
│   │   └───SignInInterface
│   │           SignInInterface.js
│   │           SignInInterface.module.css
│   │
│   ├───LoyaltyWallet
│   │   │   LoyaltyWalletInterface.js
│   │   │   LoyaltyWalletInterface.module.css
│   │   │
│   │   ├───ClaimRewardModal
│   │   │       ClaimRewardModal.js
│   │   │
│   │   ├───RewardPathArea
│   │   │       RewardPathArea.js
│   │   │       RewardPathArea.module.css
│   │   │       RewardPathAreaMobile.js
│   │   │
│   │   ├───RewardTabArea
│   │   │       AvailableRewards.js
│   │   │       AvailableRewardsMobile.js
│   │   │       MyRewards.js
│   │   │       MyRewardsMobile.js
│   │   │       RewardTabArea.js
│   │   │       RewardTabArea.module.css
│   │   │       Transaction.js
│   │   │
│   │   ├───TimeLineArea
│   │   │       TimeLineArea.js
│   │   │       TimeLineArea.module.css
│   │   │
│   │   └───TopCardArea
│   │           TopCardArea.js
│   │           TopCardArea.module.css
│   │           TopCardAreaMobile.js
│   │
│   ├───Onboarding
│   │       OnboardingPage.js
│   │       OnboardingPage.module.css
│   │       StepComponent.js
│   │
│   ├───ProfilePage
│   │   │   ProfileInteface.js
│   │   │
│   │   └───Account
│   │       │   Account.js
│   │       │   Account.module.css
│   │       │
│   │       ├───BankInfo
│   │       │   │   BankInfo.js
│   │       │   │
│   │       │   └───BankInfoModal
│   │       │           AddBankModal.js
│   │       │           AddBankModalMobile.js
│   │       │           BankInfoModal.js
│   │       │           BankInfoModalMobile.js
│   │       │
│   │       ├───KYCInfo
│   │       │   │   KYCInfo.js
│   │       │   │
│   │       │   └───KYCInfoModal
│   │       │           KYCInfoModal.js
│   │       │           KYCInfoModalMobile.js
│   │       │
│   │       ├───OtherOptions
│   │       │       ChangePasswordModal.js
│   │       │       ChangePasswordModalMobile.js
│   │       │       DeleteAccountModal.js
│   │       │       DeleteAccountModalMobile.js
│   │       │       OtherOptions.js
│   │       │
│   │       └───ProfileInfo
│   │           │   ProfileInfo.js
│   │           │
│   │           └───ProfileInfoModal
│   │                   ProfileInfoModal.js
│   │                   ProfileInfoModalMobile.js
│   │
│   ├───Registration
│   │   │   Registration.js
│   │   │   Registration.module.css
│   │   │
│   │   ├───SignUpInterface
│   │   │       SignUpInterface.js
│   │   │       SignUpInterface.module.css
│   │   │
│   │   └───TwoFAPage
│   │       │   TwoFAPage.js
│   │       │   TwoFAPin.js
│   │       │   TwoFAPin.module.css
│   │       │   TwoFAPopUp.module.css
│   │       │
│   │       └───TwoFAPinModal
│   │               TwoFAPinModal.js
│   │               TwoFAPinModal.module.css
│   │
│   ├───StaticPages
│   │       About.js
│   │       FAQ.js
│   │       PrivacyPolicy.js
│   │       StaticPageInterface.js
│   │       StaticPagesStyles.module.css
│   │       TermsAndCondition.js
│   │
│   ├───TopUpPage
│   │   │   TopUpPage.js
│   │   │   TopUpPage.module.css
│   │   │
│   │   ├───ProviderSelect
│   │   │       ProviderSelect.js
│   │   │
│   │   ├───TopUpAuthorization
│   │   │       TopUpAuthorization.js
│   │   │
│   │   └───TopUpCard
│   │           TopUpCard.js
│   │           TopUpCardMobile.js
│   │
│   └───Wallets
│           Wallets.js
│
├───Private
│       PrivateRoute.js
│
├───Theme
│       CustomTheme.js
│
└───Utilities
        LightUIButtons.js
              

Lets's take a short look how we organized all the things there

  • All the styles of the components and pages can be found within their dedicated folder. Let's say, you are customizing a modal. Then you should be able to see a file like modal.module.css is presented on that folder.
  • The Components folder only contains the independent and reusable components
  • The Pages folder only contains the pages and their correspondent components
  • All the custom Contexts, hooks, Private Route, Theme, and Utilities are presented on the src/Contexts, src/hooks, src/Private, src/Theme, and src/Utilities folder.

Components

As mentioned before the project has 15 independent and components and 10 sub-components. We described the key components below.


Layout Component

The Layout component is the most important component. This component basically holds the structure of the main page. It has 5 other sub-component which were used to construct the whole Layout Component.


<>
  <Box sx={{ display: "flex" }}>
    <CssBaseline />
    <React.Suspense
      fallback={
        <Skeleton
          variant="rectangular"
          sx={{
            background: `${
              theme.palette.mode === "dark" ? "#111" : "#fff"
            }`,
          }}
          width={"100%"}
          height={80}
        />
      }
    >
      <CustomAppbar
        handleClickMenu={handleClickMenu}
        handleDrawerToggle={handleDrawerToggle}
      />
    </React.Suspense>
    <Box
      component="nav"
      sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }}
    >
      {/* Drawer for mobile */}
      <SwipeableDrawer
        anchor="left"
        variant="temporary"
        open={mobileOpen}
        onClose={handleDrawerToggle}
        onOpen={handleDrawerToggle}
        ModalProps={{
          keepMounted: true, // Better open performance on mobile.
        }}
        sx={{
          display: { xs: "block", sm: "none" },
          "& .MuiDrawer-paper": {
            boxSizing: "border-box",
            width: drawerWidth,
            backgroundColor: "background.default",
            border: "none",
          },
        }}
      >
        <React.Suspense fallback={<ComponentLoader / >}>
          <CustomDrawer handleDrawerToggle={handleDrawerToggle} />
        < /React.Suspense>
      </SwipeableDrawer>
      {/* Drawer for larger device */}
      <Drawer
        variant="permanent"
        sx={{
          display: { xs: "none", sm: "block" },
          "& .MuiDrawer-paper": {
            boxSizing: "border-box",
            width: drawerWidth,
            backgroundColor: `${
              theme.palette.mode === "dark" ? "#1b1b1b" : "#ffffff"
            }`,
            border: "none",
          },
        }}
        open
      >
        <React.Suspense fallback={<ComponentLoader />}>
          <CustomDrawer />
        </React.Suspense>
      </Drawer>
    </Box>
    {/* Children */}
    <Box
      component="main"
      sx={{
        flexGrow: 1,
        p: `${!isMobile ? 3 : 0}`,
        width: { sm: `calc(100% - ${drawerWidth}px)` },
      }}
    >
      <Toolbar />
      {children}
    </Box >
  </Box >
  <Box>
    <React.Suspense
      fallback={
        <Skeleton
          animation="wave"
          variant="circular"
          width={20}
          height={20}
          sx={{
            backgroundColor: `${
              theme.palette.mode === "dark" ? "#111" : "#f5f5f5"
            }`,
          }}
        />
      }
    >
      <SettingsMenu
        open={openMenu}
        anchorEl={anchorElMenu}
        handleClose={handleCloseMenu}
      />
    </React.Suspense>
  </Box>
</>
            

The child components are

  • CustomAppbar which is the main appbar used throughout the application
  • CustomDrawer is basically the navigation menu. This drawer is persistent in nature for the larger devices, means it is not closable. For the relatively smaller devices, this drawer becomes temporary, means it closable and swipeable
  • MobileNavDrawer this mainly an instance of the modal for the mobile devices
  • MobileNavDrawerpermanent is the drawer for in depth routing for the mobile devices
  • SettigsMenu is the menu which can be accessable by clicking the Gear icon

Skeletons Component

This components contains all the reusable skeleton screens for the components that are presented in the application.

Some Skeleton screens are beingh described below

Card Image Skeleton



const CardImageSkeleton = () => {
  return (
    <Skeleton animation="wave" variant="rectangular" width={210} height={175} />
  );
};

            

Table Skeleton


const TableSkeleton = () => {
  return (
    <React.Fragment>
      {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((fm) => (
        <Skeleton
          key={fm}
          variant="text"
          animation="wave"
          width={"100%"}
          height={80}
        />
      ))}
    </React.Fragment>
  );
};
            

Grid Card Skeleton


const GridCardSkeleton = ({
  columns,
  spacing,
  gap = 0,
  rowGap,
  columnGap,
  md,
  xs = 1,
  sm,
  xl,
}) => {
  return (
    <Grid
      container
      mt={2}
      columns={columns}
      columnGap={columnGap}
      spacing={spacing}
      gap={gap}
      rowGap={rowGap}
    >
      {[1, 2, 3, 4, 5, 6].map((index) => (
        <Grid item key={index} md={md} xl={xl} sm={sm} xs={12}>
          <Skeleton animation="wave" width={"100%"} height={250} />
        </Grid>
      ))}
    </Grid>
  );
};
            

ProgressLoader Component

This holds all the loader components that are used in the application. It contains 3 loader which is

  • ComponentLoader
  • CustomProgress
  • ProgressLoader

Component loader


<Box className={styles.loader} height={"100%"}>
  <CircularProgress color="primary" />
</Box>
            

Custom Progress


export const BorderLinearProgress = styled(LinearProgress)(({ theme }) => ({
  height: 10,
  borderRadius: 5,
  [`&.${linearProgressClasses.colorPrimary}`]: {
    backgroundColor: theme.palette.mode === "dark" ? "#777777" : "#c4c4c4",
  },
  [`& .${linearProgressClasses.bar}`]: {
    borderRadius: 5,
    backgroundColor: "#58BD7D",
  },
}));
export const BorderLinearProgressMobile = styled(LinearProgress)(
  ({ theme }) => ({
    height: 5,
    borderRadius: 5,
    [`&.${linearProgressClasses.colorPrimary}`]: {
      backgroundColor: theme.palette.mode === "dark" ? "#777777" : "#c4c4c4",
    },
    [`& .${linearProgressClasses.bar}`]: {
      borderRadius: 5,
      backgroundColor: "#58BD7D",
    },
  })
);
            

Progress Loader


<Box
  bgcolor={theme.palette.background.default}
  className={styles.loader}
  height={"100vh"}
>
  <CircularProgress color="primary" />
</Box>
              


Other Component

All other components are auxiliary in nature and very handy. Below we just described the gist

  • Auth Progress - This loader component is liable for showing the loading phase when the user authenticating
  • Custom Stepper - This component is used in account-setup page
  • Custom Switch - This is the theme switcher component
  • CustomToolTip - This tooltip is customized and being used to show the tooltip.
  • Date Picker Text Field - It is used to show the date field of the date input component
  • Groww Bar - This small component is liable for showing the grow type animations in the auth pages. It only available for mobile devices
  • Installation Modal - This Snackbar type modal is used to tell the user that the application is installable as a PWA
  • Lazy Image Component - This component only returns the img element. All the images throughout this application is used this component to dynamically load the images. It optimized for the performance
  • Routes - this actually is not a component rather it the storage for holding the Layout routes mainly and CoinDetailsRoute.
  • StyledTable - This table component is kinda like an auxiliary one and is used to show all the data in the application
  • Table Details Modal - This component is only available for mobile devices and used to show the

Pages

As we mentioned there are 12 pages in this project with 70 different dependent component with each page. Find the brief description below

We can also divide the pages into some core pages like

  • Authentication Pages
  • Account Setup Pages
  • Wallet Pages
  • Account Pages
  • Static Pages

Authentication Pages

Authentication pages are the pages where the user land first

The Authentication Page can be divided into two main category which are Login and Registration

The Login page includes

The Registration page includes

  • SignUpInterface - It is mainly the sign up box
  • TwoFAPage - It holds three sub-component regarding the two factor setup

Account Setup Page

The account setup page holds three component named


Wallet Page

The wallet page can be considered as the main landing page of the app. It holds three different wallets, which we will describe shortly.

The wallets page is mainly a common place that holds the wallets. The three wallets are:

  • Crypto Wallet
  • Fiat Wallet
  • Loyalty Wallet

Crypto Wallet


<Box>
  <Box px={3}>
    <Suspense fallback={}>
      {!isMobile ? (
        <CryptoWalletTopCards />
      ) : (
        <CryptoWalletTopCardsMobile />
      )}
    </Suspense>
  </Box>
  <Box px={3}>
    <Suspense fallback={}>
      {!isMobile ? (
        <FundsAndTransferArea />
      ) : (
        <FundsAndTransferAreaMobile />
      )}
    </Suspense>
  </Box>
</Box>
              

As we can see it holds two main components which are CryptoWalletTopCards and FundsAndTransferArea. You may notice there are dedicated component only for mobile device. It is not repeatation rather it is organized. The Crypto Wallet Top Cards holds the top level cards and the Funds And Transfer Area holds the transaction, coin details, and transaction details

Fiat Wallet


{/* Currency Card */}
<Box>
  <Box px={3}>
    <Suspense fallback={<CardSkeleton />}>
      <CurrencyBalanceCard
        deposit={handleDepositDrawer}
        withdraw={handleWithdrawDrawer}
      />
    </Suspense>
  </Box>
{/* Transaction Area */}
<Box px={3} mt={5}>
  <Suspense fallback={<TableSkeleton />}>
    {!isMobile ? (
      <TransactionDetailsArea />
    ) : (
      <TransactionDetailsAreaMobile />
    )}
  </Suspense>
</Box>
              

The Fiat Wallet Interface basically holds two main child component which are CurrencyBalanceCard and TransactionArea. The Currency Balance Card holds two drawer type child component which are withdraw and deposit And the Transaction Details Area holds the details transaction informaition of the fiat wallet

Loyalty Wallet


<Box
  sx={{ overflowX: "hidden" }}
  px={!isMobile ? 3 : 0}
  className={styles.mainBox}
>
  {showConfetti && (
    <Confetti
      opacity={0.8}
      gravity={0.1}
      onConfettiComplete={() => setShowConfetti(false)}
      tweenDuration={100}
      numberOfPieces={250}
      recycle={false}
      width={width - 50}
      height={height}
    />
  )}
  <Box className={styles.topCardArea}>
    <Suspense fallback={<CardSkeleton />}>
      {!isMobile ? (
        <TopCardArea handleConfetti={handleConfetti} />
      ) : (
        <TopCardAreaMobile handleConfetti={handleConfetti} />
      )}
    </Suspense>
  </Box>
  {!isMobile && (
    <Box className={styles.rewardInfoArea}>
      <Grid
        container
        columns={{ xs: 1, sm: 1, md: 12 }}
        spacing={{ xs: 4, sm: 2, md: 4 }}
      >
        <Grid item xs={12} sm={12} md={4}>
          <Box className={styles.rewardPathArea}>
            <Suspense fallback={<ComponentSkeleton />}>
              <RewardPathArea />
            </Suspense>
          </Box>
        </Grid>
        <Grid item xs={12} sm={12} md={8}>
          <Box className={styles.rewardTabArea}>
            <Suspense fallback={<ComponentSkeleton />}>
              <RewardTabArea />
            </Suspense>
          </Box>
        </Grid>
      </Grid>
    </Box>
  )}
  {isMobile && (
    <Box>
      <Box className={styles.rewardTabArea}>
        <Suspense fallback={<ComponentSkeleton />}>
          <RewardTabArea />
        </Suspense>
      </Box>
    </Box>
  )}
</Box>
              

Here you can see the Confetti component. This component is used to show celebration type poppers when the user click on the reward button for claiming rewards presented in the TopCardArea.

Mainly the component is consists of three main child component which are Top Card Area, Reward Path Area, Reward Tab Area

The Top Card Area is the place where the user can see their claimed reward and can claim the reward clicking on the Badges

The Reward Path Area is a component where the user can view the reward path that he was obtained.

The Reward Tab Area is a component where the user can view the coupon cards, claimed coupons, and the detailed transaction


Account Page

The account page although a child of the ProfileInterface component which represent the main instance of this route and it also represent in the main App.js file. With the standing, the account page is the page that holds mainly four child component which are:

  • Profile Info
  • KYC Info
  • Add Bank
  • Other Options

Initial Account Page


<Box>  
  <Box>
    <Suspense
      fallback={<AccountCardSkeletons width={!isMobile ? "58%" : "100%"} />}
    >
      <ProfileInfo handleClickMenu={handleClickMenu} />
    </Suspense>
  </Box>
  <Divider />
  <Box>
    <Suspense
      fallback={<AccountCardSkeletons width={!isMobile ? "58%" : "100%"} />}
    >
      <BankInfo handleClickMenu={handleClickMenu} />
    </Suspense>
  </Box>
  <Divider />
  <Box>
    <Suspense
      fallback={<AccountCardSkeletons width={!isMobile ? "58%" : "100%"} />}
    >
      <KYCInfo handleClickMenu={handleClickMenu} />
    </Suspense>
  </Box>
  <Divider />
  <Box>
    <Suspense
      fallback={<AccountCardSkeletons width={!isMobile ? "58%" : "100%"} />}
    >
      <OtherOptions handleClickMenu={handleClickMenu} />
    </Suspense>
  </Box>
</Box>
              

As we can see the first child component is ProfileInfo which holds the profile card for the user. It is responsible for changing the user information.

The next one is BankInfo. Here a user can change the detailed banking information and can also add new bank

The third one is KYCInfo. Here the user can re-submit his/her KYC form and can also know the status whether it is accepted or rejected.


Static Page

The static page holds the static page related to About, FAQ, PrivacyPolicy, and TermsAndCondition. The StaticPageInterface components holds the above mentioned pages.

The data that were displayed on those pages are coming from a separate json file. You may be wondered why we are loading the static data from the json files instead of writing them in the code! Well, this reduced the complexity and it is also organizable and it is easy to change or alter.


Other Pages

The other important pages worth mentioning are:

  • Coin Details Page - It containes 6 different sub-components and it is being used withing the Crypto Wallet Component
  • On Boarding Page - This component will show to the user only when the user is new and never created an account here before.
  • Top Up Page - It is a huge separate page but accessable from the Crypto Wallet Page. It mainly the storage for all kinds of topup and transactions

Contexts

There mainly one context for now on the context folder which is called AuthProvider. It is used to distribute the authentication data throughout the application.

Have a look


import { createContext } from "react";
import useFirebase from "../hooks/useFirebase";

export const AuthContext = createContext(null);

const AuthProvider = ({ children }) => {
  const allAuthContext = useFirebase();

  return (
    <AuthContext.Provider value={allAuthContext}>
      {children}
    </AuthContext.Provider>
  );
};

export default AuthProvider;
              

Firebase

This folder is the separate one and is fully dedicated regarding to the configurations of the application

This folder contains two file which are:

  • firebase.config.js - All the app configurations
  • firebase.init.js - Initialized the project from here

Hooks

This project has 2 custom hooks. They are:

  • useFirebase - All the firebase related hooks and function can be found here
  • useAuth - This hook is mainly a wrapper for storing all the functionality from the above mentioned useFirebase hook

useFirebase Hook

This hook has multiple functionality such as Sign In, Sign Up, Logout, showing Auth Errors. Besides, it is used to store the user information, loading state (which is kinda like a watcher, that is why you won't be logout automatically if the page reloads) and lastly showing the Authentication pin.

Here is some important functionality

Sign Up


// Sign Up
  const registerUser = (email, password, navigate) => {
    setIsLoading(true); //It will holds the loading state

    // Registering the user
    createUserWithEmailAndPassword(auth, email, password)
      .then((userCreadential) => {
        // setting auth error default false
        setAuthError("");

        // setup new user
        const newUser = { email };
        setUser(newUser);
        navigate("/account-setup"); //navigate to account setup route
      })
      .catch((err) => {
        console.log(err.message);
        setAuthError(err.message);
      })
      .finally(() => setIsLoading(false)); // this will stop showing the initial loader
  };
              

Sign In


// Sign In
const logInUser = (email, password, location, navigate) => {
    // it will hold the loading state
    setIsLoading(true);
    setShowPin(false);
    // Signing In the user
    signInWithEmailAndPassword(auth, email, password)
      .then((userCreadential) => {
        const prevDestination = location?.state?.from || "/";
        navigate(prevDestination); //It will navigate to homepage or the destination that the user came from
        setAuthError("");
        const showPinModal = setTimeout(() => {
          setShowPin(true);
        }, 5000);

        return () => clearInterval(showPinModal);
      })
      .catch((err) => {
        console.log(err.message);
        setAuthError(err.message);
      })
      .finally(() => {
        setIsLoading(false);
      }); //
  };
              

There is another important functionality which actually watch the period of subscribing and unsubscribing a user

Have a look


// Using these will hold the user even if the page is reloaded or refreshed
  useEffect(() => {
    const unsubscribed = onAuthStateChanged(auth, (user) => {
      if (user) {
        setUser(user);
      } else {
        setUser({});
      }
      setIsLoading(false);
    });
    return () => unsubscribed;
  }, [auth]);
            

We can call it as the Authentication State Changing Watcher


useAuth Hook

This small hook basically act as an auxiliary hook for holding all the hooks from the useFirebase hook and return the data for the contexts

useAuth


const useAuth = () => {
  const auth = useContext(AuthContext);
  return auth;
};   
              

Utilities

There is only one utility components which is a button component for the light UI


const LightUIButtonPrimary = styled(Button)({
  color: "#ffffff",
  border: "none",
  background: "linear-gradient(90deg, #F9E006 0%, #F8931A 100%)",
  boxShadow: "none",
  textTransform: "uppercase",
  "&:hover": {
    boxShadow: "none",
    background: "linear-gradient(90deg, #F9E006 0%, #F8931A 100%)",
  },
  "&:active": {
    boxShadow: "none",
    background: "linear-gradient(90deg, #F9E006 100%, #F8931A 0%)",
  },
  "&:focus": {
    background: "linear-gradient(90deg, #F9E006 0%, #F8931A 100%)",
  },
});
              

Private

The Private folder containes only the private route which is dependent on the Firebase and the useAuth hook.

PrivateRoute


const PrivateRoute = ({ children }) => {
  // hooks
  const { user, isLoading } = useAuth();
  const theme = useTheme();
  const location = useLocation();

  if (isLoading) {
    return (
      <Box
        bgcolor={theme.palette.background.default}
        sx={{
          height: "100vh",
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Box>
          <CircularProgress color="primary" />
        </Box>
      </Box>
    );
  }

  if (user.email) {
    return children;
  } else {
    return <Navigate to="/login/sign-in" state={{ from: location }} />
  }
};
              

If we noticed, we can see it loads the user informations and the loading state from the useAuth hook and used a location hook from the React Router for using location.

Then it checks three conditions. First of all it checks if the page is loading or not. It the isLoading becomes true then it shows an initial loader.

Note: It will always check the loading state

Notwithstanding, this component will check another condition, which is whether the email of the user is existing in the useAuth hook or not. Then it simply execute one command at a time. If the user exists, then shows the children which is the children props that we taking and showed it. If the user is not existing on the AuthenticationContext then it simply redirect the user from his/her current location to the authentication page.


Theme

The theme contains all the theme related data and a toggler for switching between the dark and light theme

Only theme


  const theme = useMemo(
    () =>
      createTheme({
        palette: {
          mode,
          ...(mode === "light"
            ? {
                // palette values for light mode
                primary: {
                  main: "#F9E006",
                },
                secondary: {
                  main: "#111111",
                },
                accent: {
                  main: "#6C63FF",
                },
                orange: {
                  main: "#FF5722",
                },
                error: {
                  main: "#FF003D",
                  dark: "#FF003F",
                },
                success: {
                  main: "#8DD070",
                  dark: "#58BD7D",
                },
                background: {
                  default: "#fdfdfd",
                  paper: "#fbfbfb",
                  surface: "#ffffff",
                  card: "#f8f8f8",
                  primary: "#F9E006",
                },
                text: {
                  primary: "#111111",
                  secondary: "#434547",
                  tertiary: "#F9E006",
                  success: "#58BD7D",
                  orange: "#FF9100",
                },
                common: {
                  black: "#111111",
                  white: "#ffffff",
                },
              }
            : {
                // palette values for dark mode
                primary: {
                  main: "#F9E006",
                },
                secondary: {
                  main: "#FFFFFF",
                },
                accent: {
                  main: "#6C63FF",
                },
                orange: {
                  main: "#FF5722",
                },
                error: {
                  main: "#FF003D",
                  dark: "#FF003F",
                },
                success: {
                  main: "#8DD070",
                  dark: "#58BD7D",
                },
                background: {
                  default: "#111111",
                  paper: "#252628",
                  surface: "#2B2B2B",
                  card: "#1B1B1B",
                  primary: "#F9E006",
                },
                text: {
                  primary: "#ffffff",
                  secondary: "#C4C4C4",
                  tertiary: "#F9E006",
                  success: "#58BD7D",
                  orange: "#FF9100",
                },
                common: {
                  black: "#111111",
                  white: "#ffffff",
                },
              }),
        },
        typography: {
          fontFamily: "'Poppins', sans-serif",
          fontWeightLight: 300,
          fontWeightRegular: 400,
          fontWeightMedium: 500,
          fontWeightBold: 700,
        },
      }),
    [mode]
  );
              

Here we mainly use the theme with the useMemo hook for optimizing the performance. Then we call createTheme function from the Material UI and send the mode as a state. So, that we can easily manipulate the theme color based on the manipulation of the mode state.

Now, let's take a look at the toggler function


  const colorMode = useMemo(
    () => ({
      toggleColorMode: () => {
        setMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
      },
    }),
    []
  );
              

Here, we also use the useMemo hook from the React. This memoized function simply alter the previous mode of the theme and change it accordingly.

Note: You can change the theme from anywhere you like and you can also get this from the ColoModeContext which is situated in the main App file.

By now, you maybe wondering that how we invoked this function. For this you have to go to the the main App.js file where you can find a small one liner context on top of the App function.


// Color Context
export const ColorModeContext = createContext({ toggleColorMode: () => {} });
              

It is basically create the toggler context which we later invoked within the App function like this


const { theme, colorMode } = CustomTheme(); 
// First destructuring the colorMode from our CustomTheme function

// Now we're setting the provider as a wrapper for all the pages and components
<ColorModeContext.Provider value={colorMode}>
// ... our awesome pages and components
</ColoModeContext.Provider>

              

By using this contexts we are good to use the toggler switch from anywhere, like this


const colorMode = useContext(ColoModeContext);

return (
  <div>
    <CustomSwitch
      defaultChecked
      onChange={colorMode.toggleColorMode}
  />
  </div>
)
              

This switch is now responsible for switching the color theme in all over the place.

Always try to use the theme from the useTheme hook from the Material UI . Otherwise the switching and theme changing will not work as expected.


Progressive Web App

The PWA is enabled for this application. Here what you should know:

In our application we are using service-workers for enabling the feature. You can also disable it by navigating to the index.js file where you will find something like this


serviceWorkerRegistration.register();

// Just remove the register() method to unregister() 
//for disable the pwa
              

For this PWA we are using two main functions which are responsible for this feature

service-worker

serviceWorkerRegistration

For this project as it is nothing but a UI kit we were used some boilerplate code. But for production grade we do not encourage to use the codes.

But you can generate one by running this below command


yarn add cra-template-pwa
              

It will generate all the starter code that you need to configure a pwa!

You have to remember that, you need to configure the manifest.json file according to your need in order to run the PWA.


Assets

The Asset folder contains all the static image assets and icons that were used in this application.


Customization

The customization will cover the following topics


Theme Customization

if you want to customize the look and feel of the application, go to the /src/CustomTheme.js folder. There are two different palette for dark and light UI.

Have a look:


{
  // palette values for dark mode
  primary: {
    main: "#F9E006",
  },
  secondary: {
    main: "#FFFFFF",
  },
  accent: {
    main: "#6C63FF",
  },
  orange: {
    main: "#FF5722",
  },
  error: {
    main: "#FF003D",
    dark: "#FF003F",
  },
  success: {
    main: "#8DD070",
    dark: "#58BD7D",
  },
  background: {
    default: "#111111",
    paper: "#252628",
    surface: "#2B2B2B",
    card: "#1B1B1B",
    primary: "#F9E006",
  },
  text: {
    primary: "#ffffff",
    secondary: "#C4C4C4",
    tertiary: "#F9E006",
    success: "#58BD7D",
    orange: "#FF9100",
  },
  common: {
    black: "#111111",
    white: "#ffffff",
  },
}
              

This palette is for the Dark UI.

Let's break this!

  • The primary color is #F9E006 which is mainly used in Titles, Buttons and in the Loaders
  • The secondary color here is #FFFFFF which mainly used in the icons, some title text which are not primary.
  • The accent color is only used in the Reward Path Area component
  • The color mentioned in orage is mainly used to show some accented color other than the whole accent color
  • The Error and Success color is used to show errors and successes in the application.
  • Next the background object is used to tell the Material UI to alter it's default background colors.
    • The default is the default background used throughout the application
    • The paper is the default background for all the cards heavily used in auth pages.
    • The surface was not heavily used on the cards component rather it was used to show some shades for the paper
    • The card is used mainly on the wallets pages
  • The text object is mainly used to show texts
  • The common object holds black and white value which is useful where we just want to use black and white color ranther than hardcoded on the code.

Similarly, for the light UI, take a look at this palette.


{
  // palette values for light mode
  primary: {
    main: "#F9E006",
  },
  secondary: {
    main: "#111111",
  },
  accent: {
    main: "#6C63FF",
  },
  orange: {
    main: "#FF5722",
  },
  error: {
    main: "#FF003D",
    dark: "#FF003F",
  },
  success: {
    main: "#8DD070",
    dark: "#58BD7D",
  },
  background: {
    default: "#fdfdfd",
    paper: "#fbfbfb",
    surface: "#ffffff",
    card: "#f8f8f8",
    primary: "#F9E006",
  },
  text: {
    primary: "#111111",
    secondary: "#434547",
    tertiary: "#F9E006",
    success: "#58BD7D",
    orange: "#FF9100",
  },
  common: {
    black: "#111111",
    white: "#ffffff",
  },
}
              

Here we just alter the colors for the light UI

For the typography you can refer this


typography: {
  fontFamily: "'Poppins', sans-serif",
  fontWeightLight: 300,
  fontWeightRegular: 400,
  fontWeightMedium: 500,
  fontWeightBold: 700,
},
              

Authentication Customization

This application is using the firebase authentication and you can find all the firebase related configurations in src/Firebase/firebase.config.js.

Keep in mind that, you have to add your own firebase configurations as the value we are using is coming from the .env.local file.

Have a look


const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
  measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
};
              

Let's add a new configurations!

For adding firebase in your project you have to follow these below steps:

  • Go to theFirebase and login or sign up with your google account
  • Then, go to the Console and click on Add Project.
  • Then name your project like this
  • Name the project
  • Then click on continue and now you can see something like this. Disable the Google Analytics for now and click to Continue. This will take you to the project dashboard.
  • dashboard
  • Now, click on the web icon where the mouse pointer is.
  • dashboard
  • After finished loading you'll see a public facing name. Enter any name you like and click on Register
  • Here under the firebaseConfig function you'll see the configurations that are needed for your project. Copy them replace the Configuration function in /src/Firebase/firebase.config.js with your copied code.
  • dashboard
  • Now, click on continue to console.

As we are used sign in and sign up functionality from firebase so it will be great if you also enable the sign in methods. You can find them on the left side menu in Project Dashboard. authenticating

Now click on the Add New Provider and select native provider which is Email/Password selecting-provider

We are recommending to use environmental variables to store the values of the configurations. Just like we did.


Deployment

So, by far we're done with the Usage and the Customization, now this application is good to deploy.

To deploy the local build in your machine, run:


yarn build #or npm run build
              

You can also view the live website by deploying it to Netlify. For this you can simply drag your build folder on Netlify.

But, if you like command line and want to done everything automatically, you may want to use vercel cli tool.

For deploying the project with vercel without connecting to git, first you have to install the vercel cli, run:


yarn global add vercel

#or

npm i -g vercel
              

Then open your shell cd to the project folder and run


vercel #all right, that's it!!
              

It will automatically minified and build the project and give you a live url where you can `preview` the project.

If you want to go to production build, simply run:


vercel --prod
              

If you are using the vercel cli for the first time, we recommend you to go through the Vercel CLI docs at once. It just makes your life easier.


Changelog

See what's new added, changed, fixed, improved or updated in the latest versions.

Version 1.0 (01 March, 2022)

Initial Release

Version 1.1 (19 March, 2022)


- Major UI Fixes
- PWA Bug Fixes
- Responsive Issue Fixes
              

Version 1.2 (07 July, 2022)


- App Title, Description & Logo Easily Configurable
- All Routes Moved to Separated File NavigationRoutes.js
- Fixed Typo Issues
- Fixed PWA Issues
              

Version 1.3 (13 Oct, 2022)


- Added pointer for verify button in Deposit popup ( FIAT Wallet)
- Date picker issue in FIAT Wallet
- Autocomplete Issue in date picker in FIAT Wallet
              

Version 1.4 (04 Feb, 2023)


- Showing Message - Incase the user has not setup the Firebase
- 2FA Login Screen UI Fix
              

Version 1.5 (21 Jul, 2023)


- Removed unwanted logs from the console
- 2FA model fixed in account section
- Updated MUI deprecated element: datepicker & listitem, button
- Logo redirection to home page : Added, 
- Removed Yarn old dependency
              

For any query & customization, reach out us at admin@thriftysoft.tech