Files
immich-folio/app/admin/components/AlbumPicker.tsx
ralksta 9e52d7a0b8 feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00

98 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useMemo } from 'react';
interface ImmichAlbumInfo {
id: string;
albumName: string;
description: string;
thumbnailAssetId: string | null;
assetCount: number;
isConfigured: boolean;
}
interface Props {
albums: ImmichAlbumInfo[];
onSelect: (albumId: string) => void;
onClose: () => void;
usedAlbumIds: Set<string>;
}
export default function AlbumPicker({ albums, onSelect, onClose, usedAlbumIds }: Props) {
const [search, setSearch] = useState('');
const filtered = useMemo(() => {
if (!search.trim()) return albums;
const q = search.toLowerCase();
return albums.filter(
(a) =>
a.albumName.toLowerCase().includes(q) ||
a.description.toLowerCase().includes(q) ||
a.id.includes(q),
);
}, [albums, search]);
return (
<div className="picker-overlay" onClick={onClose}>
<div className="picker-modal" onClick={(e) => e.stopPropagation()}>
<div className="picker-header">
<h3>Select Album</h3>
<button className="admin-btn-icon" onClick={onClose}>
×
</button>
</div>
<div className="picker-search">
<input
type="text"
placeholder="Search albums by name or UUID..."
value={search}
onChange={(e) => setSearch(e.target.value)}
autoFocus
/>
</div>
<div className="picker-list">
{filtered.length === 0 && (
<p className="empty-hint">
No albums found. Make sure you have shared albums in Immich.
</p>
)}
{filtered.map((album) => {
const isUsed = usedAlbumIds.has(album.id);
return (
<div
key={album.id}
className={`picker-item ${isUsed ? 'used' : ''}`}
onClick={() => !isUsed && onSelect(album.id)}
>
<div className="picker-item-thumb">
{album.thumbnailAssetId ? (
<div className="picker-thumb-placeholder">📷</div>
) : (
<div className="picker-thumb-placeholder">📁</div>
)}
</div>
<div className="picker-item-info">
<span className="picker-item-name">{album.albumName}</span>
<span className="picker-item-meta">
{album.assetCount} photos
{album.description && ` · ${album.description.slice(0, 40)}`}
</span>
</div>
<div className="picker-item-action">
{isUsed ? (
<span className="picker-used-badge">In use</span>
) : (
<span className="picker-select-badge">Select</span>
)}
</div>
</div>
);
})}
</div>
</div>
</div>
);
}