Search this site
Embedded Files
Gabeler.eu Coaching
  • START
  • Redesign
  • Design
  • Login
  • NextLevel
  • doGet
  • Verificatie
  • Home
  • Code.gs
  • Renew
  • PRIVAT
  • WhatNow
  • Index.html
  • old
  • Loop
  • Questions
  • Reset
  • Compare
  • Backup
  • Duplicate
  • PadelStijn Padellessons
Gabeler.eu Coaching
  • START
  • Redesign
  • Design
  • Login
  • NextLevel
  • doGet
  • Verificatie
  • Home
  • Code.gs
  • Renew
  • PRIVAT
  • WhatNow
  • Index.html
  • old
  • Loop
  • Questions
  • Reset
  • Compare
  • Backup
  • Duplicate
  • PadelStijn Padellessons
  • More
    • START
    • Redesign
    • Design
    • Login
    • NextLevel
    • doGet
    • Verificatie
    • Home
    • Code.gs
    • Renew
    • PRIVAT
    • WhatNow
    • Index.html
    • old
    • Loop
    • Questions
    • Reset
    • Compare
    • Backup
    • Duplicate
    • PadelStijn Padellessons

Verificatie

function g20VerifyCode(email, code) {

  try {

    var ss = getActiveSpreadsheet_();

    ensureSheetsExist_(ss);

    var sheet = ss.getSheetByName('G20_Users');

    var data = sheet.getDataRange().getValues();

    email = email.trim().toLowerCase();

    var cleanInputCode = String(code).trim();

    

    for (var i = 1; i < data.length; i++) {

      if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

        var storedCode = String(data[i][4]).trim();

        if (storedCode === cleanInputCode) {

          sheet.getRange(i + 1, 6).setValue('ACTIVE');

          return { success: true, message: 'Account succesvol geverifieerd! Je kunt nu inloggen.' };

        } else {

          return { success: false, message: 'Onjuiste mailcode. Ingevuld: ' + cleanInputCode + ' vs Opgeslagen: ' + storedCode };

        }

      }

    }

    return { success: false, message: 'E-mailadres niet gevonden.' };

  } catch(e) {

    return { success: false, message: e.toString() };

  }

}

Index

<!DOCTYPE html>

<html lang="nl">

<head>

   <meta charset="UTF-8">

   <meta name="viewport" content="width=device-width, initial-scale=1.0">

   <title>G20 — Dagelijks Geluksbesef | Coaching by Gabeler.eu</title>

   <style>

       :root {

           --primary: #1e3a8a;

           --primary-light: #3b82f6;

           --accent: #10b981;

           --bg: #f8fafc;

           --card-bg: #ffffff;

           --text: #1e293b;

           --text-light: #64748b;

           --border: #e2e8f0;

           --radius: 12px;

       }


       * { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }

       body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 20px; }


       .container { width: 100%; max-width: 800px; background: var(--card-bg); border-radius: var(--radius); box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05), 0 8px 10px -6px rgba(0,0,0,0.05); padding: 30px; margin-top: 20px; margin-bottom: 40px; }


       header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid var(--border); padding-bottom: 20px; }

       header h1 { color: var(--primary); font-size: 1.8rem; margin-bottom: 5px; }

       header p { color: var(--text-light); font-size: 0.95rem; }


       .screen { display: none; }

       .screen.active { display: block; animation: fadeIn 0.3s ease-in-out; }


       @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }


       h2 { color: var(--primary); font-size: 1.4rem; margin-bottom: 15px; }

       p.sub { color: var(--text-light); font-size: 0.9rem; margin-bottom: 20px; }


       .form-group { margin-bottom: 15px; }

       label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 5px; color: var(--text); }

       input, select, textarea { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; }

       input:focus, select:focus, textarea:focus { border-color: var(--primary-light); }


       .btn { display: inline-block; width: 100%; background: var(--primary); color: white; border: none; padding: 12px 20px; font-size: 1rem; font-weight: 600; border-radius: 8px; cursor: pointer; text-align: center; transition: background 0.2s; margin-top: 10px; }

       .btn:hover { background: #1d4ed8; }

       .btn-secondary { background: #64748b; }

       .btn-secondary:hover { background: #475569; }

       .btn-accent { background: var(--accent); }

       .btn-accent:hover { background: #059669; }


       .link-text { text-align: center; margin-top: 15px; font-size: 0.9rem; color: var(--text-light); }

       .link-text a { color: var(--primary-light); text-decoration: none; font-weight: 600; cursor: pointer; }

       .link-text a:hover { text-decoration: underline; }


       /* Dashboard & Nav */

       .navbar { display: flex; justify-content: space-between; align-items: center; background: var(--primary); color: white; padding: 12px 20px; border-radius: 8px; margin-bottom: 25px; }

       .navbar span { font-weight: 600; font-size: 0.9rem; }

       .navbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }

       .navbar button:hover { background: rgba(255,255,255,0.3); }


       .menu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 25px; }

       .menu-card { background: var(--bg); border: 1px solid var(--border); padding: 20px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.2s; }

       .menu-card:hover { border-color: var(--primary-light); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }

       .menu-card h3 { color: var(--primary); font-size: 1.1rem; margin-bottom: 5px; }

       .menu-card p { font-size: 0.85rem; color: var(--text-light); }


       /* G20 Form Fields */

       .g20-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 15px; }

       .g20-item-header { display: flex; align-items: center; margin-bottom: 8px; }

       .g20-emoji { font-size: 1.4rem; margin-right: 10px; }

       .g20-title { font-weight: 600; font-size: 1rem; color: var(--primary); }

       .g20-question { font-size: 0.9rem; color: var(--text); margin-bottom: 10px; font-style: italic; }


       .spinner { display: none; text-align: center; margin: 15px 0; color: var(--primary-light); font-weight: 600; }

       .alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; font-size: 0.9rem; display: none; }

       .alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }

       .alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }


       footer { text-align: center; margin-top: auto; font-size: 0.8rem; color: var(--text-light); padding: 20px; }

   </style>

