1import { r as reactExports, j as jsxRuntimeExports, N as Navbar, L as Link, F as Footer } from './index-CeM4o6SA.js'; 2import { b as formatBlogDate } from './dateUtils-WeI0nVry.js'; 3 4const AIVideoTemplateAdmin = ({ user, setUser }) => { 5 const [templates, setTemplates] = reactExports.useState([]); 6 const [categories, setCategories] = reactExports.useState([]); 7 const [loading, setLoading] = reactExports.useState(true); 8 const [error, setError] = reactExports.useState(null); 9 const [isAuthenticated, setIsAuthenticated] = reactExports.useState(false); 10 const [authChecking, setAuthChecking] = reactExports.useState(true); 11 const [isEditing, setIsEditing] = reactExports.useState(false); 12 const [editingId, setEditingId] = reactExports.useState(null); 13 const [formData, setFormData] = reactExports.useState({ 14 title: "", 15 slug: "", 16 description: "", 17 category_id: "", 18 status: "draft", 19 featured: false, 20 tags: [] 21 }); 22 const [thumbnailFile, setThumbnailFile] = reactExports.useState(null); 23 const [tagInput, setTagInput] = reactExports.useState(""); 24 const [submitting, setSubmitting] = reactExports.useState(false); 25 const [currentPage, setCurrentPage] = reactExports.useState(1); 26 const [totalPages, setTotalPages] = reactExports.useState(1); 27 const [filterStatus, setFilterStatus] = reactExports.useState("all"); 28 const [filterCategory, setFilterCategory] = reactExports.useState(""); 29 const [searchTerm, setSearchTerm] = reactExports.useState(""); 30 const [showCategoryManagement, setShowCategoryManagement] = reactExports.useState(false); 31 const [categoryFormData, setCategoryFormData] = reactExports.useState({ 32 name: "", 33 slug: "", 34 description: "", 35 color: "#8A7CFF", 36 icon: "", 37 sort_order: 0 38 }); 39 const [isEditingCategory, setIsEditingCategory] = reactExports.useState(false); 40 const [editingCategoryId, setEditingCategoryId] = reactExports.useState(null); 41 const [categorySubmitting, setCategorySubmitting] = reactExports.useState(false); 42 reactExports.useEffect(() => { 43 checkAuthentication(); 44 }, []); 45 reactExports.useEffect(() => { 46 if (isAuthenticated) { 47 loadTemplates(); 48 loadCategories(); 49 } 50 }, [currentPage, filterStatus, filterCategory, searchTerm, isAuthenticated]); 51 const checkAuthentication = async () => { 52 try { 53 if (!user) { 54 setAuthChecking(false); 55 setError("Please login first"); 56 return; 57 } 58 if (!user.can_manage_blog) { 59 setError("Insufficient permissions. Only administrators can access this page"); 60 setAuthChecking(false); 61 return; 62 } 63 setIsAuthenticated(true); 64 } catch (error2) { 65 console.error("Authentication check failed:", error2); 66 setError("Authentication check failed"); 67 } finally { 68 setAuthChecking(false); 69 } 70 }; 71 const loadTemplates = async () => { 72 try { 73 setLoading(true); 74 const params = new URLSearchParams({ 75 page: currentPage.toString(), 76 limit: "10", 77 status: filterStatus, 78 category: filterCategory, 79 search: searchTerm 80 }); 81 const response = await fetch(`/api/ai-video-templates?${params}`, { 82 headers: { 83 "Authorization": `Bearer ${localStorage.getItem("authToken")}`, 84 "x-user-data": JSON.stringify(user) 85 } 86 }); 87 if (response.ok) { 88 const data = await response.json(); 89 console.log("ð APIè¿åçæ¨¡æ¿æ°æ®:", data.templates?.slice(0, 2)); 90 setTemplates(data.templates); 91 setTotalPages(data.pagination.pages); 92 } else { 93 setError("Failed to load templates"); 94 } 95 } catch (error2) { 96 console.error("Failed to load templates:", error2); 97 setError("Failed to load templates"); 98 } finally { 99 setLoading(false); 100 } 101 }; 102 const loadCategories = async () => { 103 try { 104 const response = await fetch("/api/ai-video-categories", { 105 headers: { 106 "Authorization": `Bearer ${localStorage.getItem("authToken")}`, 107 "x-user-data": JSON.stringify(user) 108 } 109 }); 110 if (response.ok) { 111 const data = await response.json(); 112 setCategories(data.categories || []); 113 } 114 } catch (error2) { 115 console.error("Failed to load categories:", error2); 116 } 117 }; 118 const handleSubmit = async (e) => { 119 e.preventDefault(); 120 if (submitting) return; 121 setSubmitting(true); 122 try { 123 const formDataToSend = new FormData(); 124 formDataToSend.append("title", formData.title); 125 formDataToSend.append("slug", formData.slug); 126 formDataToSend.append("description", formData.description); 127 formDataToSend.append("category_id", formData.category_id); 128 formDataToSend.append("status", formData.status); 129 formDataToSend.append("featured", formData.featured.toString()); 130 formDataToSend.append("tags", JSON.stringify(formData.tags)); 131 if (thumbnailFile) { 132 formDataToSend.append("thumbnail", thumbnailFile); 133 } 134 const url = isEditing && editingId ? `/api/ai-video-templates/${editingId}` : "/api/ai-video-templates"; 135 const method = isEditing ? "PUT" : "POST"; 136 const response = await fetch(url, { 137 method, 138 body: formDataToSend 139 }); 140 if (response.ok) { 141 setFormData({ 142 title: "", 143 slug: "", 144 description: "", 145 category_id: "", 146 status: "draft", 147 featured: false, 148 tags: [] 149 }); 150 setThumbnailFile(null); 151 setTagInput(""); 152 setIsEditing(false); 153 setEditingId(null); 154 await loadTemplates(); 155 console.log("â æ¨¡æ¿æ´æ°æåï¼æ°æ®å·²å·æ°"); 156 } else { 157 const errorData = await response.json(); 158 setError(errorData.error || "Operation failed"); 159 } 160 } catch (error2) { 161 console.error("Submit failed:", error2); 162 setError("Submit failed"); 163 } finally { 164 setSubmitting(false); 165 } 166 }; 167 const handleEdit = (template) => { 168 setFormData({ 169 title: template.title, 170 slug: template.slug, 171 description: template.description, 172 category_id: template.category_id || "", 173 status: template.status, 174 featured: template.featured, 175 tags: template.tags 176 }); 177 setThumbnailFile(null); 178 setIsEditing(true); 179 setEditingId(template.id); 180 }; 181 const handleDelete = async (id) => { 182 if (!confirm("Are you sure you want to delete this AI video template?")) return; 183 try { 184 const response = await fetch(`/api/ai-video-templates/${id}`, { 185 method: "DELETE" 186 }); 187 if (response.ok) { 188 loadTemplates(); 189 } else { 190 setError("Delete failed"); 191 } 192 } catch (error2) { 193 console.error("Delete failed:", error2); 194 setError("Delete failed"); 195 } 196 }; 197 const handleTagAdd = () => { 198 if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) { 199 setFormData({ 200 ...formData, 201 tags: [...formData.tags, tagInput.trim()] 202 }); 203 setTagInput(""); 204 } 205 }; 206 const handleTagRemove = (tagToRemove) => { 207 setFormData({ 208 ...formData, 209 tags: formData.tags.filter((tag) => tag !== tagToRemove) 210 }); 211 }; 212 const generateSlug = (title) => { 213 return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, ""); 214 }; 215 const handleCategorySubmit = async (e) => { 216 e.preventDefault(); 217 if (categorySubmitting) return; 218 setCategorySubmitting(true); 219 try { 220 const url = isEditingCategory && editingCategoryId ? `/api/ai-video-categories/${editingCategoryId}` : "/api/ai-video-categories"; 221 const method = isEditingCategory ? "PUT" : "POST"; 222 const response = await fetch(url, { 223 method, 224 headers: { 225 "Content-Type": "application/json" 226 }, 227 body: JSON.stringify(categoryFormData) 228 }); 229 if (response.ok) { 230 setCategoryFormData({ 231 name: "", 232 slug: "", 233 description: "", 234 color: "#8A7CFF", 235 icon: "", 236 sort_order: 0 237 }); 238 setIsEditingCategory(false); 239 setEditingCategoryId(null); 240 await loadCategories(); 241 console.log("â åç±»æ´æ°æåï¼æ°æ®å·²å·æ°"); 242 } else { 243 const errorData = await response.json(); 244 setError(errorData.error || "åç±»æä½å¤±è´¥"); 245 } 246 } catch (error2) { 247 console.error("åç±»æä½å¤±è´¥:", error2); 248 setError("åç±»æä½å¤±è´¥"); 249 } finally { 250 setCategorySubmitting(false); 251 } 252 }; 253 const handleEditCategory = (category) => { 254 setCategoryFormData({ 255 name: category.name, 256 slug: category.slug || generateSlug(category.name), 257 description: category.description || "", 258 color: category.color || "#8A7CFF", 259 icon: "", 260 sort_order: 0 261 }); 262 setIsEditingCategory(true); 263 setEditingCategoryId(category.id); 264 };
265 const handleDeleteCategory = async (id) => { 266 if (!confirm("ç¡®å®è¦å é¤è¿ä¸ªåç±»åï¼å é¤å该åç±»ä¸ç模æ¿å°å为æ åç±»ç¶æã")) return; 267 try { 268 const response = await fetch(`/api/ai-video-categories/${id}`, { 269 method: "DELETE" 270 }); 271 if (response.ok) { 272 loadCategories(); 273 } else { 274 const errorData = await response.json(); 275 setError(errorData.error || "å é¤å类失败"); 276 } 277 } catch (error2) { 278 console.error("å é¤å类失败:", error2); 279 setError("å é¤å类失败"); 280 } 281 }; 282 if (authChecking) { 283 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-screen bg-[#121a22] flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-white text-xl", children: "Checking permissions..." }) }); 284 } 285 if (!isAuthenticated) { 286 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-screen bg-[#121a22]", children: [ 287 /* @__PURE__ */ jsxRuntimeExports.jsx(Navbar, { user, setUser: setUser || (() => { 288 }) }),
289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "container mx-auto px-4 py-16 text-center", children: [ 290 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-4xl font-bold text-white mb-4", children: "Insufficient Permissions" }), 291 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-300 mb-8", children: error || "Only administrators can access this page" }), 292 /* @__PURE__ */ jsxRuntimeExports.jsx(Link, { to: "/", className: "bg-[#8A7CFF] text-white px-6 py-3 rounded-lg hover:bg-[#7B6FEE] transition-colors", children: "Return to Home" }) 293 ] }), 294 /* @__PURE__ */ jsxRuntimeExports.jsx(Footer, {}) 295 ] }); 296 } 297 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-h-screen bg-[#121a22]", children: [ 298 /* @__PURE__ */ jsxRuntimeExports.jsx(Navbar, { user, setUser: setUser || (() => { 299 }) }), 300 /* @__PURE__ */ jsxRuntimeExports.jsxs("main", { className: "container mx-auto px-4 py-8", children: [ 301 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8", children: [
302 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-3xl font-bold text-white mb-2", children: "AI Video Templates Management" }), 303 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-300", children: "Manage AI video templates, including create, edit and delete operations" }) 304 ] }), 305 error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-red-500/10 border border-red-500/30 rounded-lg p-4 mb-6", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-red-300", children: error }) }), 306 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-[#1a242f] rounded-lg p-6 mb-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [ 307 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 308 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Status Filter" }), 309 /* @__PURE__ */ jsxRuntimeExports.jsxs( 310 "select", 311 { 312 value: filterStatus, 313 onChange: (e) => setFilterStatus(e.target.value), 314 className: "w-full p-2 bg-[#2C3640] border border-gray-600 rounded-lg text-white", 315 children: [
316 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "all", children: "All" }), 317 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "published", children: "Published" }), 318 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "draft", children: "Draft" }) 319 ] 320 } 321 ) 322 ] }), 323 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 324 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Category Filter" }), 325 /* @__PURE__ */ jsxRuntimeExports.jsxs( 326 "select", 327 { 328 value: filterCategory, 329 onChange: (e) => setFilterCategory(e.target.value), 330 className: "w-full p-2 bg-[#2C3640] border border-gray-600 rounded-lg text-white", 331 children: [ 332 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "All Categories" }), 333 categories.map((category) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: category.id, children: category.name }, category.id)) 334 ] 335 } 336 ) 337 ] }),
338 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 339 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Search" }), 340 /* @__PURE__ */ jsxRuntimeExports.jsx( 341 "input", 342 { 343 type: "text", 344 value: searchTerm, 345 onChange: (e) => setSearchTerm(e.target.value), 346 placeholder: "Search templates...", 347 className: "w-full p-2 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 348 } 349 ) 350 ] }), 351 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-2", children: [ 352 /* @__PURE__ */ jsxRuntimeExports.jsx( 353 "button", 354 { 355 onClick: () => { 356 setFilterStatus("all"); 357 setFilterCategory(""); 358 setSearchTerm(""); 359 setCurrentPage(1); 360 }, 361 className: "flex-1 bg-[#8A7CFF] text-white px-4 py-2 rounded-lg hover:bg-[#7B6FEE] transition-colors", 362 children: "Reset Filters" 363 } 364 ),
365 /* @__PURE__ */ jsxRuntimeExports.jsxs( 366 "button", 367 { 368 onClick: () => setShowCategoryManagement(!showCategoryManagement), 369 className: "bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors", 370 children: [ 371 showCategoryManagement ? "Hide" : "Manage", 372 " Categories" 373 ] 374 } 375 ) 376 ] }) 377 ] }) }), 378 showCategoryManagement && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#1a242f] rounded-lg p-6 mb-6", children: [ 379 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold text-white mb-4", children: isEditingCategory ? "Edit Category" : "Create New Category" }), 380 /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleCategorySubmit, className: "space-y-4", children: [ 381 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
382 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 383 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Category Name *" }), 384 /* @__PURE__ */ jsxRuntimeExports.jsx( 385 "input", 386 { 387 type: "text", 388 value: categoryFormData.name, 389 onChange: (e) => { 390 setCategoryFormData({ 391 ...categoryFormData, 392 name: e.target.value, 393 slug: generateSlug(e.target.value) 394 }); 395 }, 396 required: true, 397 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 398 } 399 ) 400 ] }), 401 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 402 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Slug" }),
403 /* @__PURE__ */ jsxRuntimeExports.jsx( 404 "input", 405 { 406 type: "text", 407 value: categoryFormData.slug, 408 onChange: (e) => setCategoryFormData({ ...categoryFormData, slug: e.target.value }), 409 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 410 } 411 ) 412 ] }) 413 ] }), 414 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 415 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Description (Prompts) *" }), 416 /* @__PURE__ */ jsxRuntimeExports.jsx( 417 "textarea", 418 { 419 value: categoryFormData.description, 420 onChange: (e) => setCategoryFormData({ ...categoryFormData, description: e.target.value }), 421 rows: 3, 422 required: true, 423 placeholder: "Enter default prompts for this category (one per line or separated by commas)", 424 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 425 } 426 ),
427 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-400 text-sm mt-1", children: "These prompts will be used as default prompts for templates in this category." }) 428 ] }), 429 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [ 430 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 431 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Color" }), 432 /* @__PURE__ */ jsxRuntimeExports.jsx( 433 "input", 434 { 435 type: "color", 436 value: categoryFormData.color, 437 onChange: (e) => setCategoryFormData({ ...categoryFormData, color: e.target.value }), 438 className: "w-full p-2 bg-[#2C3640] border border-gray-600 rounded-lg" 439 } 440 ) 441 ] }), 442 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 443 /* @__P
443URE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Sort Order" }), 444 /* @__PURE__ */ jsxRuntimeExports.jsx( 445 "input", 446 { 447 type: "number", 448 value: categoryFormData.sort_order, 449 onChange: (e) => setCategoryFormData({ ...categoryFormData, sort_order: parseInt(e.target.value) || 0 }), 450 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 451 } 452 ) 453 ] }) 454 ] }), 455 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4", children: [ 456 /* @__PURE__ */ jsxRuntimeExports.jsx( 457 "button", 458 { 459 type: "submit", 460 disabled: categorySubmitting, 461 className: "bg-[#8A7CFF] text-white px-6 py-3 rounded-lg hover:bg-[#7B6FEE] transition-colors disabled:opacity-50", 462 children: categorySubmitting ? "Saving..." : isEditingCategory ? "Update Category" : "Create Category" 463 } 464 ), 465 isEditingCategory && /* @__PURE__ */ jsxRuntimeExports.jsx( 466 "button", 467 { 468 type: "button", 469 onClick: () => { 470 setIsEditingCategory(false); 471 setEditingCategoryId(null); 472 setCategoryFormData({ 473 name: "", 474 slug: "", 475 description: "", 476 color: "#8A7CFF", 477 icon: "", 478 sort_order: 0 479 }); 480 }, 481 className: "bg-gray-600 text-white px-6 py-3 rounded-lg hover:bg-gray-700 transition-colors", 482 children: "Cancel Edit" 483 } 484 ) 485 ] }) 486 ] }),
487 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-8", children: [ 488 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-semibold text-white mb-4", children: "Existing Categories" }), 489 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: categories.map((category) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#2C3640] rounded-lg p-4", children: [ 490 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2", children: [ 491 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 492 /* @__PURE__ */ jsxRuntimeExports.jsx( 493 "div", 494 { 495 className: "w-4 h-4 rounded-full", 496 style: { backgroundColor: category.color } 497 } 498 ), 499 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-white font-medium", children: category.name }) 500 ] }),
501 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 502 /* @__PURE__ */ jsxRuntimeExports.jsx( 503 "button", 504 { 505 onClick: () => handleEditCategory(category), 506 className: "bg-blue-600 text-white px-2 py-1 rounded text-sm hover:bg-blue-700 transition-colors", 507 children: "Edit" 508 } 509 ), 510 /* @__PURE__ */ jsxRuntimeExports.jsx( 511 "button", 512 { 513 onClick: () => handleDeleteCategory(category.id), 514 className: "bg-red-600 text-white px-2 py-1 rounded text-sm hover:bg-red-700 transition-colors", 515 children: "Delete" 516 } 517 ) 518 ] }) 519 ] }), 520 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-400 text-sm", children: category.description }), 521 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-gray-500 text-xs mt-1", children: [ 522 "Slug: ", 523 category.slug 524 ] }) 525 ] }, category.id)) }) 526 ] }) 527 ] }),
528 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#1a242f] rounded-lg p-6 mb-6", children: [ 529 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold text-white mb-4", children: isEditing ? "Edit AI Video Template" : "Create New AI Video Template" }), 530 /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [ 531 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [ 532 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 533 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Title *" }), 534 /* @__PURE__ */ jsxRuntimeExports.jsx( 535 "input", 536 { 537 type: "text", 538 value: formData.title, 539 onChange: (e) => { 540 setFormData({ ...formData, title: e.target.value }); 541 if (!isEditing) { 542 setFormData((prev) => ({ ...prev, slug: generateSlug(e.target.value) })); 543 } 544 }, 545 required: true, 546 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 547 } 548 ) 549 ] }),
550 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 551 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Slug" }), 552 /* @__PURE__ */ jsxRuntimeExports.jsx( 553 "input", 554 { 555 type: "text", 556 value: formData.slug, 557 onChange: (e) => setFormData({ ...formData, slug: e.target.value }), 558 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 559 } 560 ) 561 ] }) 562 ] }), 563 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 564 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Description" }), 565 /* @__PURE__ */ jsxRuntimeExports.jsx( 566 "textarea", 567 { 568 value: formData.description, 569 onChange: (e) => setFormData({ ...formData, description: e.target.value }), 570 rows: 3, 571 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 572 } 573 ) 574 ] }),
575 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [ 576 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 577 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Category" }), 578 /* @__PURE__ */ jsxRuntimeExports.jsxs( 579 "select", 580 { 581 value: formData.category_id, 582 onChange: (e) => setFormData({ ...formData, category_id: e.target.value }), 583 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white", 584 children: [ 585 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Select Category" }), 586 categories.map((category) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: category.id, children: category.name }, category.id)) 587 ] 588 } 589 ) 590 ] }),
591 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 592 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Status" }), 593 /* @__PURE__ */ jsxRuntimeExports.jsxs( 594 "select", 595 { 596 value: formData.status, 597 onChange: (e) => setFormData({ ...formData, status: e.target.value }), 598 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white", 599 children: [ 600 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "draft", children: "Draft" }), 601 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "published", children: "Published" }) 602 ] 603 } 604 ) 605 ] }), 606 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center text-gray-300", children: [
607 /* @__PURE__ */ jsxRuntimeExports.jsx( 608 "input", 609 { 610 type: "checkbox", 611 checked: formData.featured, 612 onChange: (e) => setFormData({ ...formData, featured: e.target.checked }), 613 className: "mr-2" 614 } 615 ), 616 "Featured Template" 617 ] }) }) 618 ] }), 619 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 620 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Thumbnail" }), 621 isEditing && templates.find((t) => t.id === editingId)?.thumbnail && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [ 622 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-400 text-sm mb-2", children: "Current thumbnail:" }), 623 /* @__PURE__ */ jsxRuntimeExports.jsx( 624 "img", 625 { 626 src: templates.find((t) => t.id === editingId)?.thumbnail || "", 627 alt: "Current thumbnail", 628 className: "w-32 h-20 object-cover rounded-lg border border-gray-600" 629 } 630 ) 631 ] }),
632 /* @__PURE__ */ jsxRuntimeExports.jsx( 633 "input", 634 { 635 type: "file", 636 accept: "image/*", 637 onChange: (e) => setThumbnailFile(e.target.files?.[0] || null), 638 className: "w-full p-3 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 639 } 640 ), 641 isEditing && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-gray-400 text-sm mt-1", children: "Leave empty to keep current thumbnail, or upload a new one to replace it." }) 642 ] }), 643 isEditing && templates.find((t) => t.id === editingId)?.videos && (templates.find((t) => t.id === editingId)?.videos?.length || 0) > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 644 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Current Videos" }), 645 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4 mb-4", children: templates.find((t) => t.id === editingId)?.videos?.map((video, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#2C3640] rounded-lg p-3", children: [
646 /* @__PURE__ */ jsxRuntimeExports.jsx( 647 "video", 648 { 649 src: video.url, 650 className: "w-full h-24 object-cover rounded", 651 muted: true, 652 loop: true, 653 playsInline: true 654 } 655 ), 656 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-gray-400 text-xs mt-1", children: [ 657 "Video ", 658 index + 1 659 ] }) 660 ] }, index)) }) 661 ] }), 662 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 663 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-gray-300 mb-2", children: "Tags" }), 664 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 mb-2", children: [ 665 /* @__PURE__ */ jsxRuntimeExports.jsx( 666 "input", 667 { 668 type: "text", 669 value: tagInput, 670 onChange: (e) => setTagInput(e.target.value), 671 onKeyPress: (e) => e.key === "Enter" && (e.preventDefault(), handleTagAdd()), 672 placeholder: "Enter tag...", 673 className: "flex-1 p-2 bg-[#2C3640] border border-gray-600 rounded-lg text-white" 674 } 675 ),
676 /* @__PURE__ */ jsxRuntimeExports.jsx( 677 "button", 678 { 679 type: "button", 680 onClick: handleTagAdd, 681 className: "bg-[#8A7CFF] text-white px-4 py-2 rounded-lg hover:bg-[#7B6FEE] transition-colors", 682 children: "Add" 683 } 684 ) 685 ] }), 686 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-2", children: formData.tags.map((tag, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 687 "span", 688 { 689 className: "bg-[#8A7CFF] text-white px-3 py-1 rounded-full text-sm flex items-center gap-2", 690 children: [ 691 tag, 692 /* @__PURE__ */ jsxRuntimeExports.jsx( 693 "button", 694 { 695 type: "button", 696 onClick: () => handleTagRemove(tag), 697 className: "text-white hover:text-red-300", 698 children: "Ã" 699 } 700 ) 701 ] 702 }, 703 index 704 )) }) 705 ] }),
706 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4", children: [ 707 /* @__PURE__ */ jsxRuntimeExports.jsx( 708 "button", 709 { 710 type: "submit", 711 disabled: submitting, 712 className: "bg-[#8A7CFF] text-white px-6 py-3 rounded-lg hover:bg-[#7B6FEE] transition-colors disabled:opacity-50", 713 children: submitting ? "Saving..." : isEditing ? "Update Template" : "Create Template" 714 } 715 ), 716 isEditing && /* @__PURE__ */ jsxRuntimeExports.jsx( 717 "button", 718 { 719 type: "button", 720 onClick: () => { 721 setIsEditing(false); 722 setEditingId(null); 723 setFormData({ 724 title: "", 725 slug: "", 726 description: "", 727 category_id: "", 728 status: "draft", 729 featured: false, 730 tags: [] 731 }); 732 setThumbnailFile(null); 733 setTagInput(""); 734 }, 735 className: "bg-gray-600 text-white px-6 py-3 rounded-lg hover:bg-gray-700 transition-colors", 736 children: "Cancel Edit" 737 } 738 ) 739 ] }) 740 ] }) 741 ] }),
742 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#1a242f] rounded-lg p-6", children: [ 743 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-semibold text-white mb-4", children: "AI Video Templates List" }), 744 loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-white", children: "Loading..." }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 745 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left", children: [ 746 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-gray-700", children: [ 747 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Title" }), 748 /* @__P
748URE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Status" }), 749 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Category" }), 750 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Videos" }), 751 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Views/Usage" }), 752 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Created" }), 753 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "pb-3 text-gray-300", children: "Actions" }) 754 ] }) }), 755 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: templates.map((template) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-gray-800", children: [ 756 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
757 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-white font-medium", children: template.title }), 758 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-gray-400 text-sm", children: template.slug }) 759 ] }) }), 760 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `px-2 py-1 rounded-full text-xs ${template.status === "published" ? "bg-green-500/20 text-green-400" : "bg-yellow-500/20 text-yellow-400"}`, children: template.status === "published" ? "Published" : "Draft" }) }), 761 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3 text-gray-300", children: template.category_name || "No Category" }), 762 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3 text-gray-300", children: template.video_count }), 763 /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "py-3 text-gray-300", children: [ 764 template.view_count, 765 "/", 766 template.use_count 767 ] }),
768 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3 text-gray-300", children: formatBlogDate(template.created_at) }), 769 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-3", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 770 /* @__PURE__ */ jsxRuntimeExports.jsx( 771 "button", 772 { 773 onClick: () => handleEdit(template), 774 className: "bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700 transition-colors", 775 children: "Edit" 776 } 777 ), 778 /* @__PURE__ */ jsxRuntimeExports.jsx( 779 "button", 780 { 781 onClick: () => handleDelete(template.id), 782 className: "bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition-colors", 783 children: "Delete" 784 } 785 ) 786 ] }) }) 787 ] }, template.id)) }) 788 ] }) }), 789 totalPages > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-center mt-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
790 /* @__PURE__ */ jsxRuntimeExports.jsx( 791 "button", 792 { 793 onClick: () => setCurrentPage(Math.max(1, currentPage - 1)), 794 disabled: currentPage === 1, 795 className: "px-3 py-2 bg-[#2C3640] text-white rounded-lg disabled:opacity-50 hover:bg-[#3A444E] transition-colors", 796 children: "Previous" 797 } 798 ), 799 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "px-3 py-2 text-gray-300", children: [ 800 currentPage, 801 " / ", 802 totalPages 803 ] }), 804 /* @__PURE__ */ jsxRuntimeExports.jsx( 805 "button", 806 { 807 onClick: () => setCurrentPage(Math.min(totalPages, currentPage + 1)), 808 disabled: currentPage === totalPages, 809 className: "px-3 py-2 bg-[#2C3640] text-white rounded-lg disabled:opacity-50 hover:bg-[#3A444E] transition-colors", 810 children: "Next" 811 } 812 ) 813 ] }) }) 814 ] }) 815 ] }) 816 ] }), 817 /* @__PURE__ */ jsxRuntimeExports.jsx(Footer, {}) 818 ] }); 819}; 820 821export { AIVideoTemplateAdmin as default };
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.