# integration/wix

Widget de réservation Manahôte embarquable dans un site Wix.

## Décision : custom element auto-hébergé, pas d'iframe

Wix propose plusieurs façons d'exposer un composant tiers. L'extension
**Widget (iframe)** est aujourd'hui rangée dans la section `deprecated` de la
documentation Wix ; elle est remplacée par l'extension **Site Widget** bâtie sur
la technologie **custom element**.

Différence concrète : sur le **site publié**, un custom element est rendu
directement dans le DOM de la page. Il n'y a pas d'iframe, donc pas de hauteur
figée, pas de double scroll sur mobile, et le composant hérite du contexte de la
page. (Dans l'éditeur et en prévisualisation, Wix le rend malgré tout dans une
iframe bac-à-sable pour des raisons de sécurité : c'est normal, il faut publier
le site pour voir le rendu réel.)

Le framework retenu est **self-managed** (auto-hébergé) : le bundle JS est servi
par `manahote.app`, et l'app Wix ne stocke qu'une URL de script + un nom de tag.
Le back-office PHP et l'API ne bougent pas.

### Pourquoi pas le Wix CLI

Le Wix CLI déploie une app en React/Node sur l'infrastructure Wix (avec CI/CD
GitHub Actions possible). C'est une seconde stack à maintenir à côté du PHP,
alors que toute la logique métier vit déjà derrière l'API. Le CLI devient
pertinent le jour où on veut une distribution sur l'App Market avec parcours
d'installation OAuth, pages dashboard natives Wix ou facturation via Wix.

### Pourquoi pas Wix Blocks comme base

Blocks est un constructeur visuel de widgets dans Wix Studio. Il sait embarquer
un custom element, mais la substance du widget reste des appels à notre API :
Blocks ajouterait un code Velo côté Wix pour peu de gain à ce stade. Il reste
une bonne option plus tard pour packager des presets de design et un panneau de
réglages soigné, par-dessus le même custom element.

## Contrat d'API

Le widget est un client de l'API publique, identique à `www/form.html` :

1. `POST /api/sign?property=<id>` avec l'en-tête `X-API-Key` → `{timestamp, signature}`
2. l'appel réel avec `X-API-Key`, `X-Timestamp`, `X-Signature`

Chemins signables (liste blanche côté serveur, `SignApiController::ALLOWED_PATHS`) :
`/unit`, `/unit/availability`, `/rate`, `/quote/checkouts`, `/quote/calculate`, `/booking`.

**Le secret d'API ne doit jamais être embarqué dans le widget.** C'est le serveur
qui signe, à partir de la seule clé publique. Il n'y a donc rien à stocker dans
le Wix Secrets Manager : une clé publique dans les attributs du tag suffit.

### Point de configuration par client

L'élément s'exécute sur le domaine du site Wix du propriétaire, donc l'`Origin`
des requêtes est ce domaine. Il doit être ajouté à `property.allowed_domains`
pour cette propriété, sinon `/api/sign` répond `403 Domaine non autorisé`.
Un `allowed_domains` vide signifie « aucune restriction » — pratique en test,
à éviter en production.

## Calendrier de disponibilités

`<manahote-availability>` est un second composant indépendant, bâti sur
`GET /unit/availability` qui renvoie déjà la matrice logements × jours en
demi-journées, plus la légende des statuts. Ajouter ou renommer un statut côté
back-office se répercute donc sans toucher au widget.

Douze mois par défaut, **mois par mois et empilés**, sans navigation : la période
se parcourt en descendant, comme le calendrier historique du site. Une matrice
unique courant sur douze mois imposerait un défilement horizontal interminable.

Deux rendus selon la largeur, choisis par `matchMedia` plutôt que par du CSS sur
deux arbres DOM : à onze logements sur douze mois, dupliquer les cellules
coûterait cher. Au-dessus de 820 px, une matrice logements × jours par mois ;
en dessous, un sélecteur de logement et des calendriers mensuels.

