Реализация виджета PIN-кода в мини-приложении на примере сервиса Puzzlebot

Пример код-пароля
Пример код-пароля

В данном кейсе мы рассмотрим, как реализовать виджет PIN-кода в мини-приложении, используя сервис Puzzlebot.
Этот виджет позволяет пользователям вводить PIN-код для доступа к определенным функциям приложения, что повышает уровень защиты информации и делает взаимодействие с вашим ботом более безопасным.

Зачем нужен виджет PIN-кода?

Использование PIN-кода в мини-приложении может быть полезно в следующих сценариях: - Защита пользовательских данных. PIN-код обеспечит дополнительный уровень безопасности, позволяя пользователям контролировать доступ к своим данным. - Ограничение доступа к специализированным функциям вашего бота. Например, вы можете создать контент, доступный только тем пользователям, которые ввели правильный PIN-код. - Повышение уровня информированности о безопасности и конфиденциальности. Наличие PIN-кода помогает пользователю осознать важность безопасности данных.

Давайте разберем код HTML, CSS и JavaScript, который реализует виджет для ввода PIN-кода.

Я объясню, какие главные атрибуты используются, и укажу, что отвечает за проверку ввода PIN-кода.

<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PIN-код виджет</title> <style> /* CSS стили для оформления виджета */ </style> </head> <body> <div class="pin-widget"> <div class="pin-input"> <div class="circle" id="circle1"></div> <div class="circle" id="circle2"></div> <div class="circle" id="circle3"></div> <div class="circle" id="circle4"></div> </div> <input type="tel" id="pin" maxlength="4" style="opacity: 0; position: absolute;" autofocus /> <a id="continueLink" goto_web_page="86d9caeba79a216b" style="display: none;">Перейти на следующую страницу</a> <button id="continueBtn" disabled>ㅤ</button> </div> <script> // JavaScript для обработки ввода PIN-кода </script> </body> </html>

Объяснение атрибутов и элементов

  • : Указывает, что документ является HTML5.
  • : Основной контейнер для виджета PIN-кода.
  • : Контейнер для отображения введенного PIN-кода.
  • : Круги, представляющие введенные цифры PIN-кода. Каждый круг имеет уникальный id.
  • : Скрытое текстовое поле для ввода PIN-кода.
  • :
Ссылка для перехода на следующую страницу, скрыта от пользователя.goto_web_page: Пользовательский атрибут, который хранит идентификатор страницы для перехода.

Проверка ввода PIN-кода

Проверка ввода PIN-кода осуществляется в JavaScript:

const pinInput = document.getElementById('pin'); const circles = [ document.getElementById('circle1'), document.getElementById('circle2'), document.getElementById('circle3'), document.getElementById('circle4') ]; const continueBtn = document.getElementById('continueBtn'); const continueLink = document.getElementById('continueLink'); const correctPin = '{{pin}}'; // Задайте правильный PIN-код здесь pinInput.addEventListener('input', () => { const value = pinInput.value; for (let i = 0; i < circles.length; i++) { circles[i].textContent = i < value.length ? '●' : ''; } if (value.length === 4) { if (value === correctPin) { continueBtn.disabled = false; continueLink.click(); // Автоматический переход } else { alert('Неверный PIN-код. Попробуйте еще раз.'); pinInput.value = ''; circles.forEach(circle => circle.textContent = ''); } } });
  • pinInput.addEventListener('input', ...): Добавляет обработчик события, который срабатывает при вводе данных в поле.
  • const value = pinInput.value;: Получает текущее значение, введенное пользователем.
  • circles[i].textContent = i < value.length ? '●' : '';: Обновляет отображение кругов в зависимости от введенных символов.
  • if (value.length === 4): Проверяет, введены ли все 4 символа.
  • if (value === correctPin): Сравнивает введенный PIN-код с правильным значением.
  • continueBtn.disabled = false;: Если PIN-код правильный, происходит автоматическое перенаправление пользователя к следующей странице.
  • alert('Неверный PIN-код. Попробуйте еще раз.');: Если PIN-код неверный, выводится сообщение об ошибке в попапе.
