diff --git a/admin-frontend/package-lock.json b/admin-frontend/package-lock.json
index ffb4527..a4d4e65 100644
--- a/admin-frontend/package-lock.json
+++ b/admin-frontend/package-lock.json
@@ -11,6 +11,7 @@
"@tanstack/react-query": "^5.8.4",
"axios": "^1.6.2",
"exifr": "^7.1.3",
+ "lucide-react": "^0.553.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0"
@@ -4599,6 +4600,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.553.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz",
+ "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
diff --git a/admin-frontend/package.json b/admin-frontend/package.json
index a2bffea..8a07c72 100644
--- a/admin-frontend/package.json
+++ b/admin-frontend/package.json
@@ -14,6 +14,7 @@
"@tanstack/react-query": "^5.8.4",
"axios": "^1.6.2",
"exifr": "^7.1.3",
+ "lucide-react": "^0.553.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0"
diff --git a/admin-frontend/src/pages/ApproveIdentified.tsx b/admin-frontend/src/pages/ApproveIdentified.tsx
index 0fbfe07..22e39cf 100644
--- a/admin-frontend/src/pages/ApproveIdentified.tsx
+++ b/admin-frontend/src/pages/ApproveIdentified.tsx
@@ -171,9 +171,9 @@ export default function ApproveIdentified() {
})
const message = [
- `✅ Approved: ${response.approved}`,
- `❌ Denied: ${response.denied}`,
- response.errors.length > 0 ? `⚠️ Errors: ${response.errors.length}` : ''
+ `Approved: ${response.approved}`,
+ `Denied: ${response.denied}`,
+ response.errors.length > 0 ? `Errors: ${response.errors.length}` : ''
].filter(Boolean).join('\n')
showToast(message, response.errors.length > 0 ? 'error' : 'success')
@@ -247,8 +247,8 @@ export default function ApproveIdentified() {
const response = await pendingIdentificationsApi.clearDenied()
const message = [
- `✅ Deleted ${response.deleted_records} denied record(s)`,
- response.errors.length > 0 ? `⚠️ Errors: ${response.errors.length}` : ''
+ `Deleted ${response.deleted_records} denied record(s)`,
+ response.errors.length > 0 ? `Errors: ${response.errors.length}` : ''
].filter(Boolean).join('\n')
showToast(message, response.errors.length > 0 ? 'error' : 'success')
diff --git a/admin-frontend/src/pages/Help.tsx b/admin-frontend/src/pages/Help.tsx
index 9814781..975c6a4 100644
--- a/admin-frontend/src/pages/Help.tsx
+++ b/admin-frontend/src/pages/Help.tsx
@@ -1,4 +1,22 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, type ReactNode } from 'react'
+import type { LucideIcon } from 'lucide-react'
+import {
+ ArrowLeft,
+ Bot,
+ ChevronRight,
+ Flag,
+ FolderInput,
+ Pencil,
+ Search,
+ Settings,
+ Tag,
+ Tags,
+ Upload,
+ User,
+ UserCheck,
+ Users,
+ Wrench,
+} from 'lucide-react'
type PageId = 'overview' | 'scan' | 'process' | 'identify' | 'auto-match' | 'search' | 'modify' | 'tags' | 'faces-maintenance' | 'user-identified-faces' | 'user-reported-photos' | 'user-tagged-photos' | 'user-uploaded-photos' | 'users'
@@ -69,74 +87,75 @@ export default function Help() {
)
}
+type HelpNavItem = {
+ id: PageId
+ icon: LucideIcon
+ label: string
+ description: string
+}
+
+function HelpNavButton({ item, onPageClick }: { item: HelpNavItem; onPageClick: (page: PageId) => void }) {
+ const Icon = item.icon
+ return (
+ onPageClick(item.id)}
+ className="flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
+ >
+
+
+
{item.label}
+
{item.description}
+
+
+
+ )
+}
+
function NavigationOverview({ onPageClick }: { onPageClick: (page: PageId) => void }) {
- const mainNavItems = [
- { id: 'scan' as PageId, icon: '🗂️', label: 'Scan', description: 'Import photos from folders or upload files' },
- { id: 'process' as PageId, icon: '⚙️', label: 'Process', description: 'Detect and process faces in photos' },
- { id: 'identify' as PageId, icon: '👤', label: 'Identify People', description: 'Manually identify people in faces' },
- { id: 'auto-match' as PageId, icon: '🤖', label: 'Auto-Match', description: 'Automatically match similar faces to previously identified faces' },
- { id: 'search' as PageId, icon: '🔍', label: 'Search Photos', description: 'Search and filter photos' },
- { id: 'modify' as PageId, icon: '✏️', label: 'Modify People', description: 'Edit person information' },
- { id: 'tags' as PageId, icon: '🏷️', label: 'Tag Photos', description: 'Tag photos and manage photo tags' },
+ const mainNavItems: HelpNavItem[] = [
+ { id: 'scan', icon: FolderInput, label: 'Scan', description: 'Import photos from local folders or network paths' },
+ { id: 'process', icon: Settings, label: 'Process', description: 'Detect faces in scanned photos' },
+ { id: 'identify', icon: User, label: 'Identify', description: 'Name unknown faces in photos and videos' },
+ { id: 'auto-match', icon: Bot, label: 'Auto-Match', description: 'Review suggested matches to known people' },
+ { id: 'search', icon: Search, label: 'Search photos', description: 'Browse by person, date, tags, or folder' },
+ { id: 'modify', icon: Pencil, label: 'Modify', description: 'Edit person details and fix identifications' },
+ { id: 'tags', icon: Tags, label: 'Tag photos', description: 'Organize photos with tags' },
]
- const maintenanceNavItems = [
- { id: 'faces-maintenance' as PageId, icon: '🔧', label: 'Faces', description: 'Remove unwanted faces from the database (under Maintenance tab)' },
- { id: 'user-identified-faces' as PageId, icon: '✅', label: 'User Identified Faces', description: 'Approve or deny face identifications made by users (under Maintenance tab)' },
- { id: 'user-reported-photos' as PageId, icon: '🚨', label: 'User Reported Photos', description: 'Review and manage photos reported by users (under Maintenance tab)' },
- { id: 'user-tagged-photos' as PageId, icon: '🏷️', label: 'User Tagged Photos', description: 'Approve or deny tags suggested by users (under Maintenance tab)' },
- { id: 'user-uploaded-photos' as PageId, icon: '📤', label: 'User Uploaded Photos', description: 'Approve or reject photos uploaded by users (under Maintenance tab)' },
- { id: 'users' as PageId, icon: '👥', label: 'Users', description: 'Manage backend users, frontend users, and roles (under Maintenance tab)' },
+ const maintenanceNavItems: HelpNavItem[] = [
+ { id: 'faces-maintenance', icon: Wrench, label: 'Faces', description: 'Remove low-quality or false-positive face detections' },
+ { id: 'user-identified-faces', icon: UserCheck, label: 'User-identified faces', description: 'Approve or deny identifications submitted by viewer users' },
+ { id: 'user-reported-photos', icon: Flag, label: 'Reported photos', description: 'Review photos flagged by viewer users' },
+ { id: 'user-tagged-photos', icon: Tag, label: 'User-tagged photos', description: 'Approve or deny tag suggestions from viewer users' },
+ { id: 'user-uploaded-photos', icon: Upload, label: 'User uploads', description: 'Approve or reject uploads from viewer users' },
+ { id: 'users', icon: Users, label: 'Users', description: 'Manage admin accounts, viewer accounts, and roles' },
]
return (
-
Navigation Overview
+
Navigation overview
- The application uses a left sidebar navigation with the following pages. Click on any page to learn more about it:
+ Use the left sidebar to move between pages. Pick a page below for a short walkthrough.
-
+
-
Main Pages
+
Main pages
{mainNavItems.map((item) => (
-
onPageClick(item.id)}
- className="flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
- >
- {item.icon}
-
-
- {item.label}
-
-
{item.description}
-
- →
-
+
))}
-
Maintenance Tab
-
These pages are located under the Maintenance tab in the navigation sidebar:
+
Maintenance
+
+ These pages live under the Maintenance section in the sidebar.
+
{maintenanceNavItems.map((item) => (
-
onPageClick(item.id)}
- className="flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
- >
- {item.icon}
-
-
- {item.label}
-
-
{item.description}
-
- →
-
+
))}
@@ -144,15 +163,17 @@ function NavigationOverview({ onPageClick }: { onPageClick: (page: PageId) => vo
)
}
-function PageHelpLayout({ title, onBack, children }: { title: string; onBack: () => void; children: React.ReactNode }) {
+function PageHelpLayout({ title, onBack, children }: { title: string; onBack: () => void; children: ReactNode }) {
return (
- ← Back
+
+ Back
{title}
@@ -352,10 +373,10 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
View the current face on the left panel
To exclude a face from identification:
- Click the 🚫 button next to the face counter
+ Click the Exclude button next to the face counter
The face will be marked as excluded and automatically skipped
Excluded faces are hidden by default (check "Include excluded faces" in filters to see them)
- Click the 🚫 button again to include the face back in identification
+ Click Exclude again to include the face back in identification
Choose how to identify:
@@ -379,11 +400,11 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
Confidence Colors (Similar Faces):
- 🟢 80%+ = Very High (Almost Certain)
- 🟡 70%+ = High (Likely Match)
- 🟠 60%+ = Medium (Possible Match)
- 🔴 50%+ = Low (Questionable)
- ⚫ <50% = Very Low (Unlikely)
+ 80%+ — very high (almost certain)
+ 70%+ — high (likely match)
+ 60%+ — medium (possible match)
+ 50%+ — low (questionable)
+ <50% — very low (unlikely)
@@ -435,7 +456,7 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
Tag filtering lets you identify faces only from photos with specific tags
Click on face images to open the full photo in a new window
For videos, you can identify multiple people in the same video
-
Use the exclude (🚫) button to exclude unknown, unwanted faces, or low-quality faces from appearing in your identification workflow
+
Use the Exclude button to skip unknown, unwanted, or low-quality faces in your identification workflow
Excluded faces are hidden by default, but you can view them by checking "Include excluded faces" in the filters
@@ -471,7 +492,7 @@ function AutoMatchPageHelp({ onBack }: { onBack: () => void }) {
Automatic Matching:
Navigate to Auto-Match page
- Click "🚀 Run Auto-Match" button
+ Click Run Auto-Match
The system will automatically match unidentified faces to identified people based on:
Similarity higher than 85%
@@ -499,9 +520,9 @@ function AutoMatchPageHelp({ onBack }: { onBack: () => void }) {
Check boxes next to faces you want to identify to this person
- Use "☑️ Select All" or "☐ Clear All" to quickly select/deselect all matches
- Click "💾 Save changes for [Person Name]" button to save your selections
- Use "⏮️ Back" and "⏭️ Next" buttons (or Prev/Next in the person panel) to navigate between people
+ Use Select All or Clear All to quickly select or deselect matches
+ Click Save changes for [Person Name] to save your selections
+ Use Back and Next (or Prev/Next in the person panel) to move between people
Use the search box to find specific people by last name
@@ -597,8 +618,8 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
Use "Select all" to select all photos in current results
Use "Unselect All" to clear selection
Click on a photo path to open the photo in a new window
- Click the folder icon (📁) to open the photo's folder in your file manager
- Click the star icon (⭐) to toggle favorite status for individual photos
+ Click the folder icon to open the photo folder in your file manager
+ Click the star icon to toggle favorite status for individual photos
Click column headers to sort results
@@ -610,9 +631,9 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
Choose an action:
Tag selected photos: Opens a dialog to add or remove tags from all selected photos
- ⭐ (Star icon): Add selected photos to favorites (or remove if viewing favorites)
- 🗑 Delete selected: Permanently delete selected photos (requires permission, cannot be undone)
- ▶ Play photos: Opens all search results in a full-screen photo viewer
+ Add to favorites: Add selected photos to favorites (or remove when viewing favorites)
+ Delete selected: Permanently delete selected photos (requires permission; cannot be undone)
+ Play photos: Open all search results in a full-screen photo viewer
@@ -621,7 +642,7 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
Using the Photo Viewer:
- Click "▶ Play photos" button to open the full-screen viewer
+ Click Play photos to open the full-screen viewer
If you have photos selected, the viewer will start with the first selected photo
Use arrow keys or on-screen buttons to navigate between photos
Press Escape or click the X button to close the viewer
@@ -683,7 +704,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
Editing Person Information:
Select a person from the list
- Click the edit icon (✏️) next to their name
+ Click the edit icon next to their name
In the dialog, update any fields:
First Name (required)
@@ -735,7 +756,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
Deleting a Person:
Select a person from the list
- Click the delete icon (🗑️) next to their name
+ Click the delete icon next to their name
Read the warning message carefully - this will:
Unlink all faces from this person
@@ -745,7 +766,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
Click "Delete" to confirm, or "Cancel" to abort
- ⚠️ Warning: This action cannot be undone!
+ Warning: This action cannot be undone.
@@ -814,7 +835,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
Tag Individual Photo:
- Click the tag icon (🏷️) next to a photo
+ Click the tag icon next to a photo
In the dialog, select an existing tag from the dropdown or enter a new tag name
Click "Add" to add the tag
To remove tags: Check boxes next to tags you want to remove, then click "Remove selected tags"
@@ -822,7 +843,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
Tag All Photos in a Folder:
- Click the tag icon (🏷️) next to a folder name
+ Click the tag icon next to a folder name
In the dialog, select or enter a tag name
Click "Add" to tag all photos in that folder
@@ -843,7 +864,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
Identifying Faces from Photos:
Select one or more photos that have been processed and contain faces
- Click "👤 Identify Faces" button at the top
+ Click Identify Faces at the top
The Identify page will open in a new tab, showing only faces from the selected photos
Note: Only processed photos with unidentified faces will be included
@@ -921,7 +942,7 @@ function FacesMaintenancePageHelp({ onBack }: { onBack: () => void }) {
Click "Delete Selected" button
Review the confirmation dialog - it shows how many faces will be deleted
Click "Delete" to confirm, or "Cancel" to abort
- ⚠️ Warning: Deletion is permanent and cannot be undone!
+ Warning: Deletion is permanent and cannot be undone.
After deletion, the face list will automatically refresh
@@ -1115,7 +1136,7 @@ function UserReportedPhotosPageHelp({ onBack }: { onBack: () => void }) {
-
⚠️ Important Warnings
+
Important warnings
Removing photos is permanent! This deletes the photo file, all detected faces, encodings, and tags
Always review the full photo before making a removal decision
@@ -1163,7 +1184,7 @@ function UserTaggedPhotosPageHelp({ onBack }: { onBack: () => void }) {
Status Filtering: Filter by pending, approved, or denied status
Sorting: Sort by photo, tag, submitted by, submitted at, or status
Bulk Selection: Select all to approve or deny at once
- Database Cleanup: Delete approved/denied records from temporary danatase(admin only)
+ Database cleanup: Delete approved or denied records from the temporary database (admin only)
@@ -1355,7 +1376,7 @@ function UsersPageHelp({ onBack }: { onBack: () => void }) {
Features
Three Tabs: Backend Users, Frontend Users, and Manage Roles
- Backend Users: Manage users for the current system and thier access
+ Backend users: Manage users for the admin app and their access
Frontend Users: Manage users for the Photos Viewer web application (their login accounts)
Manage Roles: Configure permissions for different user roles for current system(admin only)
User Creation: Create new users with required information
diff --git a/admin-frontend/src/pages/PendingPhotos.tsx b/admin-frontend/src/pages/PendingPhotos.tsx
index 3294b61..2f8975b 100644
--- a/admin-frontend/src/pages/PendingPhotos.tsx
+++ b/admin-frontend/src/pages/PendingPhotos.tsx
@@ -349,11 +349,11 @@ export default function PendingPhotos() {
try {
const response: CleanupResponse = await pendingPhotosApi.cleanupFiles(statusFilter)
const message = [
- `✅ Deleted ${response.deleted_files} file(s) from shared space`,
+ `Deleted ${response.deleted_files} file(s) from shared space`,
response.warnings && response.warnings.length > 0
- ? `ℹ️ ${response.warnings.length} file(s) were already deleted`
+ ? `${response.warnings.length} file(s) were already deleted`
: '',
- response.errors.length > 0 ? `⚠️ Errors: ${response.errors.length}` : '',
+ response.errors.length > 0 ? `Errors: ${response.errors.length}` : '',
]
.filter(Boolean)
.join('\n')
@@ -388,12 +388,12 @@ export default function PendingPhotos() {
try {
const response: CleanupResponse = await pendingPhotosApi.cleanupDatabase(statusFilter)
const message = [
- `✅ Deleted ${response.deleted_records} record(s) from database`,
+ `Deleted ${response.deleted_records} record(s) from database`,
response.warnings && response.warnings.length > 0
- ? `ℹ️ ${response.warnings.join(', ')}`
+ ? response.warnings.join(', ')
: '',
response.errors.length > 0
- ? `⚠️ Errors: ${response.errors.join('; ')}`
+ ? `Errors: ${response.errors.join('; ')}`
: '',
]
.filter(Boolean)
diff --git a/admin-frontend/src/pages/ReportedPhotos.tsx b/admin-frontend/src/pages/ReportedPhotos.tsx
index 09e1acc..172d528 100644
--- a/admin-frontend/src/pages/ReportedPhotos.tsx
+++ b/admin-frontend/src/pages/ReportedPhotos.tsx
@@ -157,12 +157,12 @@ export default function ReportedPhotos() {
})
const messageParts = [
- `✅ Kept: ${response.kept}`,
- `❌ Removed: ${response.removed}`,
+ `Kept: ${response.kept}`,
+ `Removed: ${response.removed}`,
]
if (import.meta.env.DEV && response.errors.length > 0) {
- messageParts.push(`⚠️ Errors: ${response.errors.length}`)
+ messageParts.push(`Errors: ${response.errors.length}`)
}
const message = messageParts.join('\n')
@@ -204,11 +204,11 @@ export default function ReportedPhotos() {
try {
const response = await reportedPhotosApi.cleanupReportedPhotos()
const summary = [
- `✅ Deleted ${response.deleted_records} record(s)`,
+ `Deleted ${response.deleted_records} record(s)`,
response.warnings && response.warnings.length > 0
- ? `ℹ️ ${response.warnings.join('; ')}`
+ ? response.warnings.join('; ')
: '',
- response.errors.length > 0 ? `⚠️ ${response.errors.join('; ')}` : '',
+ response.errors.length > 0 ? response.errors.join('; ') : '',
]
.filter(Boolean)
.join('\n')
diff --git a/admin-frontend/src/pages/UserTaggedPhotos.tsx b/admin-frontend/src/pages/UserTaggedPhotos.tsx
index e7616e4..a7e99df 100644
--- a/admin-frontend/src/pages/UserTaggedPhotos.tsx
+++ b/admin-frontend/src/pages/UserTaggedPhotos.tsx
@@ -262,11 +262,11 @@ export default function UserTaggedPhotos() {
try {
const response = await pendingLinkagesApi.cleanupPendingLinkages()
const summary = [
- `✅ Deleted ${response.deleted_records} record(s)`,
+ `Deleted ${response.deleted_records} record(s)`,
response.warnings && response.warnings.length > 0
- ? `ℹ️ ${response.warnings.join('; ')}`
+ ? response.warnings.join('; ')
: '',
- response.errors.length > 0 ? `⚠️ ${response.errors.join('; ')}` : '',
+ response.errors.length > 0 ? response.errors.join('; ') : '',
]
.filter(Boolean)
.join('\n')