@actuate-media/cms-admin 0.49.0 → 0.49.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +10 -11
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/modal.render.test.d.ts +2 -0
- package/dist/__tests__/components/modal.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/modal.render.test.js +37 -0
- package/dist/__tests__/components/modal.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/FolderTree.js +19 -19
- package/dist/components/FolderTree.js.map +1 -1
- package/dist/components/MediaPickerModal.d.ts +1 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.js +12 -11
- package/dist/components/MediaPickerModal.js.map +1 -1
- package/dist/components/SchedulePublishDialog.d.ts +1 -1
- package/dist/components/SchedulePublishDialog.d.ts.map +1 -1
- package/dist/components/SchedulePublishDialog.js +6 -4
- package/dist/components/SchedulePublishDialog.js.map +1 -1
- package/dist/components/SharePreviewLinkDialog.d.ts +1 -1
- package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -1
- package/dist/components/SharePreviewLinkDialog.js +10 -9
- package/dist/components/SharePreviewLinkDialog.js.map +1 -1
- package/dist/components/VersionHistory.d.ts +1 -1
- package/dist/components/VersionHistory.d.ts.map +1 -1
- package/dist/components/VersionHistory.js +14 -14
- package/dist/components/VersionHistory.js.map +1 -1
- package/dist/components/ui/Modal.d.ts +17 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/components/ui/Modal.js +16 -15
- package/dist/components/ui/Modal.js.map +1 -1
- package/dist/fields/BlockBuilderField.d.ts.map +1 -1
- package/dist/fields/BlockBuilderField.js +6 -4
- package/dist/fields/BlockBuilderField.js.map +1 -1
- package/dist/views/MediaBrowser.d.ts.map +1 -1
- package/dist/views/MediaBrowser.js +16 -16
- package/dist/views/MediaBrowser.js.map +1 -1
- package/dist/views/Pages/PagesListView.d.ts.map +1 -1
- package/dist/views/Pages/PagesListView.js +3 -3
- package/dist/views/Pages/PagesListView.js.map +1 -1
- package/dist/views/Posts/PostsListView.d.ts.map +1 -1
- package/dist/views/Posts/PostsListView.js +3 -3
- package/dist/views/Posts/PostsListView.js.map +1 -1
- package/dist/views/Redirects.d.ts.map +1 -1
- package/dist/views/Redirects.js +7 -7
- package/dist/views/Redirects.js.map +1 -1
- package/dist/views/ScriptTagEditor.js +4 -4
- package/dist/views/ScriptTagEditor.js.map +1 -1
- package/dist/views/ScriptTags.d.ts.map +1 -1
- package/dist/views/ScriptTags.js +5 -5
- package/dist/views/ScriptTags.js.map +1 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts +1 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -1
- package/dist/views/page-builder/AIGenerateDialog.js +23 -23
- package/dist/views/page-builder/AIGenerateDialog.js.map +1 -1
- package/package.json +1 -1
- package/src/AdminRoot.tsx +35 -30
- package/src/__tests__/components/modal.render.test.tsx +57 -0
- package/src/components/FolderTree.tsx +32 -32
- package/src/components/MediaPickerModal.tsx +143 -119
- package/src/components/SchedulePublishDialog.tsx +118 -93
- package/src/components/SharePreviewLinkDialog.tsx +115 -93
- package/src/components/VersionHistory.tsx +98 -82
- package/src/components/ui/Modal.tsx +48 -39
- package/src/fields/BlockBuilderField.tsx +26 -19
- package/src/views/MediaBrowser.tsx +128 -120
- package/src/views/Pages/PagesListView.tsx +123 -115
- package/src/views/Posts/PostsListView.tsx +114 -110
- package/src/views/Redirects.tsx +48 -44
- package/src/views/ScriptTagEditor.tsx +40 -40
- package/src/views/ScriptTags.tsx +34 -30
- package/src/views/page-builder/AIGenerateDialog.tsx +347 -335
|
@@ -361,7 +361,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
361
361
|
<button
|
|
362
362
|
type="button"
|
|
363
363
|
onClick={() => setSortConfig(toggleSort(sortConfig, sortKey))}
|
|
364
|
-
className="flex items-center gap-1 text-xs font-medium
|
|
364
|
+
className="text-foreground hover:text-foreground flex items-center gap-1 text-xs font-medium transition-colors"
|
|
365
365
|
>
|
|
366
366
|
{label}
|
|
367
367
|
{active ? (
|
|
@@ -371,7 +371,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
371
371
|
<ArrowDown className="h-3 w-3" />
|
|
372
372
|
)
|
|
373
373
|
) : (
|
|
374
|
-
<ArrowUpDown className="h-3 w-3
|
|
374
|
+
<ArrowUpDown className="text-muted-foreground h-3 w-3" />
|
|
375
375
|
)}
|
|
376
376
|
</button>
|
|
377
377
|
)
|
|
@@ -380,7 +380,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
380
380
|
if (loading) {
|
|
381
381
|
return (
|
|
382
382
|
<div className="flex h-64 items-center justify-center p-3 pr-6 sm:p-4 sm:pr-8">
|
|
383
|
-
<Loader2 className="h-6 w-6 animate-spin
|
|
383
|
+
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
|
384
384
|
</div>
|
|
385
385
|
)
|
|
386
386
|
}
|
|
@@ -388,12 +388,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
388
388
|
return (
|
|
389
389
|
<div className="flex h-full flex-col p-3 pr-6 sm:p-4 sm:pr-8">
|
|
390
390
|
{error && (
|
|
391
|
-
<div className="mb-4 flex items-center gap-3 rounded-lg border
|
|
392
|
-
<AlertTriangle className="h-5 w-5 shrink-0
|
|
393
|
-
<span className="flex-1 text-sm
|
|
391
|
+
<div className="border-destructive/30 bg-destructive/10 mb-4 flex items-center gap-3 rounded-lg border p-3">
|
|
392
|
+
<AlertTriangle className="text-destructive h-5 w-5 shrink-0" />
|
|
393
|
+
<span className="text-destructive flex-1 text-sm">{error}</span>
|
|
394
394
|
<button
|
|
395
395
|
onClick={refetch}
|
|
396
|
-
className="rounded-lg border
|
|
396
|
+
className="border-destructive/40 text-destructive hover:bg-destructive/15 rounded-lg border px-3 py-1 text-sm transition-colors"
|
|
397
397
|
>
|
|
398
398
|
Retry
|
|
399
399
|
</button>
|
|
@@ -405,14 +405,16 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
405
405
|
<button
|
|
406
406
|
type="button"
|
|
407
407
|
onClick={() => setSidebarOpen((prev) => !prev)}
|
|
408
|
-
className="rounded-lg p-1.5 transition-colors
|
|
408
|
+
className="hover:bg-muted rounded-lg p-1.5 transition-colors"
|
|
409
409
|
title={sidebarOpen ? 'Hide folders' : 'Show folders'}
|
|
410
410
|
>
|
|
411
|
-
<FolderInput className="h-5 w-5
|
|
411
|
+
<FolderInput className="text-muted-foreground h-5 w-5" />
|
|
412
412
|
</button>
|
|
413
413
|
<div>
|
|
414
|
-
<h1 className="mb-1 text-xl font-semibold
|
|
415
|
-
|
|
414
|
+
<h1 className="text-foreground mb-1 text-xl font-semibold sm:text-2xl">
|
|
415
|
+
Media Library
|
|
416
|
+
</h1>
|
|
417
|
+
<p className="text-muted-foreground text-sm">{filteredAndSorted.length} files</p>
|
|
416
418
|
</div>
|
|
417
419
|
</div>
|
|
418
420
|
<div>
|
|
@@ -427,7 +429,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
427
429
|
<button
|
|
428
430
|
onClick={() => fileInputRef.current?.click()}
|
|
429
431
|
disabled={uploading}
|
|
430
|
-
className="flex items-center gap-2 rounded-lg
|
|
432
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 rounded-lg px-4 py-2 text-sm transition-colors disabled:opacity-50"
|
|
431
433
|
>
|
|
432
434
|
{uploading ? (
|
|
433
435
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
@@ -441,7 +443,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
441
443
|
|
|
442
444
|
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden">
|
|
443
445
|
{sidebarOpen && (
|
|
444
|
-
<div className="flex w-56 shrink-0 flex-col overflow-hidden rounded-lg border
|
|
446
|
+
<div className="border-border bg-card flex w-56 shrink-0 flex-col overflow-hidden rounded-lg border">
|
|
445
447
|
<FolderTree
|
|
446
448
|
scope="media"
|
|
447
449
|
selected={folderSel}
|
|
@@ -457,23 +459,23 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
457
459
|
)}
|
|
458
460
|
|
|
459
461
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
460
|
-
<div className="mb-4 rounded-lg border
|
|
462
|
+
<div className="border-border bg-card mb-4 rounded-lg border">
|
|
461
463
|
<div className="flex items-center justify-between p-3">
|
|
462
464
|
<div className="flex flex-1 items-center gap-3">
|
|
463
465
|
<div className="relative max-w-md flex-1">
|
|
464
|
-
<Search className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2
|
|
466
|
+
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
|
465
467
|
<input
|
|
466
468
|
type="text"
|
|
467
469
|
placeholder="Search media..."
|
|
468
470
|
value={searchQuery}
|
|
469
471
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
470
|
-
className="w-full rounded-lg border
|
|
472
|
+
className="border-input focus:ring-ring w-full rounded-lg border py-2 pr-3 pl-9 text-sm focus:ring-2 focus:outline-none"
|
|
471
473
|
/>
|
|
472
474
|
</div>
|
|
473
475
|
<select
|
|
474
476
|
value={filterType}
|
|
475
477
|
onChange={(e) => setFilterType(e.target.value)}
|
|
476
|
-
className="rounded-lg border
|
|
478
|
+
className="border-input focus:ring-ring rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
477
479
|
>
|
|
478
480
|
<option value="all">All Types</option>
|
|
479
481
|
<option value="image">Images</option>
|
|
@@ -481,16 +483,16 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
481
483
|
<option value="document">Documents</option>
|
|
482
484
|
</select>
|
|
483
485
|
</div>
|
|
484
|
-
<div className="flex items-center gap-1 rounded-lg
|
|
486
|
+
<div className="bg-muted flex items-center gap-1 rounded-lg p-1">
|
|
485
487
|
<button
|
|
486
488
|
onClick={() => setViewMode('grid')}
|
|
487
|
-
className={`rounded p-1.5 transition-colors ${viewMode === 'grid' ? 'bg-
|
|
489
|
+
className={`rounded p-1.5 transition-colors ${viewMode === 'grid' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
488
490
|
>
|
|
489
491
|
<Grid3x3 className="h-4 w-4" />
|
|
490
492
|
</button>
|
|
491
493
|
<button
|
|
492
494
|
onClick={() => setViewMode('list')}
|
|
493
|
-
className={`rounded p-1.5 transition-colors ${viewMode === 'list' ? 'bg-
|
|
495
|
+
className={`rounded p-1.5 transition-colors ${viewMode === 'list' ? 'bg-card text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground'}`}
|
|
494
496
|
>
|
|
495
497
|
<List className="h-4 w-4" />
|
|
496
498
|
</button>
|
|
@@ -499,9 +501,9 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
499
501
|
</div>
|
|
500
502
|
|
|
501
503
|
{selectedMedia.length > 0 && (
|
|
502
|
-
<div className="mb-4 rounded-lg border
|
|
504
|
+
<div className="border-primary/30 bg-primary/10 mb-4 rounded-lg border p-3">
|
|
503
505
|
<div className="flex items-center justify-between">
|
|
504
|
-
<span className="text-
|
|
506
|
+
<span className="text-primary text-sm">
|
|
505
507
|
{selectedMedia.length} file{selectedMedia.length !== 1 ? 's' : ''} selected
|
|
506
508
|
</span>
|
|
507
509
|
<div className="flex items-center gap-2">
|
|
@@ -510,13 +512,13 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
510
512
|
for (const id of selectedMedia) await deleteMedia(id)
|
|
511
513
|
setSelectedMedia([])
|
|
512
514
|
}}
|
|
513
|
-
className="
|
|
515
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 rounded-lg px-3 py-1.5 text-sm transition-colors"
|
|
514
516
|
>
|
|
515
517
|
Delete Selected
|
|
516
518
|
</button>
|
|
517
519
|
<button
|
|
518
520
|
onClick={() => setSelectedMedia([])}
|
|
519
|
-
className="
|
|
521
|
+
className="border-input bg-card hover:bg-muted rounded-lg border px-3 py-1.5 text-sm transition-colors"
|
|
520
522
|
>
|
|
521
523
|
Cancel
|
|
522
524
|
</button>
|
|
@@ -527,22 +529,24 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
527
529
|
|
|
528
530
|
{filteredAndSorted.length === 0 && !loading ? (
|
|
529
531
|
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
530
|
-
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full
|
|
531
|
-
<ImageIcon className="h-6 w-6
|
|
532
|
+
<div className="bg-muted mb-4 flex h-12 w-12 items-center justify-center rounded-full">
|
|
533
|
+
<ImageIcon className="text-muted-foreground h-6 w-6" />
|
|
532
534
|
</div>
|
|
533
|
-
<h3 className="mb-1 text-sm font-medium
|
|
535
|
+
<h3 className="text-foreground mb-1 text-sm font-medium">
|
|
534
536
|
{folderSel.type === 'smart' && folderSel.smart === 'uncategorized'
|
|
535
537
|
? 'No uncategorized media'
|
|
536
538
|
: folderSel.type === 'folder'
|
|
537
539
|
? 'No media in this folder'
|
|
538
540
|
: 'No media yet'}
|
|
539
541
|
</h3>
|
|
540
|
-
<p className="text-
|
|
542
|
+
<p className="text-muted-foreground text-sm">
|
|
543
|
+
Upload your first file to get started.
|
|
544
|
+
</p>
|
|
541
545
|
</div>
|
|
542
546
|
) : (
|
|
543
547
|
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden">
|
|
544
548
|
<div
|
|
545
|
-
className={`overflow-hidden rounded-lg border
|
|
549
|
+
className={`border-border bg-card overflow-hidden rounded-lg border transition-all duration-200 ${panelOpen ? 'min-w-0 flex-1' : 'w-full'}`}
|
|
546
550
|
>
|
|
547
551
|
{viewMode === 'grid' ? (
|
|
548
552
|
<div
|
|
@@ -567,25 +571,25 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
567
571
|
key={item.id}
|
|
568
572
|
className={`group relative flex cursor-pointer flex-col overflow-hidden rounded-lg border-2 transition-all ${
|
|
569
573
|
isActive
|
|
570
|
-
? 'border-
|
|
574
|
+
? 'border-primary ring-primary/30 ring-2'
|
|
571
575
|
: selectedMedia.includes(item.id)
|
|
572
|
-
? 'border-
|
|
573
|
-
: 'border-
|
|
576
|
+
? 'border-primary/60 ring-primary/20 ring-1'
|
|
577
|
+
: 'border-border hover:border-input'
|
|
574
578
|
}`}
|
|
575
579
|
onClick={() => openDetail(item)}
|
|
576
580
|
draggable
|
|
577
581
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
578
582
|
>
|
|
579
583
|
<div className="relative aspect-square w-full overflow-hidden">
|
|
580
|
-
<div className="flex h-full w-full items-center justify-center
|
|
584
|
+
<div className="bg-muted flex h-full w-full items-center justify-center">
|
|
581
585
|
<MediaPreview item={item} />
|
|
582
586
|
</div>
|
|
583
587
|
{hasIssues && (
|
|
584
588
|
<div
|
|
585
|
-
className="absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full
|
|
589
|
+
className="bg-warning absolute top-1.5 left-1.5 flex h-5 w-5 items-center justify-center rounded-full"
|
|
586
590
|
title="Needs attention"
|
|
587
591
|
>
|
|
588
|
-
<AlertTriangle className="h-3 w-3
|
|
592
|
+
<AlertTriangle className="text-warning-foreground h-3 w-3" />
|
|
589
593
|
</div>
|
|
590
594
|
)}
|
|
591
595
|
<div
|
|
@@ -595,13 +599,13 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
595
599
|
<div
|
|
596
600
|
className={`flex h-5 w-5 items-center justify-center rounded border-2 transition-colors ${
|
|
597
601
|
selectedMedia.includes(item.id)
|
|
598
|
-
? 'border-
|
|
599
|
-
: 'border-
|
|
602
|
+
? 'border-primary bg-primary'
|
|
603
|
+
: 'border-input bg-card/80'
|
|
600
604
|
}`}
|
|
601
605
|
>
|
|
602
606
|
{selectedMedia.includes(item.id) && (
|
|
603
607
|
<svg
|
|
604
|
-
className="h-3 w-3
|
|
608
|
+
className="text-primary-foreground h-3 w-3"
|
|
605
609
|
fill="none"
|
|
606
610
|
viewBox="0 0 12 12"
|
|
607
611
|
>
|
|
@@ -617,18 +621,18 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
617
621
|
</div>
|
|
618
622
|
</div>
|
|
619
623
|
</div>
|
|
620
|
-
<div className="flex items-center gap-2 border-t
|
|
624
|
+
<div className="border-border bg-card flex items-center gap-2 border-t px-2 py-1.5">
|
|
621
625
|
<span
|
|
622
626
|
className={`mt-0.5 h-2 w-2 shrink-0 self-start rounded-full ${
|
|
623
|
-
hasIssues ? 'bg-
|
|
627
|
+
hasIssues ? 'bg-warning' : 'bg-success'
|
|
624
628
|
}`}
|
|
625
629
|
title={hasIssues ? 'Needs attention' : 'SEO complete'}
|
|
626
630
|
/>
|
|
627
631
|
<div className="min-w-0 flex-1">
|
|
628
|
-
<p className="truncate text-xs font-medium
|
|
632
|
+
<p className="text-foreground truncate text-xs font-medium">
|
|
629
633
|
{item.name}
|
|
630
634
|
</p>
|
|
631
|
-
<p className="truncate text-xs
|
|
635
|
+
<p className="text-muted-foreground truncate text-xs">
|
|
632
636
|
{[typeLabel, item.size].filter(Boolean).join(' · ')}
|
|
633
637
|
</p>
|
|
634
638
|
</div>
|
|
@@ -640,7 +644,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
640
644
|
) : (
|
|
641
645
|
<div className="h-full overflow-y-auto">
|
|
642
646
|
<table className="w-full">
|
|
643
|
-
<thead className="sticky top-0 border-b
|
|
647
|
+
<thead className="border-border bg-muted sticky top-0 border-b">
|
|
644
648
|
<tr>
|
|
645
649
|
<th className="w-8 px-3 py-2 text-left">
|
|
646
650
|
<input
|
|
@@ -650,7 +654,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
650
654
|
filteredAndSorted.length > 0
|
|
651
655
|
}
|
|
652
656
|
onChange={handleSelectAll}
|
|
653
|
-
className="
|
|
657
|
+
className="border-input rounded"
|
|
654
658
|
/>
|
|
655
659
|
</th>
|
|
656
660
|
<th className="w-6 px-1 py-2"></th>
|
|
@@ -666,19 +670,19 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
666
670
|
<th className="px-3 py-2 text-left">
|
|
667
671
|
<SortHeader label="Uploaded" sortKey="date" />
|
|
668
672
|
</th>
|
|
669
|
-
<th className="px-3 py-2 text-left text-xs font-medium
|
|
673
|
+
<th className="text-foreground px-3 py-2 text-left text-xs font-medium">
|
|
670
674
|
Status
|
|
671
675
|
</th>
|
|
672
676
|
</tr>
|
|
673
677
|
</thead>
|
|
674
|
-
<tbody className="divide-
|
|
678
|
+
<tbody className="divide-border divide-y">
|
|
675
679
|
{filteredAndSorted.map((item) => {
|
|
676
680
|
const isActive = activeItem?.id === item.id
|
|
677
681
|
const hasIssues = !item.altTag || !item.title || item.usedOn?.length === 0
|
|
678
682
|
return (
|
|
679
683
|
<tr
|
|
680
684
|
key={item.id}
|
|
681
|
-
className={`cursor-pointer transition-colors ${isActive ? 'bg-
|
|
685
|
+
className={`cursor-pointer transition-colors ${isActive ? 'bg-primary/10' : 'hover:bg-muted'}`}
|
|
682
686
|
onClick={() => openDetail(item)}
|
|
683
687
|
draggable
|
|
684
688
|
onDragStart={(e) => handleDragStart(e, item.id)}
|
|
@@ -693,34 +697,38 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
693
697
|
item.id,
|
|
694
698
|
)
|
|
695
699
|
}
|
|
696
|
-
className="
|
|
700
|
+
className="border-input rounded"
|
|
697
701
|
/>
|
|
698
702
|
</td>
|
|
699
703
|
<td className="cursor-grab px-1 py-2">
|
|
700
|
-
<GripVertical className="h-4 w-4
|
|
704
|
+
<GripVertical className="text-muted-foreground h-4 w-4" />
|
|
701
705
|
</td>
|
|
702
706
|
<td className="px-3 py-2">
|
|
703
707
|
<div className="flex items-center gap-3">
|
|
704
|
-
<div className="flex h-10 w-10 items-center justify-center overflow-hidden rounded
|
|
708
|
+
<div className="bg-muted flex h-10 w-10 items-center justify-center overflow-hidden rounded">
|
|
705
709
|
<MediaPreview item={item} />
|
|
706
710
|
</div>
|
|
707
|
-
<span className="text-sm font-medium
|
|
711
|
+
<span className="text-foreground text-sm font-medium">
|
|
708
712
|
{item.name}
|
|
709
713
|
</span>
|
|
710
714
|
</div>
|
|
711
715
|
</td>
|
|
712
|
-
<td className="px-3 py-2 text-sm
|
|
716
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
713
717
|
{item.format ?? item.type}
|
|
714
718
|
</td>
|
|
715
|
-
<td className="px-3 py-2 text-sm
|
|
716
|
-
|
|
719
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
720
|
+
{item.size}
|
|
721
|
+
</td>
|
|
722
|
+
<td className="text-muted-foreground px-3 py-2 text-sm">
|
|
723
|
+
{item.date}
|
|
724
|
+
</td>
|
|
717
725
|
<td className="px-3 py-2">
|
|
718
726
|
{hasIssues ? (
|
|
719
|
-
<span className="inline-flex items-center gap-1 rounded-full
|
|
727
|
+
<span className="bg-warning/15 text-warning inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium">
|
|
720
728
|
<AlertTriangle className="h-3 w-3" /> Needs attention
|
|
721
729
|
</span>
|
|
722
730
|
) : (
|
|
723
|
-
<span className="inline-flex items-center rounded-full
|
|
731
|
+
<span className="bg-success/15 text-success inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">
|
|
724
732
|
Complete
|
|
725
733
|
</span>
|
|
726
734
|
)}
|
|
@@ -735,42 +743,42 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
735
743
|
</div>
|
|
736
744
|
|
|
737
745
|
{panelOpen && activeItem && (
|
|
738
|
-
<div className="flex w-80 shrink-0 flex-col overflow-y-auto rounded-lg border
|
|
739
|
-
<div className="sticky top-0 z-10 flex items-center justify-between border-b
|
|
740
|
-
<h3 className="truncate text-sm font-semibold
|
|
746
|
+
<div className="border-border bg-card flex w-80 shrink-0 flex-col overflow-y-auto rounded-lg border lg:w-96">
|
|
747
|
+
<div className="border-border bg-card sticky top-0 z-10 flex items-center justify-between border-b p-4">
|
|
748
|
+
<h3 className="text-foreground truncate text-sm font-semibold">
|
|
741
749
|
{activeItem.name}
|
|
742
750
|
</h3>
|
|
743
751
|
<button
|
|
744
752
|
onClick={closeDetail}
|
|
745
|
-
className="rounded p-1 transition-colors
|
|
753
|
+
className="hover:bg-muted rounded p-1 transition-colors"
|
|
746
754
|
aria-label="Close panel"
|
|
747
755
|
>
|
|
748
|
-
<X className="h-4 w-4
|
|
756
|
+
<X className="text-muted-foreground h-4 w-4" />
|
|
749
757
|
</button>
|
|
750
758
|
</div>
|
|
751
759
|
|
|
752
760
|
<div className="flex-1 overflow-y-auto">
|
|
753
|
-
<div className="border-
|
|
754
|
-
<div className="flex aspect-video items-center justify-center overflow-hidden rounded-lg
|
|
761
|
+
<div className="border-border border-b p-4">
|
|
762
|
+
<div className="bg-muted flex aspect-video items-center justify-center overflow-hidden rounded-lg">
|
|
755
763
|
{isImageMedia(activeItem) ? (
|
|
756
764
|
<MediaPreview item={activeItem} />
|
|
757
765
|
) : (
|
|
758
|
-
<ImageIcon className="h-12 w-12
|
|
766
|
+
<ImageIcon className="text-muted-foreground h-12 w-12" />
|
|
759
767
|
)}
|
|
760
768
|
</div>
|
|
761
769
|
</div>
|
|
762
770
|
|
|
763
771
|
{issues.length > 0 && (
|
|
764
|
-
<div className="mx-4 mt-4 rounded-lg border
|
|
772
|
+
<div className="border-warning/30 bg-warning/10 mx-4 mt-4 rounded-lg border p-3">
|
|
765
773
|
<div className="flex items-start gap-2">
|
|
766
|
-
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0
|
|
774
|
+
<AlertTriangle className="text-warning mt-0.5 h-4 w-4 shrink-0" />
|
|
767
775
|
<div>
|
|
768
|
-
<p className="mb-1 text-xs font-semibold
|
|
776
|
+
<p className="text-warning mb-1 text-xs font-semibold">
|
|
769
777
|
{issues.length} issue{issues.length !== 1 ? 's' : ''} found
|
|
770
778
|
</p>
|
|
771
779
|
<ul className="space-y-0.5">
|
|
772
780
|
{issues.map((issue, i) => (
|
|
773
|
-
<li key={i} className="text-
|
|
781
|
+
<li key={i} className="text-warning text-xs">
|
|
774
782
|
• {issue}
|
|
775
783
|
</li>
|
|
776
784
|
))}
|
|
@@ -784,7 +792,7 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
784
792
|
if (!activeItem.title) await handleAiGenerate('title')
|
|
785
793
|
if (activeItem.sizeBytes > 2000000) await handleAiGenerate('optimize')
|
|
786
794
|
}}
|
|
787
|
-
className="mt-2 flex w-full items-center justify-center gap-1.5 rounded-lg
|
|
795
|
+
className="bg-warning text-warning-foreground hover:bg-warning/90 mt-2 flex w-full items-center justify-center gap-1.5 rounded-lg px-3 py-1.5 text-xs transition-colors"
|
|
788
796
|
>
|
|
789
797
|
<Sparkles className="h-3.5 w-3.5" />
|
|
790
798
|
AI Fix All Issues
|
|
@@ -792,68 +800,68 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
792
800
|
</div>
|
|
793
801
|
)}
|
|
794
802
|
|
|
795
|
-
<div className="space-y-3 border-b
|
|
796
|
-
<h4 className="text-xs font-semibold tracking-wide
|
|
803
|
+
<div className="border-border space-y-3 border-b p-4">
|
|
804
|
+
<h4 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
|
|
797
805
|
File Information
|
|
798
806
|
</h4>
|
|
799
807
|
<div className="grid grid-cols-2 gap-3">
|
|
800
808
|
<div>
|
|
801
|
-
<div className="mb-0.5 text-xs
|
|
802
|
-
<div className="text-
|
|
809
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Format</div>
|
|
810
|
+
<div className="text-foreground text-sm">
|
|
803
811
|
{activeItem.format ?? 'Unknown'}
|
|
804
812
|
</div>
|
|
805
813
|
</div>
|
|
806
814
|
<div>
|
|
807
|
-
<div className="mb-0.5 text-xs
|
|
808
|
-
<div className="flex items-center gap-1 text-sm
|
|
815
|
+
<div className="text-muted-foreground mb-0.5 text-xs">File Size</div>
|
|
816
|
+
<div className="text-foreground flex items-center gap-1 text-sm">
|
|
809
817
|
{activeItem.size}
|
|
810
818
|
{activeItem.sizeBytes > 2000000 && (
|
|
811
|
-
<span className="text-
|
|
819
|
+
<span className="text-warning text-xs">(large)</span>
|
|
812
820
|
)}
|
|
813
821
|
</div>
|
|
814
822
|
</div>
|
|
815
823
|
<div>
|
|
816
|
-
<div className="mb-0.5 text-xs
|
|
817
|
-
<div className="text-
|
|
824
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Dimensions</div>
|
|
825
|
+
<div className="text-foreground text-sm">
|
|
818
826
|
{activeItem.dimensions ?? '—'}
|
|
819
827
|
</div>
|
|
820
828
|
</div>
|
|
821
829
|
<div>
|
|
822
|
-
<div className="mb-0.5 text-xs
|
|
823
|
-
<div className="text-
|
|
830
|
+
<div className="text-muted-foreground mb-0.5 text-xs">Uploaded</div>
|
|
831
|
+
<div className="text-foreground text-sm">{activeItem.date}</div>
|
|
824
832
|
</div>
|
|
825
833
|
</div>
|
|
826
834
|
|
|
827
835
|
<div>
|
|
828
|
-
<div className="mb-1 text-xs
|
|
836
|
+
<div className="text-muted-foreground mb-1 text-xs">URL</div>
|
|
829
837
|
<div className="flex items-center gap-1">
|
|
830
|
-
<code className="flex-1 truncate rounded border
|
|
838
|
+
<code className="border-border bg-muted text-foreground flex-1 truncate rounded border px-2 py-1.5 text-xs">
|
|
831
839
|
{activeItem.url}
|
|
832
840
|
</code>
|
|
833
841
|
<button
|
|
834
842
|
onClick={handleCopyUrl}
|
|
835
|
-
className="shrink-0 rounded p-1.5 transition-colors
|
|
843
|
+
className="hover:bg-muted shrink-0 rounded p-1.5 transition-colors"
|
|
836
844
|
title="Copy URL"
|
|
837
845
|
>
|
|
838
|
-
<Copy className="h-3.5 w-3.5
|
|
846
|
+
<Copy className="text-muted-foreground h-3.5 w-3.5" />
|
|
839
847
|
</button>
|
|
840
848
|
</div>
|
|
841
849
|
</div>
|
|
842
850
|
</div>
|
|
843
851
|
|
|
844
|
-
<div className="space-y-4 border-b
|
|
845
|
-
<h4 className="text-xs font-semibold tracking-wide
|
|
852
|
+
<div className="border-border space-y-4 border-b p-4">
|
|
853
|
+
<h4 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
|
|
846
854
|
SEO & Accessibility
|
|
847
855
|
</h4>
|
|
848
856
|
|
|
849
857
|
<div>
|
|
850
858
|
<div className="mb-1 flex items-center justify-between">
|
|
851
|
-
<label className="text-sm font-medium
|
|
859
|
+
<label className="text-foreground text-sm font-medium">Alt Tag</label>
|
|
852
860
|
<button
|
|
853
861
|
type="button"
|
|
854
862
|
onClick={() => handleAiGenerate('alt')}
|
|
855
863
|
disabled={aiGenerating === 'alt'}
|
|
856
|
-
className="flex items-center gap-1 text-xs
|
|
864
|
+
className="text-primary hover:text-primary/80 flex items-center gap-1 text-xs transition-colors disabled:opacity-50"
|
|
857
865
|
>
|
|
858
866
|
<Bot className="h-3.5 w-3.5" />
|
|
859
867
|
{aiGenerating === 'alt' ? 'Generating...' : 'AI Generate'}
|
|
@@ -864,10 +872,10 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
864
872
|
onChange={(e) => setEditAlt(e.target.value)}
|
|
865
873
|
placeholder="Describe this image for accessibility..."
|
|
866
874
|
rows={2}
|
|
867
|
-
className="w-full resize-none rounded-lg border
|
|
875
|
+
className="border-input focus:ring-ring w-full resize-none rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
868
876
|
/>
|
|
869
877
|
{!editAlt && (
|
|
870
|
-
<p className="mt-1 text-xs
|
|
878
|
+
<p className="text-destructive mt-1 text-xs">
|
|
871
879
|
Required for accessibility and SEO
|
|
872
880
|
</p>
|
|
873
881
|
)}
|
|
@@ -875,12 +883,12 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
875
883
|
|
|
876
884
|
<div>
|
|
877
885
|
<div className="mb-1 flex items-center justify-between">
|
|
878
|
-
<label className="text-sm font-medium
|
|
886
|
+
<label className="text-foreground text-sm font-medium">Title</label>
|
|
879
887
|
<button
|
|
880
888
|
type="button"
|
|
881
889
|
onClick={() => handleAiGenerate('title')}
|
|
882
890
|
disabled={aiGenerating === 'title'}
|
|
883
|
-
className="flex items-center gap-1 text-xs
|
|
891
|
+
className="text-primary hover:text-primary/80 flex items-center gap-1 text-xs transition-colors disabled:opacity-50"
|
|
884
892
|
>
|
|
885
893
|
<Bot className="h-3.5 w-3.5" />
|
|
886
894
|
{aiGenerating === 'title' ? 'Generating...' : 'AI Generate'}
|
|
@@ -891,25 +899,25 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
891
899
|
value={editTitle}
|
|
892
900
|
onChange={(e) => setEditTitle(e.target.value)}
|
|
893
901
|
placeholder="Image title..."
|
|
894
|
-
className="w-full rounded-lg border
|
|
902
|
+
className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
895
903
|
/>
|
|
896
904
|
</div>
|
|
897
905
|
|
|
898
906
|
<div>
|
|
899
|
-
<label className="mb-1 block text-sm font-medium
|
|
907
|
+
<label className="text-foreground mb-1 block text-sm font-medium">
|
|
900
908
|
File Name
|
|
901
909
|
</label>
|
|
902
910
|
<input
|
|
903
911
|
type="text"
|
|
904
912
|
value={editFilename}
|
|
905
913
|
onChange={(e) => setEditFilename(e.target.value)}
|
|
906
|
-
className="w-full rounded-lg border
|
|
914
|
+
className="border-input focus:ring-ring w-full rounded-lg border px-3 py-2 text-sm focus:ring-2 focus:outline-none"
|
|
907
915
|
/>
|
|
908
916
|
</div>
|
|
909
917
|
</div>
|
|
910
918
|
|
|
911
|
-
<div className="border-
|
|
912
|
-
<h4 className="mb-3 text-xs font-semibold tracking-wide
|
|
919
|
+
<div className="border-border border-b p-4">
|
|
920
|
+
<h4 className="text-muted-foreground mb-3 text-xs font-semibold tracking-wide uppercase">
|
|
913
921
|
Used On {activeItem.usedOn && `(${activeItem.usedOn.length})`}
|
|
914
922
|
</h4>
|
|
915
923
|
{activeItem.usedOn && activeItem.usedOn.length > 0 ? (
|
|
@@ -919,23 +927,23 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
919
927
|
key={i}
|
|
920
928
|
type="button"
|
|
921
929
|
onClick={() => onNavigate?.(usage.path)}
|
|
922
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
930
|
+
className="border-border hover:bg-muted flex w-full items-center gap-2 rounded-lg border p-2 text-left transition-colors"
|
|
923
931
|
>
|
|
924
|
-
<Link2 className="h-4 w-4 shrink-0
|
|
925
|
-
<span className="flex-1 truncate text-sm
|
|
932
|
+
<Link2 className="text-muted-foreground h-4 w-4 shrink-0" />
|
|
933
|
+
<span className="text-foreground flex-1 truncate text-sm">
|
|
926
934
|
{usage.page}
|
|
927
935
|
</span>
|
|
928
|
-
<ExternalLink className="h-3.5 w-3.5 shrink-0
|
|
936
|
+
<ExternalLink className="text-muted-foreground h-3.5 w-3.5 shrink-0" />
|
|
929
937
|
</button>
|
|
930
938
|
))}
|
|
931
939
|
</div>
|
|
932
940
|
) : (
|
|
933
|
-
<div className="
|
|
941
|
+
<div className="border-warning/30 bg-warning/10 rounded-lg border p-3">
|
|
934
942
|
<div className="flex items-start gap-2">
|
|
935
|
-
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0
|
|
943
|
+
<AlertTriangle className="text-warning mt-0.5 h-4 w-4 shrink-0" />
|
|
936
944
|
<div>
|
|
937
|
-
<p className="text-xs font-medium
|
|
938
|
-
<p className="mt-0.5 text-xs
|
|
945
|
+
<p className="text-warning text-xs font-medium">Orphaned media</p>
|
|
946
|
+
<p className="text-warning mt-0.5 text-xs">
|
|
939
947
|
This file isn't used on any page. Consider deleting it to save
|
|
940
948
|
storage.
|
|
941
949
|
</p>
|
|
@@ -946,37 +954,37 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
946
954
|
</div>
|
|
947
955
|
|
|
948
956
|
<div className="space-y-3 p-4">
|
|
949
|
-
<h4 className="text-xs font-semibold tracking-wide
|
|
957
|
+
<h4 className="text-muted-foreground text-xs font-semibold tracking-wide uppercase">
|
|
950
958
|
AI Optimization
|
|
951
959
|
</h4>
|
|
952
960
|
<button
|
|
953
961
|
type="button"
|
|
954
962
|
onClick={() => handleAiGenerate('optimize')}
|
|
955
963
|
disabled={aiGenerating === 'optimize'}
|
|
956
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
964
|
+
className="border-primary/30 bg-primary/10 hover:bg-primary/15 flex w-full items-center gap-2 rounded-lg border p-3 text-left transition-colors disabled:opacity-50"
|
|
957
965
|
>
|
|
958
966
|
<Sparkles
|
|
959
|
-
className={`h-5 w-5 shrink-0
|
|
967
|
+
className={`text-primary h-5 w-5 shrink-0 ${aiGenerating === 'optimize' ? 'animate-spin' : ''}`}
|
|
960
968
|
/>
|
|
961
969
|
<div className="flex-1">
|
|
962
|
-
<div className="text-sm font-medium
|
|
970
|
+
<div className="text-primary text-sm font-medium">
|
|
963
971
|
{aiGenerating === 'optimize' ? 'Optimizing...' : 'Optimize Image'}
|
|
964
972
|
</div>
|
|
965
|
-
<div className="mt-0.5 text-xs
|
|
973
|
+
<div className="text-primary mt-0.5 text-xs">
|
|
966
974
|
Compress and convert to modern format (WebP/AVIF)
|
|
967
975
|
</div>
|
|
968
976
|
</div>
|
|
969
977
|
</button>
|
|
970
978
|
<button
|
|
971
979
|
type="button"
|
|
972
|
-
className="flex w-full items-center gap-2 rounded-lg border
|
|
980
|
+
className="border-border hover:bg-muted flex w-full items-center gap-2 rounded-lg border p-3 text-left transition-colors"
|
|
973
981
|
>
|
|
974
|
-
<Bot className="h-5 w-5 shrink-0
|
|
982
|
+
<Bot className="text-muted-foreground h-5 w-5 shrink-0" />
|
|
975
983
|
<div className="flex-1">
|
|
976
|
-
<div className="text-sm font-medium
|
|
984
|
+
<div className="text-foreground text-sm font-medium">
|
|
977
985
|
AI Content Analysis
|
|
978
986
|
</div>
|
|
979
|
-
<div className="mt-0.5 text-xs
|
|
987
|
+
<div className="text-muted-foreground mt-0.5 text-xs">
|
|
980
988
|
Detect objects, faces, text, and suggest categories
|
|
981
989
|
</div>
|
|
982
990
|
</div>
|
|
@@ -984,30 +992,30 @@ export function MediaBrowser({ onNavigate }: MediaBrowserProps) {
|
|
|
984
992
|
</div>
|
|
985
993
|
</div>
|
|
986
994
|
|
|
987
|
-
<div className="sticky bottom-0 flex items-center gap-2 border-t
|
|
995
|
+
<div className="border-border bg-card sticky bottom-0 flex items-center gap-2 border-t p-4">
|
|
988
996
|
<button
|
|
989
997
|
type="button"
|
|
990
998
|
onClick={handleSaveDetails}
|
|
991
999
|
disabled={saving}
|
|
992
|
-
className="flex flex-1 items-center justify-center gap-2 rounded-lg
|
|
1000
|
+
className="bg-primary text-primary-foreground hover:bg-primary/90 flex flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm transition-colors disabled:opacity-50"
|
|
993
1001
|
>
|
|
994
1002
|
{saving && <Loader2 className="h-4 w-4 animate-spin" />}
|
|
995
1003
|
{saving ? 'Saving...' : 'Save Changes'}
|
|
996
1004
|
</button>
|
|
997
1005
|
<button
|
|
998
1006
|
type="button"
|
|
999
|
-
className="rounded-lg p-2 transition-colors
|
|
1007
|
+
className="hover:bg-muted rounded-lg p-2 transition-colors"
|
|
1000
1008
|
title="Download"
|
|
1001
1009
|
>
|
|
1002
|
-
<Download className="h-4 w-4
|
|
1010
|
+
<Download className="text-muted-foreground h-4 w-4" />
|
|
1003
1011
|
</button>
|
|
1004
1012
|
<button
|
|
1005
1013
|
type="button"
|
|
1006
1014
|
onClick={() => deleteMedia(activeItem.id)}
|
|
1007
|
-
className="rounded-lg p-2 transition-colors
|
|
1015
|
+
className="hover:bg-muted rounded-lg p-2 transition-colors"
|
|
1008
1016
|
title="Delete"
|
|
1009
1017
|
>
|
|
1010
|
-
<Trash2 className="h-4 w-4
|
|
1018
|
+
<Trash2 className="text-destructive h-4 w-4" />
|
|
1011
1019
|
</button>
|
|
1012
1020
|
</div>
|
|
1013
1021
|
</div>
|