</head>

<body>


   <div class="container">

       <header>

           <h1>G20 — Dagelijks Geluksbesef</h1>

           <p>Coaching by Gabeler.eu | Coach Stijn Gabeler</p>

       </header>


       <div id="alertBox" class="alert"></div>

       <div id="spinner" class="spinner">Even geduld, bezig met verwerken...</div>


       <!-- SCHERM 1: LOGIN -->

       <div id="screen-login" class="screen active">

           <h2>Inloggen</h2>

           <p class="sub">Log in met je e-mailadres en gekozen wachtwoord.</p>

           <div class="form-group">

               <label>E-mailadres</label>

               <input type="email" id="loginEmail" placeholder="jouw@email.nl">

           </div>

           <div class="form-group">

               <label>Wachtwoord</label>

               <input type="password" id="loginPassword" placeholder="••••••••">

           </div>

           <button class="btn" onclick="handleLogin()">Inloggen</button>

           <div class="link-text">

               Nog geen account? <a onclick="showScreen('screen-register-req')">Registreer hier (Gratis voorproefje)</a><br>

               <a onclick="showScreen('screen-forgot')" style="margin-top: 8px; display: inline-block;">Wachtwoord vergeten?</a>

           </div>

       </div>


       <!-- SCHERM 2A: REGISTRATIE AANVRAAG -->

       <div id="screen-register-req" class="screen">

           <h2>Account Registreren</h2>

           <p class="sub">Vul je gegevens in om je gratis voorproefje te starten. Je ontvangt een verificatiecode per e-mail.</p>

           <div class="form-group">

               <label>Volledige Naam</label>

               <input type="text" id="regName" placeholder="Stijn Gabeler">

           </div>

           <div class="form-group">

               <label>E-mailadres</label>

               <input type="email" id="regEmail" placeholder="jouw@email.nl">

           </div>

           <div class="form-group">

               <label>Kies een Wachtwoord</label>

               <input type="password" id="regPassword" placeholder="••••••••">

           </div>

           <button class="btn" onclick="handleRegisterRequest()">Verstuur Verificatiecode</button>

           <div class="link-text">

               Al een account? <a onclick="showScreen('screen-login')">Direct inloggen</a>

           </div>

       </div>


       <!-- SCHERM 2B: REGISTRATIE VERIFICATIE -->

       <div id="screen-register-verify" class="screen">

           <h2>E-mail Verifiëren</h2>

           <p class="sub">We hebben een 6-cijferige verificatiecode gestuurd naar je e-mailadres. Vul deze hieronder in.</p>

           <div class="form-group">

               <label>Verificatiecode (6 cijfers)</label>

               <input type="text" id="verifyCodeInput" placeholder="123456" maxlength="6">

           </div>

           <button class="btn btn-accent" onclick="handleVerifyCode()">Account Activeren</button>

           <div class="link-text">

               <a onclick="showScreen('screen-login')">Terug naar inloggen</a>

           </div>

       </div>


       <!-- SCHERM 3: WACHTWOORD HERSTELLEN -->

       <div id="screen-forgot" class="screen">

           <h2>Wachtwoord Herstellen</h2>

           <p class="sub">Vul je e-mailadres in om een herstelcode te ontvangen.</p>

           <div class="form-group">

               <label>E-mailadres</label>

               <input type="email" id="forgotEmail" placeholder="jouw@email.nl">

           </div>

           <button class="btn" onclick="handleRequestForgot()">Stuur Herstelcode</button>

          

           <div id="resetSection" style="display:none; margin-top:20px; border-top: 1px solid var(--border); padding-top: 15px;">

               <div class="form-group">

                   <label>Herstelcode</label>

                   <input type="text" id="resetCode" placeholder="123456">

               </div>

               <div class="form-group">

                   <label>Nieuw Wachtwoord</label>

                   <input type="password" id="newPassword" placeholder="••••••••">

               </div>

               <button class="btn btn-accent" onclick="handleExecuteReset()">Wachtwoord Opslaan</button>

           </div>

           <div class="link-text" style="margin-top: 15px;">

               <a onclick="showScreen('screen-login')">Terug naar inloggen</a>

           </div>

       </div>


       <!-- SCHERM 4: HOOFDDASHBOARD -->

       <div id="screen-dashboard" class="screen">

           <div class="navbar">

               <span id="welcomeUser">Welkom</span>

               <button onclick="handleLogout()">Uitloggen</button>

           </div>

          

           <div class="menu-grid">

               <div class="menu-card" onclick="openG20Form()">

                   <h3>📅 Dagelijks G20</h3>

                   <p>Vul je dagboek en de 20 onderdelen in.</p>

               </div>

               <div class="menu-card" onclick="loadCards()">

                   <h3>🃏 Coachingkaarten</h3>

                   <p>Bekijk inzichten en mindfulness kaarten.</p>

               </div>

               <div class="menu-card" onclick="openProfile()">

                   <h3>👤 Mijn Profiel</h3>

                   <p>Beheer je quote, thema en instellingen.</p>

               </div>

               <div class="menu-card" onclick="openExport()">

                   <h3>📥 Data Exporteren</h3>

                   <p>Download je data als JSON, CSV of TXT.</p>

               </div>

           </div>

           <div style="text-align: center; margin-top: 20px;">

               <a href="https://wa.me/" target="_blank" style="color: var(--accent); font-weight: 600; text-decoration: none;">💬 Contact Coach Stijn via WhatsApp</a>

           </div>

       </div>


       <!-- SCHERM 5: G20 DAGBOEK FORMULIER -->

       <div id="screen-g20form" class="screen">

           <div class="navbar">

               <span>G20 Dagboek & Vragen</span>

               <button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>

           </div>

           <div class="form-group">

               <label>Datum</label>

               <input type="date" id="g20Date">

           </div>

          

           <div id="g20QuestionsContainer"></div>


           <div class="form-group">

               <label>Algemeen Inzicht van de dag</label>

               <textarea id="g20Inzicht" rows="3" placeholder="Wat viel je vandaag op?"></textarea>

           </div>

           <div class="form-group">

               <label>Het mooiste moment</label>

               <input type="text" id="g20Moment" placeholder="Waar werd je blij van?">

           </div>

           <div class="form-group">

               <label>Stap voor morgen</label>

               <input type="text" id="g20Stap" placeholder="Wat neem je mee?">

           </div>


           <button class="btn btn-accent" onclick="saveG20Data()">Opslaan in Spreadsheet</button>

       </div>


       <!-- SCHERM 6: COACHINGKAARTEN -->

       <div id="screen-cards" class="screen">

           <div class="navbar">

               <span>Coachingkaarten</span>

               <button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>

           </div>

           <div id="cardsContainer"></div>

       </div>


       <!-- SCHERM 7: PROFIEL -->

       <div id="screen-profile" class="screen">

           <div class="navbar">

               <span>Mijn Profiel</span>

               <button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>

           </div>

           <div class="form-group">

               <label>Naam</label>

               <input type="text" id="profileName">

           </div>

           <div class="form-group">

               <label>E-mailadres (niet wijzigbaar)</label>

               <input type="email" id="profileEmail" disabled style="background:#f1f5f9;">

           </div>

           <div class="form-group">

               <label>Persoonlijke Quote</label>

               <input type="text" id="profileQuote" placeholder="Jij bepaalt je eigen koers.">

           </div>

           <div class="form-group">

               <label>Favoriete Thema</label>

               <input type="text" id="profileTheme" placeholder="Bijv. Verstilling of Geluksbesef">

           </div>

           <button class="btn" onclick="saveProfileData()">Profiel Opslaan</button>

       </div>


       <!-- SCHERM 8: EXPORT -->

       <div id="screen-export" class="screen">

           <div class="navbar">

               <span>Data Exporteren</span>

               <button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>

           </div>

           <p class="sub">Kies je gewenste exportformaat om je dagboekgegevens direct op te slaan of te bekijken.</p>

           <div style="display: flex; gap: 10px; margin-bottom: 20px;">

               <button class="btn" onclick="executeExport('json')">Exporteer JSON</button>

               <button class="btn btn-accent" onclick="executeExport('csv')">Exporteer CSV</button>

               <button class="btn btn-secondary" onclick="executeExport('txt')">Exporteer TXT</button>

           </div>

           <div class="form-group">

               <textarea id="exportOutput" rows="10" readonly placeholder="Export resultaat verschijnt hier..." style="font-family: monospace; font-size: 0.85rem;"></textarea>

           </div>

       </div>


   </div>


   <footer>

       &copy; 2026 Gabeler.eu Coaching &mdash; Alle rechten voorbehouden.

   </footer>


   <script>

       let currentUser = null;

       let cachedQuestions = [];

       let pendingRegEmail = '';


       function showScreen(screenId) {

           document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));

           document.getElementById(screenId).classList.add('active');

           hideAlert();

       }


       function showAlert(msg, isError = false) {

           const box = document.getElementById('alertBox');

           box.innerText = msg;

           box.className = 'alert ' + (isError ? 'alert-error' : 'alert-success');

           box.style.display = 'block';

           window.scrollTo({ top: 0, behavior: 'smooth' });

       }


       function hideAlert() {

           document.getElementById('alertBox').style.display = 'none';

       }


       function setSpinner(show) {

           document.getElementById('spinner').style.display = show ? 'block' : 'none';

       }


       // --- AUTH LOGICA ---

       function handleLogin() {

           const email = document.getElementById('loginEmail').value;

           const pass = document.getElementById('loginPassword').value;

           if (!email || !pass) {

               showAlert('Vul zowel e-mailadres als wachtwoord in.', true);

               return;

           }

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       currentUser = res.user;

                       if (currentUser.status !== 'ACTIVE') {

                           showAlert('Je account is nog niet geverifieerd met de mailcode.', true);

                           pendingRegEmail = email;

                           showScreen('screen-register-verify');

                           return;

                       }

                       document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;

                       showScreen('screen-dashboard');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout bij inloggen: ' + err.message, true);

               })

               .g20Login(email, pass);

       }


       function handleRegisterRequest() {

           const name = document.getElementById('regName').value;

           const email = document.getElementById('regEmail').value;

           const pass = document.getElementById('regPassword').value;

           if (!name || !email || !pass) {

               showAlert('Vul alle velden in voor registratie.', true);

               return;

           }

           pendingRegEmail = email;

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                       showScreen('screen-register-verify');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout: ' + err.message, true);

               })

               .g20RequestRegistrationCode(name, email, pass);

       }


       function handleVerifyCode() {

           const code = document.getElementById('verifyCodeInput').value;

           if (!code) {

               showAlert('Vul de verificatiecode in.', true);

               return;

           }

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                       showScreen('screen-login');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout: ' + err.message, true);

               })

               .g20VerifyCode(pendingRegEmail, code);

       }


       function handleRequestForgot() {

           const email = document.getElementById('forgotEmail').value;

           if (!email) {

               showAlert('Vul je e-mailadres in.', true);

               return;

           }

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                       document.getElementById('resetSection').style.display = 'block';

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout: ' + err.message, true);

               })

               .g20RequestPasswordReset(email);

       }


       function handleExecuteReset() {

           const email = document.getElementById('forgotEmail').value;

           const code = document.getElementById('resetCode').value;

           const newPass = document.getElementById('newPassword').value;

           if (!code || !newPass) {

               showAlert('Vul de herstelcode en het nieuwe wachtwoord in.', true);

               return;

           }

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                       showScreen('screen-login');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout: ' + err.message, true);

               })

               .g20ResetPassword(email, code, newPass);

       }


       function handleLogout() {

           currentUser = null;

           showScreen('screen-login');

       }


       // --- G20 DAGBOEK & VRAGEN ---

       function openG20Form() {

           document.getElementById('g20Date').valueAsDate = new Date();

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       cachedQuestions = res.questions;

                       renderG20Form(res.questions);

                       loadExistingDaily();

                       showScreen('screen-g20form');

                   } else {

                       showAlert('Kon vragen niet laden: ' + res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout: ' + err.message, true);

               })

               .g20GetQuestions();

       }


       function renderG20Form(questions) {

           const container = document.getElementById('g20QuestionsContainer');

           let html = '';

           questions.forEach(q => {

               html += `

                   <div class="g20-item">

                       <div class="g20-item-header">

                           <span class="g20-emoji">${q.emoji}</span>

                           <span class="g20-title">${q.number}. ${q.title}</span>

                       </div>

                       <div class="g20-question">${q.question}</div>

                       <input type="text" id="g_answer_${q.number}" placeholder="Jouw antwoord of ervaring...">

                   </div>

               `;

           });

           container.innerHTML = html;

       }


       function loadExistingDaily() {

           const datum = document.getElementById('g20Date').value;

           google.script.run

               .withSuccessHandler(res => {

                   if (res.success && res.data) {

                       document.getElementById('g20Inzicht').value = res.data.Inzicht || '';

                       document.getElementById('g20Moment').value = res.data.MooisteMoment || '';

                       document.getElementById('g20Stap').value = res.data.MorgenStap || '';

                       cachedQuestions.forEach(q => {

                           const field = document.getElementById('g_answer_' + q.number);

                           if (field) field.value = res.data['G' + q.number] || '';

                       });

                   }

               })

               .g20GetDaily(currentUser.userId, datum);

       }


       function saveG20Data() {

           const payload = {

               UserID: currentUser.userId,

               Datum: document.getElementById('g20Date').value,

               Inzicht: document.getElementById('g20Inzicht').value,

               MooisteMoment: document.getElementById('g20Moment').value,

               MorgenStap: document.getElementById('g20Stap').value

           };

           cachedQuestions.forEach(q => {

               const field = document.getElementById('g_answer_' + q.number);

               payload['G' + q.number] = field ? field.value : '';

           });


           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .withFailureHandler(err => {

                   setSpinner(false);

                   showAlert('Fout bij opslaan: ' + err.message, true);

               })

               .g20SaveDaily(payload);

       }


       // --- KAARTEN ---

       function loadCards() {

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       const container = document.getElementById('cardsContainer');

                       let html = '';

                       res.cards.forEach(c => {

                           html += `

                               <div class="g20-item">

                                   <div class="g20-title">${c.title} <span style="font-size:0.75rem; color:var(--text-light);">(${c.category})</span></div>

                                   <p style="margin-top:5px; font-size:0.9rem;">${c.description}</p>

                               </div>

                           `;

                       });

                       container.innerHTML = html;

                       showScreen('screen-cards');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .g20GetCards();

       }


       // --- PROFIEL ---

       function openProfile() {

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       document.getElementById('profileName').value = res.profile.name;

                       document.getElementById('profileEmail').value = res.profile.email;

                       document.getElementById('profileQuote').value = res.profile.quote;

                       document.getElementById('profileTheme').value = res.profile.favoriteTheme;

                       showScreen('screen-profile');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .g20GetProfile(currentUser.userId);

       }


       function saveProfileData() {

           const profileData = {

               name: document.getElementById('profileName').value,

               quote: document.getElementById('profileQuote').value,

               favoriteTheme: document.getElementById('profileTheme').value

           };

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       showAlert(res.message);

                       currentUser.name = profileData.name;

                       document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .g20SaveProfile(currentUser.userId, profileData);

       }


       // --- EXPORT ---

       function openExport() {

           document.getElementById('exportOutput').value = '';

           showScreen('screen-export');

       }


       function executeExport(format) {

           setSpinner(true);

           google.script.run

               .withSuccessHandler(res => {

                   setSpinner(false);

                   if (res.success) {

                       document.getElementById('exportOutput').value = res.data;

                       showAlert('Export succesvol gegenereerd!');

                   } else {

                       showAlert(res.message, true);

                   }

               })

               .g20ExportData(currentUser.userId, format);

       }

   </script>