alert('Неверный PIN-код. Попробуйте еще раз.');
alert('Неверный PIN-код. Попробуйте еще раз.');

Таким образом, код создает интерактивный виджет для ввода PIN-кода с визуальной обратной связью и проверкой правильности ввода

Код виджета:

<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PIN-код виджет</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f7f7f7; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Rob "Helvetica Neue", Arial, sans-serif; } .pin-widget { display: flex; flex-direction: column; align-items: center; background: #f3f2f8; padding: 20px; border-radius: 10px; } .pin-input { display: flex; justify-content: space-between; width: 110px; margin-bottom: 20px; } .circle { width: 15px; height: 15px; border: 1px solid #000000; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 24px; background-color: #f3f2f8; } /* Удаляем стили для кнопки */ /* button { padding: 10px 110px; background-color: #f3f2f8; color: white; border: none; border-radius: 9px; cursor: pointer; font-size: 16px; } button:disabled { background-color: #f3f2f8; } */ </style> </head> <body> <div class="pin-widget"> <div class="pin-input"> <div class="circle" id="circle1"></div> <div class="circle" id="circle2"></div> <div class="circle" id="circle3"></div> <div class="circle" id="circle4"></div> </div> <input type="tel" id="pin" maxlength="4" style="opacity: 0; position: absolute;" autofocus /> <a id="continueLink" goto_web_page="86d9caeba79a216b" style="display: none;">Перейти на следующую страницу</a> <!-- При желании можно добавить кнопку --> <!-- <button id="continueBtn" disabled>ㅤ</button> --> </div> <script> const pinInput = document.getElementById('pin'); const circles = [ document.getElementById('circle1'), document.getElementById('circle2'), document.getElementById('circle3'), document.getElementById('circle4') ]; // const continueBtn = document.getElementById('continueBtn'); const continueLink = document.getElementById('continueLink'); const correctPin = '{{pin}}'; // Задайте правильный PIN-код здесь pinInput.addEventListener('input', () => { const value = pinInput.value; for (let i = 0; i < circles.length; i++) { circles[i].textContent = i < value.length ? '●' : ''; } if (value.length === 4) { if (value === correctPin) { // Убираем проверку на кнопку // continueBtn.disabled = false; continueLink.click(); // Автоматический переход } else { alert('Неверный PIN-код. Попробуйте еще раз.'); pinInput.value = ''; circles.forEach(circle => circle.textContent = ''); } } }); // continueBtn.addEventListener('click', () => { // gotoWebPage(continueLink.getAttribute('goto_web_page')); // }); function gotoWebPage(id, openInBrowser = false) { const url = getWebPageUrl(id); if (openInBrowser) { window.open(url, '_blank'); } else { window.location.href = url; } } function getWebPageUrl(id) { return `https://example.com/${id}`; // Замените на нужный URL } </script> </body> </html>
6
1
1
1 комментарий
\n\n","lang":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Объяснение атрибутов и элементов"}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":[": Указывает, что документ является HTML5.",": Основной контейнер для виджета PIN-кода.",": Контейнер для отображения введенного PIN-кода.",": Круги, представляющие введенные цифры PIN-кода. Каждый круг имеет уникальный id.",": Скрытое текстовое поле для ввода PIN-кода.",":"],"type":"UL"}},{"type":"quote","cover":false,"hidden":false,"anchor":"","data":{"text":"Ссылка для перехода на следующую страницу, скрыта от пользователя.goto_web_page: Пользовательский атрибут, который хранит идентификатор страницы для перехода.","subline1":""}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Проверка ввода PIN-кода"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Проверка ввода PIN-кода осуществляется в JavaScript:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"const pinInput = document.getElementById('pin');\nconst circles = [\n document.getElementById('circle1'),\n document.getElementById('circle2'),\n document.getElementById('circle3'),\n document.getElementById('circle4')\n];\nconst continueBtn = document.getElementById('continueBtn');\nconst continueLink = document.getElementById('continueLink');\nconst correctPin = '{{pin}}'; // Задайте правильный PIN-код здесь\n\npinInput.addEventListener('input', () => {\n const value = pinInput.value;\n for (let i = 0; i < circles.length; i++) {\n circles[i].textContent = i < value.length ? '●' : '';\n }\n\n if (value.length === 4) {\n if (value === correctPin) {\n continueBtn.disabled = false;\n continueLink.click(); // Автоматический переход\n } else {\n alert('Неверный PIN-код. Попробуйте еще раз.');\n pinInput.value = '';\n circles.forEach(circle => circle.textContent = '');\n }\n }\n});","lang":""}},{"type":"list","cover":false,"hidden":false,"anchor":"","data":{"items":["pinInput.addEventListener('input', ...): Добавляет обработчик события, который срабатывает при вводе данных в поле.","const value = pinInput.value;: Получает текущее значение, введенное пользователем.","circles[i].textContent = i < value.length ? '●' : '';: Обновляет отображение кругов в зависимости от введенных символов.","if (value.length === 4): Проверяет, введены ли все 4 символа.","if (value === correctPin): Сравнивает введенный PIN-код с правильным значением.","continueBtn.disabled = false;: Если PIN-код правильный, происходит автоматическое перенаправление пользователя к следующей странице.","alert('Неверный PIN-код. Попробуйте еще раз.');: Если PIN-код неверный, выводится сообщение об ошибке в попапе."],"type":"UL"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"alert('Неверный PIN-код. Попробуйте еще раз.');","image":{"type":"image","data":{"uuid":"555c5e41-cfb3-544b-8f5d-8a93600fddb4","width":1656,"height":930,"size":45866,"type":"jpg","color":"797a7b","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABgQF/8QAJBAAAQMDAgcBAAAAAAAAAAAAAgEDBAAGEQUzEiExMkFRclL/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/EABURAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIRAxEAPwBPb9tWlM03TZE28GoTrjSE+wuMgvrtXxz60AnCATnwaPjAXCQS/SZXC0FjOyHylZ728f0tEr//2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Таким образом, код создает интерактивный виджет для ввода PIN-кода с визуальной обратной связью и проверкой правильности ввода

"}},{"type":"header","cover":false,"hidden":false,"anchor":"","data":{"style":"h2","text":"Код виджета:"}},{"type":"code","cover":false,"hidden":false,"anchor":"-A4y5e682sk","data":{"text":"\n\n\n \n \n PIN-код виджет\n \n\n\n
\n
\n
\n
\n
\n
\n
\n \n Перейти на следующую страницу\n \n \n
\n\n \n\n","lang":""}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":1,"favorites":4,"reposts":0,"views":776,"hits":479,"reads":null,"online":0},"dateFavorite":0,"hitsCount":479,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/telegram/1853810-realizaciya-vidzheta-pin-koda-v-mini-prilozhenii-na-primere-servisa-puzzlebot","author":{"id":755936,"name":"Hacker Verifure","nickname":null,"description":"Шаблоны HTML виджетов: Мы предлагаем широкий спектр готовых шаблонов, которые помогут пригодиться в Mini-App","uri":"","avatar":{"type":"image","data":{"uuid":"f536a51a-90ad-53e6-9996-52506cd70148","width":828,"height":828,"size":59583,"type":"jpg","color":"e7d4c5","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAdHx8kKDQsJCYxJx8fLT0tMTU3Ojo6Iys/RD84QzQ5Ojf/2wBDAQoKCg0MDRoPDxo3JR8lNzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzf/wAARCAAKAAoDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIFB//EACYQAAECBAMJAAAAAAAAAAAAAAECAwAEESEFBjESExUiMkFRYXH/xAAUAQEAAAAAAAAAAAAAAAAAAAAF/8QAHREAAQQCAwAAAAAAAAAAAAAAAQACBBEDMVFhof/aAAwDAQACEQMRAD8AotzRmcTCEzZC0sgol20jmNb1tXT55hjO3NiPR7QZHSnj2KKoNre60v1LjQCw0TUtIJOpKRCEebT322we9IyTBvGwB1EDjfq//9k="}},"cover":{"cover":{"type":"image","data":{"uuid":"03bffb95-befa-548d-9188-a0dff1180e28","width":800,"height":800,"size":44565,"type":"jpg","color":"32a9f6","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAQDAwQDAwQEAwQFBAQFBgoHBgYGBg0JCggKDw0QEA8NDw4RExgUERIXEg4PFRwVFxkZGxsbEBQdHx0aHxgaGxr/2wBDAQQFBQYFBgwHBwwaEQ8RGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhr/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABgAD/8QAIhABAAEDAwQDAAAAAAAAAAAAAQIAAxIFE1EEETGRQVKh/8QAFQEBAQAAAAAAAAAAAAAAAAAABwj/xAAcEQEAAQQDAAAAAAAAAAAAAAABAgADBREhcaH/2gAMAwEAAhEDEQA/AF17X4FvpdgtXBlhed1jKPzl2RH6/tZT12MZyI3BBQR80HyeX3Vk8vunyGMtwXau/KneeQnIAA13zX//2Q=="}},"cover_y":50},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":4680765,"userId":755936,"count":0,"shareImage":"https://api.vc.ru/achievements/share/4680765"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1056678,"userId":755936,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1056678"}],"lastModificationDate":1764982586,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":3680492,"name":"Телеграм","description":"Все о мессенджере Телеграм: новые фичи, каналы, новости, Павел Дуров, TON","uri":"/telegram","avatar":{"type":"image","data":{"uuid":"a6bd3ea3-1a25-5402-8c27-d063a43ad500","width":500,"height":500,"size":6665,"type":"jpg","color":"28a7e8","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABgME/8QAIRAAAQIGAgMAAAAAAAAAAAAAAQMEAAIFERIxBiETFEH/xAAVAQEBAAAAAAAAAAAAAAAAAAAEB//EABkRAAIDAQAAAAAAAAAAAAAAAAIDAAERYf/aAAwDAQACEQMRAD8AZcWpFGqLJ24fVgJKt2yi3ryjGY4g2F5tkn4AeoNBQ21FOS5hsMSHKrM7I69C1qAgLbLd5yTVA8uo0AdQmoSf/9k="}},"cover":{"type":"image","data":{"uuid":"ebe1c612-381b-52c8-897c-ac8a8002770f","width":1920,"height":1080,"size":636123,"type":"jpg","color":"def2f4","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCAAKAAoDASEAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwEI/8QAHxAAAQMEAwEAAAAAAAAAAAAAAQACERIiQVEhMTKS/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAEDBP/EABURAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIRAxEAPwDSQjeNKtIgXD5V2EVToNx8jKRrnUjk9bQUf//Z"}},"lastModificationDate":1721721308,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"telegram","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":6},{"id":2,"count":1},{"id":15,"count":1}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null,"keywords":[],"media":{"type":"image","data":{"uuid":"19578ca9-ebf1-5d8f-85ca-f29d5c4e8990","width":1656,"height":930,"size":95786,"type":"jpg","color":"46b1f7","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ0ODg7/2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAYHCP/EACUQAAEDAwMDBQAAAAAAAAAAAAQBAgUAAxEGFCEIIlETFSRikf/EABUBAQEAAAAAAAAAAAAAAAAAAAcG/8QAIxEAAQMDBAIDAAAAAAAAAAAAAQACAwYhIgQFERQTcVFSYf/aAAwDAQACEQMRAD8A3DoeD0vNdLmvdTScNHmzkXuticQG25fHyKzHp3FTLOcrxTBUQliqeCMOIGFvbkKUx4paS1D3NBOd/TVD/evvTb1PxAPaSG2ZlwwDY4SVMFjyV+QLZJey1e7UTuai4dx5So/e4YX1BA5zQTjfgfKQNhnmZTeoa1xAztyfqEZXyv7SuiBf/9k="}},"customCover":null,"robotsTag":"noindex","categories":[10],"isAnonymized":true}};