DEPLOYMENTS / OZNANILA
COMMUNITY PLATFORMOznanila
A national Slovenian platform where parishes order printed announcement leaflets or upload their weekly PDFs — and parishioners across the country search, read and stay connected to their parish. Every user carries their parish, diocese and region on their profile; every tiskovina (printed material) is bound to a user and a status lifecycle.
CLIENT
Slovenian parishes
YEAR
2025
SYSTEMS
WEB
STATUS
DEPLOYED
4
tiskovine statuses: naročeno → v obdelavi → dostavljeno
2
storage buckets: profiles + tiskovine PDFs
100%
mobile-first — thumb-usable in a pew
[ THE BRIEF ]
The challenge we walked into.
Parish announcements were living on paper, in Facebook groups, and in inboxes. Parishes couldn't find a professional printer who worked at parish scale; parishioners couldn't find last week's announcements without opening a photo of a paper leaflet. We needed one platform that did both jobs — print ordering for the parish, browseable search for everyone — and worked on a phone in a pew.
[ TOPOLOGY ]
How the system is wired.
// SYSTEM TOPOLOGY
- LAYER 01
Parish admin
AUTHED
Parishioner
PUBLIC
- LAYER 02
Next.js 15
APP ROUTER
- LAYER 03
Supabase Auth
EMAIL + META
Supabase Postgres
users · tiskovine · support
Supabase Storage
PDFS + PHOTOS
- LAYER 04
handle_new_user()
AUTH → USERS TRIGGER
- LAYER 05
Print house
PROCESSES ORDERS
[ WHAT WE SHIPPED ]
Feature by feature.
Sign up as a parish, or a parishioner.
Registration collects the parish (zupnija), diocese (skofija), region, postal code and address into the auth user's metadata. A Postgres trigger copies it all into public.users the moment the account is created, so the profile row is ready before the user reaches the dashboard.
- ▸Zupnija + skofija + region + postal + address
- ▸handle_new_user() trigger on auth.users
- ▸Never a race between signup and dashboard
- ▸Metadata in one place — raw_user_meta_data
[ APPROACH ]
How it came together.
01 — ONE TABLE, TWO BEHAVIOURS
tiskovine.action decides whether it's an order or an upload.
Instead of splitting into two tables that both need indexes, statuses and lifecycles, we model the tiskovine table with a single action enum ('nalozi' / 'naroci'). Everything downstream — status pipeline, orders history page, Poišči index — filters on that enum. The schema stays lean; the UI branches where it needs to.
02 — THE SIGNUP TRIGGER IS THE WHOLE ONBOARDING
raw_user_meta_data → public.users in one PL/pgSQL function.
The registration form collects parish, diocese, region, postal, address, first_name, last_name, phone_number into raw_user_meta_data. A single SECURITY DEFINER trigger copies it into public.users. There is no separate 'complete your profile' step, no race condition, no half-provisioned account — a user hits the dashboard already whole.
- ▸SECURITY DEFINER function
- ▸search_path pinned to public
- ▸on conflict do nothing — safe against retries
- ▸No two-step onboarding UX
03 — MOBILE-FIRST, NOT MOBILE-FRIENDLY
Every page was built for a phone first.
The audience uses this in the pew. Every layout was designed on a phone and scaled up — the sidebar closes when a link is clicked, the user's name is visible in the sidebar for context, forms are one-column and thumb-reachable, and the Poišči Oznanila list loads fast enough that a slow parish wifi doesn't kill the experience.
04 — STORAGE AS THE SOURCE OF TRUTH FOR PDFS
Two buckets, per-user policies, signed URLs on read.
The tiskovine bucket holds every uploaded parish PDF; the profile-images bucket holds every avatar. Per-user access policies ensure a user can only mutate their own paths; reads for the public Poišči list use signed URLs so we can revoke access without editing table rows.
[ DELIVERY ]
What the build looked like.
01 ▊
[ TECH STACK ]
What’s under the hood.
TAP FOR ROLEHOVER FOR ROLE
[ IMPACT ]
What changed.
Parishes stopped losing announcements to Facebook or paper. Community members find what's happening on their phone, admins order printed leaflets in a few taps, and every user carries their parish identity with them from signup — no separate onboarding, no lost metadata. The single-table tiskovine model kept the schema small enough to move fast, and the Zod-under-Vitest discipline means schema changes don't break forms.