├── .prettierignore ├── .eslintignore ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── images │ ├── ca.png │ ├── fr.png │ ├── check.png │ ├── ro-1.jpeg │ ├── ro-2.jpeg │ ├── ro-3.jpeg │ ├── ro-4.jpeg │ ├── ro-5.jpeg │ ├── ro-6.jpeg │ ├── tsp-1.jpg │ ├── tsp-2.jpg │ ├── tsp-3.jpg │ ├── tsp-4.jpg │ ├── tsp-5.jpg │ ├── auth-1.jpeg │ ├── auth-2.jpeg │ ├── auth-3.jpeg │ ├── auth-4.jpeg │ ├── auth-5.png │ ├── avatar.jpeg │ ├── apple-btn.png │ ├── avatar-d.jpeg │ ├── banner-box.png │ ├── banner-map.png │ ├── blue_logo.png │ ├── footer-bg.png │ ├── banner-clock.png │ ├── banner-human.png │ ├── banner-rocket.png │ ├── banner-wallet.png │ ├── GooglePlay-btn.png │ ├── app-download-img.png │ ├── global-payment-img.png │ ├── our-solutions-bg.png │ ├── newsletter-one-logo.png │ ├── our-solutions-icon-1.png │ ├── our-solutions-icon-2.png │ ├── our-solutions-icon-3.png │ ├── our-solutions-icon-4.png │ ├── global-payment-icon-1.png │ ├── global-payment-icon-2.png │ ├── global-payment-icon-3.png │ ├── global-payment-icon-4.png │ ├── mali.svg │ ├── cote_ivoire.svg │ ├── benin.svg │ ├── senegal.svg │ └── togo.svg ├── robots.txt ├── manifest.json ├── index.html └── js │ └── wow.js ├── assets └── img │ ├── home.png │ ├── register.png │ ├── dashboard.png │ ├── reload-account.png │ └── logo.svg ├── src ├── helpers │ ├── env.js │ └── index.js ├── bootstrap.js ├── components │ ├── modal │ │ ├── ModalBody.jsx │ │ ├── ModalTitle.jsx │ │ ├── ModalFooter.jsx │ │ └── ModalContainer.jsx │ ├── field.jsx │ ├── ReloadAccountForm.jsx │ ├── datatables │ │ ├── TableHead.jsx │ │ ├── DatatableHead.jsx │ │ └── DatatableFooter.jsx │ ├── utils.jsx │ ├── buttons.jsx │ ├── TransferForm.jsx │ └── Analytics.jsx ├── setupTests.js ├── layouts │ ├── AdminFooter.jsx │ ├── Navigation.jsx │ ├── Sidebar.jsx │ ├── AdminLayout.jsx │ ├── AuthLayout.jsx │ ├── Banner.jsx │ ├── Nav.jsx │ └── AdminNav.jsx ├── middlewares │ └── index.js ├── reportWebVitals.js ├── config.js ├── index.js ├── pages │ ├── ResetPassword.jsx │ ├── Footer.jsx │ ├── ForgotPassword.jsx │ ├── Register.jsx │ ├── Login.jsx │ ├── ReloadAccount.jsx │ └── Home.jsx ├── constants │ └── index.js ├── App.js └── index.css ├── .env.example ├── .prettierrc ├── .gitignore ├── tailwind.config.js ├── .eslintrc.js ├── package.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | assets/ 3 | 4 | # Files 5 | README.md -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | assets/ 3 | public 4 | 5 | # Files 6 | README.md -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/logo512.png -------------------------------------------------------------------------------- /assets/img/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/assets/img/home.png -------------------------------------------------------------------------------- /public/images/ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ca.png -------------------------------------------------------------------------------- /public/images/fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/fr.png -------------------------------------------------------------------------------- /assets/img/register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/assets/img/register.png -------------------------------------------------------------------------------- /public/images/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/check.png -------------------------------------------------------------------------------- /public/images/ro-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-1.jpeg -------------------------------------------------------------------------------- /public/images/ro-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-2.jpeg -------------------------------------------------------------------------------- /public/images/ro-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-3.jpeg -------------------------------------------------------------------------------- /public/images/ro-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-4.jpeg -------------------------------------------------------------------------------- /public/images/ro-5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-5.jpeg -------------------------------------------------------------------------------- /public/images/ro-6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/ro-6.jpeg -------------------------------------------------------------------------------- /public/images/tsp-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/tsp-1.jpg -------------------------------------------------------------------------------- /public/images/tsp-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/tsp-2.jpg -------------------------------------------------------------------------------- /public/images/tsp-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/tsp-3.jpg -------------------------------------------------------------------------------- /public/images/tsp-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/tsp-4.jpg -------------------------------------------------------------------------------- /public/images/tsp-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/tsp-5.jpg -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /assets/img/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/assets/img/dashboard.png -------------------------------------------------------------------------------- /public/images/auth-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/auth-1.jpeg -------------------------------------------------------------------------------- /public/images/auth-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/auth-2.jpeg -------------------------------------------------------------------------------- /public/images/auth-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/auth-3.jpeg -------------------------------------------------------------------------------- /public/images/auth-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/auth-4.jpeg -------------------------------------------------------------------------------- /public/images/auth-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/auth-5.png -------------------------------------------------------------------------------- /public/images/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/avatar.jpeg -------------------------------------------------------------------------------- /public/images/apple-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/apple-btn.png -------------------------------------------------------------------------------- /public/images/avatar-d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/avatar-d.jpeg -------------------------------------------------------------------------------- /public/images/banner-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-box.png -------------------------------------------------------------------------------- /public/images/banner-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-map.png -------------------------------------------------------------------------------- /public/images/blue_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/blue_logo.png -------------------------------------------------------------------------------- /public/images/footer-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/footer-bg.png -------------------------------------------------------------------------------- /assets/img/reload-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/assets/img/reload-account.png -------------------------------------------------------------------------------- /public/images/banner-clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-clock.png -------------------------------------------------------------------------------- /public/images/banner-human.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-human.png -------------------------------------------------------------------------------- /public/images/banner-rocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-rocket.png -------------------------------------------------------------------------------- /public/images/banner-wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/banner-wallet.png -------------------------------------------------------------------------------- /public/images/GooglePlay-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/GooglePlay-btn.png -------------------------------------------------------------------------------- /public/images/app-download-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/app-download-img.png -------------------------------------------------------------------------------- /public/images/global-payment-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/global-payment-img.png -------------------------------------------------------------------------------- /public/images/our-solutions-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/our-solutions-bg.png -------------------------------------------------------------------------------- /public/images/newsletter-one-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/newsletter-one-logo.png -------------------------------------------------------------------------------- /public/images/our-solutions-icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/our-solutions-icon-1.png -------------------------------------------------------------------------------- /public/images/our-solutions-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/our-solutions-icon-2.png -------------------------------------------------------------------------------- /public/images/our-solutions-icon-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/our-solutions-icon-3.png -------------------------------------------------------------------------------- /public/images/our-solutions-icon-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/our-solutions-icon-4.png -------------------------------------------------------------------------------- /public/images/global-payment-icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/global-payment-icon-1.png -------------------------------------------------------------------------------- /public/images/global-payment-icon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/global-payment-icon-2.png -------------------------------------------------------------------------------- /public/images/global-payment-icon-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/global-payment-icon-3.png -------------------------------------------------------------------------------- /public/images/global-payment-icon-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onesine/atompay/HEAD/public/images/global-payment-icon-4.png -------------------------------------------------------------------------------- /src/helpers/env.js: -------------------------------------------------------------------------------- 1 | const env = key => { 2 | if ("REACT_APP_" + key in process.env) return process.env["REACT_APP_" + key]; 3 | return null; 4 | }; 5 | 6 | export default env; 7 | -------------------------------------------------------------------------------- /src/bootstrap.js: -------------------------------------------------------------------------------- 1 | try { 2 | window.moment = require("moment"); 3 | // moment.locale('fr'); 4 | 5 | window.Swal = require("sweetalert2"); 6 | } catch (e) { 7 | /* empty */ 8 | } 9 | -------------------------------------------------------------------------------- /src/components/modal/ModalBody.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ModalBody = ({ children }) => { 4 | return
{children}
; 5 | }; 6 | 7 | export default ModalBody; 8 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 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 | import "@testing-library/jest-dom"; 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_NAME="AtomPay" 2 | REACT_APP_VERSION="1" 3 | REACT_APP_LOGIN="paydunya@gmail.com" 4 | REACT_APP_PASSWORD="12345" 5 | 6 | REACT_APP_API_HOST="http://super-api.com" 7 | 8 | REACT_APP_LARAVEL_PASSPORT_CLIENT="" 9 | REACT_APP_LARAVEL_PASSPORT_CLIENT_SECRET="" -------------------------------------------------------------------------------- /public/images/mali.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/cote_ivoire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/modal/ModalTitle.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ModalTitle = ({ children }) => { 4 | return ( 5 |
6 |

{children}

7 |
8 | ); 9 | }; 10 | 11 | export default ModalTitle; 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "tabWidth": 4, 4 | "printWidth": 100, 5 | "singleQuote": false, 6 | "trailingComma": "none", 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "bracketSpacing": true, 10 | "arrowParens": "avoid", 11 | "proseWrap": "always", 12 | "endOfLine": "auto" 13 | } 14 | -------------------------------------------------------------------------------- /src/layouts/AdminFooter.jsx: -------------------------------------------------------------------------------- 1 | import config from "../config"; 2 | 3 | const Footer = () => { 4 | return ( 5 | 10 | ); 11 | }; 12 | 13 | export default Footer; 14 | -------------------------------------------------------------------------------- /src/components/modal/ModalFooter.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ModalFooter = ({ children, className = "" }) => { 4 | return ( 5 |
8 | {children} 9 |
10 | ); 11 | }; 12 | 13 | export default ModalFooter; 14 | -------------------------------------------------------------------------------- /src/middlewares/index.js: -------------------------------------------------------------------------------- 1 | import { Navigate } from "react-router-dom"; 2 | 3 | import config from "../config"; 4 | 5 | const driver = config.AUTH.DRIVER; 6 | export const Auth = ({ children }) => { 7 | return driver.getItem("user") ? children : ; 8 | }; 9 | 10 | export const Guest = ({ children }) => { 11 | return !driver.getItem("user") ? children : ; 12 | }; 13 | -------------------------------------------------------------------------------- /public/images/benin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .idea 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .env 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /public/images/senegal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import env from "./helpers/env"; 2 | 3 | const config = { 4 | APP_NAME: env("NAME"), 5 | AUTH: { 6 | DRIVER: localStorage, 7 | TYPE: "LARAVEL_PASSPORT", 8 | REDIRECT_LOGIN: "/", 9 | REDIRECT_LOGOUT: "/login", 10 | API_HOST: env("API_HOST"), 11 | LARAVEL_PASSPORT: { 12 | CLIENT: env("LARAVEL_PASSPORT_CLIENT") ? env("LARAVEL_PASSPORT_CLIENT") : "", 13 | CLIENT_SECRET: env("LARAVEL_PASSPORT_CLIENT_SECRET") 14 | ? env("LARAVEL_PASSPORT_CLIENT_SECRET") 15 | : "" 16 | } 17 | } 18 | }; 19 | 20 | export default config; 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | import "./index.css"; 5 | import App from "./App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | import "./bootstrap"; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById("root")); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/images/togo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], 4 | theme: { 5 | extend: { 6 | keyframes: { 7 | rippleAni: { 8 | "0%, 100%": { transform: "translate(0px, 0px)" }, 9 | "33%": { transform: "translate(5px, -5px)" }, 10 | "66%": { transform: "translate(-5px, 5px)" } 11 | }, 12 | ripple2Ani: { 13 | "0%, 100%": { transform: "translate(0px, 0px)" }, 14 | "33%": { transform: "translate(-5px, -5px)" }, 15 | "66%": { transform: "translate(5px, 5px)" } 16 | } 17 | }, 18 | animation: { 19 | ripple: "rippleAni 3s linear infinite", 20 | ripple2: "ripple2Ani 4s linear infinite" 21 | } 22 | } 23 | }, 24 | plugins: [require("@tailwindcss/forms"), require("tailwind-scrollbar")] 25 | }; 26 | -------------------------------------------------------------------------------- /src/layouts/Navigation.jsx: -------------------------------------------------------------------------------- 1 | const Navigation = ({ path = "Dashboard" }) => { 2 | return ( 3 |
4 |
5 | 12 | 18 | 19 | 20 |

