feat: Update UI components with logo integration and styling enhancements
This commit enhances the Layout, Dashboard, Login, and PendingPhotos components by integrating a logo with fallback support and updating various UI styles for improved aesthetics. The Dashboard section's background gradient is replaced with a linear gradient, and text colors are adjusted for better visibility. Additionally, the PendingPhotos component's confirmation messages are clarified, and the cleanup functionality is refined to specify which records are affected. Documentation has been updated to reflect these changes.
This commit is contained in:
parent
e9e8fbf3f5
commit
8f31e1942f
@ -102,16 +102,26 @@ export default function Layout() {
|
||||
{/* Top bar */}
|
||||
<div className="bg-white border-b border-gray-200 shadow-sm">
|
||||
<div className="flex">
|
||||
{/* Left sidebar - fixed position */}
|
||||
<div className="fixed left-0 top-0 w-64 bg-white border-r border-gray-200 h-12 flex items-center px-4 z-10">
|
||||
<Link to="/" className="flex items-center gap-2 hover:opacity-80 transition-opacity">
|
||||
<span className="text-2xl">🏠</span>
|
||||
<h2 className="text-xl font-bold text-gray-900">PunimTag</h2>
|
||||
{/* Left sidebar - fixed position with logo */}
|
||||
<div className="fixed left-0 top-0 w-64 bg-white border-r border-gray-200 h-20 flex items-center justify-center px-4 z-10">
|
||||
<Link to="/" className="flex items-center justify-center hover:opacity-80 transition-opacity">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="PunimTag"
|
||||
className="h-12 w-auto"
|
||||
onError={(e) => {
|
||||
// Fallback if logo.png doesn't exist, try logo.svg
|
||||
const target = e.target as HTMLImageElement
|
||||
if (target.src.endsWith('logo.png')) {
|
||||
target.src = '/logo.svg'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
{/* Header content - aligned with main content */}
|
||||
<div className="ml-64 flex-1 px-4">
|
||||
<div className="flex justify-between items-center h-12">
|
||||
<div className="flex justify-between items-center h-20">
|
||||
<div className="flex items-center">
|
||||
<h1 className="text-lg font-bold text-gray-900">{getPageTitle()}</h1>
|
||||
</div>
|
||||
@ -131,7 +141,7 @@ export default function Layout() {
|
||||
|
||||
<div className="flex relative">
|
||||
{/* Left sidebar - fixed position */}
|
||||
<div className="fixed left-0 top-12 w-64 bg-white border-r border-gray-200 h-[calc(100vh-3rem)] overflow-y-auto">
|
||||
<div className="fixed left-0 top-20 w-64 bg-white border-r border-gray-200 h-[calc(100vh-5rem)] overflow-y-auto">
|
||||
<nav className="p-4 space-y-1">
|
||||
{visiblePrimary.map((item) => renderNavLink(item))}
|
||||
|
||||
|
||||
@ -37,20 +37,16 @@ export default function Dashboard() {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<section className="relative bg-gradient-to-br from-blue-600 via-purple-600 to-pink-500 text-white py-12 px-4 overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-10">
|
||||
<div className="absolute top-10 left-10 w-72 h-72 bg-white rounded-full blur-3xl"></div>
|
||||
<div className="absolute bottom-10 right-10 w-96 h-96 bg-white rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
<section className="relative py-12 px-4 overflow-hidden" style={{ background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 50%, #f0f9ff 100%)' }}>
|
||||
<div className="max-w-6xl mx-auto relative z-10">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4 leading-tight">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4 leading-tight" style={{ color: '#F97316' }}>
|
||||
Welcome to PunimTag
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-blue-100 mb-3">
|
||||
<p className="text-xl md:text-2xl mb-3" style={{ color: '#2563EB' }}>
|
||||
Your Intelligent Photo Management System
|
||||
</p>
|
||||
<p className="text-lg text-blue-50 max-w-2xl mx-auto">
|
||||
<p className="text-lg max-w-2xl mx-auto text-gray-600">
|
||||
Organize, identify, and search through your photo collection like never before.
|
||||
</p>
|
||||
</div>
|
||||
@ -74,21 +70,21 @@ export default function Dashboard() {
|
||||
</p>
|
||||
<ul className="space-y-3 text-gray-700">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Automatically finds faces in all your photos</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Recognizes the same person across different photos</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Works even with photos taken years apart</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="bg-gradient-to-br from-blue-100 to-purple-100 rounded-2xl p-8 shadow-xl">
|
||||
<div className="bg-gray-50 rounded-2xl p-8 shadow-xl">
|
||||
<div className="aspect-video bg-white rounded-lg shadow-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">👥</div>
|
||||
@ -108,7 +104,7 @@ export default function Dashboard() {
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div className="order-2 md:order-1 relative">
|
||||
<div className="bg-gradient-to-br from-orange-100 to-pink-100 rounded-2xl p-8 shadow-xl">
|
||||
<div className="bg-gray-50 rounded-2xl p-8 shadow-xl">
|
||||
<div className="aspect-video bg-white rounded-lg shadow-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">🔍</div>
|
||||
@ -132,15 +128,15 @@ export default function Dashboard() {
|
||||
</p>
|
||||
<ul className="space-y-3 text-gray-700">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Search by person name across all photos</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Filter by date ranges and folders</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Tag-based organization and filtering</span>
|
||||
</li>
|
||||
</ul>
|
||||
@ -166,21 +162,21 @@ export default function Dashboard() {
|
||||
</p>
|
||||
<ul className="space-y-3 text-gray-700">
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Batch face detection and recognition</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Real-time progress updates</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<span className="text-green-500 text-xl">✓</span>
|
||||
<span className="text-xl" style={{ color: '#2563EB' }}>✓</span>
|
||||
<span>Background job processing</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div className="bg-gradient-to-br from-green-100 to-teal-100 rounded-2xl p-8 shadow-xl">
|
||||
<div className="bg-gray-50 rounded-2xl p-8 shadow-xl">
|
||||
<div className="aspect-video bg-white rounded-lg shadow-lg flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">📊</div>
|
||||
@ -210,7 +206,7 @@ export default function Dashboard() {
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl p-6 shadow-md aspect-square flex items-center justify-center animate-pulse"
|
||||
className="bg-gray-50 rounded-xl p-6 shadow-md aspect-square flex items-center justify-center animate-pulse"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">📸</div>
|
||||
@ -224,7 +220,7 @@ export default function Dashboard() {
|
||||
{samplePhotos.slice(0, 6).map((photo) => (
|
||||
<div
|
||||
key={photo.id}
|
||||
className="bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl p-2 shadow-md aspect-square overflow-hidden group hover:shadow-lg transition-shadow"
|
||||
className="bg-gray-50 rounded-xl p-2 shadow-md aspect-square overflow-hidden group hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<img
|
||||
src={getPhotoImageUrl(photo.id)}
|
||||
@ -253,7 +249,7 @@ export default function Dashboard() {
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="bg-gradient-to-br from-blue-50 to-purple-50 rounded-xl p-6 shadow-md aspect-square flex items-center justify-center"
|
||||
className="bg-gray-50 rounded-xl p-6 shadow-md aspect-square flex items-center justify-center"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="text-6xl mb-4">📸</div>
|
||||
@ -267,29 +263,29 @@ export default function Dashboard() {
|
||||
</section>
|
||||
|
||||
{/* CTA Section */}
|
||||
<section className="py-20 px-4 bg-gradient-to-r from-blue-600 to-purple-600 text-white">
|
||||
<section className="py-20 px-4 bg-white">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-6">
|
||||
<h2 className="text-4xl md:text-5xl font-bold mb-6" style={{ color: '#F97316' }}>
|
||||
Ready to Get Started?
|
||||
</h2>
|
||||
<p className="text-xl text-blue-100 mb-8 max-w-2xl mx-auto">
|
||||
<p className="text-xl mb-8 max-w-2xl mx-auto text-gray-600">
|
||||
Begin organizing your photo collection today. Use the navigation menu
|
||||
to explore all the powerful features PunimTag has to offer.
|
||||
</p>
|
||||
<div className="flex flex-wrap justify-center gap-4">
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg px-6 py-3 text-sm">
|
||||
<div className="border rounded-lg px-6 py-3 text-sm" style={{ borderColor: '#2563EB', color: '#2563EB' }}>
|
||||
<span className="font-semibold">🗂️</span> Scan Photos
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg px-6 py-3 text-sm">
|
||||
<div className="border rounded-lg px-6 py-3 text-sm" style={{ borderColor: '#2563EB', color: '#2563EB' }}>
|
||||
<span className="font-semibold">⚙️</span> Process Faces
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg px-6 py-3 text-sm">
|
||||
<div className="border rounded-lg px-6 py-3 text-sm" style={{ borderColor: '#2563EB', color: '#2563EB' }}>
|
||||
<span className="font-semibold">👤</span> Identify People
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg px-6 py-3 text-sm">
|
||||
<div className="border rounded-lg px-6 py-3 text-sm" style={{ borderColor: '#2563EB', color: '#2563EB' }}>
|
||||
<span className="font-semibold">🤖</span> Auto-Match
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-lg px-6 py-3 text-sm">
|
||||
<div className="border rounded-lg px-6 py-3 text-sm" style={{ borderColor: '#2563EB', color: '#2563EB' }}>
|
||||
<span className="font-semibold">🔍</span> Search Photos
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -49,9 +49,20 @@ export default function Login() {
|
||||
<div className="max-w-md w-full">
|
||||
<div className="bg-white rounded-lg shadow-md p-8">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
PunimTag
|
||||
</h1>
|
||||
<div className="flex justify-center mb-4">
|
||||
<img
|
||||
src="/logo.png"
|
||||
alt="PunimTag"
|
||||
className="h-16 w-auto"
|
||||
onError={(e) => {
|
||||
// Fallback if logo.png doesn't exist, try logo.svg
|
||||
const target = e.target as HTMLImageElement
|
||||
if (target.src.endsWith('logo.png')) {
|
||||
target.src = '/logo.svg'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-gray-600">Photo Management System</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -379,7 +379,7 @@ export default function PendingPhotos() {
|
||||
const handleCleanupDatabase = async (statusFilter?: string) => {
|
||||
const confirmMessage = statusFilter
|
||||
? `Delete all ${statusFilter} records from pending_photos table? This cannot be undone.`
|
||||
: 'Delete ALL records from pending_photos table? This cannot be undone.'
|
||||
: 'Delete all approved and rejected records from pending_photos table? Pending records will be kept. This cannot be undone.'
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
return
|
||||
@ -523,7 +523,7 @@ export default function PendingPhotos() {
|
||||
className="px-3 py-1.5 text-sm bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 font-medium"
|
||||
title={
|
||||
canRunCleanup
|
||||
? 'Delete all records from pending_photos table'
|
||||
? 'Delete approved and rejected records from pending_photos table (pending records will be kept)'
|
||||
: 'Clear database is restricted to admins'
|
||||
}
|
||||
>
|
||||
|
||||
@ -531,13 +531,13 @@ def cleanup_shared_files(
|
||||
@router.post("/cleanup-database", response_model=CleanupResponse)
|
||||
def cleanup_pending_photos_database(
|
||||
current_admin: dict = Depends(get_current_admin_user),
|
||||
status_filter: Optional[str] = Query(None, description="Filter by status: 'approved', 'rejected', or None for all"),
|
||||
status_filter: Optional[str] = Query(None, description="Filter by status: 'approved', 'rejected', or None for approved+rejected (excludes pending)"),
|
||||
auth_db: Session = Depends(get_auth_db),
|
||||
) -> CleanupResponse:
|
||||
"""Delete records from pending_photos table.
|
||||
|
||||
Args:
|
||||
status_filter: Optional filter - 'approved', 'rejected', or None for all records
|
||||
status_filter: Optional filter - 'approved', 'rejected', or None for approved+rejected (excludes pending)
|
||||
"""
|
||||
deleted_records = 0
|
||||
errors = []
|
||||
@ -545,9 +545,18 @@ def cleanup_pending_photos_database(
|
||||
|
||||
try:
|
||||
# First check if table exists and has records
|
||||
check_result = auth_db.execute(text("""
|
||||
SELECT COUNT(*) as count FROM pending_photos
|
||||
"""))
|
||||
if status_filter:
|
||||
# Check count for specific status
|
||||
check_result = auth_db.execute(text("""
|
||||
SELECT COUNT(*) as count FROM pending_photos
|
||||
WHERE status = :status_filter
|
||||
"""), {"status_filter": status_filter})
|
||||
else:
|
||||
# Check count for approved and rejected (exclude pending)
|
||||
check_result = auth_db.execute(text("""
|
||||
SELECT COUNT(*) as count FROM pending_photos
|
||||
WHERE status IN ('approved', 'rejected')
|
||||
"""))
|
||||
total_count = check_result.fetchone().count if check_result else 0
|
||||
|
||||
if total_count == 0:
|
||||
@ -566,8 +575,10 @@ def cleanup_pending_photos_database(
|
||||
WHERE status = :status_filter
|
||||
"""), {"status_filter": status_filter})
|
||||
else:
|
||||
# Default behavior: delete only approved and rejected, exclude pending
|
||||
result = auth_db.execute(text("""
|
||||
DELETE FROM pending_photos
|
||||
WHERE status IN ('approved', 'rejected')
|
||||
"""))
|
||||
|
||||
deleted_records = result.rowcount if hasattr(result, 'rowcount') else 0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user