Compare commits
No commits in common. "e7f50a66674eb5e77fdfc6092b2b7252f83cb014" and "9c19ca9dd757769249c4a67c4b82118da3200153" have entirely different histories.
e7f50a6667
...
9c19ca9dd7
@ -271,59 +271,36 @@ export const AssetViewerModal: React.FC<AssetViewerModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
) : asset.type === 'url' ? (
|
||||
<div className="w-full h-full flex flex-col min-h-0 bg-ink-0">
|
||||
<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>
|
||||
<span className="text-[10px] text-ink-400 font-mono font-normal truncate max-w-xs">{asset.url}</span>
|
||||
<div className="text-center p-12 max-w-md space-y-4 flex flex-col justify-center items-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-ink-100 border border-ink-200 flex items-center justify-center text-ink-900 shadow-sm">
|
||||
<Globe className="w-8 h-8" />
|
||||
</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>
|
||||
<h4 className="text-sm font-bold text-ink-900 font-sans">External Resource Portal</h4>
|
||||
<p className="text-xs text-ink-500 mt-2 leading-relaxed font-sans">
|
||||
This asset points to an external destination outside of the local CDN container.
|
||||
</p>
|
||||
<div className="bg-ink-100 border border-ink-200 rounded-xl px-4 py-2 text-xs font-mono text-ink-600 truncate mt-3 max-w-sm">
|
||||
{asset.url}
|
||||
</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>
|
||||
)}
|
||||
|
||||
<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>
|
||||
|
||||
<div className="w-full pt-2 px-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"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg bg-ink-900 text-ink-0 text-xs font-bold hover:bg-ink-800 transition-all shadow-sm w-full font-sans"
|
||||
>
|
||||
<span>Open Website in New Tab</span>
|
||||
<span>Open Link in New Tab</span>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
) : (
|
||||
<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.
|
||||
<div className="bg-amber-50 border border-amber-205 text-amber-900 rounded-xl p-4 text-xs text-center font-medium max-w-sm font-sans">
|
||||
Access Restricted: You must request and receive download approval from the Administrator to open this resource link.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : asset.type.includes('pdf') ? (
|
||||
<div className="w-full h-full flex flex-col min-h-0">
|
||||
<div className="bg-ink-100 px-4 py-2 border-b border-ink-200 flex items-center text-xs text-ink-600 font-bold select-none">
|
||||
|
||||
@ -95,30 +95,6 @@ export const UploadAssetModal: React.FC<UploadAssetModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetchUrlDetails = async () => {
|
||||
if (!uploadUrl) {
|
||||
error('URL Required', 'Please enter a URL first.');
|
||||
return;
|
||||
}
|
||||
if (!uploadUrl.startsWith('http')) {
|
||||
error('Invalid URL', 'Please enter a valid URL starting with http:// or https://');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsScraping(true);
|
||||
try {
|
||||
const data = await scrapeCaseStudy(uploadUrl);
|
||||
setUploadTitle(data.title);
|
||||
setThumbnailUrl(data.thumbnailUrl || '');
|
||||
success('Page Details Fetched', 'Title and banner image successfully retrieved.');
|
||||
} catch (err: any) {
|
||||
console.error('Failed to scrape URL details', err);
|
||||
error('Failed to retrieve page details', err.response?.data?.error || 'Make sure the URL is accessible.');
|
||||
} finally {
|
||||
setIsScraping(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatBytes = (bytes: number, decimals = 2) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
@ -342,29 +318,14 @@ export const UploadAssetModal: React.FC<UploadAssetModalProps> = ({
|
||||
) : uploadTab === 'url' ? (
|
||||
<div>
|
||||
<label className="text-xs font-semibold text-ink-500 mb-1.5 block">External Asset URL</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="url"
|
||||
value={uploadUrl}
|
||||
onChange={(e) => setUploadUrl(e.target.value)}
|
||||
placeholder="https://example.com/partner-docs"
|
||||
required={uploadTab === 'url'}
|
||||
className="flex-1 bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
||||
className="w-full bg-ink-50 border border-ink-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ink-900/10 text-ink-900 placeholder-ink-400"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleFetchUrlDetails}
|
||||
disabled={isScraping || !uploadUrl}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
{isScraping ? 'Fetching...' : 'Fetch Details'}
|
||||
</Button>
|
||||
</div>
|
||||
<span className="text-[10px] text-ink-450 mt-1 block">
|
||||
Fetches title and thumbnail banner image from the target webpage automatically.
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
||||
@ -29,7 +29,6 @@ import { ManageGroupsModal } from "../features/assets/components/ManageGroupsMod
|
||||
import { PageHeader } from "../components/ui/PageHeader";
|
||||
import Button from "../components/ui/Button";
|
||||
import { PageLayout } from "../components/layout/PageLayout";
|
||||
import Modal from "../components/ui/Modal";
|
||||
|
||||
// Type Definitions
|
||||
import type { Asset, Organization } from "../types/assets";
|
||||
@ -75,7 +74,6 @@ export const AssetsPage = () => {
|
||||
const [activeMenuId, setActiveMenuId] = useState<string | null>(null);
|
||||
const [selectedAssetIds, setSelectedAssetIds] = useState<string[]>([]);
|
||||
const [expandedAssetId, setExpandedAssetId] = useState<string | null>(null);
|
||||
const [assetToDelete, setAssetToDelete] = useState<{ id: string, title: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
@ -140,19 +138,16 @@ export const AssetsPage = () => {
|
||||
setIsDetailsOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteAsset = (id: string) => {
|
||||
const handleDeleteAsset = async (id: string) => {
|
||||
const asset = assets.find(a => a.id === id);
|
||||
if (asset) {
|
||||
setAssetToDelete({ id: asset.id, title: asset.title });
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeleteAsset = async () => {
|
||||
if (!assetToDelete) return;
|
||||
const title = asset ? asset.title : "this asset";
|
||||
if (
|
||||
!window.confirm(`Are you sure you want to permanently delete "${title}"?`)
|
||||
)
|
||||
return;
|
||||
try {
|
||||
await deleteAsset(assetToDelete.id);
|
||||
success("Asset deleted successfully", `"${assetToDelete.title}" has been permanently removed.`);
|
||||
setAssetToDelete(null);
|
||||
await deleteAsset(id);
|
||||
success("Asset deleted successfully", `"${title}" has been permanently removed.`);
|
||||
await fetchData();
|
||||
} catch (err: any) {
|
||||
console.error("Failed to delete asset", err);
|
||||
@ -479,37 +474,6 @@ export const AssetsPage = () => {
|
||||
groups={groups}
|
||||
onRefresh={fetchData}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Modal
|
||||
isOpen={assetToDelete !== null}
|
||||
onClose={() => setAssetToDelete(null)}
|
||||
title="Delete Asset"
|
||||
subtitle="Confirm permanent resource deletion."
|
||||
size="sm"
|
||||
>
|
||||
<div className="space-y-4 font-sans">
|
||||
<p className="text-xs text-ink-600 leading-relaxed">
|
||||
Are you sure you want to permanently delete <span className="font-bold text-ink-900">"{assetToDelete?.title}"</span>? This action cannot be undone.
|
||||
</p>
|
||||
<div className="flex gap-3 justify-end pt-2">
|
||||
<Button
|
||||
onClick={() => setAssetToDelete(null)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={confirmDeleteAsset}
|
||||
variant="danger"
|
||||
size="sm"
|
||||
>
|
||||
Delete Permanently
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user