Chaque case est coupée en diagonale — matin en haut à gauche, après-midi en bas
à droite — pour que le jour d'un départ le matin et d'une arrivée l'après-midi
se lise d'un coup d'œil, comme sur les calendriers de location classiques.

Une case est cliquable dès que son **après-midi** est libre, pas seulement quand
la journée entière l'est : le client arrive l'après-midi, un matin occupé n'est
que le départ du séjour précédent. Exiger la journée complète rendait les jours
de rotation inaccessibles — précisément les plus disputés en haute saison. Les
dates passées restent exclues, le formulaire de réservation les refuserait.

Deux pièges rencontrés, à ne pas réintroduire :

- une demi-journée occupée arrive sous la forme `["confirmed", 999900]`, un
  couple `[code, id]`. Lue comme un objet, elle retombait sur « confirmé » et
  toutes les préréservations s'affichaient en vert ;
- le quantième posé sur la case a besoin d'une pastille claire : le fond peut
  être blanc, vert ou ambre, et aucune couleur de texte ne reste lisible sur les
  trois.

### Statuts visibles

La matrice publique n'affiche que les statuts marqués `active_front = 1`.
`on_hold` (Préréservé) l'a été par la migration `ShowOnHoldStatusOnFront` : il
bloque réellement les réservations, l'ignorer côté front revenait à laisser un
visiteur aller au bout d'une demande vouée au refus. `pending` reste invisible,
volontairement — une demande ne bloque pas les dates.

## Langues

Cinq langues embarquées : `fr`, `en`, `nl`, `de`, `es`. La langue est résolue
dans cet ordre : attribut `lang` du composant, puis `document.documentElement.lang`,
puis français. Un site déjà balisé `<html lang="en">` obtient donc un widget
anglais sans rien déclarer.

Les dictionnaires vivent **dans le fichier**, pas dans des JSON chargés à la
demande : un `fetch` est soumis au CORS, contrairement au script classique qui
transporte le widget — aller chercher `en.json` depuis un site tiers rejouerait
le piège qui avait déjà empêché le composant de s'enregistrer. Cinq langues
pèsent environ 4 Ko.

Aucun nom de mois ni format de date n'est écrit à la main : `Intl` les produit
à partir de la locale, y compris les pluriels de devise et l'ordre des jours de
la semaine. Les formateurs sont mémorisés — le calendrier en construirait un par
cellule autrement.

Les libellés de statut sont traduits **par code** (`free`, `on_hold`,
`confirmed`…) et ne retombent sur le libellé envoyé par l'API que pour un code
inconnu. Aucun changement serveur n'a donc été nécessaire pour la légende.

Restent volontairement dans la langue du back-office : les noms de logements
(noms propres), les libellés d'options du catalogue, et les messages d'erreur
renvoyés par l'API — ces derniers demanderaient que l'API expose un code à côté
du message.

## Habillage : thème du site

Le widget n'impose pas sa charte. Chaque couleur et chaque police suit la même
chaîne de priorité :

1. le réglage explicite du panneau (`--mh-accent`, `--mh-font-title`, `--mh-font-body`),
2. le token du thème Wix (`--wst-color-*`, `--wst-font-style-*`),
3. une valeur de repli neutre.

Les propriétés personnalisées traversent la frontière du shadow DOM par
héritage : sur le **site publié**, les `--wst-*` posés par Wix sur la page se
résolvent donc à l'intérieur du composant, sans avoir à injecter la feuille de
style du site. Dans l'**éditeur**, où l'élément est isolé dans une iframe
bac-à-sable, ces variables sont absentes et ce sont les replis qui rendent :
c'est attendu, il faut publier pour juger de l'habillage réel.

