2026-07-05 06:41:32 +00:00
<!DOCTYPE html>
< html lang = "en" data-theme = "dark" >
< head >
< meta charset = "UTF-8" >
< link rel = "canonical" href = "https://www.quixzoom.com/zoomer-earnings.html" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, viewport-fit=cover" >
< meta name = "apple-mobile-web-app-capable" content = "yes" >
< meta name = "apple-mobile-web-app-status-bar-style" content = "black-translucent" >
< title > Earnings — quiXzoom< / title >
< meta name = "description" content = "Learn how much you can earn as a quiXzoom Zoomer. Transparent payout structure, instant payments, and bonus opportunities." >
< link rel = "stylesheet" >
< style >
* , * :: before , * :: after { box-sizing : border-box ; margin : 0 ; padding : 0 ; }
body {
font-family : var ( - - font - text ) ;
background : var ( - - grouped - background - primary ) ;
color : var ( - - label - primary ) ;
min-height : 100 dvh ;
padding-top : env ( safe - area - inset - top , 44 px ) ;
padding-bottom : calc ( env ( safe - area - inset - bottom , 34 px ) + 83 px ) ;
-webkit- font-smoothing : antialiased ;
}
/* Nav */
. nav-bar {
position : sticky ; top : 0 ; z-index : 200 ;
display : flex ; align-items : center ; justify-content : center ;
height : 44 px ; padding : 0 16 px ;
background : var ( - - navbar - bg ) ;
backdrop-filter : saturate ( 180 % ) blur ( 20 px ) ;
-webkit- backdrop-filter : saturate ( 180 % ) blur ( 20 px ) ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. nav-title { font-size : 17 px ; font-weight : 600 ; letter-spacing : -0.41 px ; }
/* Hero earnings card */
. earnings-hero {
background : linear-gradient ( 160 deg , #1a3a5c 0 % , #0d2137 50 % , #0a1929 100 % ) ;
padding : 28 px 24 px 32 px ;
text-align : center ;
position : relative ; overflow : hidden ;
}
. earnings-hero :: before {
content : '' ;
position : absolute ; top : -40 % ; left : -20 % ; right : -20 % ; bottom : -20 % ;
background : radial-gradient ( circle at 50 % 40 % , rgba ( 0 , 122 , 255 , 0.25 ) 0 % , transparent 70 % ) ;
pointer-events : none ;
}
. earnings-label {
font-size : 13 px ; font-weight : 500 ; color : rgba ( 255 , 255 , 255 , 0.75 ) ;
text-transform : uppercase ; letter-spacing : 1 px ; margin-bottom : 8 px ;
}
. earnings-amount {
font-family : var ( - - font - display ) ;
font-size : 52 px ; font-weight : 800 ;
color : #fff ; letter-spacing : -2 px ;
line-height : 1 ;
}
. earnings-currency { font-size : 24 px ; font-weight : 500 ; letter-spacing : -0.5 px ; color : rgba ( 60 , 60 , 67 , 0.7 ) ; }
. earnings-sub {
font-size : 14 px ; color : rgba ( 60 , 60 , 67 , 0.5 ) ;
margin-top : 8 px ;
}
/* Stats row */
. stats-row {
display : grid ; grid-template-columns : 1 fr 1 fr 1 fr ;
background : var ( - - background - secondary ) ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. stat-cell {
padding : 16 px 8 px ; text-align : center ;
border-right : 0.5 px solid var ( - - separator ) ;
}
. stat-cell : last-child { border-right : none ; }
. stat-val { font-size : 22 px ; font-weight : 700 ; font-family : var ( - - font - display ) ; }
. stat-label { font-size : 11 px ; color : var ( - - label - secondary ) ; margin-top : 2 px ; }
. green { color : var ( - - ios - green ) ; }
. blue { color : var ( - - ios - blue ) ; }
. orange { color : var ( - - ios - orange ) ; }
/* Section */
. section { margin : 20 px 16 px 0 ; }
. section-header {
font-size : 20 px ; font-weight : 700 ; letter-spacing : -0.5 px ;
margin-bottom : 10 px ;
}
. section-card {
background : var ( - - background - secondary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ; overflow : hidden ;
2026-07-05 06:41:32 +00:00
border : 0.5 px solid var ( - - separator ) ;
}
/* Payout row */
. payout-row {
display : flex ; align-items : center ; gap : 12 px ;
padding : 13 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. payout-row : last-child { border-bottom : none ; }
. payout-icon {
2026-07-07 07:11:50 +00:00
width : 38 px ; height : 38 px ; border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
display : flex ; align-items : center ; justify-content : center ;
font-size : 18 px ; flex-shrink : 0 ;
}
. payout-body { flex : 1 ; min-width : 0 ; }
. payout-title { font-size : 15 px ; font-weight : 500 ; white-space : nowrap ; overflow : hidden ; text-overflow : ellipsis ; }
. payout-date { font-size : 12 px ; color : var ( - - label - secondary ) ; margin-top : 2 px ; }
. payout-amount { font-size : 17 px ; font-weight : 700 ; font-family : var ( - - font - display ) ; }
/* Connect card */
. connect-card {
background : var ( - - background - secondary ) ;
2026-07-07 07:11:50 +00:00
border-radius : var ( - - radius - lg ) ;
2026-07-05 06:41:32 +00:00
border : 0.5 px solid var ( - - separator ) ;
overflow : hidden ;
}
. connect-top {
padding : 16 px ;
display : flex ; gap : 14 px ; align-items : center ;
}
. connect-icon {
2026-07-07 07:11:50 +00:00
width : 48 px ; height : 48 px ; border-radius : var ( - - radius - md ) ;
2026-07-05 06:41:32 +00:00
background : rgba ( 0 , 122 , 255 , 0.15 ) ;
display : flex ; align-items : center ; justify-content : center ;
font-size : 24 px ; flex-shrink : 0 ;
}
. connect-info { flex : 1 ; }
. connect-title { font-size : 17 px ; font-weight : 600 ; margin-bottom : 3 px ; }
. connect-desc { font-size : 13 px ; color : var ( - - label - secondary ) ; }
. connect-btn {
display : block ; width : 100 % ;
background : var ( - - ios - blue ) ; color : #fff ;
border : none ; border-radius : 0 ; padding : 15 px ;
font-family : var ( - - font - text ) ; font-size : 16 px ; font-weight : 700 ;
cursor : pointer ; transition : opacity 0.15 s ;
letter-spacing : -0.2 px ;
border-top : 0.5 px solid var ( - - separator ) ;
}
. connect-btn : active { opacity : 0.8 ; }
. connect-status {
padding : 10 px 16 px ;
font-size : 13 px ; color : var ( - - label - secondary ) ;
border-top : 0.5 px solid var ( - - separator ) ;
display : flex ; align-items : center ; gap : 6 px ;
}
. status-dot {
2026-07-07 07:11:50 +00:00
width : 8 px ; height : 8 px ; border-radius : var ( - - radius - full ) ;
2026-07-05 06:41:32 +00:00
}
. status-dot . active { background : var ( - - ios - green ) ; }
. status-dot . pending { background : var ( - - ios - orange ) ; }
. status-dot . inactive { background : var ( - - ios - gray3 ) ; }
/* Payout schedule */
. schedule-row {
display : flex ; align-items : center ; justify-content : space-between ;
padding : 13 px 16 px ;
border-bottom : 0.5 px solid var ( - - separator ) ;
}
. schedule-row : last-child { border-bottom : none ; }
. schedule-label { font-size : 15 px ; }
. schedule-value { font-size : 15 px ; font-weight : 600 ; color : var ( - - label - secondary ) ; }
/* Loader */
. loader { display : flex ; justify-content : center ; padding : 40 px ; }
@ keyframes spin { to { transform : rotate ( 360 deg ) ; } }
2026-07-07 07:11:50 +00:00
. spinner { width : 28 px ; height : 28 px ; border-radius : var ( - - radius - full ) ; border : 3 px solid var ( - - fill - secondary ) ; border-top-color : var ( - - ios - blue ) ; animation : spin 0.8 s linear infinite ; }
2026-07-05 06:41:32 +00:00
/* Tab Bar */
. tab-bar {
position : fixed ; bottom : 0 ; left : 0 ; right : 0 ; z-index : 300 ;
height : calc ( 49 px + env ( safe - area - inset - bottom , 34 px ) ) ;
background : var ( - - tabbar - bg ) ;
backdrop-filter : saturate ( 180 % ) blur ( 20 px ) ;
-webkit- backdrop-filter : saturate ( 180 % ) blur ( 20 px ) ;
border-top : 0.5 px solid var ( - - separator ) ;
display : flex ; align-items : flex-start ; padding-top : 8 px ;
}
. tab-item {
flex : 1 ; display : flex ; flex-direction : column ; align-items : center ;
gap : 3 px ; cursor : pointer ; text-decoration : none ; padding : 0 4 px ;
}
. tab-icon { font-size : 24 px ; line-height : 1 ; }
. tab-label { font-size : 10 px ; font-weight : 500 ; color : var ( - - label - tertiary ) ; }
. tab-item . active . tab-label { color : var ( - - ios - blue ) ; }
/* Toast */
2026-07-07 07:11:50 +00:00
. toast { position : fixed ; bottom : calc ( env ( safe - area - inset - bottom , 34 px ) +100 px ) ; left : 50 % ; transform : translateX ( -50 % ) translateY ( 20 px ) ; background : var ( - - background - secondary ) ; border : 0.5 px solid var ( - - separator ) ; border-radius : var ( - - radius - xl ) ; padding : 10 px 18 px ; font-size : 14 px ; font-weight : 500 ; box-shadow : 0 8 px 32 px rgba ( 0 , 0 , 0 , 0.3 ) ; opacity : 0 ; transition : all 0.3 s ; z-index : 9999 ; white-space : nowrap ; }
2026-07-05 06:41:32 +00:00
. toast . show { opacity : 1 ; transform : translateX ( -50 % ) translateY ( 0 ) ; }
@ media ( max-width : 640px ) {
. hero h1 { font-size : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !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 : 2 rem !important ; }
. hero p { font-size : 1 rem !important ; }
. card { padding : 1.5 rem !important ; }
. grid { grid-template-columns : 1 fr !important ; }
. btn { padding : 0.75 rem 1.5 rem !important ; font-size : 1 rem !important ; }
}
< / style >
< script src = "i18n.js" > < / script >
< meta property = "og:type" content = "website" >
< meta property = "og:title" content = "Earnings — quiXzoom" >
< meta property = "og:description" content = "quiXzoom — Earn per Mission. No Platform Fee." >
< meta property = "og:url" content = "https://www.quixzoom.com/zoomer-earnings.html" >
< meta property = "og:image" content = "https://www.quixzoom.com/images/zoomer-earnings.jpg" >
< meta property = "og:image:width" content = "1200" >
< meta property = "og:image:height" content = "630" >
< meta property = "og:image:alt" content = "quiXzoom Zoomer earnings and QX Credits payout dashboard" >
< meta name = "twitter:card" content = "summary_large_image" >
< meta name = "twitter:image" content = "https://www.quixzoom.com/images/zoomer-earnings.jpg" >
< meta name = "twitter:title" content = "Earnings — quiXzoom" >
< / head >
< body >
< h1 style = "position:absolute;left:-9999px;" > Zoomer Earnings - quiXzoom< / h1 >
<!-- Nav -->
< div class = "nav-bar" >
< span class = "nav-title" > Earnings< / span >
< / div >
<!-- Hero -->
< div class = "earnings-hero" >
< div class = "earnings-label" > Total earned< / div >
< div class = "earnings-amount" id = "total-amount" > < span class = "earnings-currency" > SEK < / span > —< / div >
< div class = "earnings-sub" id = "earnings-sub" > Loading data...< / div >
< / div >
<!-- Stats -->
< div class = "stats-row" >
< div class = "stat-cell" >
< div class = "stat-val green" id = "stat-uppdrag" > —< / div >
< div class = "stat-label" > Missions< / div >
< / div >
< div class = "stat-cell" >
< div class = "stat-val blue" id = "stat-snittersattning" > —< / div >
< div class = "stat-label" > Avg/mission< / div >
< / div >
< div class = "stat-cell" >
< div class = "stat-val orange" id = "stat-pending" > —< / div >
< div class = "stat-label" > Pending< / div >
< / div >
< / div >
<!-- Payouts -->
< div class = "section" >
< div class = "section-header" > Recent payouts< / div >
< div class = "section-card" id = "payouts-card" >
< div class = "loader" > < div class = "spinner" > < / div > < / div >
< / div >
< / div >
<!-- Stripe Connect -->
< div class = "section" style = "margin-top:20px" >
< div class = "section-header" > Payout account< / div >
< div class = "connect-card" >
< div class = "connect-top" >
2026-07-07 07:11:50 +00:00
< div class = "connect-icon" > < / div >
2026-07-05 06:41:32 +00:00
< div class = "connect-info" >
< div class = "connect-title" > Stripe Connect< / div >
< div class = "connect-desc" > Verify your account to receive payouts directly to your bank< / div >
< / div >
< / div >
< div class = "connect-status" id = "connect-status" >
< div class = "status-dot pending" > < / div >
Loading account status...
< / div >
< button class = "connect-btn" id = "connect-btn" onclick = "openConnectOnboarding()" >
2026-07-07 07:11:50 +00:00
Configure payout account →
2026-07-05 06:41:32 +00:00
< / button >
< / div >
< / div >
<!-- Payout schedule -->
< div class = "section" style = "margin-top:20px;margin-bottom:20px" >
< div class = "section-header" > Payout schedule< / div >
< div class = "section-card" >
< div class = "schedule-row" >
2026-07-07 07:11:50 +00:00
< span class = "schedule-label" > Frekvens< / span >
2026-07-05 06:41:32 +00:00
< span class = "schedule-value" > Every Monday< / span >
< / div >
< div class = "schedule-row" >
< span class = "schedule-label" > ⏱ Processing time< / span >
< span class = "schedule-value" > 1– 3 bankdagar< / span >
< / div >
< div class = "schedule-row" >
2026-07-07 07:11:50 +00:00
< span class = "schedule-label" > Method< / span >
2026-07-05 06:41:32 +00:00
< span class = "schedule-value" > Stripe Connect< / span >
< / div >
< div class = "schedule-row" >
2026-07-07 07:11:50 +00:00
< span class = "schedule-label" > Currency< / span >
2026-07-05 06:41:32 +00:00
< span class = "schedule-value" > SEK< / span >
< / div >
< div class = "schedule-row" >
2026-07-07 07:11:50 +00:00
< span class = "schedule-label" > < svg width = "16" height = "16" viewBox = "0 0 16 16" fill = "none" > < rect x = "3" y = "2" width = "10" height = "12" rx = "1" stroke = "#666" stroke-width = "1.5" / > < path d = "M6 5H10M6 8H10M6 11H8" stroke = "#666" stroke-width = "1.5" stroke-linecap = "round" / > < / svg > Min. payout< / span >
2026-07-05 06:41:32 +00:00
< span class = "schedule-value" > 500 kr< / span >
< / div >
< / div >
< / div >
<!-- Toast -->
< div class = "toast" id = "toast" > < / div >
<!-- Tab Bar -->
< nav class = "tab-bar" >
< a class = "tab-item" href = "zoomer-app.html" >
2026-07-07 07:11:50 +00:00
< span class = "tab-icon" > < / span >
2026-07-05 06:41:32 +00:00
< span class = "tab-label" > Missions< / span >
< / a >
< a class = "tab-item active" href = "zoomer-earnings.html" >
2026-07-07 07:11:50 +00:00
< span class = "tab-icon" > < / span >
2026-07-05 06:41:32 +00:00
< span class = "tab-label" > Earnings< / span >
< / a >
< a class = "tab-item" href = "zoomer-profile.html" >
2026-07-07 07:11:50 +00:00
< span class = "tab-icon" > < / span >
2026-07-05 06:41:32 +00:00
< span class = "tab-label" > Profile< / span >
< / a >
< / nav >
< script >
const API = 'https://api.quixzoom.com/api/qz/missions' ;
function getZoomerId ( ) {
let id = localStorage . getItem ( 'qz_zoomer_id' ) ;
if ( ! id ) { id = 'zoomer-' + Math . random ( ) . toString ( 36 ) . slice ( 2 , 10 ) ; localStorage . setItem ( 'qz_zoomer_id' , id ) ; }
return id ;
}
function toast ( msg , dur = 3000 ) {
const el = document . getElementById ( 'toast' ) ;
el . textContent = msg ;
el . classList . add ( 'show' ) ;
setTimeout ( ( ) => el . classList . remove ( 'show' ) , dur ) ;
}
function formatSEK ( n ) { return new Intl . NumberFormat ( 'sv-SE' ) . format ( n ) ; }
function formatDate ( s ) {
return new Date ( s ) . toLocaleDateString ( 'sv-SE' , { day : 'numeric' , month : 'short' , year : 'numeric' } ) ;
}
function missionTypeEmoji ( type ) {
2026-07-07 07:11:50 +00:00
const m = { facade _scan : '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="2" width="10" height="12" rx="1" stroke="#666" stroke-width="1.5"/><path d="M6 5H7M6 8H7M6 11H7M9 5H10M9 8H10M9 11H10" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg>' , roof _scan : '' , road _inspection : '' , interior _scan : '' , vegetation _survey : '' , inspection : '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="7" cy="7" r="5" stroke="#666" stroke-width="2"/><path d="M11 11L14 14" stroke="#666" stroke-width="2" stroke-linecap="round"/></svg>' } ;
return m [ type ] || '' ;
2026-07-05 06:41:32 +00:00
}
function missionTypeBg ( type ) {
const m = { facade _scan : 'rgba(0,122,255,0.15)' , roof _scan : 'rgba(255,149,0,0.15)' , road _inspection : 'rgba(142,142,147,0.15)' , interior _scan : 'rgba(52,199,89,0.15)' , vegetation _survey : 'rgba(48,209,88,0.15)' } ;
return m [ type ] || 'rgba(175,82,222,0.15)' ;
}
async function loadEarnings ( ) {
const zoomerId = getZoomerId ( ) ;
try {
const r = await fetch ( ` ${ API } /missions ` ) ;
const data = await r . json ( ) ;
const all = data . missions || [ ] ;
// My missions with payment
const mine = all . filter ( m => m . zoomer _id === zoomerId ) ;
const approved = mine . filter ( m => m . status === 'approved' ) ;
const pending = mine . filter ( m => [ 'assigned' , 'submitted' ] . includes ( m . status ) ) ;
const totalEarned = approved . reduce ( ( s , m ) => s + m . reward _sek , 0 ) ;
const totalPending = pending . reduce ( ( s , m ) => s + m . reward _sek , 0 ) ;
const avg = approved . length > 0 ? Math . round ( totalEarned / approved . length ) : 0 ;
// Update hero
document . getElementById ( 'total-amount' ) . innerHTML = ` <span class="earnings-currency">SEK </span> ${ formatSEK ( totalEarned ) } ` ;
document . getElementById ( 'earnings-sub' ) . textContent = ` ${ approved . length } approved missions · ${ formatSEK ( totalPending ) } SEK pending ` ;
// Stats
document . getElementById ( 'stat-uppdrag' ) . textContent = approved . length ;
document . getElementById ( 'stat-snittersattning' ) . textContent = avg > 0 ? formatSEK ( avg ) + ' kr' : '—' ;
document . getElementById ( 'stat-pending' ) . textContent = totalPending > 0 ? formatSEK ( totalPending ) + ' kr' : '—' ;
// Payouts list
const payoutsCard = document . getElementById ( 'payouts-card' ) ;
const recent = [ ... approved ] . sort ( ( a , b ) => new Date ( b . approved _at ) - new Date ( a . approved _at ) ) . slice ( 0 , 5 ) ;
if ( recent . length === 0 ) {
payoutsCard . innerHTML = `
<div style="padding:32px;text-align:center">
2026-07-07 07:11:50 +00:00
<div style="font-size:40px;margin-bottom:8px"></div>
2026-07-05 06:41:32 +00:00
<div style="font-size:16px;font-weight:600;margin-bottom:4px">Inga utbetalningar än</div>
<div style="font-size:14px;color:var(--label-secondary)">Slutför ditt första uppdrag för att börja tjäna</div>
</div> ` ;
} else {
payoutsCard . innerHTML = recent . map ( m => `
<div class="payout-row">
<div class="payout-icon" style="background: ${ missionTypeBg ( m . mission _type ) } "> ${ missionTypeEmoji ( m . mission _type ) } </div>
<div class="payout-body">
<div class="payout-title"> ${ m . title } </div>
<div class="payout-date"> ${ formatDate ( m . approved _at ) } · ${ m . payment _ref || '—' } </div>
</div>
<div class="payout-amount" style="color:var(--ios-green)">+ ${ formatSEK ( m . reward _sek ) } kr</div>
</div> ` ) . join ( '' ) ;
}
// Add pending items with different styling
if ( pending . length > 0 ) {
const pendingHTML = pending . slice ( 0 , 3 ) . map ( m => `
<div class="payout-row" style="opacity:0.6">
<div class="payout-icon" style="background:rgba(255,149,0,0.15)">⏳</div>
<div class="payout-body">
<div class="payout-title"> ${ m . title } </div>
<div class="payout-date">Väntande · ${ m . status === 'submitted' ? 'Under granskning' : 'Pågående' } </div>
</div>
<div class="payout-amount" style="color:var(--ios-orange)">~ ${ formatSEK ( m . reward _sek ) } kr</div>
</div> ` ) . join ( '' ) ;
payoutsCard . innerHTML += pendingHTML ;
}
} catch ( e ) {
console . error ( e ) ;
document . getElementById ( 'payouts-card' ) . innerHTML = `
<div style="padding:24px;text-align:center;color:var(--label-secondary)">
2026-07-07 07:11:50 +00:00
<div><svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg> Could not load earnings data</div>
2026-07-05 06:41:32 +00:00
</div> ` ;
}
}
async function loadConnectStatus ( ) {
const statusEl = document . getElementById ( 'connect-status' ) ;
const btnEl = document . getElementById ( 'connect-btn' ) ;
// Try to get onboarding link to check if connect is available
try {
const r = await fetch ( ` ${ API } /payments/connect/onboarding-link ` ) ;
if ( r . ok ) {
const data = await r . json ( ) ;
statusEl . innerHTML = ` <div class="status-dot active"></div> Account activated — ready for payouts ` ;
2026-07-07 07:11:50 +00:00
btnEl . textContent = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="3" stroke="#666" stroke-width="1.5"/><path d="M8 2V4M8 12V14M2 8H4M12 8H14M4.3 4.3L5.7 5.7M10.3 10.3L11.7 11.7M4.3 11.7L5.7 10.3M10.3 5.7L11.7 4.3" stroke="#666" stroke-width="1.5" stroke-linecap="round"/></svg> Hantera konto →' ;
2026-07-05 06:41:32 +00:00
if ( data . url ) { btnEl . dataset . url = data . url ; }
} else if ( r . status === 401 ) {
statusEl . innerHTML = ` <div class="status-dot pending"></div> Inloggning krävs för att verifiera konto ` ;
} else {
statusEl . innerHTML = ` <div class="status-dot inactive"></div> Konto ej konfigurerat ` ;
}
} catch {
statusEl . innerHTML = ` <div class="status-dot inactive"></div> Konto ej konfigurerat — klicka för att börja ` ;
}
}
async function openConnectOnboarding ( ) {
const btn = document . getElementById ( 'connect-btn' ) ;
btn . textContent = '⏳ Öppnar...' ;
btn . disabled = true ;
try {
const r = await fetch ( ` ${ API } /payments/connect/onboarding-link ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( { zoomer _id : getZoomerId ( ) } )
} ) ;
if ( r . ok ) {
const data = await r . json ( ) ;
if ( data . url ) { window . open ( data . url , '_blank' ) ; }
2026-07-07 07:11:50 +00:00
else { toast ( '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="9" stroke="#22c55e" stroke-width="2"/><path d="M6 10L9 13L14 7" stroke="#22c55e" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> Konto verifierat!' ) ; }
2026-07-05 06:41:32 +00:00
} else if ( r . status === 401 ) {
2026-07-07 07:11:50 +00:00
toast ( '<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="3" y="7" width="10" height="7" rx="1" stroke="#666" stroke-width="1.5"/><path d="M5 7V5C5 3.3 6.3 2 8 2C9.7 2 11 3.3 11 5V7" stroke="#666" stroke-width="1.5"/><circle cx="8" cy="10.5" r="1" fill="#666"/></svg> Behörighet saknas — kontakta support' ) ;
2026-07-05 06:41:32 +00:00
} else {
2026-07-07 07:11:50 +00:00
toast ( '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3L17 17H3L10 3Z" stroke="#f59e0b" stroke-width="2"/><path d="M10 8V12M10 14V14.5" stroke="#f59e0b" stroke-width="2" stroke-linecap="round"/></svg> Stripe Connect onboarding — kontakta support@quixzoom.com' ) ;
2026-07-05 06:41:32 +00:00
}
} catch {
2026-07-07 07:11:50 +00:00
toast ( 'ℹ Contact support to configure your payout account' ) ;
2026-07-05 06:41:32 +00:00
} finally {
2026-07-07 07:11:50 +00:00
btn . textContent = ' Configure payout account →' ;
2026-07-05 06:41:32 +00:00
btn . disabled = false ;
}
}
// Init
loadEarnings ( ) ;
loadConnectStatus ( ) ;
< / script >
< / body >
< / html >