Created
May 17, 2023 19:06
-
-
Save haris-aqeel/0a50f79affccf4b4d02bf867b4ea3cbc 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
import { createSlice } from '@reduxjs/toolkit'; | |
import { addDoc, arrayRemove, arrayUnion, collection, doc, getDoc, getDocs, query, setDoc, Timestamp, updateDoc, where } from 'firebase/firestore'; | |
// eslint-disable-next-line import/no-cycle | |
import { DB } from '../../auth/FirebaseApp'; | |
const COMPANY_ACTIONS = { | |
CREATE: 'create', | |
UPDATE: 'update', | |
} | |
const PRODUCT_ACTIONS = { | |
CREATE: 'create', | |
UPDATE: 'update', | |
GENERATE: 'generate', | |
} | |
export const initialState = { | |
error: null, | |
isUserLoading: false, | |
isCompanyLoading: false, | |
isInvitationsLoading: false, | |
isCompanyUsersLoading: false, | |
isCompanyProductsLoading: false, | |
companies: [], | |
selectedCompany: null, | |
isInitialized: false, | |
isAuthenticated: false, | |
isCompanyListingsLoading: false, | |
user: null, | |
userRef: null, | |
company: null, | |
invitations: [], | |
allProducts: [], | |
companyUsers: [], | |
companyListings: [], | |
companyProducts: [], | |
selectedProduct: null, | |
isSelectedProduct: false, | |
adminProductsList: [], | |
adminProductsListLoading: false | |
} | |
const slice = createSlice({ | |
name: 'user', | |
initialState, | |
reducers: { | |
// USER LOADING | |
userLoading(state, action) { | |
state.isUserLoading = action.payload; | |
}, | |
companyLoading(state, action) { | |
state.isCompanyLoading = action.payload; | |
}, | |
setAdminProductsList(state, action) { | |
state.adminProductsList = action.payload; | |
}, | |
setAdminProductsListLoading(state, action) { | |
state.adminProductsListLoading = action.payload; | |
}, | |
companyListingsLoading(state, action) { | |
state.isCompanyListingsLoading = action.payload; | |
}, | |
companyUsersLoading(state, action) { | |
state.isCompanyUsersLoading = action.payload; | |
}, | |
invitationsLoading(state, action) { | |
state.isInvitationsLoading = action.payload; | |
}, | |
// HAS ERROR | |
hasError(state, action) { | |
state.error = action.payload; | |
}, | |
// GET USER | |
setUserSuccess(state, action) { | |
state.user = action.payload.userProfile; | |
if (action.payload.userRef) { | |
state.userRef = action.payload.userRef; | |
} | |
}, | |
// UPDATE USER | |
updateUserSuccess(state, action) { | |
state.user = action.payload; | |
}, | |
setSelectedCompany(state, action) { | |
state.selectedCompany = action.payload; | |
}, | |
// NEW | |
setCompanies(state, action) { | |
state.companies = action.payload; | |
}, | |
setAuthentication(state, action) { | |
state.isAuthenticated = action.payload; | |
state.isInitialized = true; | |
if (action.payload === false) { | |
state = initialState; | |
} | |
}, | |
setInvitations(state, action) { | |
state.invitations = action.payload; | |
}, | |
setCompanyUsers(state, action) { | |
state.companyUsers = action.payload; | |
}, | |
companyProductsLoading(state, action) { | |
state.isCompanyProductsLoading = action.payload; | |
}, | |
setCompanyProducts(state, action) { | |
state.companyProducts = action.payload; | |
}, | |
setIsSelectedProduct(state, action) { | |
state.isSelectedProduct = action.payload; | |
}, | |
setAllProducts(state, action) { | |
state.allProducts = action.payload; | |
}, | |
getCompanyListings(state, action) { | |
state.companyListings = action.payload; | |
}, | |
resetData(state) { | |
state.isInitialized = true; | |
state.isAuthenticated = false; | |
state.isUserLoading = false; | |
state.isCompanyLoading = false; | |
state.isInvitationsLoading = false; | |
state.error = null; | |
state.user = null; | |
state.userRef = null; | |
state.company = null; | |
state.invitations = []; | |
state.companyUsers = []; | |
state.companyProducts = []; | |
state.companyListings = []; | |
state.isCompanyUsersLoading = false; | |
state.isCompanyProductsLoading = false; | |
state.isCompanyUsersLoading = false; | |
state.selectedProduct = null; | |
state.isSelectedProduct = false; | |
state.companies = []; | |
state.selectedCompany = null; | |
} | |
} | |
}); | |
// Reducer | |
export default slice.reducer; | |
// Actions | |
export const { | |
userLoading, | |
setUserSuccess, | |
updateUserSuccess, | |
companyLoading, | |
setCompanies, | |
setAuthentication, | |
setInvitations, | |
companyUsersLoading, | |
} = slice.actions; | |
/** | |
* @note: reset all data in redux store | |
*/ | |
export function resetStateData() { | |
return async (dispatch) => { | |
dispatch(slice.actions.resetData()) | |
} | |
} | |
/** | |
* @note set user after login with details saved in database | |
* @param {*} userId | |
* @param {*} otherUserData | |
* @returns | |
*/ | |
export function setUser(userId, otherUserData) { | |
return async (dispatch) => { | |
dispatch(slice.actions.userLoading(true)); | |
try { | |
const userRef = doc(DB, 'users', userId); | |
const userDocumentSnap = await getDoc(userRef); | |
const docSuperUserRef = await doc(collection(DB, 'super-user'), userId); | |
const docSuperUserSnapshot = await getDoc(docSuperUserRef); | |
const isSuperUser = docSuperUserSnapshot.exists(); | |
let userProfile = userDocumentSnap.data(); | |
if (otherUserData) { | |
userProfile = { ...otherUserData, ...userProfile, isSuperUser }; | |
} | |
if (isSuperUser) { | |
dispatch(getAllCompanies()) | |
} | |
dispatch(slice.actions.setUserSuccess({ userProfile, userRef })); | |
dispatch(setUserInvitations(userProfile)); | |
dispatch(setAllProducts()); | |
if (userProfile.companies) { | |
dispatch(slice.actions.setCompanies(userProfile.companies)); | |
} | |
dispatch(slice.actions.setAuthentication(true)); | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
dispatch(slice.actions.setAuthentication(false)); | |
} finally { | |
dispatch(slice.actions.userLoading(false)); | |
} | |
}; | |
} | |
/** | |
* @note updates the user info | |
* @param {*} userInfo | |
* @returns | |
*/ | |
export function updateUser(userInfo) { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.userLoading(true)); | |
try { | |
const { user } = getState().user; | |
const userRef = doc(DB, 'users', user?.uid); | |
const userDocumentSnap = await getDoc(userRef); | |
const userDetails = userDocumentSnap.data(); | |
const completeUserInfo = { ...userDetails, ...userInfo }; | |
dispatch(slice.actions.setUserSuccess({ userProfile: completeUserInfo, userRef: null })); | |
dispatch(setUserInvitations(completeUserInfo)); | |
dispatch(setAllProducts()); | |
if (completeUserInfo.companies) { | |
dispatch(slice.actions.setCompanies(completeUserInfo.companies)); | |
} | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.userLoading(false)); | |
} | |
} | |
} | |
/** | |
* @note selects the company from the header dropdown | |
* @param {*} companyRef | |
* @returns | |
*/ | |
export function setSelectedCompany(companyRef) { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyLoading(true)); | |
try { | |
const companyDocumentSnap = await getDoc(companyRef); | |
const company = companyDocumentSnap.data(); | |
dispatch(slice.actions.setSelectedCompany({ companyInfo: company, companyId: companyRef.id, companyRef })); | |
dispatch(setCompanyUsers()); | |
dispatch(setCompanyProducts()); | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyLoading(false)); | |
} | |
} | |
} | |
/** | |
* @note this function is used to get all user invitations | |
* @param {*} user updates the user invitations live from snapshot | |
* @returns | |
*/ | |
export function setUserInvitations(user) { | |
return async (dispatch) => { | |
try { | |
dispatch(slice.actions.invitationsLoading(true)); | |
const userRef = doc(DB, 'users', user?.uid); | |
const userInvitations = user?.invitations || []; | |
const promises = userInvitations.map(async (invitation) => { | |
const docRef = doc(DB, 'company', invitation.companyId); | |
const docSnap = await getDoc(docRef); | |
if (docSnap.exists()) { | |
const companyData = docSnap.data(); | |
const companyName = companyData.companyName || "Anonymous"; | |
return { ...invitation, companyName }; | |
} | |
await updateDoc(userRef, { | |
invitations: arrayRemove(invitation) | |
}); | |
return null; | |
}); | |
const invitations = await Promise.allSettled(promises); | |
const filteredInvitations = invitations.filter((invitation) => invitation !== null); | |
dispatch(slice.actions.setInvitations(filteredInvitations)); | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.invitationsLoading(false)); | |
} | |
}; | |
} | |
/** | |
* @param {*} action CREATE || UPDATE | |
* @param {*} data Data to be updated with | |
* @param {*} id Company to be updates (Company Id) | |
* @note This function is used to create and update companies | |
* @note This function is involves separate logic for super users and normal users | |
*/ | |
export function setCompanyOperation(action, data, id) { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyLoading(true)); | |
try { | |
const { userRef, selectedCompany, user } = getState().user; | |
const companyInfo = { | |
companyName: data.companyName, | |
richTextDescription: data.richTextDescription, | |
images: data.images, | |
isCustomer: true, | |
isProvider: true, | |
owner: "reference", | |
} | |
if (user.isSuperUser) { | |
if (action === COMPANY_ACTIONS.UPDATE) { | |
const companyRef = doc(collection(DB, 'company'), selectedCompany.companyId); | |
updateDoc(companyRef, { ...companyInfo }); | |
} else { | |
const companyRef = doc(collection(DB, 'company')); | |
companyInfo.admin = userRef; | |
companyInfo.companyProducts = []; | |
companyInfo.dateCreated = Timestamp.now(); | |
companyInfo.users = [{ userRef, permissions: ['admin'] }]; | |
await setDoc(companyRef, companyInfo); | |
await updateDoc(userRef, { | |
companyID: companyRef | |
}); | |
await updateDoc(userRef, { | |
companies: arrayUnion({ companyRef, companyName: companyInfo.companyName }) | |
}); | |
} | |
dispatch(updateUser({ isSuperUser: true })); | |
return; | |
} | |
let companyRef; | |
if (action === COMPANY_ACTIONS.CREATE) { | |
companyRef = doc(collection(DB, 'company')); | |
companyInfo.admin = userRef; | |
companyInfo.companyProducts = []; | |
companyInfo.dateCreated = Timestamp.now(); | |
companyInfo.users = [{ userRef, permissions: ['admin'] }]; | |
await setDoc(companyRef, companyInfo); | |
await updateDoc(userRef, { | |
companyID: companyRef | |
}); | |
await updateDoc(userRef, { | |
companies: arrayUnion({ companyRef, companyName: companyInfo.companyName }) | |
}); | |
} | |
else if (action === COMPANY_ACTIONS.UPDATE) { | |
companyRef = doc(collection(DB, 'company'), selectedCompany.companyId); | |
updateDoc(companyRef, { ...selectedCompany.companyInfo, ...companyInfo }); | |
} else { | |
return; | |
} | |
dispatch(updateUser({})); | |
if (action === COMPANY_ACTIONS.CREATE) { | |
dispatch(setSelectedCompany(companyRef)); | |
} | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyLoading(false)); | |
} | |
} | |
} | |
/** | |
* @note This function is used to set company users | |
* @param {*} dataOfCompany syncing the users details of the company from database | |
* @notice this function set company users | |
*/ | |
export function setCompanyUsers(dataOfCompany) { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyUsersLoading(true)); | |
try { | |
const { selectedCompany } = getState().user; | |
let listOfUsers = []; | |
if (dataOfCompany && dataOfCompany.users) { | |
listOfUsers = dataOfCompany.users; | |
} else if (selectedCompany && selectedCompany.companyInfo && selectedCompany.companyInfo.users) { | |
listOfUsers = selectedCompany.companyInfo.users; | |
} else { | |
return; | |
} | |
const users = await Promise.allSettled(listOfUsers?.map(async (user) => { | |
const id = user?.userRef?.id; | |
const documetReference = await doc(collection(DB, 'users'), id); | |
const documentSnapshot = await getDoc(documetReference); | |
const userData = documentSnapshot.data(); | |
return { | |
...user, | |
...userData, | |
} | |
})); | |
const companyUserData = users.map(async (user) => { | |
const data = await getDoc(user.value.userRef); | |
return { | |
id: user.value.userRef.id, | |
name: user.value.displayName, | |
emailAddress: user.value.email, | |
phone: user.value.phoneNumber, | |
title: user.value.title, | |
createdAt: new Date(data._document.createTime.timestamp.seconds * 1000), | |
permissions: user.value.permissions, | |
companyId: selectedCompany?.companyId, | |
} | |
}); | |
const companyData = await Promise.allSettled(companyUserData); | |
if (companyData && companyData.length > 0) { | |
dispatch(slice.actions.setCompanyUsers(companyData.map((user) => user.value))); | |
} | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyUsersLoading(false)); | |
} | |
}; | |
} | |
/** | |
* @param {*} action CREATE || UPDATE | |
* @param {*} data update product with data | |
* @param {*} id id of the product to update | |
* @returns id of the product (so page can navigate to the preview product) | |
*/ | |
export function setProductOperation(action, data, id) { | |
return async (dispatch, getState) => { | |
let idOfProduct = null; | |
dispatch(slice.actions.companyProductsLoading(true)); | |
try { | |
const { selectedCompany } = getState().user; | |
if (action === PRODUCT_ACTIONS.CREATE && selectedCompany?.companyId && selectedCompany?.companyRef) { | |
const productsRef = await collection(DB, 'products'); | |
const newProductRef = await addDoc(productsRef, { ...data, company: selectedCompany.companyRef }); | |
updateDoc(doc(collection(DB, 'company'), selectedCompany.companyId), { | |
companyProducts: arrayUnion({ id: newProductRef.id, productRef: newProductRef }) | |
}); | |
idOfProduct = newProductRef.id; | |
} | |
else if (action === PRODUCT_ACTIONS.UPDATE && id) { | |
const productRef = await doc(collection(DB, 'products'), id); | |
updateDoc(productRef, data); | |
idOfProduct = id; | |
} | |
else if (action === PRODUCT_ACTIONS.GENERATE) { | |
const productsRef = await collection(DB, 'products'); | |
const newProductRef = await addDoc(productsRef, { ...data, company: doc(collection(DB, 'company'), "generated") }); | |
updateDoc(doc(collection(DB, 'company'), "generated"), { | |
companyProducts: arrayUnion({ id: newProductRef.id, productRef: newProductRef }) | |
}); | |
} | |
if (selectedCompany && selectedCompany.companyRef) { | |
dispatch(setSelectedCompany(selectedCompany?.companyRef)) | |
} | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyProductsLoading(false)); | |
} | |
return idOfProduct; | |
} | |
} | |
/** | |
* @note This function is used to get all products for showing in the table | |
* @returns {Promise<void>} | |
*/ | |
export function getAllProducts() { | |
return async (dispatch, getState) => { | |
try { | |
dispatch(slice.actions.setAdminProductsListLoading(true)); | |
const productsRef = await query(collection(DB, 'products'), where("company", "!=", null)); | |
const productsSnapshot = await getDocs(productsRef); | |
console.log("Total Number Of Products ====>", productsSnapshot.docs.length) | |
const products = await Promise.all( | |
productsSnapshot.docs.map(async (d) => { | |
if (!d.data().company) { | |
return null; | |
} | |
const companySnapshot = await getDoc(d.data().company); | |
if (!companySnapshot.exists()) { | |
console.log(`Company does not exist for product, ${d.id}`); | |
return null; | |
} | |
const { companyName } = companySnapshot.data(); | |
return { | |
id: d.id, | |
title: d.data().name, | |
description: d.data().description, | |
category: d.data().category, | |
createdAt: new Date(d._document.createTime.timestamp.seconds * 1000) || new Date(), | |
images: d.data().images, | |
company: d.data().company, | |
companyId: companySnapshot.id, | |
companyName: companyName || "", | |
targetMarket: d.data().targetMarket, | |
tags: d.data().tags, | |
integrations: d.data().integrations || [], | |
dependencies: d.data().dependencies || [], | |
}; | |
}) | |
); | |
const productListings = products.filter((product) => product !== null); | |
dispatch(slice.actions.setAdminProductsList(productListings)); | |
} | |
catch (error) { | |
console.log(error) | |
dispatch(slice.actions.hasError(error)); | |
} | |
finally { | |
dispatch(slice.actions.setAdminProductsListLoading(false)); | |
} | |
} | |
} | |
/** | |
* @description Get all companies from the database | |
* @returns {Promise<void>} | |
* @note This function is used to get all companies from the database except for the invited companies | |
* @note This function is used throughout the application | |
*/ | |
export function getAllCompanies() { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyListingsLoading(true)); | |
try { | |
const companyRef = await query(collection(DB, 'company'), where('companyName', '!=', 'invited')); | |
const companySnapshot = await getDocs(companyRef); | |
const companies = companySnapshot.docs.map(async (d) => ({ | |
id: d.id, | |
companyName: d.data().companyName, | |
createdAt: new Date(d._document.createTime.timestamp.seconds * 1000), | |
description: d.data().richTextDescription, | |
companyRef: d.ref, | |
name: d.data().companyName, | |
images: d.data().images, | |
})); | |
const companiesListings = await Promise.allSettled(companies); | |
const companiesArray = companiesListings.map((company) => company.value); | |
const companiesArrayFiltered = companiesArray.filter((company) => company.id !== "generated"); | |
dispatch(slice.actions.getCompanyListings(companiesArrayFiltered)); | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyListingsLoading(false)); | |
} | |
} | |
} | |
/** | |
* @description Get all products of the selected company | |
* @returns {Promise<void>} | |
* @note This function is used to get all products of the selected company | |
* @note This function is used throughout the application | |
*/ | |
export function setCompanyProducts(productListing) { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyProductsLoading(true)); | |
try { | |
const { selectedCompany } = getState().user; | |
if (!selectedCompany || !selectedCompany.companyInfo || !selectedCompany.companyInfo.companyProducts || selectedCompany?.companyInfo?.companyProducts.length === 0) { | |
console.log("The company has no products or either you have not selected a company") | |
dispatch(slice.actions.setCompanyProducts([])); | |
return; | |
} | |
const companyProducts = productListing || selectedCompany?.companyInfo?.companyProducts; | |
const products = await Promise.allSettled(companyProducts.map(async (product) => { | |
const id = product?.productRef?.id || product?.id; | |
const documetReference = await doc(collection(DB, 'products'), id); | |
const documentSnapshot = await getDoc(documetReference); | |
const productData = documentSnapshot.data(); | |
const companySnapshot = await getDoc(productData.company); | |
return { | |
id, | |
name: productData.name, | |
title: productData.name, | |
description: productData.description, | |
category: productData.category, | |
createdAt: new Date(documentSnapshot._document.createTime.timestamp.seconds * 1000), | |
images: productData.images, | |
company: productData.company, | |
companyId: companySnapshot.id, | |
companyName: companySnapshot.data().companyName, | |
targetMarket: productData.targetMarket, | |
tags: productData.tags, | |
integrations: productData.integrations || [], | |
dependencies: productData.dependencies || [], | |
} | |
})); | |
if (products && products.length > 0) { | |
const filterProducts = products.filter((product) => product !== undefined); | |
dispatch(slice.actions.setCompanyProducts(filterProducts.map((product) => product.value))); | |
} | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyProductsLoading(false)); | |
} | |
}; | |
} | |
/** | |
* @description This function is used to get all the products from the database | |
* @note This function is used in three components | |
* @note This function focuses on integrations and dependencies | |
* Saves the products in the redux store allProducts | |
* @dev: In future, can be replaced with getAllProducts after some changes | |
*/ | |
export function setAllProducts() { | |
return async (dispatch, getState) => { | |
dispatch(slice.actions.companyProductsLoading(true)); | |
try { | |
const productsRef = await query(collection(DB, 'products'), where("company", "!=", null)); | |
const querySnapshot = await getDocs(productsRef); | |
const products = querySnapshot.docs.map(async (d) => { | |
const companyRef = await d.data().company; | |
const companyData = await getDoc(companyRef); | |
return { | |
id: d.id, | |
title: d.data().name, | |
company: companyData.data().companyName || "", | |
integrations: d.data().integrations, | |
dependencies: d.data().dependencies, | |
companyId: companyRef.id || "", | |
description: d.data().description, | |
category: d.data().category, | |
images: d.data().images, | |
} | |
}); | |
const productsData = await Promise.allSettled(products); | |
dispatch(slice.actions.setAllProducts(productsData.map(p => p.value))); | |
} catch (error) { | |
console.error(error); | |
dispatch(slice.actions.hasError(error)); | |
} finally { | |
dispatch(slice.actions.companyProductsLoading(false)); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment