Skip to content
Shahab Hassan
All projects

Bilingual local listingsWeb Development · 2025

Wafferli

A local listings site for Kuwait, in English and Arabic, covering things for sale, services, events, places and deals.

Wafferli screens — a category browser, a local guide article, a product listing with a map, and a Kuwait events page
01Overview

Overview

Kuwait had no single bilingual place where people and businesses could post and find local listings. Wafferli covers five different kinds at once: products for sale, services on offer, upcoming events, places worth visiting, and time limited deals. In the same app someone can browse a phone for sale, book a plumber, find a concert this weekend, discover a new restaurant, and claim a half price offer.

A visitor lands on a homepage with a rotating banner, category tabs, a flash deals slider that moves every few seconds, and rows for events, places and products. They search or filter by city, neighbourhood, category, price and rating, with suggestions appearing as they type across all five listing types. On a listing they see the photos, the seller, the location, and reviews from other people, and they can save it to a wishlist, leave their own review, like someone else's, or message the seller directly.

Deals are the reason people come back. When someone claims one they get a unique nine digit code, shown as a scannable square the shop can read at the counter, and they earn loyalty points. Those points build up through four tiers, Bronze, Silver, Gold and Diamond, all visible on a wallet page alongside every deal they have ever claimed. Flash deals expire on their own exactly 24 hours after being posted. There is also a referral code, and using someone's code when you sign up credits them 100 points.

Anyone can become a seller, either as an individual or as a business. A business gets a name, description, logo, photo gallery, website and social links. An individual just gives a city and neighbourhood. From there they post any of the five listing types through one form, reuse their profile location rather than retyping it each time, manage everything they have posted from one place, and pay to promote a listing higher up the results.

Buyers and sellers talk in a real time chat that carries a lot more than text. Photos travel through the chat connection itself, which keeps the whole exchange on one channel. There are location pins for arranging where to meet, replies attached to a specific message, and a card for the listing being discussed that opens the ad in one tap. It shows who is online, when someone is typing, and when a message was delivered and read.

When a seller posts something new, a notification service looks up which city and neighbourhood they are in, finds other people in the same area, and sends them an alert about it, delivered instantly if they are online and stored for later if they are not. That is what makes a listing nearby feel local rather than lost somewhere in a nationwide feed.

The most interesting decision architecturally is that all five listing types live in one database collection rather than five. They extend a single base model using Mongoose discriminators, so each type keeps its own fields, its own validation and its own queries, while the shared parts, favourites, reviews and ratings, are written once. Questions that span the whole platform, admin totals or a search across every type, then run as one query instead of five.

The bilingual side goes further than swapping the words. Switching to Arabic mirrors the entire interface: the direction is set at the root, row layouts reverse, text moves to the right, and the navigation flips with it. The Arabic message file ended up about 27 percent larger than the English one, which is what genuine full coverage looks like rather than a partial translation.

The admin panel is a separate application of 14 pages covering users and sellers, moderation for each of the five listing types, a blog and questions editor, a tool for sending email campaigns to newsletter subscribers, and staff accounts with permissions set page by page. I built it using the Next.js for front end, the Node and Express back end with its real time layer, and the Admin Panel in React.js.

02Features28

Features

  • Five kinds of listing in one place: products, services, events, places and deals
  • Works fully in English and Arabic, with the whole layout mirrored for Arabic
  • Homepage with a rotating banner, category tabs and a flash deals slider
  • Search and filter by city, neighbourhood, category, price and rating
  • Search suggestions appear as you type, across all five listing types
  • Listing pages with photo galleries, seller details, location and reviews
  • Sign up with a code sent to your email, or with a Google or Facebook account
  • Anyone can become a seller, as an individual or as a business
  • Business profiles with a logo, photo gallery, website and social links
  • Post any of the five listing types through one form
  • Reuse your profile location instead of typing it again on every listing
  • Manage and edit everything you have posted from one place
  • Pay to promote a listing higher up the results
  • Save listings to a wishlist
  • Leave reviews with a star rating, and like other people's reviews
  • Claim a deal and get a unique nine digit code as a scannable square
  • Earn loyalty points that move you up Bronze, Silver, Gold and Diamond tiers
  • A wallet page with every deal you have claimed and your points total
  • Flash deals that expire on their own 24 hours after being posted
  • Referral codes that credit the referrer 100 points when someone signs up
  • Real time chat with photos, location pins and replies to a specific message
  • A card for the listing being discussed that opens the ad in one tap
  • See who is online, when they are typing, and when a message was read
  • Alerts when something new is posted in your own city and neighbourhood
  • Dark and light mode
  • Blog, frequently asked questions, about and contact pages
  • A separate 14 page admin panel with staff permissions set page by page
  • Admin tool for sending email campaigns to newsletter subscribers
03Challenges & solutions5

Challenges and solutions

  1. 01
    Challenge

    Five very different listing types could easily have become five collections, five sets of duplicated logic and no way to query across them.

    Solution

    Used Mongoose discriminators so all five types extend one base model in a single collection. Shared fields, favourites, reviews and ratings are written once, while each type keeps its own schema, validation and controller. Admin totals and cross type search then run as one query instead of five.

  2. 02
    Challenge

    Arabic support meant more than translated strings, since the whole interface reads the other way.

    Solution

    Built on next-intl with a locale segment in the App Router, then handled direction properly: the direction attribute is set at the root, row layouts reverse, text alignment moves right, and navigation mirrors. The Arabic message file ended up about 27 percent larger than the English one, which reflects real full coverage rather than a partial translation.

  3. 03
    Challenge

    Sending a photo in chat through a normal upload endpoint would have meant a second request and a slower, more fragile flow.

    Solution

    Sent images as base64 over the existing socket connection, uploaded them to Cloudinary on the server, resized them, and stored only the CDN URL on the message, so chat images travel on the same channel as the text.

  4. 04
    Challenge

    Two people could claim the same deal twice, or two claims could land on the same code.

    Solution

    Put a compound unique index on user and offer so a second claim cannot be written, and generated the nine digit code in a loop that checks for collisions before committing.

  5. 05
    Challenge

    Alerts about new listings are only useful if they reach people nearby.

    Solution

    On every new listing, a notification service resolves the seller's city and neighbourhood, finds other users in the same area, and bulk creates the right notification type for each of them, pushing over Socket.IO to anyone online and storing the rest for later.


04The product13 screens

Wafferli screens