1→'use client'; 2→ 3→import { useState, useCallback, useRef, useEffect } from 'react'; 4→import { motion, AnimatePresence } from 'framer-motion'; 5→import { 6→ Upload, Image as ImageIcon, Sparkles, Download, ArrowRight, 7→ Palette, Camera, Layers, SlidersHorizontal, Loader2, Check, 8→ Trash2, RotateCcw, Eye, Wand2, Sun, Paintbrush, Film, 9→ Pencil, History, ChevronLeft, X, GripVertical, WandSparkles, 10→ Square, RectangleVertical, Monitor, Settings, Megaphone, Code, 11→ LogIn, LogOut, Shield 12→} from 'lucide-react'; 13→import { Button } from '@/components/ui/button'; 14→import { Textarea } from '@/components/ui/textarea'; 15→import { Progress } from '@/components/ui/progress'; 16→import { Card, CardContent } from '@/components/ui/card'; 17→import { 18→ Dialog, 19→ DialogContent, 20→ DialogHeader, 21→ DialogTitle, 22→ DialogTrigger, 23→} from '@/components/ui/dialog'; 24→import { Input } from '@/components/ui/input'; 25→import { Switch } from '@/components/ui/switch'; 26→import { Label } from '@/components/ui/label'; 27→import { useAppStore, type PhotoProject } from '@/store/photo-store'; 28→import { signIn, signOut } from 'next-auth/react'; 29→ 30→/* ─── Session Types ─── */ 31→interface SessionUser { 32→ id: string; 33→ name?: string | null; 34→ email?: string | null; 35→ image?: string | null; 36→ role?: string; 37→} 38→ 39→/* ─── useAdminSession Hook ─── */ 40→function useAdminSession() { 41→ const [isAdmin, setIsAdmin] = useState(false); 42→ const [isLoading, setIsLoading] = useState(true); 43→ const [userName, setUserName] = useState(null); 44→ 45→ const checkSession = useCallback(async () => { 46→ try { 47→ const res = await fetch('/api/auth/session'); 48→ if (!res.ok) { setIsAdmin(false); setIsLoading(false); return; } 49→ const session = await res.json(); 50→ const role = (session?.user as SessionUser | undefined)?.role; 51→ setIsAdmin(role === 'admin'); 52→ setUserName(session?.user?.name || null); 53→ } catch { 54→ setIsAdmin(false); 55→ } finally { 56→ setIsLoading(false); 57→ } 58→ }, []); 59→ 60→ useEffect(() => { checkSession(); }, [checkSession]); 61→ 62→ return { isAdmin, isLoading, userName, refresh: checkSession }; 63→} 64→ 65→/* ─── Types ─── */ 66→interface BannerData { 67→ id: string; 68→ adType: string; 69→ imageUrl: string; 70→ adCode: string; 71→ linkUrl: string; 72→ text: string; 73→ active: boolean; 74→ position: string; 75→} 76→ 77→/* ─── Banner Display Component ─── */ 78→function BannerDisplay({ position }: { position: 'top' | 'above-upload' | 'below-features' }) { 79→ const [banner, setBanner] = useState(null); 80→ const [dismissed, setDismissed] = useState(false); 81→ 82→ useEffect(() => { 83→ fetch('/api/banner') 84→ .then((r) => r.json()) 85→ .then((data) => { 86→ if (data.banner && data.banner.position === position) { 87→ setBanner(data.banner); 88→ } 89→ }) 90→ .catch(() => {}); 91→ }, [position]); 92→ 93→ if (!banner || !banner.active || dismissed) return null; 94→ 95→ // External ad code (Google AdSense, etc.) 96→ if (banner.adType === 'external' && banner.adCode) { 97→ return ( 98→ 103→
104→
108→
109→ 116→ 117→ ); 118→ } 119→ 120→ // Image banner 121→ if (!banner.imageUrl) return null; 122→ 123→ const Wrapper = banner.linkUrl ? 'a' : 'div'; 124→ const wrapperProps = banner.linkUrl 125→ ? { href: banner.linkUrl, target: '_blank' as const, rel: 'noopener noreferrer' } 126→ : {}; 127→ 128→ return ( 129→ 134→ 138→ {banner.text 143→ {banner.text && ( 144→
145→

