fix(config): prevent card text overflow; fix(onboarding): persist completion server-side
Cards/Grid: - Add min-width:0 to .device-grid to prevent grid overflow - Add max-width:100%, overflow:hidden, word-break:break-word to .device-card - Add min-width:0 and overflow-wrap to .device-card__body - Bump config.css cache-bust to v=2 Onboarding: - Add GET/POST /pos/api/config/onboarding-status endpoints in config_bp.py - onboarding.js now checks server first before showing wizard - On finish, POSTs completion to server (tenant_config table) - Falls back to localStorage for fast path and offline resilience - Bump onboarding.js cache-bust to v=2 in catalog.html
This commit is contained in:
@@ -577,3 +577,34 @@ def update_whatsapp_config():
|
||||
conn.close()
|
||||
|
||||
return jsonify({'message': 'WhatsApp configuration updated'})
|
||||
|
||||
|
||||
@config_bp.route('/onboarding-status', methods=['GET'])
|
||||
@require_auth('pos.view')
|
||||
def get_onboarding_status():
|
||||
"""Check if tenant onboarding wizard has been completed."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT value FROM tenant_config WHERE key = 'onboarding_completed'")
|
||||
row = cur.fetchone()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify({'completed': row[0] == 'true' if row else False})
|
||||
|
||||
|
||||
@config_bp.route('/onboarding-status', methods=['POST'])
|
||||
@require_auth('pos.view')
|
||||
def set_onboarding_status():
|
||||
"""Mark tenant onboarding wizard as completed."""
|
||||
data = request.get_json() or {}
|
||||
completed = 'true' if data.get('completed') else 'false'
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
INSERT INTO tenant_config (key, value) VALUES (%s, %s)
|
||||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value
|
||||
""", ('onboarding_completed', completed))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
conn.close()
|
||||
return jsonify({'completed': completed == 'true'})
|
||||
|
||||
@@ -1029,6 +1029,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-4);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
@@ -1042,7 +1043,10 @@
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: var(--transition-normal);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
[data-theme="modern"] .device-card {
|
||||
|
||||
@@ -1,18 +1,41 @@
|
||||
/* ==========================================================================
|
||||
NEXUS POS — Onboarding Wizard for New Tenants
|
||||
Shows a step-by-step setup guide on first login.
|
||||
Persists completion in localStorage('pos_onboarding_done').
|
||||
Persists completion in localStorage('pos_onboarding_done') and server.
|
||||
========================================================================== */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
GUARD — skip if already completed
|
||||
GUARD — skip if already completed locally (fast path)
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
if (localStorage.getItem('pos_onboarding_done') === 'true') return;
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
CHECK SERVER — if completed on server, cache locally and skip
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
function checkServerAndMaybeInit() {
|
||||
var token = '';
|
||||
try { token = localStorage.getItem('pos_token') || ''; } catch (e) {}
|
||||
fetch('/pos/api/config/onboarding-status', {
|
||||
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
|
||||
}).then(function (r) {
|
||||
if (!r.ok) return null;
|
||||
return r.json();
|
||||
}).then(function (data) {
|
||||
if (data && data.completed) {
|
||||
localStorage.setItem('pos_onboarding_done', 'true');
|
||||
return;
|
||||
}
|
||||
initWizard();
|
||||
}).catch(function () {
|
||||
initWizard();
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
STATE
|
||||
------------------------------------------------------------------ */
|
||||
@@ -335,6 +358,13 @@
|
||||
|
||||
function finish() {
|
||||
localStorage.setItem('pos_onboarding_done', 'true');
|
||||
var token = '';
|
||||
try { token = localStorage.getItem('pos_token') || ''; } catch (e) {}
|
||||
fetch('/pos/api/config/onboarding-status', {
|
||||
method: 'POST',
|
||||
headers: token ? { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' } : { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ completed: true })
|
||||
}).catch(function () {});
|
||||
if (overlay && overlay.parentNode) {
|
||||
overlay.style.opacity = '0';
|
||||
overlay.style.transition = 'opacity var(--duration-normal) var(--ease-in)';
|
||||
@@ -439,11 +469,15 @@
|
||||
renderCurrentStep();
|
||||
}
|
||||
|
||||
/* Wait for DOM */
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
function initWizard() {
|
||||
init();
|
||||
}
|
||||
|
||||
/* Wait for DOM, then check server before showing wizard */
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', checkServerAndMaybeInit);
|
||||
} else {
|
||||
checkServerAndMaybeInit();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -276,10 +276,10 @@
|
||||
<h2 style="margin:0;font-family:var(--font-heading);font-size:var(--text-h3);">Catalogo por Marca</h2>
|
||||
<button onclick="BrandCatalog.hide()" class="btn btn--sm" style="background:none;border:1px solid var(--color-border);color:var(--color-text-primary);padding:8px 16px;border-radius:var(--radius-md);cursor:pointer;">✕ Cerrar</button>
|
||||
</div>
|
||||
<div id="brandCatalogBreadcrumb" style="margin-bottom:var(--space-3);color:var(--color-text-muted);font-size:var(--text-body-sm);"></div>
|
||||
<div id="brandCatalogBreadcrumb" style="margin-bottom:var(--space-3);"></div>
|
||||
<div id="brandCatalogSearch" style="margin-bottom:var(--space-3);"></div>
|
||||
<div id="brandCatalogLoading" style="display:none;text-align:center;padding:var(--space-8);"><div class="spinner"></div></div>
|
||||
<div id="brandCatalogContent" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--space-3);"></div>
|
||||
<div id="brandCatalogLoading" class="loading"><div class="spinner"></div></div>
|
||||
<div id="brandCatalogContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||
<script src="/pos/static/js/chat.js" defer></script>
|
||||
<script src="/pos/static/js/sync-engine.js" defer></script>
|
||||
<script src="/pos/static/js/onboarding.js" defer></script>
|
||||
<script src="/pos/static/js/onboarding.js?v=2" defer></script>
|
||||
<script>
|
||||
if('serviceWorker' in navigator){
|
||||
navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'}).then(function(reg){
|
||||
@@ -308,6 +308,6 @@
|
||||
}
|
||||
</script>
|
||||
<script src="/pos/static/js/pwa-install.js" defer></script>
|
||||
<script src="/pos/static/js/brand-catalog.js?v=4" defer></script>
|
||||
<script src="/pos/static/js/brand-catalog.js?v=9" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user