11"use client" ;
22
3- import { Clipboard , Download , RefreshCw , Check } from "lucide-react" ;
4- import { useEffect , useMemo , useState } from "react" ;
3+ import {
4+ Clipboard ,
5+ Download ,
6+ RefreshCw ,
7+ Check ,
8+ X ,
9+ PartyPopper ,
10+ Terminal ,
11+ Info ,
12+ } from "lucide-react" ;
13+ import { useEffect , useState } from "react" ;
14+ import Confetti from "react-confetti" ;
15+ import { cn } from "@/lib/utils" ;
516
617interface FormStageDoneProps {
718 markdown : string ;
819 onReset : ( ) => void ;
20+ onClose : ( ) => void ;
921}
1022
11- export default function FormStageDone ( { markdown, onReset } : FormStageDoneProps ) {
23+ function useWindowSize ( ) {
24+ const [ windowSize , setWindowSize ] = useState ( {
25+ width : typeof window !== "undefined" ? window . innerWidth : 0 ,
26+ height : typeof window !== "undefined" ? window . innerHeight : 0 ,
27+ } ) ;
28+
29+ useEffect ( ( ) => {
30+ function handleResize ( ) {
31+ setWindowSize ( {
32+ width : window . innerWidth ,
33+ height : window . innerHeight ,
34+ } ) ;
35+ }
36+
37+ window . addEventListener ( "resize" , handleResize ) ;
38+ return ( ) => window . removeEventListener ( "resize" , handleResize ) ;
39+ } , [ ] ) ;
40+
41+ return windowSize ;
42+ }
43+
44+ export default function FormStageDone ( { markdown, onReset, onClose } : FormStageDoneProps ) {
1245 const [ copied , setCopied ] = useState ( false ) ;
1346 const [ showConfetti , setShowConfetti ] = useState ( true ) ;
14- const confettiPieces = useMemo (
15- ( ) =>
16- Array . from ( { length : 24 } , ( _ , index ) => ( {
17- id : index ,
18- left : `${ Math . random ( ) * 100 } %` ,
19- delay : `${ Math . random ( ) * 0.6 } s` ,
20- duration : `${ 3.2 + Math . random ( ) * 1.4 } s` ,
21- rotation : `${ Math . random ( ) * 360 } deg` ,
22- color : [ "#f59e0b" , "#22c55e" , "#3b82f6" , "#ef4444" , "#a855f7" , "#14b8a6" ] [
23- index % 6
24- ] ,
25- } ) ) ,
26- [ ] ,
27- ) ;
47+ const [ isClosing , setIsClosing ] = useState ( false ) ;
48+ const { width, height } = useWindowSize ( ) ;
2849
2950 useEffect ( ( ) => {
30- const timeout = window . setTimeout ( ( ) => setShowConfetti ( false ) , 4200 ) ;
51+ const timeout = window . setTimeout ( ( ) => setShowConfetti ( false ) , 8000 ) ;
3152 return ( ) => window . clearTimeout ( timeout ) ;
3253 } , [ ] ) ;
3354
55+ const handleClose = ( ) => {
56+ setIsClosing ( true ) ;
57+ setTimeout ( onClose , 200 ) ;
58+ } ;
59+
3460 const handleCopy = async ( ) => {
3561 await navigator . clipboard . writeText ( markdown ) ;
3662 setCopied ( true ) ;
@@ -50,102 +76,127 @@ export default function FormStageDone({ markdown, onReset }: FormStageDoneProps)
5076 } ;
5177
5278 return (
53- < div className = "relative grid h-full place-content-center gap-6 overflow-hidden px-6 text-center" >
79+ < div className = "fixed inset-0 z-[100] flex items-center justify-center p-4 sm:p-6" >
80+ { /* Backdrop */ }
81+ < div
82+ className = { cn (
83+ "absolute inset-0 bg-black/70 backdrop-blur-[2px] transition-opacity duration-300 ease-in-out" ,
84+ isClosing ? "opacity-0" : "animate-in fade-in opacity-100" ,
85+ ) }
86+ onClick = { handleClose }
87+ aria-hidden = "true"
88+ />
89+
90+ { /* Confetti */ }
5491 { showConfetti && (
55- < div aria-hidden = "true" className = "pointer-events-none absolute inset-0" >
56- { confettiPieces . map ( ( piece ) => (
57- < span
58- key = { piece . id }
59- className = "absolute top-0 h-3 w-2 rounded-full opacity-90"
60- style = { {
61- left : piece . left ,
62- backgroundColor : piece . color ,
63- animation : `done-confetti-fall ${ piece . duration } ease-out ${ piece . delay } forwards` ,
64- transform : `translateY(-12vh) rotate(${ piece . rotation } )` ,
65- } }
66- />
67- ) ) }
68- </ div >
92+ < Confetti
93+ width = { width }
94+ height = { height }
95+ recycle = { false }
96+ numberOfPieces = { 2000 }
97+ gravity = { 0.25 }
98+ initialVelocityY = { 25 }
99+ tweenDuration = { 5000 }
100+ style = { { zIndex : 110 } }
101+ colors = { [ "#238636" , "#2ea043" , "#3fb950" , "#58a6ff" , "#1f6feb" , "#f0883e" ] }
102+ />
69103 ) }
70104
71- { /* Success icon */ }
72- < div className = "relative z-10 flex justify-center" >
73- < div className = "bg-success/10 border-success/30 flex h-20 w-20 items-center justify-center rounded-full border-2" >
74- < span className = "text-4xl" > 🎉</ span >
105+ { /* Modal */ }
106+ < div
107+ className = { cn (
108+ "bg-background border-border relative z-[120] w-full max-w-lg overflow-hidden rounded-xl border shadow-2xl transition-all duration-200 ease-in-out sm:rounded-lg" ,
109+ isClosing
110+ ? "translate-y-4 scale-95 opacity-0"
111+ : "animate-in fade-in zoom-in-95 slide-in-from-bottom-4 translate-y-0 opacity-100" ,
112+ ) }
113+ >
114+ { /* Header */ }
115+ < div className = "bg-background_lighter border-border flex items-center justify-between border-b px-5 py-4" >
116+ < div className = "flex items-center gap-2.5" >
117+ < div className = "bg-success/20 text-success rounded-md p-1.5" >
118+ < PartyPopper size = { 18 } />
119+ </ div >
120+ < h2 className = "text-foreground-50 text-xl font-bold" >
121+ Your README is ready to go!
122+ </ h2 >
123+ </ div >
124+ < button
125+ onClick = { handleClose }
126+ className = "text-foreground-400 hover:text-foreground-100 hover:bg-border/30 rounded-md p-1 transition-colors"
127+ >
128+ < X size = { 20 } />
129+ </ button >
75130 </ div >
76- </ div >
77131
78- < header className = "relative z-10 space-y-2" >
79- < h2 className = "text-2xl font-bold" > Your README is Ready!</ h2 >
80- < p className = "text-foreground-400 m-auto max-w-sm text-sm" >
81- Your live preview stays on the right, and this final step is all about export.
82- Copy the markdown or download a ready-to-use{ " " }
83- < span className = "text-foreground-100 font-medium" > README.md</ span > .
84- </ p >
85- </ header >
86-
87- < div className = "relative z-10 flex flex-col gap-3" >
88- { /* Copy */ }
89- < button
90- type = "button"
91- onClick = { handleCopy }
92- className = { `flex cursor-pointer items-center justify-center gap-2 rounded-md px-4 py-2.5 text-sm font-semibold text-white transition-colors ${
93- copied ? "bg-success" : "bg-accent hover:bg-accent-600"
94- } `}
95- >
96- { copied ? (
97- < >
98- < Check size = { 16 } />
99- Copied to Clipboard!
100- </ >
101- ) : (
102- < >
103- < Clipboard size = { 16 } />
104- Copy README Markdown
105- </ >
106- ) }
107- </ button >
108-
109- { /* Download */ }
110- < button
111- type = "button"
112- onClick = { handleDownload }
113- className = "border-border hover:bg-background flex cursor-pointer items-center justify-center gap-2 rounded-md border px-4 py-2.5 text-sm font-semibold transition-colors"
114- >
115- < Download size = { 16 } />
116- Download README.md
117- </ button >
118- </ div >
132+ { /* Content */ }
133+ < div className = "p-6 sm:p-8" >
134+ < div className = "mb-6 text-left" >
135+ < p className = "text-foreground-400 text-sm leading-relaxed" >
136+ We've generated your personalized GitHub Profile README.
137+ </ p >
138+ </ div >
119139
120- < div className = "border-border relative z-10 border-t pt-4" >
121- < p className = "text-foreground-500 mb-3 text-xs" >
122- Want to make changes? Head back to any step — the preview updates live.
123- </ p >
124- < button
125- type = "button"
126- onClick = { onReset }
127- className = "text-foreground-400 hover:text-foreground-100 flex cursor-pointer items-center justify-center gap-2 text-sm transition-colors"
128- >
129- < RefreshCw size = { 14 } />
130- Start Over
131- </ button >
132- </ div >
140+ { /* GitHub-style Info Box */ }
141+ < div className = "border-accent/40 bg-accent/5 mb-8 flex gap-3 rounded-lg border p-4" >
142+ < Info className = "text-accent mt-0.5 shrink-0" size = { 18 } />
143+ < div className = "text-foreground-200 text-sm leading-relaxed" >
144+ < span className = "text-foreground-400" >
145+ Need to make changes? Close this modal and head back to any step to
146+ refine your details.
147+ </ span >
148+ </ div >
149+ </ div >
133150
134- < style jsx > { `
135- @keyframes done-confetti-fall {
136- 0% {
137- opacity: 0;
138- transform: translateY(-12vh) rotate(0deg) scale(0.85);
139- }
140- 10% {
141- opacity: 1;
142- }
143- 100% {
144- opacity: 0;
145- transform: translateY(115vh) rotate(540deg) scale(1);
146- }
147- }
148- ` } </ style >
151+ < div className = "grid gap-4" >
152+ { /* Primary Action: Copy */ }
153+ < button
154+ type = "button"
155+ onClick = { handleCopy }
156+ className = { cn (
157+ "border-success flex w-full cursor-pointer items-center justify-center gap-2 rounded-md border py-2.5 text-sm font-semibold transition-all duration-200" ,
158+ copied
159+ ? "bg-success text-white"
160+ : "bg-success hover:bg-success-500 shadow-success/20 text-white shadow-sm" ,
161+ ) }
162+ >
163+ { copied ? (
164+ < >
165+ < Check size = { 16 } />
166+ Copied!
167+ </ >
168+ ) : (
169+ < >
170+ < Clipboard size = { 16 } />
171+ Copy Markdown
172+ </ >
173+ ) }
174+ </ button >
175+
176+ { /* Secondary Action: Download */ }
177+ < button
178+ type = "button"
179+ onClick = { handleDownload }
180+ className = "border-border hover:bg-background_lighter hover:text-foreground-50 flex cursor-pointer items-center justify-center gap-2 rounded-md border bg-transparent px-4 py-2.5 text-sm font-semibold text-white transition-all duration-200"
181+ >
182+ < Download size = { 18 } />
183+ Download README.md
184+ </ button >
185+ </ div >
186+ </ div >
187+
188+ { /* Footer */ }
189+ < div className = "bg-background_lighter border-border flex items-center justify-end border-t px-6 py-4" >
190+ < button
191+ type = "button"
192+ onClick = { onReset }
193+ className = "text-foreground-400 hover:text-foreground-100 flex cursor-pointer items-center gap-2 text-sm font-medium transition-colors"
194+ >
195+ < RefreshCw size = { 14 } />
196+ Start Fresh
197+ </ button >
198+ </ div >
199+ </ div >
149200 </ div >
150201 ) ;
151202}
0 commit comments