[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technology-en-typescript":3,"technology-projects-en-typescript":29,"mdc--5nf1ay-key":104},{"id":4,"title":5,"category":6,"content":7,"description":8,"extension":9,"faq":10,"icon":11,"links":12,"meta":21,"projects":22,"since":25,"stem":26,"updated":27,"__hash__":28},"technologies_en\u002Fen\u002Ftechnologies\u002Ftypescript.yml","TypeScript","frontend","TypeScript adds static type checking to JavaScript. I use it in Vue and Nuxt applications, including VetCard and this portfolio, to make component interfaces and data models easier to understand and change.\n\n## What I type\n\n- **Components** — props, emitted events and shared state.\n- **API models** — the clinic and appointment data expected by the frontend.\n- **Reusable logic** — composable inputs and return values.\n\nThis repository also runs a type check in CI and defines schemas for its translated content. Those are complementary checks with different responsibilities.\n\n## Where static checking stops\n\nTypeScript types are removed from the generated JavaScript. An API response is not validated simply because it has a TypeScript annotation. Unexpected external data needs runtime validation; an unannounced backend change will not automatically appear as a compiler error.\n\n## Safer maintenance\n\nWhen a declared model changes, type checking can identify incompatible uses in the codebase. That makes refactoring more deliberate, especially across forms and templates. It complements tests and input validation rather than replacing them.\n","TypeScript for Vue and Nuxt: typed components, API models and safer refactoring, with runtime validation where external data enters the app.","yml",null,"i-simple-icons-typescript",[13,18],{"label":14,"to":15,"color":16,"icon":17},"typescriptlang.org","https:\u002F\u002Fwww.typescriptlang.org","neutral","i-heroicons-arrow-top-right-on-square",{"label":19,"to":20,"color":16,"icon":17},"Technical reference","https:\u002F\u002Fwww.typescriptlang.org\u002Fdocs\u002Fhandbook\u002F2\u002Fbasic-types.html",{},[23,24],"vetcard","vetspace","2020","en\u002Ftechnologies\u002Ftypescript","2026-09-20","eM8MBdB16rknzkr1XB68jtG-uh4mdyPHZvOSGX_ciRQ",[30,67],{"id":31,"title":32,"content":33,"date":27,"description":34,"extension":9,"featured":35,"image":36,"links":37,"meta":46,"stack":47,"stem":61,"tags":62,"url":40,"__hash__":66},"projects_en\u002Fen\u002Fprojects\u002Fvetcard.yml","VetCard","The public-facing side of VetSpace — a multi-tenant catalog of veterinary clinics. A single Nuxt SSR deployment serves every clinic: SEO-first clinic card pages, online appointment booking proxied to each clinic's own tenant backend, per-clinic theming, and multi-branch support.\n\n## Key Features\n\n- **One deployment, many clinics** — a single app routes by clinic slug; booking and slot requests are proxied to each clinic's own tenant backend\n- **SEO-first catalog** — 590+ clinics with SSR pages, structured data and localized routes built to be indexed\n- **Per-clinic branding** — every clinic page is themed by its own brand color, with a \"Powered by VetSpace\" badge\n- **Real booking UX** — available slots show free-doctor counts with scarcity color-coding\n- **Fully localized** — Ukrainian, English and Polish with prefix-based routing\n\n## Tech Stack\n\n- **Frontend**: Nuxt 4 \u002F Vue 3, Tailwind CSS v4\n- **Backend**: Laravel catalog API + per-tenant appointment endpoints\n- **Rendering**: SSR for SEO pages, CSR for booking flows\n- **Testing**: Vitest unit tests + Cypress E2E against a real backend\n","Multi-tenant catalog of veterinary clinics: SEO clinic pages, online appointment booking, per-clinic branding — one Nuxt app serves every clinic",true,"\u002Fhero\u002Fvetcard.avif",[38,42],{"label":39,"to":40,"color":41,"icon":17},"Live Demo","https:\u002F\u002Fvetcard.pro","primary",{"label":43,"to":44,"color":16,"icon":45},"Overview","https:\u002F\u002Fgithub.com\u002FYurij2015\u002Fvetspace-vetcard","i-simple-icons-github",{},[48,51,54,55,58],{"name":49,"icon":50},"Nuxt","i-simple-icons-nuxtdotjs",{"name":52,"icon":53},"Vue","i-simple-icons-vuedotjs",{"name":5,"icon":11},{"name":56,"icon":57},"Tailwind CSS","i-simple-icons-tailwindcss",{"name":59,"icon":60},"Laravel API","i-simple-icons-laravel","en\u002Fprojects\u002Fvetcard",[49,52,63,64,65],"Multi-tenant","SEO","SSR","E8OJJw44yMkVkIiATLwF2krirApHUeRnAApzpvv7c10",{"id":68,"title":69,"content":70,"date":71,"description":72,"extension":9,"featured":35,"image":73,"links":74,"meta":85,"stack":86,"stem":100,"tags":101,"url":76,"__hash__":103},"projects_en\u002Fen\u002Fprojects\u002Fvetspace.yml","VetSpace","A two-sided veterinary platform — an SEO clinic catalog and a subscription-based CRM with appointments and patient records. A public clinic catalog built for SEO-first customer acquisition, and a subscription-based CRM (appointments, visits, patient records) as separate Vue\u002FNuxt apps on top of a single Laravel API. Database-per-tenant architecture, Stripe subscriptions via Cashier, GraphQL + REST, and AI-assisted visit analysis through a dedicated Go microservice.\n\n## Key Features\n\n- **Two-sided funnel** - an SEO-first public catalog acquires clinics; the subscription CRM (appointments, patient records) retains them\n- **True tenant isolation** - database-per-tenant keeps every clinic's data separate and makes GDPR export\u002Fdelete a per-database operation\n- **Dual API surface** - GraphQL for complex catalog queries, REST for the CRM — one Laravel codebase\n- **Billing built-in** - Stripe subscriptions via Cashier: trials, plan changes, dunning\n- **AI where it helps** - visit analysis runs in a dedicated Go microservice, keeping the main API unburdened\n\n## Tech Stack\n\n- **Backend**: Laravel (API)\n- **Frontend**: Vue\u002FNuxt (separate apps)\n- **Architecture**: Database-per-tenant multi-tenancy\n- **Payments**: Stripe via Cashier\n- **API**: GraphQL + REST\n- **AI**: Go microservice for visit analysis\n","2026-09-19","A two-sided veterinary platform — an SEO clinic catalog and a subscription-based CRM with appointments and patient records","\u002Fhero\u002Fvetspace.avif",[75,77,80,83],{"label":39,"to":76,"color":41,"icon":17},"https:\u002F\u002Fvetspace.pro",{"label":78,"to":79,"color":16,"icon":45},"API Overview","https:\u002F\u002Fgithub.com\u002FYurij2015\u002Fvetspace-api",{"label":81,"to":82,"color":16,"icon":45},"Frontend Overview","https:\u002F\u002Fgithub.com\u002FYurij2015\u002Fvetspace-frontend",{"label":84,"to":44,"color":16,"icon":45},"VetCard Overview",{},[87,89,90,91,94,97],{"name":88,"icon":60},"Laravel",{"name":52,"icon":53},{"name":49,"icon":50},{"name":92,"icon":93},"Stripe","i-simple-icons-stripe",{"name":95,"icon":96},"GraphQL","i-simple-icons-graphql",{"name":98,"icon":99},"AI","i-heroicons-sparkles","en\u002Fprojects\u002Fvetspace",[102,88,52,63,92,98],"SaaS","NDJ-cDU24m4uCf7HzSeTbCgbh0MGVh5P5HASDymy0wA",{"data":105,"body":106},{},{"type":107,"children":108},"root",[109,117,124,160,165,171,176,182],{"type":110,"tag":111,"props":112,"children":113},"element","p",{},[114],{"type":115,"value":116},"text","TypeScript adds static type checking to JavaScript. I use it in Vue and Nuxt applications, including VetCard and this portfolio, to make component interfaces and data models easier to understand and change.",{"type":110,"tag":118,"props":119,"children":121},"h2",{"id":120},"what-i-type",[122],{"type":115,"value":123},"What I type",{"type":110,"tag":125,"props":126,"children":127},"ul",{},[128,140,150],{"type":110,"tag":129,"props":130,"children":131},"li",{},[132,138],{"type":110,"tag":133,"props":134,"children":135},"strong",{},[136],{"type":115,"value":137},"Components",{"type":115,"value":139}," — props, emitted events and shared state.",{"type":110,"tag":129,"props":141,"children":142},{},[143,148],{"type":110,"tag":133,"props":144,"children":145},{},[146],{"type":115,"value":147},"API models",{"type":115,"value":149}," — the clinic and appointment data expected by the frontend.",{"type":110,"tag":129,"props":151,"children":152},{},[153,158],{"type":110,"tag":133,"props":154,"children":155},{},[156],{"type":115,"value":157},"Reusable logic",{"type":115,"value":159}," — composable inputs and return values.",{"type":110,"tag":111,"props":161,"children":162},{},[163],{"type":115,"value":164},"This repository also runs a type check in CI and defines schemas for its translated content. Those are complementary checks with different responsibilities.",{"type":110,"tag":118,"props":166,"children":168},{"id":167},"where-static-checking-stops",[169],{"type":115,"value":170},"Where static checking stops",{"type":110,"tag":111,"props":172,"children":173},{},[174],{"type":115,"value":175},"TypeScript types are removed from the generated JavaScript. An API response is not validated simply because it has a TypeScript annotation. Unexpected external data needs runtime validation; an unannounced backend change will not automatically appear as a compiler error.",{"type":110,"tag":118,"props":177,"children":179},{"id":178},"safer-maintenance",[180],{"type":115,"value":181},"Safer maintenance",{"type":110,"tag":111,"props":183,"children":184},{},[185],{"type":115,"value":186},"When a declared model changes, type checking can identify incompatible uses in the codebase. That makes refactoring more deliberate, especially across forms and templates. It complements tests and input validation rather than replacing them."]