</body>

</html>


Code

function doGet(e) {

 return HtmlService.createHtmlOutputFromFile('Index')

   .setTitle('G20 — Dagelijks Geluksbesef | Coaching by Gabeler.eu')

   .addMetaTag('viewport', 'width=device-width, initial-scale=1.0');

}


function getActiveSpreadsheet_() {

 try {

   var ss = SpreadsheetApp.getActiveSpreadsheet();

   if (ss) return ss;

 } catch(e) {}

 return SpreadsheetApp.openById('19C7zYtV7YZ1s-1ZJl_z3E2p_ZMnZTFwvJGNK7R39D60');

}


function setupG20() {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   return { success: true, message: 'Alle G20 tabbladen en structuren zijn succesvol aangemaakt/gecontroleerd.' };

 } catch(e) {

   return { success: false, message: 'Fout bij setupG20: ' + e.toString() };

 }

}


function testG20() {

 return {

   success: true,

   app: 'G20 — Dagelijks Geluksbesef',

   coach: 'Coach Stijn Gabeler',

   message: 'G20 Code.gs werkt correct.'

 };

}


function testG20Spreadsheet() {

 try {

   var ss = getActiveSpreadsheet_();

   var name = ss.getName();

   return { success: true, message: 'Spreadsheet bereikbaar: ' + name };

 } catch(e) {

   return { success: false, message: 'Spreadsheet niet bereikbaar: ' + e.toString() };

 }

}


