import { useState, useCallback, useEffect } from "react";
/* ── WordPress Site Manager ──────────────────────────────────────
เชื่อมต่อ vanvaew.com (หรือเว็บ WP ใดๆ) ผ่าน REST API
พร้อม Application Password → แก้ไข Posts, Pages, Homepage
──────────────────────────────────────────────────────────────── */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0f0e0c; --bg2:#181614; --bg3:#211f1c;
--border:rgba(255,255,255,0.07); --border2:rgba(255,255,255,0.12);
--text:#f0ece4; --muted:#888078; --sub:#5a5650;
--green:#2ec27e; --green-d:#1a7a50; --green-bg:rgba(46,194,126,0.1);
--amber:#e8a030; --amber-bg:rgba(232,160,48,0.1);
--red:#e05050; --red-bg:rgba(224,80,80,0.1);
--blue:#4a9eff; --blue-bg:rgba(74,158,255,0.08);
--radius:12px;
}
body{background:var(--bg);color:var(--text);font-family:'Sarabun',sans-serif;font-size:14px;line-height:1.6}
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes pulse{0%,100%{opacity:.5}50%{opacity:1}}
.fade{animation:fadeUp .3s ease}
::-webkit-scrollbar{width:4px}::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:#333;border-radius:4px}
input,textarea,select{font-family:'Sarabun',sans-serif;font-size:14px}
button{font-family:'Sarabun',sans-serif;cursor:pointer}
`;
// ── Helpers
const api = (base, path, opts = {}) =>
fetch(`${base}/wp-json/wp/v2${path}`, opts);
const authHead = (user, pass) => ({
Authorization: "Basic " + btoa(`${user}:${pass}`),
"Content-Type": "application/json",
});
const Spin = () => (
);
const Badge = ({ s }) => {
const map = { publish: ["var(--green)", "เผยแพร่"], draft: ["var(--amber)", "ร่าง"], private: ["#a78bfa", "ส่วนตัว"], pending: ["#60a5fa", "รอตรวจ"] };
const [col, lbl] = map[s] ?? ["var(--muted)", s];
return {lbl};
};
const Toast = ({ msg, type }) => {
const col = type === "error" ? "var(--red)" : type === "warn" ? "var(--amber)" : "var(--green)";
return msg ? (
{msg}
) : null;
};
// ── Rich text toolbar helper
const inputStyle = {
width: "100%", background: "var(--bg3)", border: "1px solid var(--border2)",
borderRadius: 8, padding: "10px 12px", color: "var(--text)",
outline: "none", transition: "border-color .2s",
resize: "vertical",
};
// ── Login Screen
const LoginScreen = ({ onLogin }) => {
const [f, setF] = useState({ url: "https://www.vanvaew.com", user: "", pass: "" });
const [loading, setLoading] = useState(false);
const [err, setErr] = useState("");
const go = async () => {
setLoading(true); setErr("");
try {
const res = await api(f.url.replace(/\/$/, ""), "/users/me", { headers: authHead(f.user, f.pass) });
if (!res.ok) throw new Error(`HTTP ${res.status} — ตรวจสอบ username/password`);
const me = await res.json();
onLogin({ ...f, url: f.url.replace(/\/$/, ""), displayName: me.name, roles: me.roles });
} catch (e) { setErr(e.message); }
setLoading(false);
};
return (
⚡
WP Site Manager
vanvaew.com · REST API
{[
{ key: "url", lbl: "Site URL", ph: "https://www.vanvaew.com", type: "url" },
{ key: "user", lbl: "Username", ph: "admin / vanvaew", type: "text" },
{ key: "pass", lbl: "Application Password", ph: "xxxx xxxx xxxx xxxx xxxx xxxx", type: "password" },
].map(({ key, lbl, ph, type }) => (
setF(p => ({ ...p, [key]: e.target.value }))}
style={{ ...inputStyle, fontSize: 13 }}
onFocus={e => e.target.style.borderColor = "var(--green)"}
onBlur={e => e.target.style.borderColor = "var(--border2)"}
onKeyDown={e => e.key === "Enter" && go()}
/>
))}
{err &&
{err}
}
);
};
// ── Main App
export default function App() {
const [cred, setCred] = useState(null);
const [tab, setTab] = useState("posts");
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [editing, setEditing] = useState(null); // { type, item }
const [toast, setToast] = useState({ msg: "", type: "ok" });
const [saving, setSaving] = useState(false);
const [search, setSearch] = useState("");
const [creating, setCreating] = useState(false);
const showToast = (msg, type = "ok") => {
setToast({ msg, type });
setTimeout(() => setToast({ msg: "", type: "ok" }), 3500);
};
// ── Fetch list
const fetchList = useCallback(async (type) => {
if (!cred) return;
setLoading(true); setItems([]); setEditing(null); setSearch("");
try {
const endpoint = type === "posts" ? "/posts?per_page=50&_fields=id,title,status,date,link" :
type === "pages" ? "/pages?per_page=50&_fields=id,title,status,date,link" : "";
const res = await api(cred.url, endpoint, { headers: authHead(cred.user, cred.pass) });
if (!res.ok) throw new Error("โหลดไม่ได้");
setItems(await res.json());
} catch (e) { showToast("โหลดล้มเหลว: " + e.message, "error"); }
setLoading(false);
}, [cred]);
useEffect(() => { if (cred && (tab === "posts" || tab === "pages")) fetchList(tab); }, [tab, cred]);
// ── Open editor → fetch full content
const openEdit = async (item, type) => {
setEditing({ type, item: { ...item, content: { rendered: "" }, excerpt: { rendered: "" } }, loading: true });
try {
const res = await api(cred.url, `/${type}/${item.id}?context=edit`, { headers: authHead(cred.user, cred.pass) });
const full = await res.json();
setEditing({ type, item: full, loading: false });
} catch (e) { showToast("โหลด content ล้มเหลว", "error"); }
};
// ── Save (update)
const saveEdit = async () => {
if (!editing) return;
setSaving(true);
try {
const { type, item } = editing;
const body = {
title: item.title?.raw ?? item.title?.rendered ?? "",
content: item.content?.raw ?? item.content?.rendered ?? "",
excerpt: item.excerpt?.raw ?? item.excerpt?.rendered ?? "",
status: item.status,
};
const res = await api(cred.url, `/${type}/${item.id}`, {
method: "POST",
headers: authHead(cred.user, cred.pass),
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || `HTTP ${res.status}`);
}
showToast(`✓ บันทึกสำเร็จ — ${item.title?.rendered || item.title?.raw || ""}`, "ok");
fetchList(type);
setEditing(null);
} catch (e) { showToast("บันทึกล้มเหลว: " + e.message, "error"); }
setSaving(false);
};
// ── Create new
const createNew = async (type, data) => {
setSaving(true);
try {
const res = await api(cred.url, `/${type}`, {
method: "POST",
headers: authHead(cred.user, cred.pass),
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || `HTTP ${res.status}`);
}
const created = await res.json();
showToast(`✓ สร้างสำเร็จ!`, "ok");
setCreating(false);
fetchList(type);
openEdit(created, type);
} catch (e) { showToast("สร้างล้มเหลว: " + e.message, "error"); }
setSaving(false);
};
// ── Delete
const deleteItem = async (type, id, title) => {
if (!window.confirm(`⚠️ ยืนยันการลบ "${title}"?\nการกระทำนี้ไม่สามารถย้อนกลับได้`)) return;
try {
const res = await api(cred.url, `/${type}/${id}?force=false`, {
method: "DELETE",
headers: authHead(cred.user, cred.pass),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
showToast(`🗑 ย้ายไป Trash แล้ว`, "warn");
setItems(prev => prev.filter(i => i.id !== id));
if (editing?.item?.id === id) setEditing(null);
} catch (e) { showToast("ลบล้มเหลว: " + e.message, "error"); }
};
if (!cred) return ;
const filtered = items.filter(i => {
const t = (i.title?.rendered || "").toLowerCase();
return t.includes(search.toLowerCase());
});
const navItems = [
{ id: "posts", icon: "📝", label: "โพสต์" },
{ id: "pages", icon: "📄", label: "Pages" },
{ id: "homepage", icon: "🏠", label: "Homepage" },
];
return (
{/* ── Sidebar */}
⚡ WP Manager
{cred.url.replace(/https?:\/\//, "")}
● {cred.displayName}
{/* ── Main Content */}
{/* List panel */}
{(tab === "posts" || tab === "pages") && (
{/* Toolbar */}
setSearch(e.target.value)}
style={{ ...inputStyle, flex: 1, padding: "8px 12px", fontSize: 13 }}
onFocus={e => e.target.style.borderColor = "var(--green)"}
onBlur={e => e.target.style.borderColor = "var(--border2)"}
/>
{/* List */}
{loading && (
{Array(6).fill(0).map((_, i) => (
))}
)}
{!loading && filtered.map(item => (
openEdit(item, tab)}
style={{
padding: "12px 14px", borderRadius: 10, marginBottom: 4,
background: editing?.item?.id === item.id ? "var(--bg3)" : "transparent",
border: `1px solid ${editing?.item?.id === item.id ? "var(--border2)" : "transparent"}`,
cursor: "pointer", transition: "all .15s",
display: "flex", alignItems: "flex-start", gap: 10,
}}
onMouseEnter={e => { if (editing?.item?.id !== item.id) e.currentTarget.style.background = "var(--bg3)"; }}
onMouseLeave={e => { if (editing?.item?.id !== item.id) e.currentTarget.style.background = "transparent"; }}
>
))}
{!loading && filtered.length === 0 && (
{search ? "ไม่พบผลลัพธ์" : `ไม่มี ${tab === "posts" ? "โพสต์" : "Page"}`}
)}
)}
{/* Editor panel */}
{editing && (
{/* Editor header */}
#{editing.item.id} · {editing.type}
{editing.item.link && (
↗ ดูบนเว็บ
)}
{editing.loading ? (
กำลังโหลด...
) : (
{/* Title */}
setEditing(p => ({ ...p, item: { ...p.item, title: { ...p.item.title, raw: e.target.value } } }))}
style={{ ...inputStyle, fontSize: 18, fontWeight: 700, padding: "12px 14px" }}
onFocus={e => e.target.style.borderColor = "var(--green)"}
onBlur={e => e.target.style.borderColor = "var(--border2)"}
placeholder="ชื่อบทความ..."
/>
{/* Excerpt */}
{/* Content */}
{(editing.item.content?.raw ?? "").length} ตัวอักษร
)}
)}
{/* Homepage Tab */}
{tab === "homepage" &&
}
{/* Empty state */}
{(tab === "posts" || tab === "pages") && !editing && !loading && items.length > 0 && (
👆
เลือก{tab === "posts" ? "โพสต์" : "Page"}ที่ต้องการแก้ไข
)}
{/* Create Modal */}
{creating &&
setCreating(false)} onCreate={createNew} saving={saving} />}
);
}
// ── Homepage Editor
function HomepageEditor({ cred, showToast }) {
const [pages, setPages] = useState([]);
const [hp, setHp] = useState(null);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
useEffect(() => {
(async () => {
setLoading(true);
try {
// Get reading settings to find homepage ID
const settRes = await fetch(`${cred.url}/wp-json`, { headers: authHead(cred.user, cred.pass) });
// Fallback: get front page via pages list
const pRes = await api(cred.url, "/pages?per_page=50&_fields=id,title,status", { headers: authHead(cred.user, cred.pass) });
const ps = await pRes.json();
setPages(ps);
// Try to get the settings
const optRes = await fetch(`${cred.url}/wp-json/wp/v2/settings`, { headers: authHead(cred.user, cred.pass) });
if (optRes.ok) {
const opts = await optRes.json();
const frontId = opts.page_on_front;
if (frontId) {
const fpRes = await api(cred.url, `/pages/${frontId}?context=edit`, { headers: authHead(cred.user, cred.pass) });
if (fpRes.ok) { setHp(await fpRes.json()); }
}
}
} catch (e) { }
setLoading(false);
})();
}, [cred]);
const loadPage = async (id) => {
setLoading(true);
const res = await api(cred.url, `/pages/${id}?context=edit`, { headers: authHead(cred.user, cred.pass) });
if (res.ok) setHp(await res.json());
setLoading(false);
};
const save = async () => {
if (!hp) return;
setSaving(true);
try {
const res = await api(cred.url, `/pages/${hp.id}`, {
method: "POST",
headers: authHead(cred.user, cred.pass),
body: JSON.stringify({
title: hp.title?.raw ?? hp.title?.rendered,
content: hp.content?.raw ?? hp.content?.rendered,
status: hp.status,
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
showToast("✓ บันทึก Homepage สำเร็จ!", "ok");
} catch (e) { showToast("บันทึกล้มเหลว: " + e.message, "error"); }
setSaving(false);
};
return (
🏠 แก้ไข Homepage
{hp && (
)}
{loading ? (
กำลังโหลด...
) : !hp ? (
🏠
เลือก Page ที่เป็น Homepage จาก dropdown ด้านบน
ปกติคือ Page ชื่อ "Home" หรือ "หน้าแรก"
) : (
)}
);
}
// ── Create Modal
function CreateModal({ type, onClose, onCreate, saving }) {
const [f, setF] = useState({ title: "", content: "", status: "draft" });
const lbl = type === "posts" ? "โพสต์ใหม่" : "Page ใหม่";
return (
+ สร้าง{lbl}
setF(p => ({ ...p, title: e.target.value }))}
style={{ ...inputStyle, fontSize: 15 }}
onFocus={e => e.target.style.borderColor = "var(--green)"}
onBlur={e => e.target.style.borderColor = "var(--border2)"}
placeholder={type === "posts" ? "ชื่อบทความ..." : "ชื่อหน้า..."}
autoFocus
/>
);
}
https://www.vanvaew.com/post-sitemap.xml
2026-03-20T04:12:31+00:00
https://www.vanvaew.com/page-sitemap.xml
2026-03-27T08:08:24+00:00
https://www.vanvaew.com/product-sitemap.xml
2026-03-19T09:39:32+00:00
https://www.vanvaew.com/product_cat-sitemap.xml
2026-03-19T09:39:32+00:00