6989a98d75
- 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
271 lines
11 KiB
HTML
271 lines
11 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="sv">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<title>quiXzoom — Betygsätt din Zoomer</title>
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
:root {
|
|
--bg: #f2f2f7;
|
|
--surface: #ffffff;
|
|
--border: rgba(0,0,0,0.1);
|
|
--accent: #ff3b30;
|
|
--accent2: #34c759;
|
|
--text: #1c1c1e;
|
|
--text2: #6e6e73;
|
|
--font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue', sans-serif;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root { --bg:#0a0a0f; --surface:#1c1c1e; --border:rgba(255,255,255,0.08); --text:#f5f5f7; --text2:#8e8e93; }
|
|
}
|
|
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: var(--radius-xl); padding: 28px 24px;
|
|
max-width: 380px; width: 100%; box-shadow: 0 4px 32px rgba(0,0,0,0.12);
|
|
}
|
|
|
|
.widget-header { text-align: center; margin-bottom: 24px; }
|
|
.widget-title { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }
|
|
.widget-title span { color: var(--accent); }
|
|
.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: var(--radius-lg); margin-bottom: 20px; }
|
|
.zoomer-avatar {
|
|
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; }
|
|
.zoomer-meta { font-size: 12px; color: var(--text2); margin-top: 2px; }
|
|
.zoomer-score { margin-left: auto; text-align: center; }
|
|
.zoomer-score-val { font-size: 24px; font-weight: 700; color: #ff9f0a; }
|
|
.zoomer-score-label { font-size: 10px; color: var(--text2); text-transform: uppercase; }
|
|
|
|
/* Star rating */
|
|
.stars-label { font-size: 13px; color: var(--text2); margin-bottom: 8px; text-align: center; }
|
|
.stars { display: flex; justify-content: center; gap: 8px; margin-bottom: 20px; }
|
|
.star { font-size: 36px; cursor: pointer; transition: transform 0.1s; user-select: none; filter: grayscale(1) brightness(0.5); }
|
|
.star:hover, .star.active { filter: none; transform: scale(1.15); }
|
|
.star-labels { display: flex; justify-content: space-between; font-size: 11px; color: var(--text2); margin-bottom: 20px; padding: 0 4px; }
|
|
|
|
/* 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: var(--radius-md);
|
|
padding: 12px 14px; font-size: 14px; color: var(--text); font-family: var(--font);
|
|
resize: none; transition: border-color 0.15s;
|
|
}
|
|
.comment-input:focus { outline: none; border-color: var(--accent); }
|
|
.comment-input::placeholder { color: var(--text2); }
|
|
|
|
/* Submit */
|
|
.submit-btn {
|
|
width: 100%; margin-top: 16px; padding: 14px; background: var(--accent);
|
|
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; }
|
|
.submit-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
|
|
/* Recent reviews */
|
|
.reviews-section { margin-top: 24px; border-top: 1px solid var(--border); padding-top: 20px; }
|
|
.reviews-title { font-size: 15px; font-weight: 600; margin-bottom: 14px; }
|
|
.review { padding: 12px 0; border-bottom: 1px solid var(--border); }
|
|
.review:last-child { border-bottom: none; }
|
|
.review-stars { font-size: 13px; margin-bottom: 4px; }
|
|
.review-comment { font-size: 13px; color: var(--text); margin-bottom: 4px; }
|
|
.review-meta { font-size: 11px; color: var(--text2); }
|
|
|
|
/* Success state */
|
|
.success { text-align: center; padding: 20px 0; display: none; }
|
|
.success-icon { font-size: 48px; margin-bottom: 12px; }
|
|
.success-title { font-size: 20px; font-weight: 700; margin-bottom: 6px; }
|
|
.success-sub { font-size: 14px; color: var(--text2); }
|
|
@media (max-width: 640px) {
|
|
.hero h1 { font-size: 2rem !important; }
|
|
.hero p { font-size: 1rem !important; }
|
|
.card { padding: 1.5rem !important; }
|
|
.grid { grid-template-columns: 1fr !important; }
|
|
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
|
|
}
|
|
</style>
|
|
|
|
<style id="wcag-contrast-enforcement">
|
|
/* AAMOS GLOBAL CONTRAST ENFORCEMENT — WCAG AA (4.5:1 minimum) */
|
|
/* Regel: Ingen text under 70% opacity. Aldrig. */
|
|
body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
/* Fix common low-contrast patterns */
|
|
[style*="rgba(60,60,67,0.4)"] { color: rgba(60,60,67,0.65) !important; }
|
|
[style*="rgba(60,60,67,0.3)"] { color: rgba(60,60,67,0.65) !important; }
|
|
[style*="rgba(255,255,255,0.4)"] { color: rgba(255,255,255,0.72) !important; }
|
|
[style*="rgba(255,255,255,0.3)"] { color: rgba(255,255,255,0.72) !important; }
|
|
[style*="opacity: 0.4"] { opacity: 0.72 !important; }
|
|
[style*="opacity: 0.3"] { opacity: 0.72 !important; }
|
|
@media (max-width: 640px) {
|
|
.hero h1 { font-size: 2rem !important; }
|
|
.hero p { font-size: 1rem !important; }
|
|
.card { padding: 1.5rem !important; }
|
|
.grid { grid-template-columns: 1fr !important; }
|
|
.btn { padding: 0.75rem 1.5rem !important; font-size: 1rem !important; }
|
|
}
|
|
</style>
|
|
<script src="i18n.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="widget">
|
|
<div class="widget-header">
|
|
<div class="widget-title">qui<span>X</span>zoom</div>
|
|
<div class="widget-subtitle">Betygsätt din Zoomer</div>
|
|
</div>
|
|
|
|
<div class="zoomer-profile">
|
|
<div class="zoomer-avatar" id="zoomer-initials">AK</div>
|
|
<div>
|
|
<div class="zoomer-name" id="zoomer-name">Anan K.</div>
|
|
<div class="zoomer-meta" id="zoomer-meta">128 uppdrag · Phuket</div>
|
|
</div>
|
|
<div class="zoomer-score">
|
|
<div class="zoomer-score-val" id="zoomer-rating-val">—</div>
|
|
<div class="zoomer-score-label">Betyg</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
</div>
|
|
<div class="star-labels">
|
|
<span>Dålig</span>
|
|
<span>Okej</span>
|
|
<span>Bra</span>
|
|
<span>Mycket bra</span>
|
|
<span>Utmärkt</span>
|
|
</div>
|
|
|
|
<div class="comment-label">Kommentar (valfritt)</div>
|
|
<textarea class="comment-input" id="comment" rows="3" placeholder="Berätta om din upplevelse med Zoomern..."></textarea>
|
|
|
|
<button class="submit-btn" id="submit-btn" onclick="submitRating()" disabled>
|
|
Skicka betyg
|
|
</button>
|
|
</div>
|
|
|
|
<div class="success" id="success">
|
|
<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>
|
|
|
|
<div class="reviews-section" id="reviews-section">
|
|
<div class="reviews-title">Senaste omdömen</div>
|
|
<div id="reviews-list">
|
|
<div style="color:var(--text2);font-size:13px;text-align:center;padding:20px 0;">Laddar omdömen…</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Read query params
|
|
const params = new URLSearchParams(location.search);
|
|
const zoomerId = params.get('zoomerId') || 'z001';
|
|
const missionId = params.get('missionId') || 'm001';
|
|
|
|
let selectedRating = 0;
|
|
|
|
// Stars interaction
|
|
const stars = document.querySelectorAll('.star');
|
|
stars.forEach(star => {
|
|
star.addEventListener('mouseenter', () => highlightStars(parseInt(star.dataset.val)));
|
|
star.addEventListener('mouseleave', () => highlightStars(selectedRating));
|
|
star.addEventListener('click', () => {
|
|
selectedRating = parseInt(star.dataset.val);
|
|
highlightStars(selectedRating);
|
|
document.getElementById('submit-btn').disabled = false;
|
|
});
|
|
});
|
|
|
|
function highlightStars(n) {
|
|
stars.forEach(s => {
|
|
const val = parseInt(s.dataset.val);
|
|
s.classList.toggle('active', val <= n);
|
|
});
|
|
}
|
|
|
|
// Load zoomer info
|
|
async function loadZoomer() {
|
|
try {
|
|
const r = await fetch(`/api/quixzoom/ratings/zoomer/${zoomerId}`);
|
|
const d = await r.json();
|
|
if (d.ok) {
|
|
document.getElementById('zoomer-rating-val').textContent = d.public_score ?? (d.total_ratings < 3 ? '—' : d.avg_rating);
|
|
if (d.recent && d.recent.length > 0) {
|
|
renderReviews(d.recent);
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.log('Could not load zoomer:', e);
|
|
}
|
|
}
|
|
|
|
function renderReviews(reviews) {
|
|
const list = document.getElementById('reviews-list');
|
|
if (!reviews.length) {
|
|
list.innerHTML = '<div style="color:var(--text2);font-size:13px;text-align:center;padding:20px 0;">Inga omdömen ännu</div>';
|
|
return;
|
|
}
|
|
list.innerHTML = reviews.map(r => `
|
|
<div class="review">
|
|
<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
|
|
async function submitRating() {
|
|
const comment = document.getElementById('comment').value;
|
|
const btn = document.getElementById('submit-btn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Skickar…';
|
|
|
|
try {
|
|
const r = await fetch('https://api.quixzoom.com/api/qz/ratings', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ missionId, zoomerId, rating: selectedRating, comment }),
|
|
});
|
|
const d = await r.json();
|
|
if (d.ok) {
|
|
document.getElementById('rating-form').style.display = 'none';
|
|
document.getElementById('success').style.display = 'block';
|
|
} else {
|
|
btn.disabled = false;
|
|
btn.textContent = d.error || 'Fel — försök igen';
|
|
}
|
|
} catch(e) {
|
|
btn.disabled = false;
|
|
btn.textContent = 'Nätverksfel — försök igen';
|
|
}
|
|
}
|
|
|
|
// Set zoomer display
|
|
const ZOOMER_NAMES = { z001:'Anan K.', z002:'Pim S.', z003:'Chai W.', z004:'Nong T.', z005:'Fah R.', z006:'Lek P.', z007:'Mook J.' };
|
|
const name = ZOOMER_NAMES[zoomerId] || zoomerId;
|
|
const initials = name.split(' ').map(p => p[0]).join('');
|
|
document.getElementById('zoomer-name').textContent = name;
|
|
document.getElementById('zoomer-initials').textContent = initials;
|
|
document.getElementById('zoomer-meta').textContent = `Phuket, Thailand`;
|
|
|
|
loadZoomer();
|
|
</script>
|
|
</body>
|
|
</html>
|