21 | {path} 22 |

23 |
24 |
25 | ); 26 | }; 27 | 28 | export default Navigation; 29 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | node: true 7 | }, 8 | globals: { 9 | process: true 10 | }, 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:react/recommended", 14 | "plugin:prettier/recommended", 15 | "plugin:react-hooks/recommended" 16 | ], 17 | overrides: [], 18 | parserOptions: { 19 | ecmaVersion: "latest", 20 | ecmaFeatures: { 21 | jsx: true 22 | }, 23 | sourceType: "module" 24 | }, 25 | settings: { 26 | react: { 27 | version: "detect" 28 | } 29 | }, 30 | plugins: ["react", "import", "prettier"], 31 | rules: { 32 | "linebreak-style": 0, 33 | quotes: ["error", "double"], 34 | semi: ["error", "always"], 35 | "import/order": [ 36 | "error", 37 | { 38 | alphabetize: { 39 | order: "asc", 40 | caseInsensitive: true 41 | }, 42 | "newlines-between": "always" 43 | } 44 | ], 45 | "react/prop-types": "off", 46 | "react/jsx-uses-react": "off", 47 | "react/react-in-jsx-scope": "off", 48 | "react-hooks/rules-of-hooks": "error", 49 | "react-hooks/exhaustive-deps": "warn", 50 | "react/no-unescaped-entities": ["error", { forbid: [">", "}"] }], 51 | "prettier/prettier": ["error", { endOfLine: "auto" }], 52 | "no-unused-vars": [ 53 | "error", 54 | { 55 | varsIgnorePattern: "React" 56 | } 57 | ] 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /src/pages/ResetPassword.jsx: -------------------------------------------------------------------------------- 1 | import { PrimaryButton } from "../components/buttons"; 2 | import { Input } from "../components/field"; 3 | import { Link } from "../components/utils"; 4 | import AuthLayout from "../layouts/AuthLayout"; 5 | 6 | const ResetPassword = () => { 7 | return ( 8 | 11 | Welcome to
our community 12 | 13 | } 14 | > 15 |

Update password

16 |

17 | Enter and confirm the new password to make the change. 18 |

19 | 20 |
21 |
22 | 28 |
29 | 30 |
31 | 37 |
38 | 39 | Update Password 40 | 41 |

42 | Back to Login 43 |

