ngx-xtroedge-cms 1.3.2 → 1.3.4

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.mjs CHANGED
@@ -237,6 +237,58 @@ var CMS_STYLES = `
237
237
  }
238
238
  .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; }
239
239
 
240
+ /* LOGIN MODAL */
241
+ .lcms-login-overlay {
242
+ position: fixed; inset: 0; z-index: 10010;
243
+ display: flex; align-items: center; justify-content: center;
244
+ background: rgba(8, 8, 15, 0.75);
245
+ backdrop-filter: blur(16px) saturate(1.4);
246
+ -webkit-backdrop-filter: blur(16px) saturate(1.4);
247
+ font-family: system-ui, -apple-system, sans-serif;
248
+ }
249
+ .lcms-login-box {
250
+ background: #13151a; border: 1px solid rgba(255,255,255,0.1);
251
+ border-radius: 16px; padding: 36px 32px; width: 340px;
252
+ box-shadow: 0 24px 60px rgba(0,0,0,0.6);
253
+ }
254
+ .lcms-login-logo {
255
+ display: flex; align-items: center; justify-content: center;
256
+ gap: 8px; margin-bottom: 24px;
257
+ }
258
+ .lcms-login-logo-icon {
259
+ width: 32px; height: 32px; border-radius: 8px;
260
+ background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
261
+ display: flex; align-items: center; justify-content: center;
262
+ }
263
+ .lcms-login-logo-text { color: white; font-size: 15px; font-weight: 700; letter-spacing: 0.5px; }
264
+ .lcms-login-title { color: white; font-size: 18px; font-weight: 700; text-align: center; margin-bottom: 6px; }
265
+ .lcms-login-sub { color: rgba(255,255,255,0.4); font-size: 12px; text-align: center; margin-bottom: 24px; }
266
+ .lcms-login-field { margin-bottom: 14px; }
267
+ .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; }
268
+ .lcms-login-input {
269
+ width: 100%; box-sizing: border-box;
270
+ background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12);
271
+ border-radius: 8px; padding: 10px 12px;
272
+ color: white; font-size: 13px; font-family: inherit; outline: none;
273
+ transition: border-color 0.2s;
274
+ }
275
+ .lcms-login-input:focus { border-color: var(--lcms-primary, #00C853); }
276
+ .lcms-login-input::placeholder { color: rgba(255,255,255,0.25); }
277
+ .lcms-login-btn {
278
+ width: 100%; padding: 11px; margin-top: 6px; border: none; border-radius: 8px; cursor: pointer;
279
+ background: linear-gradient(135deg, var(--lcms-primary, #00C853), var(--lcms-primary-dark, #2E7D32));
280
+ color: white; font-size: 13px; font-weight: 700; font-family: inherit;
281
+ letter-spacing: 0.3px; transition: filter 0.2s;
282
+ }
283
+ .lcms-login-btn:hover { filter: brightness(1.1); }
284
+ .lcms-login-btn:disabled { opacity: 0.6; cursor: not-allowed; }
285
+ .lcms-login-error {
286
+ background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.3);
287
+ border-radius: 8px; padding: 9px 12px; color: #f87171;
288
+ font-size: 12px; text-align: center; margin-top: 12px; display: none;
289
+ }
290
+ .lcms-login-error.visible { display: block; }
291
+
240
292
  /* HIDDEN ELEMENTS */
241
293
  .lcms-hidden { display: none !important; }
242
294
  `;