function testG20Questions() {

 var res = g20GetQuestions();

 return res;

}


function ensureSheetsExist_(ss) {

 var required = {

   'G20_Users': ['UserID', 'Name', 'Email', 'PasswordHash', 'MailCode', 'Status', 'CreatedAt', 'LastLogin', 'Quote', 'FavoriteTheme'],

   'G20_Daily': ['UserID', 'Datum', 'Inzicht', 'MooisteMoment', 'MorgenStap'],

   'G20_Questions': ['Number', 'Code', 'Title', 'Emoji', 'Question', 'Reflection'],

   'G20_Cards': ['CardID', 'Title', 'Category', 'Description'],

   'G20_Favorites': ['UserID', 'ItemType', 'ItemID', 'AddedAt']

 };


 for (var name in required) {

   var sheet = ss.getSheetByName(name);

   if (!sheet) {

     sheet = ss.insertSheet(name);

     var headers = required[name];

     if (name === 'G20_Daily') {

       for (var i = 1; i <= 20; i++) headers.push('G' + i);

     }

     sheet.appendRow(headers);

     if (name === 'G20_Questions') {

       seedQuestions_(sheet);

     }

     if (name === 'G20_Cards') {

       seedCards_(sheet);

     }

   }

 }

}


function seedQuestions_(sheet) {

 var defaultQuestions = [

   [1, "GELUKSBESEF", "Geluksbesef", "😊", "Waar werd je vandaag gelukkig, blij of dankbaar van?", "Geluk wordt zichtbaar wanneer je er bewust aandacht aan geeft."],

   [2, "GEVOELENS", "Gevoelens", "❤️", "Welke gevoelens waren vandaag aanwezig?", "Gevoelens mogen eerst worden opgemerkt voordat je er iets mee hoeft te doen."],

   [3, "GEDROOMD", "Gedroomd", "💭", "Waar heb je vandaag over gedroomd of gefantaseerd?", "Een droom kan iets vertellen over wat belangrijk voor je is."],

   [4, "GEMIS", "Gemis", "🫶", "Wat of wie heb je vandaag gemist?", "Gemis kan zichtbaar maken wat waardevol voor je is."],

   [5, "GEWENST", "Gewenst", "🌱", "Wat had je vandaag graag anders, beter of mooier gewild?", "Een wens kan een richting aanwijzen."],

   [6, "GEHOOPT", "Gehoopt", "✨", "Waar hoopte je vandaag op?", "Hoop geeft ruimte aan mogelijkheden."],

   [7, "GEDACHTEN", "Gedachten", "🧠", "Welke gedachte bleef vandaag bij je hangen?", "Aandacht voor gedachten geeft ruimte om bewust te kiezen."],

   [8, "GEDRAG", "Gedrag", "🚶", "Hoe heb je vandaag gehandeld?", "Gedrag laat zien hoe gedachten en waarden in de praktijk komen."],

   [9, "GEZEGD", "Gezegd", "💬", "Wat heb je vandaag gezegd dat belangrijk voor je was?", "Woorden kunnen verbinden, duidelijkheid geven en veranderen."],

   [10, "GEDAAN", "Gedaan", "✅", "Wat heb je vandaag gedaan waar je bewust bij wilt stilstaan?", "Ook kleine acties kunnen betekenisvol zijn."],

   [11, "GESCHREVEN", "Geschreven", "✍️", "Wat heb je vandaag geschreven?", "Schrijven helpt gedachten zichtbaar te maken."],

   [12, "GELEZEN", "Gelezen", "📖", "Wat heb je vandaag gelezen dat je raakte of iets leerde?", "Wat je leest kan nieuwe perspectieven openen."],

   [13, "GELEERD", "Geleerd", "🎓", "Wat heb je vandaag geleerd?", "Iedere dag bevat momenten waarop je iets nieuws kunt ontdekken."],

   [14, "GEWETEN", "Geweten", "🧭", "Wat wist je vandaag diep vanbinnen al?", "Innerlijk weten kan een waardevol signaal zijn."],

   [15, "GEHOORD", "Gehoord", "👂", "Wat heb je vandaag gehoord dat je bijblijft?", "Luisteren kan net zo waardevol zijn als spreken."],

   [16, "GEZIEN", "Gezien", "👀", "Wat heb je vandaag bewust gezien?", "Aandacht verandert wat je opmerkt."],

   [17, "GEPROEFD", "Geproefd", "😋", "Wat heb je vandaag geproefd en bewust beleefd?", "Zintuiglijke aandacht brengt je terug naar het moment."],

   [18, "GEROKEN", "Geroken", "🌸", "Welke geur heb je vandaag bewust opgemerkt?", "Geur kan herinneringen en gevoelens oproepen."],

   [19, "GEDACHT_OVER", "Gedacht over", "🔎", "Waar heb je vandaag langer of dieper over nagedacht?", "Reflectie ontstaat wanneer je even vertraagt."],

   [20, "GELUKSGETAL", "Geluksgetal", "🔢", "Welk getal heeft vandaag betekenis voor jou?", "Een getal kan een persoonlijk anker voor je dag worden."]

 ];

 sheet.getRange(2, 1, defaultQuestions.length, 6).setValues(defaultQuestions);

}


