Apick - EdTech startup
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)