import React, {
createContext,
forwardRef,
useCallback,
useContext,
useState,
} from 'react';
import Maybe from 'folktale/maybe';
import * as azimuth from 'azimuth-js';
import { useNetwork } from './network';
export const PointCacheContext = createContext(null);
// the default value of a point's invites
const kEmptyInvites = {
availableInvites: Maybe.Nothing(),
sentInvites: Maybe.Nothing(),
acceptedInvites: Maybe.Nothing(),
};
// TODO(shrugs): this hook is too big -> refactor into individual hooks for
// details, birthday, invites, etc
function _usePointCache() {
const { contracts, web3 } = useNetwork();
const [pointCache, _setPointCache] = useState({});
const [birthdayCache, _setBirthdayCache] = useState({});
const [invitesCache, _setInvitesCache] = useState({});
const addToPointCache = useCallback(
entry => _setPointCache(cache => ({ ...cache, ...entry })),
[_setPointCache]
);
const addToBirthdayCache = useCallback(
entry => _setBirthdayCache(cache => ({ ...cache, ...entry })),
[_setBirthdayCache]
);
const addToInvitesCache = useCallback(
entry => _setInvitesCache(cache => ({ ...cache, ...entry })),
[_setInvitesCache]
);
// TODO: refactor pointCache access to use accessor like bithday
const getDetails = useCallback(
point => pointCache[point] || Maybe.Nothing(),
[pointCache]
);
const getBirthday = useCallback(
point => birthdayCache[point] || Maybe.Nothing(),
[birthdayCache]
);
const getInvites = useCallback(
point => invitesCache[point] || kEmptyInvites,
[invitesCache]
);
const syncDetails = useCallback(
async point => {
const _contracts = contracts.getOrElse(null);
if (!_contracts) {
return;
}
// fetch point details
const details = await azimuth.azimuth.getPoint(_contracts, point);
addToPointCache({
[point]: details,
});
},
[contracts, addToPointCache]
);
const syncBirthday = useCallback(
async point => {
const _contracts = contracts.getOrElse(null);
const _web3 = web3.getOrElse(null);
if (!_contracts || !web3) {
return;
}
// fetch birthday if not already known—will not change after being set
if (Maybe.Nothing.hasInstance(getBirthday(point))) {
const birthBlock = await azimuth.azimuth.getActivationBlock(
_contracts,
point
);
if (birthBlock > 0) {
const block = await _web3.eth.getBlock(birthBlock);
addToBirthdayCache({
[point]: Maybe.Just(new Date(block.timestamp * 1000)),
});
}
}
},
[contracts, web3, addToBirthdayCache, getBirthday]
);
const syncInvites = useCallback(
async point => {
const _contracts = contracts.getOrElse(null);
if (!_contracts) {
return;
}
const availableInvites = await azimuth.delegatedSending.getTotalUsableInvites(
_contracts,
point
);
const invitedPoints = await azimuth.delegatedSending.getInvited(
_contracts,
point
);
const invitedPointDetails = await Promise.all(
invitedPoints.map(async invitedPoint => {
console.log('invitedPoint', invitedPoint, typeof invitedPoint);
const active = await azimuth.azimuth.isActive(
_contracts,
invitedPoint
);
return {
point: Number(invitedPoint),
active,
};
})
);
const sentInvites = invitedPointDetails.length;
const acceptedInvites = invitedPointDetails.filter(i => i.active).length;
addToInvitesCache({
[point]: {
availableInvites: Maybe.Just(availableInvites),
sentInvites: Maybe.Just(sentInvites),
acceptedInvites: Maybe.Just(acceptedInvites),
},
});
},
[contracts, addToInvitesCache]
);
// sync all of the on-chain info required for a point that the user owns
const syncOwnedPoint = useCallback(
async point => {
await Promise.all([
syncDetails(point),
syncInvites(point),
syncBirthday(point),
]);
},
[syncDetails, syncInvites, syncBirthday]
);
return {
pointCache,
getDetails,
getBirthday,
getInvites,
addToPointCache,
syncOwnedPoint,
};
}
export function PointCacheProvider({ children }) {
const pointCache = _usePointCache();
return (
{children}
);
}
// Hook version
export function usePointCache() {
return useContext(PointCacheContext);
}
// HOC version
export const withPointCache = Component =>
forwardRef((props, ref) => (
{pointCache => }
));