11import { useState , useEffect } from "react" ;
22import { Link } from "wouter" ;
3- import { CheckCircle , XCircle , ArrowRight , ShieldCheck , Loader2 } from "lucide-react" ;
3+ import { CheckCircle , XCircle , ArrowRight , ShieldCheck , Loader2 , Play } from "lucide-react" ;
44import { Signal } from "@/lib/course-data" ;
55import { Button } from "@/components/ui/button" ;
66import { Card , CardContent } from "@/components/ui/card" ;
77import { RadioGroup , RadioGroupItem } from "@/components/ui/radio-group" ;
88import { Label } from "@/components/ui/label" ;
99import { useAleoWallet } from "@/hooks/use-aleo-wallet" ;
1010import { useToast } from "@/hooks/use-toast" ;
11+ import { motion } from "framer-motion" ;
1112
1213export function SignalCard ( { signal } : { signal : Signal } ) {
1314 return (
1415 < Link href = { `/lesson/${ signal . id } ` } >
15- < div className = "group cursor-pointer h-full" >
16- < Card className = "h-full overflow-hidden border-2 border-transparent transition-all hover:border-primary hover:shadow-md" >
17- < div className = "aspect-square bg-muted p-6 flex items-center justify-center relative overflow-hidden" >
16+ < motion . div
17+ className = "group cursor-pointer h-full"
18+ whileHover = { { y : - 5 } }
19+ transition = { { duration : 0.2 } }
20+ >
21+ < div className = "h-full rounded-2xl bg-card border-2 border-transparent hover:border-primary/30 overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300" >
22+ < div className = "aspect-square bg-gradient-to-br from-muted/50 to-muted p-6 flex items-center justify-center relative overflow-hidden" >
1823 < img
1924 src = { signal . image }
2025 alt = { signal . name }
21- className = "w-full h-full object-contain transition-transform group-hover:scale-105 "
26+ className = "w-full h-full object-contain transition-transform duration-500 group-hover:scale-110 "
2227 />
23- < div className = "absolute inset-0 bg-primary/10 opacity-0 group-hover:opacity-100 transition-opacity" />
28+ < div className = "absolute inset-0 bg-gradient-to-t from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
29+ < div className = "absolute top-3 right-3" >
30+ < span className = "px-2 py-1 bg-white/90 dark:bg-slate-800/90 rounded-md text-xs font-bold uppercase tracking-wider shadow-sm backdrop-blur-sm" >
31+ { signal . category }
32+ </ span >
33+ </ div >
34+ < div className = "absolute bottom-3 left-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-300" >
35+ < div className = "flex items-center gap-2 text-primary font-medium text-sm bg-white/90 dark:bg-slate-800/90 rounded-lg px-3 py-2 backdrop-blur-sm shadow-sm" >
36+ < Play className = "h-4 w-4" />
37+ < span > Start Lesson</ span >
38+ < ArrowRight className = "h-4 w-4 ml-auto group-hover:translate-x-1 transition-transform" />
39+ </ div >
40+ </ div >
2441 </ div >
25- < CardContent className = "p-4" >
26- < div className = "flex items-center justify-between mb-2" >
27- < span className = "text-xs font-bold uppercase tracking-wider text-primary" > { signal . category } </ span >
42+ < div className = "p-5" >
43+ < div className = "flex items-center gap-2 mb-2" >
44+ < span className = "text-xs font-bold uppercase tracking-wider text-primary bg-primary/10 px-2 py-0.5 rounded-full" >
45+ { signal . category }
46+ </ span >
2847 </ div >
29- < h3 className = "text-xl font-bold font-oswald text-foreground mb-2 group-hover:text-primary transition-colors" >
48+ < h3 className = "text-lg font-bold font-oswald uppercase text-foreground mb-2 group-hover:text-primary transition-colors line-clamp-1 " >
3049 { signal . name }
3150 </ h3 >
3251 < p className = "text-sm text-muted-foreground line-clamp-2" >
3352 { signal . description }
3453 </ p >
35- </ CardContent >
36- </ Card >
37- </ div >
54+ </ div >
55+ </ div >
56+ </ motion . div >
3857 </ Link >
3958 ) ;
4059}
@@ -51,7 +70,6 @@ export function QuizComponent({ signal }: { signal: Signal }) {
5170
5271 const isCorrect = selected === signal . quiz . correctAnswer ;
5372
54- // Poll for transaction status if we have a txId
5573 useEffect ( ( ) => {
5674 let interval : NodeJS . Timeout ;
5775 if ( txId && ( txStatus === "Pending" || ! txStatus ) ) {
@@ -80,7 +98,6 @@ export function QuizComponent({ signal }: { signal: Signal }) {
8098 try {
8199 const programId = "crane_signals_cert.aleo" ;
82100 const functionName = "issue_certification" ;
83- // Inputs: recipient address, signal_id (u8)
84101 const inputs = [ address , `${ signal . id } u8` ] ;
85102
86103 const id = await requestTransaction ( programId , functionName , inputs , 0.1 ) ;
@@ -98,73 +115,100 @@ export function QuizComponent({ signal }: { signal: Signal }) {
98115 } ;
99116
100117 return (
101- < Card className = "bg-muted/30 border-none " >
118+ < Card className = "bg-gradient-to-br from-primary/5 to-secondary/5 border-primary/20 " >
102119 < CardContent className = "p-6" >
103- < h3 className = "text-lg font-bold font-oswald mb-4 flex items-center gap-2" >
104- < span className = "bg-primary text-primary-foreground px-2 py-0.5 rounded text-sm" > QUIZ</ span >
105- Test Your Knowledge
106- </ h3 >
120+ < div className = "flex items-center gap-3 mb-6" >
121+ < div className = "bg-gradient-to-br from-primary to-secondary p-2 rounded-lg" >
122+ < span className = "text-xs font-bold text-primary-foreground" > Q</ span >
123+ </ div >
124+ < h3 className = "text-lg font-bold font-oswald" > Test Your Knowledge</ h3 >
125+ </ div >
107126
108127 { ! submitted ? (
109- < div className = "space-y-4" >
128+ < motion . div
129+ className = "space-y-5"
130+ initial = { { opacity : 0 } }
131+ animate = { { opacity : 1 } }
132+ >
110133 < p className = "font-medium text-lg" > { signal . quiz . question } </ p >
111- < RadioGroup onValueChange = { ( v ) => setSelected ( parseInt ( v ) ) } >
134+ < RadioGroup
135+ onValueChange = { ( v ) => setSelected ( parseInt ( v ) ) }
136+ className = "space-y-3"
137+ >
112138 { signal . quiz . options . map ( ( option , idx ) => (
113- < div key = { idx } className = "flex items-center space-x-2" >
139+ < motion . div
140+ key = { idx }
141+ className = { `flex items-center space-x-3 p-3 rounded-lg border-2 transition-all cursor-pointer ${
142+ selected === idx
143+ ? "border-primary bg-primary/5"
144+ : "border-transparent hover:border-muted"
145+ } `}
146+ whileHover = { { scale : 1.01 } }
147+ whileTap = { { scale : 0.99 } }
148+ >
114149 < RadioGroupItem value = { idx . toString ( ) } id = { `option-${ idx } ` } />
115- < Label htmlFor = { `option-${ idx } ` } className = "cursor-pointer" > { option } </ Label >
116- </ div >
150+ < Label htmlFor = { `option-${ idx } ` } className = "cursor-pointer flex-1" >
151+ { option }
152+ </ Label >
153+ </ motion . div >
117154 ) ) }
118155 </ RadioGroup >
119156 < Button
120157 onClick = { ( ) => setSubmitted ( true ) }
121158 disabled = { selected === null }
122- className = "w-full sm:w-auto font-oswald uppercase"
159+ className = "w-full sm:w-auto font-oswald uppercase bg-gradient-to-r from-primary to-secondary hover:opacity-90 transition-all duration-200 shadow-sm "
123160 >
124- Check Answer
161+ Check Answer < ArrowRight className = "ml-2 h-4 w-4" />
125162 </ Button >
126- </ div >
163+ </ motion . div >
127164 ) : (
128- < div className = "space-y-4 animate-in fade-in slide-in-from-bottom-2" >
129- < div className = { `flex items-center gap-3 text-lg font-bold ${ isCorrect ? 'text-green-600' : 'text-red-600' } ` } >
165+ < motion . div
166+ className = "space-y-5 animate-in fade-in slide-in-from-bottom-2"
167+ initial = { { opacity : 0 , y : 10 } }
168+ animate = { { opacity : 1 , y : 0 } }
169+ >
170+ < div className = { `flex items-center gap-3 text-lg font-bold p-4 rounded-xl ${
171+ isCorrect
172+ ? "bg-green-50 text-green-700 border-2 border-green-200"
173+ : "bg-red-50 text-red-700 border-2 border-red-200"
174+ } `} >
130175 { isCorrect ? < CheckCircle className = "h-6 w-6" /> : < XCircle className = "h-6 w-6" /> }
131- { isCorrect ? "Correct! " : "Incorrect" }
176+ { isCorrect ? "Excellent! You've mastered this signal. " : `The correct answer is: ${ signal . quiz . options [ signal . quiz . correctAnswer ] } ` }
132177 </ div >
133- < p className = "text-muted-foreground" >
134- { isCorrect
135- ? "Great job! You've mastered this signal."
136- : `The correct answer is: ${ signal . quiz . options [ signal . quiz . correctAnswer ] } `
137- }
138- </ p >
178+
139179 < div className = "flex flex-col sm:flex-row gap-3" >
140- < Button variant = "outline" onClick = { ( ) => {
141- setSubmitted ( false ) ;
142- setSelected ( null ) ;
143- setTxId ( null ) ;
144- setTxStatus ( null ) ;
145- } } >
180+ < Button
181+ variant = "outline"
182+ onClick = { ( ) => {
183+ setSubmitted ( false ) ;
184+ setSelected ( null ) ;
185+ setTxId ( null ) ;
186+ setTxStatus ( null ) ;
187+ } }
188+ className = "font-oswald uppercase"
189+ >
146190 Try Again
147191 </ Button >
148192
149193 { isCorrect && ! txId && (
150194 < Button
151195 onClick = { handleMintCertificate }
152196 disabled = { minting }
153- className = "bg-green-600 hover:bg-green-700 text-white gap-2"
197+ className = "bg-gradient-to-r from- green-600 to-emerald-600 hover:opacity-90 text-white gap-2 shadow-sm "
154198 >
155199 { minting ? < Loader2 className = "h-4 w-4 animate-spin" /> : < ShieldCheck className = "h-4 w-4" /> }
156200 { address ? "Mint ZK-Certificate" : "Connect Wallet to Mint" }
157201 </ Button >
158202 ) }
159203
160204 { txId && (
161- < div className = "flex items-center gap-2 px-4 py-2 bg-background border rounded-md text-sm font-medium" >
205+ < div className = "flex items-center gap-2 px-4 py-2 bg-background border rounded-xl text-sm font-medium shadow-sm " >
162206 < div className = { `h-2 w-2 rounded-full ${ txStatus === "Completed" ? "bg-green-500" : "bg-amber-500 animate-pulse" } ` } />
163207 Status: { txStatus || "Processing..." }
164208 </ div >
165209 ) }
166210 </ div >
167- </ div >
211+ </ motion . div >
168212 ) }
169213 </ CardContent >
170214 </ Card >
0 commit comments