@@ -8,13 +8,27 @@ import { onAuthStateChanged, User, EmailAuthProvider, reauthenticateWithCredenti
88import { doc , getDoc , getFirestore , deleteDoc } from 'firebase/firestore' ;
99import { PDFDocument , StandardFonts , rgb } from 'pdf-lib' ;
1010import QRCode from 'qrcode' ;
11+ import { decryptData } from '@/lib/utils' ;
12+ import { Button } from '@/components/ui/button' ;
13+ import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogFooter } from '@/components/ui/dialog' ;
14+ import { Input } from '@/components/ui/input' ;
15+ import { Alert , AlertDescription } from '@/components/ui/alert' ;
16+ import { PageWrapper } from '@/components/ui/page-wrapper' ;
17+ import { PinInputDialog } from '@/components/ui/pin-input-dialog' ;
18+ import { DeleteAccountDialog } from '@/components/ui/delete-account-dialog' ;
19+ import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
1120
1221const Dashboard = ( ) => {
1322 const [ user , setUser ] = useState < User | null > ( null ) ;
1423 const [ userName , setUserName ] = useState < string > ( '' ) ;
1524 const [ bloodGroup , setBloodGroup ] = useState < string > ( '' ) ;
1625 const [ tagID , setTagID ] = useState < string | null > ( null ) ;
1726 const [ loading , setLoading ] = useState < boolean > ( true ) ;
27+ const [ pinDialogOpen , setPinDialogOpen ] = useState ( false ) ;
28+ const [ pinError , setPinError ] = useState < string | null > ( null ) ;
29+ const [ verifyingPin , setVerifyingPin ] = useState ( false ) ;
30+ const [ rawData , setRawData ] = useState < any > ( null ) ;
31+ const [ deleteDialogOpen , setDeleteDialogOpen ] = useState ( false ) ;
1832 const router = useRouter ( ) ;
1933
2034 useEffect ( ( ) => {
@@ -37,6 +51,7 @@ const Dashboard = () => {
3751
3852 if ( userDoc . exists ( ) ) {
3953 const userData = userDoc . data ( ) ;
54+ setRawData ( userData ) ;
4055 setUserName ( userData . fullName ) ;
4156 setBloodGroup ( userData . bloodGroup ) ;
4257 } else {
@@ -54,7 +69,51 @@ const Dashboard = () => {
5469 return ( ) => unsubscribe ( ) ;
5570 } , [ router ] ) ;
5671
57- const handlePrintTag = async ( ) => {
72+ const handleInitiateTagDownload = ( ) => {
73+ if ( ! tagID || ! rawData ) return ;
74+
75+ // If data is encrypted, ask for PIN before generating the tag
76+ if ( rawData . isEncrypted && rawData . encryptedData ) {
77+ setPinDialogOpen ( true ) ;
78+ setPinError ( null ) ;
79+ } else {
80+ // Legacy unencrypted data, generate tag directly
81+ handlePrintTag ( null ) ;
82+ }
83+ } ;
84+
85+ const handleVerifyPin = async ( pin : string ) => {
86+ if ( ! pin || pin . length !== 4 || ! / ^ \d { 4 } $ / . test ( pin ) ) {
87+ setPinError ( 'Please enter a valid 4-digit PIN' ) ;
88+ return ;
89+ }
90+
91+ setVerifyingPin ( true ) ;
92+ setPinError ( null ) ;
93+
94+ try {
95+ // Try to decrypt the data with the provided PIN
96+ const decryptedDataStr = await decryptData ( rawData . encryptedData , pin ) ;
97+
98+ try {
99+ // If we can parse the JSON, the PIN is correct
100+ JSON . parse ( decryptedDataStr ) ;
101+
102+ // PIN verified, proceed with tag generation
103+ handlePrintTag ( pin ) ;
104+ setPinDialogOpen ( false ) ;
105+ } catch ( e ) {
106+ setPinError ( 'Incorrect PIN. Please try again.' ) ;
107+ }
108+ } catch ( error ) {
109+ console . error ( 'Error verifying PIN:' , error ) ;
110+ setPinError ( 'Incorrect PIN. Please try again.' ) ;
111+ } finally {
112+ setVerifyingPin ( false ) ;
113+ }
114+ } ;
115+
116+ const handlePrintTag = async ( verifiedPin : string | null ) => {
58117 if ( ! tagID ) return ;
59118
60119 const existingPdfBytes = await fetch ( '/template.pdf' ) . then ( res => res . arrayBuffer ( ) ) ;
@@ -66,8 +125,8 @@ const Dashboard = () => {
66125 const qrCodeOptions = {
67126 margin : 0 ,
68127 color : {
69- dark : '#FFFFFF' , // Red color
70- light : '#ff3131' , // White background
128+ dark : '#FFFFFF' , // Red color
129+ light : '#ff3131' , // White background
71130 } ,
72131 } ;
73132
@@ -97,6 +156,17 @@ const Dashboard = () => {
97156 color : rgb ( 1 , 1 , 1 ) ,
98157 } ) ;
99158
159+ // Add PIN to the PDF tag if verified
160+ if ( verifiedPin ) {
161+ firstPage . drawText ( `PIN: ${ verifiedPin } ` , {
162+ x : 150 ,
163+ y : 87 ,
164+ size : 14 ,
165+ font : await pdfDoc . embedFont ( StandardFonts . HelveticaBold ) ,
166+ color : rgb ( 1 , 1 , 1 ) ,
167+ } ) ;
168+ }
169+
100170 const pdfBytes = await pdfDoc . save ( ) ;
101171 const blob = new Blob ( [ pdfBytes ] , { type : 'application/pdf' } ) ;
102172 const url = URL . createObjectURL ( blob ) ;
@@ -124,93 +194,132 @@ const Dashboard = () => {
124194 router . push ( '/login' ) ;
125195 } catch ( error ) {
126196 console . error ( 'Error deleting account:' , error ) ;
197+ throw error ; // Re-throw to be caught by the dialog
127198 }
128199 } ;
129200
130- if ( loading ) {
131- return (
132- < div className = "flex items-center justify-center min-h-screen" >
133- < div className = "animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-red-500" > </ div >
134- </ div >
135- ) ;
136- }
137-
138201 return (
139- < div className = "min-h-screen bg-white dark:bg-black my-16 p-6" >
140- < div className = "max-w-4xl mx-auto bg-stone-100 dark:bg-stone-800 rounded-lg p-6" >
141- < h1 className = "text-2xl font-bold text-stone-800 dark:text-stone-200" >
142- Welcome, < span className = "text-red-500" > { userName || 'User' } </ span >
143- </ h1 >
144- < p className = "mt-2 text-stone-600 dark:text-stone-400" >
145- < strong > Tag ID:</ strong > { tagID || 'N/A' }
146- </ p >
147-
148- < div className = "grid grid-cols-1 sm:grid-cols-2 gap-4 mt-6" >
149- { tagID && (
150- < >
151- < button
152- onClick = { handlePrintTag }
153- className = "bg-black text-white py-2 px-4 rounded shadow hover:bg-stone-800 dark:hover:bg-stone-700 transition w-full"
154- >
155- Download / Print QR Tag < span className = "text-sm text-[#bbbbbb]" > PDF</ span >
156- </ button >
157- < Link href = { `/id?id=${ tagID } ` } passHref >
158- < button className = "bg-black text-white py-2 px-4 rounded shadow hover:bg-stone-800 dark:hover:bg-stone-700 transition w-full" >
159- View Profile
160- </ button >
161- </ Link >
162- </ >
163- ) }
164- < Link href = "/update" passHref >
165- < button className = "bg-black text-white py-2 px-4 rounded shadow hover:bg-stone-800 dark:hover:bg-stone-700 transition w-full" >
166- Modify Data
167- </ button >
168- </ Link >
169- < button
170- onClick = { ( ) => {
171- const password = prompt ( 'Please enter your password to confirm account deletion:' ) ;
172- if ( password ) {
173- handleDeleteAccount ( password ) ;
174- }
175- } }
176- className = "bg-black text-white py-2 px-4 rounded shadow hover:bg-stone-800 dark:hover:bg-stone-700 transition w-full"
177- >
178- Delete Account
179- </ button >
180- </ div >
202+ < PageWrapper isLoading = { loading } >
203+ < div className = "max-w-4xl mx-auto" >
204+ < Card className = "mb-6" >
205+ < CardHeader >
206+ < CardTitle className = "flex flex-col sm:flex-row sm:items-center justify-between" >
207+ < span >
208+ Welcome, < span className = "text-red-500" > { userName || 'User' } </ span >
209+ </ span >
210+ { tagID && (
211+ < span className = "text-sm font-normal mt-2 sm:mt-0" >
212+ Tag ID: < span className = "font-mono" > { tagID } </ span >
213+ </ span >
214+ ) }
215+ </ CardTitle >
216+ </ CardHeader >
217+ < CardContent >
218+ < div className = "grid grid-cols-1 sm:grid-cols-2 gap-4" >
219+ { tagID && (
220+ < >
221+ < Button
222+ onClick = { handleInitiateTagDownload }
223+ className = "w-full"
224+ >
225+ Download / Print QR Tag
226+ </ Button >
227+ < Button
228+ variant = "outline"
229+ asChild
230+ className = "w-full"
231+ >
232+ < Link href = { `/id?id=${ tagID } ` } > View Profile</ Link >
233+ </ Button >
234+ </ >
235+ ) }
236+ < Button
237+ variant = "outline"
238+ asChild
239+ className = "w-full"
240+ >
241+ < Link href = "/update" > Modify Data</ Link >
242+ </ Button >
243+ < Button
244+ variant = "destructive"
245+ onClick = { ( ) => setDeleteDialogOpen ( true ) }
246+ className = "w-full"
247+ >
248+ Delete Account
249+ </ Button >
250+ </ div >
251+ </ CardContent >
252+ </ Card >
181253
182- < div className = "mt-10" >
183- < h2 className = "text-xl font-semibold text-stone-800 dark:text-stone-200" > Safety Tips & QR Code Usage </ h2 >
184- < ul className = "list-disc list-inside mt-4 text-stone-600 dark:text-stone-400" >
185- < li > Place your QR code tag on your helmet, vehicle, or ID card for easy access.</ li >
186- < li > Ensure the QR code is not obstructed by scratches or dirt for proper scanning.</ li >
187- < li > Share your tag ID with trusted contacts for emergencies.</ li >
188- < li > Do not share sensitive personal details through the QR code.</ li >
189- </ ul >
190- </ div >
254+ < Card className = "mb-6" >
255+ < CardHeader >
256+ < CardTitle > Safety Tips & QR Code Usage </ CardTitle >
257+ </ CardHeader >
258+ < CardContent >
259+ < ul className = "list-disc list-inside space-y-2" >
260+ < li > Place your QR code tag on your helmet, vehicle, or ID card for easy access.</ li >
261+ < li > Ensure the QR code is not obstructed by scratches or dirt for proper scanning.</ li >
262+ < li > Share your tag ID with trusted contacts for emergencies.</ li >
263+ < li > Do not share sensitive personal details through the QR code.</ li >
264+ </ ul >
265+ </ CardContent >
266+ </ Card >
191267
192- < div className = "mt-10" >
193- < h2 className = "text-xl font-semibold text-stone-800 dark:text-stone-200" > Where to Use OpenTag</ h2 >
194- < ul className = "list-disc list-inside mt-4 text-stone-600 dark:text-stone-400" >
195- < li > Stick the QR code tag on your backpack, helmet, car, or any personal item.</ li >
196- < li > Print six tags on a sheet, cut them out, and stick them using tape.</ li >
197- < li > Ensure the tags are placed in visible and easily accessible locations.</ li >
198- < li > Replace the tags if they get damaged or worn out.</ li >
199- </ ul >
200- </ div >
201- < div className = "mt-10" >
202- < h2 className = "text-xl font-semibold text-stone-800 dark:text-stone-200" > Get Creative with Your QR Codes</ h2 >
203- < p className = "mt-4 text-stone-600 dark:text-stone-400" >
204- Feel free to customize your QR codes to match your style! You can use different colors, add logos, or even create unique designs. Just make sure the QR code remains scannable. Here are some tips:
205- </ p >
206- < ul className = "list-disc list-inside mt-4 text-stone-600 dark:text-stone-400" >
207- < li > Use high-contrast colors for the QR code and background.</ li >
208- < li > Experiment with different shapes and patterns, but keep the core structure intact.</ li >
209- < li > Test your custom QR codes with multiple devices to ensure they work properly.</ li >
210- </ ul >
211- </ div >
268+ < Card className = "mb-6" >
269+ < CardHeader >
270+ < CardTitle > Where to Use OpenTag</ CardTitle >
271+ </ CardHeader >
272+ < CardContent >
273+ < ul className = "list-disc list-inside space-y-2" >
274+ < li > Stick the QR code tag on your backpack, helmet, car, or any personal item.</ li >
275+ < li > Print six tags on a sheet, cut them out, and stick them using tape.</ li >
276+ < li > Ensure the tags are placed in visible and easily accessible locations.</ li >
277+ < li > Replace the tags if they get damaged or worn out.</ li >
278+ </ ul >
279+ </ CardContent >
280+ </ Card >
281+
282+ < Card >
283+ < CardHeader >
284+ < CardTitle > Get Creative with Your QR Codes</ CardTitle >
285+ </ CardHeader >
286+ < CardContent >
287+ < p className = "mb-4" >
288+ Feel free to customize your QR codes to match your style! You can use different colors, add logos, or even create unique designs. Just make sure the QR code remains scannable.
289+ </ p >
290+ < ul className = "list-disc list-inside space-y-2" >
291+ < li > Use high-contrast colors for the QR code and background.</ li >
292+ < li > Experiment with different shapes and patterns, but keep the core structure intact.</ li >
293+ < li > Test your custom QR codes with multiple devices to ensure they work properly.</ li >
294+ </ ul >
295+ </ CardContent >
296+ </ Card >
212297 </ div >
213- </ div >
298+
299+ { /* PIN Verification Dialog */ }
300+ < PinInputDialog
301+ isOpen = { pinDialogOpen }
302+ onClose = { ( ) => setPinDialogOpen ( false ) }
303+ onVerify = { handleVerifyPin }
304+ title = "Enter your PIN to download tag"
305+ description = "Your medical data is protected with a PIN. Please enter your 4-digit PIN to include it in your tag."
306+ isVerifying = { verifyingPin }
307+ error = { pinError }
308+ basicInfo = {
309+ < div className = "space-y-2" >
310+ < p > < strong > Name:</ strong > { userName } </ p >
311+ < p > < strong > Blood Group:</ strong > { bloodGroup } </ p >
312+ </ div >
313+ }
314+ />
315+
316+ { /* Delete Account Dialog */ }
317+ < DeleteAccountDialog
318+ isOpen = { deleteDialogOpen }
319+ onClose = { ( ) => setDeleteDialogOpen ( false ) }
320+ onConfirm = { handleDeleteAccount }
321+ />
322+ </ PageWrapper >
214323 ) ;
215324} ;
216325
0 commit comments