# Mail Integration — Phase 7 : Admin Config + Sidebar + Polish > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Finaliser l'intégration mail avec l'UI admin de configuration, le lien sidebar avec badge unread temps réel (polling 30s), et la documentation utilisateur/opérationnelle finale. **Architecture:** Onglet `AdminMailTab.vue` calqué sur `AdminZimbraTab.vue` (form IMAP/SMTP/credentials, bouton test connexion). Lien sidebar dans `layouts/default.vue` (visible ROLE_USER+ROLE_ADMIN seulement, masqué ROLE_CLIENT pur). Polling start au login / stop au logout via layout. Documentation finale dans `docs/` + section README mail. **Tech Stack:** Nuxt 4, Vue 3 Composition API, @malio/layer-ui, Pinia (useMailStore). --- ## Fichiers créés / modifiés | Fichier | Action | |---------|--------| | `frontend/components/admin/AdminMailTab.vue` | **Créer** | | `frontend/pages/admin.vue` | **Modifier** (ajout onglet mail) | | `frontend/layouts/default.vue` | **Modifier** (lien sidebar + polling lifecycle) | | `frontend/i18n/locales/fr.json` | **Modifier** (clés mail.admin.* + mail.sidebar.*) | | `frontend/i18n/locales/en.json` | **Modifier si présent** | | `docs/mail-cron-setup.md` | **Modifier** (enrichir checklist prod + sécurité) | | `docs/mail-integration.md` | **Créer** (doc complète intégration) | --- ## Task 1 : Composant `AdminMailTab.vue` **Fichier cible :** `frontend/components/admin/AdminMailTab.vue` **Modèle de référence :** `frontend/components/admin/AdminZimbraTab.vue` — reproduire exactement le même pattern (reactive form, hasPassword, isSaving/isTesting, loadSettings onMounted, handleSave/handleTest). **Service à utiliser :** `useMailService()` depuis `~/services/mail` — méthodes `getConfiguration`, `updateConfiguration`, `testConfiguration`. **DTOs :** `MailConfigurationDto`, `MailConfigurationUpdateDto`, `MailTestConnectionResultDto` depuis `~/services/dto/mail`. ### Étapes - [ ] Créer `frontend/components/admin/AdminMailTab.vue` - [ ] Déclarer le reactive form avec tous les champs de `MailConfigurationDto` (sauf `hasPassword`, qui est en lecture seule) : ``` protocol: '' (lecture seule "imap" en MVP — champ disabled) imapHost: '' imapPort: 993 (default OVH) imapEncryption: 'ssl' (default OVH) smtpHost: '' smtpPort: 465 (default OVH) smtpEncryption: 'ssl' (default OVH) username: '' password: '' (write-only — jamais pré-rempli) sentFolderPath: '' (ex: "Sent Messages" ou "INBOX.Sent") enabled: false ``` - [ ] `hasPassword` : `ref(false)` — alimenté par `getConfiguration().hasPassword` - [ ] `isSaving` : `ref(false)`, `isTesting` : `ref(false)` - [ ] `testResult` : `ref(null)` — réinitialisé à null au handleSave - [ ] `loadSettings()` : ```ts async function loadSettings(): Promise { const config = await getConfiguration() form.protocol = config.protocol ?? 'imap' form.imapHost = config.imapHost ?? '' form.imapPort = config.imapPort ?? 993 form.imapEncryption = config.imapEncryption ?? 'ssl' form.smtpHost = config.smtpHost ?? '' form.smtpPort = config.smtpPort ?? 465 form.smtpEncryption = config.smtpEncryption ?? 'ssl' form.username = config.username ?? '' form.sentFolderPath = config.sentFolderPath ?? '' form.enabled = config.enabled hasPassword.value = config.hasPassword // password jamais pré-rempli } ``` - [ ] `handleSave()` : construit un `MailConfigurationUpdateDto` — inclure `password` uniquement si `form.password` est non-vide, sinon omettre le champ. Après save réussi : `hasPassword.value = result.hasPassword`, vider `form.password`, `testResult.value = null` - [ ] `handleTest()` : appelle `testConfiguration()`, `testResult.value = result.ok`. Le champ `result.error` est affiché en sous-texte si `testResult.value === false` - [ ] Template — sections IMAP et SMTP avec labels traduits : - Titre `h2` : `$t('mail.admin.title')` - Section IMAP (`fieldset` ou `div` avec titre `$t('mail.admin.imapSection')`) : - `MalioInputText` pour `imapHost` + helper text `$t('mail.admin.ovhDefaultsHelp')` sous le champ (texte gris : `ssl0.ovh.net`) - `input[type=number]` natif pour `imapPort` (MalioInputText n'accepte pas les number — voir convention CLAUDE.md) - `select` natif pour `imapEncryption` (options : `ssl`, `tls`, `none`) - Section SMTP (`$t('mail.admin.smtpSection')`) : - `MalioInputText` pour `smtpHost` - `input[type=number]` natif pour `smtpPort` - `select` natif pour `smtpEncryption` (options : `ssl`, `tls`, `none`) - Credentials : - `MalioInputText` pour `username` - `MalioInputPassword` pour `password` + indicateur `hasPassword` (même pattern que `AdminZimbraTab.vue` : `

{{ $t('mail.admin.passwordSet') }}

`) - `MalioInputText` pour `sentFolderPath` (placeholder: `Sent Messages`) - `label` + checkbox natif pour `enabled` : `$t('mail.admin.enabled')` - Boutons côte à côte : - `MalioButton` submit `$t('mail.admin.save')` `:disabled="isSaving"` → `handleSave` - `MalioButton` variant tertiary `$t('mail.admin.test')` `:disabled="isTesting"` → `handleTest` - Résultat test : `

` coloré vert/rouge selon valeur — si false ET `testError`, afficher `testError` sous le résultat - [ ] `onMounted(() => { loadSettings() })` - [ ] Vérifier indentation 4 espaces, pas d'imports inutilisés, TypeScript strict --- ## Task 2 : Intégration `AdminMailTab` dans `pages/admin.vue` **Fichier cible :** `frontend/pages/admin.vue` Le pattern actuel utilise un tableau `tabs as const` + `activeTab` ref + v-if par composant. Il suffit d'ajouter l'entrée mail à la fin. ### Étapes - [ ] Ouvrir `frontend/pages/admin.vue` - [ ] Dans le tableau `tabs`, ajouter à la fin : ```ts { key: 'mail', label: 'Mail' }, ``` Remarque : les labels dans `tabs` sont des string litéraux inline (cf. autres onglets comme `'Zimbra'`), pas de i18n ici. - [ ] Le type `TabKey` est inféré automatiquement via `typeof tabs[number]['key']` — pas de changement nécessaire - [ ] Dans le template, après ``, ajouter : ```html ``` - [ ] Vérifier que Nuxt auto-importe `AdminMailTab` (fichier dans `components/admin/` → auto-import OK) - [ ] Test manuel : naviguer vers `/admin`, cliquer l'onglet "Mail", vérifier que le form se charge sans erreur 403 si connecté ROLE_ADMIN --- ## Task 3 : Lien sidebar dans `layouts/default.vue` **Fichier cible :** `frontend/layouts/default.vue` Le composant `SidebarLink` accepte `to`, `icon`, `label`, `collapsed`. Il n'a pas de prop `badge` native — vérifier dans `@malio/layer-ui/COMPONENTS.md` si une prop badge existe. Si non, wrapper manuel avec un `

` + badge absolu. ### Étapes - [ ] Lire `frontend/node_modules/@malio/layer-ui/COMPONENTS.md` pour vérifier les props de `SidebarLink` (présence prop `badge` ou `badgeCount`) - [ ] **Cas A — SidebarLink a une prop badge :** Utiliser directement : ```html ``` - [ ] **Cas B — SidebarLink n'a pas de prop badge (plus probable) :** Wrapper avec badge manuel : ```html
{{ mailStore.globalUnreadCount > 99 ? '99+' : mailStore.globalUnreadCount }}
``` - [ ] Dans `')) }) ``` Résultat attendu : chaîne sans `