@@ -14,25 +14,160 @@ import { useRouter } from '../../router';
1414const capitalize = ( name : string ) => name [ 0 ] . toUpperCase ( ) + name . slice ( 1 ) ;
1515const lerp = ( start : number , end : number , amt : number ) => start + ( end - start ) * amt ;
1616
17+ const SuccessRing = ( { positionX, positionY } : { positionX : number ; positionY : number } ) => {
18+ const animationRef = useRef < number | null > ( null ) ;
19+ const [ currentPosition , setCurrentPosition ] = useState ( { x : 256 , y : 256 } ) ;
20+
21+ const canHover =
22+ typeof window === 'undefined' ? true : window . matchMedia ( '(hover: hover) and (pointer: fine)' ) . matches ;
23+
24+ useEffect ( ( ) => {
25+ if ( ! canHover ) {
26+ return ;
27+ }
28+ const animate = ( ) => {
29+ setCurrentPosition ( prev => {
30+ const amt = 0.15 ;
31+ const x = lerp ( prev . x , positionX , amt ) ;
32+ const y = lerp ( prev . y , positionY , amt ) ;
33+ return { x, y } ;
34+ } ) ;
35+ animationRef . current = requestAnimationFrame ( animate ) ;
36+ } ;
37+ animationRef . current = requestAnimationFrame ( animate ) ;
38+ return ( ) => {
39+ if ( animationRef . current ) {
40+ cancelAnimationFrame ( animationRef . current ) ;
41+ }
42+ } ;
43+ } , [ positionX , positionY , canHover ] ) ;
44+
45+ // Generate unique IDs for SVG elements to avoid conflicts with multiple component instances
46+ const maskId1 = useId ( ) ;
47+ const maskId2 = useId ( ) ;
48+ const maskId3 = useId ( ) ;
49+
50+ return (
51+ < Box
52+ elementDescriptor = { descriptors . checkoutSuccessRings }
53+ as = 'svg'
54+ // @ts -ignore - viewBox is a valid prop for svg
55+ viewBox = '0 0 512 512'
56+ sx = { {
57+ position : 'absolute' ,
58+ inset : 0 ,
59+ pointerEvents : 'none' ,
60+ } }
61+ aria-hidden
62+ >
63+ < defs >
64+ < radialGradient id = 'clerk-checkout-success-gradient' >
65+ < stop
66+ offset = '0%'
67+ style = { {
68+ stopColor : 'var(--ring-highlight)' ,
69+ } }
70+ />
71+ < stop
72+ offset = '100%'
73+ stopOpacity = '0'
74+ style = { {
75+ stopColor : 'var(--ring-highlight)' ,
76+ } }
77+ />
78+ </ radialGradient >
79+ < filter id = 'clerk-checkout-success-blur-effect' >
80+ < feGaussianBlur stdDeviation = '10' />
81+ </ filter >
82+ { [
83+ { r : 225 , maskStart : 10 , maskEnd : 90 , id : maskId1 } ,
84+ { r : 162.5 , maskStart : 15 , maskEnd : 85 , id : maskId2 } ,
85+ { r : 100 , maskStart : 20 , maskEnd : 80 , id : maskId3 } ,
86+ ] . map ( ( { maskStart, maskEnd, id } ) => (
87+ < linearGradient
88+ key = { id }
89+ id = { `gradient-${ id } ` }
90+ x1 = '0%'
91+ y1 = '0%'
92+ x2 = '0%'
93+ y2 = '100%'
94+ >
95+ < stop
96+ offset = { `${ maskStart + 5 } %` }
97+ stopColor = 'white'
98+ stopOpacity = '0'
99+ />
100+ < stop
101+ offset = { `${ maskStart + 35 } %` }
102+ stopColor = 'white'
103+ stopOpacity = '1'
104+ />
105+ < stop
106+ offset = { `${ maskEnd - 35 } %` }
107+ stopColor = 'white'
108+ stopOpacity = '1'
109+ />
110+ < stop
111+ offset = { `${ maskEnd - 5 } %` }
112+ stopColor = 'white'
113+ stopOpacity = '0'
114+ />
115+ </ linearGradient >
116+ ) ) }
117+ < mask id = 'clerk-checkout-success-mask' >
118+ { [
119+ { r : 225 , id : maskId1 } ,
120+ { r : 162.5 , id : maskId2 } ,
121+ { r : 100 , id : maskId3 } ,
122+ ] . map ( ( { r, id } ) => (
123+ < circle
124+ key = { id }
125+ cx = '256'
126+ cy = '256'
127+ r = { r }
128+ stroke = { `url(#gradient-${ id } )` }
129+ fill = 'none'
130+ strokeWidth = '1'
131+ />
132+ ) ) }
133+ </ mask >
134+ </ defs >
135+ < g mask = 'url(#clerk-checkout-success-mask)' >
136+ < rect
137+ width = '512'
138+ height = '512'
139+ style = { {
140+ fill : 'var(--ring-fill)' ,
141+ } }
142+ />
143+ { canHover && (
144+ < rect
145+ id = 'movingGradientHighlight'
146+ width = '256'
147+ height = '256'
148+ x = { currentPosition . x - 128 }
149+ y = { currentPosition . y - 128 }
150+ fill = 'url(#clerk-checkout-success-gradient)'
151+ filter = 'url(#clerk-checkout-success-blur-effect)'
152+ />
153+ ) }
154+ </ g >
155+ </ Box >
156+ ) ;
157+ } ;
158+
17159export const CheckoutComplete = ( ) => {
18160 const router = useRouter ( ) ;
19161 const { setIsOpen } = useDrawerContext ( ) ;
20162 const { newSubscriptionRedirectUrl } = useCheckoutContext ( ) ;
21163 const { checkout } = useCheckout ( ) ;
22164 const { totals, paymentSource, planPeriodStart, freeTrialEndsAt } = checkout ;
23165 const [ mousePosition , setMousePosition ] = useState ( { x : 256 , y : 256 } ) ;
24- const [ currentPosition , setCurrentPosition ] = useState ( { x : 256 , y : 256 } ) ;
25-
26- // Generate unique IDs for SVG elements to avoid conflicts with multiple component instances
27- const maskId1 = useId ( ) ;
28- const maskId2 = useId ( ) ;
29- const maskId3 = useId ( ) ;
30166
31167 const prefersReducedMotion = usePrefersReducedMotion ( ) ;
32168 const { animations : layoutAnimations } = useAppearance ( ) . parsedLayout ;
33169 const isMotionSafe = ! prefersReducedMotion && layoutAnimations === true ;
34170
35- const animationRef = useRef < number | null > ( null ) ;
36171 const checkoutSuccessRootRef = useRef < HTMLSpanElement > ( null ) ;
37172 const canHover =
38173 typeof window === 'undefined' ? true : window . matchMedia ( '(hover: hover) and (pointer: fine)' ) . matches ;
@@ -59,27 +194,6 @@ export const CheckoutComplete = () => {
59194 }
60195 } ;
61196
62- useEffect ( ( ) => {
63- if ( ! canHover ) {
64- return ;
65- }
66- const animate = ( ) => {
67- setCurrentPosition ( prev => {
68- const amt = 0.15 ;
69- const x = lerp ( prev . x , mousePosition . x , amt ) ;
70- const y = lerp ( prev . y , mousePosition . y , amt ) ;
71- return { x, y } ;
72- } ) ;
73- animationRef . current = requestAnimationFrame ( animate ) ;
74- } ;
75- animationRef . current = requestAnimationFrame ( animate ) ;
76- return ( ) => {
77- if ( animationRef . current ) {
78- cancelAnimationFrame ( animationRef . current ) ;
79- }
80- } ;
81- } , [ mousePosition , canHover ] ) ;
82-
83197 const handleClose = ( ) => {
84198 if ( newSubscriptionRedirectUrl ) {
85199 void router . navigate ( newSubscriptionRedirectUrl ) ;
@@ -135,111 +249,10 @@ export const CheckoutComplete = () => {
135249 ref = { checkoutSuccessRootRef }
136250 onMouseMove = { handleMouseMove }
137251 >
138- < Box
139- elementDescriptor = { descriptors . checkoutSuccessRings }
140- as = 'svg'
141- // @ts -ignore - viewBox is a valid prop for svg
142- viewBox = '0 0 512 512'
143- sx = { {
144- position : 'absolute' ,
145- inset : 0 ,
146- pointerEvents : 'none' ,
147- } }
148- aria-hidden
149- >
150- < defs >
151- < radialGradient id = 'clerk-checkout-success-gradient' >
152- < stop
153- offset = '0%'
154- style = { {
155- stopColor : 'var(--ring-highlight)' ,
156- } }
157- />
158- < stop
159- offset = '100%'
160- stopOpacity = '0'
161- style = { {
162- stopColor : 'var(--ring-highlight)' ,
163- } }
164- />
165- </ radialGradient >
166- < filter id = 'clerk-checkout-success-blur-effect' >
167- < feGaussianBlur stdDeviation = '10' />
168- </ filter >
169- { [
170- { r : 225 , maskStart : 10 , maskEnd : 90 , id : maskId1 } ,
171- { r : 162.5 , maskStart : 15 , maskEnd : 85 , id : maskId2 } ,
172- { r : 100 , maskStart : 20 , maskEnd : 80 , id : maskId3 } ,
173- ] . map ( ( { maskStart, maskEnd, id } ) => (
174- < linearGradient
175- key = { id }
176- id = { `gradient-${ id } ` }
177- x1 = '0%'
178- y1 = '0%'
179- x2 = '0%'
180- y2 = '100%'
181- >
182- < stop
183- offset = { `${ maskStart + 5 } %` }
184- stopColor = 'white'
185- stopOpacity = '0'
186- />
187- < stop
188- offset = { `${ maskStart + 35 } %` }
189- stopColor = 'white'
190- stopOpacity = '1'
191- />
192- < stop
193- offset = { `${ maskEnd - 35 } %` }
194- stopColor = 'white'
195- stopOpacity = '1'
196- />
197- < stop
198- offset = { `${ maskEnd - 5 } %` }
199- stopColor = 'white'
200- stopOpacity = '0'
201- />
202- </ linearGradient >
203- ) ) }
204- < mask id = 'clerk-checkout-success-mask' >
205- { [
206- { r : 225 , id : maskId1 } ,
207- { r : 162.5 , id : maskId2 } ,
208- { r : 100 , id : maskId3 } ,
209- ] . map ( ( { r, id } ) => (
210- < circle
211- key = { id }
212- cx = '256'
213- cy = '256'
214- r = { r }
215- stroke = { `url(#gradient-${ id } )` }
216- fill = 'none'
217- strokeWidth = '1'
218- />
219- ) ) }
220- </ mask >
221- </ defs >
222- < g mask = 'url(#clerk-checkout-success-mask)' >
223- < rect
224- width = '512'
225- height = '512'
226- style = { {
227- fill : 'var(--ring-fill)' ,
228- } }
229- />
230- { canHover && (
231- < rect
232- id = 'movingGradientHighlight'
233- width = '256'
234- height = '256'
235- x = { currentPosition . x - 128 }
236- y = { currentPosition . y - 128 }
237- fill = 'url(#clerk-checkout-success-gradient)'
238- filter = 'url(#clerk-checkout-success-blur-effect)'
239- />
240- ) }
241- </ g >
242- </ Box >
252+ < SuccessRing
253+ positionX = { mousePosition . x }
254+ positionY = { mousePosition . y }
255+ />
243256 < Box
244257 elementDescriptor = { descriptors . checkoutSuccessBadge }
245258 sx = { t => ( {
0 commit comments