Skip to content

Instantly share code, notes, and snippets.

@GeoffMahugu
Created April 11, 2023 14:12
Show Gist options
  • Save GeoffMahugu/a01218e57bdae1eba8ae4cdc5dcf3ea7 to your computer and use it in GitHub Desktop.
Save GeoffMahugu/a01218e57bdae1eba8ae4cdc5dcf3ea7 to your computer and use it in GitHub Desktop.
/**
* Main Project - https://ravenfable.com/
* This is a react component that rewards users with points after achieving certain tasks.
* The project is an NFT project that will reward its users 50 points for every social media connection and also alow users to access whitelist posts.
* The integrations include:
* - Firebase (Twitter social link Connect)
* - Discord (social link connect)
* - Blockchain / ETH Chain (Metamask connect)
* */
import { useState, useEffect } from "react";
import { auth, firestore } from '../config/firebase';
import logging from '../config/log';
import { Providers } from '../config/firebase';
import { SignInWithSocialMedia, AnonymousSignIn } from '../modules/auth/AuthModule';
import '../App.scss';
import { FaTwitter, FaDiscord } from "react-icons/fa";
import DiscordOauth2 from "discord-oauth2";
import firebase from 'firebase';
import {
ethereum,
getBrowserWeb3,
authenticate,
isMetamaskInstalled
} from "../modules/wallet/hooks";
const oauth = new DiscordOauth2({
clientId: process.env.REACT_APP_DISCORD_CLIENT_ID,
clientSecret: process.env.REACT_APP_DISCORD_CLIENT_SECRET,
redirectUri: process.env.REACT_BASE_URI,
});
const LandingPage = (props) => {
const DISCORD_JOIN_LINK = "https://discord.gg/x566ghvmxe";
const TWITTER_JOIN_LINK = "https://twitter.com/raven_fable";
const dbAccountsRef = firestore.collection('accounts');
const [dbData, setDbData] = useState(null);
const [myRefUri, setMyRefUri] = useState(null);
const [refId, setRefId] = useState(null);
const [anonUser, setAnonUser] = useState(null);
const [discordCode, setDiscordCode] = useState(null);
const [discordUser, setDiscordUser] = useState(null);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState(null);
const [error, setError] = useState(null);
const [twitterAcc, setTwitterAcc] = useState(null);
const [points, setPoints] = useState(0);
const [invites, setInvites] = useState(0);
const [account, setAccount] = useState(null);
const [WEB3, SET_WEB3] = useState();
const NETWORK_ID = 1;
const NETWORK_NAME = "Etherium"; //"Rinkeby Test Network"
const truncate = (input, len) =>
input.length > len ? `${input.substring(0, len)}...` : input;
useEffect(() => {
let clearMessage = setTimeout(() => {
setMessage(null);
}, 10000);
let clearError = setTimeout(() => {
setError(null);
}, 10000);
return () => {
clearTimeout(clearMessage);
clearTimeout(clearError)
}
}, [message, error]);
useEffect(() => {
updateLocalData();
if (props.location.search) {
const splitter = props.location.search.split("=");
if (splitter[0] === "?code") {
setDiscordCode(splitter[1])
}
if (splitter[0] === "?ref") {
let checkTwitterTimeout = setTimeout(() => {
updateRefId(splitter[1]);
}, 1000);
return () => {
clearTimeout(checkTwitterTimeout);
clearTimeout(checkTwitterTimeout)
}
}
}
auth.onAuthStateChanged(user => {
if (user) {
logging.info('User detected.');
if (!user.isAnonymous) {
if (!twitterAcc) setTwitterAcc(user);
} else {
if (!anonUser) setAnonUser(user);
}
} else {
logging.info('No user detected');
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (account) {
localStorage.setItem("account", account);
if (!twitterAcc && !anonUser) {
let createAnonUserTimeout = setTimeout(() => {
anonSignUp();
}, 1000);
return () => {
clearTimeout(createAnonUserTimeout);
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [account]);
useEffect(() => {
if (anonUser) {
fetchAnonAcc();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [anonUser]);
const fetchAnonAcc = async () => {
await dbAccountsRef
.where("account", "==", account)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
const _data = doc.data();
if (_data) {
if (_data.discordUser && _data.discordUser !== "null") setDiscordUser(JSON.parse(_data.discordUser));
if (_data.points) setPoints(_data.points);
if (_data.ref_accs.length) {
const len = _data.ref_accs.length || 0;
setInvites(len);
}
}
});
})
.catch((error) => {
console.log("Error getting documents: ", error);
});
}
const anonSignUp = async () => {
const loc_twitter_acc = await localStorage.getItem("persist:root");
if (loc_twitter_acc) {
setTwitterAcc(JSON.parse(loc_twitter_acc));
} else {
try {
await AnonymousSignIn()
.then(result => {
logging.info(result);
setAnonUser(result.user);
})
.catch(error => {
logging.error(error);
setLoading(false);
setError(error.message);
});
} catch (e) {
console.error(e)
}
}
}
useEffect(() => {
if (twitterAcc) {
localStorage.setItem("persist:root", JSON.stringify(twitterAcc));
setTimeout(() => {
fetchDbAcc();
}, 1000);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [twitterAcc]);
useEffect(() => {
const loc_discord_access_token = localStorage.getItem('discord_token');
if (!loc_discord_access_token && discordCode) {
authWithDiscord();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [discordCode]);
useEffect(() => {
calcTotalPoints();
if (account && (anonUser || twitterAcc || discordUser)) {
let clearTimeOut = setTimeout(() => {
createDbAcc();
}, 1000);
return () => {
clearTimeout(clearTimeOut);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [account, twitterAcc, discordUser]);
const updateLocalData = async () => {
const loc_ref = await localStorage.getItem("ref");
if (loc_ref) {
updateRefId(loc_ref);
}
const loc_acc = await localStorage.getItem("account");
if (loc_acc) {
setAccount(loc_acc);
if (!WEB3) {
try {
const web3 = await getBrowserWeb3();
SET_WEB3(web3);
} catch (e) {
setError(e.message);
}
}
} else {
connectWallet();
}
const loc_twitter_acc = await localStorage.getItem("persist:root");
if (loc_twitter_acc) {
setTwitterAcc(JSON.parse(loc_twitter_acc));
}
const loc_discord_user = localStorage.getItem('discord_user');
if (loc_discord_user) {
setDiscordUser(JSON.parse(loc_discord_user));
}
};
// REF START
const updateRefId = (ref_id) => {
setRefId(ref_id);
if (twitterAcc) {
if (twitterAcc.uid !== ref_id) {
localStorage.setItem('ref', ref_id);
}
} else {
localStorage.setItem('ref', ref_id);
}
}
// REF START
// TWITTER AUTH START
const authWithSocialMedia = async (provider) => {
if (message !== '') setMessage('');
if (error !== '') setError('');
if (!twitterAcc) {
setLoading(true);
await SignInWithSocialMedia(provider)
.then(result => {
logging.info(result);
setTwitterAcc(result.user);
localStorage.setItem("persist:root", JSON.stringify(result.user))
setMessage('Successfully Authenticated in with Twitter.');
setLoading(false);
})
.catch(error => {
logging.error(error);
setLoading(false);
setError(error.message);
});
} else {
console.log('JOIN US ON TWITTER');
window.open(TWITTER_JOIN_LINK, '_blank').focus();
}
}
const calcTotalPoints = async () => {
let _points = 0;
if (account) {
_points += 50;
}
if (discordUser) {
_points += 50;
}
if (twitterAcc) {
_points += 50;
}
if (dbData && dbData.ref_accs) {
const len = dbData.ref_accs.length;
setInvites(len);
_points += Number(len * 50)
}
setPoints(_points);
return _points;
}
const fetchDbAcc = async () => {
if (twitterAcc && account) {
await dbAccountsRef.doc(twitterAcc.uid)
.get()
.then((querySnapshot) => {
if (querySnapshot.exists) {
const _data = querySnapshot.data();
setDbData(_data);
if (_data.discordUser) setDiscordUser(JSON.parse(_data.discordUser));
} else {
return createDbAcc();
}
})
.catch((error) => {
console.log("Error Fetching FIRE document: ", error.message);
});
}
}
const createDbAcc = async () => {
if (account && twitterAcc && discordUser) {
const toSend = {
account: account,
refId: refId,
points: await calcTotalPoints(),
twitterAcc: JSON.stringify(twitterAcc),
discordUser: JSON.stringify(discordUser),
updated_at: firebase.firestore.Timestamp.now(),
}
const fetchInstance = await dbAccountsRef.doc(twitterAcc.uid)
.get();
if (fetchInstance.exists) {
await dbAccountsRef.doc(twitterAcc.uid).update({
...toSend
});
} else {
await dbAccountsRef.doc(twitterAcc.uid).set({
...toSend
});
}
setTimeout(() => {
updateRef();
}, 2000);
} else if (account && anonUser) {
const toSend = {
account: account,
refId: refId,
points: await calcTotalPoints(),
updated_at: firebase.firestore.Timestamp.now(),
}
const fetchInstance = await dbAccountsRef.doc(anonUser.uid)
.get();
if (fetchInstance.exists) {
await dbAccountsRef.doc(anonUser.uid).update({
...toSend
});
} else {
await dbAccountsRef.doc(anonUser.uid).set({
...toSend
});
}
}
}
const updateRef = async () => {
if (refId && account && twitterAcc) {
if (twitterAcc.uid !== refId) {
const toSend = {
ref_accs: firebase.firestore.FieldValue.arrayUnion(twitterAcc.uid),
updated_at: firebase.firestore.Timestamp.now(),
}
const fetchInstance = await dbAccountsRef.doc(refId)
.get();
if (fetchInstance.exists) {
await dbAccountsRef.doc(refId).update({
...toSend
});
} else {
await dbAccountsRef.doc(refId).set({
...toSend
});
}
}
}
}
// TWITTER AUTH END
// WEB3 START
const connectWallet = async () => {
if (message !== '') setMessage('');
if (error !== '') setError('');
setLoading(true);
try {
setLoading(false)
const acc = await authenticate();
setAccount(acc[0])
setMessage("Successfully logged in.");
} catch (e) {
setError(e.message);
setLoading(false)
}
}
const handleConnect = () => {
if (error !== '') setError('');
console.log("Handling 'connect' event");
};
const handleChainChanged = async (chainId) => {
if (error !== '') setError('');
};
const handleAccountsChanged = (accounts) => {
if (accounts.length) {
setAccount(accounts[0]);
} else {
setAccount(null);
}
};
const handleNetworkChanged = async (networkId) => {
if (Number(networkId) !== NETWORK_ID) {
setError(`Change network to ${NETWORK_NAME}.`);
} else {
setError(null);
}
};
if (ethereum) {
ethereum.on("connect", handleConnect);
ethereum.on("chainChanged", handleChainChanged);
ethereum.on("accountsChanged", handleAccountsChanged);
ethereum.on("chainChanged", handleNetworkChanged);
}
// WEB3 END
// DISCORD CODE START
const initDiscordConnect = async () => {
if (!discordUser) {
const url = oauth.generateAuthUrl({
scope: ["identify", "guilds"],
});
window.open(url, "_blank");
} else {
window.open(DISCORD_JOIN_LINK, "_blank");
}
}
const authWithDiscord = async () => {
if (message !== '') setMessage('');
if (error !== '') setError('');
setLoading(true);
try {
await oauth.tokenRequest({
code: discordCode,
scope: ["identify", "guilds"],
grantType: "authorization_code",
}).then(data => {
setLoading(false)
if (data) {
setMessage("Successfully connected with Discord.");
localStorage.setItem('discord_token', JSON.stringify(data))
fetchDiscordUser();
}
})
} catch (e) {
setLoading(false);
setError(e.message)
}
}
const fetchDiscordUser = async () => {
if (message !== '') setMessage('');
if (error !== '') setError('');
setLoading(true);
const raw_discord_data = localStorage.getItem('discord_token');
if (raw_discord_data) {
const { access_token } = JSON.parse(raw_discord_data)
try {
await oauth.getUser(access_token).then(data => {
setLoading(false)
if (data) {
localStorage.setItem('discord_user', JSON.stringify(data))
setDiscordUser(data);
}
});
} catch (e) {
setLoading(false);
setError(e.message)
}
}
}
const generateRefUrl = async () => {
if (twitterAcc) {
const fullUri = `${process.env.REACT_BASE_URI || 'https://ravenfable.com/'}?ref=${twitterAcc.uid}`
setMyRefUri(fullUri);
} else {
setError('Please Signup with Twitter first.')
}
}
const copyToClipboard = async () => {
if (myRefUri) {
navigator.clipboard.writeText(myRefUri);
setMessage('Invite link has been copied to clipboard.')
} else {
setError('Please generate a referal link first.')
}
}
return (
<div className="page-container">
<div className="points-wrapper">
<h1>{points}</h1>
<p>POINTS</p>
</div>
<img className="logo" src="/images/ravenfable.gif" alt="Logo" />
<div className="connect-wrapper mb-20">
{(isMetamaskInstalled) ?
<>
{(message || error) &&
<div className="message-wrapper">
<p className="success-txt">{message}</p>
<p className="error-txt">{error}</p>
</div>
}
<div className="connect-item-wrapper">
<div className="connect-left-wrapper">
{account &&
<img src="/images/idle.gif" className="checked-icon" alt="Checked" />
}
<button disabled={account || loading} className="connect-btn" onClick={() => connectWallet()}>{(account) ? 'Wallet' : 'Connect Wallet'}</button>
</div>
<div className="connect-right-wrapper">
{(account && account.length) &&
<>
<div className="connect-details">
{truncate(account, 10)}
</div>
</>
}
</div>
<div className="connect-item-description">
<p>Earn <b>+50 points</b> by connecting with metamask wallet.</p>
</div>
</div>
{account &&
<>
<div className="connect-item-wrapper">
<div className="connect-left-wrapper">
{twitterAcc &&
<img src="/images/idle.gif" className="checked-icon" alt="Checked" />
}
<button disabled={loading} className="connect-btn"
onClick={() => authWithSocialMedia(Providers.twitter)}
> {twitterAcc ? 'Follow us on Twitter' : 'Connect Twitter'}</button>
</div>
<div className="connect-right-wrapper">
{twitterAcc &&
<>
<div className="connect-details">@{truncate(twitterAcc.displayName, 10)}</div>
</>
}
</div>
<div className="connect-item-description">
<p>Earn <b>+50 points</b> by connecting with your twitter.</p>
</div>
</div>
<div className="connect-item-wrapper mb-30">
<div className="connect-left-wrapper">
{discordUser &&
<img src="/images/idle.gif" className="checked-icon" alt="Checked" />
}
<button disabled={loading} className="connect-btn" onClick={() => initDiscordConnect()}> {discordUser ? 'Join Discord Server' : 'Connect Discord'}</button>
</div>
<div className="connect-right-wrapper">
{discordUser &&
<>
<div className="connect-details">{truncate(`${discordUser.username}#${discordUser.discriminator}`, 15)}</div>
</>
}
</div>
<div className="connect-item-description">
<p>Earn <b>+50 points</b> by connecting with your discord.</p>
</div>
</div>
</>
}
{(account && twitterAcc && discordUser) &&
<div className="generate-wrapper mb-20">
{myRefUri &&
<div className="gen-my-ref-wrapper">
<p>{myRefUri}</p>
</div>
}
<div className="gen-button-wrapper">
{myRefUri ?
<button disabled={loading} onClick={() => copyToClipboard()} className="gen-btn">
COPY TO CLIPBOARD
</button>
:
<>
<button disabled={loading} onClick={() => generateRefUrl()} className="gen-btn mb-10">
GENERATE INVITE LINK
</button>
<p className="mb-5"> You've invited <b>{invites} </b> confirmed members.</p>
<p >Earn <b>+50 points</b> per invite once they complete registration.</p>
</>
}
</div>
</div>
}
</>
:
<div className="no-metamask-error">
<a href="https://metamask.io/download/" target="_blank" rel="noreferrer">Please Install MetaMask</a>
</div>
}
</div>
<div className="social-media-wrapper mb-20">
<a href={TWITTER_JOIN_LINK} className="social-link" target="_blank" rel="noreferrer"><FaTwitter /></a>
<a href={DISCORD_JOIN_LINK} target="_blank" className="social-link" rel="noreferrer"><FaDiscord /></a>
</div>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment