src/app/admin/vitrine/builder/components/header-builder.tsx

component·app·9.8 KB · 235 lignes· Voir l'itinéraire
Annotation non disponible

Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.

1 export

default

Code source· tsx· tronqué à 200 lignes sur 235

'use client'

import { useState } from 'react'
import { useBuilderStore } from '../hooks/use-builder-store'
import { saveVitrineAndMarkSaved } from '../hooks/use-vitrine-sync'
import { useAutosave } from '../hooks/use-autosave'
import { Eye, Save, Upload, ChevronRight, Monitor, Smartphone, History, RotateCcw, Loader2, PenLine } from 'lucide-react'
import VersionsDrawer from './versions-drawer'
import ToastContainer, { addToast } from './toast'

interface HeaderBuilderProps {
  tenantId: string
  subdomain: string
}

export default function HeaderBuilder({ tenantId, subdomain }: HeaderBuilderProps) {
  const { pages, activePageId, isDirty, isSaving, setIsSaving, setIsDirty, getActivePage, previewMode, setPreviewMode } = useBuilderStore()
  const [showVersions, setShowVersions] = useState(false)
  const [showPublishModal, setShowPublishModal] = useState(false)
  const [lastSaveTime, setLastSaveTime] = useState<Date | null>(null)
  const [lastSaveLabel, setLastSaveLabel] = useState<string>('')

  useAutosave(() => {
    setLastSaveTime(new Date())
    setLastSaveLabel('Autosave')
    addToast('Autosave effectue', 'info')
  })

  const activePage = getActivePage()

  const buildBreadcrumb = (): string[] => {
    if (!activePage) return []
    const path: string[] = []
    const find = (pages: typeof activePage[], targetId: string): boolean => {
      for (const p of pages) {
        if (p.id === targetId) { path.push(p.titre); return true }
        if (find(p.enfants, targetId)) { path.splice(path.length - 1, 0, p.titre); return true }
      }
      return false
    }
    find(pages, activePage.id)
    return path
  }

  const breadcrumb = buildBreadcrumb()

  const getSaveIndicator = () => {
    if (isSaving) return { text: 'Sauvegarde...', color: 'text-gray-400' }
    if (isDirty) return { text: 'Non sauvegarde', color: 'text-amber-400' }
    if (lastSaveTime) {
      const diff = Math.floor((Date.now() - lastSaveTime.getTime()) / 60000)
      const label = lastSaveLabel ? lastSaveLabel + ' ' : ''
      if (diff < 1) return { text: label + 'sauvegarde a l instant', color: 'text-green-400' }
      return { text: label + 'sauvegarde il y a ' + diff + 'min', color: 'text-green-400' }
    }
    return null
  }

  const indicator = getSaveIndicator()

  const previewUrl = window.location.protocol + '//' + subdomain + '.' + (process.env.NEXT_PUBLIC_DOMAIN || 'wari.pro') + '/?preview=live'

  const handleModeToggle = (mode: 'edit' | 'desktop' | 'mobile') => {
    setPreviewMode(mode)
  }

  const handleSave = async () => {
    setIsSaving(true)
    try {
      await saveVitrineAndMarkSaved()
      const res = await fetch('/api/admin/vitrine/versions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ statut: 'DRAFT', label: null })
      })
      if (res.ok) {
        setLastSaveTime(new Date())
        setLastSaveLabel('')
        addToast('Version enregistree', 'success')
      }
    } catch (e) {
      addToast('Erreur lors de l enregistrement', 'error')
    } finally {
      setIsSaving(false)
    }
  }

  const handlePublish = async () => {
    setIsSaving(true)
    try {
      await saveVitrineAndMarkSaved()
      const res = await fetch('/api/admin/vitrine/versions', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ statut: 'PUBLISHED', label: null })
      })
      if (res.ok) {
        setLastSaveTime(new Date())
        addToast('Vitrine mise en ligne !', 'success')
      }
    } catch (e) {
      addToast('Erreur lors de la publication', 'error')
    } finally {
      setIsSaving(false)
      setShowPublishModal(false)
    }
  }

  const handleRollback = async () => {
    if (!confirm('Revenir a la version publiee ? Vos modifications non sauvegardees seront perdues.')) return
    setIsSaving(true)
    try {
      const res = await fetch('/api/admin/vitrine/versions/published')
      const data = await res.json()
      if (data.snapshot) {
        await fetch('/api/admin/vitrine/versions/' + data.snapshot.id, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action: 'load' })
        })
        addToast('Version publiee restauree', 'success')
        window.location.reload()
      } else {
        addToast('Aucune version publiee trouvee', 'info')
      }
    } catch (e) {
      addToast('Erreur lors du rollback', 'error')
    } finally {
      setIsSaving(false)
    }
  }

  return (
    <>
      <header className="h-12 flex items-center justify-between px-4 bg-gray-900 border-b border-white/10 flex-shrink-0">
        {/* Breadcrumb */}
        <div className="flex items-center gap-1 text-sm min-w-0">
          <span className="text-white/40 flex-shrink-0">Vitrine</span>
          {breadcrumb.map((crumb, i) => (
            <span key={i} className="flex items-center gap-1 min-w-0">
              <ChevronRight className="w-3 h-3 text-white/20 flex-shrink-0" />
              <span className={'truncate ' + (i === breadcrumb.length - 1 ? 'text-white font-medium' : 'text-white/40')}>
                {crumb}
              </span>
            </span>
          ))}
        </div>

        {/* Centre — mode toggle edit / desktop / mobile */}
        <div className="flex items-center gap-1 bg-gray-800 rounded-lg p-1 flex-shrink-0">
          <button
            onClick={() => handleModeToggle('edit')}
            title="Mode édition"
            className={'p-1.5 rounded-md transition-colors flex items-center gap-1 px-2 text-xs ' + (previewMode === 'edit' ? 'bg-white/10 text-white' : 'text-white/40 hover:text-white')}
          >
            <PenLine className="w-3.5 h-3.5" />
            <span className="hidden sm:inline">Edition</span>
          </button>
          <button
            onClick={() => handleModeToggle('desktop')}
            title="Aperçu desktop"
            className={'p-1.5 rounded-md transition-colors ' + (previewMode === 'desktop' ? 'bg-white/10 text-white' : 'text-white/40 hover:text-white')}
          >
            <Monitor className="w-4 h-4" />
          </button>
          <button
            onClick={() => handleModeToggle('mobile')}
            title="Aperçu mobile"
            className={'p-1.5 rounded-md transition-colors ' + (previewMode === 'mobile' ? 'bg-white/10 text-white' : 'text-white/40 hover:text-white')}
          >
            <Smartphone className="w-4 h-4" />
          </button>
        </div>

        {/* Actions droite */}
        <div className="flex items-center gap-2 flex-shrink-0">
          {indicator && (
            <span className={'text-xs flex items-center gap-1 ' + indicator.color}>
              <span className={'w-1.5 h-1.5 rounded-full ' + (isDirty ? 'bg-amber-400' : 'bg-green-400')} />
              {indicator.text}
            </span>
          )}
          <button
            onClick={() => window.open(previewUrl, '_blank')}
            className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-white/60 hover:text-white border border-white/10 hover:border-white/20 rounded-lg transition-colors"
          >
            <Eye className="w-3.5 h-3.5" />
            Apercu
          </button>
          <button onClick={handleRollback} disabled={isSaving} className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-white/60 hover:text-white border border-white/10 hover:border-white/20 rounded-lg transition-colors disabled:opacity-40" title="Revenir a la version publiee">
            <RotateCcw className="w-3.5 h-3.5" />
          </button>
          <button onClick={() => setShowVersions(true)} className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-white/60 hover:text-white border border-white/10 hover:border-white/20 rounded-lg transition-colors">
            <History className="w-3.5 h-3.5" />
            Versions
          </button>
          <button onClick={handleSave} disabled={isSaving} className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-white/60 hover:text-white border border-white/10 hover:border-white/20 rounded-lg transition-colors disabled:opacity-40">
            {isSaving ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Save className="w-3.5 h-3.5" />}
            Enregistrer
          </button>