From 6af4fc1608fe64010cd234092cc7b6ffcf631591 Mon Sep 17 00:00:00 2001
From: Tanuja Pansare
Date: Tue, 4 Aug 2026 17:05:02 +0530
Subject: [PATCH 1/3] Add event photo search
---
.../components/dashboard/EventHomeModules.tsx | 38 +-
.../panels/EventPhotoSearchPanel.tsx | 590 ++++++++++++++++++
src/services/api/eventPhoto.ts | 58 ++
src/services/api/index.ts | 1 +
4 files changed, 683 insertions(+), 4 deletions(-)
create mode 100644 src/app/components/panels/EventPhotoSearchPanel.tsx
create mode 100644 src/services/api/eventPhoto.ts
diff --git a/src/app/components/dashboard/EventHomeModules.tsx b/src/app/components/dashboard/EventHomeModules.tsx
index c0886b2d..48de1773 100644
--- a/src/app/components/dashboard/EventHomeModules.tsx
+++ b/src/app/components/dashboard/EventHomeModules.tsx
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from "react";
-import { BarChart2, CalendarDays, Check, FileText, Globe, Link2, Mail, Plus, Share2, ShieldCheck, Tag, Ticket, Trophy, Upload } from "lucide-react";
+import { BarChart2, CalendarDays, Check, FileText, Globe, Image, Link2, Mail, Plus, Share2, ShieldCheck, Tag, Ticket, Trophy, Upload } from "lucide-react";
import { useBreakpoint } from "../../hooks/useBreakpoint";
import { useAppNav } from "../../contexts/AppNavContext";
import { useAuth } from "../../contexts/AuthContext";
@@ -19,6 +19,7 @@ import { ShareFormPanel } from "../forms/ShareFormPanel";
import { CreateBadgePanel } from "../panels/CreateBadgePanel";
import { EventBadgesPanel } from "./EventBadgesPanel";
import { EventDocumentsPanel } from "./EventDocumentsPanel";
+import { EventPhotoSearchPanel } from "../panels/EventPhotoSearchPanel";
import { EventEmailCampaignsPanel } from "./EventEmailCampaignsPanel";
import { AddQuizPollModal, buildQuizPollSettingsPayload } from "../quiz/AddQuizPollModal";
import type { QuizPollModalForm } from "../quiz/AddQuizPollModal";
@@ -262,7 +263,8 @@ export function EventHomeModules() {
const [eventSettings, setEventSettings] = useState>({});
const [webCheckInEnabled, setWebCheckInEnabled] = useState(false);
const [webCheckInLoading, setWebCheckInLoading] = useState(false);
- const [showDocumentsPanel, setShowDocumentsPanel] = useState(false);
+const [showDocumentsPanel, setShowDocumentsPanel] = useState(false);
+ const [showEventPhotoSearchPanel, setShowEventPhotoSearchPanel] = useState(false);
const [emailCampaignCount, setEmailCampaignCount] = useState(0);
const [emailCampaignLoading, setEmailCampaignLoading] = useState(false);
const [showCreateEmailCampaign, setShowCreateEmailCampaign] = useState(false);
@@ -1097,6 +1099,25 @@ export function EventHomeModules() {
}
/>
) : null}
+ }
+ title="Event Photo Search"
+ description="Upload event photos and search by image"
+ actions={
+
+ }
+ />
{canReadEmailCampaigns ? (
}
@@ -1235,7 +1256,7 @@ export function EventHomeModules() {
/>
) : null}
- {showDocumentsPanel && selectedEventId ? (
+{showDocumentsPanel && selectedEventId ? (
) : null}
- {showEmailCampaignsPanel && selectedEventId ? (
+ {showEventPhotoSearchPanel && selectedEventId ? (
+ setShowEventPhotoSearchPanel(false)}
+ />
+ ) : null}
+
+{showEmailCampaignsPanel && selectedEventId ? (
void;
+};
+
+function formatWhen(iso?: string) {
+ if (!iso) return '—';
+ try {
+ return new Date(iso).toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
+ } catch {
+ return '—';
+ }
+}
+
+export function EventPhotoSearchPanel({ open, eventId, eventName, onClose }: Props) {
+ const [loadingPhotos, setLoadingPhotos] = useState(false);
+ const [photos, setPhotos] = useState([]);
+ const [err, setErr] = useState(null);
+
+
+ const [files, setFiles] = useState([]);
+ const [uploading, setUploading] = useState(false);
+ const fileInputRef = useRef(null);
+
+ const [queryFile, setQueryFile] = useState(null);
+ const queryFileInputRef = useRef(null);
+ const [searchResults, setSearchResults] = useState(null);
+ const [searching, setSearching] = useState(false);
+
+ const [deletedMsg, setDeletedMsg] = useState(null);
+
+ const loadPhotos = async () => {
+ setLoadingPhotos(true);
+ setErr(null);
+ try {
+ const r = await eventPhotoApi.listEventPhotos(eventId);
+ setPhotos(r.data?.photos ?? []);
+ } catch (e) {
+ setErr(e instanceof Error ? e.message : 'Failed to load photos');
+ setPhotos([]);
+ } finally {
+ setLoadingPhotos(false);
+ }
+ };
+
+ useEffect(() => {
+ if (!open) return;
+ void loadPhotos();
+ setSearchResults(null);
+ setQueryFile(null);
+ setFiles([]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [open, eventId]);
+
+ const onUpload = async () => {
+ if (files.length === 0) return;
+ setUploading(true);
+ setErr(null);
+ try {
+ const res = await eventPhotoApi.uploadEventPhoto(eventId, files);
+ if (!res.ok) {
+ let msg = res.statusText;
+ try {
+ const j = await res.json();
+ msg = j?.error?.message || msg;
+ } catch {}
+ throw new Error(msg);
+ }
+ setFiles([]);
+
+ if (fileInputRef.current) {
+ fileInputRef.current.value = "";
+ }
+ await loadPhotos();
+ } catch (e) {
+ setErr(e instanceof Error ? e.message : 'Upload failed');
+ } finally {
+ setUploading(false);
+ }
+ };
+
+ const onDelete = async (photo: EventPhotoRow) => {
+ const ok = window.confirm(`Delete "${photo.originalFileName}"?`);
+ if (!ok) return;
+
+ setErr(null);
+ try {
+ const res = await eventPhotoApi.deleteEventPhoto(eventId, photo._id);
+ if (!res.ok) {
+ let msg = res.statusText;
+ try {
+ const j = await res.json();
+ msg = j?.error?.message || msg;
+ } catch {}
+ throw new Error(msg);
+ }
+ setDeletedMsg('Photo deleted.');
+ await loadPhotos();
+ setTimeout(() => setDeletedMsg(null), 1800);
+ } catch (e) {
+ setErr(e instanceof Error ? e.message : 'Delete failed');
+ }
+ };
+
+ const onFindPhotos = async () => {
+ if (!queryFile) return;
+ setSearching(true);
+ setErr(null);
+ // For now, just show a placeholder — backend search integration to be added later.
+ // This simulates a search by showing all photos (no actual vector search yet).
+ try {
+ const r = await eventPhotoApi.searchEventPhotos(
+ eventId,
+ queryFile
+ );
+
+ if (!r.ok) {
+ throw new Error("Search failed");
+ }
+
+ const data = await r.json();
+
+console.log(data);
+
+setSearchResults(data.data.matches ?? []);
+
+// setSearchResults(data.matches ?? []);
+ } catch (e) {
+ setErr(e instanceof Error ? e.message : 'Search failed');
+ setSearchResults(null);
+ } finally {
+ setSearching(false);
+ }
+ };
+
+ if (!open) return null;
+
+ const displayPhotos = searchResults !== null ? searchResults : photos;
+
+ return (
+ <>
+
+
+
+
+
+ {/* Header */}
+
+
+
+ Event Photo Search
+
+
+ {eventName || 'Photos'}
+
+
+ Upload event photos
+
+
+
+
+
+
+ {/* Upload Section */}
+
+
+
+
+ {/*
setDocId(e.target.value)}
+ style={{
+ width: '100%',
+ padding: '8px 10px',
+ border: '1px solid var(--border)',
+ borderRadius: 'var(--radius-button)',
+ background: 'var(--card)',
+ color: 'var(--foreground)',
+ fontSize: 'var(--text-sm)',
+ fontFamily: 'var(--font-noto-sans)',
+ outline: 'none',
+ marginBottom: 8,
+ boxSizing: 'border-box',
+ }}
+ /> */}
+
+
+
+ {files.length === 0 ? (
+ "No files selected"
+ ) : (
+ files.map(f => f.name).join(", ")
+ )}
+
+
+
{
+ const selected = Array.from(e.target.files || []);
+ setFiles(selected);
+ }}
+ />
+
+ Supported: JPEG, PNG, GIF, WEBP, BMP
+
+
+
+
+
+
+ {/* Find Photos Section */}
+
+
+
+
+
+ Find Photos
+
+
+
{
+ const f = e.target.files?.[0] || null;
+ setQueryFile(f);
+ if (f) {
+ // Auto-trigger search when file is selected
+ setSearchResults(null);
+ }
+ }}
+ />
+ {!queryFile ? (
+
+ ) : (
+
+
+ {queryFile.name}
+
+
+
+
+ )}
+
+ {searchResults !== null && (
+
+ Showing {searchResults.length} result{searchResults.length === 1 ? '' : 's'}
+
+
+ )}
+
+
+ {/* Photo List */}
+
+ {err ? (
+
{err}
+ ) : null}
+ {deletedMsg ? (
+
{deletedMsg}
+ ) : null}
+
+ {loadingPhotos ? (
+
Loading…
+ ) : displayPhotos.length === 0 ? (
+
+
+
+
+ No photos
+
+
+ Upload the first photo for this event.
+
+
+
+ ) : (
+
+
+ {displayPhotos.map((p) => (
+
+
+ {p.url ? (
+

+ ) : (
+
+ )}
+
+
+
+ {p.originalFileName}
+
+
+ Document ID: {p._id}
+
+
+ {formatWhen(p.uploadedAt)}
+
+ {p.embedding_generated && (
+
+ Embedding Ready
+
+ )}
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+
+ >
+ );
+}
+
diff --git a/src/services/api/eventPhoto.ts b/src/services/api/eventPhoto.ts
new file mode 100644
index 00000000..d0ab5d50
--- /dev/null
+++ b/src/services/api/eventPhoto.ts
@@ -0,0 +1,58 @@
+import { api, fetchWithAuth, API_BASE_URL } from './client';
+
+export interface EventPhotoRow {
+ _id: string;
+ event: string;
+ organization: string;
+ uploadedBy?: string | null;
+
+ fileName: string;
+ originalFileName: string;
+
+ url: string;
+ s3Key: string;
+
+ mimeType?: string;
+ size?: number;
+
+ embedding_generated: boolean;
+ uploadedAt?: string;
+}
+
+export const eventPhotoApi = {
+ listEventPhotos(eventId: string) {
+ const path = `/events/${eventId}/photos`;
+ return api.get<{ photos: EventPhotoRow[] }>(path);
+ },
+
+ uploadEventPhoto(eventId: string, files: File[]) {
+ const fd = new FormData();
+
+ files.forEach(file => {
+ fd.append("files", file, file.name);
+ });
+
+ return fetchWithAuth(`/events/${eventId}/photos`, {
+ method: "POST",
+ body: fd,
+ });
+},
+
+searchEventPhotos(eventId: string, file: File) {
+ const fd = new FormData();
+
+ fd.append("files", file);
+
+ return fetchWithAuth(`/events/${eventId}/photos/search`, {
+ method: "POST",
+ body: fd,
+ });
+},
+
+ async deleteEventPhoto(eventId: string, photoId: string) {
+ return fetchWithAuth(`/events/${eventId}/photos/${photoId}`, {
+ method: 'DELETE',
+ });
+ },
+};
+
diff --git a/src/services/api/index.ts b/src/services/api/index.ts
index 50597c62..20944549 100644
--- a/src/services/api/index.ts
+++ b/src/services/api/index.ts
@@ -12,6 +12,7 @@ export { eventsApi } from './events';
export { rewardPointsApi, type LeaderboardRow, type CreateRewardPointPayload } from './rewardPoints';
export { participantsApi, type ParticipantWithEvent } from './participants';
export { participantDocumentsApi, type ParticipantDocumentRow } from './participantDocuments';
+export { eventPhotoApi, type EventPhotoRow } from './eventPhoto';
export {
attendancesApi,
From 05518813656c79c8c69c4e9896f15dd72267d6da Mon Sep 17 00:00:00 2001
From: Tanuja Pansare
Date: Wed, 5 Aug 2026 09:55:52 +0530
Subject: [PATCH 2/3] Add event photo search changes
---
src/app/components/panels/EventPhotoSearchPanel.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/app/components/panels/EventPhotoSearchPanel.tsx b/src/app/components/panels/EventPhotoSearchPanel.tsx
index 2763bfdd..70b190a3 100644
--- a/src/app/components/panels/EventPhotoSearchPanel.tsx
+++ b/src/app/components/panels/EventPhotoSearchPanel.tsx
@@ -519,13 +519,13 @@ setSearchResults(data.data.matches ?? []);
{p.originalFileName}
-
+ {/*
Document ID: {p._id}
-
+
*/}
{formatWhen(p.uploadedAt)}
- {p.embedding_generated && (
+ {/* {p.embedding_generated && (
Embedding Ready
- )}
+ )} */}