11
22
3- import React , { useState , type FormEvent } from 'react' ;
4- import { IconRefreshAlert , IconMailbox , IconSquareRoundedX } from '@tabler/icons-react' ;
5- import { markket } from '../content/ config' ;
3+ import React , { useState , useRef , useEffect , type FormEvent } from 'react' ;
4+ import { IconRefreshAlert , IconMailbox , IconSquareRoundedX , IconCheck } from '@tabler/icons-react' ;
5+ import { markket } from '../../markket. config' ;
66
77export interface SubscribeFormProps {
88 store : {
99 documentId : string ;
10- } ,
10+ } ;
1111}
1212
1313export function SubscribeForm ( { store } : SubscribeFormProps ) {
1414 const [ email , setEmail ] = useState ( '' ) ;
1515 const [ isSuccess , setIsSuccess ] = useState ( false ) ;
1616 const [ error , setError ] = useState ( '' ) ;
1717 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
18+ const successRef = useRef < HTMLDivElement | null > ( null ) ;
1819
19- const validateEmail = ( email : string ) => {
20- return / ^ \S + @ \S + $ / . test ( email ) ;
21- } ;
20+ useEffect ( ( ) => {
21+ if ( isSuccess && successRef . current ) successRef . current . focus ( ) ;
22+ } , [ isSuccess ] ) ;
23+
24+ useEffect ( ( ) => {
25+ if ( ! isSuccess ) return ;
26+ const t = setTimeout ( ( ) => setIsSuccess ( false ) , 6000 ) ;
27+ return ( ) => clearTimeout ( t ) ;
28+ } , [ isSuccess ] ) ;
29+
30+ const validateEmail = ( value : string ) => / ^ \S + @ \S + \. \S + $ / . test ( value ) ;
2231
2332 const handleSubmit = async ( e : FormEvent < HTMLFormElement > ) => {
2433 e . preventDefault ( ) ;
@@ -36,21 +45,18 @@ export function SubscribeForm({ store }: SubscribeFormProps) {
3645 method : 'POST' ,
3746 headers : {
3847 'Content-Type' : 'application/json' ,
39- ' Accept' : 'application/json' ,
48+ Accept : 'application/json' ,
4049 } ,
4150 body : JSON . stringify ( {
4251 data : {
4352 Email : email ,
4453 stores : store ?. documentId ? [ store . documentId ] : [ ] ,
45- }
54+ } ,
4655 } ) ,
4756 } ) ;
4857
4958 const data = await response . json ( ) ;
50-
51- if ( ! response . ok ) {
52- throw new Error ( data . message || 'Subscription failed' ) ;
53- }
59+ if ( ! response . ok ) throw new Error ( data ?. message || 'Subscription failed' ) ;
5460
5561 setIsSuccess ( true ) ;
5662 setEmail ( '' ) ;
@@ -63,61 +69,105 @@ export function SubscribeForm({ store }: SubscribeFormProps) {
6369 } ;
6470
6571 return (
66- < div className = "w-full max-w-md mx-auto" >
67- < form onSubmit = { handleSubmit } className = "space-y-4" >
68- { error && (
69- < p className = "text-red-600 text-sm font-medium" > { error } </ p >
70- ) }
71-
72- < div className = "flex gap-3" >
73- < input
74- type = "email"
75- value = { email }
76- onChange = { ( e ) => setEmail ( e . target . value ) }
77- placeholder = "your@email.com"
78- required
79- disabled = { isSubmitting }
80- className = "flex-1 px-4 py-2 rounded-lg border border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
81- />
82- < button
83- type = "submit"
84- disabled = { isSubmitting }
85- className = "px-6 py-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
86- >
87- { isSubmitting ? (
88- < span className = "flex items-center gap-2" >
89- < IconRefreshAlert size = { 16 } />
90- Subscribing...
91- </ span >
92- ) : (
93- < span className = "flex items-center gap-2" >
94- < IconMailbox size = { 16 } />
95- Subscribe
96- </ span >
97- ) }
98- </ button >
99- </ div >
100- </ form >
101-
102- { isSuccess && (
103- < div className = "fixed inset-0 flex items-center justify-center p-4 z-50" style = { { backgroundColor : 'rgba(5, 0, 80, 0.5)' } } onClick = { ( ) => setIsSuccess ( false ) } >
104- < div className = "bg-white rounded-xl p-6 max-w-sm w-full shadow-2xl" >
105- < div className = "flex justify-between items-start mb-4" >
106- < h3 className = "text-lg font-semibold text-gray-900" > Thank you!</ h3 >
107- < button
108- onClick = { ( ) => setIsSuccess ( false ) }
109- className = "text-gray-400 hover:text-gray-500"
110- >
111- < span className = "sr-only" > Close</ span >
112- < IconSquareRoundedX size = { 24 } />
113- </ button >
114- </ div >
115- < p className = "text-gray-600" >
116- You've been successfully subscribed to our newsletter.
117- </ p >
72+ < div className = "w-full max-w-4xl mx-auto my-16 px-4" >
73+ < div className = "relative overflow-hidden rounded-2xl shadow-2xl" style = { { background : 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(250,250,255,0.9) 100%)' } } >
74+ < div className = "pointer-events-none absolute -right-24 -top-24 w-64 h-64 rounded-full bg-gradient-to-tr from-pink-300 to-indigo-400 opacity-30 blur-3xl" />
75+
76+ < div className = "relative grid grid-cols-1 md:grid-cols-2 gap-0" >
77+ < div className = "p-8 md:p-10 flex flex-col justify-center" >
78+ < h3 className = "text-2xl md:text-3xl font-extrabold text-gray-900 leading-tight" > Join our newsletter</ h3 >
79+ < p className = "mt-2 text-gray-600 max-w-lg" > Short, delightful updates about products, design notes, and occasional exclusive offers. No spam — ever.</ p >
80+
81+ < ul className = "mt-6 space-y-3" >
82+ < li className = "flex items-start gap-3 text-sm text-gray-700" >
83+ < span className = "p-2 bg-white rounded-full shadow-sm text-green-600" > < IconCheck size = { 16 } /> </ span >
84+ < span className = "leading-tight" > Curated product updates and releases</ span >
85+ </ li >
86+ < li className = "flex items-start gap-3 text-sm text-gray-700" >
87+ < span className = "p-2 bg-white rounded-full shadow-sm text-green-600" > < IconCheck size = { 16 } /> </ span >
88+ < span className = "leading-tight" > Design insights and short articles</ span >
89+ </ li >
90+ </ ul >
91+ </ div >
92+
93+ < div className = "p-6 md:p-8 border-l md:border-l border-transparent md:border-l-gray-50 bg-white md:bg-transparent flex items-center" >
94+ < form onSubmit = { handleSubmit } className = "w-full" noValidate >
95+ { error && (
96+ < div id = "subscribe-error" role = "alert" aria-live = "assertive" className = "mb-3 text-sm text-red-600 font-medium" >
97+ { error }
98+ </ div >
99+ ) }
100+
101+ < label htmlFor = "subscribe-email" className = "sr-only" > Email address</ label >
102+ < div className = "flex flex-col gap-3" >
103+ < div className = "relative w-full" >
104+ < span className = "absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" > < IconMailbox size = { 18 } /> </ span >
105+ < input
106+ id = "subscribe-email"
107+ type = "email"
108+ value = { email }
109+ onChange = { ( e ) => setEmail ( e . target . value ) }
110+ placeholder = "you@yourdomain.com"
111+ required
112+ disabled = { isSubmitting }
113+ autoComplete = "email"
114+ aria-describedby = { error ? 'subscribe-error' : 'subscribe-success' }
115+ className = { `w-full pl-11 pr-4 py-4 rounded-2xl border transition-shadow text-base text-gray-900 placeholder-gray-400 bg-white focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:opacity-60 ${ error ? 'border-red-300' : 'border-gray-200' } ` }
116+ style = { { minWidth : '20rem' } }
117+ />
118+ </ div >
119+
120+ < div >
121+ < button
122+ type = "submit"
123+ disabled = { isSubmitting }
124+ className = "w-full inline-flex items-center justify-center gap-2 px-6 py-3 bg-gradient-to-r from-indigo-600 via-blue-600 to-purple-600 text-white font-semibold rounded-2xl shadow-md transform transition hover:-translate-y-0.5 disabled:opacity-60"
125+ >
126+ { isSubmitting ? (
127+ < span className = "flex items-center gap-2" >
128+ < IconRefreshAlert className = "animate-spin" size = { 16 } />
129+ Subscribing...
130+ </ span >
131+ ) : (
132+ < span className = "flex items-center gap-2" >
133+ < IconMailbox size = { 16 } />
134+ Subscribe
135+ </ span >
136+ ) }
137+ </ button >
138+ </ div >
139+ </ div >
140+
141+ < p className = "mt-3 text-xs text-gray-500" > We respect your privacy. Unsubscribe anytime.</ p >
142+
143+ { isSuccess && (
144+ < div className = "mt-4" >
145+ < div
146+ ref = { successRef }
147+ tabIndex = { - 1 }
148+ role = "status"
149+ aria-live = "polite"
150+ id = "subscribe-success"
151+ className = "p-4 rounded-lg bg-white border border-green-100 text-green-900 shadow-md transform transition-all duration-300"
152+ style = { { boxShadow : '0 10px 30px rgba(2,6,23,0.06)' } }
153+ >
154+ < div className = "flex items-start gap-3" >
155+ < span className = "p-2 rounded-full bg-green-50 text-green-700" > < IconCheck size = { 18 } /> </ span >
156+ < div className = "flex-1" >
157+ < p className = "font-semibold" > You're subscribed — thank you!</ p >
158+ < p className = "text-sm text-green-800/80" > We'll send occasional updates to your inbox.</ p >
159+ </ div >
160+ < button aria-label = "Dismiss" className = "text-green-700 hover:text-green-900 p-1" onClick = { ( ) => setIsSuccess ( false ) } >
161+ < IconSquareRoundedX size = { 18 } />
162+ </ button >
163+ </ div >
164+ </ div >
165+ </ div >
166+ ) }
167+ </ form >
118168 </ div >
119169 </ div >
120- ) }
170+ </ div >
121171 </ div >
122172 ) ;
123173}
0 commit comments