ylcvalencia.es is not a business-card website but a full-blown digital platform for Your Language Club, a bilingual learning centre in Valencia. It combines a trilingual public site (RU / ES / EN), student and teacher dashboards, Telegram and WhatsApp bots with an AI assistant, an online store, online payments, an in-house CRM with schedule, attendance and a payroll module, a blog content engine and a B2B-contracts system — all on a single backend and one shared database. The school runs two strictly separated tracks: Spanish for Russian speakers and English-speaking expats, and English for Spaniards.
Technology stack
- Frontend: Next.js 16 (App Router, Turbopack, standalone output) + React 19 + TypeScript 6, Tailwind CSS 4 (
@theme inline), custom design, fully responsive. - i18n: next-intl 4, three UI locales (RU / ES / EN), auto language detection (navigator.language → Accept-Language → IP), hreflang.
- Backend: Express (Node 22) — REST API for briefs, ideas, prices, CRM, media uploads and admin endpoints.
- Database: Prisma 6 + SQLite (persistent volume on the VPS), PostgreSQL 16 + Redis 7 in Docker for heavier modules.
- Telegram bot: grammY (TypeScript) with context memory.
- WhatsApp bot: WhatsApp Business Cloud API.
- AI: OpenAI GPT + Anthropic Claude, RAG on the school's materials, two assistant presets (Spanish teacher / English teacher).
- Payments: Stripe + a local Spanish acquirer, idempotent webhooks.
- Email: SMTP + Mailgun, DKIM / SPF, MailHog for development.
- Automation: n8n (briefs, leads, broadcasts, nightly jobs).
- Infrastructure: Contabo Cloud VPS, Ubuntu 24.04, pm2 (native web + brief-api) + Docker Compose (Postgres / Redis / n8n), nginx, Let's Encrypt SSL.
- CI/CD: GitHub Actions — auto-deploy on push to
main(tsc-check → build → rsync → pm2 reload → healthcheck), one deploy at a time. - Analytics and monitoring: Plausible, healthcheck endpoints, pm2 log rotation.
Full list of implemented features
🌐 Website and pages
- Three UI locales (RU / ES / EN) with auto language detection and redirect to
/<locale>/ - Two separate tracks: Spanish for Russian speakers / expats (
/ru/,/en/), English for Spaniards (/es/) — catalogues, tone and SEO semantics never mix - Video hero on the first screen + Spanish-cities slider with auto-rotation
- Course catalogue: 10 templates for Spanish + 9 for English, a dedicated page for each course
- 8 SEO landing pages for levels (A1 / A2 / B1 / B2 × 2 tracks) with course lists and FAQ
- Interactive course-picker quiz (5-step wizard → enquiry on WhatsApp)
- Free level test, "About the school", "Methodology", "Teachers" pages (8 profiles with video intros), "Activities", "Calendar", "Gallery" (replaces 360° tours)
- Kids' day-camps for school holidays, no accommodation
- Feed of 43 real Google reviews (swipe on touch, equal-height cards)
- Feed of Spanish-speaking countries with photographs
- Talking mascot FAB (60 lines, tilts towards the cursor, swipe-hides for 10 minutes)
- FAQ engine: 12 sets across pages, 15 questions × 3 locales, native
<details>, FAQPage markup - Preloader with a shimmer effect, ScrollToTop, a 404 page
- Full mobile adaptation: top branding bar, bottom tab-bar sized to the viewport, bottom-sheet navigation with lock-scroll and safe-area
🔐 Authentication and roles
- Login by phone or email + password, password recovery
- 6 roles with capability bits: guest, student, teacher, admin, director, developer
- Capability-based navigation filtering: each user only sees the sections they have access to
- Dev mode that switches the effective role for testing permissions
- Per-user permission overrides on top of the role (director-granted access)
👤 User dashboard
- Student profile, history of courses and payments
- Teacher dashboard: own schedule, own groups, attendance
- Sticky tab navigation, jump to the admin for staff
- Notifications via Telegram and email
🛠 CRM and admin
- A single server-side
AdminShellskeleton with sidebar navigation for every admin section - Leads: kanban pipeline with HTML5 drag-and-drop across stages
- Students: dedicated page with filters (track, CEFR level, status, search), a student modal card with a
?student=<id>deep-link - Schedule: 7-day × time grid, drag-and-drop of groups across cells, block transfer, colour by CEFR level, sticky group-palette by level (A0–C2), "My schedule" filter by teacher
- Attendance: pick a group and a date → bulk "present / absent" marking
- Teachers: payroll module — hours and ≈€/month are computed automatically from schedule slots at a €/hour rate
- Groups, enrolments, payments, B2B contracts — full CRUD
- Broadcasts: audience collection from CRM by segments → wa.me / t.me deep-links
- Single source of truth for status and level colours (
Badge,levelColor,statusColor) - All admin pages gated via
getMe/ capability-check with redirect to login
🛒 Online store
- Catalogue of 4 categories: textbooks, merch (with size selection), gift cards, digital materials
- Product page in 3 locales (title / description / price in euros), size variants, photos
- CRUD product admin with a modal editor and image upload (base64 → local volume, MIME validation, 5 MB limit, cache headers)
- Cart, checkout, success / failure pages
- Payments via Stripe + a Spanish acquirer, idempotent webhooks
- Fallback to a static catalogue when the backend is unreachable
📝 Content engine (blog)
- A single post model: one record = three locales (RU / ES / EN) bundled together
- CRUD admin with locale tabs, draft / published statuses, auto-stamping the publication date
- Cover images via image upload to the VPS
- Server-side rendering of posts from the API with a static fallback, content picked by locale
- "Related articles" block, breadcrumbs, Markdown paragraphs
🤖 Bots and AI assistant
- Telegram bot (grammY): course menu, schedule, profile, referral link, trial-class booking
- WhatsApp bot (Business Cloud API): most of the website's features inside the messenger
- AI assistant with two presets (Spanish teacher for Russian speakers / English teacher for Spanish speakers), context memory and RAG over the school's materials
- Proxy to OpenAI / Anthropic, conversation logging
📅 Schedule and calendar
- Calendar of dates, schedule and Spanish public holidays on the public site
- Internal group schedule in the CRM with block transfer and a teacher filter
🧾 Briefs, ideas and notes
- Briefing form with server-side storage, admin view of every brief
- Idea collection for the roadmap from the website, admin processing (
processed,addedTo,note) - Shared notes for the design-system screens (11 sections) with authorship
🎨 Design system
- 11 screen sections on a single
/design/page - Atoms: Button (continuous flash + shadows), Arrow, Logo, SpeechBubble, Highlight, CefrBadge, PhoneFrame, ScaleRow and others
- Design tokens — CSS variables
--ylc-*, adaptive sizes viaclamp()everywhere - Online CSS editor for tweaking the design without redeploying
🔍 SEO / GEO / AEO
- Sitemap.xml + robots.txt, canonical + hreflang between RU / ES / EN
- Schema.org markup: EducationalOrganization, Course, BlogPosting, FAQPage, BreadcrumbList
- OG / Twitter meta tags, server prerender for correct sharing previews
- AI Overviews readiness,
llms.txt - 8 SEO landing pages for levels + course pages aligned with track semantics
⚡ Performance and media
- Standalone output (minimal Docker / Node build)
- Self-hosted fonts (Onest, Caveat, JetBrains Mono) via next/font — no render-blocking
- Content photos
loading="lazy"+decoding="async", hero videopreload="auto"+fetchpriority="high" - Desktop / mobile video chosen via
matchMedia, leaf-shaped photo mask via SVG clip-path - Lazy Prisma client — a database error does not bring the API down
🛡 Security and reliability
- RBAC on capability bits + per-user permission overrides
- Basic Auth on admin endpoints through nginx
- Idempotent payment webhooks, upload validation (MIME + size)
- Secrets outside git (a local vault), masking in logs and commits
- Healthcheck endpoints, pm2 auto-restart via systemd on reboot
📦 Infrastructure / DevOps
- Monorepo (
apps/web+apps/brief-api), pm2 natively + Docker Compose for the databases - Auto-deploy via GitHub Actions: tsc-check → next build → rsync →
pm2 reload→ healthcheck - Concurrency locks on deploy, pnpm-store cache
- nginx routing (web :3000, brief-api :4000, static roadmap presentation), SSL with auto-renewal
- Manual rsync deploy as a fallback; log rotation; persistent data volumes

