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}` : "";
|
||||
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user