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()
|
conn.close()
|
||||||
|
|
||||||
return jsonify({'message': 'WhatsApp configuration updated'})
|
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;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-card {
|
.device-card {
|
||||||
@@ -1042,7 +1043,10 @@
|
|||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
transition: var(--transition-normal);
|
transition: var(--transition-normal);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="modern"] .device-card {
|
[data-theme="modern"] .device-card {
|
||||||
|
|||||||
@@ -1,18 +1,41 @@
|
|||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
NEXUS POS — Onboarding Wizard for New Tenants
|
NEXUS POS — Onboarding Wizard for New Tenants
|
||||||
Shows a step-by-step setup guide on first login.
|
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 () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
GUARD — skip if already completed
|
GUARD — skip if already completed locally (fast path)
|
||||||
------------------------------------------------------------------ */
|
------------------------------------------------------------------ */
|
||||||
|
|
||||||
if (localStorage.getItem('pos_onboarding_done') === 'true') return;
|
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
|
STATE
|
||||||
------------------------------------------------------------------ */
|
------------------------------------------------------------------ */
|
||||||
@@ -335,6 +358,13 @@
|
|||||||
|
|
||||||
function finish() {
|
function finish() {
|
||||||
localStorage.setItem('pos_onboarding_done', 'true');
|
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) {
|
if (overlay && overlay.parentNode) {
|
||||||
overlay.style.opacity = '0';
|
overlay.style.opacity = '0';
|
||||||
overlay.style.transition = 'opacity var(--duration-normal) var(--ease-in)';
|
overlay.style.transition = 'opacity var(--duration-normal) var(--ease-in)';
|
||||||
@@ -439,11 +469,15 @@
|
|||||||
renderCurrentStep();
|
renderCurrentStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait for DOM */
|
function initWizard() {
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
|
||||||
} else {
|
|
||||||
init();
|
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>
|
<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>
|
<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>
|
||||||
<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="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="brandCatalogLoading" class="loading"><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="brandCatalogContent"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
<script src="/pos/static/js/offline-banner.js" defer></script>
|
<script src="/pos/static/js/offline-banner.js" defer></script>
|
||||||
<script src="/pos/static/js/chat.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/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>
|
<script>
|
||||||
if('serviceWorker' in navigator){
|
if('serviceWorker' in navigator){
|
||||||
navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'}).then(function(reg){
|
navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'}).then(function(reg){
|
||||||
@@ -308,6 +308,6 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="/pos/static/js/pwa-install.js" defer></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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user