Tokens utilisés : `--wst-color-action`, `--wst-color-line`, `--wst-color-title`,
`--wst-color-text-primary`, `--wst-color-text-secondary`,
`--wst-button-color-fill-primary` (+ variantes `hover` et `border`),
`--wst-font-style-h3`, `--wst-font-style-body-medium`.

Tous vérifiés comme présents et résolus sur un site publié. Attention en lisant
une valeur renvoyée par le sélecteur de couleur : Wix expose **deux familles de
noms en parallèle**. Celle utilisée ici (`--wst-color-*`, `--wst-button-color-*`)
et une famille « palette » (`--wst-accent-1..4-color`, `--wst-base-1/2-color`,
`--wst-shade-1..3-color`, `--wst-color-custom-N`) dans laquelle puise le
sélecteur. Voir une référence à `--wst-accent-2-color` dans un attribut ne
signifie donc pas que les tokens ci-dessus sont faux.

Trois écarts assumés :

- les titres gardent `font-weight: 600` même quand le thème en impose une autre.
  Le raccourci `font` réinitialise la graisse, et sans thème le titre
  deviendrait indistinct du texte courant ;
- le survol utilise `color-mix()` quand le navigateur le gère, avec repli sur un
  gris fixe — ce qui rend le survol correct aussi sur les thèmes sombres ;
- `--wst-color-line` est atténué à 20 %. Les thèmes Wix y posent volontiers du
  noir pur : appliqué tel quel, il cerne tout le widget de bordures noires
  (séparateurs, puces, champs, boutons). On garde la teinte, pas la brutalité.

## Nommage des attributs, vérifié en production

Wix pose les props du panneau **en minuscules collées**, pas en kebab-case :

```html
<manahote-booking property="1" apikey="…" apibase="" unit=""
                  accent="var(--wst-accent-2-color, #567BFF)"
                  titlefont="16px …" bodyfont="16px …">
```

La conversion camelCase → kebab-case n'est documentée que pour les *plugins*.
C'est pourquoi `ManahoteBooking.ATTRS` accepte plusieurs orthographes par
réglage : lire uniquement `api-key` suffisait à rendre le widget inopérant.
Les attributs vides (`unit=""`, `apibase=""`) sont ignorés au profit des valeurs
par défaut.

Côté panneau, la couleur et les polices passent par les sélecteurs natifs Wix
(`inputs.selectColor`, `inputs.selectFont`). Quand le propriétaire choisit une
entrée de son thème, Wix renvoie une référence de variable
(`var(--wst-color-fill-accent-1, #000)`) que le widget pose telle quelle : le
composant continue donc de suivre le thème si celui-ci change ensuite. Le bouton
« Revenir au thème » vide la prop et rétablit l'héritage. Les polices choisies
sont déclarées à Wix via `widget.setPreloadFonts()` pour qu'il les précharge.

Les valeurs venant des réglages ne sont jamais concaténées dans la feuille de
style : elles passent par `style.setProperty()`, donc le CSSOM les valide et
elles ne peuvent pas s'échapper du bloc `<style>`.

## Embarquer hors de Wix

Deux lignes suffisent sur n'importe quelle page :

```html
<script defer src="https://www.manahote.app/public/widget/manahote-booking.js"></script>
<manahote-booking property="1" api-key="…" accent="var(--green)"></manahote-booking>
```

**Script classique, pas `type="module"`.** Un module est toujours récupéré en mode
CORS, même pour un simple `src` : sans en-tête `Access-Control-Allow-Origin` sur
`/public/widget/`, le navigateur rejette le fichier sans le moindre message dans
la console, et le composant n'est jamais enregistré. Le fichier ne contient ni
`import` ni `export`, un script classique convient donc — et il n'est pas soumis
au CORS. `defer` évite de bloquer le rendu depuis le `<head>`.

La page hôte peut habiller le composant en posant les variables sur la balise :
les règles de la page l'emportent sur celles de `:host` du shadow DOM, ce qui est
vérifié en conditions réelles.

