diff --git a/next-i18next.config.js b/next-i18next.config.js
index 89fdf501..24f31bd7 100644
--- a/next-i18next.config.js
+++ b/next-i18next.config.js
@@ -1,5 +1,3 @@
-// const path = require('path')
-
module.exports = {
i18n: {
defaultLocale: 'es',
diff --git a/package-lock.json b/package-lock.json
index 0f28476a..29f17d1f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,6 +31,8 @@
"framer-motion": "^9.0.2",
"gsap": "^3.11.4",
"jimp": "^0.22.8",
+ "moment": "^2.29.4",
+ "moment-timezone": "^0.5.43",
"mongodb": "^5.5.0",
"nes.css": "^2.3.0",
"next": "13.0.1",
@@ -19476,6 +19478,25 @@
"node": ">=10"
}
},
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.43",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
+ "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/mongodb": {
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.1.tgz",
diff --git a/package.json b/package.json
index 1b5d34d5..cbf973f0 100644
--- a/package.json
+++ b/package.json
@@ -38,6 +38,7 @@
"@web3modal/ethereum": "^2.0.0-beta.8",
"@web3modal/react": "^2.0.0-beta.8",
"axios": "1.6.0",
+ "book-flip": "^1.0.0",
"dotenv": "^16.3.1",
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
@@ -45,11 +46,12 @@
"framer-motion": "^9.0.2",
"gsap": "^3.11.4",
"jimp": "^0.22.8",
+ "moment": "^2.29.4",
+ "moment-timezone": "^0.5.43",
"mongodb": "^5.5.0",
"nes.css": "^2.3.0",
"next": "13.0.1",
"next-i18next": "^15.0.0",
- "book-flip": "^1.0.0",
"prettier": "^3.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
diff --git a/public/images/footer/arrow_left.png b/public/images/footer/arrow_left.png
new file mode 100644
index 00000000..fdbdc553
Binary files /dev/null and b/public/images/footer/arrow_left.png differ
diff --git a/public/images/footer/arrow_right.png b/public/images/footer/arrow_right.png
new file mode 100644
index 00000000..85e59ba5
Binary files /dev/null and b/public/images/footer/arrow_right.png differ
diff --git a/public/images/gamma/GammaFondo-libreta.png b/public/images/gamma/GammaFondo-libreta.png
index 4209fb4f..dcdcb33e 100644
Binary files a/public/images/gamma/GammaFondo-libreta.png and b/public/images/gamma/GammaFondo-libreta.png differ
diff --git a/public/images/notifications/message.png b/public/images/notifications/message.png
new file mode 100644
index 00000000..792c869b
Binary files /dev/null and b/public/images/notifications/message.png differ
diff --git a/public/images/notifications/message2.png b/public/images/notifications/message2.png
new file mode 100644
index 00000000..949760d9
Binary files /dev/null and b/public/images/notifications/message2.png differ
diff --git a/public/locales/br/common.json b/public/locales/br/common.json
index 6db692d3..954d860b 100644
--- a/public/locales/br/common.json
+++ b/public/locales/br/common.json
@@ -149,5 +149,17 @@
"account_send_dai_title": "Transferência de token",
"quantity": "Cantidad",
"quantity_invalid": "Cantidad Inválida.",
- "account_send_dai_error": "Ocorreu um erro ao tentar transferir tokens."
+ "account_send_dai_error": "Ocorreu um erro ao tentar transferir tokens.",
+
+ "notification_title": "Notificações",
+ "notification_no_messages": "Você não tem mensagens",
+ "notification_view_all": "Ver tudo",
+ "notification_read": "Mensagem lida",
+ "notification_deleted": "Mensagem excluída",
+ "notification_all_read": "Mensagens lidas",
+ "notification_all_deleted": "Mensagens excluídas",
+ "notification_received_card": "Você recebeu a carta {CARD} de {WALLET}.",
+ "notification_sent_card": "Você enviou a carta {CARD} para {WALLET}.",
+ "notification_pack_transfer": "Você recebeu o pacote {PACK} de {WALLET}.",
+ "notification_exchange": "Você recebeu o cartão {CARD_RECEIVED} de {WALLET} em troca do cartão {CARD_SENT}."
}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 30a4efdf..5f847762 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -149,5 +149,18 @@
"account_send_dai_title": "Token Transfer",
"quantity": "Quantity",
"quantity_invalid": "Invalid Quantity.",
- "account_send_dai_error": "There was an error trying to transfer tokens."
+ "account_send_dai_error": "There was an error trying to transfer tokens.",
+
+ "notification_title": "Notifications",
+ "notification_no_messages": "No Messages",
+ "notification_view_all": "View All",
+ "notification_read": "Message Read",
+ "notification_deleted": "Message Deleted",
+ "notification_all_read": "Messages read",
+ "notification_all_deleted": "Deleted messages",
+ "notification_received_card": "You received the letter {CARD} from {WALLET}.",
+ "notification_sent_card": "You sent the letter {CARD} to {WALLET}.",
+ "notification_pack_transfer": "You received the pack {PACK} from {WALLET}.",
+ "notification_exchange": "You received the card {CARD_RECEIVED} from {WALLET} in exchange for the card {CARD_SENT}."
+
}
\ No newline at end of file
diff --git a/public/locales/es/common.json b/public/locales/es/common.json
index 7200fb12..43c94dbe 100644
--- a/public/locales/es/common.json
+++ b/public/locales/es/common.json
@@ -150,5 +150,17 @@
"account_send_dai_title": "Transferencia de Tokens",
"quantity": "Cantidad",
"quantity_invalid": "Cantidad Inválida.",
- "account_send_dai_error": "Ocurrió un error al intentar transferir tokens."
+ "account_send_dai_error": "Ocurrió un error al intentar transferir tokens.",
+
+ "notification_title": "Notificaciones",
+ "notification_no_messages": "No tienes mensajes",
+ "notification_view_all": "Ver todo",
+ "notification_read": "Mensaje leído",
+ "notification_deleted": "Mensaje eliminado",
+ "notification_all_read": "Mensajes leídos",
+ "notification_all_deleted": "Mensajes eliminados",
+ "notification_received_card": "Has recibido la carta {CARD} de {WALLET}.",
+ "notification_sent_card": "Enviaste la carta {CARD} a {WALLET}.",
+ "notification_pack_transfer": "Recibiste el pack {PACK} de {WALLET}.",
+ "notification_exchange": "Recibiste la carta {CARD_RECEIVED} de {WALLET} a cambio de la carta {CARD_SENT}."
}
\ No newline at end of file
diff --git a/src/components/FlipBook/FlipBook.jsx b/src/components/FlipBook/FlipBook.jsx
index acbeb6c2..28eafb1d 100644
--- a/src/components/FlipBook/FlipBook.jsx
+++ b/src/components/FlipBook/FlipBook.jsx
@@ -12,7 +12,7 @@ const FlipBook = (props) => {
mainClassName = 'hero__top__album',
disableFlipByClick = true
} = props
- const { windowSize, bookRef } = useLayoutContext()
+ const { windowSize, bookRef, turnPrevPage } = useLayoutContext()
const [isClassesReplaced, setIsClassesReplaced] = useState(false)
const CloseButton = () => (
@@ -33,57 +33,69 @@ const FlipBook = (props) => {
}, [])
return (
-
-
-
- {pages.map((content, index) => (
-
+
+
+ {/* just a hack to allow click-left in book required by flip-book issue */}
+
turnPrevPage()}
+ className='hero__top__album__book__button__top__hook'
+ />
+
+ {pages.map((content, index) => (
+
-
- {index % 2 === 0 ? (
-
{content}
- ) : (
-
- {showClose && }
- {content}
-
- )}
+ }
+ data-density='hard'
+ number={index + 1}
+ >
+
+ {index % 2 === 0 ? (
+ {content}
+ ) : (
+
+ {showClose && }
+ {content}
+
+ )}
+
-
- ))}
-
+ ))}
+
+ {/* just a hack to allow click-left in book required by flip-book issue */}
+
turnPrevPage()}
+ className='hero__top__album__book__button__bottom__hook'
+ />
+
-
+
)
}
diff --git a/src/components/Navbar/AccountInfo.jsx b/src/components/Navbar/AccountInfo.jsx
index 709ca96f..3013ba44 100644
--- a/src/components/Navbar/AccountInfo.jsx
+++ b/src/components/Navbar/AccountInfo.jsx
@@ -12,6 +12,7 @@ import { NETWORK, CONTRACTS } from '../../config'
import { getBalance, getTokenName, transfer } from '../../services/dai'
import { emitError, emitInfo, emitSuccess } from '../../utils/alert'
import { checkInputAddress, checkFloatValue1GTValue2 } from '../../utils/InputValidators'
+import { getAccountAddressText } from '../../utils/stringUtils'
const AccountInfo = ({ showAccountInfo, setShowAccountInfo }) => {
const { t } = useTranslation()
@@ -33,7 +34,7 @@ const AccountInfo = ({ showAccountInfo, setShowAccountInfo }) => {
useEffect(() => {
setValidNetwork(isValidNetwork())
- }, [showAccountInfo, chainId])
+ }, [showAccountInfo, chainId]) //eslint-disable-line react-hooks/exhaustive-deps
const fetchTokenName = async () => {
if (!walletAddress || !daiContract || !validNetwork) return
@@ -63,16 +64,6 @@ const AccountInfo = ({ showAccountInfo, setShowAccountInfo }) => {
fetchBalance()
}, [showAccountInfo, walletBalance, walletAddress, validNetwork]) //eslint-disable-line react-hooks/exhaustive-deps
- const getAccountAddressText = () => {
- if (walletAddress <= 15) {
- return walletAddress
- } else {
- const firstPart = walletAddress.substring(0, 7)
- const lastPart = walletAddress.substring(walletAddress.length - 5)
- return `${firstPart}...${lastPart}`
- }
- }
-
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
@@ -216,7 +207,7 @@ const AccountInfo = ({ showAccountInfo, setShowAccountInfo }) => {
)}
- {getAccountAddressText()}
+ {getAccountAddressText(walletAddress)}
diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx
index 79811303..64ec8047 100644
--- a/src/components/Navbar/Navbar.jsx
+++ b/src/components/Navbar/Navbar.jsx
@@ -7,8 +7,9 @@ import { useRouter } from 'next/router'
import Whitepaper from './Whitepaper.jsx'
import NofTown from './NofTown.jsx'
import AccountInfo from './AccountInfo.jsx'
+import NotificationInfo from './NotificationInfo.jsx'
import LanguageSelection from '../LanguageSelection'
-import { useLayoutContext } from '../../hooks'
+import { useLayoutContext, useWeb3Context, useNotificationContext } from '../../hooks'
function Navbar() {
const { t } = useTranslation()
@@ -18,7 +19,14 @@ function Navbar() {
const router = useRouter()
const isHomePage = router.pathname === '/'
const [showAccountInfo, setShowAccountInfo] = useState(false)
+ const [showNotificationInfo, setShowNotificationInfo] = useState(false)
+ const [notificationsNbr, setNotificationsNbr] = useState(0)
+ const [notificationsNbrClass, setNotificationsNbrClass] = useState('notification__badge__1')
+ const { notifications, getNotificationsByUser } = useNotificationContext()
+ const { walletAddress } = useWeb3Context()
+
const accountRef = useRef(null)
+ const notificationRef = useRef(null)
const handleAudioClick = () => {
setClick(!click)
@@ -29,6 +37,10 @@ function Navbar() {
}
}
+ const handleNotificationClick = () => {
+ setShowNotificationInfo(!showNotificationInfo)
+ }
+
const handleAccountClick = () => {
setShowAccountInfo(!showAccountInfo)
}
@@ -37,8 +49,22 @@ function Navbar() {
if (accountRef.current && !accountRef.current.contains(event.target)) {
setShowAccountInfo(false)
}
+ if (notificationRef.current && !notificationRef.current.contains(event.target)) {
+ setShowNotificationInfo(false)
+ }
}
+ useEffect(() => {
+ const notif = getNotificationsByUser(walletAddress) || []
+ const unreadNotifications = notif.filter((notification) => !notification.read)
+ setNotificationsNbr(unreadNotifications.length)
+ setNotificationsNbrClass(
+ unreadNotifications.length > 9 ? 'notification__badge__2' : 'notification__badge__1'
+ )
+ // setNotificationsNbr(20)
+ // setNotificationsNbrClass(20 > 9 ? 'notification__badge__2' : 'notification__badge__1')
+ }, [notifications, walletAddress]) //eslint-disable-line react-hooks/exhaustive-deps
+
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside)
return () => {
@@ -76,8 +102,17 @@ function Navbar() {
)
+ const ButtonNotification = () => (
+
+ handleNotificationClick()} className='navbar__right__notif'>
+ {notificationsNbr > 0 &&
{notificationsNbr}
}
+
+
+
+ )
+
const ButtonAccount = () => (
-
handleAccountClick()} className='navbar__right__coin'>
+
handleAccountClick()} className='navbar__right__account'>
)
@@ -98,6 +133,13 @@ function Navbar() {
+
+
+
+
{
+ const { t } = useTranslation()
+ const {
+ notifications,
+ getNotificationsByUser,
+ readNotification,
+ deleteNotification,
+ readAllNotifications,
+ deleteAllNotifications
+ } = useNotificationContext()
+ const { walletAddress } = useWeb3Context()
+
+ const [updatedNotifications, setUpdatedNotifications] = useState([])
+ const [actionText, setActionText] = useState('')
+ const [actionTextVisible, setActionTextVisible] = useState(false)
+ const [actionTextPosition, setActionTextPosition] = useState(0)
+ const { languageSetted } = useSettingsContext()
+
+ useEffect(() => {
+ setUpdatedNotifications(getNotificationsByUser(walletAddress))
+ }, [showNotificationInfo, notifications, walletAddress, languageSetted]) //eslint-disable-line react-hooks/exhaustive-deps
+
+ const formatNotificationDate = (date) => {
+ moment.locale(languageSetted)
+ return moment(date).fromNow()
+ }
+
+ const translateNotificationMessage = (message, data, short = true) => {
+ let newMessage = t(message)
+ data.forEach((element) => {
+ const regex = new RegExp(`\\{${element.item}\\}`, 'g')
+ if (short) newMessage = newMessage.replaceAll(regex, element.valueShort)
+ else newMessage = newMessage.replaceAll(regex, element.value)
+ })
+ return newMessage
+ }
+
+ const existsUnreadNotifications = () => {
+ if (!updatedNotifications || updatedNotifications.length === 0) {
+ return false
+ }
+ return updatedNotifications.some((notification) => !notification.read)
+ }
+
+ const handleCopy = (notification, event) => {
+ navigator.clipboard.writeText(notification)
+ setActionText(t('account_text_copied'))
+ setActionTextPosition(event.clientY - 70)
+ setActionTextVisible(true)
+ setTimeout(() => {
+ setActionTextVisible(false)
+ }, 1500)
+ }
+
+ const handleRead = (notification, event) => {
+ readNotification(notification.id)
+ setActionText(t('notification_read'))
+ setActionTextPosition(event.clientY - 70)
+ setActionTextVisible(true)
+ setTimeout(() => {
+ setActionTextVisible(false)
+ }, 1500)
+ }
+
+ const handleDelete = (notification, event) => {
+ setActionTextPosition(event.clientY - 70)
+ setActionText(t('notification_deleted'))
+ setActionTextVisible(true)
+ setTimeout(() => {
+ setActionTextVisible(false)
+ }, 1500)
+ deleteNotification(notification.id)
+ }
+
+ const handleReadAll = (event) => {
+ readAllNotifications(walletAddress)
+ setActionTextPosition(event.clientY - 70)
+ setActionText(t('notification_all_read'))
+ setActionTextVisible(true)
+ setTimeout(() => {
+ setActionTextVisible(false)
+ }, 1500)
+ }
+
+ const handleDeleteAll = (event) => {
+ setActionTextPosition(event.clientY - 70)
+ setActionText(t('notification_all_deleted'))
+ setActionTextVisible(true)
+ setTimeout(() => {
+ setActionTextVisible(false)
+ }, 1500)
+ deleteAllNotifications(walletAddress)
+ }
+
+ const NotificationTitle = () =>
+ updatedNotifications && updatedNotifications.length > 0 ? (
+
+
+
{t('notification_title')}
+
+
+ {!existsUnreadNotifications() ? (
+
+ ) : (
+ {
+ handleReadAll(event)
+ }}
+ />
+ )}
+
+
+ {
+ handleDeleteAll(event)
+ }}
+ />
+
+
+ ) : (
+
+
+
{t('notification_title')}
+
+
+ )
+
+ const NotificationMessage = ({ notification }) => (
+
+
+
+
{
+ handleCopy(
+ translateNotificationMessage(notification.message, notification.data, false),
+ event
+ )
+ }}
+ >
+ {translateNotificationMessage(notification.message, notification.data, true)}
+
+ {actionTextVisible && (
+
+ {actionText}
+
+ )}
+
+
+ {notification.read ? (
+
+ ) : (
+ {
+ handleRead(notification, event)
+ }}
+ />
+ )}
+
+
+ {
+ handleDelete(notification, event)
+ }}
+ />
+
+
+
+
({formatNotificationDate(notification.date)})
+
+ {/*
*/}
+
+ )
+
+ const NotificationMessages = () => (
+
+ {updatedNotifications.slice(0, 7).map((notification, index) => (
+
+ ))}
+ {/*
+
+ {t('notification_view_all')}
+ */}
+
+ )
+
+ const NoMessages = () => (
+
+
+
{t('notification_no_messages')}
+
+
+ )
+
+ NotificationMessage.propTypes = {
+ notification: PropTypes.object
+ }
+
+ return (
+
+
+
+
+
+ {walletAddress && updatedNotifications && updatedNotifications.length > 0 ? (
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
+
+NotificationInfo.propTypes = {
+ showNotificationInfo: PropTypes.func
+}
+
+export default NotificationInfo
diff --git a/src/context/NotificationContext.js b/src/context/NotificationContext.js
new file mode 100644
index 00000000..f5f5ffdf
--- /dev/null
+++ b/src/context/NotificationContext.js
@@ -0,0 +1,87 @@
+import { createContext, useState, useEffect, useContext } from 'react'
+import PropTypes from 'prop-types'
+import { v4 as uuidv4 } from 'uuid'
+import { Web3Context } from './Web3Context'
+
+export const NotificationContext = createContext()
+
+export const NotificationProvider = ({ children }) => {
+ const [notifications, setNotifications] = useState([])
+ const { walletAddress } = useContext(Web3Context)
+
+ const getNotificationsByUser = (user) => {
+ if (!user) return notifications
+ return notifications.filter(
+ (notification) => notification.walletAddress === user && notification.deleted === false
+ )
+ }
+
+ useEffect(() => {
+ const filteredNotifications = getNotificationsByUser(walletAddress)
+ setNotifications(filteredNotifications)
+ }, [walletAddress]) //eslint-disable-line react-hooks/exhaustive-deps
+
+ const addNotification = (user, message, data) => {
+ const date = new Date().toLocaleString()
+ const newNotification = {
+ id: uuidv4(),
+ date: date,
+ walletAddress: user,
+ message: message,
+ data: data || [],
+ read: false,
+ deleted: false
+ }
+
+ setNotifications((prevNotifications) => {
+ const updatedNotifications = [...prevNotifications, newNotification]
+ return updatedNotifications.sort((a, b) => new Date(b.date) - new Date(a.date))
+ })
+ }
+
+ const readNotification = (notificationId) => {
+ const updatedNotifs = notifications.map((notification) =>
+ notification.id === notificationId ? { ...notification, read: true } : notification
+ )
+ setNotifications(updatedNotifs)
+ }
+
+ const deleteNotification = (notificationId) => {
+ const updatedNotifs = notifications.filter((notification) => notification.id !== notificationId)
+ setNotifications(updatedNotifs)
+ }
+
+ const readAllNotifications = (user) => {
+ const updatedNotifs = notifications.map((notification) =>
+ notification.walletAddress === user ? { ...notification, read: true } : notification
+ )
+ setNotifications(updatedNotifs)
+ }
+
+ const deleteAllNotifications = (user) => {
+ const updatedNotifs = notifications.filter(
+ (notification) => notification.walletAddress !== user
+ )
+ setNotifications(updatedNotifs)
+ }
+
+ NotificationProvider.propTypes = {
+ children: PropTypes.node.isRequired
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/context/SettingsContext.js b/src/context/SettingsContext.js
index 46c26280..30171d2c 100644
--- a/src/context/SettingsContext.js
+++ b/src/context/SettingsContext.js
@@ -1,8 +1,12 @@
import PropTypes from 'prop-types'
-import { createContext } from 'react'
+import { createContext, useEffect } from 'react'
import { useLocalStorage } from '../hooks'
import getLanguagePresets, { languagePresets } from '../utils/getLanguagePresets'
import { defaultSettings } from '../config'
+import moment from 'moment'
+import spanishLocalization from 'moment/locale/es'
+import englishLocalization from 'moment/locale/en-gb'
+import portugueseLocalization from 'moment/locale/pt-br'
// ----------------------------------------------------------------------
@@ -25,6 +29,12 @@ function SettingsProvider({ children }) {
...defaultSettings
})
+ useEffect(() => {
+ moment.updateLocale('es', spanishLocalization)
+ moment.updateLocale('en-gb', englishLocalization)
+ moment.updateLocale('pt-br', portugueseLocalization)
+ }, [])
+
const onToggleLanguageSetted = (newLng = 'es') => {
const getUrl = window.location
const urlEN = getUrl.pathname.includes('/en/') || getUrl.pathname.includes('/en')
@@ -45,6 +55,10 @@ function SettingsProvider({ children }) {
languageSetted: newLng
})
+ if (newLng === 'en') moment.locale('en-gb')
+ else if (newLng === 'br') moment.locale('pt-br')
+ else moment.locale(newLng)
+
const getUrl = window.location
const pathName = getUrl.pathname
.replace('/en', '/')
@@ -69,6 +83,7 @@ function SettingsProvider({ children }) {
{}
@@ -26,6 +28,7 @@ function Web3ContextProvider({ children }) {
const [gammaPacksContract, setGammaPacksContract] = useState(null)
const [gammaCardsContract, setGammaCardsContract] = useState(null)
const [gammaOffersContract, setGammaOffersContract] = useState(null)
+ const { addNotification } = useContext(NotificationContext)
async function requestAccount() {
const web3Modal = new Web3Modal()
@@ -34,39 +37,32 @@ function Web3ContextProvider({ children }) {
try {
const connection = await web3Modal.connect()
web3Provider = new ethers.providers.Web3Provider(connection)
+ if (!web3Provider) return
+
+ const chain = (await web3Provider.getNetwork()).chainId
+ setChainId(decToHex(chain))
+ switchOrCreateNetwork(
+ NETWORK.chainId,
+ NETWORK.chainName,
+ NETWORK.ChainRpcUrl,
+ NETWORK.chainCurrency,
+ NETWORK.chainExplorerUrl
+ )
+
accountAddress = await web3Provider.getSigner().getAddress()
setWalletAddress(accountAddress)
+ connectContracts(web3Provider.getSigner())
+ return [web3Provider, accountAddress]
} catch (e) {
console.error({ e })
}
-
- if (!web3Provider) return
- const chain = (await web3Provider.getNetwork()).chainId
- setChainId(decToHex(chain))
- switchOrCreateNetwork(
- NETWORK.chainId,
- NETWORK.chainName,
- NETWORK.ChainRpcUrl,
- NETWORK.chainCurrency,
- NETWORK.chainExplorerUrl
- )
- return [web3Provider, accountAddress]
}
- function connectWallet() {
+ async function connectWallet() {
try {
if (window.ethereum !== undefined) {
setNoMetamaskError('')
-
- requestAccount()
- .then((data) => {
- const [provider] = data
- const signer = provider.getSigner()
- connectContracts(signer)
- })
- .catch((e) => {
- console.error({ e })
- })
+ await requestAccount()
} else {
setNoMetamaskError('Por favor instala Metamask para continuar.')
}
@@ -100,6 +96,27 @@ function Web3ContextProvider({ children }) {
_signer
)
+ gammaPacksContractInstance.on('PackTransfer', (from, to, tokenId) => {
+ const packNbr = ethers.BigNumber.from(tokenId).toNumber()
+ addNotification(to, 'notification_pack_transfer', [
+ { item: 'PACK', value: packNbr, valueShort: packNbr },
+ { item: 'WALLET', value: from, valueShort: getAccountAddressText(from) }
+ ])
+ })
+
+ gammaCardsContractInstance.on('ExchangeCardOffer', (from, to, cNFrom, cNTo) => {
+ addNotification(to, 'notification_exchange', [
+ { item: 'CARD_RECEIVED', value: cNFrom, valueShort: cNFrom },
+ { item: 'CARD_SENT', value: cNTo, valueShort: cNTo },
+ { item: 'WALLET', value: from, valueShort: getAccountAddressText(from) }
+ ])
+ addNotification(from, 'notification_exchange', [
+ { item: 'CARD_RECEIVED', value: cNTo, valueShort: cNTo },
+ { item: 'CARD_SENT', value: cNFrom, valueShort: cNFrom },
+ { item: 'WALLET', value: to, valueShort: getAccountAddressText(to) }
+ ])
+ })
+
setDaiContract(daiContractInstance)
setAlphaContract(alphaContractInstance)
setGammaPacksContract(gammaPacksContractInstance)
@@ -110,6 +127,25 @@ function Web3ContextProvider({ children }) {
}
}
+ /*
+ function subscribeContractsEvents(_signer) {
+ const wallet = _signer.getAddress()
+
+ if (!gammaPacksContract || !gammaCardsContract || !gammaOffersContract) return
+ gammaPacksContract.on('PacksPurchase', (_, theEvent) => {
+ for (let i = 0; i < theEvent.length; i++) {
+ const pack_number = ethers.BigNumber.from(theEvent[i]).toNumber()
+ addNotification('Pack purchase' + pack_number.toString())
+
+ }
+ })
+
+ gammaCardsContract.on('ExchangeCardOffer', (p1, p2, p3, p4) => {
+ // console.log('ExchangeCardOffer:', { p1, p2, p3, p4 })
+ })
+ }
+ */
+
function disconnectWallet() {
setWalletAddress(null)
}
diff --git a/src/hooks/index.js b/src/hooks/index.js
index 79495f00..13f9ee8d 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -2,3 +2,4 @@ export { default as useLocalStorage } from './useLocalStorage'
export { default as useSettingsContext } from './useSettingsContext'
export { default as useLayoutContext } from './useLayoutContext'
export { default as useWeb3Context } from './useWeb3Context'
+export { default as useNotificationContext } from './useNotificationContext'
diff --git a/src/hooks/useNotificationContext.js b/src/hooks/useNotificationContext.js
new file mode 100644
index 00000000..52b0880a
--- /dev/null
+++ b/src/hooks/useNotificationContext.js
@@ -0,0 +1,6 @@
+import { useContext } from 'react'
+import { NotificationContext } from '../context/NotificationContext'
+
+const useNotificationContext = () => useContext(NotificationContext)
+
+export default useNotificationContext
diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx
index 89e8ce9c..1694a301 100644
--- a/src/pages/_app.jsx
+++ b/src/pages/_app.jsx
@@ -7,19 +7,22 @@ import '../styles/common.scss'
import { appWithTranslation } from 'next-i18next'
// import { Web3ContextProvider } from '../context/Web3ContextNew'
import { Web3ContextProvider } from '../context/Web3Context'
+import { NotificationProvider } from '../context/NotificationContext'
import { SettingsProvider } from '../context/SettingsContext'
import { LayoutProvider } from '../context/LayoutContext'
import Layout from '../components/Layout'
function MyApp({ Component, pageProps }) {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
)
}
diff --git a/src/pages/api/match.js b/src/pages/api/match.js
new file mode 100644
index 00000000..d48828da
--- /dev/null
+++ b/src/pages/api/match.js
@@ -0,0 +1,71 @@
+import { ethers } from 'ethers'
+import { NETWORK, CONTRACTS } from '../../config'
+import gammaCardsAbi from '../../context/abis/GammaCards.v5.sol/NofGammaCardsV5.json'
+import { getCardsByUser } from '../../services/gamma'
+
+// example call: http://localhost:3000/api/match?w1=0x117b706DEF40310eF5926aB57868dAcf46605b8d&w2=0x35dad65F60c1A32c9895BE97f6bcE57D32792E83
+export default async function handler(req, res) {
+ try {
+ const { w1, w2 } = req.query
+
+ if (!w1 || !w2) {
+ res.status(400).json({
+ error: 'Please provide the wallet addresses (w1 and w2) as query parameters'
+ })
+ return
+ }
+
+ const provider = new ethers.providers.JsonRpcProvider(NETWORK.chainNodeProviderUrl)
+ const gammaCardsContractInstance = new ethers.Contract(
+ CONTRACTS.gammaCardsAddress,
+ gammaCardsAbi.abi,
+ provider
+ )
+
+ // stamped = true && quantity > 1
+ const u1Cards = await getFilteredCards(gammaCardsContractInstance, w1)
+ const u2Cards = await getFilteredCards(gammaCardsContractInstance, w2)
+
+ // tiene user1 y no el user2 y viceversa
+ const u1NotInU2 = getNotPresentCards(u1Cards, u2Cards)
+ const u2NotInU1 = getNotPresentCards(u2Cards, u1Cards)
+
+ const match = Object.keys(u1NotInU2).length > 0 || Object.keys(u2NotInU1).length > 0
+
+ res.setHeader('Content-Type', 'application/json')
+ res.status(200).json({
+ user1: u1NotInU2,
+ user2: u2NotInU1,
+ match: match
+ })
+ } catch (error) {
+ console.error(error)
+ res.status(500).json({
+ error: 'An error occurred while processing the request.'
+ })
+ }
+}
+
+async function getFilteredCards(contractInstance, wallet) {
+ const userCards = (await getCardsByUser(contractInstance, wallet)).user
+
+ const filteredCards = Object.keys(userCards).reduce((filtered, key) => {
+ const card = userCards[key]
+ if (card.quantity > 1) {
+ filtered[key] = card
+ }
+ return filtered
+ }, {})
+
+ return filteredCards
+}
+
+function getNotPresentCards(userCards1, userCards2) {
+ const notPresentCards = {}
+ Object.keys(userCards1).forEach((key) => {
+ if (!userCards2[key]) {
+ notPresentCards[key] = userCards1[key]
+ }
+ })
+ return notPresentCards
+}
diff --git a/src/sections/Gamma/GammaMain.jsx b/src/sections/Gamma/GammaMain.jsx
index 60e9f450..8c327214 100644
--- a/src/sections/Gamma/GammaMain.jsx
+++ b/src/sections/Gamma/GammaMain.jsx
@@ -55,8 +55,7 @@ const GammaMain = () => {
const [showRules, setShowRules] = useState(false)
const [cardInfoOpened, setCardInfoOpened] = useState(false)
-
- const canCompleteAlbum120 = () => (cardsQtty >= 120 && albums120Qtty > 0)
+ const canCompleteAlbum120 = () => cardsQtty >= 120 && albums120Qtty > 0
const getCardsQtty = (paginationObj) => {
let total = 0
@@ -160,7 +159,8 @@ const GammaMain = () => {
numberOfPacks,
inventory,
cardInfoOpened
- ])
+ ]
+ )
useEffect(() => {
if (walletAddress && inventory) {
@@ -168,33 +168,45 @@ const GammaMain = () => {
}
}, [walletAddress, gammaPacksContract, numberOfPacks, inventory, cardInfoOpened]) //eslint-disable-line react-hooks/exhaustive-deps
- const handleFinishAlbum = useCallback(async () => {
- try {
- if (cardsQtty < 120) {
- emitInfo(t('finish_album_no_qtty'), 100000)
- return
- }
+ const handleFinishAlbum = useCallback(
+ async () => {
+ try {
+ if (cardsQtty < 120) {
+ emitInfo(t('finish_album_no_qtty'), 100000)
+ return
+ }
- if (albums120Qtty < 1) {
- emitInfo(t('finish_album_no_album'), 100000)
- return
- }
+ if (albums120Qtty < 1) {
+ emitInfo(t('finish_album_no_album'), 100000)
+ return
+ }
- startLoading()
- const result = await finishAlbum(gammaCardsContract, daiContract, walletAddress)
- if (result) {
- await updateUserData()
- emitSuccess(t('finish_album_success'))
- } else {
- emitWarning(t('finish_album_warning'), 8000, '', false)
+ startLoading()
+ const result = await finishAlbum(gammaCardsContract, daiContract, walletAddress)
+ if (result) {
+ await updateUserData()
+ emitSuccess(t('finish_album_success'))
+ } else {
+ emitWarning(t('finish_album_warning'), 8000, '', false)
+ }
+ stopLoading()
+ } catch (ex) {
+ stopLoading()
+ console.error({ ex })
+ emitError(t('finish_album_error'))
}
- stopLoading()
- } catch (ex) {
- stopLoading()
- console.error({ ex })
- emitError(t('finish_album_error'))
- }
- }, [walletAddress, gammaPacksContract, paginationObj, inventory, cardInfoOpened, cardsQtty, albums120Qtty]) //eslint-disable-line react-hooks/exhaustive-deps
+ },
+ // prettier-ignore
+ [ //eslint-disable-line react-hooks/exhaustive-deps
+ walletAddress,
+ gammaPacksContract,
+ paginationObj,
+ inventory,
+ cardInfoOpened,
+ cardsQtty,
+ albums120Qtty
+ ]
+ )
const handleTransferPack = useCallback(async () => {
try {
diff --git a/src/services/gamma.js b/src/services/gamma.js
index 522410f9..87f15818 100644
--- a/src/services/gamma.js
+++ b/src/services/gamma.js
@@ -94,7 +94,7 @@ export const getMaxPacksAllowedToOpenAtOnce = async (cardsContract) => {
export const getCardsByUser = async (cardsContract, walletAddress) => {
try {
- if(!cardsContract || !walletAddress) return
+ if (!cardsContract || !walletAddress) return
const cardData = await cardsContract?.getCardsByUser(walletAddress)
let cardsObj = { ...gammaCardsPages }
@@ -130,7 +130,7 @@ export const getCardsByUser = async (cardsContract, walletAddress) => {
export const hasCard = async (cardsContract, walletAddress, cardNumber) => {
try {
- if(!cardsContract || !walletAddress) return
+ if (!cardsContract || !walletAddress) return
const result = await cardsContract.hasCard(walletAddress, cardNumber)
return result
} catch (e) {
@@ -141,7 +141,7 @@ export const hasCard = async (cardsContract, walletAddress, cardNumber) => {
export const getPackPrice = async (cardsContract) => {
try {
- if(!cardsContract) return
+ if (!cardsContract) return
const price = await cardsContract.packPrice()
const result = ethers.utils.formatUnits(price, 18)
return result
@@ -153,7 +153,7 @@ export const getPackPrice = async (cardsContract) => {
export const getUserAlbums120Qtty = async (cardsContract, walletAddress) => {
try {
- if(!cardsContract || !walletAddress) return
+ if (!cardsContract || !walletAddress) return
const userHasAlbum = await cardsContract.cardsByUser(walletAddress, 120)
return userHasAlbum
} catch (e) {
@@ -162,10 +162,9 @@ export const getUserAlbums120Qtty = async (cardsContract, walletAddress) => {
}
}
-
export const finishAlbum = async (cardsContract, daiContract, walletAddress) => {
try {
- if(!cardsContract || !walletAddress) return
+ if (!cardsContract || !walletAddress) return
const result = await allowedToFinishAlbum(cardsContract, daiContract, walletAddress)
if (result) {
const transaction = await cardsContract.finishAlbum()
@@ -211,7 +210,7 @@ export const allowedToFinishAlbum = async (cardsContract, daiContract, walletAdd
// Las 4 se validan en el contrato y aquí (para evitar la llamada al contrato)
// require(cardsByUser[msg.sender][120] > 0, "No tienes ningun album");
- if(!cardsContract || !walletAddress) return
+ if (!cardsContract || !walletAddress) return
const userHasAlbum = await cardsContract.cardsByUser(walletAddress, 120)
const prizesBalance = await cardsContract.prizesBalance()
const mainAlbumPrize = await cardsContract.mainAlbumPrize()
diff --git a/src/styles/_hero.scss b/src/styles/_hero.scss
index 6efba4a8..74e74a77 100644
--- a/src/styles/_hero.scss
+++ b/src/styles/_hero.scss
@@ -488,7 +488,7 @@ $groundHeight: 40px;
@extend .hero__top__album;
background-image: url('/images/gamma/GammaFondo.png') !important;
@include mobile {
- background-image: url('/images/gamma/GammaFondo-libreta.png');
+ background-image: url('/images/gamma/GammaFondo-libreta.png') !important;
}
}
}
@@ -1105,3 +1105,33 @@ $groundHeight: 40px;
margin-top: 6%;
}
}
+
+.hero__top__album__book__button__bottom__hook {
+ position: absolute;
+ bottom: 30px;
+ left: 5px;
+ z-index: 999;
+}
+
+.hero__top__album__book__button__bottom__hook::before {
+ content: '';
+ position: absolute;
+ z-index: 1;
+ width: 0;
+ height: 0;
+ border-left: 30px solid transparent;
+ border-right: 30px solid transparent;
+ border-top: 20px solid transparent;
+ border-bottom: 20px solid transparent;
+}
+
+.hero__top__album__book__button__top__hook {
+ position: absolute;
+ top: 60px;
+ left: 0;
+ z-index: 999;
+}
+
+.hero__top__album__book__button__top__hook::before {
+ @extend .hero__top__album__book__button__bottom__hook;
+}
diff --git a/src/styles/_navbar-account.scss b/src/styles/_navbar-account.scss
index b65cfc1f..8a81ea07 100644
--- a/src/styles/_navbar-account.scss
+++ b/src/styles/_navbar-account.scss
@@ -85,10 +85,10 @@
background-color: rgba(0, 0, 0, 0.8);
padding: 5px 10px;
border-radius: 5px;
- animation: fadeInOut 5s ease-in-out;
+ animation: acccount__fadeInOut 5s ease-in-out;
}
-@keyframes fadeInOut {
+@keyframes acccount__fadeInOut {
0% {
opacity: 1;
}
@@ -106,7 +106,7 @@
justify-content: space-between;
}
-account__info__icon__link,
+.account__info__icon__link,
.account__info__icon {
vertical-align: middle;
margin-left: 5px;
diff --git a/src/styles/_navbar-notification.scss b/src/styles/_navbar-notification.scss
new file mode 100644
index 00000000..e3661d51
--- /dev/null
+++ b/src/styles/_navbar-notification.scss
@@ -0,0 +1,154 @@
+.notification__info {
+ position: absolute;
+ top: calc(100% + 0px);
+ right: 10px;
+ background-color: #cfa763;
+ filter: saturate(90%) brightness(120%);
+ border: 1px solid #ccc;
+ padding: 10px;
+ display: none;
+ font-family: 'Press Start 2P', 'Share Tech Mono', 'Share Tech', sans-serif;
+ font-size: 0.9em !important;
+ max-width: 450px;
+
+ @include mobile {
+ right: 5px;
+ left: 5px;
+ }
+}
+
+.notification__info.active,
+.notification__info:hover {
+ display: block;
+}
+
+.notification__info__data {
+ margin-bottom: 5px;
+}
+
+.notification__info__separator {
+ border: none;
+ border-top: 1px solid #ccc;
+ margin: 0;
+ margin-right: -10px;
+ margin-left: -10px;
+}
+
+.notification__info__title {
+ width: 100px;
+ margin-top: 5px;
+ line-height: 20px;
+}
+
+.notification__info__no__messages {
+ margin: 0;
+ margin-top: 20px;
+}
+
+.notification__info__notification__message,
+.notification__info__notification__message:hover,
+.notification__info__notification__message:visited {
+ white-space: wrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ transition: color 0.3s;
+ text-decoration: none;
+ line-height: 15px;
+}
+
+.notification__info__notification__message:hover {
+ color: #fff;
+}
+
+.notification__info__view__all {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ margin: 0;
+ margin-top: 20px;
+ font-size: 0.7em;
+}
+
+.notification__info__action__text {
+ position: absolute;
+ text-align: center;
+ left: 50%;
+ width: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 8px;
+ color: #fff;
+ background-color: rgba(0, 0, 0, 0.8);
+ padding: 5px 10px;
+ border-radius: 5px;
+ animation: notification__fadeInOut 5s ease-in-out;
+}
+
+@keyframes notification__fadeInOut {
+ 0% {
+ opacity: 1;
+ }
+ 90% {
+ opacity: 0.5;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+.notification__info__icon__and__link {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.notification__info__icon__link,
+.notification__info__icon,
+.notification__info__icon__read {
+ vertical-align: middle;
+ margin-left: 5px;
+ margin-right: 5px;
+ font-size: 17px;
+}
+
+.notification__info__icon__read {
+ cursor: auto;
+}
+
+.notification__info__icon__link {
+ fill: #000;
+}
+
+.notification__info__icon__link:hover {
+ fill: #fff;
+}
+
+.notification__info__icon__link:hover,
+.notification__info__icon:hover {
+ color: #fff;
+}
+
+.notification__info__icon__container {
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+}
+
+.notification__info__link__container {
+ width: 100%;
+ margin-right: 10px;
+}
+
+.notification__info__icon__container {
+ justify-content: flex-end;
+ margin-left: auto;
+}
+
+.notification__info__date {
+ width: 80%;
+ max-width: 80%;
+ margin-top: 0;
+ margin-bottom: 10px;
+ font-size: 8px;
+ color: #888;
+}
diff --git a/src/styles/_navbar.scss b/src/styles/_navbar.scss
index 40f0e0c2..8b954e67 100644
--- a/src/styles/_navbar.scss
+++ b/src/styles/_navbar.scss
@@ -85,22 +85,30 @@ header {
img {
position: relative;
}
- &:hover {
+ // el not evita el efecto de hover en las ventanas de account e info
+ &:hover > :not(.notification__info):not(.account__info) {
transform: scale(1.1);
transition: 0.2s;
}
}
&__coin {
- cursor: pointer;
- background-size: 100% 100%;
- img {
- position: relative;
- }
- &:hover {
- transform: scale(1.1);
- transition: 0.2s;
+ @extend .navbar__right__audio;
+ }
+ &__account {
+ @extend .navbar__right__audio;
+ }
+ &__notif {
+ @extend .navbar__right__audio;
+ margin-top: 15px !important;
+ position: relative;
+ @media (max-width: 768px) {
+ margin-top: 10px !important;
}
}
+ &__notif__badge {
+ @extend .navbar__right__audio;
+ position: relative;
+ }
}
&__left {
@@ -122,6 +130,29 @@ header {
}
}
+.notification__badge__1 {
+ position: absolute;
+ right: -15px;
+ top: -15px;
+ background-color: #3896ee;
+ color: white;
+ width: 25px;
+ height: 25px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 9px;
+ font-weight: bold;
+ z-index: 1;
+}
+
+.notification__badge__2 {
+ @extend .notification__badge__1;
+ width: 30px;
+ height: 30px;
+}
+
.corner__common {
display: flex;
align-items: center;
diff --git a/src/styles/gamma.scss b/src/styles/gamma.scss
index ab150900..fe8623a7 100644
--- a/src/styles/gamma.scss
+++ b/src/styles/gamma.scss
@@ -215,7 +215,7 @@
line-height: 0;
@include mobile {
- position: absolute;
+ position: absolute;
height: 0%;
width: 100px;
bottom: 4%;
diff --git a/src/styles/index.scss b/src/styles/index.scss
index 77f017d4..ea5ebc03 100644
--- a/src/styles/index.scss
+++ b/src/styles/index.scss
@@ -3,6 +3,7 @@
@import './variables';
@import './_navbar.scss';
@import './_navbar-account.scss';
+@import './_navbar-notification.scss';
@import './_footer.scss';
@import './_hero.scss';
diff --git a/src/utils/stringUtils.js b/src/utils/stringUtils.js
index d85fc04c..77e8f08b 100644
--- a/src/utils/stringUtils.js
+++ b/src/utils/stringUtils.js
@@ -1,2 +1,12 @@
export const capitalizeFirstLetter = (word) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
+
+export const getAccountAddressText = (walletAddress) => {
+ if (walletAddress <= 15 || !walletAddress) {
+ return walletAddress
+ } else {
+ const firstPart = walletAddress.substring(0, 7)
+ const lastPart = walletAddress.substring(walletAddress.length - 5)
+ return `${firstPart}...${lastPart}`
+ }
+}