Друзья, приветствую!
Сегодня я подготовил небольшую, расслабляющую статью, особенно в сравнении с предыдущими. Недавно я разрабатывал FullStack-приложение для небольшого лендинга, где одной из задач было привязать форму с сайта к Telegram-боту.
Суть задачи:
Пользователь вводит данные в форму на сайте, после чего эта информация отправляется заказчику через Telegram-бота. Данные отправляются в формате HTML, с полным набором информации. Всё это нужно было реализовать на чистом JavaScript + HTML + CSS. Из этой задачи родилась идея для данной статьи.
Чем мы займёмся?
Мы зарегистрируем Telegram-бота через BotFather, создадим приятную страницу с формой для сбора данных с использованием бесплатного сервиса WebSim.ai, а затем настроим отправку сообщений с формы в Telegram-бота, используя только JavaScript, HTML и CSS.
После завершения проекта, я покажу, как запустить его удалённо, чтобы каждый мог воспользоваться нашей формой. Для этого мы будем использовать хостинг Amvera Cloud, потому что это быстро и удобно за счет встроенного CI/CD. Для развертывания достаточно выполнить три команды в IDE или просто перетянуть файлы проекта в интерфейсе.
Кратко о процессе деплоя
Процесс деплоя сводится к следующему:
-
Подготовить файлы страницы.
-
Создать файл инструкций (его я предоставлю).
-
Доставить файлы на Amvera через GIT или встроенный интерфейс.
После простых манипуляций наш сайт станет доступен по бесплатному HTTPS-домену, который предоставит Amvera.
Подготовка Telegram для приёма сообщений
На этом этапе мы создадим Telegram-бота, получим свой Telegram ID, чтобы бот знал, кому отправлять сообщения, и позволим боту отправлять нам сообщения, «познакомившись» с ним.
Шаги для создания Telegram-бота:
-
Перейдите в BotFather.
-
Введите команду
/newbot
(или выберите её в меню). -
Придумайте имя бота — любое, на любом языке.
-
Присвойте уникальный username на английском, оканчивающийся на «bot», «BOT» или «Bot» (без пробелов).
-
Сохраните токен, который выдаст BotFather.
Для того чтоб позволить боту отправлять вам сообщения, перейдите в чат с ботом и нажмите кнопку «СТАРТ».
Получение Telegram ID:
Чтобы бот мог отправлять вам сообщения, нужно получить ваш Telegram ID. Один из способов — воспользоваться ботом IDBot Finder Pro:
-
Перейдите в бота.
-
Нажмите «Старт».
-
Выберите «My info».
-
Кликните на свой ID, и он скопируется в буфер обмена.
Подготовка страницы с формой
Страницу можно сделать самостоятельно или с помощью сервиса WebSim.ai, который сгенерирует нужную форму. Я воспользуюсь WebSim.ai.
Подробно про данный сервис я писал в этой статье: «WebSim AI: Бесплатный ИИ-помощник для быстрой веб-разработки – время фронтендерам напрячься».
Хочу чтоб в моей форме были следующие поля:
-
Имя (обязательно)
-
Фамилия (обязательно)
-
Дата рождения (обязательно)
-
Пол (чекбоксы, обязательно)
-
Хобби (выпадающий список, обязательно минимум одно)
-
Примечание (необязательно)
После отправки формы пользователь увидит сообщение: «Ваша анкета отправлена».
Задача для нейросети WebSim
Скрытый текст
Необходимо создать страницу с формой, которая будет содержать следующие поля:
-
Имя (input type=»text»)
-
Поле обязательно для заполнения.
-
-
Фамилия (input type=»text»)
-
Поле обязательно для заполнения.
-
-
Дата рождения (input type=»date»)
-
Обязательное поле для выбора даты через календарь.
-
-
Пол (input type=»checkbox»)
-
Чекбоксы для выбора пола. Обязательно должно быть выбрано одно значение.
-
Примеры значений: мужской, женский.
-
-
Хобби (select multiple)
-
Выпадающий список с возможностью выбора нескольких значений.
-
Обязательно должно быть выбрано как минимум одно хобби.
-
Пример хобби: чтение, спорт, музыка, путешествия.
-
-
Примечание (textarea)
-
Поле для ввода произвольного текста. Заполнение не обязательно.
-
Под формой необходимо разместить кнопку «Отправить».
После успешной отправки формы, отобразить всплывающее сообщение (popup) с текстом:
«Ваша анкета отправлена».
Требования:
-
Форма должна быть валидирована перед отправкой (проверка обязательных полей).
-
Для реализации можно использовать HTML5 и JavaScript.
Можно писать и проще, но я за то чтоб моя задача была максимально понятна.
Перейдем на сайт WebSim, выполним авторизацию и в поле ввода запроса вставим наш промт.
Посмотрим на результат
Попросим добавить больше стилей. Кроме того, пусть он подправит нам блок с хобби, добавив возможность отмечать чекбоксами и работать в формате выпадающего списка.
Новая задача для нейросети WebSim
Скрытый текст
Добавить стилизацию формы и её элементов:
-
Придать форме более современный вид с использованием CSS (flexbox/grid для организации элементов).
-
Все обязательные поля должны иметь специальную визуальную индикацию (например, красную звёздочку рядом с меткой поля).
-
Стилизация кнопки «Отправить» — сделать её заметной, с эффектом при наведении.
-
Добавить отступы, границы, и тени для полей ввода, чтобы они выглядели аккуратно и легко читались.
-
Всплывающее сообщение «Ваша анкета отправлена» стилизовать как модальное окно с затемнением фона.
Изменить блок с хобби:
-
Реализовать выпадающий список с возможностью выбора нескольких опций, где каждое хобби отмечается через чекбоксы.
-
Для этого добавить кастомный dropdown (например, с использованием select + checkbox для выбора нескольких вариантов).
-
В выпадающем списке пользователь должен видеть доступные варианты хобби и иметь возможность отмечать одно или несколько.
-
Добавить проверку, что как минимум одно хобби выбрано.
Примерные стили:
-
Сделать выпадающий список с хобби похожим на мультивыборные списки с чекбоксами внутри, который открывается при нажатии (можно использовать популярные решения с JavaScript для кастомного мультиселекта).
Валидация формы:
-
Проверить обязательность заполнения всех полей перед отправкой.
-
Подсветить ошибку, если какое-либо обязательное поле не заполнено или не выбрано.
Итоговое требование:
Форма должна выглядеть стильно, быть удобной для пользователей и работать корректно, включая валидацию и отправку данных с подтверждением в виде модального окна.
Смотрим на результат
Теперь нам остается привязать отправку этой формы в наше телеграмм. Попросим об этом сервис, так как WebSim прекрасно работает с JavaScript. После, при необходимости, подправим JS под себя.
Я бы хотел, чтоб сообщение в бота отправлялось с HTML форматированием, поэтому, задачу поставим так:
Скрытый текст
Отправка данных через чистый JavaScript
После того как форма будет валидирована и отправлена, данные должны отправляться на указанный Telegram-бот (ID бота: 7840688347:AAFsSq0EAцуU42lEnXFueфR8RtVtTа37N9BQ
).
Сообщение должно отправляться пользователю с ID: 5120841744
.
Формат сообщения:
Сообщение должно быть в формате ParseMode HTML.
Начало сообщения: «📩 Вам новая заявка:».
После этого перечислить данные из формы:
-
Имя
-
Фамилия
-
Дата рождения
-
Пол
-
Выбранные хобби
-
Примечание (если заполнено)
Примерная структура сообщения:
📩 Вам новая заявка: <b>Имя:</b> Иван <b>Фамилия:</b> Иванов <b>Дата рождения:</b> 01.01.1990 <b>Пол:</b> Мужской <b>Хобби:</b> Чтение, Путешествия <b>Примечание:</b> Люблю кодить!
Инструкция по отправке данных в Telegram:
Использовать метод Telegram API sendMessage
для отправки сообщения.
Заполним анкету
Отправим анкету
Смотрим сообщение
Отлично, все у нас работает.
Теперь давайте скачаем полученный результат и подготовим его к деплою.
Для загрузки кликаем на три точки в правом верхнем углу и нажимаем на «Загрузить».
Сохраненный файл назовем index.html
Теперь у нас в руках полный готовый код проекта , в чем можем убедиться нажав на CTRL + U.
Подготовка проекта к деплою
Сейчас, в целом, мы готовы к деплою, но мне бы хотелось навести порядок в коде. Поэтому, я разобью данный файл на шаблон HTML и на статические файлы (JS и CSS). Кроме того, перед деплоем нам нужно будет создать небольшой файл с инструкциями для корректного запуска нашей странички с формой в облаке.
Структура у меня получилась следующая.
В корневой директории проекта расположен файл index.html, а также папка static, содержащая два файла:
-
script.js — файл с кодом на JavaScript.
-
style.css — файл, содержащий стили для оформления.
Стили и JS-код я просто перенес из тех, что мне предоставил WebSim.
HTML-шаблон
<html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Личная анкета</title> <!-- Импорт стилей --> <link rel="stylesheet" href="static/style.css"> </head> <body> <div class="container"> <h1>Личная анкета</h1> <form id="personalForm"> <div class="form-group"> <label for="firstName" class="required">Имя:</label> <input type="text" id="firstName" name="firstName" required> <div class="error" id="firstNameError"></div> </div> <div class="form-group"> <label for="lastName" class="required">Фамилия:</label> <input type="text" id="lastName" name="lastName" required> <div class="error" id="lastNameError"></div> </div> <div class="form-group"> <label for="birthDate" class="required">Дата рождения:</label> <input type="date" id="birthDate" name="birthDate" required> <div class="error" id="birthDateError"></div> </div> <div class="form-group"> <label class="required">Пол:</label> <div class="radio-group"> <label> <input type="radio" name="gender" value="male" required> Мужской </label> <label> <input type="radio" name="gender" value="female" required> Женский </label> </div> <div class="error" id="genderError"></div> </div> <div class="form-group"> <label for="hobbies" class="required">Хобби:</label> <div class="multiselect"> <button type="button" class="multiselect-btn" id="hobbiesBtn">Выберите хобби</button> <div class="multiselect-dropdown" id="hobbiesDropdown"> <div class="multiselect-option"> <input type="checkbox" id="hobby-reading" value="reading"> <label for="hobby-reading">Чтение</label> </div> <div class="multiselect-option"> <input type="checkbox" id="hobby-sports" value="sports"> <label for="hobby-sports">Спорт</label> </div> <div class="multiselect-option"> <input type="checkbox" id="hobby-music" value="music"> <label for="hobby-music">Музыка</label> </div> <div class="multiselect-option"> <input type="checkbox" id="hobby-travel" value="travel"> <label for="hobby-travel">Путешествия</label> </div> </div> </div> <div class="error" id="hobbiesError"></div> </div> <div class="form-group"> <label for="notes">Примечание:</label> <textarea id="notes" name="notes"></textarea> </div> <button type="submit">Отправить</button> </form> </div> <div id="modal" class="modal"> <div class="modal-content"> <p>Ваша анкета отправлена</p> </div> </div> <!-- Импорт JavaScript --> <script src="static/script.js"></script> </body> </html>
Здесь представлена стандартная HTML-форма. Особое внимание стоит уделить импорту стилей и JavaScript-файлов. Весь код был записан в одном файле.
Импорт стилей:
<link rel="stylesheet" href="static/style.css">
Импорт JavaScript:
<script src="static/script.js"></script>
Код стилей приводить тут не буду, так как демонстрация стилей не является самоцелью данной статьи. Код JS продемонстрирую частично, а именно блок с отправкой данных с формы в телеграмм бота.
Вот фрагмент кода, который отвечает за отправку собранных данных в Telegram-бота, с комментариями:
// Функция для отправки данных в Telegram function sendDataToTelegram(formData) { const botToken = '6845698347:AAFsSq0EAlU42lEапfue7R8RtVtT397N9BQ'; // Токен вашего бота const chatId = '5120801744'; // ID получателя (пользователя) const apiUrl = `https://api.telegram.org/bot${botToken}/sendMessage`; // URL для отправки сообщения // Формируем сообщение в формате HTML const message = ` 📩 Вам новая заявка: <b>Имя:</b> ${formData.firstName} <b>Фамилия:</b> ${formData.lastName} <b>Дата рождения:</b> ${formData.birthDate} <b>Пол:</b> ${formData.gender === 'male' ? 'Мужской' : 'Женский'} <b>Хобби:</b> ${formData.hobbies.join(', ')} <b>Примечание:</b> ${formData.notes || 'Не указано'} `; // Параметры, которые будем отправлять const params = { chat_id: chatId, // ID чата text: message, // Текст сообщения parse_mode: 'HTML' // Режим парсинга HTML }; // Отправляем данные с помощью fetch API return fetch(apiUrl, { method: 'POST', // Метод отправки headers: { 'Content-Type': 'application/json', // Указываем тип содержимого }, body: JSON.stringify(params) // Преобразуем параметры в JSON }).then(response => response.json()); // Возвращаем ответ в формате JSON } // Обработчик события отправки формы form.addEventListener('submit', (e) => { e.preventDefault(); // Отменяем стандартное поведение формы if (validateForm()) { // Проверяем форму на валидность const formData = { // Собираем данные из формы firstName: document.getElementById('firstName').value, lastName: document.getElementById('lastName').value, birthDate: document.getElementById('birthDate').value, gender: document.querySelector('input[name="gender"]:checked').value, hobbies: Array.from(hobbiesCheckboxes) .filter(checkbox => checkbox.checked) .map(checkbox => checkbox.nextElementSibling.textContent), notes: document.getElementById('notes').value }; // Показать состояние загрузки modal.innerHTML = '<div class="modal-content"><p>Отправка данных...</p></div>'; modal.style.display = 'block'; // Отправляем данные в Telegram sendDataToTelegram(formData) .then(result => { if (result.ok) { // Если данные успешно отправлены modal.innerHTML = '<div class="modal-content"><p>Ваша анкета успешно отправлена</p></div>'; } else { // Если произошла ошибка при отправке modal.innerHTML = '<div class="modal-content"><p>Ошибка при отправке анкеты. Пожалуйста, попробуйте еще раз.</p></div>'; } }) .catch(error => { console.error('Error:', error); // Обработка ошибки modal.innerHTML = '<div class="modal-content"><p>Произошла ошибка. Пожалуйста, попробуйте позже.</p></div>'; }) .finally(() => { // Закрываем модальное окно и сбрасываем форму через 3 секунды setTimeout(() => { modal.style.display = 'none'; form.reset(); // Сброс формы updateHobbiesButton(); // Обновляем текст кнопки хобби }, 3000); }); } });
Комментарии к коду
-
sendDataToTelegram: Функция, отправляющая данные формы в Telegram.
-
botToken и chatId: Переменные, содержащие токен бота и ID чата, куда будет отправлено сообщение.
-
message: Форматирование сообщения с использованием HTML для выделения данных.
-
fetch: Используется для отправки данных на сервер Telegram. Ответ преобразуется в JSON.
-
Обработчик события отправки формы: Отменяет стандартное поведение формы, собирает данные и вызывает функцию отправки данных в Telegram.
-
Модальное окно: Показ состояния отправки и сообщение об успехе или ошибке отправки данных.
Этот код обеспечивает отправку данных формы в Telegram с необходимой валидацией и обработкой состояния.
Полный код этого проекта, как и другой эксклюзивный контент, который я не публикую на Хабре, вы найдете в моем телеграмм канале «Легкий путь в Python».
Деплой проекта
И, в завершении этого небольшого гайда, развернем наш проект удаленно. В качестве посадки я буду использовать сервис Amvera Cloud. И, прежде чем мы приступим к деплою, давайте подготовим небольшой файл с инструкциями для запуска проекта.
Мы будем создавать Dockerfile и, если вы совсем в этом не понимаете, то не страшно. Просто вставляйте мой код и все будет работать.
Файл Dockerfile необходимо разместить на одном уровне с файлом index.html. Вот содержимое:
# Используем официальный образ NGINX в качестве базового FROM nginx:alpine # Удаляем дефолтный файл конфигурации NGINX RUN rm /usr/share/nginx/html/index.html # Копируем наш index.html в папку с HTML файлами NGINX COPY index.html /usr/share/nginx/html/ # Копируем статические файлы из папки static в NGINX COPY static /usr/share/nginx/html/static # Экспонируем порт 80 EXPOSE 80 # Запускаем NGINX CMD ["nginx", "-g", "daemon off;"]
Этот Dockerfile создает контейнер с веб-сервером NGINX, который будет обслуживать ваш собственный HTML-файл (index.html) на порту 80. Он обеспечивает корректный запуск вашей веб-страницы в сервисе Amvera и позволяет привязать к ней бесплатное доменное имя с поддержкой HTTPS, что нас интересует.
Приступаем к деплою
-
Регистрируемся на сайте Amvera Cloud если не было регистрации. За регистрацию вы получите 111 рублей в подарок на основной счет.
-
Переходим в раздел проектов
-
Кликаем на «Создать». Далее даем название проекту и выбираем тарифный план. Мне подойдет «Пробный». Затем жмем на «Далее».
-
Теперь нужно определиться с форматом доставки файлов в проект. Есть вариант «Через GIT» и «Через интерфейс». Так как проект не большой я воспользуюсь вторым вариантом. Далее достаточно будет загрузить все файлы проекта в специальное окно загрузки.
-
После жмем «Далее» и на новом экране «Завершить», так как все необходимые настройки мы уже подготовили.
-
Теперь осталось активировать доменное имя. Для этого входим в проект и открываем вкладку «Настройки». Далее нажимаем на «Добавить домен» и активируем его.
-
После нажмем на кнопку «Пересобрать», чтоб доменное имя точно корректно привязалось к нашему проекту.
Этого достаточно. Теперь остается подождать пару минут и при переходе по доменному имени будет доступен наш сайт с возможностью сделать отправку.
У меня получилась такая ссылка: https://formahabr-yakvenalex.amvera.io/. Перейдем по ссылки и выполним тесты.
Решаем проблему с безопасностью
В текущей реализации, хотя приложение и работает, существует существенная уязвимость в области безопасности — это токен бота. Любой, кто откроет панель разработчика в браузере, сможет перехватить этот токен. В рабочих проектах это недопустимо. Чтобы устранить проблему, предлагаю использовать FastAPI для создания простого бэкенда, который будет надежно скрывать токен бота.
Новый подход
Мы создадим серверное приложение на FastAPI с двумя основными функциями:
-
Рендеринг главной страницы (index.html).
-
Обработка POST-запросов с формой.
Теперь данные с формы будут отправляться не напрямую в Telegram, а через серверный эндпоинт. Это повысит безопасность, скрыв критические данные (например, токен бота).
Новая структура проекта
FastAPI гибкий, но рекомендуется придерживаться стандартной структуры:
-
Создаем папку
templates
и помещаем в нее файлindex.html
. -
Изменяем пути к статическим файлам в HTML-шаблоне:
-
Было:
<link rel="stylesheet" href="static/style.css">
-
Стало:
<link rel="stylesheet" href="/static/style.css">
-
Было:
<script src="static/script.js"></script>
-
Стало:
<script src="/static/script.js"></script>
-
JavaScript-код для отправки данных теперь будет выглядеть так:
form.addEventListener('submit', async (e) => { e.preventDefault(); if (validateForm()) { const formData = { firstName: document.getElementById('firstName').value, lastName: document.getElementById('lastName').value, birthDate: document.getElementById('birthDate').value, gender: document.querySelector('input[name="gender"]:checked').value, hobbies: Array.from(hobbiesCheckboxes) .filter(checkbox => checkbox.checked) .map(checkbox => checkbox.nextElementSibling.textContent), notes: document.getElementById('notes').value }; // Показать состояние загрузки modal.innerHTML = '<div class="modal-content"><p>Отправка данных...</p></div>'; modal.style.display = 'block'; // Отправка данных на сервер FastAPI try { const response = await fetch('/send-data/', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.ok) { modal.innerHTML = '<div class="modal-content"><p>Ваша анкета успешно отправлена</p></div>'; } else { const errorMessage = result.error || 'Ошибка при отправке анкеты. Пожалуйста, попробуйте еще раз.'; modal.innerHTML = `<div class="modal-content"><p>${errorMessage}</p></div>`; } } catch (error) { console.error('Error:', error); modal.innerHTML = '<div class="modal-content"><p>Произошла ошибка. Пожалуйста, попробуйте позже.</p></div>'; } finally { setTimeout(() => { modal.style.display = 'none'; form.reset(); updateHobbiesButton(); }, 3000); } } });
Файл requirements.txt
Для Python-кода создадим файл requirements.txt
со следующими зависимостями:
fastapi==0.115.0 uvicorn==0.31.0 python-decouple==3.8 httpx==1.0.0b0 jinja2==3.1.4
-
FastAPI — основной бэкенд фреймворк.
-
Uvicorn — сервер для запуска приложения.
-
python-decouple — для безопасного хранения токена в
.env
. -
httpx — для асинхронного общения с Telegram API.
-
Jinja2 — для рендеринга HTML-шаблонов.
Команда установки зависимостей:
pip install -r requirements.txt
Файл .env
Создаем файл .env
для хранения конфиденциальных данных:
BOT_TOKEN=тут_ваш_токен CHAT_ID=тут_id_чата
Логика бэкенда
Вся логика приложения будет находиться в файле main.py
:
from decouple import config from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates import httpx from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # Разрешаем CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], # Разрешить запросы с любых источников allow_credentials=True, allow_methods=["*"], # Разрешить все методы allow_headers=["*"], # Разрешить все заголовки ) # Настройка статических файлов app.mount("/static", StaticFiles(directory="static"), name="static") # Настройка шаблонов templates = Jinja2Templates(directory="templates") @app.get("/", response_class=HTMLResponse) async def read_index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.post("/send-data/") async def send_data(form_data: dict): bot_token = config('BOT_TOKEN') chat_id = config('CHAT_ID') api_url = f'https://api.telegram.org/bot{bot_token}/sendMessage' message = f""" 📩 Вам новая заявка: <b>Имя:</b> {form_data['firstName']} <b>Фамилия:</b> {form_data['lastName']} <b>Дата рождения:</b> {form_data['birthDate']} <b>Пол:</b> {'Мужской' if form_data['gender'] == 'male' else 'Женский'} <b>Хобби:</b> {', '.join(form_data['hobbies'])} <b>Примечание:</b> {form_data.get('notes', 'Не указано')} """ params = { "chat_id": chat_id, "text": message, "parse_mode": "HTML" } async with httpx.AsyncClient() as client: response = await client.post(api_url, json=params) return {"ok": response.status_code == 200}
Комментарии к коду
-
CORS: Мы добавили
CORSMiddleware
, чтобы разрешить запросы с любых источников. Для повышения безопасности можно ограничить допустимые домены. -
Статические файлы: FastAPI монтирует папку
/static
для работы с CSS и JavaScript. -
Асинхронный запрос: Мы используем
httpx.AsyncClient
для выполнения асинхронного запроса к Telegram API. -
Шаблоны: Для рендеринга HTML-страниц используем Jinja2.
Теперь проект защищен, и критические данные, такие как токен бота, надежно скрыты на сервере. Токен больше не виден в исходном коде фронтенда, что предотвращает его компрометацию.
Деплой нового формата
Так как наша проектная логика теперь изменилась, нам нужно обновить файл с инструкциями для деплоя на Amvera Cloud. Конечно, можно снова использовать Dockerfile, но я хочу продемонстрировать вам другой подход.
Давайте теперь создадим файл amvera.yml с таким содержимым
meta: environment: python toolchain: name: pip version: 3.12 build: requirementsPath: requirements.txt run: persistenceMount: /data containerPort: 8000 command: uvicorn main:app --host 0.0.0.0 --port 8000
В этом файле мы прописали стандартные инструкции для запуска FastApi приложения.
Теперь нам остается доставить файлы проекта на сервер Amvera, чтоб там получилась такая структура:
То есть, Dockerfile нам больше не нужен. После доставки не забудьте пересобрать проект.
Заключение
В этой статье я не стремился убедить вас, что создание сайтов с помощью нейронных сетей — это единственный путь. Моя цель была показать, как можно быстро и просто создать фронтенд для своих приложений без лишних сложностей.
Кроме того, я продемонстрировал, как интегрировать формы захвата данных в Telegram-ботов, не используя языки бэкенда, такие как Python или PHP, и наглядно рассказал, почему лучше так не делать.
Надеюсь, что эта информация была для вас полезной и интересной. Если это так, ставьте лайки и оставляйте комментарии — это мотивирует меня создавать больше полезного и обучающего контента для вас.
До скорого.
ссылка на оригинал статьи https://habr.com/ru/articles/847346/
Добавить комментарий