function seedCards_(sheet) {

 var cards = [

   ["CARD-01", "Verstilling", "Mindfulness", "Neem een moment om stil te staan bij je ademhaling."],

   ["CARD-02", "Grenzen", "Balans", "Nee zeggen tegen een ander is ja zeggen tegen jezelf."],

   ["CARD-03", "Dankbaarheid", "Geluksbesef", "Tel je zegeningen en merk op wat er al is."]

 ];

 sheet.getRange(2, 1, cards.length, 4).setValues(cards);

}


function hashPassword_(pass) {

 var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, pass, Utilities.Charset.UTF_8);

 var txt = '';

 for (var i = 0; i < rawHash.length; i++) {

   var byte = rawHash[i];

   if (byte < 0) byte += 256;

   var bStr = byte.toString(16);

   if (bStr.length == 1) bStr = '0' + bStr;

   txt += bStr;

 }

 return txt;

}


function g20GetQuestions() {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Questions');

   var data = sheet.getDataRange().getValues();

   var questions = [];

   for (var i = 1; i < data.length; i++) {

     if (data[i][0]) {

       questions.push({

         number: data[i][0],

         code: data[i][1],

         title: data[i][2],

         emoji: data[i][3],

         question: data[i][4],

         reflection: data[i][5]

       });

     }

   }

   return { success: true, questions: questions };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20RequestRegistrationCode(name, email, password) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

  

   email = email.trim().toLowerCase();

   for (var i = 1; i < data.length; i++) {

     if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

       if (data[i][5] === 'ACTIVE') {

         return { success: false, message: 'Dit e-mailadres is al geregistreerd. Log direct in.' };

       }

     }

   }

  

   var userId = 'G20-' + Utilities.getUuid().substring(0, 8).toUpperCase();

   var hash = hashPassword_(password);

   var mailCode = Math.floor(100000 + Math.random() * 900000).toString();

  

   MailApp.sendEmail(

     email,

     'Jouw G20 Verificatiecode — Coaching by Gabeler.eu',

     'Hi ' + name + ',\n\nBedankt voor je registratie bij G20 — Dagelijks Geluksbesef.\nJouw verificatiecode is: ' + mailCode + '\n\nMet vriendelijke groet,\nCoach Stijn Gabeler'

   );

  

   sheet.appendRow([userId, name, email, hash, mailCode, 'PENDING_VERIFICATION', new Date(), new Date(), '', '']);

  

   return { success: true, message: 'Registratie gestart! Er is een verificatiecode verzonden naar ' + email, userId: userId };

 } catch(e) {

   return { success: false, message: 'Fout bij registratie: ' + e.toString() };

 }

}