44 |
45 |
46 | ); 47 | }; 48 | 49 | export default ResetPassword; 50 | -------------------------------------------------------------------------------- /src/layouts/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { HiOutlineHome } from "react-icons/hi"; 2 | import { TfiReload } from "react-icons/tfi"; 3 | import { NavLink } from "react-router-dom"; 4 | 5 | import { scrollTop } from "../helpers"; 6 | 7 | const Sidebar = () => { 8 | const handleClickLink = () => { 9 | scrollTop(); 10 | }; 11 | 12 | const getActiveClassByLink = link => { 13 | return { 14 | isActive: window.location.pathname === link, 15 | class: window.location.pathname === link ? " text-white shadow bg-indigo-600" : "" 16 | }; 17 | }; 18 | 19 | const getLinkClass = link => { 20 | return `transition-all duration-300 flex items-center space-x-3 px-2.5 py-3 transition duration-300 rounded-md ${ 21 | getActiveClassByLink(link).isActive ? "" : "hover:bg-gray-200 hover:shadow" 22 | }${getActiveClassByLink(link).class}`; 23 | }; 24 | 25 | return ( 26 | 49 | ); 50 | }; 51 | 52 | export default Sidebar; 53 | -------------------------------------------------------------------------------- /src/layouts/AdminLayout.jsx: -------------------------------------------------------------------------------- 1 | import PerfectScrollbar from "perfect-scrollbar"; 2 | import { useEffect } from "react"; 3 | 4 | import { PageAnimation } from "../components/utils"; 5 | 6 | import AdminFooter from "./AdminFooter"; 7 | import AdminNav from "./AdminNav"; 8 | import Navigation from "./Navigation"; 9 | import Sidebar from "./Sidebar"; 10 | 11 | const AdminLayout = props => { 12 | useEffect(() => { 13 | const demo = document.querySelector("#scroll-bar", { 14 | wheelSpeed: 0.5, 15 | swipeEasing: !0, 16 | minScrollbarLength: 40, 17 | maxScrollbarLength: 100, 18 | suppressScrollY: true 19 | }); 20 | new PerfectScrollbar(demo); 21 | }, []); 22 | 23 | return ( 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | {props.children} 43 | 44 | 45 |
46 |
47 |
48 | ); 49 | }; 50 | 51 | export default AdminLayout; 52 | -------------------------------------------------------------------------------- /src/pages/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | SlSocialFacebook, 3 | SlSocialInstagram, 4 | SlSocialLinkedin, 5 | SlSocialTwitter 6 | } from "react-icons/sl"; 7 | 8 | import { Container } from "../components/utils"; 9 | 10 | const Footer = () => { 11 | return ( 12 |
13 | 14 | 19 | 20 |
21 |

22 | Copyright © 2022 AtomPay. All Rights Reserved. 23 |

24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | ); 46 | }; 47 | 48 | export default Footer; 49 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 25 | AtomPay 26 | 27 | 28 | 29 |
30 | 40 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const RELOAD_ACCOUNT_HISTORY = [ 2 | { 3 | id: 1, 4 | registerNumber: "#081451", 5 | date: "30 Jan 2021", 6 | amount: "400", 7 | status: "success" 8 | }, 9 | { 10 | id: 2, 11 | registerNumber: "#081652", 12 | date: "20 Jan 2021", 13 | amount: "5000", 14 | status: "success" 15 | }, 16 | { 17 | id: 57, 18 | registerNumber: "#081452", 19 | date: "20 Jan 2021", 20 | amount: "4000", 21 | status: "failure" 22 | }, 23 | { 24 | id: 3, 25 | registerNumber: "#081681", 26 | date: "25 Jan 2021", 27 | amount: "4000", 28 | status: "failure" 29 | }, 30 | { 31 | id: 4, 32 | registerNumber: "#091768", 33 | date: "15 Dec 2020", 34 | amount: "2200", 35 | status: "success" 36 | }, 37 | { 38 | id: 5, 39 | registerNumber: "#082693", 40 | date: "06 Feb 2021", 41 | amount: "50000", 42 | status: "success" 43 | }, 44 | { 45 | id: 6, 46 | registerNumber: "#084743", 47 | date: "27 Dec 2020", 48 | amount: "2000", 49 | status: "failure" 50 | }, 51 | { 52 | id: 7, 53 | registerNumber: "#086643", 54 | date: "31 Dec 2020", 55 | amount: "34400", 56 | status: "success" 57 | }, 58 | { 59 | id: 8, 60 | registerNumber: "#086773", 61 | date: "03 Jan 2021", 62 | amount: "40500", 63 | status: "success" 64 | }, 65 | { 66 | id: 9, 67 | registerNumber: "#087916", 68 | date: "28 Jan 2021", 69 | amount: "14900", 70 | status: "success" 71 | }, 72 | { 73 | id: 10, 74 | registerNumber: "#089472", 75 | date: "14 Jan 2021", 76 | amount: "10000", 77 | status: "success" 78 | }, 79 | { 80 | id: 11, 81 | registerNumber: "#091768", 82 | date: "10 Feb 2021", 83 | amount: "23400", 84 | status: "success" 85 | }, 86 | { 87 | id: 12, 88 | registerNumber: "#095841", 89 | date: "20 Dec 2020", 90 | amount: "104400", 91 | status: "success" 92 | } 93 | ]; 94 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 3 | 4 | import { Auth, Guest } from "./middlewares"; 5 | import Dashboard from "./pages/Dashboard"; 6 | import ForgotPassword from "./pages/ForgotPassword"; 7 | import Home from "./pages/Home"; 8 | import Login from "./pages/Login"; 9 | import Register from "./pages/Register"; 10 | import ReloadAccount from "./pages/ReloadAccount"; 11 | import ResetPassword from "./pages/ResetPassword"; 12 | 13 | const App = () => { 14 | return ( 15 | 16 | 17 | } /> 18 | 22 | 23 | 24 | } 25 | /> 26 | 30 | 31 | 32 | } 33 | /> 34 | 38 | 39 | 40 | } 41 | /> 42 | 46 | 47 | 48 | } 49 | /> 50 | 51 | 55 | 56 | 57 | } 58 | /> 59 | 63 | 64 | 65 | } 66 | /> 67 | } /> 68 | 69 | 70 | ); 71 | }; 72 | 73 | export default App; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-paydunya", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@headlessui/react": "^1.7.7", 7 | "@testing-library/jest-dom": "^5.16.5", 8 | "@testing-library/react": "^13.4.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "apexcharts": "^3.36.3", 11 | "aphrodite": "^2.4.0", 12 | "moment": "^2.29.4", 13 | "perfect-scrollbar": "^1.5.5", 14 | "react": "^18.2.0", 15 | "react-animations": "^1.0.0", 16 | "react-apexcharts": "^1.4.0", 17 | "react-countup": "^6.4.0", 18 | "react-dom": "^18.2.0", 19 | "react-icons": "^4.7.1", 20 | "react-router-dom": "^6.6.2", 21 | "react-scripts": "5.0.1", 22 | "styled-components": "^5.3.6", 23 | "sweetalert2": "^11.7.0", 24 | "web-vitals": "^2.1.4" 25 | }, 26 | "scripts": { 27 | "start": "npm run lint && npm run pret && react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject", 31 | "lint": "eslint \"./**/*.{js,jsx,ts,tsx,md}\"", 32 | "lint:fix": "eslint --fix \"./**/*.{js,jsx,ts,tsx,md}\"", 33 | "pret": "prettier -c .", 34 | "pret:fix": "prettier -w .", 35 | "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,css,md,json}\" --config ./.prettierrc" 36 | }, 37 | "eslintConfig": { 38 | "extends": [ 39 | "react-app", 40 | "react-app/jest" 41 | ] 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.2%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | }, 55 | "devDependencies": { 56 | "@tailwindcss/forms": "^0.5.3", 57 | "eslint": "^8.32.0", 58 | "eslint-config-prettier": "^8.6.0", 59 | "eslint-plugin-import": "^2.27.4", 60 | "eslint-plugin-prettier": "^4.2.1", 61 | "eslint-plugin-react": "^7.32.0", 62 | "eslint-plugin-react-hooks": "^4.6.0", 63 | "init": "^0.1.2", 64 | "npx": "^10.2.2", 65 | "prettier": "^2.8.3", 66 | "tailwind-scrollbar": "^2.1.0", 67 | "tailwindcss": "^3.2.4" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/modal/ModalContainer.jsx: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from "@headlessui/react"; 2 | import React, { Fragment } from "react"; 3 | 4 | const ModalContainer = ({ 5 | children, 6 | isOpen, 7 | setIsOpen, 8 | clickOutSideToClose = false, 9 | size = "md", 10 | margin = "yes" 11 | }) => { 12 | const closeModal = () => { 13 | if (clickOutSideToClose) { 14 | setIsOpen(true); 15 | } 16 | }; 17 | let modalWidth = "w-3/6"; 18 | if (size === "lg") { 19 | modalWidth = "w-4/6"; 20 | } 21 | 22 | if (size === "xl") { 23 | modalWidth = "w-5/6"; 24 | } 25 | 26 | if (size === "sm") { 27 | modalWidth = "w-2/6"; 28 | } 29 | 30 | return ( 31 | 32 | 37 |
38 | 47 | 48 | 49 | 50 | 59 |
64 | {children} 65 |
66 |
67 |
68 |
69 |
70 | ); 71 | }; 72 | 73 | export default ModalContainer; 74 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | import config from "../config"; 2 | 3 | export function getOffset(el) { 4 | const rect = el.getBoundingClientRect(); 5 | return { 6 | left: rect.left + window.scrollX, 7 | top: rect.top + window.scrollY 8 | }; 9 | } 10 | 11 | export const toast = (type, message) => { 12 | (function () { 13 | const Toast = window.Swal.mixin({ 14 | toast: true, 15 | position: "top-end", 16 | showConfirmButton: false, 17 | timer: 3000, 18 | timerProgressBar: true, 19 | didOpen: toast => { 20 | toast.addEventListener("mouseenter", window.Swal.stopTimer); 21 | toast.addEventListener("mouseleave", window.Swal.resumeTimer); 22 | } 23 | }); 24 | Toast.fire({ 25 | icon: type, 26 | title: message 27 | }); 28 | })(); 29 | }; 30 | 31 | export const confirmAlert = (executing, confirmButtonText = "Yes, delete!") => { 32 | window.Swal.fire({ 33 | title: "Are you sure?", 34 | text: "You can't go back !", 35 | icon: "warning", 36 | showCancelButton: true, 37 | confirmButtonColor: "#ef4444", 38 | cancelButtonColor: "#52525b", 39 | confirmButtonText: confirmButtonText, 40 | cancelButtonText: "Cancel", 41 | showLoaderOnConfirm: true, 42 | preConfirm: executing 43 | }).then(result => { 44 | if (result.isConfirmed) { 45 | // toast('success', "Succès de la suppression"); 46 | } 47 | }); 48 | }; 49 | 50 | export const checkPermissions = (permissions = []) => { 51 | let found = false; 52 | const userPermissions = ["add", "update", "delete"]; 53 | if (userPermissions) { 54 | permissions.forEach(item => { 55 | found = found || userPermissions.includes(item); 56 | }); 57 | } 58 | return found; 59 | }; 60 | 61 | export const scrollTop = () => { 62 | const element = document.getElementById("top-page"); 63 | if (element) { 64 | const position = getOffset(element); 65 | window.scrollTo(position.left, 0); 66 | } 67 | }; 68 | 69 | export const formatToCurrency = value => { 70 | return new Intl.NumberFormat("de-DE", { style: "currency", currency: "XOF" }).format( 71 | parseFloat(value) 72 | ); 73 | }; 74 | 75 | export const checkUser = () => { 76 | return !!config.AUTH.DRIVER.getItem("user"); 77 | }; 78 | 79 | export const logout = navigate => { 80 | config.AUTH.DRIVER.removeItem("user"); 81 | navigate("/", { replace: true }); 82 | toast("success", "Successful logout"); 83 | }; 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

AtomPay

2 | 3 |

4 | 5 | AtomPay Applicationo 6 |

7 | AtomPay is a money transfer platform made with React and Tailwindcss. 8 |

9 | 10 | ## Pages 11 | * Landing 12 | * Login 13 | * Register 14 | * Forgot password 15 | * Reset password 16 | * Dashboard 17 | * Reload account 18 | 19 | ## Installation 20 | 21 | 1. Clone this repo 22 | 23 | ```sh 24 | git clone https://github.com/onesine/atompay.git 25 | ``` 26 | 27 | 2. Go into the project root directory 28 | 29 | ```sh 30 | cd atompay 31 | ``` 32 | 3. Set up the user account 33 | By default, we have a user account with the login `paydunya@gmail.com` and the password `12345`. To modify this account, create an .env file from the .env.example file. 34 | 35 | ```dotenv 36 | REACT_APP_NAME="AtomPay" 37 | REACT_APP_VERSION="1" 38 | REACT_APP_LOGIN="paydunya@gmail.com" 39 | REACT_APP_PASSWORD="12345" 40 | 41 | REACT_APP_API_HOST="http://admin-api.com" 42 | 43 | REACT_APP_LARAVEL_PASSPORT_CLIENT="" 44 | REACT_APP_LARAVEL_PASSPORT_CLIENT_SECRET="" 45 | ``` 46 | 47 | 4. Install JS dependencies 48 | 49 | ```sh 50 | yarn 51 | ``` 52 | 53 | 5. Start the dev server 54 | 55 | ```sh 56 | yarn start 57 | ``` 58 | 59 | ## Online Demo 60 | 61 | You can find the online demo at [here](https://test-paydunya-atompay.vercel.app/). (login: `paydunya@gmail.com`, password: `12345`) 62 | 63 | ## Overview of some pages 64 | **Landing page** 65 | ![Landing page](https://raw.githubusercontent.com/onesine/atompay/master/assets/img/home.png?raw=true) 66 | 67 | **Register page** 68 | ![Register page](https://raw.githubusercontent.com/onesine/atompay/master/assets/img/register.png?raw=true) 69 | 70 | **Dashboard page** 71 | ![Dashboard page](https://raw.githubusercontent.com/onesine/atompay/master/assets/img/dashboard.png?raw=true) 72 | 73 | **Reload account page** 74 | ![Reload account page 75 | ](https://raw.githubusercontent.com/onesine/atompay/master/assets/img/reload-account.png?raw=true) -------------------------------------------------------------------------------- /src/pages/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { PrimaryButton } from "../components/buttons"; 4 | import { Input } from "../components/field"; 5 | import { Link, Loader } from "../components/utils"; 6 | import { toast } from "../helpers"; 7 | import AuthLayout from "../layouts/AuthLayout"; 8 | 9 | const ForgotPassword = () => { 10 | const [validationMessage, setValidationMessage] = useState([]); 11 | const [email, setEmail] = useState(""); 12 | const [loading, setLoading] = useState(false); 13 | 14 | const onSubmit = () => { 15 | setLoading(true); 16 | 17 | setTimeout(() => { 18 | setLoading(false); 19 | if (email) { 20 | if (email !== (process.env.REACT_APP_LOGIN || "paydunya@gmail.com")) { 21 | toast("error", "Failed to reload account"); 22 | setValidationMessage(["The email does not match a user"]); 23 | } else { 24 | toast("success", "An email has been sent to you to reset your password."); 25 | setEmail(""); 26 | setValidationMessage([]); 27 | } 28 | } else { 29 | toast("error", "Failed to reload account"); 30 | setValidationMessage(["This field is required"]); 31 | } 32 | }, 3000); 33 | }; 34 | 35 | return ( 36 | 39 | Welcome to
our community 40 | 41 | } 42 | > 43 |

Reset password

44 |

45 | If you forgot your password, don't worry! we’ll email you
instructions to 46 | reset your password. 47 |

48 | 49 |
50 |
51 | { 58 | setEmail(e.target.value); 59 | }} 60 | error={validationMessage} 61 | /> 62 |
63 | 64 | 65 | {loading && } 66 | Send Reset Link 67 | 68 | 69 |

70 | Back to Login 71 |

72 |
73 |
74 | ); 75 | }; 76 | 77 | export default ForgotPassword; 78 | -------------------------------------------------------------------------------- /src/components/field.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { HiEye, HiEyeSlash } from "react-icons/hi2"; 3 | 4 | export const Input = ({ 5 | id = "", 6 | type = "text", 7 | label = "", 8 | required = true, 9 | placeholder = "", 10 | value = "", 11 | disabled = false, 12 | error = [], 13 | onChange = () => {} 14 | }) => { 15 | const [show, setShow] = useState(false); 16 | 17 | return ( 18 | <> 19 | 22 | 23 |
24 | 41 | 42 | {type === "password" && ( 43 | <> 44 | {show ? ( 45 | setShow(!show)} 47 | className="cursor-pointer right-3 top-[1.2rem] text-gray-300 h-5 w-5 absolute" 48 | /> 49 | ) : ( 50 | setShow(!show)} 52 | className="cursor-pointer right-3 top-[1.2rem] text-gray-300 h-5 w-5 absolute" 53 | /> 54 | )} 55 | 56 | )} 57 | 58 | {error.map((item, index) => ( 59 |

60 | {item} 61 |

62 | ))} 63 |
64 | 65 | ); 66 | }; 67 | 68 | export const Checkbox = ({ id = "", label = "" }) => { 69 | return ( 70 | 78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /*Start Perfect Scroll*/ 6 | /* 7 | * Container style 8 | */ 9 | .ps { 10 | overflow: hidden !important; 11 | overflow-anchor: none; 12 | -ms-overflow-style: none; 13 | touch-action: auto; 14 | -ms-touch-action: auto; 15 | } 16 | 17 | /* 18 | * Scrollbar rail styles 19 | */ 20 | .ps__rail-x { 21 | display: none; 22 | opacity: 0; 23 | transition: background-color 0.2s linear, opacity 0.2s linear; 24 | -webkit-transition: background-color 0.2s linear, opacity 0.2s linear; 25 | height: 10px; 26 | /* there must be 'bottom' or 'top' for ps__rail-x */ 27 | bottom: 0px; 28 | /* please don't change 'position' */ 29 | position: absolute; 30 | } 31 | 32 | .ps__rail-y { 33 | display: none; 34 | opacity: 0; 35 | transition: background-color 0.2s linear, opacity 0.2s linear; 36 | -webkit-transition: background-color 0.2s linear, opacity 0.2s linear; 37 | width: 10px; 38 | /* there must be 'right' or 'left' for ps__rail-y */ 39 | right: 0; 40 | /* please don't change 'position' */ 41 | position: absolute; 42 | } 43 | 44 | .ps--active-x > .ps__rail-x, 45 | .ps--active-y > .ps__rail-y { 46 | display: block; 47 | background-color: transparent; 48 | } 49 | 50 | .ps:hover > .ps__rail-x, 51 | .ps:hover > .ps__rail-y, 52 | .ps--focus > .ps__rail-x, 53 | .ps--focus > .ps__rail-y, 54 | .ps--scrolling-x > .ps__rail-x, 55 | .ps--scrolling-y > .ps__rail-y { 56 | opacity: 0.6; 57 | } 58 | 59 | .ps .ps__rail-x:hover, 60 | .ps .ps__rail-y:hover, 61 | .ps .ps__rail-x:focus, 62 | .ps .ps__rail-y:focus, 63 | .ps .ps__rail-x.ps--clicking, 64 | .ps .ps__rail-y.ps--clicking { 65 | background-color: #f1f2f3; 66 | opacity: 0.9; 67 | } 68 | 69 | /* 70 | * Scrollbar thumb styles 71 | */ 72 | .ps__thumb-x { 73 | background-color: #d3d3d3; 74 | border-radius: 6px; 75 | transition: background-color 0.2s linear, height 0.2s ease-in-out; 76 | -webkit-transition: background-color 0.2s linear, height 0.2s ease-in-out; 77 | height: 4px; 78 | /* there must be 'bottom' for ps__thumb-x */ 79 | bottom: 2px; 80 | /* please don't change 'position' */ 81 | position: absolute; 82 | } 83 | 84 | .ps__thumb-y { 85 | background-color: #d3d3d3; 86 | border-radius: 6px; 87 | transition: background-color 0.2s linear, width 0.2s ease-in-out; 88 | -webkit-transition: background-color 0.2s linear, width 0.2s ease-in-out; 89 | width: 4px; 90 | /* there must be 'right' for ps__thumb-y */ 91 | right: 2px; 92 | /* please don't change 'position' */ 93 | position: absolute; 94 | } 95 | 96 | .ps__rail-x:hover > .ps__thumb-x, 97 | .ps__rail-x:focus > .ps__thumb-x, 98 | .ps__rail-x.ps--clicking .ps__thumb-x { 99 | background-color: #888ea8; 100 | height: 6px; 101 | } 102 | 103 | .ps__rail-y:hover > .ps__thumb-y, 104 | .ps__rail-y:focus > .ps__thumb-y, 105 | .ps__rail-y.ps--clicking .ps__thumb-y { 106 | background-color: #888ea8; 107 | width: 6px; 108 | } 109 | 110 | /* MS supports */ 111 | @supports (-ms-overflow-style: none) { 112 | .ps { 113 | overflow: auto !important; 114 | } 115 | } 116 | 117 | @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { 118 | .ps { 119 | overflow: auto !important; 120 | } 121 | } 122 | /*End Perfect Scroll*/ 123 | -------------------------------------------------------------------------------- /src/components/ReloadAccountForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | import { formatToCurrency, toast } from "../helpers"; 4 | 5 | import { Button } from "./buttons"; 6 | import { Input } from "./field"; 7 | import ModalBody from "./modal/ModalBody"; 8 | import ModalContainer from "./modal/ModalContainer"; 9 | import ModalFooter from "./modal/ModalFooter"; 10 | import ModalTitle from "./modal/ModalTitle"; 11 | import { Loader } from "./utils"; 12 | 13 | const ReloadAccountForm = ({ 14 | loadData, 15 | isOpen, 16 | setIsOpen, 17 | type = null, 18 | resetType, 19 | balance = 10000 20 | }) => { 21 | const [validationMessage, setValidationMessage] = useState([]); 22 | const [amount, setAmount] = useState(""); 23 | const [loading, setLoading] = useState(false); 24 | 25 | const onSubmit = () => { 26 | setLoading(true); 27 | 28 | setTimeout(() => { 29 | setLoading(false); 30 | if (amount) { 31 | if (amount > 10000) { 32 | toast("error", "Failed to reload account"); 33 | setValidationMessage([ 34 | `This amount cannot exceed your balance which is ${formatToCurrency( 35 | balance 36 | )}` 37 | ]); 38 | } else { 39 | toast("success", "Successful account reloading"); 40 | loadData(amount); 41 | setAmount(""); 42 | setValidationMessage([]); 43 | setIsOpen(false); 44 | } 45 | } else { 46 | toast("error", "Failed to reload account"); 47 | setValidationMessage(["This field is required"]); 48 | } 49 | }, 3000); 50 | }; 51 | 52 | return ( 53 | 54 | Reload Account 55 | 56 | 57 | { 63 | setAmount(e.target.value); 64 | }} 65 | placeholder={"Please enter the amount"} 66 | error={validationMessage} 67 | /> 68 | 69 | 70 | 71 | 87 | 88 | 92 | 93 | 94 | ); 95 | }; 96 | 97 | export default ReloadAccountForm; 98 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.jsx: -------------------------------------------------------------------------------- 1 | import { HiBeaker } from "react-icons/hi2"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const AuthLayout = ({ title = "", children }) => { 5 | return ( 6 |
7 |
8 |
9 | 10 | 11 |
12 | 13 | 14 |
15 |

16 | Atompay 17 |

18 |
19 | 20 | React 21 | 22 |
23 |
24 |
25 | 26 | 27 |
28 |

{title}

29 | 30 |

31 | Discover how to manage Two-Factor Authentication in{" "} 32 |
Joomla. The 33 | two-factor
authentication 34 | in Joomla is a very{" "} 35 |
popular security 36 | practice. 37 |

38 |
39 | 40 |
41 |
42 | {[1, 2, 3, 4].map(item => ( 43 | 49 | ))} 50 |
51 | 52 |

53 | More than 2k people joined us, it's your turn 54 |

55 |
56 |
57 |
58 | 59 |
60 | {children} 61 |
62 |
63 |
64 | ); 65 | }; 66 | 67 | export default AuthLayout; 68 | -------------------------------------------------------------------------------- /src/components/datatables/TableHead.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const TableHead = ({ 5 | head = [], 6 | checkAll = true, 7 | checkValue = false, 8 | onChangeCheckValue, 9 | sort = null, 10 | onSort, 11 | viewButton, 12 | editButton, 13 | deleteButton, 14 | paginateData = null 15 | }) => { 16 | return ( 17 | 18 | 19 | {checkAll && ( 20 | 21 | onChangeCheckValue(e.target.checked)} 26 | /> 27 | 28 | )} 29 | 30 | {head.map((item, index) => ( 31 | onSort(index)} key={index}> 32 | 33 | {item} 34 | 35 | {sort !== null && paginateData === null && ( 36 | 37 | 43 | 48 | 49 | 56 | 61 | 62 | 63 | )} 64 | 65 | 66 | ))} 67 | 68 | {(viewButton !== null || editButton !== null || deleteButton !== null) && ( 69 | Actions 70 | )} 71 | 72 | 73 | ); 74 | }; 75 | 76 | export default TableHead; 77 | 78 | const Container = styled.thead.attrs(() => ({ 79 | className: "" 80 | }))``; 81 | 82 | const CheckboxContainer = styled.th.attrs(() => ({ 83 | className: "w-10 px-4 py-2 font-normal text-left text-gray-600" 84 | }))``; 85 | 86 | const HeadItem = styled.th.attrs(props => ({ 87 | className: `px-4 py-2 font-semibold tracking-wider cursor-pointer text-${ 88 | ["left", "center", "right"].includes(props.position) ? props.position : "left" 89 | } text-gray-600` 90 | }))``; 91 | 92 | const HeadItemContainer = styled.div.attrs(() => ({ 93 | className: "flex items-center justify-between w-full" 94 | }))``; 95 | 96 | const ChevronContainer = styled.div.attrs(() => ({ 97 | className: "flex flex-col" 98 | }))``; 99 | 100 | const ChevronIcon = styled.svg.attrs(props => ({ 101 | className: `w-3 h-3 text-gray-500 ${props.opacityIcon ? "text-opacity-30" : ""} ${ 102 | props.position === "bottom" ? "transform rotate-180" : "" 103 | }` 104 | }))``; 105 | -------------------------------------------------------------------------------- /src/components/datatables/DatatableHead.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | import styled from "styled-components"; 4 | 5 | import { Loader } from "../utils"; 6 | 7 | const DatatableHead = ({ 8 | data, 9 | searchValue = "", 10 | onChangeSearchValue, 11 | numberPerPage = 7, 12 | onChangeNumberPerPage, 13 | onClick, 14 | addButton, 15 | checkAll, 16 | loading = false, 17 | paginateData = null 18 | }) => { 19 | return ( 20 | 21 | 22 | Results: 23 | 29 | 30 | {addButton && 31 | (addButton.type === "link" ? ( 32 | 38 | {addButton.text} 39 | 40 | ) : ( 41 | <> 42 | 45 | 46 | {addButton.form} 47 | 48 | ))} 49 | 50 | 51 | 52 |
53 | onChangeSearchValue(e.target.value)} 58 | /> 59 | {searchValue.length > 0 && paginateData !== null && loading ? ( 60 | 65 | ) : ( 66 | 72 | 78 | 79 | )} 80 |
81 | 82 | {checkAll && data.filter(item => item.checked).length > 0 && ( 83 | 86 | )} 87 |
88 |
89 | ); 90 | }; 91 | 92 | export default DatatableHead; 93 | 94 | const Container = styled.div.attrs(() => ({ 95 | className: "flex items-center justify-between p-5" 96 | }))``; 97 | 98 | const LeftContainer = styled.div.attrs(() => ({ 99 | className: "flex items-center space-x-3" 100 | }))``; 101 | 102 | const RightContainer = styled.div.attrs(() => ({ 103 | className: "flex items-center space-x-3" 104 | }))``; 105 | 106 | const Select = styled.select.attrs(() => ({ 107 | className: 108 | "py-1.5 rounded-md text-xs focus:ring-0 border-gray-300 text-gray-700 focus:border-gray-500" 109 | }))``; 110 | 111 | const SelectLabel = styled.span.attrs(() => ({ 112 | className: "text-sm text-gray-500" 113 | }))``; 114 | 115 | const Button = styled.button.attrs(props => ({ 116 | className: `px-6 py-2 text-xs font-light text-white transition duration-300 bg-${props.color}-${props.varient} rounded shadow-xl hover:shadow-none` 117 | }))``; 118 | 119 | const Input = styled.input.attrs(() => ({ 120 | className: 121 | "text-xs text-gray-700 placeholder-gray-300 transition duration-300 border-gray-200 rounded-md pr-9 focus:ring-0 focus:border-gray-500" 122 | }))``; 123 | 124 | const InputIcon = styled.svg.attrs(() => ({ 125 | className: "w-6 h-6 absolute right-0 top-1.5 mr-2 text-gray-300" 126 | }))``; 127 | -------------------------------------------------------------------------------- /src/components/utils.jsx: -------------------------------------------------------------------------------- 1 | import { css, StyleSheet } from "aphrodite"; 2 | import { HiBeaker } from "react-icons/hi2"; 3 | import { Link as RouterLink } from "react-router-dom"; 4 | 5 | export const Link = ({ href = "", children }) => { 6 | return ( 7 | 11 | {children} 12 | 13 | ); 14 | }; 15 | 16 | export const Container = ({ children, className }) => { 17 | return ( 18 |
23 | {children} 24 |
25 | ); 26 | }; 27 | 28 | const styles = StyleSheet.create({ 29 | fadeInDown: { 30 | animationName: { 31 | from: { 32 | opacity: 0, 33 | transform: "translate3d(0, -20px, 0)" 34 | }, 35 | to: { 36 | opacity: 1, 37 | transform: "none" 38 | } 39 | }, 40 | animationDuration: "0.5s" 41 | } 42 | }); 43 | 44 | export const PageAnimation = props => { 45 | return ( 46 |
47 | {props.children} 48 |
49 | ); 50 | }; 51 | 52 | export const LogoLink = ({ url = "/" }) => { 53 | return ( 54 | 55 | 56 |
57 |

58 | AtomPay 59 |

60 |
61 | 62 | React 63 | 64 |
65 |
66 |
67 | 68 | ); 69 | }; 70 | 71 | export const PageTitle = ({ children }) => { 72 | return

{children}

; 73 | }; 74 | 75 | export const Loader = ({ size = null, color = null, className = "" }) => { 76 | let newSize = "h-5 w-5"; 77 | let newColor = "text-white"; 78 | 79 | switch (color) { 80 | case "yellow": 81 | newColor = "text-yellow-500"; 82 | break; 83 | case "orange": 84 | newColor = "text-orange-500"; 85 | break; 86 | case "green": 87 | newColor = "text-green-500"; 88 | break; 89 | case "blue": 90 | newColor = "text-blue-500"; 91 | break; 92 | case "indigo": 93 | newColor = "text-indigo-500"; 94 | break; 95 | case "pink": 96 | newColor = "text-pink-500"; 97 | break; 98 | case "purple": 99 | newColor = "text-purple-500"; 100 | break; 101 | case "red": 102 | newColor = "text-red-500"; 103 | break; 104 | case "black": 105 | newColor = "text-gray-700"; 106 | break; 107 | case "white": 108 | newColor = "text-white"; 109 | break; 110 | default: 111 | newColor = "text-gray-700"; 112 | break; 113 | } 114 | 115 | switch (size) { 116 | case "sm": 117 | newSize = "h-4 w-4"; 118 | break; 119 | case "md": 120 | newSize = "h-5 w-5"; 121 | break; 122 | case "lg": 123 | newSize = "h-8 w-8"; 124 | break; 125 | case "xl": 126 | newSize = "h-10 w-10"; 127 | break; 128 | case "2xl": 129 | newSize = "h-12 w-12"; 130 | break; 131 | case "3xl": 132 | newSize = "h-16 w-16"; 133 | break; 134 | default: 135 | break; 136 | } 137 | 138 | return ( 139 | 145 | 153 | 158 | 159 | ); 160 | }; 161 | -------------------------------------------------------------------------------- /src/components/buttons.jsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | export const PrimaryButton = ({ 5 | type = "button", 6 | children, 7 | as = "button", 8 | href = "#", 9 | disabled = false, 10 | ...other 11 | }) => { 12 | const Tag = as; 13 | 14 | const otherAttributes = useMemo(() => { 15 | const attributes = {}; 16 | if (as === "button") { 17 | attributes.type = type; 18 | } 19 | 20 | if (as === "a") { 21 | attributes.href = href; 22 | } 23 | 24 | return attributes; 25 | }, [as, href, type]); 26 | 27 | return ( 28 | 35 | {children} 36 | 37 | ); 38 | }; 39 | 40 | export const SecondaryButton = ({ type = "button", children, as = "button", href = "#" }) => { 41 | const Tag = as; 42 | 43 | const otherAttributes = useMemo(() => { 44 | const attributes = {}; 45 | if (as === "button") { 46 | attributes.type = type; 47 | } 48 | 49 | if (as === "a") { 50 | attributes.href = href; 51 | } 52 | 53 | return attributes; 54 | }, [as, href, type]); 55 | 56 | return ( 57 | 61 | {children} 62 | 63 | ); 64 | }; 65 | 66 | export const Button = ({ 67 | color = "primary", 68 | onClick = () => {}, 69 | children, 70 | disabled = false, 71 | size = "normal", 72 | url = null, 73 | className = "" 74 | }) => { 75 | const navigate = useNavigate(); 76 | let sizeClass = "py-2"; 77 | if (size === "normal") { 78 | sizeClass = "py-2"; 79 | } 80 | 81 | if (size === "small") { 82 | sizeClass = "py-1"; 83 | } 84 | 85 | const gotoLink = () => { 86 | navigate(url); 87 | }; 88 | 89 | switch (color) { 90 | case "primary": 91 | return ( 92 | 101 | ); 102 | case "secondary": 103 | return ( 104 | 113 | ); 114 | case "outline": 115 | return ( 116 | 127 | ); 128 | default: 129 | return ( 130 | 139 | ); 140 | } 141 | }; 142 | -------------------------------------------------------------------------------- /src/layouts/Banner.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | import TransferForm from "../components/TransferForm"; 4 | import { Container } from "../components/utils"; 5 | import { checkUser } from "../helpers"; 6 | 7 | const Banner = () => { 8 | return ( 9 |
10 |
14 |
15 | 20 | 25 | 30 | 35 |
36 | 37 |
38 | 39 | 44 |
45 |
46 | Trusted by over 3M customers 47 |
48 |

49 | Pay Anyone, 50 |
Anywhere 51 |

52 |

53 | Quickly and easily send, receive and{" "} 54 |
request{" "} 55 |
money online with 56 | AtomPay.
Get a 57 | customised
solution to fit your{" "} 58 |
business needs. 59 |

60 | 61 |
62 | {!checkUser() && ( 63 | 67 | Open a Free Account 68 | 69 | )} 70 | 71 | 75 | See How it Works 76 | 77 |
78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 |
86 |
87 |
88 |
89 | ); 90 | }; 91 | 92 | export default Banner; 93 | -------------------------------------------------------------------------------- /src/pages/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { FcGoogle } from "react-icons/fc"; 3 | import { RiFacebookCircleFill } from "react-icons/ri"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | import { PrimaryButton, SecondaryButton } from "../components/buttons"; 7 | import { Checkbox, Input } from "../components/field"; 8 | import { Link, Loader } from "../components/utils"; 9 | import { toast } from "../helpers"; 10 | import AuthLayout from "../layouts/AuthLayout"; 11 | 12 | const Register = () => { 13 | const navigate = useNavigate(); 14 | const defaultMessage = { 15 | email: [], 16 | password: [], 17 | telephone: [] 18 | }; 19 | 20 | const [loading, setLoading] = useState(false); 21 | const [telephone, setTelephone] = useState(""); 22 | const [errorMessage, setErrorMessage] = useState(defaultMessage); 23 | const [email, setEmail] = useState(""); 24 | const [password, setPassword] = useState(""); 25 | 26 | const register = () => { 27 | setLoading(true); 28 | setTimeout(() => { 29 | const newErrorMessage = defaultMessage; 30 | if (!email) { 31 | newErrorMessage.email = ["This field is required"]; 32 | } 33 | if (!password) { 34 | newErrorMessage.password = ["This field is required"]; 35 | } 36 | 37 | if (!telephone) { 38 | newErrorMessage.telephone = ["This field is required"]; 39 | } 40 | 41 | if (email && telephone && password) { 42 | toast("success", "Successful registration"); 43 | navigate("/login"); 44 | } else { 45 | toast("error", "Failed registration"); 46 | } 47 | setErrorMessage(defaultMessage); 48 | setLoading(false); 49 | }, 3000); 50 | }; 51 | 52 | return ( 53 | 56 | Welcome to
our community 57 | 58 | } 59 | > 60 |

Create New Account

61 |

62 | Use your remail email continue with Nioboard (it's free)! 63 |

64 | 65 |
66 |
67 | setTelephone(e.target.value)} 74 | error={errorMessage.telephone} 75 | /> 76 |
77 | 78 |
79 | setEmail(e.target.value)} 86 | error={errorMessage.email} 87 | /> 88 |
89 | 90 |
91 | setPassword(e.target.value)} 98 | error={errorMessage.password} 99 | /> 100 |
101 | 102 |
103 | 104 |
105 | 106 | 107 | {loading && } 108 | Sign up 109 | 110 | 111 |
112 |
113 | Or 114 |
115 |
116 | 117 |
118 | 119 | 120 | 121 | 122 | Continue with Google 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | Continue with Facebook 131 | 132 | 133 |
134 | 135 |

