diff --git a/.eslintrc.json b/.eslintrc.json index c9fcf13..3f92d73 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,6 +2,8 @@ "extends": ["next/core-web-vitals", "next/typescript"], "rules": { "@next/next/no-img-element": "off", - "@typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-object-type": "off", + "@typescript-eslint/no-unused-vars":"off" } } diff --git a/README.md b/README.md index d4a666e..7a04e02 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # TicketChain ## Problem + Many popular ticket sites exist, that allow customers to browse different upcoming events in their area and book tickets, however we identified several issues with these centralised service model: + - Tickets cannot be easily transferred - - Many existing platforms make it difficult if not impossible to transfer tickets between different users. This means people don't have *true* ownership over their purchased tickets. + - Many existing platforms make it difficult if not impossible to transfer tickets between different users. This means people don't have _true_ ownership over their purchased tickets. - Complex user interfaces - User interfaces on many of these websites can be quite complex and complicated, which can make it hard to book the right ticket and can often lead to customers making costly mistakes. - Scalability issues @@ -12,8 +14,9 @@ Many popular ticket sites exist, that allow customers to browse different upcomi - These central services consolidate all of the data and compute for their services in one spot. One possible risk to consider is a cyberattack - if the central organisation is compromised, all customer data is at risk of being stolen and leaked. Another possible risk is the central server going down, meaning ticket services would go down, which is not ideal if thousands of attendees are trying to verify themselves at an event. ## Proposal + We propose to build a solution which tackles these issues head-on. TicketChain is a decentralised website which allows users to browse upcoming events, and book tickets for these events through verifiable, immutable blockchain transactions. This allows users to purchase tickets. Functionality also exists for users to transfer their tickets to other users. This is made possible through the use of smart contracts deployed on the blockchain which expose several public read and write methods, which can be invoked through our front-end user interface. A record of transactions are kept secure on the blockchain, and no central entity can tamper with these transactions. -Because the main application logic is on the blockchain, this means we can exploit the blockchains' scalability and durability during high levels of demand. The workload of execution is split between nodes and transaction gas fees pay for the compute required during levels of high demand for the service, solving the issue of central services being unscalable and avoiding us from having a single point of failure. \ No newline at end of file +Because the main application logic is on the blockchain, this means we can exploit the blockchains' scalability and durability during high levels of demand. The workload of execution is split between nodes and transaction gas fees pay for the compute required during levels of high demand for the service, solving the issue of central services being unscalable and avoiding us from having a single point of failure. diff --git a/app/events/page.tsx b/app/events/page.tsx index 8f61e8d..0784c0a 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -62,7 +62,7 @@ const fetchEvents = (): Event[] => { const EventsPage: React.FC = () => { const [events, setEvents] = useState([]); const [filteredEvents, setFilteredEvents] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); + const [searchQuery, setSearchQuery] = useState(''); const [hoveredEventId, setHoveredEventId] = useState(null); const [sortOption, setSortOption] = useState(''); const [filterOptions, setFilterOptions] = useState([]); @@ -81,14 +81,16 @@ const EventsPage: React.FC = () => { }, []); const SearchBox = () => { - setSearchQuery(useSearchParams().get("q") || ""); - return setSearchQuery(e.target.value)} - className="search-bar mt-4 p-2 border border-gray-300 rounded w-full max-w-md" - /> + setSearchQuery(useSearchParams().get('q') || ''); + return ( + setSearchQuery(e.target.value)} + className="search-bar mt-4 p-2 border border-gray-300 rounded w-full max-w-md" + /> + ); }; useEffect(() => { @@ -177,16 +179,18 @@ const EventsPage: React.FC = () => {
- setSearchQuery(e.target.value)} - className="search-bar mt-4 p-2 border border-gray-300 rounded w-full max-w-md" - /> - }> + setSearchQuery(e.target.value)} + className="search-bar mt-4 p-2 border border-gray-300 rounded w-full max-w-md" + /> + } + >
diff --git a/app/page.tsx b/app/page.tsx index 775ec3d..2e06782 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -57,11 +57,11 @@ export default function Home() {
-
+
Book your next on the Flare blockchain.
diff --git a/components/PreviousTickets.tsx b/components/PreviousTickets.tsx new file mode 100644 index 0000000..8f17eb8 --- /dev/null +++ b/components/PreviousTickets.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { PreviousTicketComponent } from '@/components/custom/previousTicket'; + +const PreviousTickets = () => { + return ( +
+ + +
+ ); +}; + +export default PreviousTickets; diff --git a/components/TicketListings.tsx b/components/TicketListings.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/custom/EventForm.tsx b/components/custom/EventForm.tsx new file mode 100644 index 0000000..9273dd0 --- /dev/null +++ b/components/custom/EventForm.tsx @@ -0,0 +1,223 @@ +'use client'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; + +// Define the schema using Zod +const eventSchema = z + .object({ + name: z.string().min(1, { message: 'Event name is required' }), + description: z.string().min(1, { message: 'Description is required' }), + capacity: z + .number({ invalid_type_error: 'Capacity must be a number' }) + .min(1, { message: 'Capacity must be at least 1' }) + .refine(Number.isInteger, { message: 'Capacity must be an integer' }), + ticketPrice: z + .number({ invalid_type_error: 'Ticket price must be a number' }) + .min(0, { message: 'Ticket price must be at least 0' }) + .refine(Number.isInteger, { message: 'Ticket price must be in cents' }), + location: z.string().min(1, { message: 'Location is required' }), + eventStartTime: z.preprocess( + (val) => + typeof val === 'string' && val !== '' ? new Date(val) : undefined, + z + .date({ required_error: 'Event start time is required' }) + .min(new Date(), { message: 'Event start time must be in the future' }) + ), + eventEndTime: z.preprocess( + (val) => + typeof val === 'string' && val !== '' ? new Date(val) : undefined, + z.date().optional() + ), + images: z.preprocess( + (val) => { + if (Array.isArray(val)) { + // Filter out empty strings + return val.filter((v) => v !== ''); + } + return []; + }, + z + .array(z.string().url({ message: 'Invalid image URL format' })) + .optional() + ), + }) + .superRefine((data, ctx) => { + if (data.eventEndTime && data.eventEndTime <= data.eventStartTime) { + ctx.addIssue({ + code: 'custom', + message: 'Event end time must be after the start time', + path: ['eventEndTime'], + }); + } + }); + +// Define the TypeScript type based on the Zod schema +type EventFormData = z.infer; + +interface EventFormProps { + onSubmit: (data: EventFormData) => void; +} + +const EventForm = ({ onSubmit }: EventFormProps) => { + const [imageFields, setImageFields] = useState(['']); + + const { + register, + handleSubmit, + formState: { errors }, + setValue, + watch, + } = useForm({ + resolver: zodResolver(eventSchema), + mode: 'onChange', + defaultValues: { + images: [''], + }, + }); + + const images = watch('images') || []; + + const handleAddImageField = () => { + setImageFields((prev) => [...prev, '']); + }; + + const handleRemoveImageField = (index: number) => { + const updatedImages = [...imageFields]; + updatedImages.splice(index, 1); + setImageFields(updatedImages); + // Also update the form values + setValue('images', updatedImages); + }; + + return ( +
+ {/* Name Field */} +
+ + + {errors.name &&

{errors.name.message}

} +
+ + {/* Description Field */} +
+ +