diff --git a/.eslintignore b/.eslintignore
index 2770e2f..32b2fa8 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,4 +1,5 @@
node_modules
+src/vendor
dist
webpack.config.js
jest.config.js
diff --git a/.gitignore b/.gitignore
index 1437c53..0d18882 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,9 @@
/.pnp
.pnp.js
+# Typescript build
+tsconfig.tsbuildinfo
+
# testing
/coverage
diff --git a/next.config.js b/next.config.js
index 75d08e3..64ace7c 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,5 +1,33 @@
const { version } = require('./package.json')
+const isDev = process.env.NODE_ENV !== 'production'
+
+const securityHeaders = [
+ {
+ key: 'X-XSS-Protection',
+ value: '1; mode=block',
+ },
+ {
+ key: 'X-Frame-Options',
+ value: 'DENY',
+ },
+ {
+ key: 'X-Content-Type-Options',
+ value: 'nosniff',
+ },
+ {
+ key: 'Referrer-Policy',
+ value: 'strict-origin-when-cross-origin',
+ },
+ // TODO remove imgsrc githubcontent when use-ck updated with celo terminal icon
+ {
+ key: 'Content-Security-Policy',
+ value: `default-src 'self'; script-src 'self'${
+ isDev ? " 'unsafe-eval'" : ''
+ }; connect-src 'self' https://*.celo.org https://*.celo-testnet.org wss://walletconnect.celo.org wss://relay.walletconnect.org; img-src 'self' data: https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; base-uri 'self'; form-action 'self'`,
+ },
+]
+
module.exports = {
webpack: (config, { webpack }) => {
config.resolve.fallback = {
@@ -9,7 +37,7 @@ module.exports = {
child_process: false,
readline: false,
}
- config.plugins.push(new webpack.IgnorePlugin(/^electron$/))
+ config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /^electron$/ }))
return config
},
@@ -22,6 +50,15 @@ module.exports = {
]
},
+ async headers() {
+ return [
+ {
+ source: '/(.*)',
+ headers: securityHeaders,
+ },
+ ]
+ },
+
env: {
NEXT_PUBLIC_VERSION: version,
},
diff --git a/package.json b/package.json
index dcf5df4..46838cd 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,11 @@
{
"name": "@celo-tools/mento-fi",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "A simple DApp for Celo Mento exchanges",
"keywords": [
"Celo",
"Mento",
+ "Granda",
"Exchange"
],
"author": "J M Rossy",
@@ -12,7 +13,7 @@
"type": "git",
"url": "https://github.com/celo-tools/mento-fi"
},
- "homepage": "TODO",
+ "homepage": "https://mento.finance",
"license": "Apache-2.0",
"scripts": {
"dev": "next",
@@ -26,43 +27,45 @@
"dependencies": {
"@celo-tools/use-contractkit": "^1.3.0",
"@ethersproject/address": "^5.5.0",
+ "@metamask/inpage-provider": "6.0.1",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28",
"@reduxjs/toolkit": "^1.6.2",
"bignumber.js": "^9.0.1",
"formik": "^2.2.9",
"frappe-charts": "^1.6.2",
- "next": "11.1.2",
+ "next": "12.0.3",
"next-persist": "^1.2.4",
+ "post-message-stream": "3.0.0",
"react": "^17.0.2",
"react-accessible-dropdown-menu-hook": "^3.1.0",
"react-dom": "^17.0.2",
- "react-redux": "^7.2.5",
+ "react-redux": "^7.2.6",
"react-select": "4.3.1",
- "react-toastify": "^8.0.3"
+ "react-toastify": "^8.1.0"
},
"devDependencies": {
- "@testing-library/jest-dom": "^5.0.0",
+ "@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^12.1.2",
- "@testing-library/user-event": "^13.4.1",
+ "@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.0.2",
- "@types/node": "16.11.1",
- "@types/react": "17.0.30",
- "@types/react-dom": "17.0.9",
- "@types/react-redux": "7.1.19",
+ "@types/node": "16.11.7",
+ "@types/react": "17.0.34",
+ "@types/react-dom": "17.0.11",
+ "@types/react-redux": "7.1.20",
"@types/react-select": "4.0.18",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
- "autoprefixer": "^10.3.7",
+ "autoprefixer": "^10.4.0",
"eslint": "7.32.0",
- "eslint-config-next": "^11.1.2",
+ "eslint-config-next": "^12.0.3",
"eslint-config-prettier": "^8.3.0",
- "jest": "^27.2.5",
+ "jest": "^27.3.1",
"jest-css-modules-transform": "^4.3.0",
- "postcss": "^8.3.9",
+ "postcss": "^8.3.11",
"prettier": "^2.4.1",
- "tailwindcss": "^2.2.17",
+ "tailwindcss": "^2.2.19",
"ts-jest": "^27.0.7",
- "ts-node": "^10.3.0",
+ "ts-node": "^10.4.0",
"typescript": "4.4.4"
},
"resolutions": {
diff --git a/src/app/store.ts b/src/app/store.ts
index 9df3581..8033bd2 100644
--- a/src/app/store.ts
+++ b/src/app/store.ts
@@ -3,15 +3,17 @@ import { config } from 'src/config/config'
import { accountReducer } from 'src/features/accounts/accountSlice'
import { blockReducer } from 'src/features/blocks/blockSlice'
import { tokenPriceReducer } from 'src/features/chart/tokenPriceSlice'
+import { grandaReducer } from 'src/features/granda/grandaSlice'
import { swapReducer } from 'src/features/swap/swapSlice'
export function createStore() {
return configureStore({
reducer: {
account: accountReducer,
+ block: blockReducer,
+ granda: grandaReducer,
swap: swapReducer,
tokenPrice: tokenPriceReducer,
- block: blockReducer,
},
devTools: config.debug,
})
diff --git a/src/components/Chevron.tsx b/src/components/Chevron.tsx
new file mode 100644
index 0000000..5a4c0cb
--- /dev/null
+++ b/src/components/Chevron.tsx
@@ -0,0 +1,51 @@
+import { memo } from 'react'
+
+interface Props {
+ width?: string | number
+ height?: string | number
+ direction: 'n' | 'e' | 's' | 'w'
+ color?: string
+ classes?: string
+}
+
+function _ChevronIcon({ width, height, direction, color, classes }: Props) {
+ let className: string
+ switch (direction) {
+ case 'n':
+ className = 'rotate-180'
+ break
+ case 'e':
+ className = 'rotate-270'
+ break
+ case 's':
+ className = ''
+ break
+ case 'w':
+ className = 'rotate-90'
+ break
+ default:
+ throw new Error(`Invalid chevron direction ${direction}`)
+ }
+
+ return (
+
+ )
+}
+
+export const ChevronIcon = memo(_ChevronIcon)
diff --git a/src/components/TxSuccessToast.tsx b/src/components/TxSuccessToast.tsx
new file mode 100644
index 0000000..1a3d327
--- /dev/null
+++ b/src/components/TxSuccessToast.tsx
@@ -0,0 +1,27 @@
+import { toast } from 'react-toastify'
+import { TextLink } from 'src/components/buttons/TextLink'
+
+export function toastToYourSuccess(msg: string, txHash: string, blockscoutUrl: string) {
+ toast.success(, {
+ autoClose: 15000,
+ })
+}
+
+export function TxSuccessToast({
+ msg,
+ txHash,
+ blockscoutUrl,
+}: {
+ msg: string
+ txHash: string
+ blockscoutUrl: string
+}) {
+ return (
+
+ {msg + ' '}
+
+ See Details
+
+
+ )
+}
diff --git a/src/components/animation/Spinner.module.css b/src/components/animation/Spinner.module.css
new file mode 100644
index 0000000..83ca1ab
--- /dev/null
+++ b/src/components/animation/Spinner.module.css
@@ -0,0 +1,39 @@
+.spinner {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+ opacity: 0.8;
+}
+
+.spinner div {
+ box-sizing: border-box;
+ display: block;
+ position: absolute;
+ width: 64px;
+ height: 64px;
+ margin: 8px;
+ border: 8px solid #2e3338;
+ border-radius: 50%;
+ animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
+ border-color: #2e3338 transparent transparent transparent;
+}
+
+.spinner div:nth-of-type(1) {
+ animation-delay: -0.45s;
+}
+.spinner div:nth-of-type(2) {
+ animation-delay: -0.3s;
+}
+.spinner div:nth-of-type(3) {
+ animation-delay: -0.15s;
+}
+
+@keyframes lds-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/components/animation/Spinner.tsx b/src/components/animation/Spinner.tsx
new file mode 100644
index 0000000..b0b527d
--- /dev/null
+++ b/src/components/animation/Spinner.tsx
@@ -0,0 +1,16 @@
+import { memo } from 'react'
+import styles from 'src/components/animation/Spinner.module.css'
+
+// From https://loading.io/css/
+function _Spinner() {
+ return (
+
+ )
+}
+
+export const Spinner = memo(_Spinner)
diff --git a/src/components/buttons/BackButton.tsx b/src/components/buttons/BackButton.tsx
new file mode 100644
index 0000000..aa17b8f
--- /dev/null
+++ b/src/components/buttons/BackButton.tsx
@@ -0,0 +1,6 @@
+import { IconButton, IconButtonProps } from 'src/components/buttons/IconButton'
+import LeftArrow from 'src/images/icons/arrow-left-circle.svg'
+
+export function BackButton(props: IconButtonProps) {
+ return
+}
diff --git a/src/components/buttons/IconButton.tsx b/src/components/buttons/IconButton.tsx
index df71ac7..26dd84e 100644
--- a/src/components/buttons/IconButton.tsx
+++ b/src/components/buttons/IconButton.tsx
@@ -1,7 +1,7 @@
import Image from 'next/image'
import { PropsWithChildren } from 'react'
-interface ButtonProps {
+export interface IconButtonProps {
width: number
height: number
classes?: string
@@ -12,7 +12,7 @@ interface ButtonProps {
passThruProps?: any
}
-export function IconButton(props: PropsWithChildren) {
+export function IconButton(props: PropsWithChildren) {
const { width, height, classes, onClick, imgSrc, disabled, title, passThruProps } = props
const base = 'flex items-center justify-center transition-all'
diff --git a/src/components/buttons/RefreshButton.tsx b/src/components/buttons/RefreshButton.tsx
new file mode 100644
index 0000000..110f558
--- /dev/null
+++ b/src/components/buttons/RefreshButton.tsx
@@ -0,0 +1,6 @@
+import { IconButton, IconButtonProps } from 'src/components/buttons/IconButton'
+import RepeatArrow from 'src/images/icons/arrow-repeat.svg'
+
+export function RefreshButton(props: IconButtonProps) {
+ return
+}
diff --git a/src/components/buttons/SolidButton.tsx b/src/components/buttons/SolidButton.tsx
index 42395c9..d3e8845 100644
--- a/src/components/buttons/SolidButton.tsx
+++ b/src/components/buttons/SolidButton.tsx
@@ -4,7 +4,7 @@ interface ButtonProps {
size?: 'xs' | 's' | 'm' | 'l' | 'xl'
type?: 'submit' | 'reset' | 'button'
onClick?: () => void
- dark?: boolean // defaults to false
+ color?: 'white' | 'green' | 'red' // defaults to green
classes?: string
bold?: boolean
disabled?: boolean
@@ -14,16 +14,39 @@ interface ButtonProps {
}
export function SolidButton(props: PropsWithChildren) {
- const { size, type, onClick, dark, classes, bold, icon, disabled, title, passThruProps } = props
+ const {
+ size,
+ type,
+ onClick,
+ color: _color,
+ classes,
+ bold,
+ icon,
+ disabled,
+ title,
+ passThruProps,
+ } = props
+ const color = _color ?? 'green'
const base = 'flex items-center justify-center rounded-full transition-all duration-300'
const sizing = sizeToClasses(size)
- const colors = dark ? 'bg-green text-white' : 'bg-white text-black'
- const onHover = dark ? 'hover:bg-green-dark' : 'hover:bg-gray-50'
- const onDisabled = 'disabled:bg-gray-300 disabled:text-gray-300'
- const onActive = dark ? 'active:bg-green-darkest' : 'active:bg-gray-100'
+ let baseColors, onHover, onActive
+ if (color === 'green') {
+ baseColors = 'bg-green text-white'
+ onHover = 'hover:bg-green-dark'
+ onActive = 'active:bg-green-darkest'
+ } else if (color === 'red') {
+ baseColors = 'bg-red-600 text-white'
+ onHover = 'hover:bg-red-500'
+ onActive = 'active:bg-red-400'
+ } else if (color === 'white') {
+ baseColors = 'bg-white text-black'
+ onHover = 'hover:bg-gray-50'
+ onActive = 'active:bg-gray-100'
+ }
+ const onDisabled = 'disabled:bg-gray-300 disabled:text-gray-500'
const weight = bold ? 'font-semibold' : ''
- const allClasses = `${base} ${sizing} ${colors} ${onHover} ${onDisabled} ${onActive} ${weight} ${classes}`
+ const allClasses = `${base} ${sizing} ${baseColors} ${onHover} ${onDisabled} ${onActive} ${weight} ${classes}`
return (