136 | Already have an account? Login 137 |

138 |
139 |
140 | ); 141 | }; 142 | 143 | export default Register; 144 | -------------------------------------------------------------------------------- /src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { FcGoogle } from "react-icons/fc"; 3 | import { RiFacebookCircleFill } from "react-icons/ri"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | import { PrimaryButton, SecondaryButton } from "../components/buttons"; 7 | import { Checkbox, Input } from "../components/field"; 8 | import { Link, Loader } from "../components/utils"; 9 | import config from "../config"; 10 | import { toast } from "../helpers"; 11 | import AuthLayout from "../layouts/AuthLayout"; 12 | 13 | const Login = () => { 14 | const navigate = useNavigate(); 15 | const defaultMessage = { 16 | email: [], 17 | password: [] 18 | }; 19 | 20 | const [loading, setLoading] = useState(false); 21 | const [invalid, setInvalid] = useState(false); 22 | const [errorMessage, setErrorMessage] = useState(defaultMessage); 23 | const [email, setEmail] = useState(""); 24 | const [password, setPassword] = useState(""); 25 | 26 | const login = () => { 27 | setLoading(true); 28 | setTimeout(() => { 29 | const newErrorMessage = defaultMessage; 30 | if (!email) { 31 | newErrorMessage.email = ["This field is required"]; 32 | } 33 | if (!password) { 34 | newErrorMessage.password = ["This field is required"]; 35 | } 36 | 37 | if ( 38 | email === (process.env.REACT_APP_LOGIN || "paydunya@gmail.com") && 39 | password === (process.env.REACT_APP_PASSWORD || "12345") 40 | ) { 41 | setInvalid(true); 42 | toast("success", "Successful connection"); 43 | config.AUTH.DRIVER.setItem("user", { 44 | name: "Paydunya", 45 | permissions: ["reload-account", "dashboard", "transfer-money"] 46 | }); 47 | navigate(config.AUTH.REDIRECT_LOGIN); 48 | } 49 | 50 | if (!email || !password || email !== "paydunya@gmail.com" || password !== "12345") { 51 | if ( 52 | email !== process.env.REACT_APP_LOGIN || 53 | password !== process.env.REACT_APP_PASSWORD 54 | ) { 55 | setInvalid(true); 56 | } else { 57 | setInvalid(false); 58 | } 59 | toast("error", "Connection failed"); 60 | } 61 | 62 | setErrorMessage(newErrorMessage); 63 | setLoading(false); 64 | }, 3000); 65 | }; 66 | 67 | return ( 68 | 71 | Welcome back to
our community 72 | 73 | } 74 | > 75 |

Login to Account

76 |

77 | Please sign-in to your account and start the adventure. 78 |

79 | 80 | {invalid && ( 81 |
82 | Invalid email or password 83 |
84 | )} 85 | 86 |
87 |
88 | setEmail(e.target.value)} 95 | error={errorMessage.email} 96 | /> 97 |
98 | 99 |
100 | setPassword(e.target.value)} 107 | error={errorMessage.password} 108 | /> 109 |
110 | 111 |
112 | 113 | 114 | Forgot Password? 115 |
116 | 117 | 118 | {loading && } 119 | Login to account 120 | 121 | 122 |
123 |
124 | Or 125 |
126 |
127 | 128 |
129 | 130 | 131 | 132 | 133 | Continue with Google 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Continue with Facebook 142 | 143 | 144 |
145 | 146 |

