refactor: simplify layout flow
Some checks failed
Build & Deploy / build-and-deploy (push) Has been cancelled
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:
parent
612222ccde
commit
8212bc7391
@ -22,50 +22,60 @@ export default function DevicePairingPanel() {
|
|||||||
|
|
||||||
const pairUrl = groupId ? `${BASE_URL}/pair?g=${groupId}` : "";
|
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 (
|
return (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={handleShowQR}
|
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
|
border border-slate-700 hover:border-brand-500 rounded-xl
|
||||||
text-slate-300 hover:text-white transition-all
|
text-slate-300 hover:text-white transition-all
|
||||||
bg-slate-900/30 hover:bg-slate-900/50"
|
bg-slate-900/30 hover:bg-slate-900/50"
|
||||||
>
|
>
|
||||||
<span className="text-lg">📲</span>
|
<span className="text-lg">📲</span>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-xs font-medium text-center leading-tight">
|
||||||
{groupId ? "Ajouter un autre appareil" : "Appairer mes appareils"}
|
{groupId ? "Ajouter un appareil" : "Appairer"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,13 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useStore } from "../stores/useStore";
|
import { useStore } from "../stores/useStore";
|
||||||
import PeerAvatar from "./PeerAvatar";
|
import PeerAvatar from "./PeerAvatar";
|
||||||
|
|
||||||
interface PeerListProps {
|
interface PeerListProps {
|
||||||
onPeerSelect: (peerId: string) => void;
|
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 peers = useStore((s) => s.peers);
|
||||||
const selectedPeerId = useStore((s) => s.selectedPeerId);
|
const selectedPeerId = useStore((s) => s.selectedPeerId);
|
||||||
const [joinCode, setJoinCode] = useState("");
|
|
||||||
|
|
||||||
if (peers.length === 0) {
|
if (peers.length === 0) {
|
||||||
return (
|
return (
|
||||||
@ -18,56 +15,9 @@ export default function PeerList({ onPeerSelect, onCreateRoom }: PeerListProps)
|
|||||||
<div className="text-5xl mb-4">📡</div>
|
<div className="text-5xl mb-4">📡</div>
|
||||||
<p className="text-lg font-medium">En attente d'appareils...</p>
|
<p className="text-lg font-medium">En attente d'appareils...</p>
|
||||||
<p className="text-sm mt-2 text-center max-w-xs">
|
<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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
import { useStore } from "../stores/useStore";
|
import { useStore } from "../stores/useStore";
|
||||||
|
|
||||||
@ -8,54 +9,104 @@ interface PublicRoomPanelProps {
|
|||||||
export default function PublicRoomPanel({ onCreateRoom }: PublicRoomPanelProps) {
|
export default function PublicRoomPanel({ onCreateRoom }: PublicRoomPanelProps) {
|
||||||
const publicRoomCode = useStore((s) => s.publicRoomCode);
|
const publicRoomCode = useStore((s) => s.publicRoomCode);
|
||||||
const publicRoomUrl = useStore((s) => s.publicRoomUrl);
|
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 (
|
return (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={onCreateRoom}
|
onClick={handleClick}
|
||||||
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
|
border border-slate-700 hover:border-brand-500 rounded-xl
|
||||||
text-slate-300 hover:text-white transition-all
|
text-slate-300 hover:text-white transition-all
|
||||||
bg-slate-900/30 hover:bg-slate-900/50"
|
bg-slate-900/30 hover:bg-slate-900/50"
|
||||||
>
|
>
|
||||||
<span className="text-lg">🔗</span>
|
<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>
|
</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 = () => {
|
const copyToClipboard = () => {
|
||||||
navigator.clipboard.writeText(publicRoomUrl);
|
if (url) navigator.clipboard.writeText(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-slate-800/50 border border-slate-700 rounded-2xl p-6 text-center space-y-4">
|
<div
|
||||||
<h3 className="text-sm font-medium text-slate-400 uppercase tracking-wider">
|
className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-6"
|
||||||
Lien public
|
onClick={onClose}
|
||||||
</h3>
|
>
|
||||||
|
<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="flex justify-center">
|
||||||
<div className="bg-white p-3 rounded-xl">
|
<div className="bg-white p-3 rounded-xl">
|
||||||
<QRCodeSVG value={publicRoomUrl} size={160} />
|
<QRCodeSVG value={url} size={180} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-3xl font-mono font-bold text-brand-400 tracking-widest">
|
<p className="text-3xl font-mono font-bold text-brand-400 tracking-widest">
|
||||||
{publicRoomCode.toUpperCase()}
|
{code.toUpperCase()}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={copyToClipboard}
|
onClick={copyToClipboard}
|
||||||
className="text-sm text-slate-400 hover:text-white transition-colors
|
className="text-sm text-slate-400 hover:text-white transition-colors
|
||||||
underline underline-offset-4 decoration-slate-600"
|
underline underline-offset-4 decoration-slate-600"
|
||||||
>
|
>
|
||||||
{publicRoomUrl}
|
{url}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-slate-500">
|
<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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,19 +123,21 @@ function HomeConnected() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Peer list */}
|
{/* 1. Appareils disponibles */}
|
||||||
<section className="mb-6">
|
<section className="mb-6">
|
||||||
<PeerList onPeerSelect={handlePeerSelect} onCreateRoom={createPublicRoom} />
|
<PeerList onPeerSelect={handlePeerSelect} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Drop zone */}
|
{/* 2. Appairage + Lien public */}
|
||||||
<section className="mb-6">
|
<section className="mb-4 flex gap-3">
|
||||||
<DropZone onFilesSelected={handleFilesSelected} disabled={!selectedPeerId} />
|
<DevicePairingPanel />
|
||||||
|
<PublicRoomPanel onCreateRoom={createPublicRoom} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Text share button */}
|
{/* 3. Envoi fichiers + texte (seulement quand un peer est sélectionné) */}
|
||||||
{selectedPeerId && (
|
{selectedPeerId && (
|
||||||
<section className="mb-6">
|
<section className="mb-6 space-y-3">
|
||||||
|
<DropZone onFilesSelected={handleFilesSelected} />
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowTextModal(true)}
|
onClick={() => setShowTextModal(true)}
|
||||||
className="w-full flex items-center justify-center gap-2 px-4 py-3
|
className="w-full flex items-center justify-center gap-2 px-4 py-3
|
||||||
@ -149,21 +151,11 @@ function HomeConnected() {
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Transfer progress */}
|
{/* 4. Transferts en cours */}
|
||||||
<section className="mb-6">
|
<section className="mb-6">
|
||||||
<TransferProgress />
|
<TransferProgress />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Device pairing */}
|
|
||||||
<section className="mb-6">
|
|
||||||
<DevicePairingPanel />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Public room */}
|
|
||||||
<section className="mb-6">
|
|
||||||
<PublicRoomPanel onCreateRoom={createPublicRoom} />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="text-center text-xs text-slate-600 mt-12">
|
<footer className="text-center text-xs text-slate-600 mt-12">
|
||||||
<p>Peer-to-peer · Chiffré · Aucun fichier ne transite par le serveur</p>
|
<p>Peer-to-peer · Chiffré · Aucun fichier ne transite par le serveur</p>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user