Instantly share code, notes, and snippets.
Created
April 11, 2023 14:12
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save GeoffMahugu/a01218e57bdae1eba8ae4cdc5dcf3ea7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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