├── .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 |
2 |
3 |
--------------------------------------------------------------------------------
/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 |
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 |
27 |
28 |
33 |
34 | Dashboard
35 |
36 |
37 |
38 |
39 |
44 |
45 | Reload Account
46 |
47 |
48 |
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 |
30 |
31 |
32 |
33 |
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 | You need to enable JavaScript to run this app.
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 |
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 | 
66 |
67 | **Register page**
68 | 
69 |
70 | **Dashboard page**
71 | 
72 |
73 | **Reload account page**
74 | 
--------------------------------------------------------------------------------
/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 |
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 |
20 | {label} {required && * }
21 |
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 |
71 |
76 | {label}
77 |
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 | {
75 | setIsOpen(false);
76 | setValidationMessage([]);
77 | setAmount("");
78 | setTimeout(() => {
79 | if (type !== null) {
80 | resetType();
81 | }
82 | }, 300);
83 | }}
84 | >
85 | Close
86 |
87 |
88 |
89 | {loading && }
90 | {type === null ? "Reload" : "Update"}
91 |
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 | onChangeNumberPerPage(e.target.value)}>
24 | 10
25 | 20
26 | 50
27 | 100
28 |
29 |
30 | {addButton &&
31 | (addButton.type === "link" ? (
32 |
38 | {addButton.text}
39 |
40 | ) : (
41 | <>
42 |
43 | {addButton.text}
44 |
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 |
84 | Delete
85 |
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 |
99 | {children}
100 |
101 | );
102 | case "secondary":
103 | return (
104 |
111 | {children}
112 |
113 | );
114 | case "outline":
115 | return (
116 |
125 | {children}
126 |
127 | );
128 | default:
129 | return (
130 |
137 | {children}
138 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
236 | {loading && }
237 | Get started
238 |
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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | +
21 |
22 |
Supported Currencies
23 |
24 |
25 |
26 |
27 | +
28 |
29 |
Available Countries
30 |
31 |
32 |
33 |
34 | +
35 |
36 |
Payment Methods
37 |
38 |
39 |
40 |
41 | / +
42 |
43 |
Support Team
44 |
45 |
46 |
47 |
48 |
49 |
Send money in a heartbeat
50 |
51 |
52 | The World At Your Fingertips
53 |
54 |
55 |
56 | Sign up to start saving on international money transfers and currency exchange.
57 |
58 |
59 |
60 |
61 |
62 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Peace of Mind
76 |
77 |
78 | Lorem ipsum dolor sit amet consectetur adipiscing elit posuere vel
79 | venenatis, eu sit massa. Volutpat massa rhoncus odio.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Business-Ready
89 |
90 |
91 | Lorem ipsum dolor sit amet consectetur adipiscing elit posuere vel
92 | venenatis, eu sit massa. Volutpat massa rhoncus odio.
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | 100% Transparent
102 |
103 |
104 | Lorem ipsum dolor sit amet consectetur adipiscing elit posuere vel
105 | venenatis, eu sit massa. Volutpat massa rhoncus odio.
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | International Network
115 |
116 |
117 | Lorem ipsum dolor sit amet consectetur adipiscing elit posuere vel
118 | venenatis, eu sit massa. Volutpat massa rhoncus odio.
119 |
120 |
121 |
122 |
123 |
124 |
128 |
129 | High speeds. Low fees. No hassle.
130 |
131 |
132 | All Your Payments In One Place
133 |
134 |
135 | Get used to low fees and great exchange rates on international money {" "}
136 | transfers.Expand your business worldwide
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
Payments
147 |
148 | Lorem Ipsum is simply dummy text of the printing and typesetting
149 | industry.
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | Collections
159 |
160 |
161 | Lorem Ipsum is simply dummy text of the printing and typesetting
162 | industry.
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | Conversions
172 |
173 |
174 | Lorem Ipsum is simply dummy text of the printing and typesetting
175 | industry.
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | Global Account
185 |
186 |
187 | Lorem Ipsum is simply dummy text of the printing and typesetting
188 | industry.
189 |
190 |
191 |
192 |
193 |
194 |
199 |
200 |
201 |
202 |
203 | App Download
204 |
205 |
206 |
207 | Fast, Secure Money Transfers
208 |
209 |
210 |
211 | Access your account via your mobile phone. View balance, transfer{" "}
212 | funds, view transactions wherever you are.
213 |
214 |
215 |
216 |
217 |
218 |
219 |
Login with fingerprint or Face ID.
220 |
221 |
222 |
223 |
224 |
Simple few Taps to send money.
225 |
226 |
227 |
228 |
229 |
View transaction history.
230 |
231 |
232 |
233 |
234 |
Get instant App notifications.
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
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 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | AtomPay
30 |
31 |
32 |
33 | React
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
45 | Personal
46 |
47 |
51 | Business
52 |
53 |
57 | How it work
58 |
59 |
63 | About us
64 |
65 |
66 |
67 |
68 | {checkUser() ? (
69 |
70 |
71 |
72 |
77 |
78 |
79 |
87 |
88 |
148 |
149 |
150 |
151 |
152 | ) : (
153 |
154 |
158 | Login
159 |
160 |
161 |
165 | Sign Up
166 |
167 |
168 | )}
169 |
170 | {!checkUser() && (
171 |
172 |
173 |
174 |
175 |
176 |
177 |
186 |
187 |
188 |
189 |
193 | Personal
194 |
195 |
196 |
197 |
198 |
202 | Business
203 |
204 |
205 |
206 |
207 |
211 | How it work
212 |
213 |
214 |
215 |
216 |
220 | About us
221 |
222 |
223 |
224 |
225 |
229 | Personal
230 |
231 |
232 |
233 |
234 |
235 |
239 | Login
240 |
241 |
242 |
243 |
244 |
248 | Sign Up
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 | )}
258 |
259 |
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 |
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 |
231 |
Expenses
232 |
233 |
234 |
304 |
305 |
306 |
307 |
308 |
309 | {formatToCurrency(expenses.value)}
310 |
311 |
312 |
313 | {expenses.period}
314 |
315 |
322 |
328 |
329 |
330 |
331 |
332 |
333 |
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 |
368 |
375 |
381 |
382 |
383 |
384 |
385 |
392 |
398 |
399 |
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 |
--------------------------------------------------------------------------------