[{"data":1,"prerenderedAt":71},["ShallowReactive",2],{"project:accuhit-campaign-hub:en":3,"i-tabler:database":59,"i-tabler:components":63,"i-tabler:shield-check":65,"i-tabler:route":67,"i-tabler:login":69},{"slug":4,"name":5,"summary":6,"role":7,"updateNote":-1,"meta":8,"coverUrl":-1,"lang":23,"isFallback":24,"featuredRank":25,"badge":26,"lastActivity":18,"tapeNumber":27,"blocks":28},"accuhit-campaign-hub","Campaign Page Platform","Consolidated several client campaign pages, each built with plain HTML and JS, into a single Vue and Vuetify app.","Frontend Engineer",{"origin":9,"categories":10,"tags":12,"released":18,"company":19,"period":20},"company",[11],"frontend",[13,14,15,16,17],"vue","typescript","vuetify","pinia","liff","2025-11-01","accuhit",{"from":21,"to":22},"2025-11","2026-01","en",false,3,null,5,[29,52],{"type":30,"items":31},"features",[32,36,40,44,48],{"title":33,"description":34,"icon":35},"Path-based project routing","Brought multiple campaign pages into one app, separated by path. Routing traffic only means handling the data each path needs, such as parameters passed in the URL.","tabler:route",{"title":37,"description":38,"icon":39},"Single source of truth for config","Gathered each project’s UI and settings into a single source, with types derived from the config itself, cutting down on mistakes and maintenance.","tabler:database",{"title":41,"description":42,"icon":43},"Type-checked config map","Replaced switch statements scattered across the code with a single map. Leaving out a setting for a new option fails the type check, and unknown values fall back to a default.","tabler:shield-check",{"title":45,"description":46,"icon":47},"Consistent UI","Unified the interface of every campaign page with Vuetify, replacing ad hoc styles.","tabler:components",{"title":49,"description":50,"icon":51},"LINE login and error handling","Integrated LINE login through LIFF and handled loading and expired sessions in one place.","tabler:login",{"type":53,"title":54,"problem":55,"solution":57},"challenge","Every campaign page did its own thing",[56],"Each campaign page was built separately with HTML and JS, and the UI didn’t match. The same kinds of settings lived in many places, so rules drifted apart and one change meant edits in several files.",[58],"Merged everything into a single Vue app, separated by path. Settings became a single source of truth constrained by TypeScript, so missing or wrong values show up in the type check.",{"left":60,"top":60,"width":61,"height":61,"rotate":60,"vFlip":24,"hFlip":24,"body":62},0,24,"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M4 6a8 3 0 1 0 16 0A8 3 0 1 0 4 6\"\u002F>\u003Cpath d=\"M4 6v6a8 3 0 0 0 16 0V6\"\u002F>\u003Cpath d=\"M4 12v6a8 3 0 0 0 16 0v-6\"\u002F>\u003C\u002Fg>",{"left":60,"top":60,"width":61,"height":61,"rotate":60,"vFlip":24,"hFlip":24,"body":64},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m3 12l3 3l3-3l-3-3zm12 0l3 3l3-3l-3-3zM9 6l3 3l3-3l-3-3zm0 12l3 3l3-3l-3-3z\"\u002F>",{"left":60,"top":60,"width":61,"height":61,"rotate":60,"vFlip":24,"hFlip":24,"body":66},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.46 20.846A12 12 0 0 1 3.5 6A12 12 0 0 0 12 3a12 12 0 0 0 8.5 3a12 12 0 0 1-.09 7.06M15 19l2 2l4-4\"\u002F>",{"left":60,"top":60,"width":61,"height":61,"rotate":60,"vFlip":24,"hFlip":24,"body":68},"\u003Cpath fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M3 19a2 2 0 1 0 4 0a2 2 0 0 0-4 0M19 7a2 2 0 1 0 0-4a2 2 0 0 0 0 4m-8 12h5.5a3.5 3.5 0 0 0 0-7h-8a3.5 3.5 0 0 1 0-7H13\"\u002F>",{"left":60,"top":60,"width":61,"height":61,"rotate":60,"vFlip":24,"hFlip":24,"body":70},"\u003Cg fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\">\u003Cpath d=\"M15 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2v-2\"\u002F>\u003Cpath d=\"M21 12H8l3-3m0 6l-3-3\"\u002F>\u003C\u002Fg>",1793948871848]