docs: add implementation plan for client questionnaire
This commit is contained in:
@@ -0,0 +1,782 @@
|
||||
# Client Questionnaire Form — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Создать веб-форму на русском языке с 12 вопросами для сбора требований от клиента парка, с отправкой ответов в Airtable и на email.
|
||||
|
||||
**Architecture:** Статичная HTML страница с vanilla JS. При отправке JS делает два запроса: POST в Airtable API (сохранить запись) и POST в EmailJS (отправить письмо). Хостинг на Netlify через GitHub.
|
||||
|
||||
**Tech Stack:** HTML5, CSS3, Vanilla JS, Airtable API, EmailJS, Netlify
|
||||
|
||||
---
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
C:\pro\Sunset Pass\
|
||||
├── index.html # Главная страница с формой
|
||||
├── style.css # Стили
|
||||
├── script.js # Логика отправки (Airtable + EmailJS)
|
||||
├── success.html # Страница после успешной отправки
|
||||
└── .env.example # Пример переменных (ключи API)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1: HTML структура формы
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\pro\Sunset Pass\index.html`
|
||||
|
||||
- [ ] **Step 1: Создать index.html с формой**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sunset Pass — Опросник</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Sunset Pass</h1>
|
||||
<p class="subtitle">Опросник для разработки системы бронирования парка</p>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progressFill"></div>
|
||||
</div>
|
||||
<p class="progress-text" id="progressText">Вопрос 1 из 12</p>
|
||||
</header>
|
||||
|
||||
<form id="questionnaireForm">
|
||||
|
||||
<!-- О парке -->
|
||||
<section class="section">
|
||||
<h2>О парке</h2>
|
||||
|
||||
<div class="question" data-index="1">
|
||||
<label>1. Какой размер парка? (площадь, примерно)</label>
|
||||
<textarea name="park_size" placeholder="Например: 2000 кв.м, примерно 50x40 метров"></textarea>
|
||||
<textarea name="park_size_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="2">
|
||||
<label>2. Сколько человек максимум могут быть в парке одновременно?</label>
|
||||
<textarea name="max_people" placeholder="Например: 5 человек с собаками"></textarea>
|
||||
<textarea name="max_people_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="3">
|
||||
<label>3. Какова минимальная и максимальная длительность одного слота?</label>
|
||||
<textarea name="slot_duration" placeholder="Например: минимум 30 минут, максимум 2 часа"></textarea>
|
||||
<textarea name="slot_duration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="4">
|
||||
<label>4. Часы работы парка (по дням недели)?</label>
|
||||
<textarea name="working_hours" placeholder="Например: пн-пт 8:00-20:00, сб-вс 9:00-21:00"></textarea>
|
||||
<textarea name="working_hours_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О бронировании -->
|
||||
<section class="section">
|
||||
<h2>О бронировании</h2>
|
||||
|
||||
<div class="question" data-index="5">
|
||||
<label>5. Как вы хотите принимать оплату?</label>
|
||||
<textarea name="payment" placeholder="Например: онлайн картой, наличными на месте, выставлять счёт"></textarea>
|
||||
<textarea name="payment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="6">
|
||||
<label>6. Нужна ли возможность отмены бронирования? За сколько часов до начала?</label>
|
||||
<textarea name="cancellation" placeholder="Например: да, за 24 часа до начала / нет, отмена невозможна"></textarea>
|
||||
<textarea name="cancellation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="7">
|
||||
<label>7. Нужна ли регистрация пользователя или достаточно имя + email?</label>
|
||||
<textarea name="registration" placeholder="Например: достаточно имени и email / нужна полная регистрация с паролем"></textarea>
|
||||
<textarea name="registration_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О воротах -->
|
||||
<section class="section">
|
||||
<h2>О воротах</h2>
|
||||
|
||||
<div class="question" data-index="8">
|
||||
<label>8. Какое оборудование уже есть на воротах или нужно закупить?</label>
|
||||
<textarea name="gate_equipment" placeholder="Например: уже есть электронный замок марки X / нужно закупить всё с нуля"></textarea>
|
||||
<textarea name="gate_equipment_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="9">
|
||||
<label>9. PIN-код — кто его генерирует: вы вручную или система автоматически?</label>
|
||||
<textarea name="pin_generation" placeholder="Например: система должна генерировать автоматически после оплаты"></textarea>
|
||||
<textarea name="pin_generation_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О дизайне -->
|
||||
<section class="section">
|
||||
<h2>О дизайне и языке</h2>
|
||||
|
||||
<div class="question" data-index="10">
|
||||
<label>10. Есть ли логотип, фирменные цвета, название парка?</label>
|
||||
<textarea name="branding" placeholder="Например: название 'Koira Park', цвета зелёный и белый, логотип есть (могу прислать)"></textarea>
|
||||
<textarea name="branding_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="question" data-index="11">
|
||||
<label>11. На каком языке должен быть сайт/приложение?</label>
|
||||
<textarea name="language" placeholder="Например: только финский / финский + английский / финский + русский"></textarea>
|
||||
<textarea name="language_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- О сроках -->
|
||||
<section class="section">
|
||||
<h2>О сроках</h2>
|
||||
|
||||
<div class="question" data-index="12">
|
||||
<label>12. Когда нужен готовый продукт?</label>
|
||||
<textarea name="deadline" placeholder="Например: через 2 месяца / к лету 2026 / как можно скорее"></textarea>
|
||||
<textarea name="deadline_comment" class="comment" placeholder="Комментарий (необязательно)"></textarea>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Контактные данные -->
|
||||
<section class="section">
|
||||
<h2>Ваши контактные данные</h2>
|
||||
<div class="question">
|
||||
<label>Имя</label>
|
||||
<input type="text" name="client_name" required placeholder="Ваше имя">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label>Email</label>
|
||||
<input type="email" name="client_email" required placeholder="ваш@email.com">
|
||||
</div>
|
||||
<div class="question">
|
||||
<label>Телефон (необязательно)</label>
|
||||
<input type="tel" name="client_phone" placeholder="+358 XX XXX XXXX">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<button type="submit" id="submitBtn">Отправить ответы</button>
|
||||
<p class="submit-note">После отправки мы свяжемся с вами в течение 1-2 рабочих дней.</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@emailjs/browser@3/dist/email.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Проверить в браузере — форма отображается**
|
||||
|
||||
Открыть `index.html` в браузере (двойной клик по файлу).
|
||||
Ожидание: видны все 12 вопросов, поля для ввода работают.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add index.html
|
||||
git commit -m "feat: add questionnaire HTML structure with 12 questions"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: CSS стили
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\pro\Sunset Pass\style.css`
|
||||
|
||||
- [ ] **Step 1: Создать style.css**
|
||||
|
||||
```css
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f8f9fa;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px 80px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #2d6a4f;
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 32px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.section h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2d6a4f;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid #e8f5e9;
|
||||
}
|
||||
|
||||
.question {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.question:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
textarea, input[type="text"], input[type="email"], input[type="tel"] {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1.5px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
font-family: inherit;
|
||||
color: #1a1a1a;
|
||||
background: #fafafa;
|
||||
transition: border-color 0.2s;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
textarea:focus, input:focus {
|
||||
outline: none;
|
||||
border-color: #2d6a4f;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
textarea.comment {
|
||||
min-height: 50px;
|
||||
margin-top: 8px;
|
||||
font-size: 0.88rem;
|
||||
color: #555;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
textarea.comment::placeholder {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
background: #2d6a4f;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, transform 0.1s;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background: #1b4332;
|
||||
}
|
||||
|
||||
button[type="submit"]:active {
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
button[type="submit"]:disabled {
|
||||
background: #aaa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.submit-note {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #fff3f3;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 0.9rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: 24px 16px 60px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Проверить в браузере — форма выглядит аккуратно**
|
||||
|
||||
Обновить `index.html` в браузере.
|
||||
Ожидание: белые карточки с секциями, зелёный акцент, прогресс-бар вверху.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add style.css
|
||||
git commit -m "feat: add questionnaire styles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Страница успеха
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\pro\Sunset Pass\success.html`
|
||||
|
||||
- [ ] **Step 1: Создать success.html**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sunset Pass — Спасибо!</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.success-container {
|
||||
text-align: center;
|
||||
padding: 80px 20px;
|
||||
}
|
||||
.checkmark {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.success-container h2 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 16px;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.success-container p {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="success-container">
|
||||
<div class="checkmark">✅</div>
|
||||
<h2>Спасибо! Ответы получены.</h2>
|
||||
<p>Мы свяжемся с вами в течение 1-2 рабочих дней для обсуждения деталей проекта.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add success.html
|
||||
git commit -m "feat: add success page"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Настройка Airtable
|
||||
|
||||
- [ ] **Step 1: Создать аккаунт Airtable**
|
||||
|
||||
Зайти на airtable.com → Sign up (бесплатно).
|
||||
|
||||
- [ ] **Step 2: Создать новую Base**
|
||||
|
||||
Нажать "Add a base" → "Start from scratch" → назвать "Sunset Pass Questionnaire".
|
||||
|
||||
- [ ] **Step 3: Создать таблицу с полями**
|
||||
|
||||
Переименовать таблицу в "Responses". Добавить поля (тип: Long text для всех textarea, Single line text для имени/email/телефона):
|
||||
|
||||
| Имя поля | Тип |
|
||||
|---|---|
|
||||
| client_name | Single line text |
|
||||
| client_email | Email |
|
||||
| client_phone | Single line text |
|
||||
| park_size | Long text |
|
||||
| park_size_comment | Long text |
|
||||
| max_people | Long text |
|
||||
| max_people_comment | Long text |
|
||||
| slot_duration | Long text |
|
||||
| slot_duration_comment | Long text |
|
||||
| working_hours | Long text |
|
||||
| working_hours_comment | Long text |
|
||||
| payment | Long text |
|
||||
| payment_comment | Long text |
|
||||
| cancellation | Long text |
|
||||
| cancellation_comment | Long text |
|
||||
| registration | Long text |
|
||||
| registration_comment | Long text |
|
||||
| gate_equipment | Long text |
|
||||
| gate_equipment_comment | Long text |
|
||||
| pin_generation | Long text |
|
||||
| pin_generation_comment | Long text |
|
||||
| branding | Long text |
|
||||
| branding_comment | Long text |
|
||||
| language | Long text |
|
||||
| language_comment | Long text |
|
||||
| deadline | Long text |
|
||||
| deadline_comment | Long text |
|
||||
|
||||
- [ ] **Step 4: Получить API ключ и Base ID**
|
||||
|
||||
1. Зайти на airtable.com/create/tokens → "Create new token"
|
||||
2. Дать название "sunset-pass", scope: `data.records:write`, выбрать свою Base
|
||||
3. Скопировать токен (показывается только раз!) → сохранить
|
||||
|
||||
Base ID найти: открыть Base → Help → API documentation → в URL будет `appXXXXXXXXXXXXXX`
|
||||
|
||||
- [ ] **Step 5: Создать .env.example**
|
||||
|
||||
```bash
|
||||
# Airtable
|
||||
AIRTABLE_TOKEN=patXXXXXXXXXXXXXX
|
||||
AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX
|
||||
AIRTABLE_TABLE_NAME=Responses
|
||||
|
||||
# EmailJS
|
||||
EMAILJS_SERVICE_ID=service_XXXXXXX
|
||||
EMAILJS_TEMPLATE_ID=template_XXXXXXX
|
||||
EMAILJS_PUBLIC_KEY=XXXXXXXXXXXXXXXX
|
||||
```
|
||||
|
||||
```bash
|
||||
git add .env.example
|
||||
git commit -m "docs: add .env.example with required API keys"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Настройка EmailJS
|
||||
|
||||
- [ ] **Step 1: Создать аккаунт EmailJS**
|
||||
|
||||
Зайти на emailjs.com → Sign up (бесплатно, 200 писем/месяц).
|
||||
|
||||
- [ ] **Step 2: Добавить Email Service**
|
||||
|
||||
Dashboard → Email Services → Add New Service → выбрать Gmail → Connect Account → войти через `anton.semenov.ser@gmail.com`.
|
||||
Скопировать **Service ID** (вида `service_XXXXXXX`).
|
||||
|
||||
- [ ] **Step 3: Создать Email Template**
|
||||
|
||||
Dashboard → Email Templates → Create New Template.
|
||||
|
||||
Subject: `Новый ответ на опросник Sunset Pass от {{client_name}}`
|
||||
|
||||
Body:
|
||||
```
|
||||
Новый ответ на опросник Sunset Pass
|
||||
|
||||
Клиент: {{client_name}}
|
||||
Email: {{client_email}}
|
||||
Телефон: {{client_phone}}
|
||||
|
||||
--- О ПАРКЕ ---
|
||||
1. Размер парка: {{park_size}}
|
||||
Комментарий: {{park_size_comment}}
|
||||
|
||||
2. Макс. людей: {{max_people}}
|
||||
Комментарий: {{max_people_comment}}
|
||||
|
||||
3. Длительность слота: {{slot_duration}}
|
||||
Комментарий: {{slot_duration_comment}}
|
||||
|
||||
4. Часы работы: {{working_hours}}
|
||||
Комментарий: {{working_hours_comment}}
|
||||
|
||||
--- О БРОНИРОВАНИИ ---
|
||||
5. Оплата: {{payment}}
|
||||
Комментарий: {{payment_comment}}
|
||||
|
||||
6. Отмена: {{cancellation}}
|
||||
Комментарий: {{cancellation_comment}}
|
||||
|
||||
7. Регистрация: {{registration}}
|
||||
Комментарий: {{registration_comment}}
|
||||
|
||||
--- О ВОРОТАХ ---
|
||||
8. Оборудование: {{gate_equipment}}
|
||||
Комментарий: {{gate_equipment_comment}}
|
||||
|
||||
9. PIN генерация: {{pin_generation}}
|
||||
Комментарий: {{pin_generation_comment}}
|
||||
|
||||
--- О ДИЗАЙНЕ ---
|
||||
10. Брендинг: {{branding}}
|
||||
Комментарий: {{branding_comment}}
|
||||
|
||||
11. Язык: {{language}}
|
||||
Комментарий: {{language_comment}}
|
||||
|
||||
--- О СРОКАХ ---
|
||||
12. Дедлайн: {{deadline}}
|
||||
Комментарий: {{deadline_comment}}
|
||||
```
|
||||
|
||||
To email: `anton.semenov.ser@gmail.com`
|
||||
|
||||
Скопировать **Template ID** (вида `template_XXXXXXX`).
|
||||
|
||||
- [ ] **Step 4: Получить Public Key**
|
||||
|
||||
Dashboard → Account → General → скопировать **Public Key**.
|
||||
|
||||
---
|
||||
|
||||
### Task 6: JavaScript логика отправки
|
||||
|
||||
**Files:**
|
||||
- Create: `C:\pro\Sunset Pass\script.js`
|
||||
|
||||
- [ ] **Step 1: Создать script.js**
|
||||
|
||||
Заменить `YOUR_AIRTABLE_TOKEN`, `YOUR_BASE_ID`, `YOUR_EMAILJS_PUBLIC_KEY`, `YOUR_SERVICE_ID`, `YOUR_TEMPLATE_ID` на реальные значения из Task 4 и Task 5.
|
||||
|
||||
```javascript
|
||||
// Конфигурация — заменить на реальные ключи
|
||||
const CONFIG = {
|
||||
airtable: {
|
||||
token: 'YOUR_AIRTABLE_TOKEN',
|
||||
baseId: 'YOUR_BASE_ID',
|
||||
tableName: 'Responses'
|
||||
},
|
||||
emailjs: {
|
||||
publicKey: 'YOUR_EMAILJS_PUBLIC_KEY',
|
||||
serviceId: 'YOUR_SERVICE_ID',
|
||||
templateId: 'YOUR_TEMPLATE_ID'
|
||||
}
|
||||
};
|
||||
|
||||
// Инициализация EmailJS
|
||||
emailjs.init(CONFIG.emailjs.publicKey);
|
||||
|
||||
// Прогресс-бар: обновляется при фокусе на вопросах
|
||||
const questions = document.querySelectorAll('.question[data-index]');
|
||||
const progressFill = document.getElementById('progressFill');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const total = 12;
|
||||
|
||||
function updateProgress(index) {
|
||||
const pct = Math.round((index / total) * 100);
|
||||
progressFill.style.width = pct + '%';
|
||||
progressText.textContent = `Вопрос ${index} из ${total}`;
|
||||
}
|
||||
|
||||
questions.forEach(q => {
|
||||
const textarea = q.querySelector('textarea:not(.comment)');
|
||||
if (textarea) {
|
||||
textarea.addEventListener('focus', () => {
|
||||
updateProgress(parseInt(q.dataset.index));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Отправка формы
|
||||
document.getElementById('questionnaireForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const btn = document.getElementById('submitBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Отправляем...';
|
||||
|
||||
const form = e.target;
|
||||
const data = {};
|
||||
const inputs = form.querySelectorAll('textarea, input');
|
||||
inputs.forEach(input => {
|
||||
if (input.name) data[input.name] = input.value.trim();
|
||||
});
|
||||
|
||||
try {
|
||||
// 1. Сохранить в Airtable
|
||||
const airtableRes = await fetch(
|
||||
`https://api.airtable.com/v0/${CONFIG.airtable.baseId}/${encodeURIComponent(CONFIG.airtable.tableName)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${CONFIG.airtable.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ fields: data })
|
||||
}
|
||||
);
|
||||
|
||||
if (!airtableRes.ok) {
|
||||
const err = await airtableRes.json();
|
||||
throw new Error('Airtable: ' + (err.error?.message || airtableRes.status));
|
||||
}
|
||||
|
||||
// 2. Отправить email через EmailJS
|
||||
await emailjs.send(
|
||||
CONFIG.emailjs.serviceId,
|
||||
CONFIG.emailjs.templateId,
|
||||
data
|
||||
);
|
||||
|
||||
// 3. Перенаправить на страницу успеха
|
||||
window.location.href = 'success.html';
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Отправить ответы';
|
||||
alert('Ошибка при отправке: ' + err.message + '\n\nПопробуйте ещё раз или напишите нам напрямую.');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Вставить реальные ключи в CONFIG**
|
||||
|
||||
Открыть `script.js` и заменить:
|
||||
- `YOUR_AIRTABLE_TOKEN` → токен из Task 4 Step 4
|
||||
- `YOUR_BASE_ID` → Base ID из Task 4 Step 4
|
||||
- `YOUR_EMAILJS_PUBLIC_KEY` → Public Key из Task 5 Step 4
|
||||
- `YOUR_SERVICE_ID` → Service ID из Task 5 Step 2
|
||||
- `YOUR_TEMPLATE_ID` → Template ID из Task 5 Step 3
|
||||
|
||||
- [ ] **Step 3: Проверить в браузере — заполнить форму и отправить**
|
||||
|
||||
Открыть `index.html` → заполнить все поля → нажать "Отправить".
|
||||
Ожидание:
|
||||
- Редирект на `success.html`
|
||||
- В Airtable появилась новая запись
|
||||
- На `anton.semenov.ser@gmail.com` пришло письмо
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add script.js
|
||||
git commit -m "feat: add form submission logic (Airtable + EmailJS)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Деплой на Netlify
|
||||
|
||||
- [ ] **Step 1: Создать .gitignore**
|
||||
|
||||
```
|
||||
# Если будешь хранить реальные ключи в отдельном файле
|
||||
.env
|
||||
*.local
|
||||
```
|
||||
|
||||
```bash
|
||||
git add .gitignore
|
||||
git commit -m "chore: add gitignore"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Создать репозиторий на GitHub**
|
||||
|
||||
Зайти на github.com → New repository → назвать `sunset-pass-questionnaire` → Public → Create.
|
||||
|
||||
```bash
|
||||
git remote add origin https://github.com/ТВО_ИМЯ/sunset-pass-questionnaire.git
|
||||
git branch -M main
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Деплой на Netlify**
|
||||
|
||||
Зайти на netlify.com → Sign up → "Import from Git" → выбрать GitHub → выбрать репозиторий `sunset-pass-questionnaire` → Deploy site.
|
||||
|
||||
Через 1-2 минуты Netlify даст ссылку вида `https://random-name.netlify.app` — это и есть ссылка для клиента.
|
||||
|
||||
- [ ] **Step 4: Финальная проверка**
|
||||
|
||||
Открыть ссылку Netlify в браузере → заполнить форму → отправить.
|
||||
Ожидание: всё работает так же как локально.
|
||||
|
||||
- [ ] **Step 5: Commit финального состояния**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "chore: final state before sending to client"
|
||||
git push
|
||||
```
|
||||
Reference in New Issue
Block a user