diff --git a/tickity-rn-app/app/(tabs)/account.tsx b/tickity-rn-app/app/(tabs)/account.tsx
index 5c2fa02..3bbd2f1 100644
--- a/tickity-rn-app/app/(tabs)/account.tsx
+++ b/tickity-rn-app/app/(tabs)/account.tsx
@@ -102,13 +102,6 @@ export default function AccountScreen() {
/>
}
>
-
- Account
-
- Manage your wallet and preferences
-
-
-
{account ? (
{/* Wallet Info Card */}
diff --git a/tickity-rn-app/app/(tabs)/favourites.tsx b/tickity-rn-app/app/(tabs)/favourites.tsx
index 5bd90d2..d26b44e 100644
--- a/tickity-rn-app/app/(tabs)/favourites.tsx
+++ b/tickity-rn-app/app/(tabs)/favourites.tsx
@@ -1,28 +1,122 @@
+import AnimatedNFTCard from "@/components/AnimatedNFTCard";
+import useGetUserNFTs from "@/hooks/useGetUserNFTs";
+import { LinearGradient } from "expo-linear-gradient";
import { router } from "expo-router";
-import React, { useState } from "react";
-import { Button, StyleSheet, Text, View } from "react-native";
+import React from "react";
+import {
+ ActivityIndicator,
+ Dimensions,
+ FlatList,
+ RefreshControl,
+ StyleSheet,
+ Text,
+ View,
+} from "react-native";
+
+const { width } = Dimensions.get("window");
const Favourites = () => {
- const [showAR, setShowAR] = useState(false);
+ const { nfts, hasNFTs, nftCount, isLoading, error, refetch } =
+ useGetUserNFTs();
- return (
-
- Favourites
+ const renderNFTCard = ({ item, index }: { item: any; index: number }) => (
+ {
+ // Navigate to NFT details or event page
+ router.push(`/yourevent/${item.eventAddress}`);
+ }}
+ />
+ );
+
+ const renderEmptyState = () => (
+
+
+
+ 🎫
+ No NFTs Yet
+
+ Your minted NFTs will appear here
+
+
+ Purchase tickets to events and they'll be displayed as beautiful
+ NFTs in your collection
+
+
+
+
+ );
+
+ const renderHeader = () => (
+
+ My NFT Collection
- Here you'll find all your favourite events in one place
+ {hasNFTs
+ ? `You have ${nftCount} minted NFT${nftCount !== 1 ? "s" : ""}`
+ : "Your minted NFTs will appear here"}
-
- Save events you love and access them quickly whenever you need them
-
-
-
+ );
+
+ if (isLoading) {
+ return (
+
+
+ Loading your NFTs...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ Oops! Something went wrong
+
+ {error instanceof Error ? error.message : "Failed to load your NFTs"}
+
+ );
+ }
+
+ return (
+
+ {hasNFTs ? (
+ item.ticketId}
+ numColumns={2}
+ columnWrapperStyle={styles.row}
+ contentContainerStyle={styles.listContainer}
+ ListHeaderComponent={renderHeader}
+ refreshControl={
+
+ }
+ showsVerticalScrollIndicator={false}
+ />
+ ) : (
+
+ {renderHeader()}
+ {renderEmptyState()}
+
+ )}
);
};
@@ -30,39 +124,99 @@ const Favourites = () => {
const styles = StyleSheet.create({
container: {
flex: 1,
- padding: 20,
+ backgroundColor: "#000",
+ },
+ loadingContainer: {
+ flex: 1,
backgroundColor: "#000",
justifyContent: "center",
alignItems: "center",
},
+ loadingText: {
+ color: "#fff",
+ fontSize: 16,
+ marginTop: 16,
+ },
+ errorContainer: {
+ flex: 1,
+ backgroundColor: "#000",
+ justifyContent: "center",
+ alignItems: "center",
+ padding: 20,
+ },
+ errorTitle: {
+ color: "#fff",
+ fontSize: 20,
+ fontWeight: "bold",
+ marginBottom: 8,
+ },
+ errorText: {
+ color: "#ccc",
+ fontSize: 16,
+ textAlign: "center",
+ },
+ header: {
+ padding: 20,
+ paddingBottom: 10,
+ },
title: {
fontSize: 28,
fontWeight: "bold",
- marginBottom: 10,
- textAlign: "center",
+ marginBottom: 8,
color: "#fff",
},
subtitle: {
- fontSize: 18,
+ fontSize: 16,
+ color: "#ccc",
+ lineHeight: 22,
+ },
+ listContainer: {
+ paddingHorizontal: 20,
+ paddingBottom: 20,
+ },
+ row: {
+ justifyContent: "space-between",
+ },
+ emptyContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ paddingHorizontal: 20,
+ },
+ emptyGradient: {
+ borderRadius: 20,
+ padding: 2,
+ width: "100%",
+ maxWidth: 300,
+ },
+ emptyContent: {
+ backgroundColor: "rgba(20, 20, 20, 0.95)",
+ borderRadius: 18,
+ padding: 30,
+ alignItems: "center",
+ },
+ emptyEmoji: {
+ fontSize: 48,
+ marginBottom: 16,
+ },
+ emptyTitle: {
+ fontSize: 24,
+ fontWeight: "bold",
color: "#fff",
- marginBottom: 15,
+ marginBottom: 8,
textAlign: "center",
- lineHeight: 24,
},
- description: {
- fontSize: 14,
+ emptySubtitle: {
+ fontSize: 16,
color: "#ccc",
+ marginBottom: 12,
textAlign: "center",
- lineHeight: 20,
- marginBottom: 30,
- },
- buttonContainer: {
- flexDirection: "row",
- justifyContent: "center",
- alignItems: "center",
},
- buttonSpacer: {
- width: 20,
+ emptyDescription: {
+ fontSize: 14,
+ color: "#aaa",
+ textAlign: "center",
+ lineHeight: 20,
},
});
diff --git a/tickity-rn-app/components/AnimatedNFTCard.tsx b/tickity-rn-app/components/AnimatedNFTCard.tsx
new file mode 100644
index 0000000..b608036
--- /dev/null
+++ b/tickity-rn-app/components/AnimatedNFTCard.tsx
@@ -0,0 +1,302 @@
+import { LinearGradient } from "expo-linear-gradient";
+import React, { useEffect } from "react";
+import {
+ Animated,
+ Dimensions,
+ Image,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+} from "react-native";
+
+interface AnimatedNFTCardProps {
+ ticketId: string;
+ ticketType: string;
+ eventName?: string;
+ eventImage?: string;
+ purchaseDate?: string;
+ tokenURI?: string;
+ metadata?: any;
+ onPress?: () => void;
+ index?: number;
+}
+
+const { width } = Dimensions.get("window");
+
+const AnimatedNFTCard: React.FC = ({
+ ticketId,
+ ticketType,
+ eventName = "Event Ticket",
+ eventImage,
+ purchaseDate,
+ tokenURI,
+ metadata,
+ onPress,
+ index = 0,
+}) => {
+ const scaleAnim = new Animated.Value(0.8);
+ const opacityAnim = new Animated.Value(0);
+
+ useEffect(() => {
+ // Stagger animation based on index
+ const delay = index * 100;
+
+ Animated.parallel([
+ Animated.timing(scaleAnim, {
+ toValue: 1,
+ duration: 300,
+ delay,
+ useNativeDriver: true,
+ }),
+ Animated.timing(opacityAnim, {
+ toValue: 1,
+ duration: 300,
+ delay,
+ useNativeDriver: true,
+ }),
+ ]).start();
+ }, []);
+
+ const handlePressIn = () => {
+ Animated.spring(scaleAnim, {
+ toValue: 0.95,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const handlePressOut = () => {
+ Animated.spring(scaleAnim, {
+ toValue: 1,
+ useNativeDriver: true,
+ }).start();
+ };
+
+ const gradients = [
+ ["#667eea", "#764ba2"],
+ ["#f093fb", "#f5576c"],
+ ["#4facfe", "#00f2fe"],
+ ["#43e97b", "#38f9d7"],
+ ["#fa709a", "#fee140"],
+ ["#a8edea", "#fed6e3"],
+ ];
+
+ const gradientIndex = parseInt(ticketId) % gradients.length;
+ const selectedGradient = gradients[gradientIndex];
+
+ // Determine ticket type display
+ const getTicketTypeDisplay = (type: string) => {
+ if (type.includes("VIP")) return "VIP";
+ if (type.includes("Premium")) return "Premium";
+ if (type.includes("Standard")) return "Standard";
+ return type;
+ };
+
+ const ticketTypeDisplay = getTicketTypeDisplay(ticketType);
+
+ return (
+
+
+
+
+
+
+ NFT
+
+ #{ticketId}
+
+
+
+ {metadata?.image ? (
+
+ ) : eventImage ? (
+
+ ) : (
+
+ 🎫
+
+ )}
+
+
+
+
+ {metadata?.name || eventName}
+
+
+
+ {ticketTypeDisplay}
+
+
+ {purchaseDate && (
+
+ Purchased: {new Date(purchaseDate).toLocaleDateString()}
+
+ )}
+
+
+
+
+
+ Ethereum
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ width: (width - 60) / 2,
+ marginBottom: 16,
+ },
+ touchable: {
+ borderRadius: 16,
+ elevation: 8,
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 4,
+ },
+ shadowOpacity: 0.3,
+ shadowRadius: 8,
+ },
+ gradient: {
+ borderRadius: 16,
+ padding: 1,
+ },
+ cardContent: {
+ backgroundColor: "rgba(20, 20, 20, 0.95)",
+ borderRadius: 15,
+ padding: 12,
+ height: 200,
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.3,
+ shadowRadius: 4,
+ elevation: 3,
+ },
+ header: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginBottom: 8,
+ },
+ badge: {
+ backgroundColor: "rgba(255, 255, 255, 0.15)",
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 12,
+ },
+ badgeText: {
+ fontSize: 10,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ ticketId: {
+ fontSize: 12,
+ fontWeight: "600",
+ color: "#ccc",
+ },
+ imageContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ marginBottom: 8,
+ },
+ eventImage: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: "#333",
+ },
+ placeholderImage: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ placeholderText: {
+ fontSize: 24,
+ },
+ details: {
+ flex: 1,
+ },
+ eventName: {
+ fontSize: 14,
+ fontWeight: "bold",
+ color: "#fff",
+ marginBottom: 4,
+ },
+ ticketTypeContainer: {
+ marginBottom: 4,
+ },
+ ticketTypeBadge: {
+ backgroundColor: "rgba(255, 255, 255, 0.15)",
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 12,
+ alignSelf: "flex-start",
+ },
+ ticketTypeText: {
+ fontSize: 10,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ purchaseDate: {
+ fontSize: 10,
+ color: "#aaa",
+ },
+ footer: {
+ flexDirection: "row",
+ justifyContent: "flex-end",
+ alignItems: "center",
+ marginTop: 4,
+ },
+ chainIndicator: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ chainDot: {
+ width: 6,
+ height: 6,
+ borderRadius: 3,
+ backgroundColor: "#4CAF50",
+ marginRight: 4,
+ },
+ chainText: {
+ fontSize: 10,
+ color: "#ccc",
+ },
+});
+
+export default AnimatedNFTCard;
diff --git a/tickity-rn-app/components/EventsScreen.tsx b/tickity-rn-app/components/EventsScreen.tsx
index ef523c3..6fd5ae9 100644
--- a/tickity-rn-app/components/EventsScreen.tsx
+++ b/tickity-rn-app/components/EventsScreen.tsx
@@ -30,57 +30,39 @@ const categories = [
// Mock data for cities
const cities = [
{
- id: "1",
- name: "Mumbai",
+ id: "3",
+ name: "San Francisco",
image:
- "https://images.unsplash.com/photo-1710582307396-5ca7b4390aa8?q=80&w=1867&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
- icon: "🏛️",
+ "https://images.unsplash.com/photo-1501594907352-04cda38ebc29?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
+ icon: "🌉",
},
{
- id: "2",
- name: "Bengaluru",
+ id: "4",
+ name: "London",
image:
- "https://images.unsplash.com/photo-1565018054866-968e244671af?q=80&w=2679&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
- icon: "🏛️",
+ "https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
+ icon: "🏰",
},
{
- id: "3",
- name: "Delhi",
+ id: "5",
+ name: "New York",
image:
- "https://images.unsplash.com/photo-1564507592333-c60657eea523?w=400&h=200&fit=crop",
- icon: "🏛️",
+ "https://images.unsplash.com/photo-1496442226666-8d4d0e62e6e9?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
+ icon: "🗽",
},
{
- id: "4",
- name: "Chennai",
+ id: "1",
+ name: "Mumbai",
image:
- "https://images.unsplash.com/photo-1602216056096-3b40cc0c9944?w=400&h=200&fit=crop",
+ "https://images.unsplash.com/photo-1710582307396-5ca7b4390aa8?q=80&w=1867&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
icon: "🏛️",
},
-];
-
-// Mock data for featured calendars
-const featuredCalendars = [
- {
- id: "1",
- title: "Reading Rhythms Global",
- description:
- "Not a book club. A reading party. Read with friends to live music & curated playlists.",
- icon: "📚",
- },
{
id: "2",
- title: "ADPList",
- description:
- "Your favorite all-things happening at ADPList! We feature local meetups, ADPList events, and more.",
- icon: "😊",
- },
- {
- id: "3",
- title: "Tech Meetups",
- description:
- "Discover the latest in technology with local tech enthusiasts and industry experts.",
- icon: "💻",
+ name: "Bengaluru",
+ image:
+ "https://images.unsplash.com/photo-1565018054866-968e244671af?q=80&w=2679&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
+ icon: "🏛️",
},
];
@@ -138,60 +120,6 @@ const EventsScreen = () => {
);
- const renderEventItem = ({ item }: { item: Event }) => (
-
-
-
-
-
-
-
-
- {item.name || `Event #${item.eventAddress}`}
-
-
- {item.description || "Amazing event experience awaits you"}
-
-
-
-
- Date
-
- {format(
- new Date(Number(item.startTime) * 1000),
- "MMM d, yyyy"
- ) || "Coming Soon"}
-
-
-
- ID
- #{item.id.slice(-6)}
-
-
-
- router.push(`/${item.eventAddress}`)}
- >
- View Details
-
-
-
-
- );
-
- const renderEmptyState = () => (
-
- No events found
-
- );
-
return (
{
) : (
void;
+}
+
+const { width } = Dimensions.get("window");
+
+const NFTCard: React.FC = ({
+ ticketId,
+ ticketType,
+ eventName = "Event Ticket",
+ eventImage,
+ purchaseDate,
+ tokenURI,
+ metadata,
+ onPress,
+}) => {
+ const gradients = [
+ ["#667eea", "#764ba2"],
+ ["#f093fb", "#f5576c"],
+ ["#4facfe", "#00f2fe"],
+ ["#43e97b", "#38f9d7"],
+ ["#fa709a", "#fee140"],
+ ["#a8edea", "#fed6e3"],
+ ];
+
+ const gradientIndex = parseInt(ticketId) % gradients.length;
+ const selectedGradient = gradients[gradientIndex];
+
+ // Determine ticket type display
+ const getTicketTypeDisplay = (type: string) => {
+ if (type.includes("VIP")) return "VIP";
+ if (type.includes("Premium")) return "Premium";
+ if (type.includes("Standard")) return "Standard";
+ return type;
+ };
+
+ const ticketTypeDisplay = getTicketTypeDisplay(ticketType);
+
+ return (
+
+
+
+
+
+ NFT
+
+ #{ticketId}
+
+
+
+ {metadata?.image ? (
+
+ ) : eventImage ? (
+
+ ) : (
+
+ 🎫
+
+ )}
+
+
+
+
+ {metadata?.name || eventName}
+
+
+
+ {ticketTypeDisplay}
+
+
+ {purchaseDate && (
+
+ Purchased: {new Date(purchaseDate).toLocaleDateString()}
+
+ )}
+
+
+
+
+
+ Ethereum
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ width: (width - 60) / 2,
+ marginBottom: 16,
+ borderRadius: 16,
+ elevation: 8,
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 4,
+ },
+ shadowOpacity: 0.3,
+ shadowRadius: 8,
+ },
+ gradient: {
+ borderRadius: 16,
+ padding: 1,
+ },
+ cardContent: {
+ backgroundColor: "rgba(20, 20, 20, 0.95)",
+ borderRadius: 15,
+ padding: 12,
+ height: 200,
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 2,
+ },
+ shadowOpacity: 0.3,
+ shadowRadius: 4,
+ elevation: 3,
+ },
+ header: {
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginBottom: 8,
+ },
+ badge: {
+ backgroundColor: "rgba(255, 255, 255, 0.15)",
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 12,
+ },
+ badgeText: {
+ fontSize: 10,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ ticketId: {
+ fontSize: 12,
+ fontWeight: "600",
+ color: "#ccc",
+ },
+ imageContainer: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ marginBottom: 8,
+ },
+ eventImage: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: "#333",
+ },
+ placeholderImage: {
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
+ justifyContent: "center",
+ alignItems: "center",
+ },
+ placeholderText: {
+ fontSize: 24,
+ },
+ details: {
+ flex: 1,
+ },
+ eventName: {
+ fontSize: 14,
+ fontWeight: "bold",
+ color: "#fff",
+ marginBottom: 4,
+ },
+ ticketTypeContainer: {
+ marginBottom: 4,
+ },
+ ticketTypeBadge: {
+ backgroundColor: "rgba(255, 255, 255, 0.15)",
+ paddingHorizontal: 8,
+ paddingVertical: 4,
+ borderRadius: 12,
+ alignSelf: "flex-start",
+ },
+ ticketTypeText: {
+ fontSize: 10,
+ fontWeight: "bold",
+ color: "#fff",
+ },
+ purchaseDate: {
+ fontSize: 10,
+ color: "#aaa",
+ },
+ footer: {
+ flexDirection: "row",
+ justifyContent: "flex-end",
+ alignItems: "center",
+ marginTop: 4,
+ },
+ chainIndicator: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ chainDot: {
+ width: 6,
+ height: 6,
+ borderRadius: 3,
+ backgroundColor: "#4CAF50",
+ marginRight: 4,
+ },
+ chainText: {
+ fontSize: 10,
+ color: "#ccc",
+ },
+});
+
+export default NFTCard;
diff --git a/tickity-rn-app/hooks/useGetUserNFTs.tsx b/tickity-rn-app/hooks/useGetUserNFTs.tsx
new file mode 100644
index 0000000..c639da0
--- /dev/null
+++ b/tickity-rn-app/hooks/useGetUserNFTs.tsx
@@ -0,0 +1,155 @@
+import getEvents from "@/constants/subgraph";
+import { chain, client } from "@/constants/thirdweb";
+import { useQuery } from "@tanstack/react-query";
+import { getContract, readContract } from "thirdweb";
+import { useActiveAccount } from "thirdweb/react";
+
+interface UserNFT {
+ ticketId: string;
+ ticketType: string;
+ eventName: string;
+ eventImage?: string;
+ eventAddress: string;
+ purchaseDate?: string;
+ tokenURI?: string;
+ metadata?: any;
+}
+
+const useGetUserNFTs = () => {
+ const account = useActiveAccount();
+
+ const {
+ data: allNFTs,
+ isLoading,
+ error,
+ refetch,
+ } = useQuery({
+ queryKey: ["userNFTs", account?.address],
+ queryFn: async () => {
+ try {
+ if (!account?.address) {
+ return [];
+ }
+
+ const events = await getEvents();
+ const nfts: UserNFT[] = [];
+
+ // For each event, check if user has tickets
+ for (const event of events.eventCreateds) {
+ try {
+ const eventContract = getContract({
+ client: client,
+ address: event.eventAddress,
+ chain: chain,
+ });
+
+ // Get user's tickets for this event
+ const userTickets = await readContract({
+ contract: eventContract,
+ method:
+ "function getUserTickets(address user) external view returns (uint256[] memory)",
+ params: [account.address],
+ });
+
+ if (userTickets && userTickets.length > 0) {
+ // For each ticket, get the actual NFT data
+ for (let i = 0; i < userTickets.length; i++) {
+ try {
+ const ticketId = userTickets[i].toString();
+
+ // Get ticket type from the contract
+ let ticketType = "Standard";
+ try {
+ const ticketTypeResult = await readContract({
+ contract: eventContract,
+ method:
+ "function getTicketType(uint256 tokenId) external view returns (uint256)",
+ params: [BigInt(ticketId)],
+ });
+
+ // Map ticket type index to readable name
+ const typeIndex = Number(ticketTypeResult);
+ if (typeIndex === 0) ticketType = "Standard";
+ else if (typeIndex === 1) ticketType = "Premium";
+ else if (typeIndex === 2) ticketType = "VIP";
+ else ticketType = `Type ${typeIndex}`;
+ } catch (typeError) {
+ console.log(
+ `Could not get ticket type for ${ticketId}, using default`
+ );
+ }
+
+ // Try to get token URI for metadata
+ let tokenURI = "";
+ let metadata = null;
+ try {
+ tokenURI = await readContract({
+ contract: eventContract,
+ method:
+ "function tokenURI(uint256 tokenId) external view returns (string)",
+ params: [BigInt(ticketId)],
+ });
+
+ if (tokenURI) {
+ // Fetch metadata if URI is available
+ try {
+ const response = await fetch(tokenURI);
+ metadata = await response.json();
+ } catch (metadataError) {
+ console.log(`Could not fetch metadata for ${ticketId}`);
+ }
+ }
+ } catch (uriError) {
+ console.log(`Could not get token URI for ${ticketId}`);
+ }
+
+ nfts.push({
+ ticketId,
+ ticketType,
+ eventName: event.name,
+ eventImage: event.image,
+ eventAddress: event.eventAddress,
+ purchaseDate: event.createdAt,
+ tokenURI,
+ metadata,
+ });
+ } catch (ticketError) {
+ console.error(
+ `Error processing ticket ${i} for event ${event.name}:`,
+ ticketError
+ );
+ }
+ }
+ }
+ } catch (error) {
+ console.error(
+ `Error fetching tickets for event ${event.eventAddress}:`,
+ error
+ );
+ }
+ }
+
+ return nfts;
+ } catch (error) {
+ console.error("Error fetching user NFTs:", error);
+ return [];
+ }
+ },
+ enabled: !!account?.address,
+ retry: 2,
+ retryDelay: 1000,
+ staleTime: 30 * 1000, // 30 seconds
+ gcTime: 5 * 60 * 1000, // 5 minutes
+ });
+
+ return {
+ nfts: allNFTs || [],
+ hasNFTs: (allNFTs && allNFTs.length > 0) || false,
+ nftCount: allNFTs?.length || 0,
+ isLoading,
+ error,
+ refetch,
+ };
+};
+
+export default useGetUserNFTs;
diff --git a/tickity-rn-app/utils/bigIntHelper.ts b/tickity-rn-app/utils/bigIntHelper.ts
new file mode 100644
index 0000000..7d243cb
--- /dev/null
+++ b/tickity-rn-app/utils/bigIntHelper.ts
@@ -0,0 +1,48 @@
+/**
+ * Safely converts BigInt values to strings for serialization
+ */
+export const safeBigIntToString = (value: any): string => {
+ if (typeof value === "bigint") {
+ return value.toString();
+ }
+ if (value && typeof value.toString === "function") {
+ return value.toString();
+ }
+ return String(value);
+};
+
+/**
+ * Safely converts an array of BigInt values to strings
+ */
+export const safeBigIntArrayToStrings = (array: any[]): string[] => {
+ return array.map(safeBigIntToString);
+};
+
+/**
+ * Recursively converts all BigInt values in an object to strings
+ */
+export const serializeBigInts = (obj: any): any => {
+ if (obj === null || obj === undefined) {
+ return obj;
+ }
+
+ if (typeof obj === "bigint") {
+ return obj.toString();
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map(serializeBigInts);
+ }
+
+ if (typeof obj === "object") {
+ const result: any = {};
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ result[key] = serializeBigInts(obj[key]);
+ }
+ }
+ return result;
+ }
+
+ return obj;
+};