PageSourceSearch

https://veo3free.ai/AIVideoTemplateAdmin-6HM3__J3.js

js veo3free.ai collected 2026-10-05 13:16:32 UTC 40,247 bytes, 821 lines download raw bytes

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.