{"id":13406613,"url":"https://github.com/eduardorerick/letmeask","last_synced_at":"2025-04-11T15:10:39.992Z","repository":{"id":49142602,"uuid":"378793673","full_name":"eduardorerick/letmeask","owner":"eduardorerick","description":"Aplicação que me garantiu uma bolsa de estudos no Ignite! Desenvolvida durante a NLW com outras funcionalidades adicionadas por mim. ","archived":false,"fork":false,"pushed_at":"2021-07-04T19:57:50.000Z","size":735,"stargazers_count":24,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-07-31T19:53:18.574Z","etag":null,"topics":["firebase","react","typescript"],"latest_commit_sha":null,"homepage":"https://letmeask-510c3.web.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eduardorerick.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-21T03:17:30.000Z","updated_at":"2022-12-13T01:56:55.000Z","dependencies_parsed_at":"2022-09-15T12:12:37.870Z","dependency_job_id":null,"html_url":"https://github.com/eduardorerick/letmeask","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eduardorerick%2Fletmeask","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eduardorerick%2Fletmeask/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eduardorerick%2Fletmeask/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eduardorerick%2Fletmeask/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eduardorerick","download_url":"https://codeload.github.com/eduardorerick/letmeask/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248429104,"owners_count":21101783,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["firebase","react","typescript"],"created_at":"2024-07-30T19:02:34.766Z","updated_at":"2025-04-11T15:10:39.970Z","avatar_url":"https://github.com/eduardorerick.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\" \u003e   \n\n![Letmeasklogo](.github/Logo.png)    \n\n\u003c/div\u003e\n\n![Letmeask](.github/letmeaskhome.png)\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://letmeask-510c3.web.app\"\u003eVisite o site aqui\u003c/a\u003e\u003cbr\u003e\n  \u003ca href=\"#-tecnologias\"\u003eTecnologias\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#-projeto\"\u003eProjeto\u003c/a\u003e\u003cbr\u003e\n  \u003ca href=\"#dia-1\"\u003eDia 1\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#dia-2\"\u003eDia 2\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#dia-3\"\u003eDia 3\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#dia-4\"\u003eDia 4\u003c/a\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003ca href=\"#dia-5\"\u003eDia 5\u003c/a\u003e\n  \u003cbr\u003e\t\n  \u003ca href=\"#minhas-alterações-no-projeto\"\u003eMinhas alterações no projeto\u003c/a\u003e\n\u003c/p\u003e\n\n\n\n\n## ✨ Tecnologias\n\nEsse projeto foi desenvolvido com as seguintes tecnologias:\n\n- [React](https://reactjs.org)\n- [TypeScript](https://www.typescriptlang.org/)\n- [Firebase](https://firebase.google.com/)\n\n## 💻 Projeto\n\nO letmeask é um app desenvolvido durante a NLW que permite que alguém realizando lives crie uma sala para receber perguntas, tendo maior interação com o usuário.\n\n\n## Dia 1 \n\u003ch2\u003e Configuração de ambiente \u003c/h2\u003e\n\u003cp\u003e Foi dado inicio ao aplicativo React com \u003ccode\u003eyarn create react-app letmeask --template typescript\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eTópicos que eu considerei importantes no dia:\u003c/p\u003e\n\u003cul\u003e\n  \u003cli\u003e Introdução ao Typescript \u003c/li\u003e\n  \u003cli\u003e Introdução ao Firebase \u003c/li\u003e\n  \u003cli\u003e Resumo sobre SPA \u003c/li\u003e\n  \u003cli\u003e Benefícios na utilizando de functions no React \u003c/li\u003e\n  \u003cli\u003e Introdução a Hooks \u003c/li\u003e\n\u003c/ul\u003e\n\n\n## Dia 2 \n\u003ch2\u003e Páginas iniciais e autenticação \u003c/h2\u003e\n\u003cp\u003e Uma bomba de conteúdo! Começamos com uma simples página com HTML e SCSS e então partimos para o início do método de autenticação. \u003c/p\u003e\n\u003cp\u003e Primeiro fizemos a integração do \u003ccode\u003ereact-router-dom\u003c/code\u003e para navegar pelas páginas, então fizemos o método de login pelo google utilizando o firebase. Com uma função assíncrona \u003ccode\u003easync function signInWithGoogle()\u003c/code\u003e definimos o provedor como \u003ccode\u003econst provider = firebase.auth.GoogleAuthProvider()\u003c/code\u003e e definimos o resultado como \u003ccode\u003econst result = await auth.signInWithPopup(provider)\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e Nesse ponto, se o cliente concluir a autenticação já temos um \u003ccode\u003eresult\u003c/code\u003e com várias informações, que permite a gente a criar uma estrutura condicional para o nosso código, então podemos checar se o cliente tem foto, nome. \u003c/p\u003e\n     \n      if (result.user) {\n      const { displayName, photoURL, uid} = result.user;\n      \n      if(!displayName || !photoURL) {\n        throw new Error('Missing information from Google Account.')\n      };\n\n      setUser({\n        id: uid,\n        name: displayName,\n        avatar: photoURL\n      });\n      };\n\n\n\u003cp\u003e Se o usuário não possuir um nome e foto de perfil, a função retornará um erro com uma string, se ele tem todos os dados, a função vai \"setar\" o estado com os dados novos do usuário.\u003c/p\u003e\n\u003cp\u003ePara manter os dados do usuário caso ele atualize a página foi utilizado o \u003ccode\u003euseEffect\u003c/code\u003e, foi usado dentro da função um observador para garantir que o objeto Auth não esteja em um estado intermediário (como inicialização) ao identificar o usuário atual.\n\n\u003cp\u003ePor fim, foi feito uma refatoração do código, todo o AuthContext foi passado para um arquivo TSX próprio, e foi criado também o arquivo UseAuth.js para simplificar o uso de hooks\u003c/p\u003e\n\n    import { useContext } from 'react';\n    import { AuthContext } from '../contexts/AuthContext';\n\n    export function useAuth() {\n      const value = useContext(AuthContext)\n      return value\n    };\n\n## Dia 3 \n\u003ch2\u003e Criando novas salas e novas perguntas\u003c/h2\u003e\n\n\u003cp\u003ePara criar uma nova sala no database do firebase precisamos da função \n  \u003ccode\u003efirebase.database().ref()\u003c/code\u003e que retorna uma referência, que é uma localização dentro da database do Firebase.\nassim podemos escrever :\u003c/p\u003e\n\n      const roomRef = database.ref('rooms');\n\n      const firebaseRoom = await roomRef.push({\n            title: newRoom,\n            authorId: user?.id,\n          })\n\n\u003cp\u003eDessa maneira, estamos passando para a database na localização \"rooms\" um objeto contendo \u003ccode\u003etitle\u003c/code\u003e e \u003ccode\u003eauthorId\u003c/code\u003e que foi passado pelo usuário. \u003c/p\u003e\n\n\u003cp\u003eDepois de feito o scss da Sala, foi criado um component RoomCode para apenas pegar o código. \u003c/p\u003e\n\u003cp\u003ePara ter acesso ao código utilizamos o useParams do react-router-dom e para poder guardar os valores precisamos definir uma constante:\u003c/p\u003e\n       \n       const params = useParams();\n\n\u003cp\u003eporém, precisamos definir quais são os parametros que queremos receber nessa rota.\u003c/p\u003e\n\u003cp\u003eentão definimos:\u003c/p\u003e\n\n      type RoomParams = {\n        id: string;\n      }\n\nentão fica:\n\n    const params = useParams\u003cRoomParams\u003e();\n\nagora a função sabe quais parametros vai receber.\n\ne agora usamos o componente criado e passamos esse parametro como uma prop: \n\n    \u003cRoomCode code={params.id} /\u003e\n\nCriamos uma pequena função para o botão de copiar o numero da sala\n\n    function copyRoomCodeToClipboard() {\n        navigator.clipboard.writeText(props.code)\n      }\n\n\n\u003cp\u003eO próximo passo é fazer o botão de enviar questões funcionar.\u003c/p\u003e\n\u003cp\u003ePara isso definidos um novo estado chamado newQuestion \u003c/p\u003e\n\n    const [newQuestion, setNewQuestion] = useState('')\n\ne importamos também o user que guardamos com a função \u003ccode\u003esignInWithGoogle()\u003c/code\u003e\n\nagora é só checar se a perguntava enviada tem mesmo algum conteúdo. \n\n    if (newQuestion.trim() === \"\") {\n          return\n        }\n\ne verificar se o usuário está logado.\n\n    if(!user) {\n      throw new Error('You must be logged in');\n    }\n\nse passar por essas condições, definimos um objeto com os dados da nova pergunta e os dados do usuário. \n\n\n    const question = {\n          content: newQuestion,\n          author: {\n            name: user?.name,\n            avatar: user.avatar,\n          },\n          isHighlighted: false,\n          isAnswered: false\n        }\n\n\u003ccode\u003eisHighlighted\u003c/code\u003e e \u003ccode\u003eisAnswered\u003c/code\u003e com valores booleanos para no futuro termos um controle da interface de acordo com seus valores.\n\t\nentão passamos esse objeto para a database com \n\n    await database.ref(`rooms/${roomId}/questions`).push(question)\n\nE para consumir questões da database do Firebase vamos utilizar o hook \u003ccode\u003euseEffect(() =\u003e {}, [])\u003c/code\u003e para buscar no firebase os dados das perguntas.\n\n    useEffect(() =\u003e {\n        const roomRef = database.ref(`rooms/${roomId}`);\n\n        roomRef.on('value', room =\u003e {\n          console.log(room.val());\n        })\n      }, []) \n\nEste evento \u003ccode\u003e.on\u003c/code\u003e irá disparar uma vez com os dados iniciais armazenados neste local e, em seguida, disparar novamente cada vez que os dados forem alterados.\n\nO \u003ccode\u003econsole.log(room.val())\u003c/code\u003e vai nos devolver um objeto com authorId:string, questions:object, title:string.\n\nentão definimos o tipo de objeto\n\n\n    //Record para tipar objetos, e dentro de \u003c\u003e fica o tipo da chave\n    type FirebaseQuestions = Record\u003cstring, {\n      author: {\n        name: string;\n        avatar: string;\n      }\n      content: string; \n      isAnswered: boolean;\n      isHighlighted: boolean;\n    }\u003e\n\nentão definimos a constante: \n\n    const firebaseQuestions: FirebaseQuestions = databaseRoom.questions;\n\ne transformamos esse objeto em um vetor com \u003ccode\u003eObject.entries();\u003c/code\u003e\n\n    const parsedQuestions = Object.entries(firebaseQuestions)\n\ndessa forma o objeto \u003ccode\u003e{\"name\": \"Eduardo\", \"cidade\": \"belém\"}\u003c/code\u003e vai retornar \u003ccode\u003e[[\"name\", \"eduardo\"], [\"cidade\",\"belém\"]]\u003c/code\u003e\n\nentão podemos utilizar o \u003ccode\u003e.map(value =\u003e {})\u003c/code\u003e tratando o \u003ccode\u003evalue\u003c/code\u003e como um vetor, fazendo uma desestruturação sabendo que o primeiro valor é a chave e o segundo valor é o valor dessa chave. \u003ccode\u003e[key, value]\u003c/code\u003e\n\n\n    const parsedQuestions = Object.entries(firebaseQuestions).map(([key, value]) =\u003e {\n            return {\n              id: key,\n              content: value.content,\n              author: value.author,\n              isHighlighted: value.isHighlighted,\n              isAnswered: value.isAnswered,\n            }\n          })\n\nagora que temos um [] que contém um object com as perguntas, precisamos salvar isso em algum estado.\n\nCriamos um para as perguntas e um para o titulo.  \n\n    const [questions,setQuestions] = useState\u003cQuestion[]\u003e([])\n    const [title, setTitle] = useState('')\n\ne definimos o tipo do estado das perguntas: \n\n    type Question = {\n      id:string;\n      author: {\n        name: string;\n        avatar: string;\n      }\n      content: string; \n      isAnswered: boolean;\n      isHighlighted: boolean;\n    }\n\ne passamos os valores para os estados ainda dentro de \u003ccodeuseEffect()\u003c/code\u003e\n\n    setTitle(databaseRoom.title)\n    setQuestions(parsedQuestions)\n\n\n\nAgora basta usarmos essas informações na interface. \n\n\n## Dia 4 \n\n\n\u003ch2\u003eEstrutura das perguntas HTML e CSS\u003c/h2\u003e\n\n\nFoi feito um componente Question com HTML e CSS para servir como a div que vai conter as perguntas.\nEsse componente foi importado para Room.tsx onde foi feito um \u003ccode\u003e.map()\u003c/code\u003e nele.\n\n\t{questions.map(question =\u003e {\n\t\t      return (\n\t\t\t\u003cQuestion\n\t\t\t  key={question.id}\n\t\t\t  content={question.content}\n\t\t\t  author={question.author}\n\t\t\t/\u003e\n\t\t      )\n\t\t    })}\n\nAgora todo item contido em questions vai retornar como um componente \u003ccode\u003eQuestion\u003c/code\u003e.\n\n\u003ch2\u003eCriando o hook useRoom\u003c/h2\u003e\n\nCriamos uma função chamada \u003ccode\u003euseRoom()\u003c/code\u003e e agora temos que trazer todas as funcionalidades que vão ser utilizadas tanto na página do usuário quanto na página do admin .\nEntão pegamos a parte de carregamento das questões \n\nPassamos as funções de \u003ccode\u003euseEffect()\u003c/code\u003e do arquivo Room.tsx, suas tipagens, os estados:questions e title, e então exportamos dessa função \u003ccode\u003euseRoom()\u003c/code\u003e apenas as perguntas e os titulos, para que possamos importar de volta no Room.tsx.\n\n\t\n\treturn { questions, title }. \n\n\t\n\nMas para que o firebase consiga localizar aonde queremos fazer a referência no banco de dados é necessário do \u003ccode\u003eroomId\u003c/code\u003e, que é os pedaços dinâmicos do URL da página que colocamos como placeholder no path, precisamos passar essa rota para o \u003ccode\u003euseRoom()\u003c/code\u003e, então : \u003ccode\u003euseRoom(roomId: string)\u003c/code\u003e\n\nAgora quando usarmos o hook na page Room.tsx passamos o \u003ccode\u003eroomId\u003c/code\u003e, que é o id da pagina no Route que foi inserido pelo \u003ccode\u003ehandleCreateRoom\u003c/code\u003e na page NewRoom.tsx\n\n\tconst { questions, title } = useRoom(roomId)\n\nFeito isso, o código na page Room.tsx já parece muito mais limpo e podemos aproveitar essa funcionalidade na página do admin!\n\nCriamos a page AdminRoom.tsx copiando toda a page Room.tsx, retiramos todo o \u003ccode\u003eform\u003c/code\u003e e adicionamos o componente \u003ccode\u003eButton\u003c/code\u003e no header. \n\nNo componente \u003ccode\u003eButton\u003c/code\u003e foi passado um type \u003ccode\u003e{ isOutlined?: boolean }\u003c/code\u003e e nas props da function agora podemos passar \u003ccode\u003e({isOutlined = false, ...props})\u003c/code\u003e \n\nEntão colocamos uma condicional no className:\n\n\t\n\tclassName={`button ${isOutlined? 'outlined' : ''}`}\n\n\t\nE agora caso \u003ccode\u003eisOutlined\u003c/code\u003e seja \u003ccode\u003etrue\u003c/code\u003e a classe outlined também é aplicada. \n\n\u003ch2\u003eCriando funcionalidade de Like\u003c/h2\u003e\n\nDepois de feito o CSS do botão do like, é criado na page Room.tsx uma função assíncrona que recebe a \u003ccode\u003equestionId\u003c/code\u003e e a informação se já foi dado o like ou não.\n\n\thandleLikeQuestion(questionId:string, likeId: string | undefined) {}\n\nessa função vai fazer o push para a database com o authorId.\n\nPrimeiro fazemos uma condição para saber se o usuário já deu o like ou não.\n\n\tif (likeId) {\n\tawait database.ref(`rooms/${roomId}/questions/${questionId}/likes/${likeId}`).remove()\n\t}\n\nCaso retorne false então selecionamos a localização na database.\n\n\tawait database.ref(`rooms/${roomId}/questions/${questionId}/likes`)\n\ne enviamos os dados nessa localização\n\n\tawait database.ref(`rooms/${roomId}/questions/${questionId}/likes`).push({\n\t\tauthorId: user?.id,\n\t})\n\n\nPara contarmos os números de likes é necessário voltarmos no nosso hook \u003ccode\u003euseRoom()\u003c/code\u003e\n\nAdicionamos a linha \u003ccode\u003elikeCount: Object.values(value.likes ?? {}).length\u003c/code\u003e para que a gente receba a quantidade de objetos com o \u003ccode\u003eauthorId\u003c/code\u003e que foi passado anteriormente e o \u003ccode\u003e?? {}\u003c/code\u003e serve para caso não tenha nenhum. \n\nE para acompanhar se o usuário deu like ou não precisamos pegar seus dados de autenticação com \u003ccode\u003euseAuth()\u003c/code\u003e\n\n\tconst { user } = useAuth() \n\nAgora que temos o \u003ccode\u003euser.id\u003c/code\u003e adicionamos a linha \n\n\tlikeId: Object.entries(value.likes ?? {}).find(([ key , like ]) =\u003e like.authorId === user?.id)?.[0]\n\n\u003ccode\u003e.find()\u003c/code\u003e percorre o array até encontrar uma condição que satisfaça o que passamos para ele, retornando seu conteúdo.\n\n\u003ccode\u003e?.[0]\u003c/code\u003e retorna nulo caso ele não ache nada na posição 0.\n\nEntão pegamos cada um dos like e verificamos se o authorId é igual ao \u003ccode\u003euser?.id\u003c/code\u003e.\n\nAgora adicionamos cada um no QuestionType informando seus tipos. \n\n\ttype QuestionType = {\n\t  id:string;\n\t  author: {\n\t    name: string;\n\t    avatar: string;\n\t  }\n\t  content: string; \n\t  isAnswered: boolean;\n\t  isHighlighted: boolean;\n\t  likeCount: number;\n\t  likeId: string | undefined;\n\t}\n\nE atualizamos também a tipagem no FirebaseQuestions\n\n\ttype FirebaseQuestions = Record\u003cstring, {\n\t  author: {\n\t    name: string;\n\t    avatar: string;\n\t  }\n\t  content: string; \n\t  isAnswered: boolean;\n\t  isHighlighted: boolean;\n\t  likes: Record\u003cstring, {\n\t    authorId:string;\n\t  }\u003e\n\t}\u003e\n\npara remover todos os event listener utilizamos \n\n\treturn () =\u003e {\n\troomRef.off('value')\n\t}\n\nE no final adicionamos \u003ccode\u003euser?.id\u003c/code\u003e no array de dependências, pois essa variável não está sendo definida dentro do \u003ccode\u003euseEffect()\u003c/code\u003e\n\nEntão fica: \n\n\tuseEffect(() =\u003e {\n\t    const roomRef = database.ref(`rooms/${roomId}`);\n\n\t    roomRef.on('value', room =\u003e {\n\t      const databaseRoom = room.val();\n\t      const firebaseQuestions: FirebaseQuestions = databaseRoom.questions ?? {};\n\n\t      const parsedQuestions = Object.entries(firebaseQuestions).map(([key, value]) =\u003e {\n\t\treturn {\n\t\t  id: key,\n\t\t  content: value.content,\n\t\t  author: value.author,\n\t\t  isHighlighted: value.isHighlighted,\n\t\t  isAnswered: value.isAnswered,\n\t\t  likeCount: Object.values(value.likes ?? {}).length,\n\t\t   likeId: Object.entries(value.likes ?? {}).find(([key, like]) =\u003e like.authorId === user?.id)?.[0],\n\t\t}\n\t      })\n\n\t      setTitle(databaseRoom.title)\n\t      setQuestions(parsedQuestions)\n\t    })\n\n\t    return () =\u003e {\n\t      roomRef.off('value')\n\t    }\n\t  }, [roomId, user?.id]) \n\n\nEntão agora no botão adicionamos uma classe para caso \u003ccode\u003elikeId\u003c/code\u003e retorne o Id do usuário.\n\n\tclassName={`like-button ${question.likeId ? 'liked' : ''}`}\n\nE a função onClick:\n\n\tonClick={() =\u003e handleLikeQuestion(question.id, question.likeId)}\n\n\nPronto! a funcionalidade de dar like está completa. \n\n\n\u003ch2\u003eRemoção de pergunta sem o modal\u003c/h2\u003e\n\nPrecisamos criar um botão dentro de \u003ccode\u003e\u003cQuestions\u003e\u003c/code\u003e  que recebe a função \u003ccode\u003ehandleDeleteQuestion(question.id)\u003c/code\u003e\nE essa função assíncrona que recebe uma string:\n\n\tasync function handleDeleteQuestion(questionId: string) {\n\t    if (window.confirm('Tem certeza que deseja excluir essa pergunta?')) {\n\t      await database.ref(`rooms/${roomId}/questions/${questionId}`).remove();\n\t    }\n\t}\n\nSe \u003ccode\u003ewindow.confirm()\u003c/code\u003e retornar \u003ccode\u003etrue\u003c/code\u003e, ele acha a pergunta com a questionId na \u003ccode\u003e.ref()\u003c/code\u003e passada e remove a pergunta com \u003ccode\u003e.remove()\u003c/code\u003e\n\n\nPara encerrar a sala criamos uma função para fazer o update do objeto no banco de dados para conter a data que a sala foi encerrada e enviamos o usuário para a tela inicial do app, então:\n\n\t const history = useHistory()\n\n\t async function handleEndRoom () {\n\t    database.ref(`rooms/${roomId}`).update({\n\t      endedAt: new Date()\n\t    })\n\t    history.push('/')\n\t }\n\nE para evitar que pessoas entrem na sala colocamos no \u003ccode\u003ehandleJoinRoom()\u003c/code\u003e do Home.tsx a seguinte condicional : \n\t\n\tif (roomRef.val().endedAt) {\n\t      alert('Room already closed');\n\t      return;\n\t    }\n\t\nFim do dia 4! Ufa!\n\n\n\t\n\n## Dia 5 \n\t\n\u003ch2\u003e Criação dos botões  \u003c/h2\u003e\n\nMuito HTML e CSS para as criações dos botões \u003ccode\u003ehandleCheckQuestionAsAnswered\u003c/code\u003e e \u003ccode\u003ehandleHighlightQuestion\u003c/code\u003e\n\nFoi atualizado as tipagens do component Questions \n\n\t\ttype QuestionProps = {\n\t\t  content: string;\n\t\t  author: {\n\t\t    name: string;\n\t\t    avatar: string;\n\t\t  }\n\t\t  children?: ReactNode;\n\t\t  isAnswered?: boolean;\n\t\t  isHighlighted?: boolean;\n\t\t}\n\nE então exporta esse componente recebendo \u003ccode\u003efalse\u003c/code\u003e como a prop default \n\t\n\t\n\texport function Question({\n\t  content,\n\t  author,\n\t  isAnswered = false,\n\t  isHighlighted = false,\n\t  children,\n\t}\n\t\nE adicionado suas respectivas classes de acordo com o valor desses estados]\n\t\n\t className={cx(\n        'question',\n        { answered: isAnswered},\n        { highlighted: isHighlighted \u0026\u0026 !isAnswered}\n      )}\n\t\n\n\u003ch2\u003eHospedando o projeto\u003c/h2\u003e\n\nO hosting é feito com o próprio hosting do Firebase.\n\nO primeiro passo é instalar o Firebase Tools\n\n\tnpm install -g firebase-tools\n\nE então fazer o login no google\n\t\n\tfirebase login\n\nIr para a pasta do projeto e executar este comando no diretório raiz do seu app:\n\t\n\tfirebase init\n\t\nE precisamos dizer quais features estamos usando do Firebase, no nosso caso: Realtime Database e Hosting. \nEscolhemos usar um projeto já existente e selecionamos o public diretory : build, que é o arquivo que o create-react-app gera os arquivos para produção.\nPerguntam se é uma SPA e respondemos que sim.\n\nAgora que temos o firebase.json e os outros arquivos na nossa aplicação estamos prontos para por em produção. \n\t\nRodamos a build do projeto  \n\t\n\tyarn build\n\t\ne iniciamos o deploy. \n\t\n\tfirebase deploy\n\t\nE a aplicação já está funcionando online. \n\t\n\n\t\n## Minhas alterações no projeto\n\n\u003ch2\u003e Criar a página de lista de salas \u003c/h2\u003e\n\t\n\t\nPrimeiro criei um estado para armazenar esses dados.\n\t\n\n\tconst [rooms, setRooms] = useState\u003cRoomType\u003e([])\n\t\n\t\npara criar a página de lista de salas, criei o arquivo RoomList.tsx e usei o hook \u003ccode\u003euseEffect()\u003c/code\u003e para carregar os dados necessários para renderizar a sala. \n\t\n\nPeguei a referência do meu banco de dados. \n\t\n\tconst dbRef = database.ref(`rooms`);\n\nEntão li todos os dados e retornei eles em um array contendo vários objetos.\n\n\tdbRef.once('value', rooms =\u003e {\n\t      const dbRoom: object = rooms.val() ?? {}\n\t      const parsedRooms = Object.entries(dbRoom).map(([key,value]) =\u003e {\n\t\treturn {\n\t\t  roomId: key,\n\t\t  title: value.title,\n\t\t  roomIsOpen: value.roomIsOpen\n\t\t}\n\t      })\n\t      setRooms(parsedRooms)\n\t})\n\t\nFiz as devidas tipagens de como eu queria esse objeto dentro do array.\n\t\n\ttype RoomType = {\n\t  roomId:string;\n\t  title: string;\n\t  roomIsOpen?: boolean;\n\t}[]\n\t\nAgora só preciso utilizar \u003ccode\u003e.map()\u003c/code\u003e para me retornar as salas no formato que eu quero, porém, também quero mostrar algo caso não tenha nenhuma sala disponível.\n\t\nEntão crio a seguinte condicional: \n\t\n\t{rooms.length !== 0 ? \n\t\trooms.map((item: any) =\u003e {\n\t\t  return(\n\t\t    \u003cdiv \n\t\t      className={`room-item-div ${item.roomIsOpen? '': 'closed'}`} \n\t\t      onClick={() =\u003e handleGoToRoom(item.roomId, item?.roomIsOpen)}\n\t\t      key={item.roomId} \u003e\n\t\t\t  {item.title}\n\t\t    \u003c/div\u003e)}) \n         : (\n\t\t\u003cdiv className=\"empty-list\"\u003e \n\t\t  \u003ch1\u003eNão temos salas no momento\u003c/h1\u003e\n\t\t  \u003cimg src={emptyImg} alt=\"Empty Room\" /\u003e\n\t\t\u003c/div\u003e)}\n\nAgora, se rooms contém algum resultado vai aparecer: \n\t\n![Room List](.github/roomlist.png)\n\t\n\t\n\t\n\t\nE se não tiver resultado: \n\t\n\t\n![Empty Room List](.github/emptyroomlist.png)\n\t\n\t\n\u003ch2\u003eAdicionando animações CSS\u003c/h2\u003e\n\nFoi adicionado utilizando o site [Animista](https://animista.net/)\n![screen-capture](https://user-images.githubusercontent.com/82004348/123522184-2dfc2280-d692-11eb-90b9-81c42fa115a2.gif)\n\n\t\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feduardorerick%2Fletmeask","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feduardorerick%2Fletmeask","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feduardorerick%2Fletmeask/lists"}