diff --git a/frontend/src/components/AddPersonButton.css b/frontend/src/components/AddPersonButton.css deleted file mode 100644 index ad59fb0..0000000 --- a/frontend/src/components/AddPersonButton.css +++ /dev/null @@ -1,172 +0,0 @@ -/* ============================================================ - Add Person Button - FAB draggable pour ajouter personne - ============================================================ */ - -.add-person-button { - position: fixed; - width: 64px; - height: 64px; - border-radius: 50%; - - background: rgba(242, 239, 234, 0.95); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 2px solid rgba(147, 64, 42, 0.3); - - box-shadow: - 0 8px 32px rgba(42, 38, 34, 0.25), - 0 2px 8px rgba(42, 38, 34, 0.12); - - cursor: grab; - user-select: none; - - display: flex; - align-items: center; - justify-content: center; - - transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1); - z-index: 900; - - /* Position initiale : bas-gauche */ - bottom: 24px; - left: 24px; -} - -.add-person-button:hover { - transform: scale(1.12); - box-shadow: - 0 12px 40px rgba(147, 64, 42, 0.35), - 0 4px 12px rgba(42, 38, 34, 0.18); - border-color: rgba(147, 64, 42, 0.5); -} - -.add-person-button.dragging { - cursor: grabbing; - transform: scale(1.2) rotate(5deg); - box-shadow: - 0 16px 48px rgba(147, 64, 42, 0.45), - 0 6px 16px rgba(42, 38, 34, 0.25); - border-color: rgba(147, 64, 42, 0.7); - z-index: 1100; -} - -/* Icône + */ -.add-person-button-icon { - font-size: 32px; - font-weight: 300; - color: #93402A; - line-height: 1; - filter: drop-shadow(0 1px 3px rgba(42, 38, 34, 0.15)); - transition: transform 0.2s ease; -} - -.add-person-button:hover .add-person-button-icon { - transform: rotate(90deg); -} - -.add-person-button.dragging .add-person-button-icon { - transform: rotate(180deg); -} - -/* Pulse animation sur apparition */ -@keyframes addPersonPulse { - 0% { - opacity: 0; - transform: scale(0.7); - } - 50% { - transform: scale(1.15); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -.add-person-button.entering { - animation: addPersonPulse 0.5s cubic-bezier(0.22, 1, 0.36, 1); -} - -/* Tooltip */ -.add-person-button-tooltip { - position: absolute; - top: 50%; - left: calc(100% + 12px); - transform: translateY(-50%); - - padding: 8px 14px; - background: rgba(42, 38, 34, 0.95); - color: #FFFFFF; - font-size: 14px; - font-weight: 500; - border-radius: 8px; - white-space: nowrap; - - opacity: 0; - pointer-events: none; - transition: opacity 0.2s ease; - - box-shadow: 0 4px 12px rgba(42, 38, 34, 0.2); -} - -.add-person-button:hover .add-person-button-tooltip { - opacity: 1; -} - -.add-person-button.dragging .add-person-button-tooltip { - opacity: 0; -} - -/* Instructions drag (affichées pendant drag) */ -.add-person-drag-hint { - position: fixed; - top: 24px; - left: 50%; - transform: translateX(-50%); - z-index: 1050; - - padding: 10px 18px; - background: rgba(242, 239, 234, 0.98); - backdrop-filter: blur(24px); - border-radius: 50px; - border: 1px solid rgba(147, 64, 42, 0.2); - - font-size: 14px; - font-weight: 450; - color: #2A2622; - - box-shadow: - 0 6px 24px rgba(42, 38, 34, 0.18), - 0 2px 8px rgba(42, 38, 34, 0.08); - - animation: hintSlideDown 0.3s cubic-bezier(0.22, 1, 0.36, 1); -} - -@keyframes hintSlideDown { - from { - opacity: 0; - transform: translateX(-50%) translateY(-10px); - } - to { - opacity: 1; - transform: translateX(-50%) translateY(0); - } -} - -/* Responsive mobile */ -@media (max-width: 768px) { - .add-person-button { - width: 56px; - height: 56px; - bottom: 20px; - left: 20px; - } - - .add-person-button-icon { - font-size: 28px; - } - - .add-person-button-tooltip { - display: none; - } -} diff --git a/frontend/src/components/AddPersonButton.jsx b/frontend/src/components/AddPersonButton.jsx deleted file mode 100644 index 7087c49..0000000 --- a/frontend/src/components/AddPersonButton.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useRef, useEffect, useState } from 'react' -import { Plus, Baby, Heart, Users, MapPin } from 'lucide-react' -import './AddPersonButton.css' -import { t } from '../i18n/index.js' - -/** - * Bouton FAB draggable pour ajouter une personne - * Utilisé en mode édition pour drag-and-drop dans la galaxie - * - * @param {Object} props - * @param {boolean} props.isDragging - État dragging (du hook) - * @param {Object} props.dragPosition - Position drag {x, y} - * @param {Function} props.onDragStart - Handler début drag - * @param {string} props.linkType - Type de lien détecté ('child', 'spouse', 'sibling') - */ -function AddPersonButton({ isDragging, dragPosition, onDragStart, linkType }) { - const buttonRef = useRef(null) - const [entering, setEntering] = useState(true) - - // Animation d'entrée - useEffect(() => { - const timer = setTimeout(() => setEntering(false), 500) - return () => clearTimeout(timer) - }, []) - - const handleMouseDown = (e) => { - e.preventDefault() - if (!buttonRef.current) return - - const rect = buttonRef.current.getBoundingClientRect() - const buttonPosition = { - x: rect.left + rect.width / 2, - y: rect.top + rect.height / 2, - } - - onDragStart?.(e, buttonPosition) - } - - // Position inline si dragging - const style = isDragging - ? { - left: `${dragPosition.x - 32}px`, - top: `${dragPosition.y - 32}px`, - transition: 'none', - } - : {} - - // Message tooltip selon linkType - let tooltipText = t('Glisser pour ajouter une personne') - if (isDragging) { - if (linkType === 'child') tooltipText = t('Ajouter un enfant') - else if (linkType === 'spouse') tooltipText = t('Ajouter un conjoint') - else if (linkType === 'sibling') tooltipText = t('Ajouter un frère/sœur') - else tooltipText = t('Relâcher pour créer') - } - - return ( - <> - - - {/* Hint pendant drag */} - {isDragging && ( -
- {linkType === 'child' && <> {t('Enfant')}} - {linkType === 'spouse' && <> {t('Conjoint')}} - {linkType === 'sibling' && <> {t('Frère·Sœur')}} - {!linkType && <> {t('Positionnez la personne')}} -
- )} - - ) -} - -export default AddPersonButton diff --git a/frontend/src/components/CreateMyTreeButton.css b/frontend/src/components/CreateMyTreeButton.css deleted file mode 100644 index fa43248..0000000 --- a/frontend/src/components/CreateMyTreeButton.css +++ /dev/null @@ -1,261 +0,0 @@ -/* ============================================================ - CreateMyTreeButton — Pill + Panel morph, bas-droite - Même langage visuel que la ContextualNavbar - ============================================================ */ - -/* ---------------------------------------------------------- - Pill bouton (état fermé) — même aesthetic que la navbar - ---------------------------------------------------------- */ -.cmt-pill { - position: fixed; - bottom: 100px; - right: 20px; - z-index: 1001; - - display: flex; - align-items: center; - padding: 13px 22px; - border: 1px solid rgba(42, 38, 34, 0.1); - border-radius: 50px; - background: rgba(242, 239, 234, 0.97); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - box-shadow: - 0 4px 40px rgba(42, 38, 34, 0.22), - 0 1px 6px rgba(42, 38, 34, 0.1); - - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 14px; - font-weight: 500; - color: #2A2622; - cursor: pointer; - white-space: nowrap; - - animation: cmtPillIn 0.5s cubic-bezier(0.22, 1, 0.36, 1); - transition: box-shadow 0.2s ease, transform 0.2s ease, background 0.2s ease; -} - -@keyframes cmtPillIn { - from { - opacity: 0; - transform: translateY(12px) scale(0.9); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -.cmt-pill:hover { - background: rgba(242, 239, 234, 1); - box-shadow: - 0 8px 48px rgba(42, 38, 34, 0.28), - 0 2px 8px rgba(42, 38, 34, 0.12); - transform: translateY(-2px); -} - -.cmt-pill:active { - transform: translateY(0) scale(0.97); -} - -/* ---------------------------------------------------------- - Panel (état ouvert) — card qui morphe depuis la pill - ---------------------------------------------------------- */ -.cmt-panel { - position: fixed; - bottom: 100px; - right: 20px; - z-index: 1001; - - width: 300px; - background: rgba(255, 255, 255, 0.97); - backdrop-filter: blur(28px); - -webkit-backdrop-filter: blur(28px); - border-radius: 20px; - border: 1px solid rgba(147, 64, 42, 0.18); - box-shadow: - 0 12px 48px rgba(42, 38, 34, 0.22), - 0 2px 10px rgba(42, 38, 34, 0.1); - - transform-origin: bottom right; - animation: cmtMorphIn 0.38s cubic-bezier(0.22, 1, 0.36, 1); - overflow: hidden; -} - -@keyframes cmtMorphIn { - 0% { - opacity: 0; - transform: scale(0.55) translateY(8px); - border-radius: 50px; - } - 60% { - border-radius: 22px; - } - 100% { - opacity: 1; - transform: scale(1) translateY(0); - border-radius: 20px; - } -} - -/* Header */ -.cmt-panel-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 18px 12px; - border-bottom: 1px solid rgba(147, 64, 42, 0.1); -} - -.cmt-panel-title { - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 13px; - font-weight: 600; - color: #2A2622; - line-height: 1.3; -} - -.cmt-panel-close { - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border: none; - border-radius: 50%; - background: rgba(42, 38, 34, 0.07); - color: #6B645D; - font-size: 12px; - cursor: pointer; - flex-shrink: 0; - transition: background 0.15s, color 0.15s; -} - -.cmt-panel-close:hover { - background: rgba(42, 38, 34, 0.13); - color: #2A2622; -} - -/* Body */ -.cmt-panel-body { - padding: 16px 18px 18px; -} - -.cmt-label { - display: block; - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 12px; - color: #6B645D; - margin-bottom: 7px; - letter-spacing: 0.02em; -} - -.cmt-input { - width: 100%; - box-sizing: border-box; - border-radius: 12px; - border: 1px solid rgba(147, 64, 42, 0.25); - background: rgba(255, 255, 255, 0.9); - color: #2a2622; - padding: 10px 13px; - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 14px; - outline: none; - transition: border-color 0.15s, box-shadow 0.15s; -} - -.cmt-input:focus { - border-color: #d2694a; - box-shadow: 0 0 0 3px rgba(210, 105, 74, 0.12); -} - -.cmt-slug-preview { - margin-top: 8px; - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 11px; - color: #93402A; -} - -.cmt-slug-preview code { - background: rgba(147, 64, 42, 0.1); - border-radius: 4px; - padding: 1px 5px; - color: #2a2622; -} - -.cmt-error { - margin-top: 8px; - background: rgba(171, 49, 49, 0.08); - border: 1px solid rgba(171, 49, 49, 0.25); - color: #862f2f; - border-radius: 10px; - padding: 9px 12px; - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 12px; -} - -.cmt-btn-create { - margin-top: 14px; - width: 100%; - padding: 11px 16px; - border-radius: 12px; - border: none; - background: #2a2622; - color: #ffffff; - font-family: 'DM Sans', system-ui, sans-serif; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, box-shadow 0.15s, transform 0.15s; -} - -.cmt-btn-create:hover:not(:disabled) { - background: linear-gradient(135deg, #93402a, #2a2622); - box-shadow: 0 4px 14px rgba(147, 64, 42, 0.32); - transform: translateY(-1px); -} - -.cmt-btn-create:active:not(:disabled) { - transform: translateY(0); -} - -.cmt-btn-create:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* ---------------------------------------------------------- - Mobile — bottom sheet - ---------------------------------------------------------- */ -@media (max-width: 768px) { - .cmt-pill { - bottom: 84px; - right: 12px; - font-size: 13px; - padding: 11px 18px; - } - - .cmt-panel { - bottom: 0; - right: 0; - left: 0; - width: 100%; - border-radius: 20px 20px 0 0; - transform-origin: bottom center; - } - - @keyframes cmtMorphIn { - from { - opacity: 0; - transform: translateY(100%); - } - to { - opacity: 1; - transform: translateY(0); - } - } - - .cmt-panel-body { - padding: 16px 20px 32px; - } -} diff --git a/frontend/src/components/CreateMyTreeButton.jsx b/frontend/src/components/CreateMyTreeButton.jsx deleted file mode 100644 index ebbf6cd..0000000 --- a/frontend/src/components/CreateMyTreeButton.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useState, useMemo } from 'react' -import './CreateMyTreeButton.css' -import PzBusy from './PzBusy.jsx' -import { t } from '../i18n/index.js' - -function normalizeSlug(input) { - return input - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-+|-+$/g, '') -} - -const CreateMyTreeButton = ({ onCreateTree, loading }) => { - const [open, setOpen] = useState(false) - const [name, setName] = useState('') - const [error, setError] = useState('') - - const slug = useMemo(() => normalizeSlug(name.trim()), [name]) - const canSubmit = name.trim().length >= 2 && slug.length >= 3 && !loading - - const handleOpen = () => { - setError('') - setOpen(true) - } - - const handleClose = () => { - setOpen(false) - setName('') - setError('') - } - - const handleSubmit = async (e) => { - e.preventDefault() - if (!canSubmit) return - setError('') - const result = await onCreateTree(name.trim()) - if (result !== null && result !== undefined) { - setError(typeof result === 'string' ? result : t('Erreur lors de la création. Essayez un autre nom.')) - } - } - - return ( - <> - {!open && ( - - )} - - {open && ( -
-
- {t('Créer votre arbre dès maintenant')} - -
-
-
- - setName(e.target.value)} - placeholder={t('Ex : Famille Martin')} - required - /> - {slug && ( -
- {t('Adresse :')} {slug} -
- )} - {error &&
{error}
} - -
-
-
- )} - - ) -} - -export default CreateMyTreeButton diff --git a/frontend/src/components/EditModeOverlay.jsx b/frontend/src/components/EditModeOverlay.jsx deleted file mode 100644 index a58ce18..0000000 --- a/frontend/src/components/EditModeOverlay.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useMemo } from 'react' -import { X } from 'lucide-react' -import './EditModeOverlay.css' -import { t } from '../i18n/index.js' - -function EditModeOverlay({ active, hasDraft, role = 'contributor', onDeactivate, onSubmit }) { - const isAdmin = role === 'admin' - - const borderClass = useMemo(() => ( - `edit-mode-border ${isAdmin ? 'edit-mode-border--admin' : 'edit-mode-border--contributor'}` - ), [isAdmin]) - - const toastLabel = isAdmin ? t('Mode édition actif') : t('Mode contribution actif') - const submitLabel = isAdmin ? t("Mettre à jour l'arbre") : t('Envoyer les modifications') - - if (!active) return null - - return ( - <> -