CognxSafeTrack Claude Sonnet 4.6 commited on
Commit
a3f8777
·
1 Parent(s): e8b9f53

fix(admin): make all modals scrollable and responsive on mobile

Browse files

RechargeModal (BillingPage), create-org modal (ClientsManagementView),
and create-template modal (TemplatesPage) now use the bottom-sheet
pattern: fixed header + scrollable body + fixed footer, max-h-[90vh],
and items-end on mobile so they slide up from the bottom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

apps/admin/src/pages/BillingPage.tsx CHANGED
@@ -13,37 +13,61 @@ const CREDIT_PACKS = [
13
  const SUPPORT_WA_URL = 'https://wa.me/221700000000?text=Bonjour%2C%20je%20souhaite%20recharger%20mes%20cr%C3%A9dits%20IA%20Xaml%C3%A9%20Studio.';
14
 
15
  function RechargeModal({ onClose }: { onClose: () => void }) {
 
 
 
 
 
 
 
 
 
 
16
  return (
17
- <div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50 flex items-center justify-center p-4" onClick={onClose}>
18
- <div className="bg-white rounded-2xl shadow-xl max-w-md w-full p-6" onClick={e => e.stopPropagation()}>
19
- <div className="flex items-center justify-between mb-5">
 
 
 
 
 
 
 
20
  <h2 className="text-lg font-bold text-slate-900">Recharger les crédits IA</h2>
21
- <button onClick={onClose} className="text-slate-400 hover:text-slate-600 transition-colors">
 
 
 
22
  <X className="w-5 h-5" />
23
  </button>
24
  </div>
25
 
26
- <div className="space-y-3 mb-6">
 
27
  {CREDIT_PACKS.map(pack => (
28
  <a
29
  key={pack.credits}
30
  href={`${SUPPORT_WA_URL}%20-%20Pack%20${pack.label}`}
31
  target="_blank"
32
  rel="noopener noreferrer"
33
- className="flex items-center justify-between p-4 border border-slate-200 rounded-xl hover:border-indigo-400 hover:bg-indigo-50 transition-all group cursor-pointer"
34
  >
35
  <div>
36
  <p className="font-semibold text-slate-800 group-hover:text-indigo-700">{pack.label}</p>
37
  <p className="text-xs text-slate-400">Pack recharge unique</p>
38
  </div>
39
- <span className="text-indigo-600 font-bold">{pack.price}</span>
40
  </a>
41
  ))}
42
  </div>
43
 
44
- <p className="text-xs text-slate-400 text-center">
45
- En cliquant sur un pack, vous serez redirigé vers WhatsApp pour finaliser votre commande avec notre équipe.
46
- </p>
 
 
 
47
  </div>
48
  </div>
49
  );
 
13
  const SUPPORT_WA_URL = 'https://wa.me/221700000000?text=Bonjour%2C%20je%20souhaite%20recharger%20mes%20cr%C3%A9dits%20IA%20Xaml%C3%A9%20Studio.';
14
 
15
  function RechargeModal({ onClose }: { onClose: () => void }) {
16
+ useEffect(() => {
17
+ document.body.style.overflow = 'hidden';
18
+ const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onClose();
19
+ window.addEventListener('keydown', onKey);
20
+ return () => {
21
+ document.body.style.overflow = '';
22
+ window.removeEventListener('keydown', onKey);
23
+ };
24
+ }, [onClose]);
25
+
26
  return (
27
+ <div
28
+ className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50 flex items-end sm:items-center justify-center sm:p-4"
29
+ onClick={onClose}
30
+ >
31
+ <div
32
+ className="bg-white w-full sm:max-w-md rounded-t-3xl sm:rounded-2xl shadow-2xl flex flex-col max-h-[90vh]"
33
+ onClick={e => e.stopPropagation()}
34
+ >
35
+ {/* Header — fixe */}
36
+ <div className="flex items-center justify-between px-6 py-5 border-b border-slate-100 shrink-0">
37
  <h2 className="text-lg font-bold text-slate-900">Recharger les crédits IA</h2>
38
+ <button
39
+ onClick={onClose}
40
+ className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-slate-100 text-slate-400 hover:text-slate-600 transition-colors"
41
+ >
42
  <X className="w-5 h-5" />
43
  </button>
44
  </div>
45
 
46
+ {/* Contenu scrollable */}
47
+ <div className="overflow-y-auto flex-1 px-6 py-5 space-y-3">
48
  {CREDIT_PACKS.map(pack => (
49
  <a
50
  key={pack.credits}
51
  href={`${SUPPORT_WA_URL}%20-%20Pack%20${pack.label}`}
52
  target="_blank"
53
  rel="noopener noreferrer"
54
+ className="flex items-center justify-between p-4 border border-slate-200 rounded-xl hover:border-indigo-400 hover:bg-indigo-50 active:bg-indigo-100 transition-all group"
55
  >
56
  <div>
57
  <p className="font-semibold text-slate-800 group-hover:text-indigo-700">{pack.label}</p>
58
  <p className="text-xs text-slate-400">Pack recharge unique</p>
59
  </div>
60
+ <span className="text-indigo-600 font-bold shrink-0 ml-4">{pack.price}</span>
61
  </a>
62
  ))}
63
  </div>
64
 
65
+ {/* Footer fixe */}
66
+ <div className="px-6 py-4 border-t border-slate-100 shrink-0">
67
+ <p className="text-xs text-slate-400 text-center">
68
+ En cliquant sur un pack, vous serez redirigé vers WhatsApp pour finaliser votre commande avec notre équipe.
69
+ </p>
70
+ </div>
71
  </div>
72
  </div>
73
  );
apps/admin/src/pages/ClientsManagementView.tsx CHANGED
@@ -296,117 +296,124 @@ export default function ClientsManagementView() {
296
 
297
  {/* Modal de création d'organisation */}
298
  {isModalOpen && (
299
- <div className="fixed inset-0 bg-slate-900/60 backdrop-blur-md flex items-center justify-center p-4 z-50">
300
- <div className="bg-white rounded-[2rem] shadow-2xl w-full max-w-xl p-10 animate-in zoom-in-95 duration-200">
301
- <div className="flex items-center justify-between mb-8">
 
302
  <h2 className="text-2xl font-black text-slate-900">Nouvelle Organisation</h2>
303
  <button onClick={() => { setIsModalOpen(false); setCreateErrors({}); }} className="p-2 hover:bg-slate-100 rounded-full transition">
304
  <X className="w-5 h-5 text-slate-400" />
305
  </button>
306
  </div>
307
- <form onSubmit={handleCreateOrg} className="space-y-6">
308
- <div className="grid grid-cols-2 gap-4">
309
- <div className="col-span-2">
310
- <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Nom de l'entreprise <span className="text-red-400">*</span></label>
311
- <input
312
- type="text"
313
- placeholder="Ex: AgroBusiness Senegal"
314
- value={newOrg.name}
315
- onChange={e => { setNewOrg({...newOrg, name: e.target.value}); setCreateErrors(p => ({...p, name: ''})); }}
316
- className={`w-full border rounded-xl px-4 py-3 outline-none focus:ring-2 transition ${createErrors.name ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
317
- />
318
- {createErrors.name && <p className="text-xs text-red-500 mt-1">{createErrors.name}</p>}
319
- </div>
320
- <div>
321
- <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Slug (URL) <span className="text-red-400">*</span></label>
322
- <input
323
- type="text"
324
- placeholder="agro-sn"
325
- value={newOrg.slug}
326
- onChange={e => { setNewOrg({...newOrg, slug: e.target.value.toLowerCase().replace(/\s+/g, '-')}); setCreateErrors(p => ({...p, slug: ''})); }}
327
- className={`w-full border rounded-xl px-4 py-3 outline-none focus:ring-2 transition font-mono text-sm ${createErrors.slug ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
328
- />
329
- {createErrors.slug && <p className="text-xs text-red-500 mt-1">{createErrors.slug}</p>}
330
- </div>
331
- <div>
332
- <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Cas d'usage</label>
333
- <select
334
- value={newOrg.mode}
335
- onChange={e => setNewOrg({...newOrg, mode: e.target.value as any})}
336
- className="w-full border border-slate-200 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-slate-900 transition bg-white"
337
- >
338
- <option value="CRM_MARKETING">CRM & Marketing</option>
339
- <option value="PEDAGOGY">Pédagogie & Formation</option>
340
- <option value="CUSTOMER_SERVICE">Service Client IA</option>
341
- </select>
342
- </div>
343
- <div className="col-span-2">
344
- <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Espace de travail (Workstream)</label>
345
- <select
346
- value={newOrg.useCase}
347
- onChange={e => {
348
- const val = e.target.value as any;
349
- setNewOrg({
350
- ...newOrg,
351
- useCase: val,
352
- // Sync mode for convenience if they pick CRM
353
- mode: val === 'CRM_WHATSAPP' ? 'CRM_MARKETING' : 'PEDAGOGY'
354
- });
355
- }}
356
- className="w-full border border-slate-200 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-slate-900 transition bg-white"
357
- >
358
- <option value="EDUCATION">🎓 Plateforme Éducative (Modules)</option>
359
- <option value="CRM_WHATSAPP">💬 CRM & Messages WhatsApp (Excel)</option>
360
- </select>
361
- </div>
362
- </div>
363
 
364
- <div className="p-6 bg-slate-50 rounded-2xl border border-slate-100 space-y-4">
365
- <h4 className="text-xs font-bold text-slate-500 uppercase tracking-widest">Administrateur Principal</h4>
 
366
  <div className="grid grid-cols-2 gap-4">
367
- <div>
368
- <label className="block text-xs font-bold text-slate-500 mb-1">Nom complet <span className="text-red-400">*</span></label>
369
  <input
370
  type="text"
371
- placeholder="Jean Dupont"
372
- value={newOrg.adminName}
373
- onChange={e => { setNewOrg({...newOrg, adminName: e.target.value}); setCreateErrors(p => ({...p, adminName: ''})); }}
374
- className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.adminName ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
375
  />
376
- {createErrors.adminName && <p className="text-xs text-red-500 mt-1">{createErrors.adminName}</p>}
377
  </div>
378
  <div>
379
- <label className="block text-xs font-bold text-slate-500 mb-1">Email <span className="text-red-400">*</span></label>
380
  <input
381
- type="email"
382
- placeholder="admin@entreprise.com"
383
- value={newOrg.adminEmail}
384
- onChange={e => { setNewOrg({...newOrg, adminEmail: e.target.value}); setCreateErrors(p => ({...p, adminEmail: ''})); }}
385
- className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.adminEmail ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
386
  />
387
- {createErrors.adminEmail && <p className="text-xs text-red-500 mt-1">{createErrors.adminEmail}</p>}
 
 
 
 
 
 
 
 
 
 
 
 
388
  </div>
389
  <div className="col-span-2">
390
- <label className="block text-xs font-bold text-slate-500 mb-1">Mot de passe provisoire <span className="text-red-400">*</span></label>
391
- <input
392
- type="password"
393
- placeholder="Min. 8 caractères"
394
- value={newOrg.password}
395
- onChange={e => { setNewOrg({...newOrg, password: e.target.value}); setCreateErrors(p => ({...p, password: ''})); }}
396
- className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.password ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
397
- />
398
- {createErrors.password && <p className="text-xs text-red-500 mt-1">{createErrors.password}</p>}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  </div>
400
  </div>
401
  </div>
402
 
403
- <button
404
- type="submit"
405
- disabled={isCreating}
406
- className="w-full bg-slate-900 text-white py-4 rounded-2xl font-bold hover:bg-slate-800 transition disabled:opacity-50 flex items-center justify-center gap-2 shadow-xl shadow-slate-200"
407
- >
408
- {isCreating ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Créer l\'organisation & Admin'}
409
- </button>
 
 
 
410
  </form>
411
  </div>
412
  </div>
 
296
 
297
  {/* Modal de création d'organisation */}
298
  {isModalOpen && (
299
+ <div className="fixed inset-0 bg-slate-900/60 backdrop-blur-md flex items-end sm:items-center justify-center sm:p-4 z-50">
300
+ <div className="bg-white rounded-t-[2rem] sm:rounded-[2rem] shadow-2xl w-full sm:max-w-xl flex flex-col max-h-[90vh] animate-in zoom-in-95 duration-200">
301
+ {/* Fixed header */}
302
+ <div className="shrink-0 flex items-center justify-between px-8 pt-8 pb-5 border-b border-slate-100">
303
  <h2 className="text-2xl font-black text-slate-900">Nouvelle Organisation</h2>
304
  <button onClick={() => { setIsModalOpen(false); setCreateErrors({}); }} className="p-2 hover:bg-slate-100 rounded-full transition">
305
  <X className="w-5 h-5 text-slate-400" />
306
  </button>
307
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
+ {/* Scrollable body */}
310
+ <form onSubmit={handleCreateOrg} className="flex flex-col flex-1 min-h-0">
311
+ <div className="overflow-y-auto flex-1 px-8 py-6 space-y-6">
312
  <div className="grid grid-cols-2 gap-4">
313
+ <div className="col-span-2">
314
+ <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Nom de l'entreprise <span className="text-red-400">*</span></label>
315
  <input
316
  type="text"
317
+ placeholder="Ex: AgroBusiness Senegal"
318
+ value={newOrg.name}
319
+ onChange={e => { setNewOrg({...newOrg, name: e.target.value}); setCreateErrors(p => ({...p, name: ''})); }}
320
+ className={`w-full border rounded-xl px-4 py-3 outline-none focus:ring-2 transition ${createErrors.name ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
321
  />
322
+ {createErrors.name && <p className="text-xs text-red-500 mt-1">{createErrors.name}</p>}
323
  </div>
324
  <div>
325
+ <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Slug (URL) <span className="text-red-400">*</span></label>
326
  <input
327
+ type="text"
328
+ placeholder="agro-sn"
329
+ value={newOrg.slug}
330
+ onChange={e => { setNewOrg({...newOrg, slug: e.target.value.toLowerCase().replace(/\s+/g, '-')}); setCreateErrors(p => ({...p, slug: ''})); }}
331
+ className={`w-full border rounded-xl px-4 py-3 outline-none focus:ring-2 transition font-mono text-sm ${createErrors.slug ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
332
  />
333
+ {createErrors.slug && <p className="text-xs text-red-500 mt-1">{createErrors.slug}</p>}
334
+ </div>
335
+ <div>
336
+ <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Cas d'usage</label>
337
+ <select
338
+ value={newOrg.mode}
339
+ onChange={e => setNewOrg({...newOrg, mode: e.target.value as any})}
340
+ className="w-full border border-slate-200 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-slate-900 transition bg-white"
341
+ >
342
+ <option value="CRM_MARKETING">CRM & Marketing</option>
343
+ <option value="PEDAGOGY">Pédagogie & Formation</option>
344
+ <option value="CUSTOMER_SERVICE">Service Client IA</option>
345
+ </select>
346
  </div>
347
  <div className="col-span-2">
348
+ <label className="block text-xs font-bold uppercase text-slate-400 mb-2">Espace de travail (Workstream)</label>
349
+ <select
350
+ value={newOrg.useCase}
351
+ onChange={e => {
352
+ const val = e.target.value as any;
353
+ setNewOrg({
354
+ ...newOrg,
355
+ useCase: val,
356
+ mode: val === 'CRM_WHATSAPP' ? 'CRM_MARKETING' : 'PEDAGOGY'
357
+ });
358
+ }}
359
+ className="w-full border border-slate-200 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-slate-900 transition bg-white"
360
+ >
361
+ <option value="EDUCATION">🎓 Plateforme Éducative (Modules)</option>
362
+ <option value="CRM_WHATSAPP">💬 CRM & Messages WhatsApp (Excel)</option>
363
+ </select>
364
+ </div>
365
+ </div>
366
+
367
+ <div className="p-6 bg-slate-50 rounded-2xl border border-slate-100 space-y-4">
368
+ <h4 className="text-xs font-bold text-slate-500 uppercase tracking-widest">Administrateur Principal</h4>
369
+ <div className="grid grid-cols-2 gap-4">
370
+ <div>
371
+ <label className="block text-xs font-bold text-slate-500 mb-1">Nom complet <span className="text-red-400">*</span></label>
372
+ <input
373
+ type="text"
374
+ placeholder="Jean Dupont"
375
+ value={newOrg.adminName}
376
+ onChange={e => { setNewOrg({...newOrg, adminName: e.target.value}); setCreateErrors(p => ({...p, adminName: ''})); }}
377
+ className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.adminName ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
378
+ />
379
+ {createErrors.adminName && <p className="text-xs text-red-500 mt-1">{createErrors.adminName}</p>}
380
+ </div>
381
+ <div>
382
+ <label className="block text-xs font-bold text-slate-500 mb-1">Email <span className="text-red-400">*</span></label>
383
+ <input
384
+ type="email"
385
+ placeholder="admin@entreprise.com"
386
+ value={newOrg.adminEmail}
387
+ onChange={e => { setNewOrg({...newOrg, adminEmail: e.target.value}); setCreateErrors(p => ({...p, adminEmail: ''})); }}
388
+ className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.adminEmail ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
389
+ />
390
+ {createErrors.adminEmail && <p className="text-xs text-red-500 mt-1">{createErrors.adminEmail}</p>}
391
+ </div>
392
+ <div className="col-span-2">
393
+ <label className="block text-xs font-bold text-slate-500 mb-1">Mot de passe provisoire <span className="text-red-400">*</span></label>
394
+ <input
395
+ type="password"
396
+ placeholder="Min. 8 caractères"
397
+ value={newOrg.password}
398
+ onChange={e => { setNewOrg({...newOrg, password: e.target.value}); setCreateErrors(p => ({...p, password: ''})); }}
399
+ className={`w-full border rounded-xl px-4 py-2 text-sm outline-none focus:ring-2 transition ${createErrors.password ? 'border-red-400 focus:ring-red-200' : 'border-slate-200 focus:ring-slate-900'}`}
400
+ />
401
+ {createErrors.password && <p className="text-xs text-red-500 mt-1">{createErrors.password}</p>}
402
+ </div>
403
  </div>
404
  </div>
405
  </div>
406
 
407
+ {/* Fixed footer */}
408
+ <div className="shrink-0 px-8 pb-8 pt-4 border-t border-slate-100">
409
+ <button
410
+ type="submit"
411
+ disabled={isCreating}
412
+ className="w-full bg-slate-900 text-white py-4 rounded-2xl font-bold hover:bg-slate-800 transition disabled:opacity-50 flex items-center justify-center gap-2 shadow-xl shadow-slate-200"
413
+ >
414
+ {isCreating ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Créer l\'organisation & Admin'}
415
+ </button>
416
+ </div>
417
  </form>
418
  </div>
419
  </div>
apps/admin/src/pages/TemplatesPage.tsx CHANGED
@@ -253,23 +253,27 @@ export default function TemplatesPage() {
253
  onClick={() => setIsModalOpen(false)}
254
  className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm"
255
  />
256
- <motion.div
257
  initial={{ opacity: 0, scale: 0.9, y: 20 }}
258
  animate={{ opacity: 1, scale: 1, y: 0 }}
259
  exit={{ opacity: 0, scale: 0.9, y: 20 }}
260
- className="bg-white rounded-[32px] w-full max-w-xl shadow-2xl relative overflow-hidden z-10"
261
  >
262
- <div className="p-8">
263
- <h2 className="text-2xl font-bold text-slate-900 mb-6">
 
264
  {t('whatsapp.templates.create_modal.title')}
265
  </h2>
 
266
 
267
- <form onSubmit={handleCreate} className="space-y-6">
 
 
268
  <div>
269
  <label className="block text-sm font-bold text-slate-700 mb-2">
270
  {t('whatsapp.templates.create_modal.name_label')}
271
  </label>
272
- <input
273
  required
274
  type="text"
275
  value={newTemplate.name}
@@ -284,7 +288,7 @@ export default function TemplatesPage() {
284
  <label className="block text-sm font-bold text-slate-700 mb-2">
285
  {t('whatsapp.templates.create_modal.category_label')}
286
  </label>
287
- <select
288
  value={newTemplate.category}
289
  onChange={e => setNewTemplate({...newTemplate, category: e.target.value})}
290
  className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all font-medium"
@@ -297,7 +301,7 @@ export default function TemplatesPage() {
297
  <label className="block text-sm font-bold text-slate-700 mb-2">
298
  {t('whatsapp.templates.create_modal.language_label')}
299
  </label>
300
- <select
301
  value={newTemplate.language}
302
  onChange={e => setNewTemplate({...newTemplate, language: e.target.value})}
303
  className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all font-medium"
@@ -314,7 +318,7 @@ export default function TemplatesPage() {
314
  <label className="block text-sm font-bold text-slate-700 mb-2">
315
  {t('whatsapp.templates.create_modal.body_label')}
316
  </label>
317
- <textarea
318
  required
319
  rows={4}
320
  value={newTemplate.body}
@@ -323,24 +327,25 @@ export default function TemplatesPage() {
323
  placeholder="Hello {{1}}, welcome to our school!"
324
  />
325
  </div>
 
326
 
327
- <div className="flex gap-4 pt-4">
328
- <button
329
- type="button"
330
- onClick={() => setIsModalOpen(false)}
331
- className="flex-1 py-4 bg-slate-50 text-slate-600 rounded-2xl font-bold hover:bg-slate-100 transition-all active:scale-95"
332
- >
333
- {t('common.cancel')}
334
- </button>
335
- <button
336
- type="submit"
337
- className="flex-1 py-4 bg-indigo-600 text-white rounded-2xl font-bold hover:bg-indigo-700 transition-all shadow-lg shadow-indigo-100 active:scale-95"
338
- >
339
- {t('whatsapp.templates.create_modal.submit')}
340
- </button>
341
- </div>
342
- </form>
343
- </div>
344
  </motion.div>
345
  </div>
346
  )}
 
253
  onClick={() => setIsModalOpen(false)}
254
  className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm"
255
  />
256
+ <motion.div
257
  initial={{ opacity: 0, scale: 0.9, y: 20 }}
258
  animate={{ opacity: 1, scale: 1, y: 0 }}
259
  exit={{ opacity: 0, scale: 0.9, y: 20 }}
260
+ className="bg-white rounded-[32px] w-full max-w-xl shadow-2xl relative flex flex-col max-h-[90vh] z-10"
261
  >
262
+ {/* Fixed header */}
263
+ <div className="shrink-0 px-8 pt-8 pb-6 border-b border-slate-100">
264
+ <h2 className="text-2xl font-bold text-slate-900">
265
  {t('whatsapp.templates.create_modal.title')}
266
  </h2>
267
+ </div>
268
 
269
+ {/* Scrollable body */}
270
+ <form onSubmit={handleCreate} className="flex flex-col flex-1 min-h-0">
271
+ <div className="overflow-y-auto flex-1 px-8 py-6 space-y-6">
272
  <div>
273
  <label className="block text-sm font-bold text-slate-700 mb-2">
274
  {t('whatsapp.templates.create_modal.name_label')}
275
  </label>
276
+ <input
277
  required
278
  type="text"
279
  value={newTemplate.name}
 
288
  <label className="block text-sm font-bold text-slate-700 mb-2">
289
  {t('whatsapp.templates.create_modal.category_label')}
290
  </label>
291
+ <select
292
  value={newTemplate.category}
293
  onChange={e => setNewTemplate({...newTemplate, category: e.target.value})}
294
  className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all font-medium"
 
301
  <label className="block text-sm font-bold text-slate-700 mb-2">
302
  {t('whatsapp.templates.create_modal.language_label')}
303
  </label>
304
+ <select
305
  value={newTemplate.language}
306
  onChange={e => setNewTemplate({...newTemplate, language: e.target.value})}
307
  className="w-full px-5 py-3.5 bg-slate-50 border border-slate-200 rounded-2xl outline-none focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 transition-all font-medium"
 
318
  <label className="block text-sm font-bold text-slate-700 mb-2">
319
  {t('whatsapp.templates.create_modal.body_label')}
320
  </label>
321
+ <textarea
322
  required
323
  rows={4}
324
  value={newTemplate.body}
 
327
  placeholder="Hello {{1}}, welcome to our school!"
328
  />
329
  </div>
330
+ </div>
331
 
332
+ {/* Fixed footer */}
333
+ <div className="shrink-0 px-8 pb-8 pt-4 border-t border-slate-100 flex gap-4">
334
+ <button
335
+ type="button"
336
+ onClick={() => setIsModalOpen(false)}
337
+ className="flex-1 py-4 bg-slate-50 text-slate-600 rounded-2xl font-bold hover:bg-slate-100 transition-all active:scale-95"
338
+ >
339
+ {t('common.cancel')}
340
+ </button>
341
+ <button
342
+ type="submit"
343
+ className="flex-1 py-4 bg-indigo-600 text-white rounded-2xl font-bold hover:bg-indigo-700 transition-all shadow-lg shadow-indigo-100 active:scale-95"
344
+ >
345
+ {t('whatsapp.templates.create_modal.submit')}
346
+ </button>
347
+ </div>
348
+ </form>
349
  </motion.div>
350
  </div>
351
  )}