From 0491db9c3661d8a0a7f61d81290e69d168ad91d3 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 02:24:21 -0500 Subject: [PATCH 1/7] ignore ios/pods --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index d25f7cd..9cdf800 100644 --- a/.gitignore +++ b/.gitignore @@ -290,3 +290,5 @@ teamd.config.*.map teamd.config.d.mts *.gen.ts .tanstack/ +src/mobile/ios/Pods/hermes-engine/destroot/Library/Frameworks/macosx/hermes.framework/Versions/Current +src/mobile/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework/ios-arm64_x86_64-maccatalyst/hermes.framework/Versions/Current From 3b883f9a38c465154ed981a5bc7398f4a0184559 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 02:27:07 -0500 Subject: [PATCH 2/7] fixed cancel sign up button --- src/frontend/mobile/app/_lib/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend/mobile/app/_lib/api.ts b/src/frontend/mobile/app/_lib/api.ts index 32705dd..e1fce34 100644 --- a/src/frontend/mobile/app/_lib/api.ts +++ b/src/frontend/mobile/app/_lib/api.ts @@ -274,5 +274,6 @@ export function cancelSignup( ): Promise<{ cancelled?: boolean; error?: string }> { return apiFetch(`/events/${eventId}/cancel`, { method: 'POST', + body: JSON.stringify({}), }); } From 666f96c17bff1f4e690a7845c94f1b1997e8eb61 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 03:26:05 -0500 Subject: [PATCH 3/7] qr code generation and ticket --- src/backend/src/db/schema.ts | 1 + src/backend/src/db/seed-data.ts | 73 +++- src/backend/src/events/events.service.ts | 36 +- src/frontend/mobile/app/(tabs)/_layout.tsx | 4 +- src/frontend/mobile/app/(tabs)/index.tsx | 41 +- src/frontend/mobile/app/(tabs)/my-tickets.tsx | 335 +++++++++++++++ src/frontend/mobile/app/_layout.tsx | 7 + src/frontend/mobile/app/_lib/api.ts | 1 + src/frontend/mobile/app/ticket-detail.tsx | 233 +++++++++++ src/frontend/mobile/package-lock.json | 391 ++++++++++++++++++ src/frontend/mobile/package.json | 2 + src/frontend/web-admin/package-lock.json | 11 - 12 files changed, 1101 insertions(+), 34 deletions(-) create mode 100644 src/frontend/mobile/app/(tabs)/my-tickets.tsx create mode 100644 src/frontend/mobile/app/ticket-detail.tsx diff --git a/src/backend/src/db/schema.ts b/src/backend/src/db/schema.ts index e7a0b6f..1758da8 100644 --- a/src/backend/src/db/schema.ts +++ b/src/backend/src/db/schema.ts @@ -106,6 +106,7 @@ export const tickets = pgTable('tickets', { checkedIn: boolean('checked_in').default(false), busSeat: varchar('bus_seat', { length: 50 }), // e.g. "Bus 1 - Seat 5" (display) tableSeat: varchar('table_seat', { length: 50 }), // e.g. "Table 5, Seat 4" (display) + qrCodeData: varchar('qr_code_data', { length: 255 }), // QR code data for ticket verification createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow(), }); diff --git a/src/backend/src/db/seed-data.ts b/src/backend/src/db/seed-data.ts index 84af053..6293b2e 100644 --- a/src/backend/src/db/seed-data.ts +++ b/src/backend/src/db/seed-data.ts @@ -137,14 +137,71 @@ export async function runSeedDb(db: SeedDb): Promise { } if (busSeatRows.length > 0) await db.insert(busSeats).values(busSeatRows); - const [ticket1, ticket2, ticket3] = await db - .insert(tickets) - .values([ - { userId: user1.id, eventId: event1.id }, - { userId: user2.id, eventId: event1.id }, - { userId: user3.id, eventId: event2.id }, - ]) - .returning(); + // Check if tickets already exist + const existingTickets = await db.select().from(tickets).limit(1); + let ticket1, ticket2, ticket3; + + if (existingTickets.length > 0) { + // Update existing tickets with QR codes + const allTickets = await db.select().from(tickets); + for (const ticket of allTickets) { + const qrData = `TICKET:${ticket.userId}:${ticket.eventId}:${ticket.id}:${Date.now()}`; + await db + .update(tickets) + .set({ qrCodeData: qrData, updatedAt: new Date() }) + .where(eq(tickets.id, ticket.id)); + } + // Get first 3 for seat assignment + [ticket1, ticket2, ticket3] = allTickets.slice(0, 3); + } else { + // Create new tickets with QR codes + const timestamp = Date.now(); + const [t1, t2, t3] = await db + .insert(tickets) + .values([ + { + userId: user1.id, + eventId: event1.id, + qrCodeData: `TICKET:${user1.id}:${event1.id}:temp1:${timestamp}` + }, + { + userId: user2.id, + eventId: event1.id, + qrCodeData: `TICKET:${user2.id}:${event1.id}:temp2:${timestamp}` + }, + { + userId: user3.id, + eventId: event2.id, + qrCodeData: `TICKET:${user3.id}:${event2.id}:temp3:${timestamp}` + }, + ]) + .returning(); + + // Update with actual ticket IDs + if (t1) { + await db + .update(tickets) + .set({ qrCodeData: `TICKET:${user1.id}:${event1.id}:${t1.id}:${timestamp}`, updatedAt: new Date() }) + .where(eq(tickets.id, t1.id)); + } + if (t2) { + await db + .update(tickets) + .set({ qrCodeData: `TICKET:${user2.id}:${event1.id}:${t2.id}:${timestamp}`, updatedAt: new Date() }) + .where(eq(tickets.id, t2.id)); + } + if (t3) { + await db + .update(tickets) + .set({ qrCodeData: `TICKET:${user3.id}:${event2.id}:${t3.id}:${timestamp}`, updatedAt: new Date() }) + .where(eq(tickets.id, t3.id)); + } + + ticket1 = t1; + ticket2 = t2; + ticket3 = t3; + } + if (!ticket1 || !ticket2 || !ticket3) throw new Error('Ticket insert failed'); const tableSeatToAssign = await db diff --git a/src/backend/src/events/events.service.ts b/src/backend/src/events/events.service.ts index 7d9ea5c..9e0e828 100644 --- a/src/backend/src/events/events.service.ts +++ b/src/backend/src/events/events.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import * as crypto from 'crypto'; import { DatabaseService } from '../database/database.service'; import { PaymentsService } from '../payments/payments.service'; import { @@ -23,6 +24,24 @@ export class EventsService { private readonly paymentsService: PaymentsService, ) {} + /** + * Generate secure QR code data for a ticket + */ + private generateQRCodeData( + ticketId: number, + userId: number, + eventId: number, + ): string { + const data = `${ticketId}:${userId}:${eventId}`; + const secret = + process.env.QR_SECRET || 'default-secret-change-in-production'; + const signature = crypto + .createHmac('sha256', secret) + .update(data) + .digest('hex'); + return `${data}:${signature}`; + } + async findAll() { const rows = await this.dbService.db .select({ @@ -71,7 +90,7 @@ export class EventsService { busCapacity: events.busCapacity, createdAt: events.createdAt, updatedAt: events.updatedAt, - registeredCount: sql`(SELECT COUNT(*) FROM tickets WHERE tickets.event_id = ${events.id})::int`, + registeredCount: sql`(SELECT COUNT(*)::int FROM tickets WHERE tickets.event_id = events.id)`, }) .from(events) .where(eq(events.id, id)); @@ -206,6 +225,7 @@ export class EventsService { checkedIn: tickets.checkedIn, busSeat: tickets.busSeat, tableSeat: tickets.tableSeat, + qrCodeData: tickets.qrCodeData, createdAt: tickets.createdAt, eventName: events.name, eventDate: events.date, @@ -398,6 +418,13 @@ export class EventsService { .returning(); const ticket = result[0]; + // Generate and update QR code data + const qrCodeData = this.generateQRCodeData(ticket.id, userId, eventId); + await this.dbService.db + .update(tickets) + .set({ qrCodeData, updatedAt: new Date() }) + .where(eq(tickets.id, ticket.id)); + let assignedTableSeat: string | null = null; let assignedBusSeat: string | null = null; @@ -548,6 +575,13 @@ export class EventsService { .returning(); const ticket = result[0]; + // Generate and update QR code data + const qrCodeData = this.generateQRCodeData(ticket.id, userId, eventId); + await this.dbService.db + .update(tickets) + .set({ qrCodeData, updatedAt: new Date() }) + .where(eq(tickets.id, ticket.id)); + let assignedTableSeat: string | null = null; let assignedBusSeat: string | null = null; diff --git a/src/frontend/mobile/app/(tabs)/_layout.tsx b/src/frontend/mobile/app/(tabs)/_layout.tsx index 39d4781..4181624 100644 --- a/src/frontend/mobile/app/(tabs)/_layout.tsx +++ b/src/frontend/mobile/app/(tabs)/_layout.tsx @@ -86,9 +86,9 @@ export default function TabLayout() { }} /> ( 🎟️ ), diff --git a/src/frontend/mobile/app/(tabs)/index.tsx b/src/frontend/mobile/app/(tabs)/index.tsx index 8045d68..2d5d470 100644 --- a/src/frontend/mobile/app/(tabs)/index.tsx +++ b/src/frontend/mobile/app/(tabs)/index.tsx @@ -142,14 +142,31 @@ function EventCard({ {/* Action button */} {isSignedUp ? ( - onCancel(event.id)} - className="w-full py-3 rounded-xl border border-red-200 bg-red-50 active:bg-red-100" - > - - Cancel Sign-Up - - + event.price === 0 ? ( + onCancel(event.id)} + className="w-full py-3 rounded-xl border border-red-200 bg-red-50 active:bg-red-100" + > + + Cancel Sign-Up + + + ) : ( + { + if (Platform.OS === 'web') { + alert('Refund request feature coming soon'); + } else { + Alert.alert('Coming Soon', 'Refund request feature will be available soon'); + } + }} + className="w-full py-3 rounded-xl border border-yellow-200 bg-yellow-50 active:bg-yellow-100" + > + + Request Refund + + + ) ) : ( onSignUp(event.id)} @@ -280,9 +297,9 @@ export default function EventsScreen() { await loadEvents(); await loadUserTickets(); if (Platform.OS === 'web') { - alert("Sign-up cancelled successfully."); + alert("Ticket cancelled successfully."); } else { - Alert.alert("Success", "Sign-up cancelled successfully."); + Alert.alert("Success", "Ticket cancelled successfully."); } } catch (err: any) { console.error("Cancel error:", err); @@ -394,10 +411,10 @@ export default function EventsScreen() { - Cancel Sign-Up + Cancel Ticket - Are you sure you want to cancel this sign-up? + Are you sure you want to cancel this ticket? ([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [showCancelModal, setShowCancelModal] = useState(false); + const [eventToCancel, setEventToCancel] = useState(null); + + const loadTickets = async () => { + if (!user) return; + try { + const data = await getUserTickets(user.id); + setTickets(data); + } catch (err) { + console.error("Failed to load tickets:", err); + } finally { + setLoading(false); + } + }; + + // Reload tickets every time this screen gets focus + useFocusEffect( + useCallback(() => { + if (user) { + setLoading(true); + loadTickets(); + } + }, [user]) + ); + + const onRefresh = async () => { + setRefreshing(true); + await loadTickets(); + setRefreshing(false); + }; + + const handleCancel = async (eventId: number) => { + if (!user) return; + console.log("Cancel clicked for event:", eventId); + setEventToCancel(eventId); + setShowCancelModal(true); + }; + + const confirmCancel = async () => { + if (!user || !eventToCancel) return; + console.log("Cancelling signup..."); + setShowCancelModal(false); + try { + const result = await cancelSignup(eventToCancel); + console.log("Cancel result:", result); + if (result.error) { + if (Platform.OS === 'web') { + alert(result.error); + } else { + Alert.alert("Error", result.error); + } + return; + } + await loadTickets(); + if (Platform.OS === 'web') { + alert("Ticket cancelled successfully."); + } else { + Alert.alert("Success", "Ticket cancelled successfully."); + } + } catch (err: any) { + console.error("Cancel error:", err); + if (Platform.OS === 'web') { + alert(err.message || "Failed to cancel"); + } else { + Alert.alert("Error", err.message || "Failed to cancel"); + } + } finally { + setEventToCancel(null); + } + }; + + const formatDate = (dateStr: string) => { + const d = new Date(dateStr); + return d.toLocaleDateString("en-US", { + month: "long", + day: "numeric", + year: "numeric", + }); + }; + + const formatPrice = (price: number) => { + if (price === 0) return "Free Event"; + return `$${(price / 100).toFixed(2)}`; + }; + + const formatTime = (dateStr: string) => { + const d = new Date(dateStr); + return d.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + }; + + const formatDateLong = (dateStr: string) => { + const d = new Date(dateStr); + return d.toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }); + }; + + if (status === "loading" || loading) { + return ( + + + + ); + } + + if (!user) { + return ( + + + Please sign in to view your tickets. + + + ); + } + + return ( + + + } + > + {/* Page header */} + + My Tickets + + Events you're registered for + + + + {tickets.length === 0 ? ( + + 🎟️ + + You haven't purchased any tickets yet. + + + ) : ( + + {tickets.map((ticket) => ( + + router.push({ + pathname: "/ticket-detail", + params: { ticketId: ticket.ticketId } + }) + } + className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden active:bg-gray-50" + > + + {/* Image */} + + {ticket.eventImageUrl ? ( + + ) : ( + + 🖼️ + + )} + + + {/* Content */} + + + + + {ticket.eventName} + + + View Ticket → + + + {ticket.eventPrice === 0 ? ( + { + e.stopPropagation(); + handleCancel(ticket.eventId); + }} + className="px-3 py-1.5 border border-red-200 rounded-lg bg-red-50 active:bg-red-100" + > + + Cancel + + + ) : ( + { + e.stopPropagation(); + // TODO: Implement refund request + if (Platform.OS === 'web') { + alert('Refund request feature coming soon'); + } else { + Alert.alert('Coming Soon', 'Refund request feature will be available soon'); + } + }} + className="px-3 py-1.5 border border-yellow-200 rounded-lg bg-yellow-50 active:bg-yellow-100" + > + + Request Refund + + + )} + + + + + 📅 + + {formatDate(ticket.eventDate)} + + + {ticket.eventLocation && ( + + 📍 + + {ticket.eventLocation} + + + )} + + 💰 + + {formatPrice(ticket.eventPrice)} + + + + + {/* Seat info */} + {(ticket.tableSeat || ticket.busSeat) && ( + + {ticket.tableSeat && ( + + 💺 + + {ticket.tableSeat} + + + )} + {ticket.busSeat && ( + + 🚌 + + {ticket.busSeat} + + + )} + + )} + + + + ))} + + )} + + + {/* Cancel Confirmation Modal */} + setShowCancelModal(false)} + > + + + + Cancel Ticket + + + Are you sure you want to cancel this ticket? + + + setShowCancelModal(false)} + className="flex-1 py-3 border border-gray-300 rounded-xl active:bg-gray-50" + > + + No + + + + + Yes, Cancel + + + + + + + + ); +} diff --git a/src/frontend/mobile/app/_layout.tsx b/src/frontend/mobile/app/_layout.tsx index a130fd3..1617622 100644 --- a/src/frontend/mobile/app/_layout.tsx +++ b/src/frontend/mobile/app/_layout.tsx @@ -34,6 +34,13 @@ function RootNavigator() { headerShown: false, }} /> + (); + const { user } = useAuth(); + const insets = useSafeAreaInsets(); + const [ticket, setTicket] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + loadTicket(); + }, [ticketId]); + + const loadTicket = async () => { + if (!user || !ticketId) return; + try { + const tickets = await getUserTickets(user.id); + const found = tickets.find((t) => t.ticketId === parseInt(ticketId)); + setTicket(found || null); + } catch (err) { + console.error("Failed to load ticket:", err); + } finally { + setLoading(false); + } + }; + + const formatDate = (dateStr: string) => { + const d = new Date(dateStr); + return d.toLocaleDateString("en-US", { + weekday: "long", + month: "long", + day: "numeric", + year: "numeric", + }); + }; + + const formatTime = (dateStr: string) => { + const d = new Date(dateStr); + return d.toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + }); + }; + + const formatPrice = (price: number) => { + if (price === 0) return "Free Event"; + return `$${(price / 100).toFixed(2)}`; + }; + + if (loading) { + return ( + + + + ); + } + + if (!ticket) { + return ( + + + router.back()}> + + ← Back to My Tickets + + + + + 🎟️ + + Ticket not found + + + + ); + } + + return ( + + {/* Header */} + + router.back()}> + + ← Back to My Tickets + + + + + + {/* Event Image */} + {ticket.eventImageUrl ? ( + + ) : ( + + 🎪 + + )} + + {/* Event Info Card */} + + + {ticket.eventName} + + + + + 📅 + + Date & Time + + {formatDate(ticket.eventDate)} + + + {formatTime(ticket.eventDate)} + + + + + {ticket.eventLocation && ( + + 📍 + + Location + + {ticket.eventLocation} + + + + )} + + + 💰 + + Price + + {formatPrice(ticket.eventPrice)} + + + + + {/* Seat info */} + {(ticket.tableSeat || ticket.busSeat) && ( + + 💺 + + Seat Assignment + + {ticket.tableSeat && ( + + + {ticket.tableSeat} + + + )} + {ticket.busSeat && ( + + + {ticket.busSeat} + + + )} + + + + )} + + + + {/* QR Code Card */} + + + Entry QR Code + + + Show this QR code at the event entrance + + + {ticket.qrCodeData ? ( + + + + ) : ( + + 🎟️ + + QR code will be generated shortly + + + )} + + + + 💡 Save a screenshot of this QR code in case you lose internet connection + + + + + {/* Ticket ID */} + + + Ticket ID: {ticket.ticketId} + + + + + ); +} diff --git a/src/frontend/mobile/package-lock.json b/src/frontend/mobile/package-lock.json index 29e911a..ea119d9 100644 --- a/src/frontend/mobile/package-lock.json +++ b/src/frontend/mobile/package-lock.json @@ -18,8 +18,10 @@ "nativewind": "^4.2.1", "react": "18.3.1", "react-native": "0.76.5", + "react-native-qrcode-svg": "^6.3.21", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", + "react-native-svg": "^15.15.3", "react-native-web": "~0.19.13", "tailwindcss": "^3.4.17" }, @@ -4678,6 +4680,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, "node_modules/bplist-creator": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.7.tgz", @@ -5456,6 +5464,56 @@ "hyphenate-style-name": "^1.0.3" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5492,6 +5550,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -5621,6 +5688,12 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5639,6 +5712,61 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -5722,6 +5850,18 @@ "once": "^1.4.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-editor": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", @@ -8211,6 +8351,12 @@ "node": ">=0.10" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -9009,6 +9155,18 @@ "node": ">=4" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -9799,6 +9957,23 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/qrcode-terminal": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.11.0.tgz", @@ -9807,6 +9982,165 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/qrcode/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qrcode/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -10105,6 +10439,22 @@ "react-native": "*" } }, + "node_modules/react-native-qrcode-svg": { + "version": "6.3.21", + "resolved": "https://registry.npmjs.org/react-native-qrcode-svg/-/react-native-qrcode-svg-6.3.21.tgz", + "integrity": "sha512-6vcj4rcdpWedvphDR+NSJcudJykNuLgNGFwm2p4xYjR8RdyTzlrELKI5LkO4ANS9cQUbqsfkpippPv64Q2tUtA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.0", + "qrcode": "^1.5.4", + "text-encoding": "^0.7.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.63.4", + "react-native-svg": ">=14.0.0" + } + }, "node_modules/react-native-reanimated": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", @@ -10159,6 +10509,22 @@ "react-native": "*" } }, + "node_modules/react-native-svg": { + "version": "15.15.3", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.3.tgz", + "integrity": "sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==", + "license": "MIT", + "peer": true, + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-web": { "version": "0.19.13", "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.19.13.tgz", @@ -10583,6 +10949,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/requireg": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/requireg/-/requireg-0.2.2.tgz", @@ -10925,6 +11297,12 @@ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", "license": "MIT" }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -11699,6 +12077,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-encoding": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", + "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==", + "deprecated": "no longer maintained", + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -12127,6 +12512,12 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/wonka": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", diff --git a/src/frontend/mobile/package.json b/src/frontend/mobile/package.json index 959e495..8c7ff6c 100644 --- a/src/frontend/mobile/package.json +++ b/src/frontend/mobile/package.json @@ -19,8 +19,10 @@ "nativewind": "^4.2.1", "react": "18.3.1", "react-native": "0.76.5", + "react-native-qrcode-svg": "^6.3.21", "react-native-safe-area-context": "4.12.0", "react-native-screens": "~4.4.0", + "react-native-svg": "^15.15.3", "react-native-web": "~0.19.13", "tailwindcss": "^3.4.17" }, diff --git a/src/frontend/web-admin/package-lock.json b/src/frontend/web-admin/package-lock.json index 35152cb..cbc2cd4 100644 --- a/src/frontend/web-admin/package-lock.json +++ b/src/frontend/web-admin/package-lock.json @@ -955,7 +955,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1016,7 +1015,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -1503,7 +1501,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2492,7 +2489,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2666,7 +2662,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -3852,7 +3847,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -4554,7 +4548,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4756,7 +4749,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4769,7 +4761,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5581,7 +5572,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5738,7 +5728,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From 53db405cece1f92a417c39430bf21210fb836b40 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 03:28:26 -0500 Subject: [PATCH 4/7] new tickets page, keeping temp signups --- src/frontend/mobile/app/(tabs)/my-signups.tsx | 538 +++++++++--------- 1 file changed, 269 insertions(+), 269 deletions(-) diff --git a/src/frontend/mobile/app/(tabs)/my-signups.tsx b/src/frontend/mobile/app/(tabs)/my-signups.tsx index e6bae0c..a66ed64 100644 --- a/src/frontend/mobile/app/(tabs)/my-signups.tsx +++ b/src/frontend/mobile/app/(tabs)/my-signups.tsx @@ -1,284 +1,284 @@ -import { useState, useCallback } from "react"; -import { - View, - Text, - ScrollView, - Pressable, - Image, - ActivityIndicator, - Alert, - RefreshControl, - Modal, - Platform, -} from "react-native"; -import { useFocusEffect } from "@react-navigation/native"; -import { getUserTickets, cancelSignup, type Ticket } from "../_lib/api"; -import { useAuth } from "../_context/AuthContext"; +// import { useState, useCallback } from "react"; +// import { +// View, +// Text, +// ScrollView, +// Pressable, +// Image, +// ActivityIndicator, +// Alert, +// RefreshControl, +// Modal, +// Platform, +// } from "react-native"; +// import { useFocusEffect } from "@react-navigation/native"; +// import { getUserTickets, cancelSignup, type Ticket } from "../_lib/api"; +// import { useAuth } from "../_context/AuthContext"; -export default function MySignUpsScreen() { - const { user, status } = useAuth(); - const [tickets, setTickets] = useState([]); - const [loading, setLoading] = useState(true); - const [refreshing, setRefreshing] = useState(false); - const [showCancelModal, setShowCancelModal] = useState(false); - const [eventToCancel, setEventToCancel] = useState(null); +// export default function MySignUpsScreen() { +// const { user, status } = useAuth(); +// const [tickets, setTickets] = useState([]); +// const [loading, setLoading] = useState(true); +// const [refreshing, setRefreshing] = useState(false); +// const [showCancelModal, setShowCancelModal] = useState(false); +// const [eventToCancel, setEventToCancel] = useState(null); - const loadTickets = async () => { - if (!user) return; - try { - const data = await getUserTickets(user.id); - setTickets(data); - } catch (err) { - console.error("Failed to load tickets:", err); - } finally { - setLoading(false); - } - }; +// const loadTickets = async () => { +// if (!user) return; +// try { +// const data = await getUserTickets(user.id); +// setTickets(data); +// } catch (err) { +// console.error("Failed to load tickets:", err); +// } finally { +// setLoading(false); +// } +// }; - // Reload tickets every time this screen gets focus - useFocusEffect( - useCallback(() => { - if (user) { - setLoading(true); - loadTickets(); - } - }, [user]) - ); +// // Reload tickets every time this screen gets focus +// useFocusEffect( +// useCallback(() => { +// if (user) { +// setLoading(true); +// loadTickets(); +// } +// }, [user]) +// ); - const onRefresh = async () => { - setRefreshing(true); - await loadTickets(); - setRefreshing(false); - }; +// const onRefresh = async () => { +// setRefreshing(true); +// await loadTickets(); +// setRefreshing(false); +// }; - const handleCancel = async (eventId: number) => { - if (!user) return; - console.log("Cancel clicked for event:", eventId); - setEventToCancel(eventId); - setShowCancelModal(true); - }; +// const handleCancel = async (eventId: number) => { +// if (!user) return; +// console.log("Cancel clicked for event:", eventId); +// setEventToCancel(eventId); +// setShowCancelModal(true); +// }; - const confirmCancel = async () => { - if (!user || !eventToCancel) return; - console.log("Cancelling signup..."); - setShowCancelModal(false); - try { - const result = await cancelSignup(eventToCancel); - console.log("Cancel result:", result); - if (result.error) { - if (Platform.OS === 'web') { - alert(result.error); - } else { - Alert.alert("Error", result.error); - } - return; - } - await loadTickets(); - if (Platform.OS === 'web') { - alert("Sign-up cancelled successfully."); - } else { - Alert.alert("Success", "Sign-up cancelled successfully."); - } - } catch (err: any) { - console.error("Cancel error:", err); - if (Platform.OS === 'web') { - alert(err.message || "Failed to cancel"); - } else { - Alert.alert("Error", err.message || "Failed to cancel"); - } - } finally { - setEventToCancel(null); - } - }; +// const confirmCancel = async () => { +// if (!user || !eventToCancel) return; +// console.log("Cancelling signup..."); +// setShowCancelModal(false); +// try { +// const result = await cancelSignup(eventToCancel); +// console.log("Cancel result:", result); +// if (result.error) { +// if (Platform.OS === 'web') { +// alert(result.error); +// } else { +// Alert.alert("Error", result.error); +// } +// return; +// } +// await loadTickets(); +// if (Platform.OS === 'web') { +// alert("Sign-up cancelled successfully."); +// } else { +// Alert.alert("Success", "Sign-up cancelled successfully."); +// } +// } catch (err: any) { +// console.error("Cancel error:", err); +// if (Platform.OS === 'web') { +// alert(err.message || "Failed to cancel"); +// } else { +// Alert.alert("Error", err.message || "Failed to cancel"); +// } +// } finally { +// setEventToCancel(null); +// } +// }; - const formatDate = (dateStr: string) => { - const d = new Date(dateStr); - return d.toLocaleDateString("en-US", { - month: "long", - day: "numeric", - year: "numeric", - }); - }; +// const formatDate = (dateStr: string) => { +// const d = new Date(dateStr); +// return d.toLocaleDateString("en-US", { +// month: "long", +// day: "numeric", +// year: "numeric", +// }); +// }; - const formatPrice = (price: number) => { - if (price === 0) return "Free"; - return `$${(price / 100).toFixed(2)}`; - }; +// const formatPrice = (price: number) => { +// if (price === 0) return "Free"; +// return `$${(price / 100).toFixed(2)}`; +// }; - if (status === "loading" || loading) { - return ( - - - - ); - } +// if (status === "loading" || loading) { +// return ( +// +// +// +// ); +// } - if (!user) { - return ( - - - Please sign in to view your sign-ups. - - - ); - } +// if (!user) { +// return ( +// +// +// Please sign in to view your sign-ups. +// +// +// ); +// } - return ( - - - } - > - {/* Page header */} - - My Sign-Ups - - Events you're registered for - - +// return ( +// +// +// } +// > +// {/* Page header */} +// +// My Sign-Ups +// +// Events you're registered for +// +// - {tickets.length === 0 ? ( - - 🎟️ - - You haven't signed up for any events yet. - - - ) : ( - - {tickets.map((ticket) => ( - - - {/* Image */} - - {ticket.eventImageUrl ? ( - - ) : ( - - 🖼️ - - )} - +// {tickets.length === 0 ? ( +// +// 🎟️ +// +// You haven't signed up for any events yet. +// +// +// ) : ( +// +// {tickets.map((ticket) => ( +// +// +// {/* Image */} +// +// {ticket.eventImageUrl ? ( +// +// ) : ( +// +// 🖼️ +// +// )} +// - {/* Content */} - - - - - {ticket.eventName} - - - - 📅 - - {formatDate(ticket.eventDate)} - - - {ticket.eventLocation && ( - - 📍 - - {ticket.eventLocation} - - - )} - - 💰 - - {formatPrice(ticket.eventPrice)} - - - - - handleCancel(ticket.eventId)} - className="px-3 py-1.5 border border-red-200 rounded-lg bg-red-50 active:bg-red-100" - > - - Cancel - - - +// {/* Content */} +// +// +// +// +// {ticket.eventName} +// +// +// +// 📅 +// +// {formatDate(ticket.eventDate)} +// +// +// {ticket.eventLocation && ( +// +// 📍 +// +// {ticket.eventLocation} +// +// +// )} +// +// 💰 +// +// {formatPrice(ticket.eventPrice)} +// +// +// +// +// handleCancel(ticket.eventId)} +// className="px-3 py-1.5 border border-red-200 rounded-lg bg-red-50 active:bg-red-100" +// > +// +// Cancel +// +// +// - {/* Seat info */} - {(ticket.tableSeat || ticket.busSeat) && ( - - {ticket.tableSeat && ( - - 💺 - - {ticket.tableSeat} - - - )} - {ticket.busSeat && ( - - 🚌 - - {ticket.busSeat} - - - )} - - )} - - - - ))} - - )} - +// {/* Seat info */} +// {(ticket.tableSeat || ticket.busSeat) && ( +// +// {ticket.tableSeat && ( +// +// 💺 +// +// {ticket.tableSeat} +// +// +// )} +// {ticket.busSeat && ( +// +// 🚌 +// +// {ticket.busSeat} +// +// +// )} +// +// )} +// +// +// +// ))} +// +// )} +// - {/* Cancel Confirmation Modal */} - setShowCancelModal(false)} - > - - - - Cancel Sign-Up - - - Are you sure you want to cancel this sign-up? - - - setShowCancelModal(false)} - className="flex-1 py-3 border border-gray-300 rounded-xl active:bg-gray-50" - > - - No - - - - - Yes, Cancel - - - - - - - - ); -} +// {/* Cancel Confirmation Modal */} +// setShowCancelModal(false)} +// > +// +// +// +// Cancel Sign-Up +// +// +// Are you sure you want to cancel this sign-up? +// +// +// setShowCancelModal(false)} +// className="flex-1 py-3 border border-gray-300 rounded-xl active:bg-gray-50" +// > +// +// No +// +// +// +// +// Yes, Cancel +// +// +// +// +// +// +// +// ); +// } From af8884dec5ac20c4f7c72d3a90bf39aa0e084e20 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 03:40:16 -0500 Subject: [PATCH 5/7] image height fix --- src/frontend/mobile/app/(tabs)/my-tickets.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/mobile/app/(tabs)/my-tickets.tsx b/src/frontend/mobile/app/(tabs)/my-tickets.tsx index b3b868b..9962e32 100644 --- a/src/frontend/mobile/app/(tabs)/my-tickets.tsx +++ b/src/frontend/mobile/app/(tabs)/my-tickets.tsx @@ -185,9 +185,9 @@ export default function MyTickets() { {ticket.eventImageUrl ? ( ) : ( Date: Thu, 12 Feb 2026 03:58:41 -0500 Subject: [PATCH 6/7] mobile lint error fix --- src/frontend/mobile/app/_context/AuthContext.tsx | 12 +++++++++--- src/frontend/mobile/app/_lib/api.ts | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/frontend/mobile/app/_context/AuthContext.tsx b/src/frontend/mobile/app/_context/AuthContext.tsx index 2a6f97a..b413c68 100644 --- a/src/frontend/mobile/app/_context/AuthContext.tsx +++ b/src/frontend/mobile/app/_context/AuthContext.tsx @@ -23,7 +23,7 @@ interface AuthContextType { login: (email: string, password: string) => Promise; requestCode: (email: string) => Promise; requestOtp: (email: string) => Promise; - confirmCode: (email: string, code: string) => Promise<'needsRegistration'>; + confirmCode: (email: string, code: string) => Promise<'needsRegistration' | 'authenticated'>; completeRegistration: (data: { firstName: string; lastName: string; @@ -114,8 +114,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const result = await verifyOtp(email, code); await setAuthToken(result.token); setPendingEmail(email); - setStatus('needsRegistration'); - return 'needsRegistration'; + if (result.needsRegistration) { + setUser(result.user ?? null); + setStatus('needsRegistration'); + return 'needsRegistration'; + } + setUser(result.user ?? null); + setStatus('authenticated'); + return 'authenticated'; }; const completeRegistration = async (data: { diff --git a/src/frontend/mobile/app/_lib/api.ts b/src/frontend/mobile/app/_lib/api.ts index 817f381..73bdbce 100644 --- a/src/frontend/mobile/app/_lib/api.ts +++ b/src/frontend/mobile/app/_lib/api.ts @@ -105,7 +105,7 @@ export function registerProfile(data: { program: string; password: string; confirmPassword?: string; -}) { +}): Promise<{ token: string; user: User }> { return apiFetch('/auth/register', { method: 'POST', body: JSON.stringify(data), From 7acdd4a758d09b04bab8c582aae46350eef58212 Mon Sep 17 00:00:00 2001 From: Abyan Jaigirdar Date: Thu, 12 Feb 2026 04:21:02 -0500 Subject: [PATCH 7/7] backend: fix linter errors --- src/backend/src/auth/auth.controller.ts | 12 ++- src/backend/src/auth/auth.service.ts | 5 +- src/backend/src/auth/jwt-auth.guard.ts | 9 ++- src/backend/src/auth/onboarding.guard.ts | 10 ++- src/backend/src/db/seed-data.ts | 87 ++++++++++++--------- src/backend/src/events/events.controller.ts | 14 ++-- src/backend/src/users/users.controller.ts | 16 ++-- 7 files changed, 94 insertions(+), 59 deletions(-) diff --git a/src/backend/src/auth/auth.controller.ts b/src/backend/src/auth/auth.controller.ts index 92cbb80..d81b0e9 100644 --- a/src/backend/src/auth/auth.controller.ts +++ b/src/backend/src/auth/auth.controller.ts @@ -10,6 +10,14 @@ import { AuthService } from './auth.service'; import { JwtAuthGuard } from './jwt-auth.guard'; import { OnboardingGuard } from './onboarding.guard'; +interface RequestWithUser extends Request { + user: { sub: number; email: string }; +} + +interface RequestWithOnboarding extends Request { + onboardingEmail: string; +} + @Controller('auth') export class AuthController { constructor(private readonly authService: AuthService) {} @@ -56,14 +64,14 @@ export class AuthController { password: string; confirmPassword?: string; }, - @Req() req: any, + @Req() req: RequestWithOnboarding, ) { return this.authService.registerUser(req.onboardingEmail, body); } @Get('me') @UseGuards(JwtAuthGuard) - me(@Req() req: any) { + me(@Req() req: RequestWithUser) { return this.authService.getUserInfo(req.user.sub); } diff --git a/src/backend/src/auth/auth.service.ts b/src/backend/src/auth/auth.service.ts index cb4f050..16fb64d 100644 --- a/src/backend/src/auth/auth.service.ts +++ b/src/backend/src/auth/auth.service.ts @@ -5,9 +5,8 @@ import { } from '@nestjs/common'; import { createHash, randomInt } from 'crypto'; import bcrypt from 'bcryptjs'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const nodemailer = require('nodemailer'); -type Transporter = { sendMail: (options: any) => Promise }; +import * as nodemailer from 'nodemailer'; +type Transporter = ReturnType; import { sign, type SignOptions } from 'jsonwebtoken'; import { and, desc, eq, gt, isNull } from 'drizzle-orm'; import { DatabaseService } from '../database/database.service'; diff --git a/src/backend/src/auth/jwt-auth.guard.ts b/src/backend/src/auth/jwt-auth.guard.ts index 7b109b0..dfad9d5 100644 --- a/src/backend/src/auth/jwt-auth.guard.ts +++ b/src/backend/src/auth/jwt-auth.guard.ts @@ -6,11 +6,16 @@ import { } from '@nestjs/common'; import { verify } from 'jsonwebtoken'; +interface RequestWithHeaders { + headers?: { authorization?: string }; + user?: { sub: number; email: string }; +} + @Injectable() export class JwtAuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { // M8: validateToken(token) -> verify JWT before protected routes. - const request = context.switchToHttp().getRequest(); + const request = context.switchToHttp().getRequest(); const header = request.headers?.authorization ?? ''; const token = typeof header === 'string' && header.startsWith('Bearer ') ? header.slice(7).trim() @@ -37,7 +42,7 @@ export class JwtAuthGuard implements CanActivate { request.user = { sub: Number(sub), email }; return true; - } catch (error) { + } catch { throw new UnauthorizedException('Invalid or expired token.'); } } diff --git a/src/backend/src/auth/onboarding.guard.ts b/src/backend/src/auth/onboarding.guard.ts index 4eb5b15..39b9306 100644 --- a/src/backend/src/auth/onboarding.guard.ts +++ b/src/backend/src/auth/onboarding.guard.ts @@ -5,12 +5,16 @@ import { UnauthorizedException, } from '@nestjs/common'; import { verify } from 'jsonwebtoken'; -import type { OnboardingJwtPayload } from './auth.types'; + +interface RequestWithHeaders { + headers?: { authorization?: string }; + onboardingEmail?: string; +} @Injectable() export class OnboardingGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { - const request = context.switchToHttp().getRequest(); + const request = context.switchToHttp().getRequest(); const header = request.headers?.authorization ?? ''; const token = typeof header === 'string' && header.startsWith('Bearer ') ? header.slice(7).trim() @@ -40,7 +44,7 @@ export class OnboardingGuard implements CanActivate { request.onboardingEmail = email; return true; - } catch (error) { + } catch { throw new UnauthorizedException('Invalid or expired onboarding token.'); } } diff --git a/src/backend/src/db/seed-data.ts b/src/backend/src/db/seed-data.ts index 6293b2e..c76bbb6 100644 --- a/src/backend/src/db/seed-data.ts +++ b/src/backend/src/db/seed-data.ts @@ -11,6 +11,9 @@ import { tickets, tableSeats, busSeats, + type Ticket, + type TableSeat, + type BusSeat, } from './schema'; import { eq } from 'drizzle-orm'; @@ -139,11 +142,13 @@ export async function runSeedDb(db: SeedDb): Promise { // Check if tickets already exist const existingTickets = await db.select().from(tickets).limit(1); - let ticket1, ticket2, ticket3; + let ticket1: Ticket | undefined; + let ticket2: Ticket | undefined; + let ticket3: Ticket | undefined; if (existingTickets.length > 0) { // Update existing tickets with QR codes - const allTickets = await db.select().from(tickets); + const allTickets: Ticket[] = await db.select().from(tickets); for (const ticket of allTickets) { const qrData = `TICKET:${ticket.userId}:${ticket.eventId}:${ticket.id}:${Date.now()}`; await db @@ -152,7 +157,9 @@ export async function runSeedDb(db: SeedDb): Promise { .where(eq(tickets.id, ticket.id)); } // Get first 3 for seat assignment - [ticket1, ticket2, ticket3] = allTickets.slice(0, 3); + ticket1 = allTickets[0]; + ticket2 = allTickets[1]; + ticket3 = allTickets[2]; } else { // Create new tickets with QR codes const timestamp = Date.now(); @@ -204,42 +211,46 @@ export async function runSeedDb(db: SeedDb): Promise { if (!ticket1 || !ticket2 || !ticket3) throw new Error('Ticket insert failed'); - const tableSeatToAssign = await db - .select() - .from(tableSeats) - .where(eq(tableSeats.eventId, event1.id)) - .limit(1); - if (tableSeatToAssign[0]) { - await db - .update(tableSeats) - .set({ ticketId: ticket1.id, updatedAt: new Date() }) - .where(eq(tableSeats.id, tableSeatToAssign[0].id)); - await db - .update(tickets) - .set({ - tableSeat: `Table ${tableSeatToAssign[0].tableNumber}, Seat ${tableSeatToAssign[0].seatNumber}`, - updatedAt: new Date(), - }) - .where(eq(tickets.id, ticket1.id)); - } + if (ticket1 && ticket2) { + const tableSeatResults: TableSeat[] = await db + .select() + .from(tableSeats) + .where(eq(tableSeats.eventId, event1.id)) + .limit(1); + const tableSeat: TableSeat | undefined = tableSeatResults[0]; + if (tableSeat) { + await db + .update(tableSeats) + .set({ ticketId: ticket1.id, updatedAt: new Date() }) + .where(eq(tableSeats.id, tableSeat.id)); + await db + .update(tickets) + .set({ + tableSeat: `Table ${tableSeat.tableNumber}, Seat ${tableSeat.seatNumber}`, + updatedAt: new Date(), + }) + .where(eq(tickets.id, ticket1.id)); + } - const firstBusSeat = await db - .select() - .from(busSeats) - .where(eq(busSeats.eventId, event1.id)) - .limit(1); - if (firstBusSeat[0]) { - await db - .update(busSeats) - .set({ ticketId: ticket2.id, updatedAt: new Date() }) - .where(eq(busSeats.id, firstBusSeat[0].id)); - await db - .update(tickets) - .set({ - busSeat: `Bus ${firstBusSeat[0].busNumber} - Seat ${firstBusSeat[0].seatNumber}`, - updatedAt: new Date(), - }) - .where(eq(tickets.id, ticket2.id)); + const busSeatResults: BusSeat[] = await db + .select() + .from(busSeats) + .where(eq(busSeats.eventId, event1.id)) + .limit(1); + const busSeat: BusSeat | undefined = busSeatResults[0]; + if (busSeat) { + await db + .update(busSeats) + .set({ ticketId: ticket2.id, updatedAt: new Date() }) + .where(eq(busSeats.id, busSeat.id)); + await db + .update(tickets) + .set({ + busSeat: `Bus ${busSeat.busNumber} - Seat ${busSeat.seatNumber}`, + updatedAt: new Date(), + }) + .where(eq(tickets.id, ticket2.id)); + } } return true; diff --git a/src/backend/src/events/events.controller.ts b/src/backend/src/events/events.controller.ts index 6231931..5ba198d 100644 --- a/src/backend/src/events/events.controller.ts +++ b/src/backend/src/events/events.controller.ts @@ -15,6 +15,10 @@ import { EventsService } from './events.service'; import type { NewEvent } from '../db/schema'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +interface RequestWithUser extends Request { + user: { sub: number; email: string }; +} + @Controller('events') @UseGuards(JwtAuthGuard) export class EventsController { @@ -49,7 +53,7 @@ export class EventsController { @Post(':id/signup') signup( @Param('id') id: string, - @Req() req: any, + @Req() req: RequestWithUser, @Body('selectedTable') selectedTable?: number, ) { return this.eventsService.signup(+id, req.user.sub, selectedTable); @@ -58,7 +62,7 @@ export class EventsController { @Post(':id/checkout-session') createCheckoutSession( @Param('id') id: string, - @Req() req: any, + @Req() req: RequestWithUser, @Body() body: { successUrl?: string; @@ -109,13 +113,13 @@ export class EventsController { } @Post(':id/cancel') - cancelSignup(@Param('id') id: string, @Req() req: any) { + cancelSignup(@Param('id') id: string, @Req() req: RequestWithUser) { return this.eventsService.cancelSignup(+id, req.user.sub); } @Get('user/:userId/tickets') - getUserTickets(@Param('userId') userId: string, @Req() req: any) { - if (req.user?.sub !== +userId) { + getUserTickets(@Param('userId') userId: string, @Req() req: RequestWithUser) { + if (req.user.sub !== +userId) { throw new ForbiddenException('Access denied.'); } return this.eventsService.getTicketsForUser(+userId); diff --git a/src/backend/src/users/users.controller.ts b/src/backend/src/users/users.controller.ts index 7022adb..4c9728a 100644 --- a/src/backend/src/users/users.controller.ts +++ b/src/backend/src/users/users.controller.ts @@ -14,6 +14,10 @@ import { UsersService } from './users.service'; import type { NewUser } from '../db/schema'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +interface RequestWithUser extends Request { + user: { sub: number; email: string }; +} + @Controller('users') @UseGuards(JwtAuthGuard) export class UsersController { @@ -25,8 +29,8 @@ export class UsersController { } @Get(':id') - findOne(@Param('id') id: string, @Req() req: any) { - if (req.user?.sub !== +id) { + findOne(@Param('id') id: string, @Req() req: RequestWithUser) { + if (req.user.sub !== +id) { throw new ForbiddenException('Access denied.'); } return this.usersService.findOneWithRoles(+id); @@ -38,16 +42,16 @@ export class UsersController { } @Put(':id') - update(@Param('id') id: string, @Body() user: Partial, @Req() req: any) { - if (req.user?.sub !== +id) { + update(@Param('id') id: string, @Body() user: Partial, @Req() req: RequestWithUser) { + if (req.user.sub !== +id) { throw new ForbiddenException('Access denied.'); } return this.usersService.update(+id, user); } @Delete(':id') - delete(@Param('id') id: string, @Req() req: any) { - if (req.user?.sub !== +id) { + delete(@Param('id') id: string, @Req() req: RequestWithUser) { + if (req.user.sub !== +id) { throw new ForbiddenException('Access denied.'); } return this.usersService.delete(+id);