| // src/components/async/OfflineCollaborationQueue.tsx | |
| import { useOfflineSync } from "@/hooks/useOfflineSync"; | |
| import { | |
| CloudUpload, | |
| CloudOff, | |
| Clock, | |
| CheckCircle2, | |
| AlertTriangle, | |
| RefreshCw, | |
| Trash2, | |
| } from "lucide-react"; | |
| export function OfflineCollaborationQueue() { | |
| const { status, queue, queueCount, processQueue } = useOfflineSync(); | |
| // Don't render anything if queue is empty and we are fully synced | |
| if (queueCount === 0 && status === "synced") return null; | |
| // Status icon and text | |
| const getStatusIcon = () => { | |
| switch (status) { | |
| case "syncing": | |
| return <CloudUpload className="w-4 h-4 text-blue-500 animate-pulse" />; | |
| case "pending": | |
| return <Clock className="w-4 h-4 text-amber-500" />; | |
| default: | |
| return <CloudOff className="w-4 h-4 text-red-500" />; | |
| } | |
| }; | |
| const getStatusText = () => { | |
| switch (status) { | |
| case "syncing": | |
| return "Syncing..."; | |
| case "pending": | |
| return "Pending Sync"; | |
| default: | |
| return `${queueCount} queued`; | |
| } | |
| }; | |
| return ( | |
| <div className="glass rounded-2xl border border-white/20 dark:border-white/10 p-4 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md shadow-lg"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between mb-3"> | |
| <h4 className="text-sm font-semibold flex items-center gap-2"> | |
| {getStatusIcon()} | |
| <span>{getStatusText()}</span> | |
| </h4> | |
| <button | |
| onClick={processQueue} | |
| disabled={status === "syncing"} | |
| className="btn btn-ghost btn-xs gap-1 flex items-center text-xs px-2 py-1 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 disabled:opacity-50" | |
| > | |
| <RefreshCw className={`w-3 h-3 ${status === "syncing" ? "animate-spin" : ""}`} /> | |
| Sync Now | |
| </button> | |
| </div> | |
| {/* Queue items (up to 5) */} | |
| <div className="space-y-0"> | |
| {queue.slice(0, 5).map((item) => ( | |
| <div | |
| key={item.id} | |
| className="flex items-center gap-2 text-xs py-1.5 border-b border-white/10 dark:border-gray-700 last:border-0" | |
| > | |
| {item.status === "failed" ? ( | |
| <AlertTriangle className="w-3.5 h-3.5 text-red-400" /> | |
| ) : item.status === "processing" ? ( | |
| <RefreshCw className="w-3.5 h-3.5 text-blue-400 animate-spin" /> | |
| ) : ( | |
| <Clock className="w-3.5 h-3.5 text-amber-400" /> | |
| )} | |
| <span className="flex-1 truncate capitalize">{item.type}</span> | |
| <span className="text-gray-400 text-[10px]"> | |
| {new Date(item.createdAt).toLocaleTimeString()} | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| {/* More items indicator */} | |
| {queue.length > 5 && ( | |
| <p className="text-[10px] text-gray-400 text-center mt-2"> | |
| +{queue.length - 5} more items | |
| </p> | |
| )} | |
| </div> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 2.95 kB
- Xet hash:
- ec187b043214a8e4249bee7d1b6aa03c7e3509e7aef87702cee2d20e0ce301cd
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.