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

{banner.text}

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

{error}

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

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

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

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

405→ 405→

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

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

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

419→ 419→