Scrub admin Help and toast copy #108

Merged
ilia merged 1 commits from chore/admin-help-copy into master 2026-08-05 17:50:18 -05:00
7 changed files with 133 additions and 101 deletions
+10
View File
@@ -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",
+1
View File
@@ -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"
@@ -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')
+102 -81
View File
@@ -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 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 (
<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 exclude (🚫) button to exclude unknown, unwanted faces, or low-quality faces from appearing in your identification workflow</li>
<li>Use the Exclude 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 navigate 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 move 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>
+6 -6
View File
@@ -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)
+6 -6
View File
@@ -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')
@@ -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')