Apick - EdTech startup

Sample Code
Live Website

Hired as the one and only dev to build a marketplace for courses aimed for young adults in our dynamic economy. The solution was to create a React App, with Google's Firebase Auth & Firestore as the database. The company is within the top 10 regional survivors of Founder's Intitute seed program.

Stack: React.js, Firebase, Firestore, Redux, Bootstrap, Scss, Node/Express.js

Sample Code:

SearchResult.js

import React, { useState, useEffect } from "react"
import { Link, useParams } from "react-router-dom"
import { connect } from "react-redux"
import { Alert } from "react-bootstrap"

//context
import { useAuth } from "../../contexts/AuthContext"

//components
import MetaDecorator from "../MetaDecorator"
import SuspenseFallback from "../SuspenseFallback"
import Pagination from "./Pagination"
import useMediaQuery from "../useMediaQuery"

//icons
import { RiArrowGoBackLine } from "react-icons/ri"
import { CgClose } from "react-icons/cg"

// styles
import styles from "./SearchResult.module.scss"

const SearchResult = ({ subjects, courses, isMobile }) => {
	const params = useParams()
	const _query = params.query

	const { currentUser } = useAuth()

	const [query, setQuery] = useState()
	const [isProcessing, setIsProcessing] = useState(false)
	const [isLoadingProps, setIsLoadingProps] = useState(true)
	const [categoryParam, setCategoryParam] = useState(null)
	const [subcategoryParam, setSubcategoryParam] = useState(null)
	const [currentSubject, setCurrentSubject] = useState(null)
	const [currentCourses, setCurrentCourses] = useState([])
	const [matchCourses, setMatchCourses] = useState(null)
	const [alert, setAlert] = useState(false)

	useEffect(() => {
		if (!currentSubject) {
			setIsProcessing(true)
			setQuery(_query)
		} else if (_query.toLowerCase() !== currentSubject.toLowerCase()) {
			setIsProcessing(true)
			setQuery(_query)
		}
	}, [_query])

	useEffect(() => {
		if (subjects.length > 0 && courses.length > 0) {
			setIsProcessing(true)
			setIsLoadingProps(false)
		}
	}, [subjects, courses])

	const isKeyWord = word => {
		const filterCategoryParam = subjects.find(subject => {
			return subject.category.toLowerCase() === word.toLowerCase()
		})
		if (filterCategoryParam) {
			setCategoryParam(filterCategoryParam.category)
			setSubcategoryParam(null)
		} else {
			const subcategoriesArrays = subjects.map(subject => {
				return subject.subcategories
			})
			const subcategoriesDirty = subcategoriesArrays.flat(1)

			const subcategories = subcategoriesDirty.filter(subcategoryDirty => {
				return subcategoryDirty !== undefined
			})

			const _subcategoryParam = subcategories.find(v => {
				return v.toLowerCase() === word.toLowerCase()
			})
			if (_subcategoryParam) {
				setSubcategoryParam(_subcategoryParam)
				setCategoryParam(null)
			} else {
				setSubcategoryParam(null)
				setCategoryParam(null)
				setCurrentSubject(`"${query}"`)
			}
		}
	}
	useEffect(() => {
		if (courses && courses.length > 0 && subjects && subjects.length > 0) {
			!isLoadingProps && isKeyWord(query)
		}
	}, [isLoadingProps, query])

	useEffect(() => {
		if (categoryParam) {
			setCurrentSubject(categoryParam)
			return
		}
		if (subcategoryParam) {
			setCurrentSubject(subcategoryParam)
			return
		}
	}, [categoryParam, subcategoryParam])

	const searchQuery = query => {
		const titles = []
		courses.forEach(course => {
			if (course.title !== undefined) {
				titles.push(course.title)
			}
		})
		const matchTitles = titles.filter(title => title.toLowerCase().includes(query.toLowerCase()))
		// if part of any course title matched the query, return course(s)
		const _matchCourses = courses.filter(course => matchTitles.includes(course.title))
		_matchCourses.push()
		if (_matchCourses) {
			setMatchCourses(_matchCourses)
			console.log("matchCourses: ", _matchCourses)
		} else {
			setIsProcessing(false)
		}
	}

	useEffect(() => {
		if (currentSubject === `"${query}"` && !categoryParam && !subcategoryParam && courses && courses.length > 0) {
			searchQuery(query)
		}
	}, [currentSubject])

	useEffect(() => {
		if (categoryParam) {
			setCurrentCourses(
				courses.filter(course => {
					return course.category === currentSubject
				})
			)
		}
		if (subcategoryParam) {
			setCurrentCourses(
				courses.filter(course => {
					return course.subcategory === currentSubject
				})
			)
		}
		if (matchCourses) {
			setCurrentCourses(matchCourses)
		}
	}, [currentSubject, matchCourses])

	useEffect(() => {
		setIsProcessing(false)
	}, [currentCourses])

	useEffect(() => {
		if (isLoadingProps || isProcessing || currentUser) {
			return
		}
		setTimeout(() => {
			setAlert(true)
		}, 2000)
		setTimeout(() => {
			setAlert(false)
		}, 8000)
	}, [isLoadingProps, isProcessing, currentUser])

	const handleCloseIcon = () => {
		setAlert(false)
	}

	const abbreviateLongWords = sentence => {
		//check if letter is vowel
		const isVowel = letter => {
			let result = Boolean
			if (letter == "A" || letter == "E" || letter == "I" || letter == "O" || letter == "U") {
				result = true
			} else {
				result = false
			}
			return result
		}
		// conditions to reduce words or keep them original
		if (sentence.length < 12) {
			return sentence
		} else {
			const sentenceWords = sentence.split(" ")
			const reduceLongWords = sentenceWords.map(word => {
				if (word.length > 14) {
					if (!isVowel(word[6])) {
						return `${word.substring(0, 6)}.`
					} else if (!isVowel(word[7])) {
						return `${word.substring(0, 7)}.`
					} else {
						return `${word.substring(0, 8)}.`
					}
				} else if (word.length > 10) {
					if (!isVowel(word[3])) {
						return `${word.substring(0, 3)}.`
					} else if (!isVowel(word[4])) {
						return `${word.substring(0, 4)}.`
					} else if (!isVowel(word[5])) {
						return `${word.substring(0, 5)}.`
					} else {
						return `${word.substring(0, 6)}.`
					}
				} else {
					return word
				}
			})
			const reassembleWords = reduceLongWords.join(" ")
			return reassembleWords
		}
	}

	// Pagination Starts
	const isDesktop = useMediaQuery("(min-width: 1350px)")
	const isLaptop = useMediaQuery("(min-width: 1000px)")
	const isIpad = useMediaQuery("(min-width: 800px)")
	const isTablet = useMediaQuery("(min-width: 600px)")

	const [pageNumber, setPageNumber] = useState(1)
	const [itemsPerPage, setItemsPerPage] = useState(10)

	// change how many cards will render depending on the size of the screen
	useEffect(() => {
		if (isDesktop) {
			setItemsPerPage(10)
			return
		} else if (isLaptop) {
			setItemsPerPage(8)
			return
		} else if (isIpad) {
			setItemsPerPage(9)
			return
		} else if (isTablet) {
			setItemsPerPage(8)
			return
		} else {
			// isMobile
			setItemsPerPage(10)
		}
	}, [isLaptop, isTablet, isIpad, isDesktop])

	const paginate = page => {
		setPageNumber(page)
		window.scrollTo(0, 0)
	}
	// Paginations Ends

	return (
		<div>
			<MetaDecorator title={currentSubject ? `Cursos de ${currentSubject}` : "Buscador de Cursos"} description={"A Apick ajuda você a encontrar o seu curso ideal!"} />
			<section className={styles.main_container}>
				<div className="back-and-save">
					<Link to="/">
						<div className="back">
							{" "}
							<RiArrowGoBackLine />
							<p>Voltar</p>
						</div>
					</Link>
					<div className={styles.alertContainer}>
						{alert && (
							<Alert variant="primary" className={styles.alert}>
								<Link to="/criar-conta">Crie uma conta para salvar seus cursos preferidos!</Link>
								<CgClose className={styles.closeIcon} onClick={handleCloseIcon} />
							</Alert>
						)}
					</div>
				</div>

				{!isLoadingProps && !isProcessing && currentCourses ? (
					<>
						<div className={styles.title}>
							<p style={{ marginRight: "5px" }}>
								Encontramos {currentCourses.length} curso(s)
								{currentSubject && (
									<>
										{" "}
										sobre
										<strong> {!isMobile ? currentSubject : abbreviateLongWords(currentSubject)}</strong>
									</>
								)}
								:
							</p>
						</div>

						<div>{currentCourses && currentCourses.length > 0 ? <Pagination currentCourses={currentCourses} pageNumber={pageNumber} itemsPerPage={itemsPerPage} paginate={paginate} /> : "Desculpe, não encontramos nenhum curso."}</div>

					</>
				) : (
					<SuspenseFallback />
				)}
			</section>
		</div>
	)
}

const mapStateToProps = state => {
	return {
		currentSubject: state.currentSubject || [],
	}
}

export default connect(mapStateToProps)(SearchResult)
Felipe Smaniotto © 2021