@@ -286,6 +338,9 @@ var XtroedgeCMS = class _XtroedgeCMS {
286
338
  this.brandingEl = null;
287
339
  this.siteIdentifier = "";
288
340
  this.siteIdEl = null;
341
+ this.loginModalEl = null;
342
+ this.pendingEditMode = false;
343
+ // edit=true was requested but token missing
289
344
  // ===== State =====
290
345
  this.editMode = false;
291
346
  this.currentLang = "en";
@@ -475,12 +530,24 @@ var XtroedgeCMS = class _XtroedgeCMS {
475
530
  this.resetAll();
476
531
  }
477
532
  const editFromSession = sessionStorage.getItem("builder_edit_mode") === "true";
478
- if (editViaParam || editFromSession) {
533
+ const wantsEdit = editViaParam || editFromSession;
534
+ if (wantsEdit) {
535
+ const token = localStorage.getItem("builder_token");
536
+ if (!token) {
537
+ this.pendingEditMode = true;
538
+ this.currentLang = this.detectCurrentLanguage();
539
+ this.setLoading(true);
540
+ setTimeout(() => {
541
+ this.loadTranslationsAndInit(false);
542
+ this.showLoginModal();
543
+ }, 300);
544
+ return;
545
+ }
479
546
  this.editMode = true;
480
547
  }
481
548
  this.currentLang = this.detectCurrentLanguage();
482
549
  this.setLoading(true);
483
- const loadDraft = editViaParam || editFromSession;
550
+ const loadDraft = wantsEdit;
484
551
  setTimeout(() => {
485
552
  this.loadTranslationsAndInit(loadDraft);
486
553
  }, 300);
@@ -1691,6 +1758,89 @@ var XtroedgeCMS = class _XtroedgeCMS {
1691
1758
  return `${months[d.getMonth()]} ${d.getDate()}, ${h12}:${m} ${ampm}`;
1692
1759
  }
1693
1760
  // ===============================================
1761
+ // LOGIN MODAL
1762
+ // ===============================================
1763
+ showLoginModal() {
1764
+ if (this.loginModalEl) return;
1765
+ const overlay = document.createElement("div");
1766
+ overlay.className = "lcms-login-overlay";
1767
+ overlay.id = "lcms-login-modal";
1768
+ overlay.innerHTML = `
1769
+ <div class="lcms-login-box">
1770
+ <div class="lcms-login-logo">
1771
+ <div class="lcms-login-logo-icon">
1772
+ <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>
1773
+ </div>
1774
+ <span class="lcms-login-logo-text">XtroEdge CMS</span>
1775
+ </div>
1776
+ <div class="lcms-login-title">Builder Login</div>
1777
+ <div class="lcms-login-sub">Sign in to access edit mode</div>
1778
+ <div class="lcms-login-field">
1779
+ <label class="lcms-login-label">Username</label>
1780
+ <input class="lcms-login-input" id="lcms-login-email" type="text" placeholder="Enter your username" autocomplete="username" />
1781
+ </div>
1782
+ <div class="lcms-login-field">
1783
+ <label class="lcms-login-label">Password</label>
1784
+ <input class="lcms-login-input" id="lcms-login-password" type="password" placeholder="Enter your password" autocomplete="current-password" />
1785
+ </div>
1786
+ <button class="lcms-login-btn" id="lcms-login-btn">Sign In</button>
1787
+ <div class="lcms-login-error" id="lcms-login-error"></div>
1788
+ </div>
1789
+ `;
1790
+ document.body.appendChild(overlay);
1791
+ this.loginModalEl = overlay;
1792
+ const btn = overlay.querySelector("#lcms-login-btn");
1793
+ const emailInput = overlay.querySelector("#lcms-login-email");
1794
+ const passInput = overlay.querySelector("#lcms-login-password");
1795
+ const doLogin = () => this.attemptLogin(emailInput.value.trim(), passInput.value);
1796
+ btn.addEventListener("click", doLogin);
1797
+ passInput.addEventListener("keydown", (e) => {
1798
+ if (e.key === "Enter") doLogin();
1799
+ });
1800
+ setTimeout(() => emailInput.focus(), 50);
1801
+ }
1802
+ hideLoginModal() {
1803
+ this.loginModalEl?.remove();
1804
+ this.loginModalEl = null;
1805
+ }
1806
+ async attemptLogin(email, password) {
1807
+ const btn = document.getElementById("lcms-login-btn");
1808
+ const errorEl = document.getElementById("lcms-login-error");
1809
+ if (!email || !password) {
1810
+ errorEl.textContent = "Please enter your username and password.";
1811
+ errorEl.classList.add("visible");
1812
+ return;
1813
+ }
1814
+ btn.disabled = true;
1815
+ btn.textContent = "Signing in...";
1816
+ errorEl.classList.remove("visible");
1817
+ try {
1818
+ const loginUrl = this.config.loginUrl || `${this.config.apiBase}/auth/login`;
1819
+ const res = await fetch(loginUrl, {
1820
+ method: "POST",
1821
+ headers: { "Content-Type": "application/json" },
1822
+ body: JSON.stringify({ username: email, password })
1823
+ });
1824
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1825
+ const data = await res.json();
1826
+ const token = data?.token || data?.authToken || data?.auth_token || data?.data?.token;
1827
+ if (!token) throw new Error("No token in response");
1828
+ localStorage.setItem("builder_token", token);
1829
+ this.hideLoginModal();
1830
+ this.editMode = true;
1831
+ this.pendingEditMode = false;
1832
+ sessionStorage.setItem("builder_edit_mode", "true");
1833
+ this.applyEditMode(true);
1834
+ this.updateUI();
1835
+ this.loadPageContent("draft");
1836
+ } catch {
1837
+ btn.disabled = false;
1838
+ btn.textContent = "Sign In";
1839
+ errorEl.textContent = "Invalid credentials. Please try again.";
1840
+ errorEl.classList.add("visible");
1841
+ }
1842
+ }
1843
+ // ===============================================
1694
1844
  // SITE IDENTIFIER
1695
1845
  // ===============================================
1696
1846
  resolveSiteIdentifier() {