11'use client' ;
22
3- import { useCallback , useState , type ReactElement } from 'react' ;
3+ import { useCallback , useEffect , useRef , useState , type ReactElement } from 'react' ;
44import Link from 'next/link' ;
55import { motion } from 'framer-motion' ;
66import { ControlsPanel } from './components/ControlsPanel' ;
@@ -23,12 +23,24 @@ export default function CustomizePage(): ReactElement {
2323 const [ size , setSize ] = useState < BadgeSize > ( 'medium' ) ;
2424 const [ exportFormat , setExportFormat ] = useState < ExportFormat > ( 'markdown' ) ;
2525 const [ copied , setCopied ] = useState ( false ) ;
26+ const [ copyStatusMessage , setCopyStatusMessage ] = useState ( '' ) ;
27+ const copyResetTimeoutRef = useRef < number | null > ( null ) ;
2628 const trimmedUsername = username . trim ( ) ;
2729 const hasUsername = trimmedUsername . length > 0 ;
2830 const isAutoTheme = theme === 'auto' ;
2931 const isRandomTheme = theme === 'random' ;
3032 const skipsCustomColors = isAutoTheme || isRandomTheme ;
3133
34+ useEffect ( ( ) => {
35+ return ( ) => {
36+ if ( copyResetTimeoutRef . current !== null ) {
37+ window . clearTimeout ( copyResetTimeoutRef . current ) ;
38+ }
39+ } ;
40+ } , [ ] ) ;
41+
42+ // Clear custom hex overrides when switching to auto — fixed colors
43+ // conflict with the dual-palette prefers-color-scheme switching.
3244 // Clear custom hex overrides when switching to virtual themes because
3345 // fixed colors conflict with their palette-selection behavior.
3446 const handleThemeChange = useCallback ( ( newTheme : string ) : void => {
@@ -89,12 +101,37 @@ export default function CustomizePage(): ReactElement {
89101 const previewSrc = `/api/streak?${ queryString } ` ;
90102 const exportSnippet = getExportSnippet ( exportFormat , queryString ) ;
91103
92- const copyExportSnippet = ( ) : void => {
104+ const announceCopyStatus = useCallback ( ( message : string ) : void => {
105+ setCopyStatusMessage ( '' ) ;
106+ window . setTimeout ( ( ) => {
107+ setCopyStatusMessage ( message ) ;
108+ } , 0 ) ;
109+ } , [ ] ) ;
110+
111+ const copyExportSnippet = async ( ) : Promise < void > => {
93112 if ( ! hasUsername ) return ;
94113
95- navigator . clipboard . writeText ( exportSnippet ) ;
96- setCopied ( true ) ;
97- setTimeout ( ( ) => setCopied ( false ) , 3000 ) ;
114+ try {
115+ await navigator . clipboard . writeText ( exportSnippet ) ;
116+ setCopied ( true ) ;
117+ announceCopyStatus (
118+ `${ exportFormat === 'markdown' ? 'Markdown' : 'HTML' } snippet copied to clipboard.`
119+ ) ;
120+
121+ if ( copyResetTimeoutRef . current !== null ) {
122+ window . clearTimeout ( copyResetTimeoutRef . current ) ;
123+ }
124+
125+ copyResetTimeoutRef . current = window . setTimeout ( ( ) => {
126+ setCopied ( false ) ;
127+ setCopyStatusMessage ( '' ) ;
128+ } , 3000 ) ;
129+ } catch {
130+ setCopied ( false ) ;
131+ announceCopyStatus (
132+ `Unable to copy the ${ exportFormat === 'markdown' ? 'Markdown' : 'HTML' } snippet.`
133+ ) ;
134+ }
98135 } ;
99136
100137 return (
@@ -273,6 +310,7 @@ export default function CustomizePage(): ReactElement {
273310 format = { exportFormat }
274311 snippet = { exportSnippet }
275312 copied = { copied }
313+ copyStatusMessage = { copyStatusMessage }
276314 hasUsername = { hasUsername }
277315 onFormatChange = { setExportFormat }
278316 onCopy = { copyExportSnippet }
0 commit comments