{banner.text}

146→
147→ )} 148→
149→ 156→
157→ ); 158→} 159→ 160→/* ─── Login Dialog ─── */ 161→function LoginDialog({ open, onOpenChange, onLoginSuccess }: { open: boolean; onOpenChange: (v: boolean) => void; onLoginSuccess: () => void }) { 162→ const [username, setUsername] = useState(''); 163→ const [password, setPassword] = useState(''); 164→ const [loading, setLoading] = useState(false); 165→ const [error, setError] = useState(null); 166→ 167→ const handleSubmit = useCallback(async (e: React.FormEvent) => { 168→ e.preventDefault(); 169→ if (!username.trim() || !password.trim()) return; 170→ setLoading(true); 171→ setError(null); 172→ try { 173→ const res = await signIn('credentials', { 174→ username, 175→ password, 176→ redirect: false, 177→ }); 178→ if (res?.error) { 179→ setError('اسم المستخدم أو كلمة المرور غير صحيحة'); 180→ } else { 181→ setUsername(''); 182→ setPassword(''); 183→ onOpenChange(false); 184→ onLoginSuccess(); 185→ } 186→ } catch { 187→ setError('حدث خطأ أثناء تسجيل الدخول'); 188→ } finally { 189→ setLoading(false); 190→ } 191→ }, [username, password, onOpenChange, onLoginSuccess]); 192→ 193→ return ( 194→ { if (!v) setError(null); onOpenChange(v); }}> 195→ 196→ 197→ 198→ 199→ تسجيل دخول المدير 200→ 201→ 202→
203→
204→ 205→ setUsername(e.target.value)} 209→ placeholder="admin" 210→ autoComplete="username" 211→ autoFocus 212→ /> 213→
214→
215→ 216→ setPassword(e.target.value)} 221→ placeholder="••••••••" 222→ autoComplete="current-password" 223→ /> 224→
225→ {error && ( 226→

{error}

227→ )} 228→ 232→
233→
234→
235→ ); 236→} 237→ 238→/* ─── Banner Admin Panel ─── */ 239→function BannerAdmin({ open, onOpenChange }: { open: boolean; onOpenChange: (v: boolean) => void }) { 240→ const [adType, setAdType] = useState<'image' | 'external'>('image'); 241→ const [imageUrl, setImageUrl] = useState(''); 242→ const [adCode, setAdCode] = useState(''); 243→ const [linkUrl, setLinkUrl] = useState(''); 244→ const [text, setText] = useState(''); 245→ const [active, setActive] = useState(false); 246→ const [position, setPosition] = useState('top'); 247→ const [saving, setSaving] = useState(false); 248→ const [uploading, setUploading] = useState(false); 249→ const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); 250→ const fileInputRef = useRef(null); 251→ 252→ // Load existing banner on open 253→ useEffect(() => { 254→ if (!open) return; 255→ fetch('/api/banner') 256→ .then((r) => r.json()) 257→ .then((data) => { 258→ if (data.banner) { 259→ setAdType((data.banner.adType as 'image' | 'external') || 'image'); 260→ setImageUrl(data.banner.imageUrl || ''); 261→ setAdCode(data.banner.adCode || ''); 262→ setLinkUrl(data.banner.linkUrl); 263→ setText(data.banner.text); 264→ setActive(data.banner.active); 265→ setPosition(data.banner.position); 266→ } else { 267→ setAdType('image'); 268→ setImageUrl(''); 269→ setAdCode(''); 270→ setLinkUrl(''); 271→ setText(''); 272→ setActive(false); 273→ setPosition('top'); 274→ } 275→ setMessage(null); 276→ }) 277→ .catch(() => {}); 278→ }, [open]); 279→ 280→ const handleUpload = useCallback(async (file: File) => { 281→ if (!file.type.startsWith('image/')) return; 282→ setUploading(true); 283→ setMessage(null); 284→ try { 285→ const formData = new FormData(); 286→ formData.append('image', file); 287→ const res = await fetch('/api/banner', { method: 'POST', body: formData }); 288→ const data = await res.json(); 289→ if (!res.ok) throw new Error(data.error || 'فشل رفع الصورة'); 290→ setImageUrl(data.imageUrl); 291→ } catch (err: unknown) { 292→ setMessage({ type: 'error', text: err instanceof Error ? err.message : 'خطأ' }); 293→ } finally { 294→ setUploading(false); 295→ } 296→ }, []); 297→ 298→ const handleSave = useCallback(async () => { 299→ if (adType === 'image' && !imageUrl) { 300→ setMessage({ type: 'error', text: 'يرجى رفع صورة البانر أولاً' }); 301→ return; 302→ } 303→ if (adType === 'external' && !adCode.trim()) { 304→ setMessage({ type: 'error', text: 'يرجى لصق كود الإعلان الخارجي' }); 305→ return; 306→ } 307→ setSaving(true); 308→ setMessage(null); 309→ try { 310→ const res = await fetch('/api/banner', { 311→ method: 'PUT', 312→ headers: { 'Content-Type': 'application/json' }, 313→ body: JSON.stringify({ adType, imageUrl, adCode, linkUrl, text, active, position }), 314→ }); 315→ const data = await res.json(); 316→ if (!res.ok) throw new Error(data.error || 'فشل الحفظ'); 317→ setMessage({ type: 'success', text: 'تم حفظ البانر بنجاح' }); 318→ } catch (err: unknown) { 319→ setMessage({ type: 'error', text: err instanceof Error ? err.message : 'خطأ' }); 320→ } finally { 321→ setSaving(false); 322→ } 323→ }, [adType, imageUrl, adCode, linkUrl, text, active, position]); 324→ 325→ const POSITION_OPTIONS = [ 326→ { value: 'top', label: 'أعلى الصفحة' }, 327→ { value: 'above-upload', label: 'فوق منطقة الرفع' }, 328→ { value: 'below-features', label: 'أسفل المميزات' }, 329→ ]; 330→ 331→ return ( 332→ 333→ 334→ 335→ 336→ 337→ إدارة البانر الإعلاني 338→ 339→ 340→ 341→
342→ {/* Ad Type Selector */} 343→
344→ 345→
346→ 357→ 368→
369→
370→ 371→ {/* Image Upload (only for image type) */} 372→ {adType === 'image' && ( 373→
374→ 375→
!uploading && fileInputRef.current?.click()} 377→ className={`relative rounded-lg border-2 border-dashed p-4 text-center cursor-pointer transition-all 378→ ${imageUrl ? 'border-primary/30' : 'border-muted-foreground/30 hover:border-primary/50'}`} 379→ > 380→ { 386→ const f = e.target.files?.[0]; 387→ if (f) handleUpload(f); 388→ e.target.value = ''; 389→ }} 390→ /> 391→ {uploading ? ( 392→
393→ 394→ جارٍ الرفع... 395→
396→ ) : imageUrl ? ( 397→
398→ بانر 399→

انقر لتغيير الصورة

400→
401→ ) : ( 402→
403→ 404→

انقر لاختيار صورة البانر

405→

PNG, JPG, WEBP — حتى 2 ميجا

406→
407→ )} 408→
409→
410→ )} 411→ 412→ {/* External Ad Code (only for external type) */} 413→ {adType === 'external' && ( 414→
415→ 416→

417→ الصق كود الإعلان من جوجل أدسنس أو أي شبكة إعلانية أخرى 418→

419→