1- import { useEffect , useRef , useState } from "react " ;
1+ import { Button } from "@/components/ui/button " ;
22import {
33 Dialog ,
44 DialogContent ,
55 DialogHeader ,
66 DialogTitle ,
77} from "@/components/ui/dialog" ;
8- import { Button } from "@/components/ui/button" ;
9- import { Copy , Check , Loader2 , Download } from "lucide-react" ;
108import { cn } from "@/lib/utils" ;
119import { invoke } from "@tauri-apps/api/core" ;
10+ import { Check , Copy , Download , Loader2 } from "lucide-react" ;
11+ import { useEffect , useRef , useState } from "react" ;
1212import { toast } from "sonner" ;
1313
1414interface ShareStatsModalProps {
@@ -31,6 +31,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
3131 const [ copied , setCopied ] = useState ( false ) ;
3232 const [ imageDataUrl , setImageDataUrl ] = useState < string > ( "" ) ;
3333 const [ isLoading , setIsLoading ] = useState ( true ) ;
34+ const [ isCopying , setIsCopying ] = useState ( false ) ;
3435
3536 useEffect ( ( ) => {
3637 if ( open ) {
@@ -68,7 +69,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
6869 const gradient = ctx . createLinearGradient ( 0 , 0 , canvas . width , canvas . height ) ;
6970 gradient . addColorStop ( 0 , "#0f0f0f" ) ;
7071 gradient . addColorStop ( 1 , "#1a1a1a" ) ;
71-
72+
7273 ctx . fillStyle = gradient ;
7374 ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
7475
@@ -123,7 +124,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
123124 const col = index % 2 ;
124125 const x = gridStartX + col * ( cardWidth + cardGap ) ;
125126 const y = gridStartY + row * ( cardHeight + cardGap ) ;
126-
127+
127128 // Simple card background
128129 ctx . fillStyle = "rgba(255, 255, 255, 0.05)" ;
129130 ctx . strokeStyle = "rgba(255, 255, 255, 0.1)" ;
@@ -134,7 +135,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
134135 ctx . stroke ( ) ;
135136
136137 // Card label - scaled up
137- ctx . font = "40px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
138+ ctx . font = "48px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
138139 ctx . fillStyle = "rgba(255, 255, 255, 0.5)" ;
139140 ctx . textAlign = "center" ;
140141 ctx . fillText ( card . label , x + cardWidth / 2 , y + 90 ) ;
@@ -145,22 +146,22 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
145146 ctx . fillText ( card . value , x + cardWidth / 2 , y + 220 ) ;
146147
147148 // Card subtitle - scaled up
148- ctx . font = "36px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
149+ ctx . font = "40px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
149150 ctx . fillStyle = "rgba(255, 255, 255, 0.4)" ;
150151 ctx . fillText ( card . subtitle , x + cardWidth / 2 , y + 290 ) ;
151152 } ) ;
152153
153- // Current Streak at the bottom - scaled up
154- if ( stats . currentStreak > 0 ) {
155- ctx . font = "bold 80px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
156- ctx . fillStyle = "#ffffff" ;
157- ctx . textAlign = "center" ;
158- const streakY = gridStartY + 2 * ( cardHeight + cardGap ) + 120 ;
159- ctx . fillText ( `🔥 ${ stats . currentStreak } Day Streak` , canvas . width / 2 , streakY ) ;
160- }
154+ // // Current Streak at the bottom - scaled up
155+ // if (stats.currentStreak > 0) {
156+ // ctx.font = "bold 80px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
157+ // ctx.fillStyle = "#ffffff";
158+ // ctx.textAlign = "center";
159+ // const streakY = gridStartY + 2 * (cardHeight + cardGap) + 120;
160+ // ctx.fillText(`🔥 ${stats.currentStreak} Day Streak`, canvas.width / 2, streakY);
161+ // }
161162
162163 // Website at the bottom - scaled up
163- ctx . font = "48px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
164+ ctx . font = "56px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif" ;
164165 ctx . fillStyle = "rgba(255, 255, 255, 0.6)" ;
165166 ctx . textAlign = "center" ;
166167 ctx . fillText ( "voicetypr.com" , canvas . width / 2 , canvas . height - 80 ) ;
@@ -171,20 +172,24 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
171172 } ;
172173
173174 const copyImageToClipboard = async ( ) => {
174- if ( ! canvasRef . current || ! imageDataUrl ) return ;
175+ if ( ! canvasRef . current || ! imageDataUrl || isCopying ) return ;
175176
177+ setIsCopying ( true ) ;
178+
176179 try {
177180 // Use Tauri's clipboard API for system-level copy
178- await invoke ( "copy_image_to_clipboard" , {
179- imageDataUrl : imageDataUrl
181+ await invoke ( "copy_image_to_clipboard" , {
182+ imageDataUrl : imageDataUrl
180183 } ) ;
181-
184+
182185 setCopied ( true ) ;
183186 toast . success ( "Stats image copied to clipboard!" ) ;
184187 setTimeout ( ( ) => setCopied ( false ) , 2000 ) ;
185188 } catch ( err ) {
186189 console . error ( "Failed to copy image to clipboard:" , err ) ;
187190 toast . error ( "Failed to copy image. Try the download button instead." ) ;
191+ } finally {
192+ setIsCopying ( false ) ;
188193 }
189194 } ;
190195
@@ -193,7 +198,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
193198
194199 try {
195200 const fileName = `voicetypr-stats-${ Date . now ( ) } .png` ;
196-
201+
197202 // Use Tauri's save dialog to let user choose location
198203 const { save } = await import ( '@tauri-apps/plugin-dialog' ) ;
199204 const filePath = await save ( {
@@ -206,7 +211,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
206211
207212 if ( filePath ) {
208213 // Use the Rust backend to save the file (best practice)
209- await invoke ( "save_image_to_file" , {
214+ await invoke ( "save_image_to_file" , {
210215 imageDataUrl : imageDataUrl ,
211216 filePath : filePath
212217 } ) ;
@@ -229,7 +234,7 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
229234 < DialogHeader >
230235 < DialogTitle > Share Your Stats</ DialogTitle >
231236 </ DialogHeader >
232-
237+
233238 < div className = "space-y-4" >
234239 { /* Canvas Preview */ }
235240 < div className = "relative rounded-lg overflow-hidden bg-black/5 border border-border/50 min-h-[300px]" >
@@ -252,12 +257,18 @@ export function ShareStatsModal({ open, onOpenChange, stats }: ShareStatsModalPr
252257 < div className = "flex justify-center gap-2" >
253258 < Button
254259 onClick = { copyImageToClipboard }
260+ disabled = { isCopying || ! imageDataUrl }
255261 className = { cn (
256- "gap-2" ,
262+ "gap-2 min-w-[120px] " ,
257263 copied && "bg-green-600 hover:bg-green-600"
258264 ) }
259265 >
260- { copied ? (
266+ { isCopying ? (
267+ < >
268+ < Loader2 className = "h-4 w-4 animate-spin" />
269+ Copying...
270+ </ >
271+ ) : copied ? (
261272 < >
262273 < Check className = "h-4 w-4" />
263274 Copied!
0 commit comments