147 | Don't have an account? Register 148 |

149 |
150 |
151 | ); 152 | }; 153 | 154 | export default Login; 155 | -------------------------------------------------------------------------------- /src/components/datatables/DatatableFooter.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import styled from "styled-components"; 3 | 4 | import { scrollTop } from "../../helpers"; 5 | 6 | const DatatableFooter = ({ currentPage = 0, totalPage = 0, next, previous, gotoPage }) => { 7 | const scrollToPagination = () => { 8 | document.getElementById("table_pagination").scrollIntoView(); 9 | scrollTop(); 10 | }; 11 | 12 | const onNext = async () => { 13 | await next(); 14 | scrollToPagination(); 15 | }; 16 | 17 | const onPrevious = async () => { 18 | await previous(); 19 | scrollToPagination(); 20 | }; 21 | 22 | const onGotoPage = async item => { 23 | await gotoPage(item); 24 | scrollToPagination(); 25 | }; 26 | 27 | const active = 28 | "px-3 py-1.5 bg-indigo-600 font-medium rounded-md text-sm text-white hover:outline-none focus:outline-none hover:bg-indigo-700"; 29 | const notActive = 30 | "px-3 py-1.5 text-gray-600 font-medium hover:text-indigo-500 hover:bg-indigo-100 rounded-md text-sm hover:outline-none focus:outline-none"; 31 | const buttonHover = 32 | "w-8 h-8 text-gray-500 px-1 py-0.5 rounded-md hover:bg-indigo-100 hover:text-indigo-500"; 33 | const buttonNotHover = "w-8 h-8 text-gray-300 px-1 py-0.5"; 34 | 35 | const pages = useMemo(() => { 36 | let result = []; 37 | let i; 38 | for (i = 0; i < totalPage; i++) { 39 | result[i] = i + 1; 40 | } 41 | if (result.length === 0) { 42 | return [1]; 43 | } 44 | return result; 45 | }, [totalPage]); 46 | const pagesLength = pages.length; 47 | const firstPage = pages[0]; 48 | const lastPage = pages[pagesLength - 1]; 49 | const firstNextPages = [...pages].splice(1, 4); 50 | const lastPreviousPages = [...pages].splice(pagesLength - 5, 4); 51 | const previousCurrent = pages[pages.indexOf(currentPage) - 1]; 52 | const nextCurrent = pages[pages.indexOf(currentPage) + 1]; 53 | const centerPages = 54 | previousCurrent && nextCurrent ? [previousCurrent, currentPage, nextCurrent] : []; 55 | const showFirstNextPages = currentPage <= 4; 56 | const showLastPreviousPages = currentPage >= lastPreviousPages[1]; 57 | 58 | return ( 59 | 60 | 61 | Display page {currentPage} of {totalPage} 62 | 63 | 64 |
65 | 85 | 86 | {pagesLength <= 7 && 87 | pages.map((item, index) => ( 88 | 95 | ))} 96 | 97 | {pagesLength >= 8 && ( 98 | <> 99 | 105 | 106 | {showFirstNextPages && 107 | firstNextPages.map((item, index) => ( 108 | 115 | ))} 116 | 117 | {!showLastPreviousPages && ( 118 | 121 | )} 122 | 123 | {!showLastPreviousPages && 124 | !showFirstNextPages && 125 | centerPages.map((item, index) => ( 126 | 133 | ))} 134 | 135 | {!showFirstNextPages && ( 136 | 139 | )} 140 | 141 | {showLastPreviousPages && 142 | lastPreviousPages.map((item, index) => ( 143 | 150 | ))} 151 | 152 | 158 | 159 | )} 160 | 161 | 185 |
186 |
187 | ); 188 | }; 189 | 190 | export default DatatableFooter; 191 | 192 | const Container = styled.div.attrs(() => ({ 193 | className: "p-5 flex items-center justify-between" 194 | }))``; 195 | 196 | const ShowingInfo = styled.div.attrs(() => ({ 197 | className: "inline-block px-6 py-2 text-sm tracking-wide text-indigo-400 border rounded-md" 198 | }))``; 199 | -------------------------------------------------------------------------------- /src/pages/ReloadAccount.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useCallback, useState } from "react"; 2 | 3 | import Analytics from "../components/Analytics"; 4 | import Datatable from "../components/datatables/Datatable"; 5 | import ReloadAccountForm from "../components/ReloadAccountForm"; 6 | import { PageTitle } from "../components/utils"; 7 | import { RELOAD_ACCOUNT_HISTORY } from "../constants"; 8 | import { checkPermissions, formatToCurrency, toast } from "../helpers"; 9 | import AdminLayout from "../layouts/AdminLayout"; 10 | 11 | const ReloadAccount = () => { 12 | const [balance, setBalance] = useState(10840); 13 | const [data, setData] = useState([]); 14 | const [loading, setLoading] = useState(false); 15 | const [showForm, setShowForm] = useState(false); 16 | 17 | useEffect(() => { 18 | setLoading(true); 19 | setTimeout(() => { 20 | setData( 21 | RELOAD_ACCOUNT_HISTORY.map(item => { 22 | return { 23 | id: item.id, 24 | registerNumber: { 25 | text: item.registerNumber, 26 | jsx: ( 27 |
28 | {item.registerNumber} 29 |
30 | ) 31 | }, 32 | date: { 33 | text: item.date, 34 | jsx: ( 35 |
36 | 43 | 49 | 50 | {item.date} 51 |
52 | ) 53 | }, 54 | amount: { 55 | text: item.amount, 56 | jsx: {formatToCurrency(item.amount)} 57 | }, 58 | status: { 59 | text: item.status, 60 | jsx: ( 61 |
68 | {item.status} 69 |
70 | ) 71 | } 72 | }; 73 | }) 74 | ); 75 | setLoading(false); 76 | }, 3000); 77 | }, []); 78 | 79 | const deleteItem = useCallback( 80 | itemId => { 81 | setData(data.filter(item => item.id !== itemId)); 82 | toast("success", "Success of the suppression"); 83 | }, 84 | [data] 85 | ); 86 | 87 | const deleteItems = useCallback( 88 | itemsId => { 89 | setData(data.filter(item => !itemsId.includes(item.id))); 90 | toast("success", "Success of the deletions"); 91 | }, 92 | [data] 93 | ); 94 | 95 | const addItem = useCallback( 96 | amount => { 97 | setBalance(b => parseInt(b) + parseInt(amount)); 98 | setData([ 99 | { 100 | id: Date.now().toString(), 101 | registerNumber: { 102 | text: Date.now().toString(), 103 | jsx: ( 104 |
105 | #{Date.now().toString()} 106 |
107 | ) 108 | }, 109 | date: { 110 | text: window.moment().format("ll"), 111 | jsx: ( 112 |
113 | 120 | 126 | 127 | {window.moment().format("ll")} 128 |
129 | ) 130 | }, 131 | amount: { 132 | text: amount, 133 | jsx: {formatToCurrency(amount)} 134 | }, 135 | status: { 136 | text: "success", 137 | jsx: ( 138 |
143 | success 144 |
145 | ) 146 | } 147 | }, 148 | ...data 149 | ]); 150 | }, 151 | [data] 152 | ); 153 | 154 | return ( 155 | 156 |
157 | 158 |
159 | 160 | Reload Account History 161 | 162 | 182 | ), 183 | click: () => { 184 | setShowForm(!showForm); 185 | } 186 | } 187 | : null 188 | } 189 | deleteButton={ 190 | checkPermissions(["delete"]) 191 | ? { 192 | text: "Delete" 193 | } 194 | : null 195 | } 196 | loading={loading} 197 | /> 198 |
199 | ); 200 | }; 201 | 202 | export default ReloadAccount; 203 | -------------------------------------------------------------------------------- /src/components/TransferForm.jsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from "@headlessui/react"; 2 | import React, { useState } from "react"; 3 | import { HiChevronRight } from "react-icons/hi"; 4 | import { IoMdSync } from "react-icons/io"; 5 | import { useNavigate } from "react-router-dom"; 6 | 7 | import { checkUser, formatToCurrency, toast } from "../helpers"; 8 | 9 | import { Loader } from "./utils"; 10 | 11 | const TransferForm = () => { 12 | const defaultMessage = { 13 | amount: [], 14 | phoneNumber: [] 15 | }; 16 | const countries = [ 17 | { 18 | text: "Bénin", 19 | img: "benin" 20 | }, 21 | { 22 | text: "Togo", 23 | img: "togo" 24 | }, 25 | { 26 | text: "Sénégal", 27 | img: "senegal" 28 | } 29 | ]; 30 | 31 | const navigate = useNavigate(); 32 | 33 | const [errorMessage, setErrorMessage] = useState(defaultMessage); 34 | const [loading, setLoading] = useState(false); 35 | const [country, setCountry] = useState(countries[0]); 36 | const [currency, setCurrency] = useState("XOF"); 37 | const [amount, setAmount] = useState(""); 38 | const [phoneNumber, setPhoneNumber] = useState(""); 39 | 40 | const submit = () => { 41 | if (!checkUser()) { 42 | toast("error", "This action requires you to be connected"); 43 | navigate("/login"); 44 | } else { 45 | setLoading(true); 46 | setTimeout(() => { 47 | const newErrorMessage = defaultMessage; 48 | if (!amount) { 49 | newErrorMessage.amount = ["This field is required"]; 50 | } 51 | if (!phoneNumber) { 52 | newErrorMessage.phoneNumber = ["This field is required"]; 53 | } 54 | 55 | if (amount && phoneNumber) { 56 | toast("success", "Successful transfer"); 57 | setAmount(""); 58 | setCurrency(""); 59 | } else { 60 | toast("error", "Transfer failure"); 61 | } 62 | setErrorMessage(newErrorMessage); 63 | 64 | setLoading(false); 65 | }, 3000); 66 | } 67 | }; 68 | 69 | return ( 70 | <> 71 |
72 |
73 |
74 |
You send
75 | setAmount(e.target.value)} 85 | /> 86 | {errorMessage.amount.length > 0 && ( 87 |

{errorMessage.amount}

88 | )} 89 |
90 | 91 | 92 | {({ open }) => ( 93 |
94 | 95 | {currency} 96 | 101 | 102 | 103 | 111 | 112 |
113 | 117 | XOF 118 | FRANC CFA 119 | 120 |
121 |
122 |
123 |
124 | )} 125 |
126 |
127 |
128 | 129 |
130 |
131 | 132 | Show calculations 133 |
134 | 135 |
136 |
137 | 1 XOF = 1 XOF 138 |
139 |
Currency
140 |
141 | 142 |
143 |
144 | 145 | {formatToCurrency(180)} /{" "} 146 | {formatToCurrency(parseFloat(amount || 0) + 180)} 147 | 148 |
149 |
TVA / Total
150 |
151 | 152 |
153 |
154 | {formatToCurrency(parseFloat(amount || 0))} 155 |
156 |
Recipient gets
157 |
158 |
159 | 160 |
161 |
162 |
163 |
Recipient's phone number
164 | setPhoneNumber(e.target.value)} 174 | /> 175 | {errorMessage.phoneNumber.length > 0 && ( 176 |

177 | {errorMessage.phoneNumber} 178 |

179 | )} 180 |
181 | 182 | 183 | {({ open }) => ( 184 |
185 | 186 | 191 | 196 | 197 | 198 | 206 | 207 |
208 | {countries.map((item, index) => ( 209 | setCountry(item)} 214 | > 215 | 220 | {item.text} 221 | 222 | ))} 223 |
224 |
225 |
226 |
227 | )} 228 |
229 |
230 |
231 | 232 | 239 | 240 | ); 241 | }; 242 | 243 | export default TransferForm; 244 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import CountUp from "react-countup"; 2 | 3 | import { Container } from "../components/utils"; 4 | import Banner from "../layouts/Banner"; 5 | import Nav from "../layouts/Nav"; 6 | 7 | import Footer from "./Footer"; 8 | 9 | const Home = () => { 10 | return ( 11 |
12 |
253 | ); 254 | }; 255 | 256 | export default Home; 257 | -------------------------------------------------------------------------------- /src/layouts/Nav.jsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from "@headlessui/react"; 2 | import { Fragment } from "react"; 3 | import { AiOutlineDashboard } from "react-icons/ai"; 4 | import { HiMenu } from "react-icons/hi"; 5 | import { HiBeaker } from "react-icons/hi2"; 6 | import { Link, useNavigate } from "react-router-dom"; 7 | 8 | import { Container } from "../components/utils"; 9 | import { checkUser, logout } from "../helpers"; 10 | 11 | const Nav = () => { 12 | const navigate = useNavigate(); 13 | const handleClick = e => { 14 | e.preventDefault(); 15 | logout(navigate); 16 | setTimeout(() => { 17 | window.location.reload(); 18 | }, 100); 19 | }; 20 | 21 | return ( 22 | 260 | ); 261 | }; 262 | 263 | export default Nav; 264 | -------------------------------------------------------------------------------- /src/layouts/AdminNav.jsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from "@headlessui/react"; 2 | import { AiOutlineHome } from "react-icons/ai"; 3 | import { HiBeaker } from "react-icons/hi2"; 4 | import { Link, useNavigate } from "react-router-dom"; 5 | 6 | import { logout } from "../helpers"; 7 | 8 | const AdminNav = () => { 9 | const navigate = useNavigate(); 10 | 11 | const handleClick = e => { 12 | e.preventDefault(); 13 | logout(navigate); 14 | }; 15 | 16 | return ( 17 |
18 |
19 | 20 | 21 |
22 |

23 | AtomPay 24 |

25 |
26 | 27 | React 28 | 29 |
30 |
31 |
32 | 33 | 34 |
35 | 40 | 41 | 53 | 54 | 55 | 56 |
57 |
58 | 59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 | 74 | 75 |
76 | 77 | 81 | 82 |

French

83 | 84 |
85 | 86 | 87 | 91 | 92 |

English

93 | 94 |
95 |
96 |
97 |
98 |
99 |
100 | 101 | 102 |
103 | 104 | 111 | 117 | 118 | 119 | 120 | 128 | 129 |
130 | 131 | 135 |
136 | 142 | 147 | 148 |
149 |

Server Rebooted

150 |

45 min ago

151 |
152 |
153 | 154 | 161 | 167 | 168 | 169 |
170 |
171 | 172 | 173 | 177 |
178 | 185 | 191 | 192 |
193 |

Licence Expiring Soon

194 |

8 hrs ago

195 |
196 |
197 | 198 | 205 | 211 | 212 | 213 |
214 |
215 |
216 |
217 |
218 |
219 | 220 | 221 |
222 | 223 | 224 | 225 | 226 | 234 | 235 |
236 | 237 | 241 | 242 | Lending Page 243 | 244 | 245 |
246 | 247 | 248 | 252 | 259 | 265 | 266 | Profile 267 | 268 | 269 |
270 | 271 | 272 | 277 | 284 | 290 | 291 | Logout 292 | 293 | 294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 | ); 302 | }; 303 | 304 | export default AdminNav; 305 | -------------------------------------------------------------------------------- /src/components/Analytics.jsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from "@headlessui/react"; 2 | import { useState } from "react"; 3 | import Chart from "react-apexcharts"; 4 | 5 | import { formatToCurrency } from "../helpers"; 6 | 7 | const Analytics = ({ balance = 0 }) => { 8 | const [expenses, setExpenses] = useState({ 9 | value: "45141", 10 | period: "This week", 11 | percentage: 57 12 | }); 13 | 14 | const sparkOptions1 = { 15 | chart: { 16 | id: "unique-visits", 17 | group: "sparks2", 18 | type: "line", 19 | height: 58, 20 | sparkline: { 21 | enabled: true 22 | }, 23 | dropShadow: { 24 | enabled: true, 25 | top: 3, 26 | left: 1, 27 | blur: 3, 28 | color: "#009688", 29 | opacity: 0.7 30 | } 31 | }, 32 | stroke: { 33 | curve: "smooth", 34 | width: 2 35 | }, 36 | markers: { 37 | size: 0 38 | }, 39 | grid: { 40 | padding: { 41 | top: 0, 42 | bottom: 0, 43 | left: 0 44 | } 45 | }, 46 | colors: ["#009688"], 47 | tooltip: { 48 | x: { 49 | show: false 50 | }, 51 | y: { 52 | title: { 53 | formatter: function formatter() { 54 | return ""; 55 | } 56 | } 57 | } 58 | }, 59 | responsive: [ 60 | { 61 | breakpoint: 576, 62 | options: { 63 | chart: { 64 | height: 95 65 | }, 66 | grid: { 67 | padding: { 68 | top: 45, 69 | bottom: 0, 70 | left: 0 71 | } 72 | } 73 | } 74 | } 75 | ] 76 | }; 77 | const sparkSeries1 = [ 78 | { 79 | data: [21, 9, 36, 12, 44, 25, 59, 41, 66, 25] 80 | } 81 | ]; 82 | 83 | const sparkOptions2 = { 84 | chart: { 85 | id: "total-users", 86 | group: "sparks1", 87 | type: "line", 88 | height: 58, 89 | sparkline: { 90 | enabled: true 91 | }, 92 | dropShadow: { 93 | enabled: true, 94 | top: 1, 95 | left: 1, 96 | blur: 2, 97 | color: "#e2a03f", 98 | opacity: 0.7 99 | } 100 | }, 101 | stroke: { 102 | curve: "smooth", 103 | width: 2 104 | }, 105 | markers: { 106 | size: 0 107 | }, 108 | grid: { 109 | padding: { 110 | top: 0, 111 | bottom: 0, 112 | left: 0 113 | } 114 | }, 115 | colors: ["#e2a03f"], 116 | tooltip: { 117 | x: { 118 | show: false 119 | }, 120 | y: { 121 | title: { 122 | formatter: function formatter() { 123 | return ""; 124 | } 125 | } 126 | } 127 | }, 128 | responsive: [ 129 | { 130 | breakpoint: 576, 131 | options: { 132 | chart: { 133 | height: 95 134 | }, 135 | grid: { 136 | padding: { 137 | top: 35, 138 | bottom: 0, 139 | left: 0 140 | } 141 | } 142 | } 143 | } 144 | ] 145 | }; 146 | const sparkSeries2 = [ 147 | { 148 | data: [22, 19, 30, 47, 32, 44, 34, 55, 41, 69] 149 | } 150 | ]; 151 | 152 | return ( 153 |
154 |
155 |
156 |

Statistics

157 | 158 | 159 |
160 | 161 | 168 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 184 | View detail 185 | 186 | 187 | 188 | 189 | 193 | Download 194 | 195 | 196 | 197 | 198 |
199 |
200 |
201 | 202 |
203 |
204 |

Total transfer

205 |

338

206 | 207 | 213 |
214 | 215 |
216 |

Total of reception

217 |

393

218 | 219 | 225 |
226 |
227 |
228 | 229 |
230 | 306 | 307 |
308 |

309 | {formatToCurrency(expenses.value)} 310 |

311 |
312 |

313 | {expenses.period} 314 |

315 | 322 | 328 | 329 |
330 |
331 | 332 |
333 |
334 |
338 |
339 |
340 |
341 | 342 |

57%

343 |
344 |
345 | 346 |
347 |
351 |
352 |

353 | Total balance in your account 354 |

355 |
356 |

357 | {formatToCurrency(balance)} 358 |

359 |

360 | + 2453 361 |

362 |
363 |
364 | 365 |
366 |
367 | 383 | 384 | 400 |
401 | 402 |

Upgrade

403 |
404 |
405 |
406 | ); 407 | }; 408 | 409 | export default Analytics; 410 | -------------------------------------------------------------------------------- /public/js/wow.js: -------------------------------------------------------------------------------- 1 | /*! WOW wow.js - v1.3.0 - 2016-10-04 2 | * https://wowjs.uk 3 | * Copyright (c) 2016 Thomas Grainger; Licensed MIT */ !(function (a, b) { 4 | if ("function" == typeof define && define.amd) define(["module", "exports"], b); 5 | else if ("undefined" != typeof exports) b(module, exports); 6 | else { 7 | var c = { exports: {} }; 8 | b(c, c.exports), (a.WOW = c.exports); 9 | } 10 | })(this, function (a, b) { 11 | "use strict"; 12 | function c(a, b) { 13 | if (!(a instanceof b)) throw new TypeError("Cannot call a class as a function"); 14 | } 15 | function d(a, b) { 16 | return b.indexOf(a) >= 0; 17 | } 18 | function e(a, b) { 19 | for (var c in b) 20 | if (null == a[c]) { 21 | var d = b[c]; 22 | a[c] = d; 23 | } 24 | return a; 25 | } 26 | function f(a) { 27 | return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a); 28 | } 29 | function g(a) { 30 | var b = arguments.length <= 1 || void 0 === arguments[1] ? !1 : arguments[1], 31 | c = arguments.length <= 2 || void 0 === arguments[2] ? !1 : arguments[2], 32 | d = arguments.length <= 3 || void 0 === arguments[3] ? null : arguments[3], 33 | e = void 0; 34 | return ( 35 | null != document.createEvent 36 | ? ((e = document.createEvent("CustomEvent")), e.initCustomEvent(a, b, c, d)) 37 | : null != document.createEventObject 38 | ? ((e = document.createEventObject()), (e.eventType = a)) 39 | : (e.eventName = a), 40 | e 41 | ); 42 | } 43 | function h(a, b) { 44 | null != a.dispatchEvent 45 | ? a.dispatchEvent(b) 46 | : b in (null != a) 47 | ? a[b]() 48 | : "on" + b in (null != a) && a["on" + b](); 49 | } 50 | function i(a, b, c) { 51 | null != a.addEventListener 52 | ? a.addEventListener(b, c, !1) 53 | : null != a.attachEvent 54 | ? a.attachEvent("on" + b, c) 55 | : (a[b] = c); 56 | } 57 | function j(a, b, c) { 58 | null != a.removeEventListener 59 | ? a.removeEventListener(b, c, !1) 60 | : null != a.detachEvent 61 | ? a.detachEvent("on" + b, c) 62 | : delete a[b]; 63 | } 64 | function k() { 65 | return "innerHeight" in window ? window.innerHeight : document.documentElement.clientHeight; 66 | } 67 | Object.defineProperty(b, "__esModule", { value: !0 }); 68 | var l, 69 | m, 70 | n = (function () { 71 | function a(a, b) { 72 | for (var c = 0; c < b.length; c++) { 73 | var d = b[c]; 74 | (d.enumerable = d.enumerable || !1), 75 | (d.configurable = !0), 76 | "value" in d && (d.writable = !0), 77 | Object.defineProperty(a, d.key, d); 78 | } 79 | } 80 | return function (b, c, d) { 81 | return c && a(b.prototype, c), d && a(b, d), b; 82 | }; 83 | })(), 84 | o = 85 | window.WeakMap || 86 | window.MozWeakMap || 87 | (function () { 88 | function a() { 89 | c(this, a), (this.keys = []), (this.values = []); 90 | } 91 | return ( 92 | n(a, [ 93 | { 94 | key: "get", 95 | value: function (a) { 96 | for (var b = 0; b < this.keys.length; b++) { 97 | var c = this.keys[b]; 98 | if (c === a) return this.values[b]; 99 | } 100 | } 101 | }, 102 | { 103 | key: "set", 104 | value: function (a, b) { 105 | for (var c = 0; c < this.keys.length; c++) { 106 | var d = this.keys[c]; 107 | if (d === a) return (this.values[c] = b), this; 108 | } 109 | return this.keys.push(a), this.values.push(b), this; 110 | } 111 | } 112 | ]), 113 | a 114 | ); 115 | })(), 116 | p = 117 | window.MutationObserver || 118 | window.WebkitMutationObserver || 119 | window.MozMutationObserver || 120 | ((m = l = 121 | (function () { 122 | function a() { 123 | c(this, a), 124 | "undefined" != typeof console && 125 | null !== console && 126 | (console.warn("MutationObserver is not supported by your browser."), 127 | console.warn( 128 | "WOW.js cannot detect dom mutations, please call .sync() after loading new content." 129 | )); 130 | } 131 | return n(a, [{ key: "observe", value: function () {} }]), a; 132 | })()), 133 | (l.notSupported = !0), 134 | m), 135 | q = 136 | window.getComputedStyle || 137 | function (a) { 138 | var b = /(\-([a-z]){1})/g; 139 | return { 140 | getPropertyValue: function (c) { 141 | "float" === c && (c = "styleFloat"), 142 | b.test(c) && 143 | c.replace(b, function (a, b) { 144 | return b.toUpperCase(); 145 | }); 146 | var d = a.currentStyle; 147 | return (null != d ? d[c] : void 0) || null; 148 | } 149 | }; 150 | }, 151 | r = (function () { 152 | function a() { 153 | var b = arguments.length <= 0 || void 0 === arguments[0] ? {} : arguments[0]; 154 | c(this, a), 155 | (this.defaults = { 156 | boxClass: "wow", 157 | animateClass: "animated", 158 | offset: 0, 159 | mobile: !0, 160 | live: !0, 161 | callback: null, 162 | scrollContainer: null, 163 | resetAnimation: !0 164 | }), 165 | (this.animate = (function () { 166 | return "requestAnimationFrame" in window 167 | ? function (a) { 168 | return window.requestAnimationFrame(a); 169 | } 170 | : function (a) { 171 | return a(); 172 | }; 173 | })()), 174 | (this.vendors = ["moz", "webkit"]), 175 | (this.start = this.start.bind(this)), 176 | (this.resetAnimation = this.resetAnimation.bind(this)), 177 | (this.scrollHandler = this.scrollHandler.bind(this)), 178 | (this.scrollCallback = this.scrollCallback.bind(this)), 179 | (this.scrolled = !0), 180 | (this.config = e(b, this.defaults)), 181 | null != b.scrollContainer && 182 | (this.config.scrollContainer = document.querySelector(b.scrollContainer)), 183 | (this.animationNameCache = new o()), 184 | (this.wowEvent = g(this.config.boxClass)); 185 | } 186 | return ( 187 | n(a, [ 188 | { 189 | key: "init", 190 | value: function () { 191 | (this.element = window.document.documentElement), 192 | d(document.readyState, ["interactive", "complete"]) 193 | ? this.start() 194 | : i(document, "DOMContentLoaded", this.start), 195 | (this.finished = []); 196 | } 197 | }, 198 | { 199 | key: "start", 200 | value: function () { 201 | var a = this; 202 | if ( 203 | ((this.stopped = !1), 204 | (this.boxes = [].slice.call( 205 | this.element.querySelectorAll("." + this.config.boxClass) 206 | )), 207 | (this.all = this.boxes.slice(0)), 208 | this.boxes.length) 209 | ) 210 | if (this.disabled()) this.resetStyle(); 211 | else 212 | for (var b = 0; b < this.boxes.length; b++) { 213 | var c = this.boxes[b]; 214 | this.applyStyle(c, !0); 215 | } 216 | if ( 217 | (this.disabled() || 218 | (i( 219 | this.config.scrollContainer || window, 220 | "scroll", 221 | this.scrollHandler 222 | ), 223 | i(window, "resize", this.scrollHandler), 224 | (this.interval = setInterval(this.scrollCallback, 50))), 225 | this.config.live) 226 | ) { 227 | var d = new p(function (b) { 228 | for (var c = 0; c < b.length; c++) 229 | for (var d = b[c], e = 0; e < d.addedNodes.length; e++) { 230 | var f = d.addedNodes[e]; 231 | a.doSync(f); 232 | } 233 | }); 234 | d.observe(document.body, { childList: !0, subtree: !0 }); 235 | } 236 | } 237 | }, 238 | { 239 | key: "stop", 240 | value: function () { 241 | (this.stopped = !0), 242 | j( 243 | this.config.scrollContainer || window, 244 | "scroll", 245 | this.scrollHandler 246 | ), 247 | j(window, "resize", this.scrollHandler), 248 | null != this.interval && clearInterval(this.interval); 249 | } 250 | }, 251 | { 252 | key: "sync", 253 | value: function () { 254 | p.notSupported && this.doSync(this.element); 255 | } 256 | }, 257 | { 258 | key: "doSync", 259 | value: function (a) { 260 | if ( 261 | (("undefined" != typeof a && null !== a) || (a = this.element), 262 | 1 === a.nodeType) 263 | ) { 264 | a = a.parentNode || a; 265 | for ( 266 | var b = a.querySelectorAll("." + this.config.boxClass), c = 0; 267 | c < b.length; 268 | c++ 269 | ) { 270 | var e = b[c]; 271 | d(e, this.all) || 272 | (this.boxes.push(e), 273 | this.all.push(e), 274 | this.stopped || this.disabled() 275 | ? this.resetStyle() 276 | : this.applyStyle(e, !0), 277 | (this.scrolled = !0)); 278 | } 279 | } 280 | } 281 | }, 282 | { 283 | key: "show", 284 | value: function (a) { 285 | return ( 286 | this.applyStyle(a), 287 | (a.className = a.className + " " + this.config.animateClass), 288 | null != this.config.callback && this.config.callback(a), 289 | h(a, this.wowEvent), 290 | this.config.resetAnimation && 291 | (i(a, "animationend", this.resetAnimation), 292 | i(a, "oanimationend", this.resetAnimation), 293 | i(a, "webkitAnimationEnd", this.resetAnimation), 294 | i(a, "MSAnimationEnd", this.resetAnimation)), 295 | a 296 | ); 297 | } 298 | }, 299 | { 300 | key: "applyStyle", 301 | value: function (a, b) { 302 | var c = this, 303 | d = a.getAttribute("data-wow-duration"), 304 | e = a.getAttribute("data-wow-delay"), 305 | f = a.getAttribute("data-wow-iteration"); 306 | return this.animate(function () { 307 | return c.customStyle(a, b, d, e, f); 308 | }); 309 | } 310 | }, 311 | { 312 | key: "resetStyle", 313 | value: function () { 314 | for (var a = 0; a < this.boxes.length; a++) { 315 | var b = this.boxes[a]; 316 | b.style.visibility = "visible"; 317 | } 318 | } 319 | }, 320 | { 321 | key: "resetAnimation", 322 | value: function (a) { 323 | if (a.type.toLowerCase().indexOf("animationend") >= 0) { 324 | var b = a.target || a.srcElement; 325 | b.className = b.className 326 | .replace(this.config.animateClass, "") 327 | .trim(); 328 | } 329 | } 330 | }, 331 | { 332 | key: "customStyle", 333 | value: function (a, b, c, d, e) { 334 | return ( 335 | b && this.cacheAnimationName(a), 336 | (a.style.visibility = b ? "hidden" : "visible"), 337 | c && this.vendorSet(a.style, { animationDuration: c }), 338 | d && this.vendorSet(a.style, { animationDelay: d }), 339 | e && this.vendorSet(a.style, { animationIterationCount: e }), 340 | this.vendorSet(a.style, { 341 | animationName: b ? "none" : this.cachedAnimationName(a) 342 | }), 343 | a 344 | ); 345 | } 346 | }, 347 | { 348 | key: "vendorSet", 349 | value: function (a, b) { 350 | for (var c in b) 351 | if (b.hasOwnProperty(c)) { 352 | var d = b[c]; 353 | a["" + c] = d; 354 | for (var e = 0; e < this.vendors.length; e++) { 355 | var f = this.vendors[e]; 356 | a["" + f + c.charAt(0).toUpperCase() + c.substr(1)] = d; 357 | } 358 | } 359 | } 360 | }, 361 | { 362 | key: "vendorCSS", 363 | value: function (a, b) { 364 | for ( 365 | var c = q(a), d = c.getPropertyCSSValue(b), e = 0; 366 | e < this.vendors.length; 367 | e++ 368 | ) { 369 | var f = this.vendors[e]; 370 | d = d || c.getPropertyCSSValue("-" + f + "-" + b); 371 | } 372 | return d; 373 | } 374 | }, 375 | { 376 | key: "animationName", 377 | value: function (a) { 378 | var b = void 0; 379 | try { 380 | b = this.vendorCSS(a, "animation-name").cssText; 381 | } catch (c) { 382 | b = q(a).getPropertyValue("animation-name"); 383 | } 384 | return "none" === b ? "" : b; 385 | } 386 | }, 387 | { 388 | key: "cacheAnimationName", 389 | value: function (a) { 390 | return this.animationNameCache.set(a, this.animationName(a)); 391 | } 392 | }, 393 | { 394 | key: "cachedAnimationName", 395 | value: function (a) { 396 | return this.animationNameCache.get(a); 397 | } 398 | }, 399 | { 400 | key: "scrollHandler", 401 | value: function () { 402 | this.scrolled = !0; 403 | } 404 | }, 405 | { 406 | key: "scrollCallback", 407 | value: function () { 408 | if (this.scrolled) { 409 | this.scrolled = !1; 410 | for (var a = [], b = 0; b < this.boxes.length; b++) { 411 | var c = this.boxes[b]; 412 | if (c) { 413 | if (this.isVisible(c)) { 414 | this.show(c); 415 | continue; 416 | } 417 | a.push(c); 418 | } 419 | } 420 | (this.boxes = a), 421 | this.boxes.length || this.config.live || this.stop(); 422 | } 423 | } 424 | }, 425 | { 426 | key: "offsetTop", 427 | value: function (a) { 428 | for (; void 0 === a.offsetTop; ) a = a.parentNode; 429 | for (var b = a.offsetTop; a.offsetParent; ) 430 | (a = a.offsetParent), (b += a.offsetTop); 431 | return b; 432 | } 433 | }, 434 | { 435 | key: "isVisible", 436 | value: function (a) { 437 | var b = a.getAttribute("data-wow-offset") || this.config.offset, 438 | c = 439 | (this.config.scrollContainer && 440 | this.config.scrollContainer.scrollTop) || 441 | window.pageYOffset, 442 | d = c + Math.min(this.element.clientHeight, k()) - b, 443 | e = this.offsetTop(a), 444 | f = e + a.clientHeight; 445 | return d >= e && f >= c; 446 | } 447 | }, 448 | { 449 | key: "disabled", 450 | value: function () { 451 | return !this.config.mobile && f(navigator.userAgent); 452 | } 453 | } 454 | ]), 455 | a 456 | ); 457 | })(); 458 | (b["default"] = r), (a.exports = b["default"]); 459 | }); 460 | --------------------------------------------------------------------------------