Feat: Redesign URL asset viewer preview with browser mockup & access guide
This commit is contained in:
parent
c32c4b9499
commit
e6d3d07fd4
@ -12,13 +12,23 @@ import {
|
||||
ExternalLink,
|
||||
Download,
|
||||
Clock,
|
||||
AlertCircle
|
||||
AlertCircle,
|
||||
Globe
|
||||
} from 'lucide-react';
|
||||
import type { Asset } from '../../../types/assets';
|
||||
import type { User } from '../../../types/auth';
|
||||
import { axiosInstance } from '../../../services/axios';
|
||||
import { PdfThumbnail } from './PdfThumbnail';
|
||||
|
||||
const getFriendlyHostname = (urlStr: string) => {
|
||||
try {
|
||||
const urlObj = new URL(urlStr);
|
||||
return urlObj.hostname.replace('www.', '');
|
||||
} catch {
|
||||
return 'website.com';
|
||||
}
|
||||
};
|
||||
|
||||
interface AssetCardProps {
|
||||
asset: Asset;
|
||||
user: User | null;
|
||||
@ -250,33 +260,56 @@ export const AssetCard: React.FC<AssetCardProps> = ({
|
||||
}
|
||||
/>
|
||||
) : asset.type === 'url' ? (
|
||||
<div className="w-full h-full relative bg-ink-50">
|
||||
<img
|
||||
src={`https://image.thum.io/get/width/400/crop/800/maxAge/12/${asset.url}`}
|
||||
alt={asset.title}
|
||||
className="w-full h-full object-cover object-top transition-transform duration-500 group-hover:scale-105"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const placeholder = document.getElementById(`fallback-${asset.id}`);
|
||||
if (placeholder) placeholder.style.display = 'flex';
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
id={`fallback-${asset.id}`}
|
||||
className="absolute inset-0 flex items-center justify-center bg-ink-50"
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center w-full h-full p-4 bg-ink-500/[0.03] hover:bg-ink-500/[0.06] transition-colors">
|
||||
<div className="w-[105px] h-[135px] bg-ink-0 border border-ink-200 shadow-md rounded-lg flex flex-col justify-between p-3 relative overflow-hidden transition-all duration-300 group-hover:scale-105">
|
||||
<div className="absolute top-0 right-0 left-0 bg-ink-700 text-ink-0 py-1 text-[8px] font-extrabold uppercase text-center tracking-wider font-sans">
|
||||
Web Link
|
||||
<div className="w-full h-full flex flex-col bg-slate-900 border border-slate-800 relative overflow-hidden group/browser">
|
||||
{/* Browser Header Bar */}
|
||||
<div className="h-6 bg-slate-800/80 border-b border-slate-700/50 flex items-center px-3 gap-2 shrink-0 select-none">
|
||||
{/* Windows Dots */}
|
||||
<div className="flex gap-1.5 shrink-0">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-red-500/80" />
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-yellow-500/80" />
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-green-500/80" />
|
||||
</div>
|
||||
{/* URL Bar */}
|
||||
<div className="flex-1 max-w-[150px] mx-auto bg-slate-900/60 border border-slate-700/30 rounded px-2 py-0.5 flex items-center gap-1 text-[9px] text-slate-400 font-mono select-none truncate">
|
||||
<Globe className="w-2.5 h-2.5 text-slate-500 shrink-0" />
|
||||
<span className="truncate">{getFriendlyHostname(asset.url)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Browser Body Web Mockup */}
|
||||
<div className="flex-1 bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 p-3 flex flex-col justify-between relative overflow-hidden">
|
||||
{/* Decorative Grid Pattern */}
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:10px_10px]" />
|
||||
|
||||
{/* Mock Web Page Hero Abstract Layout */}
|
||||
<div className="space-y-2 mt-1.5 relative z-10">
|
||||
{/* Mock Navbar */}
|
||||
<div className="flex justify-between items-center bg-white/[0.03] border border-white/5 rounded px-2 py-0.5">
|
||||
<div className="w-8 h-1.5 bg-indigo-400/30 rounded" />
|
||||
<div className="flex gap-1">
|
||||
<div className="w-4 h-1 bg-white/20 rounded" />
|
||||
<div className="w-4 h-1 bg-white/20 rounded" />
|
||||
</div>
|
||||
<div className="flex-grow flex items-center justify-center pt-4">
|
||||
<File className="w-8 h-8 text-ink-400" />
|
||||
</div>
|
||||
{/* Mock Page Content */}
|
||||
<div className="space-y-1 pt-1">
|
||||
<div className="h-2.5 bg-gradient-to-r from-indigo-400 to-cyan-400 rounded w-3/4" />
|
||||
<div className="h-1.5 bg-white/20 rounded w-11/12" />
|
||||
<div className="h-1.5 bg-white/10 rounded w-5/6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mock Card Domain Display */}
|
||||
<div className="relative z-10 bg-slate-900/80 border border-slate-800/80 rounded-lg p-2 flex items-center gap-2 select-none">
|
||||
<div className="w-6 h-6 rounded bg-indigo-650 flex items-center justify-center text-[10px] font-black text-white shrink-0">
|
||||
{getFriendlyHostname(asset.url).charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<div className="text-[10px] font-bold text-white font-mono truncate leading-none">
|
||||
{getFriendlyHostname(asset.url)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-[7px] text-ink-400 font-bold border-t border-ink-100 pt-1 mt-1 font-sans">
|
||||
<span>URL LINK</span>
|
||||
<span className="font-extrabold uppercase text-[6px]">HTML</span>
|
||||
<div className="text-[7px] text-slate-500 font-mono mt-0.5 leading-none">
|
||||
EXTERNAL PORTAL
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -299,46 +299,134 @@ export const AssetViewerModal: React.FC<AssetViewerModalProps> = ({
|
||||
<div className="bg-ink-100 px-4 py-2 border-b border-ink-200 flex justify-between items-center text-xs text-ink-600 font-bold select-none shrink-0">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<Globe className="w-4 h-4 text-ink-950" />
|
||||
<span className="font-sans">External Web Link Preview</span>
|
||||
<span className="font-sans">External Web Link Portal Guide</span>
|
||||
</span>
|
||||
<span className="text-[10px] text-ink-400 font-mono font-normal truncate max-w-xs">{asset.url}</span>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto p-6 md:p-12 flex items-center justify-center bg-ink-50">
|
||||
<div className="bg-ink-0 border border-ink-200 rounded-2xl p-6 shadow-xl max-w-xl w-full flex flex-col items-center text-center space-y-5">
|
||||
{asset.thumbnailUrl ? (
|
||||
<div className="w-full h-48 border border-ink-100 rounded-xl overflow-hidden shadow-sm shrink-0">
|
||||
<img
|
||||
src={asset.thumbnailUrl}
|
||||
alt="Web Preview Banner"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
<div className="flex-1 overflow-auto p-4 md:p-8 flex flex-col md:flex-row items-center justify-center gap-8 bg-ink-50">
|
||||
{/* Visual Section: Browser Mockup */}
|
||||
<div className="w-full max-w-md aspect-video md:aspect-[4/3] rounded-2xl overflow-hidden shadow-lg border border-ink-200 bg-slate-900 flex flex-col shrink-0">
|
||||
{/* Browser Header Bar */}
|
||||
<div className="h-7 bg-slate-800 border-b border-slate-700/60 flex items-center px-4 gap-2 shrink-0 select-none">
|
||||
<div className="flex gap-1.5 shrink-0">
|
||||
<span className="w-3 h-3 rounded-full bg-red-500/80" />
|
||||
<span className="w-3 h-3 rounded-full bg-yellow-500/80" />
|
||||
<span className="w-3 h-3 rounded-full bg-green-500/80" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-16 h-16 rounded-2xl bg-ink-50 border border-ink-200 flex items-center justify-center text-ink-900 shadow-sm shrink-0">
|
||||
<Globe className="w-8 h-8 text-ink-500" />
|
||||
<div className="flex-1 max-w-[200px] mx-auto bg-slate-900/60 border border-slate-700/30 rounded-md px-3 py-0.5 flex items-center gap-1.5 text-[10px] text-slate-400 font-mono select-none truncate">
|
||||
<Globe className="w-3 h-3 text-slate-500 shrink-0" />
|
||||
<span className="truncate">{(() => {
|
||||
try {
|
||||
return new URL(asset.url).hostname.replace('www.', '');
|
||||
} catch {
|
||||
return 'website.com';
|
||||
}
|
||||
})()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Browser Content */}
|
||||
<div className="flex-1 bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 p-6 flex flex-col justify-between relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808008_1px,transparent_1px),linear-gradient(to_bottom,#80808008_1px,transparent_1px)] bg-[size:16px_16px]" />
|
||||
|
||||
<div className="space-y-3 relative z-10 text-left">
|
||||
<div className="flex justify-between items-center bg-white/[0.02] border border-white/5 rounded-lg px-3 py-1.5">
|
||||
<div className="w-16 h-2 bg-indigo-400/40 rounded" />
|
||||
<div className="flex gap-1.5">
|
||||
<div className="w-6 h-1.5 bg-white/20 rounded" />
|
||||
<div className="w-6 h-1.5 bg-white/20 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 pt-2">
|
||||
<div className="h-5 bg-gradient-to-r from-indigo-400 to-cyan-400 rounded-lg w-5/6" />
|
||||
<div className="h-2 bg-white/20 rounded w-11/12" />
|
||||
<div className="h-2 bg-white/15 rounded w-3/4" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2 w-full">
|
||||
<h3 className="text-base font-extrabold text-ink-950 font-sans leading-snug">{asset.title}</h3>
|
||||
{asset.description ? (
|
||||
<p className="text-xs text-ink-500 font-sans leading-relaxed px-4">{asset.description}</p>
|
||||
) : (
|
||||
<p className="text-xs text-ink-400 font-sans italic leading-relaxed">No description provided for this external link.</p>
|
||||
)}
|
||||
<div className="relative z-10 bg-slate-900/80 border border-slate-800/80 rounded-xl p-3.5 flex items-center gap-3 select-none">
|
||||
<div className="w-10 h-10 rounded-lg bg-indigo-650 flex items-center justify-center text-sm font-black text-white shrink-0 shadow-md shadow-indigo-600/20">
|
||||
{(() => {
|
||||
try {
|
||||
return new URL(asset.url).hostname.replace('www.', '').charAt(0).toUpperCase();
|
||||
} catch {
|
||||
return 'W';
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<div className="text-xs font-bold text-white font-mono truncate leading-none">
|
||||
{(() => {
|
||||
try {
|
||||
return new URL(asset.url).hostname.replace('www.', '');
|
||||
} catch {
|
||||
return 'website.com';
|
||||
}
|
||||
})()}
|
||||
</div>
|
||||
<div className="text-[8px] text-slate-500 font-mono mt-1 leading-none tracking-wider">
|
||||
SECURE PORTAL LINK
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Info & Guide Section */}
|
||||
<div className="flex-1 bg-ink-0 border border-ink-200 rounded-2xl p-6 md:p-8 shadow-lg max-w-lg w-full flex flex-col justify-between min-h-[350px]">
|
||||
<div className="space-y-4 text-left">
|
||||
<div>
|
||||
<span className="inline-flex px-2 py-0.5 rounded text-[9px] font-extrabold uppercase tracking-wider text-indigo-650 bg-indigo-50 border border-indigo-100">
|
||||
External Portal Link
|
||||
</span>
|
||||
<h3 className="text-lg font-extrabold text-ink-950 font-sans leading-snug mt-1.5">{asset.title}</h3>
|
||||
{asset.description ? (
|
||||
<p className="text-xs text-ink-500 font-sans mt-2 leading-relaxed">{asset.description}</p>
|
||||
) : (
|
||||
<p className="text-xs text-ink-400 font-sans mt-2 italic">No description provided for this external portal link.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Step-by-Step Access Guide */}
|
||||
<div className="bg-ink-50 border border-ink-200 rounded-xl p-4 space-y-3">
|
||||
<span className="text-[9px] font-bold uppercase tracking-wider text-ink-400 block">Access Guide</span>
|
||||
<ul className="space-y-2 text-xs text-ink-700 leading-relaxed font-sans">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="w-4 h-4 rounded-full bg-ink-200 text-ink-800 text-[10px] font-bold flex items-center justify-center shrink-0 mt-0.5">1</span>
|
||||
<span>Click the primary button below or select <strong>Open in New Tab</strong> from the footer to access this asset.</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="w-4 h-4 rounded-full bg-ink-200 text-ink-800 text-[10px] font-bold flex items-center justify-center shrink-0 mt-0.5">2</span>
|
||||
<span>If the partner resource requires authorization, use your partner account credentials to sign in.</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full pt-2 px-4">
|
||||
<div className="pt-6 border-t border-ink-100 w-full mt-4">
|
||||
{(user?.role === 'ADMIN' || asset.isDownloadable || asset.downloadRequests?.[0]?.status === 'APPROVED') ? (
|
||||
<a
|
||||
href={asset.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-xl bg-ink-900 text-ink-0 text-xs font-bold hover:bg-ink-850 transition-all hover:scale-[1.02] active:scale-95 shadow-md w-full font-sans cursor-pointer"
|
||||
>
|
||||
<span>Open Website in New Tab</span>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<div className="space-y-2 w-full">
|
||||
<a
|
||||
href={asset.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-xl bg-ink-900 text-ink-0 text-xs font-bold hover:bg-ink-850 transition-all hover:scale-[1.02] active:scale-95 shadow-md w-full font-sans cursor-pointer"
|
||||
>
|
||||
<span>Open Website in New Tab</span>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(asset.url);
|
||||
alert('Link copied to clipboard!');
|
||||
}}
|
||||
className="w-full text-center text-xs font-bold text-ink-600 hover:text-ink-900 bg-ink-50 hover:bg-ink-100 border border-ink-200 py-2 rounded-xl transition-all cursor-pointer"
|
||||
>
|
||||
Copy Link Address
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-amber-50 border border-amber-200 text-amber-900 rounded-xl p-4 text-xs font-medium text-center font-sans">
|
||||
Access Restricted: You must request and receive approval from the Administrator to open this external link.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user