1+ "use client" ;
2+
3+ import { useState } from "react" ;
4+ import * as Icons from "lucide-react" ;
5+ import { FeatureModal , ModalFeature } from "../type" ;
6+ import { upsertActiveModal } from "../actions" ;
7+ import IconPicker from "./IconPicker" ;
8+
9+ interface EditPromoModalProps {
10+ data : FeatureModal | null ;
11+ }
12+
13+ export default function EditPromoModal ( { data } : EditPromoModalProps ) {
14+ const [ form , setForm ] = useState < Partial < FeatureModal > > ( {
15+ id : data ?. id ,
16+ badge : data ?. badge ?? "" ,
17+ headline : data ?. headline ?? "" ,
18+ subheadline : data ?. subheadline ?? "" ,
19+ cta_label : data ?. cta_label ?? "" ,
20+ cta_href : data ?. cta_href ?? "" ,
21+ dismiss_label : data ?. dismiss_label ?? "Maybe later" ,
22+ features : data ?. features ?? [ ] ,
23+ is_active : data ?. is_active ?? true ,
24+ } ) ;
25+ const [ saving , setSaving ] = useState ( false ) ;
26+ const [ message , setMessage ] = useState < string | null > ( null ) ;
27+
28+ function handleChange ( field : keyof FeatureModal , value : unknown ) {
29+ setForm ( ( prev ) => ( { ...prev , [ field ] : value } ) ) ;
30+ }
31+
32+ function handleFeatureChange (
33+ index : number ,
34+ field : keyof ModalFeature ,
35+ value : string
36+ ) {
37+ const updated = ( form . features ?? [ ] ) . map ( ( f , i ) =>
38+ i === index ? { ...f , [ field ] : value } : f
39+ ) as ModalFeature [ ] ;
40+ handleChange ( "features" , updated ) ;
41+ }
42+
43+ function addFeature ( ) {
44+ handleChange ( "features" , [
45+ ...( form . features ?? [ ] ) ,
46+ { icon : "" , title : "" , description : "" } satisfies ModalFeature ,
47+ ] ) ;
48+ }
49+
50+ function removeFeature ( index : number ) {
51+ handleChange (
52+ "features" ,
53+ ( form . features ?? [ ] ) . filter ( ( _ , i ) => i !== index )
54+ ) ;
55+ }
56+
57+ async function handleSubmit ( ) {
58+ setSaving ( true ) ;
59+ setMessage ( null ) ;
60+ const { error } = await upsertActiveModal ( form ) ;
61+ setSaving ( false ) ;
62+ setMessage ( error ?? "Changes saved successfully." ) ;
63+ }
64+
65+ return (
66+ < div className = "space-y-6" >
67+ { /* ── Two-column grid ── */ }
68+ < div className = "grid grid-cols-1 gap-6 lg:grid-cols-2" >
69+
70+ { /* ── LEFT COLUMN: Content fields ── */ }
71+ < div className = "rounded-2xl border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900" >
72+ < div className = "mb-5 flex items-center justify-between" >
73+ < h2 className = "text-sm font-semibold text-gray-700 dark:text-gray-200" >
74+ Modal Content
75+ </ h2 >
76+ { /* Active toggle */ }
77+ < div className = "flex items-center gap-2" >
78+ < span className = "text-xs text-gray-500 dark:text-gray-400" >
79+ { form . is_active ? "Visible" : "Hidden" }
80+ </ span >
81+ < button
82+ onClick = { ( ) => handleChange ( "is_active" , ! form . is_active ) }
83+ className = { `relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
84+ form . is_active
85+ ? "bg-violet-600"
86+ : "bg-gray-300 dark:bg-gray-600"
87+ } `}
88+ >
89+ < span
90+ className = { `inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
91+ form . is_active ? "translate-x-6" : "translate-x-1"
92+ } `}
93+ />
94+ </ button >
95+ </ div >
96+ </ div >
97+
98+ < div className = "space-y-4" >
99+ { (
100+ [
101+ { field : "badge" , label : "Badge" } ,
102+ { field : "headline" , label : "Headline" } ,
103+ { field : "subheadline" , label : "Subheadline" } ,
104+ ] as const
105+ ) . map ( ( { field, label } ) => (
106+ < div key = { field } className = "flex flex-col gap-1.5" >
107+ < label className = "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400" >
108+ { label }
109+ </ label >
110+ { field === "subheadline" ? (
111+ < textarea
112+ rows = { 3 }
113+ value = { ( form [ field ] as string ) ?? "" }
114+ onChange = { ( e ) => handleChange ( field , e . target . value ) }
115+ className = "rounded-xl border border-gray-200 bg-gray-50 px-4 py-2.5 text-sm outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white resize-none"
116+ />
117+ ) : (
118+ < input
119+ value = { ( form [ field ] as string ) ?? "" }
120+ onChange = { ( e ) => handleChange ( field , e . target . value ) }
121+ className = "rounded-xl border border-gray-200 bg-gray-50 px-4 py-2.5 text-sm outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
122+ />
123+ ) }
124+ </ div >
125+ ) ) }
126+ </ div >
127+
128+ < hr className = "my-5 border-gray-200 dark:border-gray-700" />
129+
130+ { /* CTA + Dismiss */ }
131+ < h3 className = "mb-4 text-sm font-semibold text-gray-700 dark:text-gray-200" >
132+ Call to Action
133+ </ h3 >
134+ < div className = "space-y-4" >
135+ { (
136+ [
137+ { field : "cta_label" , label : "Button Label" } ,
138+ { field : "cta_href" , label : "Button URL" } ,
139+ { field : "dismiss_label" , label : "Dismiss Text" } ,
140+ ] as const
141+ ) . map ( ( { field, label } ) => (
142+ < div key = { field } className = "flex flex-col gap-1.5" >
143+ < label className = "text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400" >
144+ { label }
145+ </ label >
146+ < input
147+ value = { ( form [ field ] as string ) ?? "" }
148+ onChange = { ( e ) => handleChange ( field , e . target . value ) }
149+ className = "rounded-xl border border-gray-200 bg-gray-50 px-4 py-2.5 text-sm outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
150+ />
151+ </ div >
152+ ) ) }
153+ </ div >
154+ </ div >
155+
156+ { /* ── RIGHT COLUMN: Features + Preview ── */ }
157+ < div className = "flex flex-col gap-6" >
158+
159+ { /* Features editor */ }
160+ < div className = "rounded-2xl border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900" >
161+ < h2 className = "mb-4 text-sm font-semibold text-gray-700 dark:text-gray-200" >
162+ Features
163+ </ h2 >
164+ < div className = "space-y-4" >
165+ { form . features ?. map ( ( f , i ) => (
166+ < div
167+ key = { i }
168+ className = "relative rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800"
169+ >
170+ < button
171+ onClick = { ( ) => removeFeature ( i ) }
172+ className = "absolute right-3 top-3 text-xs text-red-400 hover:text-red-600"
173+ >
174+ Remove
175+ </ button >
176+ < div className = "grid grid-cols-4 gap-2 mb-2" >
177+ < div className = "flex flex-col gap-1 col-span-4" >
178+ < label className = "text-xs text-gray-500 dark:text-gray-400" >
179+ Icon
180+ </ label >
181+ < IconPicker
182+ value = { f . icon }
183+ onChange = { ( name ) => handleFeatureChange ( i , "icon" , name ) }
184+ />
185+ </ div >
186+ < div className = "col-span-4 flex flex-col gap-1" >
187+ < label className = "text-xs text-gray-500 dark:text-gray-400" >
188+ Title
189+ </ label >
190+ < input
191+ value = { f . title }
192+ onChange = { ( e ) =>
193+ handleFeatureChange ( i , "title" , e . target . value )
194+ }
195+ className = "rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
196+ />
197+ </ div >
198+ </ div >
199+ < div className = "flex flex-col gap-1" >
200+ < label className = "text-xs text-gray-500 dark:text-gray-400" >
201+ Description
202+ </ label >
203+ < input
204+ value = { f . description }
205+ onChange = { ( e ) =>
206+ handleFeatureChange ( i , "description" , e . target . value )
207+ }
208+ className = "rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm outline-none focus:border-violet-500 focus:ring-2 focus:ring-violet-500/20 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
209+ />
210+ </ div >
211+ </ div >
212+ ) ) }
213+ </ div >
214+ < button
215+ onClick = { addFeature }
216+ className = "mt-3 w-full rounded-xl border border-dashed border-violet-400 px-4 py-2 text-sm text-violet-600 hover:bg-violet-50 dark:hover:bg-violet-900/20 transition-colors"
217+ >
218+ + Add Feature
219+ </ button >
220+ </ div >
221+
222+ { /* Live preview */ }
223+ < div className = "rounded-2xl border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900" >
224+ < h2 className = "mb-4 text-sm font-semibold text-gray-700 dark:text-gray-200" >
225+ Preview
226+ </ h2 >
227+ < div className = "overflow-hidden rounded-xl border border-gray-200 dark:border-gray-700 shadow-lg" >
228+ < div className = "h-1.5 w-full bg-gradient-to-r from-violet-500 via-blue-500 to-cyan-400" />
229+ < div className = "bg-white dark:bg-gray-900 px-5 py-4" >
230+ { form . badge && (
231+ < span className = "mb-2 inline-flex items-center gap-1.5 rounded-full bg-violet-100 px-3 py-1 text-xs font-semibold text-violet-700 dark:bg-violet-900/40 dark:text-violet-300" >
232+ { form . badge }
233+ </ span >
234+ ) }
235+ < p className = "text-base font-bold text-gray-900 dark:text-white" >
236+ { form . headline || (
237+ < span className = "text-gray-300 dark:text-gray-600" > Headline...</ span >
238+ ) }
239+ </ p >
240+ < p className = "mt-1 text-xs text-gray-500 dark:text-gray-400" >
241+ { form . subheadline || (
242+ < span className = "text-gray-300 dark:text-gray-600" > Subheadline...</ span >
243+ ) }
244+ </ p >
245+ { ( form . features ?? [ ] ) . length > 0 && (
246+ < ul className = "mt-3 space-y-1.5" >
247+ { form . features ?. map ( ( f , i ) => (
248+ < li
249+ key = { i }
250+ className = "flex items-center gap-2 rounded-lg bg-gray-50 px-3 py-2 dark:bg-gray-800"
251+ >
252+ { ( ( ) => {
253+ const Icon = f . icon ? ( Icons [ f . icon as keyof typeof Icons ] as Icons . LucideIcon ) : null ;
254+ return Icon ? < Icon className = "h-4 w-4 shrink-0 text-violet-600" /> : null ;
255+ } ) ( ) }
256+ < div >
257+ < p className = "text-xs font-semibold text-gray-800 dark:text-gray-100" >
258+ { f . title }
259+ </ p >
260+ < p className = "text-xs text-gray-500 dark:text-gray-400" >
261+ { f . description }
262+ </ p >
263+ </ div >
264+ </ li >
265+ ) ) }
266+ </ ul >
267+ ) }
268+ < div className = "mt-3 flex gap-2" >
269+ < div className = "flex-1 rounded-lg bg-gradient-to-r from-violet-600 to-blue-600 px-3 py-1.5 text-center text-xs font-semibold text-white" >
270+ { form . cta_label || "CTA Button" }
271+ </ div >
272+ < div className = "flex-1 rounded-lg px-3 py-1.5 text-center text-xs text-gray-500" >
273+ { form . dismiss_label || "Maybe later" }
274+ </ div >
275+ </ div >
276+ </ div >
277+ </ div >
278+ </ div >
279+ </ div >
280+ </ div >
281+
282+ { /* ── Save bar ── */ }
283+ < div className = "flex items-center justify-between rounded-2xl border border-gray-200 bg-white px-6 py-4 dark:border-gray-700 dark:bg-gray-900" >
284+ { message ? (
285+ < p
286+ className = { `text-sm font-medium ${
287+ message . includes ( "Failed" ) ? "text-red-500" : "text-green-500"
288+ } `}
289+ >
290+ { message }
291+ </ p >
292+ ) : (
293+ < p className = "text-sm text-gray-400" > Unsaved changes</ p >
294+ ) }
295+ < button
296+ onClick = { handleSubmit }
297+ disabled = { saving }
298+ className = "rounded-xl bg-gradient-to-r from-violet-600 to-blue-600 px-6 py-2.5 text-sm font-semibold text-white shadow-md hover:brightness-110 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 transition-all"
299+ >
300+ { saving ? "Saving..." : "Save Changes" }
301+ </ button >
302+ </ div >
303+ </ div >
304+ ) ;
305+ }
0 commit comments