11'use client' ;
22
3- import { useEffect , useState } from 'react' ;
3+ import { useState } from 'react' ;
44import { fetchAuth } from '@/services/apiClient' ;
55import VoxelButton from 'lib/components/voxel-button/button' ;
66import { toast } from 'sonner' ;
7+ import TicketImage from '@/images/devconnect-arg-ticket.png' ;
8+ import Image from 'next/image' ;
79import {
810 useAdditionalTicketEmails ,
911 ensureUserData ,
@@ -13,6 +15,7 @@ import { useGlobalStore } from '@/app/store.provider';
1315import { RequiresAuthHOC } from '@/components/RequiresAuthHOC' ;
1416import { homeTabs } from '../navigation' ;
1517import PageLayout from '@/components/PageLayout' ;
18+ import cn from 'classnames' ;
1619
1720const TicketWrapper = ( ) => {
1821 return (
@@ -22,23 +25,178 @@ const TicketWrapper = () => {
2225 ) ;
2326} ;
2427
25- const TicketTab = RequiresAuthHOC ( ( ) => {
28+ const ConnectedEmails = ( ) => {
2629 const additionalTicketEmails = useAdditionalTicketEmails ( ) ;
2730 const setUserData = useGlobalStore ( ( state ) => state . setUserData ) ;
2831 const email = useGlobalStore ( ( state ) => state . userData ?. email ) ;
32+ const { refresh } = useTickets ( ) ;
2933
30- // Use the tickets hook from store
31- const { tickets, loading, qrCodes, refresh } = useTickets ( ) ;
32-
33- // Additional email state
3434 const [ checkYourEmail , setCheckYourEmail ] = useState ( '' ) ;
3535 const [ verificationCode , setVerificationCode ] = useState ( '' ) ;
3636 const [ verifyingCode , setVerifyingCode ] = useState ( false ) ;
3737 const [ loadingAdditionalEmail , setLoadingAdditionalEmail ] = useState ( false ) ;
38+
39+ return (
40+ < >
41+ < div className = "flex gap-4 items-center space-evenly my-4" >
42+ < div className = "w-auto shrink grow h-px bg-gray-300" />
43+ < p className = "text-sm text-gray-600 shrink-0" >
44+ Your connected email addresses
45+ </ p >
46+ < div className = "w-auto shrink grow h-px bg-gray-300" />
47+ </ div >
48+
49+ < div className = "flex flex-col text-center text-xs font-medium" >
50+ < div className = "" > { email } </ div >
51+ { additionalTicketEmails . map ( ( email : string ) => (
52+ < div key = { email } className = "" >
53+ { email }
54+ </ div >
55+ ) ) }
56+ </ div >
57+
58+ < div className = "flex gap-4 items-center space-evenly mt-4" >
59+ < div className = "w-auto shrink grow h-px bg-gray-300" />
60+ < p className = "text-sm text-gray-600 shrink-0" >
61+ Is your ticket on a different email address?
62+ </ p >
63+ < div className = "w-auto shrink grow h-px bg-gray-300" />
64+ </ div >
65+
66+ < div className = "flex mt-4 flex-col sm:justify-center sm:items-center" >
67+ < VoxelButton
68+ size = "sm"
69+ className = ""
70+ onClick = { async ( ) => {
71+ const email = prompt ( 'Enter your email address' ) ;
72+
73+ if ( email ) {
74+ setLoadingAdditionalEmail ( true ) ;
75+ setVerificationCode ( '' ) ;
76+ setCheckYourEmail ( '' ) ;
77+
78+ const response = await fetchAuth < { email : string } > (
79+ '/api/auth/tickets/attach-email' ,
80+ {
81+ method : 'POST' ,
82+ body : JSON . stringify ( {
83+ email,
84+ redirectTo : 'https://app.devconnect.org/wallet/tickets' ,
85+ } ) ,
86+ }
87+ ) ;
88+
89+ if ( response . success ) {
90+ setCheckYourEmail ( email ) ;
91+ toast . success ( 'Check your email for a verification code.' ) ;
92+ } else {
93+ console . error ( 'Error adding email: ' + response . error ) ;
94+ toast . error ( 'Something went wrong. Try again later.' ) ;
95+ }
96+
97+ setLoadingAdditionalEmail ( false ) ;
98+ }
99+ } }
100+ >
101+ { loadingAdditionalEmail
102+ ? 'Preparing...'
103+ : 'Add another email address' }
104+ </ VoxelButton >
105+ { checkYourEmail && (
106+ < div className = "text-sm text-gray-800 mt-2 text-center mt-6 flex flex-col items-center max-w-[95%]" >
107+ < div >
108+ Check < span className = "font-bold" > { checkYourEmail } </ span > for a
109+ verification code:
110+ </ div >
111+ < input
112+ type = "text"
113+ className = "border border-neutral-300 w-full border-[1px] outline-none p-2 px-4 mt-2 text-center"
114+ value = { verificationCode }
115+ placeholder = "Enter verification code"
116+ onChange = { ( e ) => setVerificationCode ( e . target . value ) }
117+ />
118+
119+ < VoxelButton
120+ size = "sm"
121+ className = "mt-2"
122+ color = "green-1"
123+ disabled = { verificationCode . length !== 6 }
124+ onClick = { async ( ) => {
125+ setVerifyingCode ( true ) ;
126+
127+ const response = await fetchAuth < { email : string } > (
128+ '/api/auth/tickets/verify-email-ownership' ,
129+ {
130+ method : 'POST' ,
131+ body : JSON . stringify ( {
132+ emailToVerify : checkYourEmail ,
133+ verificationCode,
134+ } ) ,
135+ }
136+ ) ;
137+
138+ if ( response . success ) {
139+ toast . success ( 'Email verified successfully!' ) ;
140+ await ensureUserData ( setUserData ) ;
141+ await refresh ( ) ;
142+ } else {
143+ if ( response . error ) {
144+ toast . error ( response . error ) ;
145+ } else {
146+ toast . error ( 'Something went wrong. Try again later.' ) ;
147+ }
148+ }
149+
150+ setVerifyingCode ( false ) ;
151+ } }
152+ >
153+ { verifyingCode ? 'Verifying...' : 'Verify code' } { ' ' }
154+ { verificationCode . length !== 6 && '(6 digits required)' }
155+ </ VoxelButton >
156+ </ div >
157+ ) }
158+ </ div >
159+ </ >
160+ ) ;
161+ } ;
162+
163+ const Ticket = ( { ticket, qrCodes } : { ticket : any ; qrCodes : any } ) => {
164+ return (
165+ < div className = "relative max-w-[350px] mx-auto" >
166+ < Image src = { TicketImage } alt = "Ticket" />
167+ < div className = "absolute text-gray-600 top-[25%] left-[9%] mt-1 h-[32%] flex justify-center flex-col" >
168+ < div className = "flex flex-col relative items-start justify-start max-w-[80%]" >
169+ < div className = "font-bold text-[rgba(136,85,204,1)] bg-[rgba(252,252,252,0.7)] self-start text-2xl inline leading-tight" >
170+ { ticket . attendeeName }
171+ </ div >
172+ </ div >
173+
174+ < div className = "text-sm mt-1" >
175+ Ethereum World's Fair < br /> Attendee Ticket
176+ </ div >
177+ </ div >
178+
179+ < img
180+ src = { qrCodes [ ticket . secret ] }
181+ alt = "Ticket QR Code"
182+ className = "absolute bottom-[15%] right-[8.5%] h-[22%] aspect-square p-1 border border-solid border-gray-300 rounded-sm"
183+ />
184+ </ div >
185+ ) ;
186+ } ;
187+
188+ const TicketTab = RequiresAuthHOC ( ( ) => {
189+ // Use the tickets hook from store
190+ const { tickets, loading, qrCodes } = useTickets ( ) ;
38191 const hasTickets = tickets && tickets . length > 0 ;
39192
40193 return (
41- < div className = "w-full py-6 sm:py-8 px-4 sm:px-6 max-w-4xl mx-auto" >
194+ < div
195+ className = { cn (
196+ 'w-full py-6 sm:py-8 px-4 sm:px-6 mx-auto' ,
197+ 'gradient-background'
198+ ) }
199+ >
42200 < div className = "w-full mb-8" >
43201 < div className = "w-full space-y-2" >
44202 { loading && ! hasTickets && (
@@ -56,7 +214,25 @@ const TicketTab = RequiresAuthHOC(() => {
56214 </ div >
57215 ) }
58216
59- { hasTickets &&
217+ < ConnectedEmails />
218+
219+ { hasTickets && (
220+ < div className = "flex flex-col gap-8 mt-8" >
221+ { tickets . map ( ( order ) => (
222+ < >
223+ { order . tickets . map ( ( ticket , idx ) => (
224+ < Ticket
225+ ticket = { ticket }
226+ qrCodes = { qrCodes }
227+ key = { ticket . secret }
228+ />
229+ ) ) }
230+ </ >
231+ ) ) }
232+ </ div >
233+ ) }
234+
235+ { /* {hasTickets &&
60236 tickets.map((order) => (
61237 <>
62238 {order.tickets.map((ticket, idx) => (
@@ -100,126 +276,7 @@ const TicketTab = RequiresAuthHOC(() => {
100276 </div>
101277 ))}
102278 </>
103- ) ) }
104-
105- < div className = "flex gap-4 items-center space-evenly my-4" >
106- < div className = "w-auto shrink grow h-px bg-gray-300" />
107- < p className = "text-sm text-gray-600 shrink-0" >
108- Your connected email addresses
109- </ p >
110- < div className = "w-auto shrink grow h-px bg-gray-300" />
111- </ div >
112-
113- < div className = "flex flex-col text-center text-xs font-medium" >
114- < div className = "" > { email } </ div >
115- { additionalTicketEmails . map ( ( email : string ) => (
116- < div key = { email } className = "" >
117- { email }
118- </ div >
119- ) ) }
120- </ div >
121-
122- < div className = "flex gap-4 items-center space-evenly mt-4" >
123- < div className = "w-auto shrink grow h-px bg-gray-300" />
124- < p className = "text-sm text-gray-600 shrink-0" >
125- Is your ticket on a different email address?
126- </ p >
127- < div className = "w-auto shrink grow h-px bg-gray-300" />
128- </ div >
129-
130- < div className = "flex mt-4 flex-col sm:justify-center sm:items-center" >
131- < VoxelButton
132- size = "sm"
133- className = ""
134- onClick = { async ( ) => {
135- const email = prompt ( 'Enter your email address' ) ;
136-
137- if ( email ) {
138- setLoadingAdditionalEmail ( true ) ;
139- setVerificationCode ( '' ) ;
140- setCheckYourEmail ( '' ) ;
141-
142- const response = await fetchAuth < { email : string } > (
143- '/api/auth/tickets/attach-email' ,
144- {
145- method : 'POST' ,
146- body : JSON . stringify ( {
147- email,
148- redirectTo : 'https://app.devconnect.org/wallet/tickets' ,
149- } ) ,
150- }
151- ) ;
152-
153- if ( response . success ) {
154- setCheckYourEmail ( email ) ;
155- toast . success ( 'Check your email for a verification code.' ) ;
156- } else {
157- console . error ( 'Error adding email: ' + response . error ) ;
158- toast . error ( 'Something went wrong. Try again later.' ) ;
159- }
160-
161- setLoadingAdditionalEmail ( false ) ;
162- }
163- } }
164- >
165- { loadingAdditionalEmail
166- ? 'Preparing...'
167- : 'Add another email address' }
168- </ VoxelButton >
169- { checkYourEmail && (
170- < div className = "text-sm text-gray-800 mt-2 text-center mt-6 flex flex-col items-center max-w-[95%]" >
171- < div >
172- Check < span className = "font-bold" > { checkYourEmail } </ span > for
173- a verification code:
174- </ div >
175- < input
176- type = "text"
177- className = "border border-neutral-300 w-full border-[1px] outline-none p-2 px-4 mt-2 text-center"
178- value = { verificationCode }
179- placeholder = "Enter verification code"
180- onChange = { ( e ) => setVerificationCode ( e . target . value ) }
181- />
182-
183- < VoxelButton
184- size = "sm"
185- className = "mt-2"
186- color = "green-1"
187- disabled = { verificationCode . length !== 6 }
188- onClick = { async ( ) => {
189- setVerifyingCode ( true ) ;
190-
191- const response = await fetchAuth < { email : string } > (
192- '/api/auth/tickets/verify-email-ownership' ,
193- {
194- method : 'POST' ,
195- body : JSON . stringify ( {
196- emailToVerify : checkYourEmail ,
197- verificationCode,
198- } ) ,
199- }
200- ) ;
201-
202- if ( response . success ) {
203- toast . success ( 'Email verified successfully!' ) ;
204- await ensureUserData ( setUserData ) ;
205- await refresh ( ) ;
206- } else {
207- if ( response . error ) {
208- toast . error ( response . error ) ;
209- } else {
210- toast . error ( 'Something went wrong. Try again later.' ) ;
211- }
212- }
213-
214- setVerifyingCode ( false ) ;
215- } }
216- >
217- { verifyingCode ? 'Verifying...' : 'Verify code' } { ' ' }
218- { verificationCode . length !== 6 && '(6 digits required)' }
219- </ VoxelButton >
220- </ div >
221- ) }
222- </ div >
279+ ))} */ }
223280 </ div >
224281 </ div >
225282 </ div >
0 commit comments