[{"data":1,"prerenderedAt":431},["ShallowReactive",2],{"$pj5rZOPieK":3},[4,198,343],{"id":5,"title":6,"body":7,"case_studies":171,"client":172,"cover":23,"date":173,"description":174,"excerpt":175,"extension":179,"headline":180,"meta":181,"navigation":182,"order":183,"partner":184,"partner_link":185,"path":186,"seo":187,"stem":188,"technology":189,"__hash__":197},"portfolio\u002Fportfolio\u002Fgrappos.md","Grappos Product Locator",{"type":8,"value":9,"toc":154},"minimark",[10,14,24,29,32,42,45,48,51,55,58,61,64,69,72,75,78,82,115,119,123,126,130,133,137,140,144,147,151],[11,12,13],"p",{},"Grappos' Product Locator is a highly configurable application that can be embedded in a customer's website to provide a branded, accessible UI for finding products based on the visitor's location. Used extensively in the Beverage and CPG industries, this application has grown to adapt to client's needs for reporting, tracking, branding, and website technology.",[11,15,16],{},[17,18],"img",{"alt":19,"className":20,"src":23},"screenshots of Grappos Locator in different modes",[21,22],"my-12","rounded-2xl","\u002Fimg\u002Fportfolio\u002Fgrappos\u002Flineup.png",[25,26,28],"h2",{"id":27},"the-challenge","The Challenge",[11,30,31],{},"Product locators tend to come in a couple categories:",[33,34,35,39],"ul",{},[36,37,38],"li",{},"Manual and rigid",[36,40,41],{},"Complex and expensive",[11,43,44],{},"Grappos entered the market with a vision to deliver premium features, high-touch customer service, and an exceptional user experience. The goal was to provide enterprise-class features, without the complexity and cost of larger competitors.",[11,46,47],{},"The final product needed to be able to embed directly in a customer's website and meet the needs for branding, customized functionality, and analytics.",[11,49,50],{},"In addition to consent laws like GDPR and CIPA, the beverage market is also a frequent target for legal action for failure to meet accessibility criteria. In order to succeed, the Locator needed to avoid tracking personal data by default, and provide a compliant and genuinely usable interface.",[25,52,54],{"id":53},"the-solution","The Solution",[11,56,57],{},"The Grappos Locator brings together quality data from their custom REST API, a huge number of options for customization, GDPR compliant activity tracking, and WCAG 2.1 AA certification.",[11,59,60],{},"I began by breaking down the provided designs into reusable components, following the need to reuse data across multiple interface elements.",[11,62,63],{},"Since this interface powers critical sales path functionality, there is zero tolerance for downtime and breaking changes. A robust suite of checks and tests help prevent disruption, while allowing rapid iteration and continuous deployment.",[65,66,68],"h3",{"id":67},"my-role","My Role",[11,70,71],{},"I own the public UI end-to-end. I have built and iterated the UI across React and Vue, from Javascript into TypeScript, and migrated dependencies like moving from Google Maps to MapBox. I was responsible for interfacing with our partners for accessibility audits, taking their feedback and making adjustments that worked within our needs.",[11,73,74],{},"I frequently interface with clients and their technology\u002Fmarketing teams to help address edge case needs for installing the locator or implementing tracking.",[11,76,77],{},"I consult with our development partner company on how to implement and manage the Public API and Admin interfaces, and keep it aligned with Grappos' needs.",[25,79,81],{"id":80},"key-features","Key Features",[33,83,84,91,97,103,109],{},[36,85,86,90],{},[87,88,89],"strong",{},"Flexible Branding"," — Configurable branding colors that affect buttons, text, and other UI elements. Also configurable are custom background images, fonts, and map styles.",[36,92,93,96],{},[87,94,95],{},"Multiple Embed strategies"," — Init script provides options to embed the locator as an iFrame or a Web Component, delay initialization, even deliver the app from a custom domain.",[36,98,99,102],{},[87,100,101],{},"Location Detection"," — A user's location can be discovered silently with AWS's CloudFront headers, explicity via the browser's Geolocation API, or manually by typing a location into the Search Box. The Search Box implements MapBox's Search API.",[36,104,105,108],{},[87,106,107],{},"Custom Analytics"," — A client can optionally add behavior tracking with native integrations into GTM, Google Analytics, Meta, Snapchat, Pinterest, TikTok, and bespoke tracking pixels.",[36,110,111,114],{},[87,112,113],{},"Accessible experience"," — Audited to meet WCAG 2.1, level AA standards.",[25,116,118],{"id":117},"built-with","Built With",[65,120,122],{"id":121},"ui","UI",[11,124,125],{},"The application interface is built with Vue + TypeScript and utilizes MapBox services for mapping rendering, reverse geolocation, and place search. Tailwind provides the utility classes",[65,127,129],{"id":128},"build","Build",[11,131,132],{},"The application is built with Vite, including a secondary process for building a collection of init scripts based environment variables and common modules. The code is kept to a consistent standard with eslint, and Prettier.",[65,134,136],{"id":135},"testing","Testing",[11,138,139],{},"Unit testing is implemented in Vitetest, covering utilities, api calls, and state modification. Playwright provides E2E testing, with the a11y package to prevent regression from WCAG requirements.",[65,141,143],{"id":142},"deployment","Deployment",[11,145,146],{},"The UI and API are served by Amazon Web Services. CodePipeline is triggered by CodeCommit, built with CodeBuild, deployed to S3, and invalidated on CloudFront. Multiple pipelines exist to deploy to development, staging, and production environments.",[65,148,150],{"id":149},"api","API",[11,152,153],{},"The Public API runs on AWS' Elastic Beanstalk and served via CloudFront. It is powered by Node.js, with data housed in OpenSearch.",{"title":155,"searchDepth":156,"depth":156,"links":157},"",2,[158,159,163,164],{"id":27,"depth":156,"text":28},{"id":53,"depth":156,"text":54,"children":160},[161],{"id":67,"depth":162,"text":68},3,{"id":80,"depth":156,"text":81},{"id":117,"depth":156,"text":118,"children":165},[166,167,168,169,170],{"id":121,"depth":162,"text":122},{"id":128,"depth":162,"text":129},{"id":135,"depth":162,"text":136},{"id":142,"depth":162,"text":143},{"id":149,"depth":162,"text":150},null,"Grappos","2024-12-31","Configurable locator that provides a branded, accessible UI for finding products based on the user's location",{"type":8,"value":176},[177],[11,178,13],{},"md","Embeddable Product Locator app for Beverage and CPG brands",{},true,1,"Grappos, LLC","https:\u002F\u002Fwww.grappos.com\u002F","\u002Fportfolio\u002Fgrappos",{"title":6,"description":174},"portfolio\u002Fgrappos",[190,191,192,193,194,195,196],"TypeScript","Vue","Tailwind","WCAG","Playwright","Node.js","OpenSearch","SCN8BX_pfUAQyceM27sI_iy1xMjbrwXTMwJWreFtnOc",{"id":199,"title":200,"body":201,"case_studies":318,"client":200,"cover":214,"date":173,"description":325,"excerpt":326,"extension":179,"headline":330,"meta":331,"navigation":182,"order":156,"partner":332,"partner_link":333,"path":334,"seo":335,"stem":336,"technology":337,"__hash__":342},"portfolio\u002Fportfolio\u002Fmarket-muscles.md","MarketMuscles",{"type":8,"value":202,"toc":311},[203,206,215,219,222,225,229,232,235,238,241,245,248,251,260,267,271,274,281,290,294],[11,204,205],{},"Market Muscles provides an all-in-one marketing and membership management platform for martial arts and dance studios. I joined in October 2018 as the first full-stack engineer, with the company scaling rapidly over the next 18 months.",[11,207,208],{},[17,209],{"alt":210,"className":211,"src":214},"image alt text",[212,213,21,22],"md:w-1\u002F3","md:float-right","\u002Fimg\u002Fportfolio\u002Fmarketmuscles\u002Fmessages-ui.jpg",[25,216,218],{"id":217},"role","Role",[11,220,221],{},"When I first joined Market Muscles, they had a complex series of WordPress plugins that handled basic lead form collection, and a very poorly performing messaging system which had been built by contractors over the previous 12 months. Like most startup codebases, the code worked, but it was difficult to understand, maintain, and it did not scale.",[11,223,224],{},"My role over the first 6 months was to understand how customers used the software and get to know the codebase by handling support tickets. Throughout the process I was able to build enough context to determine ways to improve it.",[25,226,228],{"id":227},"version-one","Version One",[11,230,231],{},"The first big endeavour was to migrate the core functionality out of WordPress and into a multi-tenant, headless application built on Laravel and a UI built with Vue that could be embedded in the WordPress admin interface.",[11,233,234],{},"Part of this iteration involved migrating hundreds of thousands of records out of 100+ individual WordPress installs and into a centralized MySQL database. I developed custom migration scripts to handle the extract-transform-load process, including creating new accounts in the multi-tenant system and assigning imported data to the correct owners.",[11,236,237],{},"This phase also expanded functionality by providing an interface for two-way SMS messaging and adding features to the marketing workflows system.",[11,239,240],{},"I also formalized policies for GitHub branching, created a standardized deployment process, and shifted to serving assets from AWS CloudFront.",[25,242,244],{"id":243},"version-two","Version Two",[11,246,247],{},"There were several major drivers to begin another major iteration. Market Muscles wanted to massively expand the CRM functionality, marketing automation tools, and break into the membership management industry. For this phase, I hired and onboarded an additional US-based software engineer and contracted with two offshore developers.",[11,249,250],{},"The first constraint to be addressed was to move the UI out of an embedded spot in their website admin and into a full web application experience. The Laravel application was updated with a login flow, dashboard, and dedicated interfaces for each feature. The new UI utilized Tailwind, Vue, and Inertia.",[11,252,253,254,259],{},"Rapid growth of our customer base and the increased utility of our Bulk Messaging and Marketing Automation features exposed the need to optimize these workflows. A daily flood of 10s of thousands of messages required us to rethink this system. Read more about the solution in ",[255,256,258],"a",{"href":257},"\u002Fcase-study\u002Fmm-automated-messaging","the Automated Messaging case study",".",[11,261,262,263,259],{},"To meet the increase in traffic and requirements for security and uptime, I designed a distributed system that shares workload and avoids single-points-of-failure. Read more about this solution in ",[255,264,266],{"href":265},"\u002Fcase-study\u002Fmm-high-availability","the High Availability case study",[25,268,270],{"id":269},"version-three","Version Three",[11,272,273],{},"This iteration brought together the years of data and experience from the previous versions and let us focus on crafting a refined experience for business owners and their customers. I hired and brought up an additional US-based software engineer, as well as another EU-based developer.",[11,275,276,277,259],{},"The a major step in this version was to codify best practices, coding standards, testing, and static analysis across the codebase. As we progressively refactored into utilizing strict typing in PHP, these tools helped us surface issues without needing to write explicit tests. Read more about how this was addressed in ",[255,278,280],{"href":279},"\u002Fcase-study\u002Fmm-static-analysis-testing-linting-laravel","the Static Analysis, Testing, and Linting case study",[11,282,283,284,289],{},"Another benefit of this refactor was to migrate to a Domain-Driven Design pattern. The rapidly expanding set of functionality required us to consider intertwining complexity and enforce ",[285,286,288],"abbr",{"title":287},"Single Responsibility Principle","SRP",", along with the rest of the SOLID design principles.",[25,291,293],{"id":292},"responsibilities-impact","Responsibilities & impact:",[33,295,296,299,302,305,308],{},[36,297,298],{},"Owned the design and implementation of the platform's infrastructure and development lifecycle",[36,300,301],{},"Led the migration of a monolithic application to a layered architecture, improving separation of concerns and enabling future complexity",[36,303,304],{},"Built and standardised shared systems for data retrieval, DTOs, components, forms, and layouts, reducing duplication and inconsistency",[36,306,307],{},"Reverse-engineered competitors' functionality to guide feature development to maintain industry relevancy and allow us to focus on defining features",[36,309,310],{},"Established conventions and patterns across the full stack that improved developer velocity and long-term maintainability",{"title":155,"searchDepth":156,"depth":156,"links":312},[313,314,315,316,317],{"id":217,"depth":156,"text":218},{"id":227,"depth":156,"text":228},{"id":243,"depth":156,"text":244},{"id":269,"depth":156,"text":270},{"id":292,"depth":156,"text":293},[319,321,323],{"title":320,"path":257},"Automated Messaging",{"title":322,"path":265},"High Availability",{"title":324,"path":279},"Static Analysis, Testing, and Linting","Membership Management CRM",{"type":8,"value":327},[328],[11,329,205],{},"Architecture and Development for a Membership Management CRM",{},"MarketMuscles, LLC","https:\u002F\u002Fwww.marketmuscles.com\u002F","\u002Fportfolio\u002Fmarket-muscles",{"title":200,"description":325},"portfolio\u002Fmarket-muscles",[338,339,190,191,192,340,341],"Enterprise Architecture","Laravel","WebSockets","Event Driven","VhlDUZmV60kVJzFhlew0S_Sk2PHihkOVCUdStOKpxsk",{"id":344,"title":345,"body":346,"case_studies":171,"client":345,"cover":356,"date":413,"description":414,"excerpt":415,"extension":179,"headline":419,"meta":420,"navigation":182,"order":162,"partner":387,"partner_link":385,"path":421,"seo":422,"stem":423,"technology":424,"__hash__":430},"portfolio\u002Fportfolio\u002Fcherry-street-pier.md","Cherry Street Pier",{"type":8,"value":347,"toc":408},[348,351,357,367,373,375,378,380,389,392,395,399,402,405],[11,349,350],{},"Cherry Street Pier is public space on the Delaware River waterfront in Philadelphia, hosting artists, performers, art, and concessions.",[11,352,353],{},[17,354],{"alt":210,"className":355,"src":356},[21,22],"\u002Fimg\u002Fportfolio\u002Fcherry-street-pier\u002Fhomepage-screenshot.png",[11,358,359,360],{},"Visit the site at ",[255,361,366],{":target":362,"href":363,"rel":364},"_blank","https:\u002F\u002Fwww.cherrystreetpier.com\u002F",[365],"nofollow","cherrystreetpier.com",[368,369,370],"blockquote",{},[11,371,372],{},"It is built into the shell of a century-old municipal pier, it is a reflection of Philadelphia today—creative, diverse, historic, adaptable, and inspired. The Pier is free to enter and open to the public.",[25,374,28],{"id":27},[11,376,377],{},"Cherry Street Pier is a mixed-use public space built on an iconic pier on Philadelphia's Delaware River waterfront. The website needed an appealing, usable interface, and strong elements that brings the brand's visual language together. Their constantly updating listing of events, artist residencies, and concessions must be managed by non-technical staff, while maintaing visual consistency.",[25,379,54],{"id":53},[11,381,382,383,388],{},"My design partner, ",[255,384,387],{":target":362,"href":385,"rel":386},"https:\u002F\u002Fronilagin.com\u002F",[365],"Roni Lagin"," designed a beautiful interface, using well paired typography, on-site photography, and nautical visual elements to build a cohesive branded experience.",[11,390,391],{},"I implemented his designs in mobile-first responsive, custom theme for WordPress. The theme utilizes multiple custom layouts and a comprehensive Advanced Custom Fields setup that allows updating pages and building new ones with consistent output.",[11,393,394],{},"Almost every content element is editable through the WordPress interface, allowing staff to easily keep the site fresh.",[25,396,398],{"id":397},"under-the-hood","Under the hood",[11,400,401],{},"This custom theme uses Sass to set up a custom framework for blocks, columns, etc. Variables are used throughout the Sass assets for rapid, consistent changes to colors, fonts, breakpoints, etc.",[11,403,404],{},"The Sass and Javascript assets are built and combined with Webpack.",[11,406,407],{},"The Advanced Custom Fields plugin is used to provide a library of custom blocks used to build pages that are visually consistent, mobile-responsive interface. This method avoids the brittle results of typical commercial page builders like Divi.",{"title":155,"searchDepth":156,"depth":156,"links":409},[410,411,412],{"id":27,"depth":156,"text":28},{"id":53,"depth":156,"text":54},{"id":397,"depth":156,"text":398},"2020-02-12","Custom WordPress theme for popular Philadelphia landmark",{"type":8,"value":416},[417],[11,418,350],{},"Custom WordPress theme development",{},"\u002Fportfolio\u002Fcherry-street-pier",{"title":345,"description":414},"portfolio\u002Fcherry-street-pier",[425,426,427,428,429],"WordPress","Advanced Custom Fields","Sass","jQuery","Webpack","GbOpXnsf_xMgR1kuj-QtjTU4kdyc3aWGsvLUUBOavGw",1790357551895]