ngx-xtroedge-cms 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -263,6 +263,58 @@ var CMS_STYLES = `
263
263
  }
264
264
  .lcms-spinner-lg { width: 36px; height: 36px; border: 3px solid rgba(255,255,255,0.2); border-top-color: var(--lcms-primary, #00C853); border-radius: 50%; animation: lcmsSpin 0.6s linear infinite; }
265
265
 
266
+ /* LOGIN MODAL */
267
+ .lcms-login-overlay {
268
+ position: fixed; inset: 0; z-index: 10010;
269
+ display: flex; align-items: center; justify-content: center;
270
+ background: rgba(8, 8, 15, 0.75);
271
+ backdrop-filter: blur(16px) saturate(1.4);
272
+ -webkit-backdrop-filter: blur(16px) saturate(1.4);
273
+ font-family: system-ui, -apple-system, sans-serif;
274
+ }
275
+ .lcms-login-box {
276
+ background: #13151a; border: 1px solid rgba(255,255,255,0.1);
277
+ border-radius: 16px; padding: 36px 32px; width: 340px;
278
+ box-shadow: 0 24px 60px rgba(0,0,0,0.6);
279
+ }
280
+ .lcms-login-logo {
281
+ display: flex; align-items: center; justify-content: center;
282
+ gap: 8px; margin-bottom: 24px;
283
+ }
284
+ .lcms-login-logo-icon {
285
+ width: 32px; height: 32px; border-radius: 8px;
286
+ background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
287
+ display: flex; align-items: center; justify-content: center;
288
+ }
289
+ .lcms-login-logo-text { color: white; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; }
290
+ .lcms-login-title { color: white; font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 6px; }
291
+ .lcms-login-sub { color: rgba(255,255,255,0.4); font-size: 12px; text-align: center; margin-bottom: 24px; }
292
+ .lcms-login-field { margin-bottom: 14px; }
293
+ .lcms-login-label { display: block; color: rgba(255,255,255,0.6); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.8px; margin-bottom: 6px; }
294
+ .lcms-login-input {
295
+ width: 100%; box-sizing: border-box;
296
+ background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
297
+ border-radius: 8px; padding: 10px 12px;
298
+ color: white; font-size: 13px; font-family: inherit; outline: none;
299
+ transition: border-color 0.2s;
300
+ }
301
+ .lcms-login-input:focus { border-color: var(--lcms-primary, #00C853); }
302
+ .lcms-login-input::placeholder { color: rgba(255,255,255,0.25); }
303
+ .lcms-login-btn {
304
+ width: 100%; padding: 11px; margin-top: 6px; border: none; border-radius: 8px; cursor: pointer;
305
+ background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
306
+ color: white; font-size: 13px; font-weight: 700; font-family: inherit;
307
+ letter-spacing: 0.3px; transition: filter 0.2s;
308
+ }
309
+ .lcms-login-btn:hover { filter: brightness(1.1); }
310
+ .lcms-login-btn:disabled { opacity: 0.6; cursor: not-allowed; }
311
+ .lcms-login-error {
312
+ background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.3);
313
+ border-radius: 8px; padding: 9px 12px; color: #f87171;
314
+ font-size: 12px; text-align: center; margin-top: 12px; display: none;
315
+ }
316
+ .lcms-login-error.visible { display: block; }
317
+
266
318
  /* HIDDEN ELEMENTS */
267
319
  .lcms-hidden { display: none !important; }
268
320
  `;
@@ -312,6 +364,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
312
364
  this.brandingEl = null;
313
365
  this.siteIdentifier = "";
314
366
  this.siteIdEl = null;
367
+ this.loginModalEl = null;
368
+ this.pendingEditMode = false;
369
+ // edit=true was requested but token missing
315
370
  // ===== State =====
316
371
  this.editMode = false;
317
372
  this.currentLang = "en";
@@ -501,12 +556,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
501
556
  this.resetAll();
502
557
  }
503
558
  const editFromSession = sessionStorage.getItem("builder_edit_mode") === "true";
