diff --git a/.env b/.env
new file mode 100644
index 0000000..5f53ba8
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_RPC_URL=https://coston2.enosys.global/ext/C/rpc
+NEXT_PUBLIC_CONTRACT_ADDRESS=0xa12B3168e8FC621493c9db2dc1Db31e75cCA00bb
\ No newline at end of file
diff --git a/app/events/[...eventId]/page.tsx b/app/events/[...eventId]/page.tsx
index c44afbb..60ad8d6 100644
--- a/app/events/[...eventId]/page.tsx
+++ b/app/events/[...eventId]/page.tsx
@@ -40,7 +40,7 @@ const ListingPage: React.FC = () => {
return (
<>
-
+
>
);
diff --git a/app/layout.tsx b/app/layout.tsx
index 980cf69..b0f9839 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,6 +4,7 @@ import './globals.css';
import { Inter } from 'next/font/google';
import './globals.css';
+import { Toaster } from '@/components/ui/toaster';
const inter = Inter({ subsets: ['latin'] });
@@ -30,7 +31,10 @@ export default function RootLayout({
}>) {
return (
-
{children}
+
+ {children}
+
+
);
}
diff --git a/components/custom/EventDescription.tsx b/components/custom/EventDescription.tsx
index d8a9ffb..1ef1ecb 100644
--- a/components/custom/EventDescription.tsx
+++ b/components/custom/EventDescription.tsx
@@ -1,4 +1,3 @@
-'use client';
import React from 'react';
import {
Card,
@@ -11,8 +10,15 @@ import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import ImageCarousel from './ImageCarousel';
import TicketButton from './TicketButton';
+import { buyHandler } from '@/lib/buyHandler';
+import { useToast } from '@/hooks/use-toast';
+
+const EventDescription = ({ eventId }: { eventId: string }) => {
+ const { toast } = useToast();
+ const handleBuyNow = () => {
+ buyHandler(Number(eventId), toast);
+ };
-const EventDescription = () => {
return (
@@ -47,6 +53,7 @@ const EventDescription = () => {
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
new file mode 100644
index 0000000..31e7841
--- /dev/null
+++ b/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+'use client';
+
+import * as React from 'react';
+import { Cross2Icon } from '@radix-ui/react-icons';
+import * as ToastPrimitives from '@radix-ui/react-toast';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+const ToastProvider = ToastPrimitives.Provider;
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
+
+const toastVariants = cva(
+ 'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
+ {
+ variants: {
+ variant: {
+ default: 'border bg-background text-foreground',
+ destructive:
+ 'destructive group border-destructive bg-destructive text-destructive-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ }
+);
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ );
+});
+Toast.displayName = ToastPrimitives.Root.displayName;
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastAction.displayName = ToastPrimitives.Action.displayName;
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+ToastClose.displayName = ToastPrimitives.Close.displayName;
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastTitle.displayName = ToastPrimitives.Title.displayName;
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastDescription.displayName = ToastPrimitives.Description.displayName;
+
+type ToastProps = React.ComponentPropsWithoutRef;
+
+type ToastActionElement = React.ReactElement;
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+};
diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx
new file mode 100644
index 0000000..8705cd8
--- /dev/null
+++ b/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import { useToast } from '@/hooks/use-toast';
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from '@/components/ui/toast';
+
+export function Toaster() {
+ const { toasts } = useToast();
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title}}
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/hooks/use-toast.ts b/hooks/use-toast.ts
new file mode 100644
index 0000000..7337115
--- /dev/null
+++ b/hooks/use-toast.ts
@@ -0,0 +1,191 @@
+'use client';
+
+// Inspired by react-hot-toast library
+import * as React from 'react';
+
+import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
+
+const TOAST_LIMIT = 1;
+const TOAST_REMOVE_DELAY = 1000000;
+
+type ToasterToast = ToastProps & {
+ id: string;
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+ action?: ToastActionElement;
+};
+
+const actionTypes = {
+ ADD_TOAST: 'ADD_TOAST',
+ UPDATE_TOAST: 'UPDATE_TOAST',
+ DISMISS_TOAST: 'DISMISS_TOAST',
+ REMOVE_TOAST: 'REMOVE_TOAST',
+} as const;
+
+let count = 0;
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
+ return count.toString();
+}
+
+type ActionType = typeof actionTypes;
+
+type Action =
+ | {
+ type: ActionType['ADD_TOAST'];
+ toast: ToasterToast;
+ }
+ | {
+ type: ActionType['UPDATE_TOAST'];
+ toast: Partial;
+ }
+ | {
+ type: ActionType['DISMISS_TOAST'];
+ toastId?: ToasterToast['id'];
+ }
+ | {
+ type: ActionType['REMOVE_TOAST'];
+ toastId?: ToasterToast['id'];
+ };
+
+interface State {
+ toasts: ToasterToast[];
+}
+
+const toastTimeouts = new Map>();
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return;
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId);
+ dispatch({
+ type: 'REMOVE_TOAST',
+ toastId: toastId,
+ });
+ }, TOAST_REMOVE_DELAY);
+
+ toastTimeouts.set(toastId, timeout);
+};
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case 'ADD_TOAST':
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ };
+
+ case 'UPDATE_TOAST':
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ };
+
+ case 'DISMISS_TOAST': {
+ const { toastId } = action;
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId);
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id);
+ });
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ };
+ }
+ case 'REMOVE_TOAST':
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ };
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ };
+ }
+};
+
+const listeners: Array<(state: State) => void> = [];
+
+let memoryState: State = { toasts: [] };
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action);
+ listeners.forEach((listener) => {
+ listener(memoryState);
+ });
+}
+
+type Toast = Omit;
+
+function toast({ ...props }: Toast) {
+ const id = genId();
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: 'UPDATE_TOAST',
+ toast: { ...props, id },
+ });
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
+
+ dispatch({
+ type: 'ADD_TOAST',
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss();
+ },
+ },
+ });
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ };
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState);
+
+ React.useEffect(() => {
+ listeners.push(setState);
+ return () => {
+ const index = listeners.indexOf(setState);
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ };
+ }, [state]);
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
+ };
+}
+
+export { useToast, toast };
diff --git a/lib/buyHandler.ts b/lib/buyHandler.ts
new file mode 100644
index 0000000..0f8530d
--- /dev/null
+++ b/lib/buyHandler.ts
@@ -0,0 +1,65 @@
+import { ethers } from 'ethers';
+import { getContract } from '@/lib/ethers';
+
+declare global {
+ interface Window {
+ ethereumProvider?: ethers.providers.ExternalProvider & {
+ isMetaMask?: boolean;
+ request?: (method: string, params?: unknown[]) => Promise;
+ };
+ }
+}
+
+type ToastFunction = (options: {
+ title: string;
+ variant?: 'default' | 'destructive' | null | undefined;
+}) => void;
+
+export const buyHandler = async (
+ eventId: number,
+ toast: ToastFunction
+): Promise => {
+ if (eventId < 0) {
+ toast({ title: 'Please enter a valid Event ID.', variant: 'destructive' });
+ return;
+ }
+
+ try {
+ if (typeof window.ethereum === 'undefined') {
+ toast({
+ title: 'Please install MetaMask or another Ethereum wallet',
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ // @ts-expect-error: window.ethereum might not match ExternalProvider exactly
+ const provider = new ethers.providers.Web3Provider(window.ethereum);
+ const signer = provider.getSigner();
+ const contract = getContract().connect(signer);
+
+ const ticketCost = await contract.getEventPriceFlare(eventId);
+ const balance = await provider.getBalance(await signer.getAddress());
+
+ if (balance.lt(ticketCost)) {
+ toast({
+ title: 'Insufficient balance to cover ticket cost and gas fees.',
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ const tx = await contract.buyTicket(eventId, { value: ticketCost });
+ const receipt = await tx.wait();
+
+ toast({
+ title: `Ticket purchased successfully! Transaction Hash: ${receipt.transactionHash}`,
+ });
+ } catch (error) {
+ console.error('Error buying ticket:', error);
+ toast({
+ title: 'Transaction failed. Please try again.',
+ variant: 'destructive',
+ });
+ }
+};
diff --git a/lib/ethers.ts b/lib/ethers.ts
new file mode 100644
index 0000000..f16d3ab
--- /dev/null
+++ b/lib/ethers.ts
@@ -0,0 +1,520 @@
+import { ethers } from 'ethers';
+
+const FLARE_TESTNET_RPC_URL = process.env.NEXT_PUBLIC_RPC_URL;
+const CONTRACT_ADDRESS = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
+
+export function getFlareProvider() {
+ const flareRpcUrl = FLARE_TESTNET_RPC_URL;
+ const provider = new ethers.providers.JsonRpcProvider(flareRpcUrl);
+ return provider;
+}
+
+export function getContract() {
+ const provider = getFlareProvider();
+ const contractAddress = CONTRACT_ADDRESS;
+ const contractABI = [
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_ticketId',
+ type: 'uint256',
+ },
+ {
+ internalType: 'address',
+ name: '_to',
+ type: 'address',
+ },
+ {
+ internalType: 'bool',
+ name: '_allowed',
+ type: 'bool',
+ },
+ ],
+ name: 'approveTicket',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_eventId',
+ type: 'uint256',
+ },
+ ],
+ name: 'buyTicket',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '_ticketId',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'payable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'string',
+ name: '_name',
+ type: 'string',
+ },
+ {
+ internalType: 'string',
+ name: '_description',
+ type: 'string',
+ },
+ {
+ internalType: 'uint256',
+ name: '_capacity',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: '_ticketPrice',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: '_eventDate',
+ type: 'uint256',
+ },
+ {
+ internalType: 'string[]',
+ name: '_images',
+ type: 'string[]',
+ },
+ ],
+ name: 'createEvent',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '_eventId',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ stateMutability: 'nonpayable',
+ type: 'constructor',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'eventId',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'string',
+ name: 'name',
+ type: 'string',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'eventDate',
+ type: 'uint256',
+ },
+ ],
+ name: 'EventCreated',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'ticketId',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'eventId',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'buyer',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'price',
+ type: 'uint256',
+ },
+ ],
+ name: 'TicketPurchased',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'ticketId',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'owner',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'trustee',
+ type: 'address',
+ },
+ ],
+ name: 'TicketTransferApproved',
+ type: 'event',
+ },
+ {
+ anonymous: false,
+ inputs: [
+ {
+ indexed: false,
+ internalType: 'uint256',
+ name: 'ticketId',
+ type: 'uint256',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'from',
+ type: 'address',
+ },
+ {
+ indexed: false,
+ internalType: 'address',
+ name: 'to',
+ type: 'address',
+ },
+ ],
+ name: 'TicketTransferred',
+ type: 'event',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_ticketId',
+ type: 'uint256',
+ },
+ {
+ internalType: 'address',
+ name: '_to',
+ type: 'address',
+ },
+ ],
+ name: 'transferTicket',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_ticketId',
+ type: 'uint256',
+ },
+ {
+ internalType: 'address',
+ name: '_to',
+ type: 'address',
+ },
+ ],
+ name: 'transferTicketFrom',
+ outputs: [],
+ stateMutability: 'nonpayable',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_cents',
+ type: 'uint256',
+ },
+ ],
+ name: 'centsToFlare',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '_flr',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'eventCounter',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ name: 'events',
+ outputs: [
+ {
+ internalType: 'string',
+ name: 'name',
+ type: 'string',
+ },
+ {
+ internalType: 'string',
+ name: 'description',
+ type: 'string',
+ },
+ {
+ internalType: 'uint256',
+ name: 'capacity',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: 'ticketsSold',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: 'ticketPrice',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: 'eventDate',
+ type: 'uint256',
+ },
+ {
+ internalType: 'address payable',
+ name: 'eventHost',
+ type: 'address',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ name: 'feedIds',
+ outputs: [
+ {
+ internalType: 'bytes21',
+ name: '',
+ type: 'bytes21',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_eventId',
+ type: 'uint256',
+ },
+ ],
+ name: 'getEventImages',
+ outputs: [
+ {
+ internalType: 'string[]',
+ name: '',
+ type: 'string[]',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_eventId',
+ type: 'uint256',
+ },
+ ],
+ name: 'getEventPriceFlare',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '_flr',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '_eventId',
+ type: 'uint256',
+ },
+ ],
+ name: 'getEventTickets',
+ outputs: [
+ {
+ internalType: 'uint256[]',
+ name: '',
+ type: 'uint256[]',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'getFlareFeed',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '_feedValue',
+ type: 'uint256',
+ },
+ {
+ internalType: 'int8',
+ name: '_decimals',
+ type: 'int8',
+ },
+ {
+ internalType: 'uint64',
+ name: '_timestamp',
+ type: 'uint64',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'getFtsoV2CurrentFeedValues',
+ outputs: [
+ {
+ internalType: 'uint256[]',
+ name: '_feedValues',
+ type: 'uint256[]',
+ },
+ {
+ internalType: 'int8[]',
+ name: '_decimals',
+ type: 'int8[]',
+ },
+ {
+ internalType: 'uint64',
+ name: '_timestamp',
+ type: 'uint64',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [],
+ name: 'ticketCounter',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ name: 'tickets',
+ outputs: [
+ {
+ internalType: 'address',
+ name: 'holder',
+ type: 'address',
+ },
+ {
+ internalType: 'uint256',
+ name: 'boughtTime',
+ type: 'uint256',
+ },
+ {
+ internalType: 'uint256',
+ name: 'eventId',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ {
+ inputs: [
+ {
+ internalType: 'address',
+ name: '',
+ type: 'address',
+ },
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ name: 'userTickets',
+ outputs: [
+ {
+ internalType: 'uint256',
+ name: '',
+ type: 'uint256',
+ },
+ ],
+ stateMutability: 'view',
+ type: 'function',
+ },
+ ];
+ return new ethers.Contract(contractAddress!, contractABI, provider);
+}
diff --git a/package-lock.json b/package-lock.json
index 02109a7..64643f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,10 +16,10 @@
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toast": "^1.2.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.3.0",
- "ethers": "^5.7.2",
"framer-motion": "^11.11.10",
"lucide-react": "^0.446.0",
"next": "14.2.13",
@@ -4976,7 +4976,6 @@
"cpu": [
"x64"
],
- "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -5951,6 +5950,40 @@
}
}
},
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz",
+ "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.1",
+ "@radix-ui/react-portal": "1.1.2",
+ "@radix-ui/react-presence": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@@ -13017,6 +13050,7 @@
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz",
"integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==",
"dev": true,
+ "hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
@@ -13379,6 +13413,7 @@
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz",
"integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==",
"dev": true,
+ "hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
@@ -24012,111 +24047,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
- },
- "node_modules/@next/swc-darwin-arm64": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.13.tgz",
- "integrity": "sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-darwin-x64": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.13.tgz",
- "integrity": "sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.13.tgz",
- "integrity": "sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.13.tgz",
- "integrity": "sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.13.tgz",
- "integrity": "sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.13.tgz",
- "integrity": "sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.13",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.13.tgz",
- "integrity": "sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
}
}
}
diff --git a/package.json b/package.json
index 74f4bca..04053c0 100644
--- a/package.json
+++ b/package.json
@@ -20,10 +20,10 @@
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toast": "^1.2.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.3.0",
- "ethers": "^5.7.2",
"framer-motion": "^11.11.10",
"lucide-react": "^0.446.0",
"next": "14.2.13",