[{"data":1,"prerenderedAt":335},["ShallowReactive",2],{"project-case-cdek-shopping":3},[4,13,19,69,85,102,139,267,299,323,329],{"component":5,"data":6},"ProjectCaseHeader",{"index":7,"subtitle":8,"name":9,"headline":10,"url":11,"tone":12},"05","SHOPPING FROM ABROAD · CUSTOMER ACCOUNT","CDEK.Shopping","37% fewer regression bugs after refactoring the legacy code","https:\u002F\u002Fcdek.shopping\u002F","secondary",{"component":14,"data":15},"ProjectCaseCover",{"image":16,"imageMobile":17,"imageAlt":18},"cdek-shopping\u002Fmedia\u002Fpreview.webp","cdek-shopping\u002Fmedia\u002Fpreview-mobile.webp","Home page of the CDEK.Shopping website",{"component":20,"data":21},"ProjectCaseMeta",{"tags":22,"stack":25},[23,24],"E-commerce","Accounts",[26,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67],{"label":27,"own":28},"JavaScript",true,{"label":30,"own":28},"jQuery",{"label":32,"own":28},"Vue 2",{"label":34,"own":28},"Vuex",{"label":36,"own":28},"SCSS",{"label":38,"own":28},"Webpack",{"label":40,"own":28},"Cypress",{"label":42,"own":28},"Jest",{"label":44,"own":28},"axios",{"label":46,"own":28},"portal-vue",{"label":48,"own":28},"Bootstrap",{"label":50,"own":28},"Popper",{"label":52,"own":28},"Owl Carousel",{"label":54,"own":28},"Slick",{"label":56,"own":28},"lightGallery",{"label":58,"own":28},"noUiSlider",{"label":60,"own":28},"jQuery Masked Input",{"label":62,"own":28},"Laravel",{"label":64,"own":28},"Blade",{"label":66},"Sentry",{"label":68},"Diginetica",{"component":70,"data":71},"ProjectCaseFacts",{"facts":72},[73,76,79,82],{"key":74,"value":75},"role","Middle Frontend Developer",{"key":77,"value":78},"team","4 frontend, 8 backend, 2 QA and a designer",{"key":80,"value":81},"duration","2 years, 10.2021 – 10.2023",{"key":83,"value":84},"status","The site is live, my work on the project is over",{"component":86,"data":87},"ProjectCaseRole",{"id":74,"name":88,"items":89},"What I did",[90,93,96,99],{"title":91,"text":92},"Customer account","The entire frontend of the account in Vue 2 and Vuex: five sections in one app with no page reloads. I wrote every component of the account from the team designer's mockups.",{"title":94,"text":95},"Vue inside Blade","Convinced the team to adopt Vue 2 and came up with a way to embed the SPA account into Laravel + Blade without a separate frontend project.",{"title":97,"text":98},"Legacy refactoring","Covered the jQuery code with Cypress E2E tests and visual baselines, then split files of 6,000+ lines into modules with Jest unit tests.",{"title":100,"text":101},"Laravel and analytics","Blade templates, routes and account data in API responses. Set up Yandex Metrica and added events. Reviewed the tasks of the other frontend developers.",{"component":103,"data":104},"ProjectCaseChapter",{"id":105,"name":106,"blocks":107},"context","Context and starting point",[108,120,131],{"component":103,"data":109},{"id":110,"name":111,"blocks":112},"business","Business",[113,117],{"component":114,"data":115},"ProjectCaseText",{"text":116},"CDEK.Shopping is an agent for shopping abroad run by CDEK.Market LLC, a service of the CDEK group. Customers in Russia order products of brands from the US, Europe and Asia: they pay with a Russian card and see prices in rubles right away.",{"component":114,"data":118},{"text":119},"The service handles customs and logistics, and its own logistics company carries the delivery. If a product is not in the catalog, the customer can leave a purchase request.",{"component":103,"data":121},{"id":122,"name":123,"blocks":124},"situation","The situation",[125,128],{"component":114,"data":126},{"text":127},"The business wanted to launch as soon as possible, so the new service was built on the CDEK Market codebase: jQuery and Blade, files of 6,000+ lines. The customer account there looked straight out of the 2000s, and every action reloaded the page.",{"component":114,"data":129},{"text":130},"The business had no interest in tech debt, so refactoring had to fit into the same deadlines as the launch.",{"component":103,"data":132},{"id":133,"name":134,"blocks":135},"goal","Goal",[136],{"component":114,"data":137},{"text":138},"Build an interactive customer account by the launch, where no action reloads the page. CDEK.Shopping users never saw the old account: they started right away in the new one.",{"component":103,"data":140},{"id":141,"name":142,"blocks":143},"process","Process, architecture and decisions",[144,170,204,215,232,240],{"component":103,"data":145},{"id":146,"name":91,"blocks":147},"account",[148,151],{"component":114,"data":149},{"text":150},"The customer moves between the account sections without a reload. The tabs show counters: how many orders are waiting for payment and how many products have no review yet.",{"component":152,"data":153},"ProjectCaseSteps",{"steps":154},[155,158,161,164,167],{"title":156,"text":157},"My details","Contacts with a separate email and phone change, details for customs clearance",{"title":159,"text":160},"Orders","“All”, “Unpaid” and “By purchase request” tabs, a filter, the status of every item, a separate order page",{"title":162,"text":163},"My points","Active, accrued and frozen points, the points history",{"title":165,"text":166},"Delivery address","Default address, delivery to the door or to a pickup point",{"title":168,"text":169},"Reviews","Products waiting for a review and reviews already written",{"component":103,"data":171},{"id":172,"name":173,"blocks":174},"architecture","Architecture",[175,178],{"component":114,"data":176},{"text":177},"The storefront stayed server-rendered, while the account runs as a single-page app inside the same site. It did not need a separate frontend project.",{"component":179,"data":180},"ProjectCaseArchitecture",{"items":181},[182,186,192,200],{"label":183,"title":184,"text":185},"STOREFRONT","Storefront","Server rendering with Blade, interactivity with jQuery.",{"label":187,"title":91,"text":188,"code":189},"ACCOUNT","Vue 2 and Vuex. The backend serves an #app container, the app mounts inside it, routing goes through the hash.",[146,190,191],"orderListing","orderDetailsPage",{"label":193,"title":194,"text":195,"code":196},"API","REST on Laravel","The account gets its data through axios: profile, orders, points, delivery and reviews.",[197,198,199],"\u002Fapi\u002Fv1\u002Faccount","\u002Fapi\u002Fv1\u002Floyalty","\u002Fapi\u002Fv1\u002Fcustomer",{"label":201,"title":202,"text":203},"SERVICES","External services","Payments, the Mindbox marketing tracker, Diginetica search, Yandex Metrica.",{"component":103,"data":205},{"id":206,"name":207,"blocks":208},"components","Account components",[209,212],{"component":114,"data":210},{"text":211},"We designed the component system together with the team designer, with BEM naming. The account has 16 base UI components: buttons, a checkbox, tabs, a select, a filter, a modal, a notification, a tooltip, pagination and others.",{"component":114,"data":213},{"text":214},"They make up about 40 section blocks, and the icons live in a sprite of about 30. I built the account responsive from the start, with a separate mobile navigation.",{"component":103,"data":216},{"id":217,"name":218,"blocks":219},"integrations","Integrations",[220],{"component":221,"data":222},"ProjectCaseIntegrations",{"items":223},[224,226,228,230],{"label":225,"own":28},"Yandex Metrica · events",{"label":227},"Online payments",{"label":229},"Mindbox · marketing tracker",{"label":231},"Diginetica · search with autocomplete",{"component":103,"data":233},{"id":234,"name":235,"blocks":236},"security","Data security",[237],{"component":114,"data":238},{"text":239},"The account works with customers' personal data, including the data needed for customs clearance. The backend returns sensitive fields masked, and the interface shows them in full only after the “Show data” button.",{"component":103,"data":241},{"id":242,"name":243,"blocks":244},"quality","Quality and releases",[245,248,251,264],{"component":114,"data":246},{"text":247},"Before the refactoring I covered the store's critical scenarios with Cypress E2E tests and took visual baselines of the components. The modules that appeared after the refactoring are covered with Jest unit tests.",{"component":114,"data":249},{"text":250},"Every task goes through CI checks and a test stand, then on the planned release date it lands on a preprod identical to production. After acceptance the release rolls out to production with no downtime, and if something goes wrong we can quickly roll back to the previous stable version.",{"component":252,"data":253},"ProjectCasePipeline",{"stages":254},[255,258,261],{"name":256,"next":257},"develop","test stand",{"name":259,"next":260},"preprod","acceptance",{"name":262,"next":263},"master","production, no downtime",{"component":114,"data":265},{"text":266},"We worked in two-week sprints with a demo at the end of each. Every task went through code review, and I reviewed the tasks of the other frontend developers.",{"component":103,"data":268},{"id":269,"name":270,"blocks":271},"challenges","Challenges",[272],{"component":273,"data":274},"ProjectCaseChallenges",{"items":275},[276,289],{"id":277,"name":278,"rows":279},"legacy","Legacy files of 6,000+ lines",[280,283,286],{"key":281,"text":282},"problem","The jQuery client code lived in files of 6,000+ lines. Writing unit tests for such spaghetti was impossible, and touching it without tests was risky.",{"key":284,"text":285},"solution","First I covered the critical scenarios with Cypress E2E tests and took visual baselines, so the refactoring would break neither the logic nor the layout. Then, following the Refactoring.Guru principles, I moved repeated code into modules and kept splitting them until only pure functions covered with Jest unit tests were left.",{"key":287,"text":288},"result","Regression bugs dropped by 37% according to QA reports. Features started shipping faster, and sprint overruns went down.",{"id":290,"name":291,"rows":292},"vue","Vue 2 in a jQuery project",[293,295,297],{"key":281,"text":294},"The whole project was written in jQuery and Blade. An interactive account needed a modern framework, and the team had to be convinced to adopt one.",{"key":284,"text":296},"Got approval to add Vue 2 to the account and embedded it without a separate project: the backend serves an #app container, the app mounts inside it, routing goes through the hash. The account was meant to show on a live example that writing in Vue is simpler and faster than maintaining the legacy code.",{"key":287,"text":298},"The new service's account launched on Vue 2 and Vuex and became an argument for leaving the legacy code: the team decided to rewrite the project in Nuxt.",{"component":103,"data":300},{"id":301,"name":302,"blocks":303},"results","Metrics and results",[304,320],{"component":305,"data":306},"ProjectCaseResults",{"stats":307},[308,311,314,317],{"value":309,"label":310},"−37%","regression bugs after the refactoring",{"value":312,"label":313},"5","account sections in one app",{"value":315,"label":316},"16","base UI components of the account",{"value":318,"label":319},"2 years","on the project",{"component":114,"data":321},{"text":322},"CDEK.Shopping customers worked in the new account from day one, and the legacy refactoring cut regressions and sped up feature delivery.",{"component":324,"data":325},"ProjectCaseCta",{"id":326,"title":327,"text":328,"url":11},"cta","Need a modern customer account in a legacy project?","I will embed an interactive account into your existing site without a separate frontend project, cover the legacy code with tests and split it into modules.",{"component":330,"data":331},"ProjectCaseNext",{"slug":332,"name":333,"index":334,"tone":12},"fly-post","Fly Post","06",1792947429759]