504
- if (editViaParam || editFromSession) {
559
+ const wantsEdit = editViaParam || editFromSession;
560
+ if (wantsEdit) {
561
+ const token = localStorage.getItem("builder_token");
562
+ if (!token) {
563
+ this.pendingEditMode = true;
564
+ this.currentLang = this.detectCurrentLanguage();
565
+ this.setLoading(true);
566
+ setTimeout(() => {
567
+ this.loadTranslationsAndInit(false);
568
+ this.showLoginModal();
569
+ }, 300);
570
+ return;
571
+ }
505
572
  this.editMode = true;
506
573
  }
507
574
  this.currentLang = this.detectCurrentLanguage();
508
575
  this.setLoading(true);
509
- const loadDraft = editViaParam || editFromSession;
576
+ const loadDraft = wantsEdit;
510
577
  setTimeout(() => {
511
578
  this.loadTranslationsAndInit(loadDraft);
512
579
  }, 300);
@@ -779,7 +846,7 @@ var XtroedgeCMS = class _XtroedgeCMS {
779
846
  if (this.containerSelector) {
780
847
  return document.querySelector(this.containerSelector) || document.body;
781
848
  }
782
- return document.querySelector(".layout") || document.body;
849
+ return document.body;
783
850
  }
784
851
  autoDetectElements() {
785
852
  const container = this.resolveContainer();
@@ -787,7 +854,29 @@ var XtroedgeCMS = class _XtroedgeCMS {
787
854
  for (const el of this.autoDetectedElements) {
788
855
  if (!document.contains(el)) this.autoDetectedElements.delete(el);
789
856
  }
857
+ const getSectionSlug = (el) => {
858
+ let parent = el.parentElement;
859
+ while (parent && parent !== document.body) {
860
+ const tag = parent.tagName.toLowerCase();
861
+ if (tag === "header" || tag.endsWith("-header")) return "/header";
862
+ if (tag === "footer" || tag.endsWith("-footer")) return "/footer";
863
+ parent = parent.parentElement;
864
+ }
865
+ return this.currentSlug;
866
+ };
790
867
  const tagCounters = {};
868
+ const assignKey = (el, sectionSlug) => {
869
+ const sectionKey = sectionSlug === "/header" ? "header" : sectionSlug === "/footer" ? "footer" : "page";
870
+ if (!tagCounters[sectionKey]) tagCounters[sectionKey] = {};
871
+ const tag = el.tagName.toLowerCase();
872
+ if (!tagCounters[sectionKey][tag]) tagCounters[sectionKey][tag] = 0;
873
+ const prefix = sectionKey !== "page" ? `${sectionKey}_` : "";
874
+ const key = `${prefix}xcms_${tag}_${tagCounters[sectionKey][tag]}`;
875
+ tagCounters[sectionKey][tag]++;
876
+ el.setAttribute("data-cms", key);
877
+ el.setAttribute("data-cms-section", sectionSlug);
878
+ this.autoDetectedElements.add(el);
879
+ };
791
880
  const selector = this.editableTags.join(",");
792
881
  const elements = container.querySelectorAll(selector);
793
882
  elements.forEach((el) => {
@@ -796,12 +885,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
796
885
  if (el.children.length > 3) return;
797
886
  const text = this.getDirectTextContent(el).trim();
798
887
  if (text.length < 2) return;
799
- const tag = el.tagName.toLowerCase();
800
- if (!tagCounters[tag]) tagCounters[tag] = 0;
801
- const key = `xcms_${tag}_${tagCounters[tag]}`;
802
- tagCounters[tag]++;
803
- el.setAttribute("data-cms", key);
804
- this.autoDetectedElements.add(el);
888
+ assignKey(el, getSectionSlug(el));
889
+ });
890
+ const dataEditables = container.querySelectorAll("[data-editable]");
891
+ dataEditables.forEach((el) => {
892
+ if (el.hasAttribute("data-cms")) return;
893
+ if (el.closest("#xtroedge-cms-root, script, style, noscript")) return;
894
+ assignKey(el, getSectionSlug(el));
805
895
  });
806
896
  }
807
897
  scanDOM() {
@@ -872,8 +962,13 @@ var XtroedgeCMS = class _XtroedgeCMS {
872
962
  const currentVal = this.getPageText(key);
873
963
  if (text !== currentVal) this.onTextChanged(key, text);
874
964
  };
875
- this.managedElements.set(el, { key, blurHandler, keydownHandler, inputHandler });
876
- if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler);
965
+ const clickHandler = (e) => {
966
+ e.preventDefault();
967
+ e.stopPropagation();
968
+ };
969
+ const sectionSlug = el.getAttribute("data-cms-section") || this.currentSlug;
970
+ this.managedElements.set(el, { key, sectionSlug, blurHandler, keydownHandler, inputHandler, clickHandler });
971
+ if (this.editMode) this.enableElementEdit(el, key, blurHandler, keydownHandler, inputHandler, clickHandler);
877
972
  }
878
973
  detachElement(el) {
879
974
  const info = this.managedElements.get(el);
@@ -881,11 +976,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
881
976
  el.removeEventListener("blur", info.blurHandler);
882
977
  el.removeEventListener("keydown", info.keydownHandler);
883
978
  el.removeEventListener("input", info.inputHandler);
979
+ el.removeEventListener("click", info.clickHandler, true);
884
980
  el.removeAttribute("contenteditable");
885
981
  this.managedElements.delete(el);
886
982
  }
887
983
  }