```css
manahote-booking {
  --mh-line: var(--border);
  --mh-title: var(--green-dark);
  --mh-text: var(--text);
  --mh-text-soft: var(--muted);
}
```

## Contraintes Wix à annoncer au propriétaire

Pour qu'un custom element s'affiche sur un site Wix publié :

- plan premium et domaine connecté,
- pas de bandeau publicitaire Wix sur le site,
- URL du script en **HTTPS** (sinon le composant n'est pas rendu en production).

## Enregistrement de l'app Wix

Dans [Custom Apps](https://manage.wix.com/account/custom-apps) (Wix Studio) →
**Extensions** → **Site Widget** → **Custom Element** :

| Champ | Valeur |
|---|---|
| Tag name | `manahote-booking` (identique à `customElements.define()`) |
| Script URL | `https://www.manahote.app/public/widget/manahote-booking.js` |
| Settings panel URL | page HTML de réglages (à écrire, rendue en iframe par Wix) |

## Fichiers

| Fichier | Rôle |
|---|---|
| `src/manahote-booking.js` | custom element de réservation (parcours complet) |
| `src/manahote-availability.js` | custom element de calendrier de disponibilités |
| `src/settings-panel.html` | panneau de réglages affiché dans l'éditeur Wix |
| `build.sh` | copie les deux sous `www/public/widget/` (pas de compilation) |
| `assets/widget-icon.svg` | source de l'icône 1:1 du widget |
| `assets/render.sh` | rasterise l'icône en 512 / 256 / 128 / 64 px |

L'icône est **téléversée** dans l'app dashboard Wix (vignette du panneau
« Add Elements » et fiche du market listing) : il n'y a pas d'URL à déclarer,
les PNG ne sont donc pas servis publiquement. Utilisez `widget-icon-512.png`,
les tailles inférieures servent aux vérifications de lisibilité.

Après toute modification des sources : `bash integration/wix/build.sh`, puis
`bash infra/deploy.sh`.

## État actuel

Le parcours est porté depuis `www/form.html` : sélection du logement, calendrier
sur deux mois avec disponibilités, stop-sell et CTA/CTD, sélection
arrivée/départ, voyageurs plafonnés par la capacité, options du catalogue,
devis (y compris le mode « Sur devis ») et formulaire de demande.

Vérifié dans Chrome contre l'API locale : tarifs réels affichés là où
`rate_calendar` est renseigné, « sur devis » ailleurs, jours en stop-sell
désactivés, recalcul du devis au changement d'option (309 € → 389 € avec le
forfait ménage), et réservation créée avec le bon montant.

L'habillage sur le thème du site est vérifié en simulant les variables `--wst-*`
d'un thème sombre : couleurs de titre, de texte, de traits et polices reprises,
et priorité respectée entre réglage explicite, token de thème et repli.

Reste à faire : traduction, le widget est en français uniquement.

## Sources

- [About Site Widget Extensions](https://dev.wix.com/docs/build-apps/develop-your-app/extensions/site-extensions/site-widgets/about-site-widget-extensions)
- [Add Self-hosted Site Widget Extensions with Custom Elements](https://dev.wix.com/docs/build-apps/develop-your-app/frameworks/self-hosting/supported-extensions/site-extensions/site-widgets-and-plugins/add-self-hosted-site-widget-extensions-with-custom-elements)
- [Guide to Widget Extensions (iframe) — deprecated](https://dev.wix.com/docs/build-apps/develop-your-app/frameworks/self-hosting/supported-extensions/deprecated/iframe/guide-to-widget-extensions-iframe)
- [About Self-hosting for Wix Apps](https://dev.wix.com/docs/build-apps/develop-your-app/frameworks/self-hosting/about-self-hosting-for-wix-apps)
- [About Wix CLI Apps](https://dev.wix.com/docs/api-reference/articles/platform-overview/about-wix-cli-apps)