function g20VerifyCode(email, code) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   email = email.trim().toLowerCase();

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

       if (String(data[i][4]) === String(code).trim()) {

         sheet.getRange(i + 1, 6).setValue('ACTIVE');

         return { success: true, message: 'Account succesvol geverifieerd! Je kunt nu inloggen.' };

       } else {

         return { success: false, message: 'Onjuiste mailcode.' };

       }

     }

   }

   return { success: false, message: 'E-mailadres niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20Login(email, password) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   email = email.trim().toLowerCase();

   var hash = hashPassword_(password);

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

       if (data[i][3] === hash) {

         sheet.getRange(i + 1, 8).setValue(new Date());

         return {

           success: true,

           user: { userId: data[i][0], name: data[i][1], email: data[i][2], status: data[i][5], quote: data[i][8] || '', favoriteTheme: data[i][9] || '' }

         };

       } else {

         return { success: false, message: 'Onjuist wachtwoord.' };

       }

     }

   }

   return { success: false, message: 'Gebruiker niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20RequestPasswordReset(email) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   email = email.trim().toLowerCase();

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

       var resetCode = Math.floor(100000 + Math.random() * 900000).toString();

       sheet.getRange(i + 1, 5).setValue(resetCode);

       MailApp.sendEmail(

         email,

         'Wachtwoord Herstelcode — G20 Coaching',

         'Hi ' + data[i][1] + ',\n\nJe hebt een nieuw wachtwoord aangevraagd. Jouw herstelcode is: ' + resetCode + '\n\nCoach Stijn Gabeler'

       );

       return { success: true, message: 'Herstelcode verzonden naar je e-mailadres.' };

     }

   }

   return { success: false, message: 'E-mailadres niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20ResetPassword(email, code, newPassword) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   email = email.trim().toLowerCase();

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][2] && data[i][2].toString().toLowerCase() === email) {

       if (String(data[i][4]) === String(code).trim()) {

         var newHash = hashPassword_(newPassword);

         sheet.getRange(i + 1, 4).setValue(newHash);

         sheet.getRange(i + 1, 5).setValue(''); // Clear code

         return { success: true, message: 'Wachtwoord succesvol gewijzigd!' };

       } else {

         return { success: false, message: 'Onjuiste herstelcode.' };

       }

     }

   }

   return { success: false, message: 'E-mailadres niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20SaveDaily(payload) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Daily');

   var data = sheet.getDataRange().getValues();

   var rowIndex = -1;

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === payload.UserID && String(data[i][1]).substring(0, 10) === String(payload.Datum).substring(0, 10)) {

       rowIndex = i + 1;

       break;

     }

   }

  

   var rowValues = [payload.UserID, payload.Datum, payload.Inzicht || '', payload.MooisteMoment || '', payload.MorgenStap || ''];

   for (var j = 1; j <= 20; j++) {

     rowValues.push(payload['G' + j] || '');

   }

  

   if (rowIndex > -1) {

     sheet.getRange(rowIndex, 1, 1, rowValues.length).setValues([rowValues]);

   } else {

     sheet.appendRow(rowValues);

   }

  

   return { success: true, message: 'Opgeslagen in spreadsheet!' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20GetDaily(userId, datum) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Daily');

   var data = sheet.getDataRange().getValues();

   if (data.length <= 1) return { success: true, data: null };

   var headers = data[0];

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === userId && String(data[i][1]).substring(0, 10) === String(datum).substring(0, 10)) {

       var record = { Inzicht: data[i][2], MooisteMoment: data[i][3], MorgenStap: data[i][4] };

       for (var j = 5; j < headers.length; j++) {

         record[headers[j]] = data[i][j];

       }

       return { success: true, data: record };

     }

   }

   return { success: true, data: null };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20GetCards() {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Cards');

   var data = sheet.getDataRange().getValues();

   var cards = [];

   for (var i = 1; i < data.length; i++) {

     if (data[i][0]) {

       cards.push({ cardId: data[i][0], title: data[i][1], category: data[i][2], description: data[i][3] });

     }

   }

   return { success: true, cards: cards };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20GetProfile(userId) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === userId) {

       return {

         success: true,

         profile: { userId: data[i][0], name: data[i][1], email: data[i][2], quote: data[i][8] || '', favoriteTheme: data[i][9] || '' }

       };

     }

   }

   return { success: false, message: 'Profiel niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20SaveProfile(userId, profileData) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Users');

   var data = sheet.getDataRange().getValues();

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === userId) {

       if (profileData.name) sheet.getRange(i + 1, 2).setValue(profileData.name);

       if (profileData.quote !== undefined) sheet.getRange(i + 1, 9).setValue(profileData.quote);

       if (profileData.favoriteTheme !== undefined) sheet.getRange(i + 1, 10).setValue(profileData.favoriteTheme);

       return { success: true, message: 'Profiel bijgewerkt!' };

     }

   }

   return { success: false, message: 'Gebruiker niet gevonden.' };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20GetHistory(userId) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Daily');

   var data = sheet.getDataRange().getValues();

   var history = [];

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === userId) {

       history.push({ datum: String(data[i][1]).substring(0, 10), inzicht: data[i][2], moment: data[i][3] });

     }

   }

   return { success: true, history: history };

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