888
- enableElementEdit(el, _key, blurH, keyH, inputH) {
984
+ enableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
889
985
  const val = this.getPageText(_key);
890
986
  if (val) el.textContent = val;
891
987
  el.setAttribute("contenteditable", "true");
@@ -899,8 +995,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
899
995
  el.addEventListener("blur", blurH);
900
996
  el.addEventListener("keydown", keyH);
901
997
  el.addEventListener("input", inputH);
998
+ el.addEventListener("click", clickH, true);
902
999
  }
903
- disableElementEdit(el, _key, blurH, keyH, inputH) {
1000
+ disableElementEdit(el, _key, blurH, keyH, inputH, clickH) {
904
1001
  el.removeAttribute("contenteditable");
905
1002
  el.style.borderBottom = "";
906
1003
  el.style.padding = "";
@@ -914,11 +1011,12 @@ var XtroedgeCMS = class _XtroedgeCMS {
914
1011
  el.removeEventListener("blur", blurH);
915
1012
  el.removeEventListener("keydown", keyH);
916
1013
  el.removeEventListener("input", inputH);
1014
+ el.removeEventListener("click", clickH, true);
917
1015
  }
918
1016
  applyEditMode(editMode) {
919
1017
  for (const [el, info] of this.managedElements) {
920
- if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
921
- else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler);
1018
+ if (editMode) this.enableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
1019
+ else this.disableElementEdit(el, info.key, info.blurHandler, info.keydownHandler, info.inputHandler, info.clickHandler);
922
1020
  }
923
1021
  this.applyImageEditMode(editMode);
924
1022
  }
@@ -1240,23 +1338,43 @@ var XtroedgeCMS = class _XtroedgeCMS {
1240
1338
  getPageSection() {
1241
1339
  return this.currentSlug.replace(/^\//, "").replace(/-/g, "_").toUpperCase();
1242
1340
  }
1341
+ // Fetch texts for a single slug and merge into pageTexts
1342
+ async fetchSectionTexts(slug, status) {
1343
+ try {
1344
+ const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(slug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1345
+ if (!res.ok) return null;
1346
+ const page = await res.json();
1347
+ return page?.published_content?.texts || page?.content?.texts || null;
1348
+ } catch {
1349
+ return null;
1350
+ }
1351
+ }
1243
1352
  async loadPageContent(status) {
1244
1353
  this.setLoading(true);
1245
1354
  this.cleanOldHistory();
1246
1355
  try {
1247
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1248
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1249
- const page = await res.json();
1250
- const texts = page?.published_content?.texts || page?.content?.texts;
1251
- if (texts) {
1252
- this.pageTexts = JSON.parse(JSON.stringify(texts));
1356
+ const [pageTexts, headerTexts, footerTexts] = await Promise.all([
1357
+ this.fetchSectionTexts(this.currentSlug, status),
1358
+ this.fetchSectionTexts("/header", status),
1359
+ this.fetchSectionTexts("/footer", status)
1360
+ ]);
1361
+ if (pageTexts || headerTexts || footerTexts) {
1362
+ this.pageTexts = {
1363
+ ...pageTexts || {},
1364
+ ...headerTexts || {},
1365
+ ...footerTexts || {}
1366
+ };
1253
1367
  } else {
1254
1368
  this.buildDefaultTexts();
1255
1369
  }
1256
- const images = page?.published_content?.images || page?.content?.images;
1257
- if (images) {
1258
- this.pageImages = JSON.parse(JSON.stringify(images));
1259
- this.applyPageImages();
1370
+ const resImg = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=${status}&site_identifier=${encodeURIComponent(this.siteIdentifier)}`).catch(() => null);
1371
+ if (resImg?.ok) {
1372
+ const page = await resImg.json();
1373
+ const images = page?.published_content?.images || page?.content?.images;
1374
+ if (images) {
1375
+ this.pageImages = JSON.parse(JSON.stringify(images));
1376
+ this.applyPageImages();
1377
+ }
1260
1378
  }
1261
1379
  this.originalTexts = JSON.parse(JSON.stringify(this.pageTexts));
1262
1380
  this.originalImages = JSON.parse(JSON.stringify(this.pageImages));
@@ -1274,18 +1392,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
1274
1392
  async loadPublishedContent() {
1275
1393
  this.setLoading(true);
1276
1394
  try {
1277
- const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1278
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1279
- const page = await res.json();
1280
- const texts = page?.published_content?.texts;
1281
- if (texts && Object.keys(texts).length > 0) {
1282
- this.pageTexts = JSON.parse(JSON.stringify(texts));
1395
+ const [pageTexts, headerTexts, footerTexts] = await Promise.all([
1396
+ this.fetchSectionTexts(this.currentSlug, "published"),
1397
+ this.fetchSectionTexts("/header", "published"),
1398
+ this.fetchSectionTexts("/footer", "published")
1399
+ ]);
1400
+ const merged = { ...pageTexts || {}, ...headerTexts || {}, ...footerTexts || {} };
1401
+ if (Object.keys(merged).length > 0) {
1402
+ this.pageTexts = JSON.parse(JSON.stringify(merged));
1283
1403
  this.updateElementTexts();
1284
1404
  }
1285
- const images = page?.published_content?.images;
1286
- if (images && Object.keys(images).length > 0) {
1287
- this.pageImages = JSON.parse(JSON.stringify(images));
1288
- this.applyPageImages();
1405
+ const res = await fetch(`${this.config.apiBase}/web-page/get?slug=${encodeURIComponent(this.currentSlug)}&status=published&site_identifier=${encodeURIComponent(this.siteIdentifier)}`);
1406
+ if (res.ok) {
1407
+ const page = await res.json();
1408
+ const images = page?.published_content?.images;
1409
+ if (images && Object.keys(images).length > 0) {
1410
+ this.pageImages = JSON.parse(JSON.stringify(images));
1411
+ this.applyPageImages();
1412
+ }
1289
1413
  }
1290
1414
  } catch {
1291
1415
  } finally {
@@ -1323,6 +1447,18 @@ var XtroedgeCMS = class _XtroedgeCMS {
1323
1447
  }
1324
1448
  }
1325
1449
  }
1450
+ // Group pageTexts by section slug based on managedElements info
1451
+ groupTextsBySection() {
1452
+ const sections = {};
1453
+ for (const [, info] of this.managedElements) {
1454
+ const slug = info.sectionSlug;
1455
+ if (!sections[slug]) sections[slug] = {};
1456
+ if (this.pageTexts[info.key] !== void 0) {
1457
+ sections[slug][info.key] = this.pageTexts[info.key];
1458
+ }
1459
+ }
1460
+ return sections;
1461
+ }
1326
1462
  async saveChanges() {
1327
1463
  if (!this.config.apiBase) {
1328
1464
  this.showToast("No API configured. Set apiBase to enable save.", "error");
@@ -1330,19 +1466,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1330
1466
  }
1331
1467
  this.isSaving = true;
1332
1468
  this.updateUI();
1333
- const payload = {
1334
- slug: this.currentSlug,
1335
- title: this.currentTitle,
1336
- site_identifier: this.siteIdentifier,
1337
- content: { texts: this.pageTexts, images: this.pageImages }
1338
- };
1339
1469
  try {
1340
- const res = await fetch(`${this.config.apiBase}/web_page/save`, {
1341
- method: "POST",
1342
- headers: { "Content-Type": "application/json" },
1343
- body: JSON.stringify(payload)
1344
- });
1345
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1470
+ const sections = this.groupTextsBySection();
1471
+ const requests = Object.entries(sections).map(
1472
+ ([slug, texts]) => fetch(`${this.config.apiBase}/web_page/save`, {
1473
+ method: "POST",
1474
+ headers: { "Content-Type": "application/json" },
1475
+ body: JSON.stringify({
1476
+ slug,
1477
+ title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
1478
+ site_identifier: this.siteIdentifier,
1479
+ content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
1480
+ })
1481
+ })
1482
+ );
1483
+ const results = await Promise.all(requests);
1484
+ if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to save");
1346
1485
  this.isSaving = false;
1347
1486
  this.resetAfterSave();
1348
1487
  this.updateUI();
@@ -1362,19 +1501,22 @@ var XtroedgeCMS = class _XtroedgeCMS {
1362
1501
  }
1363
1502
  this.isPublishing = true;
1364
1503
  this.updateUI();
1365
- const payload = {
1366
- slug: this.currentSlug,
1367
- title: this.currentTitle,
1368
- site_identifier: this.siteIdentifier,
1369
- published_content: { texts: this.pageTexts, images: this.pageImages }
1370
- };
1371
1504
  try {
1372
- const res = await fetch(`${this.config.apiBase}/web_page/publish`, {
1373
- method: "POST",
1374
- headers: { "Content-Type": "application/json" },
1375
- body: JSON.stringify(payload)
1376
- });
1377
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
1505
+ const sections = this.groupTextsBySection();
1506
+ const requests = Object.entries(sections).map(
1507
+ ([slug, texts]) => fetch(`${this.config.apiBase}/web_page/publish`, {
1508
+ method: "POST",
1509
+ headers: { "Content-Type": "application/json" },
1510
+ body: JSON.stringify({
1511
+ slug,
1512
+ title: slug === this.currentSlug ? this.currentTitle : slug.replace("/", ""),
1513
+ site_identifier: this.siteIdentifier,
1514
+ published_content: { texts, ...slug === this.currentSlug ? { images: this.pageImages } : {} }
1515
+ })
1516
+ })
1517
+ );
1518
+ const results = await Promise.all(requests);
1519
+ if (results.some((r) => !r.ok)) throw new Error("One or more sections failed to publish");
1378
1520
  this.isPublishing = false;
1379
1521
  this.resetAfterSave();
1380
1522
  this.updateUI();
@@ -1642,6 +1784,89 @@ var XtroedgeCMS = class _XtroedgeCMS {
1642
1784
  return `${months[d.getMonth()]} ${d.getDate()}, ${h12}:${m} ${ampm}`;
1643
1785
  }
1644
1786
  // ===============================================
1787
+ // LOGIN MODAL
1788
+ // ===============================================
1789
+ showLoginModal() {
1790
+ if (this.loginModalEl) return;
1791
+ const overlay = document.createElement("div");
1792
+ overlay.className = "lcms-login-overlay";
1793
+ overlay.id = "lcms-login-modal";
1794
+ overlay.innerHTML = `
1795
+ <div class="lcms-login-box">
1796
+ <div class="lcms-login-logo">
1797
+ <div class="lcms-login-logo-icon">
1798
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
1799
+ </div>
1800
+ <span class="lcms-login-logo-text">XtroEdge CMS</span>
1801
+ </div>
1802
+ <div class="lcms-login-title">Builder Login</div>
1803
+ <div class="lcms-login-sub">Sign in to access edit mode</div>
1804
+ <div class="lcms-login-field">
1805
+ <label class="lcms-login-label">Email or Username</label>
1806
+ <input class="lcms-login-input" id="lcms-login-email" type="text" placeholder="Enter your email or username" autocomplete="username" />
1807
+ </div>
1808
+ <div class="lcms-login-field">
1809
+ <label class="lcms-login-label">Password</label>
1810
+ <input class="lcms-login-input" id="lcms-login-password" type="password" placeholder="Enter your password" autocomplete="current-password" />
1811
+ </div>
1812
+ <button class="lcms-login-btn" id="lcms-login-btn">Sign In</button>
1813
+ <div class="lcms-login-error" id="lcms-login-error"></div>
1814
+ </div>
1815
+ `;
1816
+ document.body.appendChild(overlay);
1817
+ this.loginModalEl = overlay;
1818
+ const btn = overlay.querySelector("#lcms-login-btn");
1819
+ const emailInput = overlay.querySelector("#lcms-login-email");
1820
+ const passInput = overlay.querySelector("#lcms-login-password");
1821
+ const doLogin = () => this.attemptLogin(emailInput.value.trim(), passInput.value);
1822
+ btn.addEventListener("click", doLogin);
1823
+ passInput.addEventListener("keydown", (e) => {
1824
+ if (e.key === "Enter") doLogin();
1825
+ });
1826
+ setTimeout(() => emailInput.focus(), 50);
1827
+ }
1828
+ hideLoginModal() {
1829
+ this.loginModalEl?.remove();
1830
+ this.loginModalEl = null;
1831
+ }
1832
+ async attemptLogin(email, password) {
1833
+ const btn = document.getElementById("lcms-login-btn");
1834
+ const errorEl = document.getElementById("lcms-login-error");
1835
+ if (!email || !password) {
1836
+ errorEl.textContent = "Please enter your email/username and password.";
1837
+ errorEl.classList.add("visible");
1838
+ return;
1839
+ }
1840
+ btn.disabled = true;
1841
+ btn.textContent = "Signing in...";
1842
+ errorEl.classList.remove("visible");
1843
+ try {
1844
+ const loginUrl = this.config.loginUrl || `${this.config.apiBase}/auth/login`;
1845
+ const res = await fetch(loginUrl, {
1846
+ method: "POST",
1847
+ headers: { "Content-Type": "application/json" },
1848
+ body: JSON.stringify({ email, password })
1849
+ });
1850
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1851
+ const data = await res.json();
1852
+ const token = data?.token || data?.authToken || data?.auth_token || data?.data?.token;
1853
+ if (!token) throw new Error("No token in response");
1854
+ localStorage.setItem("builder_token", token);
1855
+ this.hideLoginModal();
1856
+ this.editMode = true;
1857
+ this.pendingEditMode = false;
1858
+ sessionStorage.setItem("builder_edit_mode", "true");
1859
+ this.applyEditMode(true);
1860
+ this.updateUI();
1861
+ this.loadPageContent("draft");
1862
+ } catch {
1863
+ btn.disabled = false;
1864
+ btn.textContent = "Sign In";
1865
+ errorEl.textContent = "Invalid credentials. Please try again.";
1866
+ errorEl.classList.add("visible");
1867
+ }
1868
+ }
1869
+ // ===============================================
1645
1870
  // SITE IDENTIFIER
1646
1871
  // ===============================================
1647
1872
  resolveSiteIdentifier() {