refactor: simplify layout flow
Some checks failed
Build & Deploy / build-and-deploy (push) Has been cancelled

1. Peers (available devices)
2. Pair device + Public link (side by side buttons, modals for details)
3. File drop + Text send (only when a peer is selected)
4. Transfer progress

Removed the always-visible drop zone and join-code input from
PeerList. Pairing and public room now open as overlay modals.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ordinarthur 2026-04-14 12:52:54 +02:00
parent 612222ccde
commit 8212bc7391
4 changed files with 144 additions and 141 deletions

View File

@ -22,50 +22,60 @@ export default function DevicePairingPanel() {
const pairUrl = groupId ? `${BASE_URL}/pair?g=${groupId}` : "";
if (showQR && groupId) {
return (
<div className="bg-slate-800/50 border border-slate-700 rounded-2xl p-6 text-center space-y-4">
<h3 className="text-sm font-medium text-slate-400 uppercase tracking-wider">
Ajouter un appareil
</h3>
<p className="text-sm text-slate-300">
Scannez ce QR code depuis votre autre appareil
</p>
<div className="flex justify-center">
<div className="bg-white p-3 rounded-xl">
<QRCodeSVG value={pairUrl} size={180} />
</div>
</div>
<p className="text-xs text-slate-500">
L'appairage est permanent vos appareils se verront toujours,
même sur des réseaux différents.
</p>
<button
onClick={() => setShowQR(false)}
className="text-sm text-slate-500 hover:text-slate-300 transition-colors"
>
Fermer
</button>
</div>
);
}
return (
<>
<button
onClick={handleShowQR}
className="w-full flex items-center justify-center gap-2 px-4 py-3
className="flex-1 flex flex-col items-center justify-center gap-1.5 px-3 py-3
border border-slate-700 hover:border-brand-500 rounded-xl
text-slate-300 hover:text-white transition-all
bg-slate-900/30 hover:bg-slate-900/50"
>
<span className="text-lg">📲</span>
<span className="text-sm font-medium">
{groupId ? "Ajouter un autre appareil" : "Appairer mes appareils"}
<span className="text-xs font-medium text-center leading-tight">
{groupId ? "Ajouter un appareil" : "Appairer"}
</span>
</button>
{showQR && groupId && (
<div
className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-6"
onClick={() => setShowQR(false)}
>
<div
className="bg-slate-900 border border-slate-700 rounded-2xl p-6 max-w-sm w-full
text-center space-y-4"
onClick={(e) => e.stopPropagation()}
>
<h3 className="text-lg font-semibold text-white">
Appairer un appareil
</h3>
<p className="text-sm text-slate-400">
Scannez ce QR code depuis votre autre appareil
</p>
<div className="flex justify-center">
<div className="bg-white p-3 rounded-xl">
<QRCodeSVG value={pairUrl} size={200} />
</div>
</div>
<p className="text-xs text-slate-500">
Appairage permanent vos appareils se verront toujours,
sur n'importe quel réseau.
</p>
<button
onClick={() => setShowQR(false)}
className="px-6 py-2 bg-slate-800 hover:bg-slate-700 rounded-xl
text-sm text-slate-300 transition-colors"
>
Fermer
</button>
</div>
</div>
)}
</>
);
}

View File