function g20ExportData(userId, format) {

 try {

   var ss = getActiveSpreadsheet_();

   ensureSheetsExist_(ss);

   var sheet = ss.getSheetByName('G20_Daily');

   var data = sheet.getDataRange().getValues();

   var headers = data[0];

   var userRows = [];

  

   for (var i = 1; i < data.length; i++) {

     if (data[i][0] === userId) {

       var obj = {};

       for (var j = 0; j < headers.length; j++) {

         obj[headers[j]] = data[i][j];

       }

       userRows.push(obj);

     }

   }

  

   if (format === 'json') {

     return { success: true, format: 'json', data: JSON.stringify(userRows, null, 2) };

   } else if (format === 'csv') {

     var csv = headers.join(',') + '\n';

     userRows.forEach(function(row) {

       var line = headers.map(function(h) { return '"' + (row[h] || '') + '"'; }).join(',');

       csv += line + '\n';

     });

     return { success: true, format: 'csv', data: csv };

   } else {

     var txt = 'G20 EXPORT — COACHING BY GABELER.EU\n\n';

     userRows.forEach(function(row) {

       txt += 'Datum: ' + row.Datum + '\nInzicht: ' + row.Inzicht + '\nMooiste moment: ' + row.MooisteMoment + '\n---\n';

     });

     return { success: true, format: 'txt', data: txt };

   }

 } catch(e) {

   return { success: false, message: e.toString() };

 }

}


Site, Sheet, Script by  Stijn  Gabeler.eu  ~ Gabeler.eu@gmail.com 


Google Sites
Report abuse
Page details
Page updated
Google Sites
Report abuse