@@ -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 (
< button
type = "button"
onClick = { ( ) = > onPageClick ( item . id ) }
className = "flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
>
< Icon className = "h-5 w-5 shrink-0 text-muted-foreground" aria-hidden = "true" / >
< div className = "flex-1" >
< span className = "font-semibold text-foreground" > { item . label } < / span >
< p className = "text-sm text-muted-foreground mt-1" > { item . description } < / p >
< / div >
< ChevronRight className = "h-4 w-4 shrink-0 text-muted-foreground" aria-hidden = "true" / >
< / button >
)
}
function NavigationOverview ( { onPageClick } : { onPageClick : ( page : PageId ) = > void } ) {
const mainNavItems = [
{ id : 'scan' as PageId , icon : '🗂️' , label : 'Scan' , description : 'Import photos from folders or upload file s' } ,
{ 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 face s' } ,
{ 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 P hotos' , description : 'Search and filter photos ' } ,
{ id : 'modify' as PageId , icon : '✏️' , label : 'Modify People ' , description : 'Edit person inform ation' } ,
{ id : 'tags' as PageId , icon : '🏷️' , label : 'Tag P hotos' , description : 'Tag photos and manag e photo tags' } ,
const mainNavItems : HelpNavItem [ ] = [
{ id : 'scan' , icon : FolderInput , label : 'Scan' , description : 'Import photos from local folders or network path s' } ,
{ 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 video s' } ,
{ id : 'auto-match' , icon : Bot , label : 'Auto-Match' , description : 'Review suggested matches to known people ' } ,
{ id : 'search' , icon : Search , label : 'Search p hotos' , description : 'Browse by person, date, tags, or folder ' } ,
{ id : 'modify' , icon : Pencil , label : 'Modify' , description : 'Edit person details and fix identific ations ' } ,
{ id : 'tags' , icon : Tags , label : 'Tag p hotos' , description : 'Organiz e 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 I dentified F aces' , description : 'Approve or deny face identifications made by users (under Maintenance tab) ' } ,
{ id : 'user-reported-photos' as PageId , icon : '🚨' , label : 'User Reported P hotos' , description : 'Review and manage photos reported by users (under Maintenance tab) ' } ,
{ id : 'user-tagged-photos' as PageId , icon : '🏷️' , label : 'User T agged P hotos' , description : 'Approve or deny tags suggested by users (under Maintenance tab) ' } ,
{ id : 'user-uploaded-photos' as PageId , icon : '📤' , label : 'User U ploaded Photo s' , 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-i dentified f aces' , description : 'Approve or deny identifications submitted by viewer users ' } ,
{ id : 'user-reported-photos' , icon : Flag , label : 'Reported p hotos' , description : 'Review photos flagged by viewer users ' } ,
{ id : 'user-tagged-photos' , icon : Tag , label : 'User-t agged p hotos' , description : 'Approve or deny tag suggestions from viewer users ' } ,
{ id : 'user-uploaded-photos' , icon : Upload , label : 'User u ploads' , description : 'Approve or reject uploads from viewer users ' } ,
{ id : 'users' , icon : Users , label : 'Users' , description : 'Manage admin accounts, viewer accounts, and roles ' } ,
]
return (
< div className = "bg-card rounded-lg shadow p-6" >
< h2 className = "text-2xl font-bold text-foreground mb-4" > Navigation Overview < / h2 >
< h2 className = "text-2xl font-bold text-foreground mb-4" > Navigation overview < / h2 >
< p className = "text-foreground/80 mb-6" >
The application uses a < strong > left sidebar navigation < / strong > 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 .
< / p >
< div className = "mb-6" >
< h3 className = "text-lg font-semibold text-foreground mb-3" > Main Pages < / h3 >
< h3 className = "text-lg font-semibold text-foreground mb-3" > Main pages < / h3 >
< div className = "grid grid-cols-1 gap-3" >
{ mainNavItems . map ( ( item ) = > (
< button
key = { item . id }
onClick = { ( ) = > onPageClick ( item . id ) }
className = "flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
>
< span className = "text-2xl" > { item . icon } < / span >
< div className = "flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-semibold text-foreground" > { item . label } < / span >
< / div >
< p className = "text-sm text-muted-foreground mt-1" > { item . description } < / p >
< / div >
< span className = "text-muted-foreground" > → < / span >
< / button >
< HelpNavButton key = { item . id } item = { item } onPageClick = { onPageClick } / >
) ) }
< / div >
< / div >
< div >
< h3 className = "text-lg font-semibold text-foreground mb-3" > Maintenance Tab < / h3 >
< p className = "text-sm text-muted-foreground mb-3" > These pages are located under the < strong > Maintenance < / strong > tab in the navigation sidebar : < / p >
< h3 className = "text-lg font-semibold text-foreground mb-3" > Maintenance < / h3 >
< p className = "text-sm text-muted-foreground mb-3" >
These pages live under the Maintenance section in the sidebar .
< / p >
< div className = "grid grid-cols-1 gap-3" >
{ maintenanceNavItems . map ( ( item ) = > (
< button
key = { item . id }
onClick = { ( ) = > onPageClick ( item . id ) }
className = "flex items-center gap-4 p-4 hover:bg-muted/40 rounded-lg border border-border text-left transition-colors"
>
< span className = "text-2xl" > { item . icon } < / span >
< div className = "flex-1" >
< div className = "flex items-center gap-2" >
< span className = "font-semibold text-foreground" > { item . label } < / span >
< / div >
< p className = "text-sm text-muted-foreground mt-1" > { item . description } < / p >
< / div >
< span className = "text-muted-foreground" > → < / span >
< / button >
< HelpNavButton key = { item . id } item = { item } onPageClick = { onPageClick } / >
) ) }
< / div >
< / div >
@@ -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 (
< div className = "bg-card rounded-lg shadow p-6" >
< div className = "flex items-center gap-4 mb-6" >
< button
type = "button"
onClick = { onBack }
className = "px-4 py-2 bg-muted text-foreground/80 rounded hover:bg-muted font-medium"
className = "inline-flex items-center gap-2 px-4 py-2 bg-muted text-foreground/80 rounded hover:bg-muted font-medium"
>
← Back
< ArrowLeft className = "h-4 w-4" aria-hidden = "true" / >
Back
< / button >
< h2 className = "text-2xl font-bold text-foreground" > { title } < / h2 >
< / div >
@@ -352,10 +373,10 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
< li > View the current face on the left panel < / li >
< li > To exclude a face from identification :
< ul className = "list-disc list-inside ml-4 mt-1" >
< li > Click the 🚫 button next to the face counter < / li >
< li > Click the Exclude button next to the face counter < / li >
< li > The face will be marked as excluded and automatically skipped < / li >
< li > Excluded faces are hidden by default ( check "Include excluded faces" in filters to see them ) < / li >
< li > Click the 🚫 button again to include the face back in identification < / li >
< li > Click Exclude again to include the face back in identification < / li >
< / ul >
< / li >
< li > Choose how to identify :
@@ -379,11 +400,11 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
< div className = "mb-4" >
< h4 className = "text-md font-semibold text-foreground/80 mb-2" > Confidence Colors ( Similar Faces ) : < / h4 >
< ul className = "list-disc list-inside space-y-1 text-muted-foreground ml-4" >
< li > 🟢 < strong > 80 % + < / strong > = Very High ( Almost Certain ) < / li >
< li > 🟡 < strong > 70 % + < / strong > = High ( Likely Match ) < / li >
< li > 🟠 < strong > 60 % + < / strong > = Medium ( Possible Match ) < / li >
< li > 🔴 < strong > 50 % + < / strong > = Low ( Questionable ) < / li >
< li > ⚫ < strong > & lt ; 50 % < / strong > = Very Low ( Unlikely ) < / li >
< li > < strong > 80 % + < / strong > — very high ( almost certain ) < / li >
< li > < strong > 70 % + < / strong > — high ( likely match ) < / li >
< li > < strong > 60 % + < / strong > — medium ( possible match ) < / li >
< li > < strong > 50 % + < / strong > — low ( questionable ) < / li >
< li > < strong > & lt ; 50 % < / strong > — very low ( unlikely ) < / li >
< / ul >
< / div >
< / div >
@@ -435,7 +456,7 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
<li>Tag filtering lets you identify faces only from photos with specific tags</li>
<li>Click on face images to open the full photo in a new window</li>
<li>For videos, you can identify multiple people in the same video</li>
<li>Use the e xclude (🚫) button to exclude unknown, unwanted faces , or low-quality faces from appearing in your identification workflow</li>
<li>Use the E xclude button to skip unknown, unwanted, or low-quality faces in your identification workflow</li>
<li>Excluded faces are hidden by default, but you can view them by checking "Include excluded faces" in the filters</li>
</ul>
@@ -471,7 +492,7 @@ function AutoMatchPageHelp({ onBack }: { onBack: () => void }) {
<p className="text-foreground/80 font-medium mb-2">Automatic Matching:</p>
<ol className="list-decimal list-inside space-y-1 text-muted-foreground ml-4">
<li>Navigate to Auto-Match page</li>
<li>Click "🚀 Run Auto-Match" button </li>
<li>Click Run Auto-Match</li>
<li>The system will automatically match unidentified faces to identified people based on:
<ul className="list-disc list-inside ml-4 mt-1">
<li>Similarity higher than 85%</li>
@@ -499,9 +520,9 @@ function AutoMatchPageHelp({ onBack }: { onBack: () => void }) {
<li>Check boxes next to faces you want to identify to this person</li>
</ul>
</li>
<li>Use "☑️ Select All" or "☐ Clear All" to quickly select/ deselect all matches</li>
<li>Click "💾 Save changes for [Person Name]" button to save your selections</li>
<li>Use "⏮️ Back" and "⏭️ Next" buttons (or Prev/Next in the person panel) to navigat e between people</li>
<li>Use Select All or Clear All to quickly select or deselect matches</li>
<li>Click Save changes for [Person Name] to save your selections</li>
<li>Use Back and Next (or Prev/Next in the person panel) to mov e between people</li>
<li>Use the search box to find specific people by last name</li>
</ol>
</div>
@@ -597,8 +618,8 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
< li > Use "Select all" to select all photos in current results < / li >
< li > Use "Unselect All" to clear selection < / li >
< li > Click on a photo path to open the photo in a new window < / li >
< li > Click the folder icon ( 📁 ) to open the photo 's folder in your file manager</li>
<li>Click the star icon (⭐) to toggle favorite status for individual photos</li>
< li > Click the folder icon to open the photo folder in your file manager < / li >
< li > Click the star icon to toggle favorite status for individual photos < / li >
< li > Click column headers to sort results < / li >
< / ol >
< / div >
@@ -610,9 +631,9 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
< li > Choose an action :
< ul className = "list-disc list-inside ml-4 mt-1" >
< li > < strong > Tag selected photos : < / strong > Opens a dialog to add or remove tags from all selected photos < / li >
<li><strong>⭐ (Star icon):</strong> Add selected photos to favorites (or remove if viewing favorites)</li>
<li><strong>🗑 Delete selected:</strong> Permanently delete selected photos (requires permission, cannot be undone)</li>
<li><strong>▶ Play photos:</strong> Opens all search results in a full-screen photo viewer</li>
< li > < strong > Add to favorites : < / strong > Add selected photos to favorites ( or remove when viewing favorites ) < / li >
< li > < strong > Delete selected : < / strong > Permanently delete selected photos ( requires permission ; cannot be undone ) < / li >
< li > < strong > Play photos : < / strong > Open all search results in a full - screen photo viewer < / li >
< / ul >
< / li >
< / ol >
@@ -621,7 +642,7 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) {
< div className = "mb-3" >
< p className = "text-foreground/80 font-medium mb-2" > Using the Photo Viewer : < / p >
< ol className = "list-decimal list-inside space-y-1 text-muted-foreground ml-4" >
<li>Click "▶ Play photos" button to open the full-screen viewer</li>
< li > Click Play photos to open the full - screen viewer < / li >
< li > If you have photos selected , the viewer will start with the first selected photo < / li >
< li > Use arrow keys or on - screen buttons to navigate between photos < / li >
< li > Press Escape or click the X button to close the viewer < / li >
@@ -683,7 +704,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
<p className="text-foreground/80 font-medium mb-2">Editing Person Information:</p>
<ol className="list-decimal list-inside space-y-1 text-muted-foreground ml-4">
<li>Select a person from the list</li>
< li > Click the edit icon ( ✏ ️ ) next to their name < / li >
<li>Click the edit icon next to their name</li>
<li>In the dialog, update any fields:
<ul className="list-disc list-inside ml-4 mt-1">
<li>First Name (required)</li>
@@ -735,7 +756,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
<p className="text-foreground/80 font-medium mb-2">Deleting a Person:</p>
<ol className="list-decimal list-inside space-y-1 text-muted-foreground ml-4">
<li>Select a person from the list</li>
< li > Click the delete icon ( 🗑 ️ ) next to their name < / li >
<li>Click the delete icon next to their name</li>
<li>Read the warning message carefully - this will:
<ul className="list-disc list-inside ml-4 mt-1">
<li>Unlink all faces from this person</li>
@@ -745,7 +766,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) {
</ul>
</li>
<li>Click "Delete" to confirm, or "Cancel" to abort</li>
< li > ⚠ ️ < strong > Warning : < / strong > This action cannot be undone ! < / li >
<li><strong>Warning:</strong> This action cannot be undone.</li>
</ol>
</div>
</div>
@@ -814,7 +835,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
< ol className = "list-decimal list-inside space-y-1 text-muted-foreground ml-4" >
< li > < strong > Tag Individual Photo : < / strong >
< ul className = "list-disc list-inside ml-4 mt-1" >
<li>Click the tag icon (🏷️) next to a photo</li>
< li > Click the tag icon next to a photo < / li >
< li > In the dialog , select an existing tag from the dropdown or enter a new tag name < / li >
< li > Click "Add" to add the tag < / li >
< li > To remove tags : Check boxes next to tags you want to remove , then click "Remove selected tags" < / li >
@@ -822,7 +843,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
< / li >
< li > < strong > Tag All Photos in a Folder : < / strong >
< ul className = "list-disc list-inside ml-4 mt-1" >
<li>Click the tag icon (🏷️) next to a folder name</li>
< li > Click the tag icon next to a folder name < / li >
< li > In the dialog , select or enter a tag name < / li >
< li > Click "Add" to tag all photos in that folder < / li >
< / ul >
@@ -843,7 +864,7 @@ function TagsPageHelp({ onBack }: { onBack: () => void }) {
< p className = "text-foreground/80 font-medium mb-2" > Identifying Faces from Photos : < / p >
< ol className = "list-decimal list-inside space-y-1 text-muted-foreground ml-4" >
< li > Select one or more photos that have been processed and contain faces < / li >
<li>Click "👤 Identify Faces" button at the top</li>
< li > Click Identify Faces at the top < / li >
< li > The Identify page will open in a new tab , showing only faces from the selected photos < / li >
< li > Note : Only processed photos with unidentified faces will be included < / li >
< / ol >
@@ -921,7 +942,7 @@ function FacesMaintenancePageHelp({ onBack }: { onBack: () => void }) {
< li > Click "Delete Selected" button < / li >
< li > Review the confirmation dialog - it shows how many faces will be deleted < / li >
< li > Click "Delete" to confirm , or "Cancel" to abort < / li >
<li>⚠️ <strong>Warning:</strong> Deletion is permanent and cannot be undone!</li>
< li > < strong > Warning : < / strong > Deletion is permanent and cannot be undone . < / li >
< li > After deletion , the face list will automatically refresh < / li >
< / ol >
< / div >
@@ -1115,7 +1136,7 @@ function UserReportedPhotosPageHelp({ onBack }: { onBack: () => void }) {
< / ul >
< / div >
< div >
<h3 className= "text-lg font-semibold text-foreground mb-2">⚠️ Important Warnings</h3>
< h3 className = "text-lg font-semibold text-foreground mb-2" > Important warnings < / h3 >
< ul className = "list-disc list-inside space-y-1 text-muted-foreground ml-4" >
< li > < strong > Removing photos is permanent ! < / strong > This deletes the photo file , all detected faces , encodings , and tags < / li >
< li > Always review the full photo before making a removal decision < / li >
@@ -1163,7 +1184,7 @@ function UserTaggedPhotosPageHelp({ onBack }: { onBack: () => void }) {
< li > < strong > Status Filtering : < / strong > Filter by pending , approved , or denied status < / li >
< li > < strong > Sorting : < / strong > Sort by photo , tag , submitted by , submitted at , or status < / li >
< li > < strong > Bulk Selection : < / strong > Select all to approve or deny at once < / li >
<li><strong>Database Cleanup:</strong> Delete approved/denied records from temporary danatase(admin only)</li>
< li > < strong > Database cleanup : < / strong > Delete approved or denied records from the temporary database ( admin only ) < / li >
< / ul >
< / div >
< div >
@@ -1355,7 +1376,7 @@ function UsersPageHelp({ onBack }: { onBack: () => void }) {
<h3 className="text-lg font-semibold text-foreground mb-2">Features</h3>
<ul className="list-disc list-inside space-y-1 text-muted-foreground ml-4">
<li><strong>Three Tabs:</strong> Backend Users, Frontend Users, and Manage Roles</li>
< li > < strong > Backend Users : < / strong > Manage users for the current system and thier access < / li >
<li><strong>Backend users:</strong> Manage users for the admin app and their access</li>
<li><strong>Frontend Users:</strong> Manage users for the Photos Viewer web application (their login accounts)</li>
<li><strong>Manage Roles:</strong> Configure permissions for different user roles for current system(admin only)</li>
<li><strong>User Creation:</strong> Create new users with required information</li>