@ -1,16 +1,13 @@
import { useState } from "react";
import { useStore } from "../stores/useStore";
import PeerAvatar from "./PeerAvatar";
interface PeerListProps {
onPeerSelect: (peerId: string) => void;
onCreateRoom?: () => void;
}
export default function PeerList({ onPeerSelect, onCreateRoom }: PeerListProps) {
export default function PeerList({ onPeerSelect }: PeerListProps) {
const peers = useStore((s) => s.peers);
const selectedPeerId = useStore((s) => s.selectedPeerId);
const [joinCode, setJoinCode] = useState("");
if (peers.length === 0) {
return (
@ -18,56 +15,9 @@ export default function PeerList({ onPeerSelect, onCreateRoom }: PeerListProps)
<div className="text-5xl mb-4">📡</div>
<p className="text-lg font-medium">En attente d'appareils...</p>
<p className="text-sm mt-2 text-center max-w-xs">
Ouvrez AnyDrop sur un autre appareil connecté au même Wi-Fi.
Ouvrez AnyDrop sur un autre appareil connecté au même Wi-Fi,
ou appairez vos appareils ci-dessous.
</p>
<div className="mt-6 w-full max-w-xs space-y-3">
<p className="text-xs text-slate-600 text-center">Pas sur le même réseau ?</p>
{onCreateRoom && (
<button
onClick={onCreateRoom}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5
border border-slate-700 hover:border-brand-500 rounded-xl
text-slate-300 hover:text-white transition-all
bg-slate-900/30 hover:bg-slate-900/50"
>
<span>🔗</span>
<span className="text-sm font-medium">Créer un lien de partage</span>
</button>
)}
<div className="flex gap-2">
<input
type="text"
value={joinCode}
onChange={(e) => setJoinCode(e.target.value.toLowerCase().trim())}
placeholder="Code de partage"
maxLength={4}
className="flex-1 px-3 py-2.5 bg-slate-800/50 border border-slate-700
rounded-xl text-white text-sm text-center font-mono tracking-widest
placeholder:text-slate-600 focus:outline-none focus:border-brand-500"
onKeyDown={(e) => {
if (e.key === "Enter" && joinCode.length >= 3) {
window.location.href = `/${joinCode}`;
}
}}
/>
<button
onClick={() => {
if (joinCode.length >= 3) {
window.location.href = `/${joinCode}`;
}
}}
disabled={joinCode.length < 3}
className="px-4 py-2.5 bg-brand-500 hover:bg-brand-400 disabled:opacity-30
disabled:cursor-not-allowed rounded-xl text-white text-sm font-medium
transition-colors"
>
Rejoindre
</button>
</div>
</div>
</div>
);
}

View File

@ -1,3 +1,4 @@
import { useState } from "react";
import { QRCodeSVG } from "qrcode.react";
import { useStore } from "../stores/useStore";
@ -8,54 +9,104 @@ interface PublicRoomPanelProps {
export default function PublicRoomPanel({ onCreateRoom }: PublicRoomPanelProps) {
const publicRoomCode = useStore((s) => s.publicRoomCode);
const publicRoomUrl = useStore((s) => s.publicRoomUrl);
const [showModal, setShowModal] = useState(false);
const handleClick = () => {
if (publicRoomCode && publicRoomUrl) {
setShowModal(true);
} else {
onCreateRoom();
setShowModal(true);
}
};
if (!publicRoomCode || !publicRoomUrl) {
return (
<>
<button
onClick={onCreateRoom}
className="w-full flex items-center justify-center gap-2 px-4 py-3
onClick={handleClick}
className="flex-1 flex flex-col items-center justify-center gap-1.5 px-3 py-3
border border-slate-700 hover:border-brand-500 rounded-xl
text-slate-300 hover:text-white transition-all
bg-slate-900/30 hover:bg-slate-900/50"
>
<span className="text-lg">🔗</span>
<span className="text-sm font-medium">Partager via lien public</span>
<span className="text-xs font-medium text-center leading-tight">Lien public</span>
</button>
);
}
{showModal && (
<PublicRoomModal
code={publicRoomCode}
url={publicRoomUrl}
onClose={() => setShowModal(false)}
/>
)}
</>
);
}
function PublicRoomModal({
code,
url,
onClose,
}: {
code: string | null;
url: string | null;
onClose: () => void;
}) {
const copyToClipboard = () => {
navigator.clipboard.writeText(publicRoomUrl);
if (url) navigator.clipboard.writeText(url);
};
return (
<div className="bg-slate-800/50 border border-slate-700 rounded-2xl p-6 text-center space-y-4">
<h3 className="text-sm font-medium text-slate-400 uppercase tracking-wider">
Lien public
</h3>
<div
className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-6"
onClick={onClose}
>
<div
className="bg-slate-900 border border-slate-700 rounded-2xl p-6 max-w-sm w-full
text-center space-y-4"
onClick={(e) => e.stopPropagation()}
>
<h3 className="text-lg font-semibold text-white">Lien public</h3>
{!code || !url ? (
<p className="text-sm text-slate-400">Création en cours...</p>
) : (
<>
<div className="flex justify-center">
<div className="bg-white p-3 rounded-xl">
<QRCodeSVG value={publicRoomUrl} size={160} />
<QRCodeSVG value={url} size={180} />
</div>
</div>
<div className="space-y-2">
<p className="text-3xl font-mono font-bold text-brand-400 tracking-widest">
{publicRoomCode.toUpperCase()}
{code.toUpperCase()}
</p>
<button
onClick={copyToClipboard}
className="text-sm text-slate-400 hover:text-white transition-colors
underline underline-offset-4 decoration-slate-600"
>
{publicRoomUrl}
{url}
</button>
</div>
<p className="text-xs text-slate-500">
Partagez ce QR code ou ce lien pour recevoir des fichiers de n'importe qui.
Partagez ce lien pour recevoir des fichiers de n'importe qui.
Expire dans 10 minutes.
</p>
</>
)}
<button
onClick={onClose}
className="px-6 py-2 bg-slate-800 hover:bg-slate-700 rounded-xl
text-sm text-slate-300 transition-colors"
>
Fermer
</button>
</div>
</div>
);
}

View File

@ -123,19 +123,21 @@ function HomeConnected() {
</div>
)}
{/* Peer list */}
{/* 1. Appareils disponibles */}
<section className="mb-6">
<PeerList onPeerSelect={handlePeerSelect} onCreateRoom={createPublicRoom} />
<PeerList onPeerSelect={handlePeerSelect} />
</section>
{/* Drop zone */}
<section className="mb-6">
<DropZone onFilesSelected={handleFilesSelected} disabled={!selectedPeerId} />
{/* 2. Appairage + Lien public */}
<section className="mb-4 flex gap-3">
<DevicePairingPanel />
<PublicRoomPanel onCreateRoom={createPublicRoom} />
</section>
{/* Text share button */}
{/* 3. Envoi fichiers + texte (seulement quand un peer est sélectionné) */}
{selectedPeerId && (
<section className="mb-6">
<section className="mb-6 space-y-3">
<DropZone onFilesSelected={handleFilesSelected} />
<button
onClick={() => setShowTextModal(true)}
className="w-full flex items-center justify-center gap-2 px-4 py-3
@ -149,21 +151,11 @@ function HomeConnected() {
</section>
)}
{/* Transfer progress */}
{/* 4. Transferts en cours */}
<section className="mb-6">
<TransferProgress />
</section>
{/* Device pairing */}
<section className="mb-6">
<DevicePairingPanel />
</section>
{/* Public room */}
<section className="mb-6">
<PublicRoomPanel onCreateRoom={createPublicRoom} />
</section>
{/* Footer */}
<footer className="text-center text-xs text-slate-600 mt-12">
<p>Peer-to-peer · Chiffré · Aucun fichier ne transite par le serveur</p>