From 97d33ec8b539515467f93bea1e8753a7c5876207 Mon Sep 17 00:00:00 2001 From: Yan Luiz Date: Wed, 25 Sep 2024 18:05:36 -0300 Subject: [PATCH 1/3] refact-fix: separated logic to handle github login & added proper awaits to avoid storing empty data at firestore --- context/AuthContext.js | 87 ++++++++++++++++++++++-------------------- lib/user.js | 32 ++++++++++------ 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/context/AuthContext.js b/context/AuthContext.js index cc090b9d..628cd5d0 100644 --- a/context/AuthContext.js +++ b/context/AuthContext.js @@ -15,7 +15,7 @@ import { } from 'firebase/auth' import { auth } from '../firebase/initFirebase.js' import { getUserFromFirestore, createUserinFirestore, updateUserGithub } from '../lib/user.js' -import { useTranslation } from "react-i18next" +import { useTranslation } from 'react-i18next' const AuthContext = createContext() @@ -71,9 +71,9 @@ export function AuthProvider({ children }) { const signup = (data) => { setLoading(true) createUserWithEmailAndPassword(auth, data.email, data.password) - .then((userCredential) => { + .then(async (userCredential) => { setUser(userCredential.user) - createUserinFirestore(userCredential.user) + await createUserinFirestore(userCredential.user) Router.push('/courses') toast.success(t('messages.registration_success'), { toastParameters, @@ -122,7 +122,35 @@ export function AuthProvider({ children }) { } const loginGithub = async () => { - return loginWithProvider(GithubAuthProvider) + setLoading(true) + try { + const result = await signInWithPopup(auth, new GithubAuthProvider()) + await handleGithubLogin(result.user) + } catch (error) { + console.error('GitHub login error:', error) + toast.error(t('messages.something_wrong_try_again'), { + toastParameters, + }) + } finally { + setLoading(false) + } + } + + const handleGithubLogin = async (user) => { + const userData = await getUserFromFirestore(user) + if (!userData) { + await createUserinFirestore(user) + } + setUser(user) + const providerData = user.providerData.find((provider) => provider.providerId === 'github.com') + if (providerData) { + const githubUrl = `https://github.com/${providerData.uid}` + await updateUserGithub(githubUrl, user.uid) + } + Router.push('/courses') + toast.success(t('messages.login_success'), { + toastParameters, + }) } const loginWithProvider = async (Provider) => { @@ -133,34 +161,19 @@ export function AuthProvider({ children }) { useEffect(() => { async function fetchUser() { - await getRedirectResult(auth) - .then(async (result) => { - const user = result?.user - if (!user) return - const cred = JSON.parse(sessionStorage.getItem('credential')) - - if (cred?.providerId == 'github.com') { - await linkGithub(cred, user) - } - const providerObj = user.reloadUserInfo.providerUserInfo[0] - if (providerObj.providerId !== 'github.com') return - await getUserFromFirestore(user) - githubUrl = `https://${providerObj.providerId}/${providerObj.screenName}` - await updateUserGithub(githubUrl, user.uid) - sessionStorage.clear() - - toast.success(t('messages.login_success'), { - toastParameters, - }) - }) - .catch((error) => { - console.log(error) - if (error.code === 'auth/account-exists-with-different-credential') { - const credential = OAuthProvider.credentialFromResult(error.customData) - sessionStorage.setItem('credential', JSON.stringify(credential)) - Router.push('/auth') - } - }) + try { + const result = await getRedirectResult(auth) + if (result?.user) { + await handleGithubLogin(result.user) + } + } catch (error) { + console.error('Redirect result error:', error) + if (error.code === 'auth/account-exists-with-different-credential') { + const credential = OAuthProvider.credentialFromResult(error.customData) + sessionStorage.setItem('credential', JSON.stringify(credential)) + Router.push('/auth') + } + } } fetchUser() if (user) { @@ -168,16 +181,6 @@ export function AuthProvider({ children }) { mixpanel.people.set(user) } }, [auth.currentUser]) - - async function linkGithub(cred, user) { - if (user) { - const credential = GithubAuthProvider.credential(cred.accessToken) - const userCredential = await linkWithCredential(user, credential) - githubUrl = JSON.parse(userCredential._tokenResponse.rawUserInfo).html_url - await updateUserGithub(githubUrl, user.uid) - await getUserFromFirestore(user, user.providerData) - } - } const logout = async () => { try { sessionStorage.clear() diff --git a/lib/user.js b/lib/user.js index 2cddde92..7bb199e0 100644 --- a/lib/user.js +++ b/lib/user.js @@ -26,17 +26,24 @@ export const getUserFromFirestore = async (userCredential) => { } export const createUserinFirestore = async (userCredential) => { + const docRef = doc(collectionRef, userCredential.uid) + const docSnap = await getDoc(docRef) + + if (docSnap.exists()) { + console.log('User already exists in Firestore') + return + } + let githubUrl = '' - if (userCredential?.providerData) { - const github_id = userCredential.providerData.find( - (item) => item.providerId == 'github.com' - )?.uid - await fetch(`https://api.github.com/user/${github_id}`) - .then((res) => res.json()) - .then(async (data) => { - githubUrl = data.html_url - }) + if (userCredential.providerData && Array.isArray(userCredential.providerData)) { + const githubProvider = userCredential.providerData.find( + (item) => item.providerId === 'github.com' + ) + if (githubProvider) { + githubUrl = `https://github.com/${githubProvider.uid}` + } } + const userProps = { uid: userCredential?.uid, name: userCredential?.displayName || null, @@ -65,6 +72,7 @@ export const createUserinFirestore = async (userCredential) => { }, ], } + mixpanel.track('user_created', userProps) await setDoc(doc(collectionRef, userProps.uid), userProps) } @@ -168,7 +176,7 @@ export const submitLessonInFirestore = async ( export const findSocialLinks = (name, user) => user?.socialLinks?.find((link) => link.name === name) -export const getUserByDiscordId = async(discordId) => { +export const getUserByDiscordId = async (discordId) => { const q = query(usersRef, where('discord.id', '==', discordId)) const querySnapshot = await getDocs(q) @@ -193,10 +201,10 @@ export const registerUserInStudyGroupInFirestore = async (studyGroupId, userCred }), study_group_ids: arrayUnion(studyGroupId), } - + await updateDoc(doc(collectionRef, userCredential), userProps) mixpanel.track('study_group_signup', studyGroupData) } else { console.error(`Study group with ID ${studyGroupId} does not exist`) } -} \ No newline at end of file +} From 093bbee78b4714a3670a7f77a223e82e53b2b72f Mon Sep 17 00:00:00 2001 From: Yan Luiz Date: Wed, 25 Sep 2024 18:05:55 -0300 Subject: [PATCH 2/3] fix: save name param at mailchimp --- functions/lib/mailchimp.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/functions/lib/mailchimp.js b/functions/lib/mailchimp.js index 3f0384fa..c15ebc0f 100644 --- a/functions/lib/mailchimp.js +++ b/functions/lib/mailchimp.js @@ -26,19 +26,11 @@ exports.createUser = async function (user) { const result = await mailchimp.lists.addListMember('b578d43584', { email_address: user.email, status: 'subscribed', - merge_fields: objectWithUppercasedKeys(user), + full_name: user.name, + merge_fields: { WALLET: user.wallet_address, NAME: user.name }, }) + console.log('User added to Mailchimp successfully:') } catch (error) { console.error(error) } } - -function objectWithUppercasedKeys(object) { - const newObject = {} - Object.keys(object).forEach((key) => { - if(object[key]) { - newObject[key.toUpperCase()] = object[key] - } - }) - return newObject -} From a0f10ce3cfc0efed79d3e47986fa0b2033f7d7b0 Mon Sep 17 00:00:00 2001 From: Yan Luiz Date: Wed, 25 Sep 2024 18:23:43 -0300 Subject: [PATCH 3/3] fix mailchimp test --- functions/test/lib/mailchimp.test.js | 48 +++++++++++++++------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/functions/test/lib/mailchimp.test.js b/functions/test/lib/mailchimp.test.js index 16c9b66d..1904d4e5 100644 --- a/functions/test/lib/mailchimp.test.js +++ b/functions/test/lib/mailchimp.test.js @@ -5,56 +5,58 @@ jest.mock('@mailchimp/mailchimp_marketing', () => ({ setConfig: jest.fn(), lists: { addListMember: jest.fn(), - } + }, })) -describe('add User to list', function() { +describe('add User to list', function () { beforeEach(() => { mailchimp.lists.addListMember.mockClear() }) - it("Should add users to list correctly", async () => { + it('Should add users to list correctly', async () => { const emailData = { - user_email: "example@gmail.com", - firstName: "John", - lastName: "Doe", + user_email: 'example@gmail.com', + firstName: 'John', + lastName: 'Doe', params: { - cohort: "test", - course: "test", + cohort: 'cohort_list_id', + course: 'course_list_id', }, } await addUserToList(emailData) expect(mailchimp.setConfig).toHaveBeenCalledTimes(1) - expect(mailchimp.lists.addListMember).toHaveBeenCalledWith("test", { - email_address: "example@gmail.com", + expect(mailchimp.lists.addListMember).toHaveBeenCalledWith('cohort_list_id', { + email_address: 'example@gmail.com', + status: 'subscribed', + }) + expect(mailchimp.lists.addListMember).toHaveBeenCalledWith('course_list_id', { + email_address: 'example@gmail.com', status: 'subscribed', }) - expect(mailchimp.lists.addListMember).toHaveBeenCalledTimes(2) }) }) -describe("create user in mailchimp", () => { +describe('create user in mailchimp', () => { beforeEach(() => { mailchimp.lists.addListMember.mockClear() }) const user = { - email: "test@gmail.com", - firstName: "John", - lastName: "Doe", + email: 'test@gmail.com', + name: 'John', + wallet_address: '0x1234567890abcdef', } - it("Should create user in mailchimp", async () => { + it('Should create user in mailchimp', async () => { await createUser(user) - expect(mailchimp.lists.addListMember).toHaveBeenCalledWith("b578d43584", { - email_address: "test@gmail.com", - status: "subscribed", + expect(mailchimp.lists.addListMember).toHaveBeenCalledWith('b578d43584', { + email_address: 'test@gmail.com', + status: 'subscribed', merge_fields: { - EMAIL: "test@gmail.com", - FIRSTNAME: "John", - LASTNAME: "Doe", + NAME: 'John', + WALLET: '0x1234567890abcdef', }, }) }) -}) \ No newline at end of file +})