Skip to content

Instantly share code, notes, and snippets.

@haris-aqeel
Created May 17, 2023 19:06
Show Gist options
  • Save haris-aqeel/0a50f79affccf4b4d02bf867b4ea3cbc to your computer and use it in GitHub Desktop.
Save haris-aqeel/0a50f79affccf4b4d02bf867b4ea3cbc to your computer and use it in GitHub Desktop.
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