feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+15 -15
View File
@@ -22,7 +22,7 @@
body { background: var(--bg); color: var(--text); font-family: var(--font); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 16px; }
.widget {
background: var(--surface); border-radius: 20px; padding: 28px 24px;
background: var(--surface); border-radius: var(--radius-xl); padding: 28px 24px;
max-width: 380px; width: 100%; box-shadow: 0 4px 32px rgba(0,0,0,0.12);
}
@@ -32,9 +32,9 @@
.widget-subtitle { font-size: 14px; color: var(--text2); margin-top: 4px; }
/* Zoomer profile */
.zoomer-profile { display: flex; align-items: center; gap: 14px; padding: 14px; background: var(--bg); border-radius: 14px; margin-bottom: 20px; }
.zoomer-profile { display: flex; align-items: center; gap: 14px; padding: 14px; background: var(--bg); border-radius: var(--radius-lg); margin-bottom: 20px; }
.zoomer-avatar {
width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, #34c759, #30b952);
width: 48px; height: 48px; border-radius: var(--radius-full); background: linear-gradient(135deg, #34c759, #30b952);
display: flex; align-items: center; justify-content: center; font-weight: 700; color: white; font-size: 18px; flex-shrink: 0;
}
.zoomer-name { font-size: 16px; font-weight: 600; }
@@ -53,7 +53,7 @@
/* Comment */
.comment-label { font-size: 13px; font-weight: 500; margin-bottom: 6px; }
.comment-input {
width: 100%; background: var(--bg); border: 1.5px solid var(--border); border-radius: 12px;
width: 100%; background: var(--bg); border: 1.5px solid var(--border); border-radius: var(--radius-md);
padding: 12px 14px; font-size: 14px; color: var(--text); font-family: var(--font);
resize: none; transition: border-color 0.15s;
}
@@ -63,7 +63,7 @@
/* Submit */
.submit-btn {
width: 100%; margin-top: 16px; padding: 14px; background: var(--accent);
color: white; border: none; border-radius: 14px; font-size: 16px; font-weight: 600;
color: white; border: none; border-radius: var(--radius-lg); font-size: 16px; font-weight: 600;
cursor: pointer; font-family: var(--font); transition: opacity 0.15s;
}
.submit-btn:hover:not(:disabled) { opacity: 0.88; }
@@ -135,11 +135,11 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
<div id="rating-form">
<div class="stars-label">Hur var upplevelsen?</div>
<div class="stars" id="stars">
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
<span class="star" data-val="1"></span>
<span class="star" data-val="2"></span>
<span class="star" data-val="3"></span>
<span class="star" data-val="4"></span>
<span class="star" data-val="5"></span>
</div>
<div class="star-labels">
<span>Dålig</span>
@@ -158,7 +158,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
</div>
<div class="success" id="success">
<div class="success-icon"></div>
<div class="success-icon"></div>
<div class="success-title">Tack för ditt betyg!</div>
<div class="success-sub">Ditt omdöme hjälper andra kunder att välja rätt Zoomer.</div>
</div>
@@ -179,7 +179,7 @@ const missionId = params.get('missionId') || 'm001';
let selectedRating = 0;
// ── Stars interaction ─────────────────────────────────────────────────────
// Stars interaction
const stars = document.querySelectorAll('.star');
stars.forEach(star => {
star.addEventListener('mouseenter', () => highlightStars(parseInt(star.dataset.val)));
@@ -198,7 +198,7 @@ function highlightStars(n) {
});
}
// ── Load zoomer info ──────────────────────────────────────────────────────
// Load zoomer info
async function loadZoomer() {
try {
const r = await fetch(`/api/quixzoom/ratings/zoomer/${zoomerId}`);
@@ -222,14 +222,14 @@ function renderReviews(reviews) {
}
list.innerHTML = reviews.map(r => `
<div class="review">
<div class="review-stars">${''.repeat(r.rating)}${''.repeat(5 - r.rating)}</div>
<div class="review-stars">${''.repeat(r.rating)}${''.repeat(5 - r.rating)}</div>
${r.comment ? `<div class="review-comment">"${r.comment}"</div>` : ''}
<div class="review-meta">${new Date(r.createdAt).toLocaleDateString('sv-SE')}</div>
</div>
`).join('');
}
// ── Submit rating ─────────────────────────────────────────────────────────
// Submit rating
async function submitRating() {
const comment = document.getElementById('comment').value;
const btn = document.getElementById('submit-btn');