Multilingual paragliding hub for pilots exploring the skies of Morocco.
Built with Next.js, Tailwind CSS, and the occasional chaos-fueled tea session.
Flymorocco is a fully static, SEO-friendly, and i18n-ready web app tailored for paragliding pilots looking to fly in Morocco.
It serves guide pages for multiple flying sites, dynamically rendered and maintained by a single JSON data source, localized in both English and French.
- 🌍 Multilingual with
next-intl(EN/FR) - 🪁 Dynamic site guides powered by slug-based routing
- 🖼️ Visual-first layout to highlight the beauty of flying sites
- 🔍 SEO-ready with custom meta tags per page
- 🗺️ Interactive airspace maps with Morocco's official ENR 5.5 data
- 💳 Multi-currency booking (GBP, USD, EUR, CAD) through Wise
- 👥 Multi-participant support with pilot detection and verification
- 🏠 Solo room options with dynamic pricing calculations
- 📧 Professional email pipeline (confirmations, notifications, verifications)
- 🎯 Marketing-optimized emails with clickable tour banners
- 📋 PDF document generation for CAA authorization forms
- 🧑💻 Google Spreadsheet integration for booking management
- ⚙️ Type-safe data validation with Zod schemas
- 🔒 Security features with reCAPTCHA and input sanitization
- 📱 Responsive design with accessibility built-in
- 🚀 Static generation for optimal performance
- Next.js 15 the goat 🐐
- Tailwind CSS the 1000 pieces jigsaw puzzle of styling 🧩
- DaisyUI the picture of the box of the puzzle 🧩
- Lucide React for clean, consistent icons
- Zod to break my app again
- React Leaflet, who does like a good map?
- OpenAir parser, need content for map
- pdf-lib, advanced pdf generation 📝
- sendGrid, to send the advanced pdfs
- react-scroll-parallax, smooth scrolling effects
bash
git clone https://github.com/skye-flyhigh/flymorocco.git
cd flymorocco
npm install
npm run dev
app/
├── [locale]/
│ ├── layout.tsx # Locale-aware layout << Case of the missing E
│ ├── page.tsx # Localized homepage
│ ├── components/ # 🪄 Magic 💫
│ └── site-guides/ # Dynamic 🏔️ (per location)
├── layout.tsx # Language overlay
├── lib/
│ └── site.ts
├── types/
│ └── siteMeta.ts # Zod's schema validation for site guide meta
├── i18n/ # next-intl language package
└── middleware.ts # i18n routing redirect
The messages folder for the translations are living outside of the src/app folder according to next-intl docs.
📂
├── messages/
│ ├── en.json
│ └── fr.json
├── public/images/
├── src/app/
“The Case of the Missing E”
During the i18n setup, a rogue param named local (missing the "e") hijacked the entire layout and crashed the app via a top-level await.
After hours of meticulous logs, terminal therapy, and a French-accented scream, the bug was identified.
🧪 Resolved by explicitly setting:
const { local: locale } = resolvedParams;
// Official docs said it returns { locale }... THEY LIED. IT'S 'local'.
// Yes, I logged it. Yes, I screamed.
Also the correct solution is in middleware.ts, to force the correct param key
createMiddleware({
...routing,
localeParam: 'locale' // << WITH a 'e'
})
This project will proudly wear the battle scars of this journey.
"🗺️ The Cartographic Conquest (April 2025)"
“It started with a file… and ended in 20,000 lines of polygon geometry.”
- Parsed Morocco’s official ENR 5.5 airspaces from OpenAir format using a hacked parser and caffeine-fueled determination
- Resolved cryptic parser errors like "Unknown altitude definition '3500'" and "Token 'DB' does not allow subsequent token 'AY'" by reverse-engineering format expectations
- Used fixGeometry: true to handle self-intersecting madness (and hoped nothing got lost in convex purgatory)
- Successfully converted nested sectors, parachute zones, and TMA fragments into GeoJSON, rendered live with Leaflet
- Deployed hoverable, paintable polygons with visual filtering (GSEC toggle) and type-based coloring
📌 Status: All airspaces rendered. CTR drama avoided. Moroccan CAA blocked my IP — success confirmed.
💬 “Turns out drawing maps is harder than flying through them.” — Skye
The CAA submission form was fully implemented using:
useActionStatefrom React 19 for async validation + response control- Schema-driven layout using Zod and dynamic JSX generation
- Modular error handling with inline accessibility-friendly feedback
SiteSelectorcustom component for dynamic zone selection- Locale-aware translation of all labels and placeholders (
next-intl) - Future-proofed design for PDF generation and multilingual submission
This form is now the template for any future high-logic form on FlyMorocco or other apps.
💬 “The amount of logic this form contains is unhealthy. But it's clean.” — Skye
Skye – Paraglider pilot and instructor, Chaos Wielder, Front-End Dev, AI Architect, Mint Tea Enthusiast
This is a personal dev playground, not open source (yet). You’re welcome to star, fork, or fly by.
© 2025 Skye.cmd / FlyMorocco. All rights reserved.
This project is not open source. No reuse, redistribution, or derivative works permitted without express permission.