Community Platform
Ecalendy
A community events platform for Lithuanians in Denmark. Aggregates 6 organizations' Google Calendars into one searchable interface with grid and list views, an interactive map, one-click calendar export, and full Lithuanian language support.

Status
Live
Organizations
6 integrated
Deploy
Vercel
Cron
Daily 06:00 UTC
Why I built this
As president of the Lithuanian Youth Society in Denmark, I saw that Lithuanian events in Copenhagen were scattered across different organizations' Google Calendars, Facebook pages, and websites. Nobody had a single place to see everything. I built Ecalendy to aggregate all events into one searchable, filterable interface that the entire community can use.
How it works
Aggregate from 6 organizations
Fetches events from LYS, Credo choir, Lithuanian Embassy, DLB community, AI Workshops CPH, and Lithuanian Professionals — all via Google Calendar API. AllEvents.in scraper serves as a fallback source.
Geocode and enrich
Smart address parsing extracts street names using Danish patterns (gade, vej, stræde, allé), geocodes via Nominatim with rate limiting and caching. Events get keyword-based fallback images when no photo is available.
Browse, search, and filter
Desktop shows card grid with holographic shimmer effects; mobile shows a compact list. Filter by organization, language (Lithuanian or English), and month. Real-time text search across titles and descriptions.
Export to your calendar
One-click export to Google Calendar via URL intent, or download .ics files for Apple Calendar / Outlook. Lithuanian characters (ąčęėįšųūž) are properly encoded.
Integrated organizations
LYS — Lietuvių Jaunimo Draugija
Lithuanian
Choras Credo
Lithuanian
LR Ambasada Danijoje
Lithuanian
Danijos Lietuvių Bendruomenė
Lithuanian
AI Workshops CPH
English
Lithuanian Professionals in CPH
English
Key features
Grid + list views
Desktop card grid with holographic shimmer; mobile compact list — CSS-based responsive switching
Interactive map
Leaflet map with color-coded organization markers, popups, legend, and unmapped count indicator
Smart geocoding
Multi-strategy address parsing with Danish street patterns, Nominatim fallback, and in-memory caching
Lithuanian localization
Full Lithuanian UI with proper grammatical cases (nominative + genitive months), weekday names, and date formatting
Calendar export
Google Calendar URL intent + ICS download with UTF-8 Lithuanian character support
PWA installable
Add to home screen on iOS/Android, dark theme default, custom geometric logo icon
Tech stack
Frontend
Maps
react-leaflet with dynamic imports for SSR safety, CartoDB dark/light tiles
Data Sources
6 Google Calendars + AllEvents.in scraper as fallback
Infrastructure
ISR (1h revalidation), Vercel Cron (daily 06:00 UTC), file-based event cache
Architecture
Multi-source aggregation: fetches 6 Google Calendars in parallel with Promise.allSettled() — one failure doesn't block others. Deduplicates by org-prefixed IDs.
Smart geocoding pipeline: extracts Danish street patterns via regex, appends "Denmark" if missing, falls back to full address. Rate-limited to 1.1s/request for Nominatim compliance. Results cached in-memory + file.
Vercel Cron hits /api/cron/scrape daily at 06:00 UTC, fetching all orgs, geocoding new events, and writing to /tmp cache (1-hour TTL). ISR revalidates the page every hour.
Client-side filtering with useMemo — organization toggles, language tags, month selector, and text search all computed efficiently without API calls.
What I learned
- -Lithuanian grammatical cases matter in UI — months need both nominative ("Balandis") and genitive ("Balandžio") forms depending on context.
- -Google Calendar events set to "Private" visibility show as "busy" even with a public calendar — cost me hours of debugging until I found the setting.
- -Nominatim geocoding needs careful rate limiting (1 request per 1.1s) and smart address parsing — Danish compound street names like "Langebrogade" need special handling.
- -Building for a real community gives you feedback you can't get from personal projects nobody uses.