{"componentChunkName":"component---src-templates-project-jsx","path":"/work/chicha","result":{"data":{"prismicProject":{"uid":"chicha","data":{"project_title":{"html":"<h1>Chicha</h1>","text":"Chicha"},"project_hero_image":{"url":"https://images.prismic.io/michael-portfolio/57a35c45-cf68-4d05-af3b-c10144b0f2fc_chicha-hero-4-iphones-2000.png?auto=compress%2Cformat","alt":"Chicha app screen shots"},"body":[{"__typename":"PrismicProjectBodyText","slice_type":"text","primary":{"rich_text":{"html":"<p>The world is littered with the bodies of dead “<strong>things to do</strong>” startups (<a target=\"_blank\" rel=\"noopener\" href=\"https://techcrunch.com/2015/11/05/postmates-acquihires-activity-concierge-sosh-in-a-fire-sale/\">Sosh</a>, <a target=\"_blank\" rel=\"noopener\" href=\"https://www.telegraph.co.uk/business/2016/10/21/time-out-snaps-up-one-time-rising-star-yplan-for-just-16m/\">YPlan</a>, etc.). Solving event discovery is <a target=\"_blank\" rel=\"noopener\" href=\"http://www.hughmalkin.com/blogwriter/2015/9/23/why-no-one-has-solved-event-discovery\">hard</a>, something I know from my project <em><strong><a target=\"_blank\" rel=\"noopener\" href=\"https://miniguide.co/\">Miniguide</a></strong></em>, a digital guide to Barcelona. A successful events discovery app needs to connect three groups of people:</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/c87252c6-3414-4f43-a5ab-9ec89a792b86_chicha-triangle.png?auto=compress,format\" alt=\"triangle connecting users, tastemakers and local businesses\" copyright=\"\">\n    </p>\n  <p><strong>Users</strong> want a curated list of events, <strong>tastemakers</strong> want influence and <strong>local businesses</strong> want more customers. This served as the inspiration for my final project at Ironhack: <a target=\"_blank\" rel=\"noopener\" href=\"https://chicha.app\"><strong>Chicha</strong></a>, a web app built in React with a RESTful API created using a Node.js server, Express routing and MongoDB database.</p><p>I&#39;m happy with the results, and also proud that Chicha was only the <strong>only solo project</strong> recognized by the Ironhack jury out of many talented entries. Below please find some highlights.</p><h2>Links</h2><ul><li><a target=\"_blank\" rel=\"noopener\" href=\"https://chicha.app/register\"><strong>Chicha</strong></a> (optimized for dev tools iPhone 8 view)</li><li><a  href=\"https://api.chicha.app/\"><strong>API</strong></a></li><li><a target=\"_blank\" rel=\"noopener\" href=\"https://github.com/michaelsmueller/chicha\"><strong>GitHub</strong></a> (front end)</li><li><a target=\"_blank\" rel=\"noopener\" href=\"https://github.com/michaelsmueller/chicha-api\"><strong>GitHub</strong></a> (back end)</li><li><a target=\"_blank\" rel=\"noopener\" href=\"https://www.figma.com/file/BsoiB5w5Bs3kwC1vOBkCv1/Chicha-Wireframes?node-id=0%3A1\"><strong>Wireframes</strong></a> &amp; <a target=\"_blank\" rel=\"noopener\" href=\"https://www.figma.com/file/3nKA7yEvj6A0MT0WW8CxWF/Colors?node-id=0%3A1\"><strong>colors</strong></a></li><li><a  href=\"https://trello.com/b/O8DhDgcu/chicha\"><strong>Trello</strong></a></li><li><a target=\"_blank\" rel=\"noopener\" href=\"https://docs.google.com/presentation/d/1ZDxZknsIUCLrHTaYEsYjcc_06KRAZxgNZ9bg7SMssiY/edit?usp=sharing\"><strong>Presentation</strong></a></li></ul>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/a7bdd369-7936-4a13-8a51-69d7655dfd0b_chicha-colored-rectangle.png?auto=compress,format\" alt=\"gradient colored separator\" copyright=\"\">\n    </p>\n  <h2>Process</h2><ol><li><strong>Visualize product</strong>: describe MVP as backlog of user stories tracked in Trello plus bugs and other info.</li><li><strong>Define data model</strong>: determine models – User, Event, Vote and Offer – as well as fields and relationships.</li><li><strong>Determine routes</strong>: define the back end API endpoints and front end React views (will become components).</li><li><strong>Create wireframes &amp; prototypes</strong>: design wireframes and prototypes (typefaces, colors, etc.) in Figma.</li><li><strong>Configure database</strong>: configure MongoDB database in Atlas cloud.</li><li><strong>Scaffold back end</strong>: set up Node.js / Express API starter, connect DB, commit to GitHub and deploy to Heroku.</li><li><strong>Scaffold front end</strong>: set up React starter (Create React App) and deploy to Netlify.</li><li><strong>Code MVP</strong>: following product backlog, develop both front and back end in parallel.</li><li><strong>Apply styles</strong>: after MVP functionality is working, write CSS.</li><li><strong>Code additional features</strong>: the backlog only grows with time!</li></ol><h2>Getting data from Facebook</h2><p>Users add events to Chicha by pasting a <strong>Facebook event URL</strong>. Following the Cambridge Analytica scandal in 2018, Facebook cut off access to user event data through its Graph API. But you can still access <strong>your own</strong> event data!</p><p>So to work around the restriction, when a user submits an event, the server launches a <strong>Puppeteer headless browser</strong> instance, logs in to Facebook with my personal user, clicks &quot;interested&quot; on the event and finally the server is able to request the event data from Graph API.</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/aa1fdb9c-87e3-439c-96ab-92faf906c096_chicha-facebook-process.png?auto=compress,format\" alt=\"server process flow to request data from Facebook Graph API\" copyright=\"\">\n    </p>\n  <p>In order to defeat Facebook bot detection, I used the <a target=\"_blank\" rel=\"noopener\" href=\"https://www.npmjs.com/package/puppeteer-extra-plugin-stealth\"><strong>Stealth Plugin</strong></a> for Puppeteer Extra. I also connected using a <strong>VPN</strong> from the same geographic location where the server (Heroku) is based when running localhost.</p><p>The nice thing about this functionality is the user can simply copy a Facebook URL to add an event, no form-filling is necessary. The user can later update the event and modify the details if they so desire.</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/a872aa6c-0b7f-4926-945c-00143071d8cc_chicha-add-event.gif?auto=compress,format\" alt=\"Chicha animated screen shot, add event\" copyright=\"\">\n    </p>\n  <p>Here is some of the code on the server that handles the launch of Puppeteer and Facebook login / navigation:</p>"}}},{"__typename":"PrismicProjectBodyCodeJavascript","slice_type":"code_javascript","primary":{"code_text":{"text":"const likeEvent = async (url) => {\n  const eventId = getEventId(url);\n  const mobileUrl = `https://m.facebook.com/events/${eventId}`;\n  const args = [\n    '--no-sandbox',\n    '--disable-setuid-sandbox',\n    '--disable-dev-shm-usage',\n    '--single-process',\n  ];\n  const options = { args, headless: true };\n  const browser = await puppeteer.launch(options);\n  const page = await browser.newPage();\n  await page.setViewport({ width: 800, height: 600 })\n  if (Object.keys(cookies).length) await page.setCookie(...cookies);\n  await loginToFacebook(page);\n  await goToEventAndClickInterested(mobileUrl, page);\n  await page.waitFor(1500);\n  browser.close();\n};\n\nconst goToEventAndClickInterested = async (mobileUrl, page) => {\n  await page.goto(mobileUrl, { waitUntil: 'networkidle2' });\n  const links = await page.$x(\"//a[contains(., 'Interested')]\");\n  await links[0].click();\n};\n\nconst checkIfLoggedIn = async (page) => {\n  if (await page.$('#pass')) return false;\n  else return true;\n};\n\nconst loginToFacebook = async (page) => {\n  try {\n    await page.goto('https://www.facebook.com/login', { waitUntil: 'networkidle2' });\n  } catch (error) {\n    console.log('error navigating to Facebook login');\n  }\n  const isLoggedIn = await checkIfLoggedIn(page);\n  const isLoggedIn = true;\n  if (!isLoggedIn) {\n    await page.type('#email', FB_USERNAME, { delay: 1 });\n    await page.type('#pass', FB_PASSWORD, { delay: 1 });\n    await page.click('#loginbutton');\n    await page.waitFor(2000);\n    await writeCookies(page);\n  }\n};\n\nconst writeCookies = async (page) => {\n  const currentCookies = await page.cookies();\n  fs.writeFileSync('./cookies.json', JSON.stringify(currentCookies));\n}"}}},{"__typename":"PrismicProjectBodyText","slice_type":"text","primary":{"rich_text":{"html":"<h2>Design</h2><p>Previously I&#39;ve used Sketch but for this project decided to try Figma for <strong>wireframing</strong>:</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/0d385194-b854-48e1-b6f5-1074182108fe_chicha-wireframes-2-rows.png?auto=compress,format\" alt=\"Chicha wireframes in Figma\" copyright=\"\">\n    </p>\n  <p>The <strong>colors</strong> are inspired by Peruvian <em>chicha</em> street art:</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/132a9ac5-a537-4dc7-aa93-6c3550dd205e_chicha-fiesta-popular.jpg?auto=compress,format\" alt=\"Peruvian street art, Fiesta Popular\" copyright=\"\">\n    </p>\n  <p>For typefaces I used <strong>Roboto Slab</strong> (serif, titles), <strong>Open Sans</strong> (sans serif, text) and <strong>Material Design</strong> (icons). I mixed and matched the following <strong>CSS custom properties</strong> to create light and dark modes:</p>"}}},{"__typename":"PrismicProjectBodyCodeCss","slice_type":"code_css","primary":{"code_text":{"text":":root {\n  --near-black: #212121;\n  --gray-primary: #565656;\n  --gray-lighter: #b2b2b2;\n  --gray-lightest: #e4e4e4;\n  --near-white: rgba(255, 255, 255, 0.9);\n  --yellow-primary: #fef200;\n  --yellow-lighter: #ffff8b;\n  --pink-primary: #ee2B7a;\n  --blue-primary: #59c7de;\n  --blue-primary-faded: rgba(89, 199, 222, 0.2);\n  --blue-lighter: #7fd5e6;\n  --blue-lighter-faded: rgba(127, 213, 230, 0.6);\n  --blue-lightest: #ccfcff;\n  --heavy-shadow: rgba(0, 0, 0, 0.4);\n  --light-shadow: rgba(0, 0, 0, 0.1);\n  --white-shadow: rgba(255, 255, 255, 0.15);\n}\n\nbody.light {\n  --bg-color: white;\n  --text-color: var(--near-black);\n  --text-gray: var(--gray-primary);\n  --button-bg: white;\n  --control-bg: var(--near-white);\n  --blue-bg: var(--blue-primary-faded);\n  --light-line: var(--gray-lightest);\n  --shadow: var(--light-shadow);\n}\n\nbody.dark {\n  --bg-color: var(--near-black);\n  --text-color: var(--near-white);\n  --text-gray: var(--gray-lighter);\n  --button-bg: var(--near-white);\n  --control-bg: var(--near-white);\n  --blue-bg: var(--blue-lighter-faded);\n  --light-line: var(--gray-primary);\n  --shadow: var(--white-shadow);\n}"}}},{"__typename":"PrismicProjectBodyText","slice_type":"text","primary":{"rich_text":{"html":"<h2>Dark mode</h2><p>Dark mode is trendy, and for good reason. When you&#39;re lying in bed scrolling through your phone, that app better have dark mode!</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/dd46796b-cadc-4060-8998-b4fdd85d0378_chicha-dark-mode.gif?auto=compress,format\" alt=\"Chicha, animated screen shot, dark mode\" copyright=\"\">\n    </p>\n  <h2>Dragger</h2><p>For the events page with the map and listings , I built a drag-to-resize drawer myself instead of importing a package.</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/25e8b8cb-33b2-4b40-a748-95f9d3814b0f_chicha-dragger.gif?auto=compress,format\" alt=\"Chicha animated screen shot, drag to resize\" copyright=\"\">\n    </p>\n  <p>Here&#39;s the code for the component, which picks up the user&#39;s touch when it&#39;s near the handle and resizes the drawer until the user lifts their finger:</p>"}}},{"__typename":"PrismicProjectBodyCodeJsx","slice_type":"code_jsx","primary":{"code_text":{"text":"export default class DragToResizeDrawer extends Component {\n  state = { isResizing: false, marginTop: 350 };\n\n  isTouchOnHandle = (clientY) => {\n    const distanceBelowHandle = clientY - this.state.marginTop;\n    if (distanceBelowHandle < 40) return true;\n    else return false;\n  }\n\n  onTouchStart = (e) => {\n    const { clientY } = e.touches[0];\n    const touchIsOnHandle = this.isTouchOnHandle(clientY);\n    if (touchIsOnHandle) this.setState({ isResizing: true })\n  }\n\n  onTouchMove = (e) => {\n    const { isResizing } = this.state;\n    const { clientY } = e.touches[0];\n    const maxTopMargin = window.innerHeight * 0.8; // map container height is 80vh;\n    const marginTop = Math.max(0, clientY < maxTopMargin ? clientY : maxTopMargin );\n    if (isResizing) this.setState({ marginTop });\n  }\n\n  onTouchEnd = (e) => this.setState({ isResizing: false })\n\n  componentDidMount = () => {\n    document.addEventListener('onTouchStart', this.onTouchStart);\n    document.addEventListener('onTouchMove', this.onTouchMove);\n    document.addEventListener('onTouchEnd', this.onTouchEnd);\n  }\n\n  componentWillUnmount = () => {\n    document.removeEventListener('onTouchStart', this.onTouchStart);\n    document.removeEventListener('onTouchMove', this.onTouchMove);\n    document.removeEventListener('onTouchEnd', this.onTouchEnd);\n  }\n\n  render() {\n    const { marginTop } = this.state;\n    const dragHandleStyle = { marginTop };\n    const draggerStyle = {\n      marginTop: marginTop + 20,\n      overflow: this.state.isResizing ? 'hidden' : 'scroll',\n      paddingBottom: marginTop + 70,\n    }\n    return (\n      <div className='drag-to-resize-container' onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove} onTouchEnd={this.onTouchEnd}>\n        <div className='drag-handle' style={dragHandleStyle}><i className='material-icons'>drag_handle</i></div>\n        <div className='dragger' style={draggerStyle}>{this.props.children}</div>\n      </div>\n    );\n  }\n}"}}},{"__typename":"PrismicProjectBodyText","slice_type":"text","primary":{"rich_text":{"html":"<h2>QR Codes</h2><p>Users earn points by sharing events and voting, and can then redeem them for <strong>offers</strong> at local businesses.</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/8880df2f-24d4-4c25-9d2f-962e50875af0_chicha-get-coupon.gif?auto=compress,format\" alt=\"Chicha animated screen shot, get coupon.\" copyright=\"\">\n    </p>\n  <p>The QR code encodes a <strong>coupon ID</strong> which may be scanned by an authorized merchant. When the merchant scans the QR code, the encoded coupon ID is read and sent to the server, which checks to ensure the code is valid. Scan dates are stored in the database to prevent the same QR code being used more than once.</p>\n    <p class=\" block-img\">\n      <img src=\"https://images.prismic.io/michael-portfolio/f8efea91-ac03-41a2-b1a0-8985271ab25d_chicha-scan-coupon.gif?auto=compress,format\" alt=\"Chicha animated screen shot, scan and redeem coupon\" copyright=\"\">\n    </p>\n  <h2>API routes</h2><p>As noted, Chicha has models for User, Event, Vote and Offer with back end Express routing that receives request from the front end React app that allow for <strong>CRUD</strong> – create, read, update and delete – operations. Below is the code for the front end API service; for the back end API endpoints check my <a target=\"_blank\" rel=\"noopener\" href=\"https://github.com/michaelsmueller/chicha-api\">API repository</a> on GitHub.</p>"}}},{"__typename":"PrismicProjectBodyCodeJavascript","slice_type":"code_javascript","primary":{"code_text":{"text":"import axios from 'axios';\n\nclass ApiClient {\n  constructor() {\n    this.apiClient = axios.create({\n      baseURL: process.env.REACT_APP_BACKEND_URI,\n      withCredentials: true,\n      headers: { 'X-Requested-With': 'XMLHttpRequest' },\n    });\n  }\n\n  whoami = () => this.apiClient.get('/whoami');\n  signIn = (body) => this.apiClient.post('/signin', body);\n  logout = () => this.apiClient.get('/logout');\n\n  register = (user) => this.apiClient.post('/users', user);\n  getUser = (userId) => this.apiClient.get(`/users/${userId}`);\n  editUser = (userId, body) => this.apiClient.put(`/users/${userId}`, body);\n  deleteUser = (userId) => this.apiClient.delete(`/users/${userId}`);\n  getHeavies = () => this.apiClient.get('/users/heavies');\n\n  addCoupon = (userId, body) => this.apiClient.patch(`/users/${userId}/coupons`, body);\n  getUserWithCoupon = (couponId) => this.apiClient.get(`/users/find?coupon=${couponId}`);\n  redeemCoupon = (userId, couponId) => this.apiClient.patch(`/users/${userId}/coupons/${couponId}`);\n  getRedeemedCoupons = (partnerId) => this.apiClient.get(`/users/coupons/find?partner=${partnerId}`)\n\n  getOffer = (offerId) => this.apiClient.get(`/offers/${offerId}`);\n  getOffers = () => this.apiClient.get('/offers');\n\n  addEvent = (body) => this.apiClient.post('/events', body);\n  getEvent = (eventId) => this.apiClient.get(`/events/${eventId}`);\n  getEvents = () => this.apiClient.get('/events');\n  editEvent = (eventId, body) => this.apiClient.put(`/events/${eventId}`, body);\n  deleteEvent = (eventId) => this.apiClient.delete(`/events/${eventId}`);\n  searchEvents = (query) => this.apiClient.get(`/events/search?query=${query}`);\n\n  createVote = (body) => this.apiClient.post('/votes', body);\n  getVotes = () => this.apiClient.get('/votes');\n  changeVote = (voteId, body) => this.apiClient.put(`/votes/${voteId}`, body);\n  removeVote = (voteId, eventId, direction) => this.apiClient.delete(`/votes/${voteId}?eventid=${eventId}&direction=${direction}`);\n}\n\nconst apiClient = new ApiClient();\nexport default apiClient;"}}}]}},"site":{"siteMetadata":{"title":"Michael Mueller | web / blockchain & financial advisor","description":"My work portfolio.","author":"Michael Mueller","siteUrl":"https://michaelmueller.dev"}}},"pageContext":{"uid":"chicha"}},"staticQueryHashes":["3649515864"]}