WorkU2VESELJKO

Veseljko

Everything a family celebration needs, in one place.

Status: Live

Role: Co-built with a colleague

The veseljko.hr home page in Croatian: the headline “Veseljko: everything your party needs”, a feature list, and phone mock-ups of a wedding’s guest-upload page and RSVP form beside a printed QR cardVeseljko’s package pricing in Croatian: three cards for a free QR gallery starter, digital invitations and a one-month QR gallery, each with a one-off price and its features
veseljko.hr on a phone: the same headline and feature list, with buttons to start a party, try the demo or learn more

Key specifications

U2 · VESELJKO · Rev 2026.10

Role
Co-built with a colleague
Status
Live
Repo
Private; happy to walk through it
Capabilities
  • Back-end & data
  • Front-end
  • Infra and delivery
Stack
  • C#
  • .NET 10
  • Clean Architecture
  • CQRS
  • ASP.NET Core MVC
  • Razor
  • EF Core 10
  • PostgreSQL
  • Cloudflare R2
  • QR generation
  • headless Chromium PDF
  • hr-HR i18n

Signal path · 5 stages

How it works

Veseljko, from input to output

  1. Event owner: picks an event and a theme
  2. Theme engine: 30+ themes, CSS only
  3. Invitations & RSVP: QR codes for guests
  4. Guest uploads: photos and video to R2
  5. Print: headless Chromium PDF

What it does

Veseljko lets event owners run the whole lifecycle of a wedding, baptism, birthday, communion or bachelor(ette) party in one place: digital invitations with RSVP, guest photo and video uploads, printable invitations and QR cards, table seating, package and add-on commerce, and a back-office admin app.

It’s the kind of product you could hand to a couple six months before their wedding and never have to explain again. They set up an event, pick an invitation theme, fill in their schedule and contacts, send the invitation link and watch the RSVPs come in. During the celebration they get a live gallery of every photo and video their guests upload, which a single share link can put on the venue’s screen. Afterwards everything stays in one place: gallery, guest list, seating, expenses and paperwork.

Behind that one product are eight event types: wedding, bachelor party, bachelorette party, baptism, birthday, confirmation, first communion and a generic catch-all. Each has its own set of invitation themes, its own fields in the editor and its own copy on the public pages. A second, back-office app gives staff a shopping cart for packages and add-ons, partner management, reviews, enquiries and a blog. The two front ends share auth and persistence but stay separate processes with separate concerns.

How it’s built

Clean architecture in four layers

The solution splits into Domain, Application, Infrastructure and SharedKernel projects, with two front ends on top: Web for owners and guests, and Office for staff. Every layer’s dependencies point inward (Infrastructure depends on Application, Application on Domain, never the reverse), so the business rules live in code that has no idea how requests arrive or where data lands.

CQRS through custom abstractions

Reads and writes are physically separate handler types, IQueryHandler for reads and ICommandHandler for writes, registered automatically by convention, so there’s no service-locator wiring per feature. Every handler returns a Result<T> instead of throwing, which keeps failure paths explicit at every call site rather than bubbling up through middleware. We rolled the whole pattern by hand rather than pulling it from a library: small enough to fit in the SharedKernel, opinionated enough to keep the team honest.

“Roll your own CQRS” is a phrase that scares people for good reason. The trick was keeping it small: two handler interfaces, one auto-registration helper, one Result type, no mediator.

Persistence and migrations

EF Core 10 runs on PostgreSQL with snake_case naming conventions applied globally. Code-first migrations are applied across three environments (Development, RemoteDevelopment and RemoteProduction) by dedicated migration scripts that pick up the right connection string for each. Both front ends use antiforgery protection and cookie auth, backed by a shared identity.

Themed templating engine

More than 30 invitation themes are seeded in the database, organised as the cross-product of event type (Wedding, Bachelor, Bachelorette, Baptism, Birthday, Confirmation, First Communion, Other) and visual style (Classic, Deco, Editorial, Modern). Theming is CSS-only: a CustomCss field on the theme is scoped at render time, so two events with the same theme can diverge without forking the template. A placeholder substitution pipeline injects per-event data into slots such as names, schedule HTML, RSVP QR codes and contacts, so the same theme file serves every event of its type without code changes.

Owner-editable text and lock islands

Owners edit invitation copy inline, directly on the rendered page, through contenteditable and a tiny JavaScript driver. The trick is that some regions must stay in sync with the database because they change as the event evolves: schedule blocks, RSVP QR codes, organiser contact lists and RSVP-by dates. Those regions are wrapped in non-editable lock islands that the editor refuses to touch, and everything else is the owner’s to edit. Edits round-trip to the database without ever overwriting the live, database-derived content.

Invitations print as multi-page, browser-native documents (blank, front, blank, back, two-up on 10×7-inch sheets) using a print stylesheet tuned per theme. A5 QR cards run on a separate layout for the bachelor and bachelorette party scenarios. The whole pipeline is verified end to end with headless Chromium running --print-to-pdf against the same templates, so the WYSIWYG preview really is what comes out of the printer.

Pluggable storage providers

Media upload sits behind a provider interface with two implementations: the local filesystem for development and Cloudflare R2 for production. Per-package upload limits control how much guests can submit, and thumbnails are generated at upload time so the gallery loads cheaply.

Password-protected guest galleries

Guest galleries are password-protected. Photos and videos share the same gallery view, with thumbnails for both.

Live-presentation slug

Every event gets a public slug that streams new guest uploads onto a projection screen. Open the link on the venue’s laptop, go full screen, and the gallery refreshes itself for the rest of the night. The projection slug has no auth, because guests don’t have accounts, but it is randomised per event.

Cart with per-event feature overrides

The Office app runs a shopping cart for packages and add-ons. The interesting twist is that some features, such as digital invitations and table management, can be toggled per event through cart admin actions, so a staffer can grant a one-off feature to a customer’s event without changing their package tier in the database. The overrides are first-class citizens, not flags hidden in a JSON blob.

Office modules

Beyond the cart, the back office handles partners (venues, photographers, caterers), customer reviews, inbound enquiries and a blog. It uses the same Clean Architecture skeleton and the same CQRS handler pattern, just with different read and write surfaces.

Localisation

The UI is fully Croatian, with culture-aware hr-HR date formatting throughout. Resources live in centralised .resx bundles under Web/Localization, so a future second language is a translation pass rather than an engineering project. Most of the engineering tax for that future internationalisation has already been paid.

Status

Veseljko is a personal project. The full feature surface above is implemented end to end (invitations, RSVPs, print, galleries, live presentation, cart and back office) and verified with the headless Chromium PDF pipeline.

In numbers

event types
8
invitation themes
30+
front ends: Web and Office
2