Calendly удалён. Заменили за вечер вайб-кодингом с Claude.
Четвёртый пункт из поста про SaaS крупным планом: страница бронирования на этом сайте. Date picker, доступность из Google Calendar, ссылки на Meet, письма. И как на практике выглядел «вайб-кодинг».
Я писал, что отписываюсь от мелких SaaS и заменяю их кодом. Четвёртым пунктом был Calendly: «страница Next.js с date picker». Вот этот пункт, раскрытый.
Если ты когда-нибудь бронировал со мной звонок, ты этим пользовался: страница записаться на звонок на этом сайте. Никакого Calendly, никакого Cal.com, никакого iframe, который стучится домой. Один роут в том же репозитории, что и всё остальное. Тот самый, который я мельком упомянул в посте про SaaS.
Что на самом деле делал SaaS
Разбери Calendly на части. Он делает четыре вещи:
- Показывает посетителю календарь дней и свободные слоты.
- Знает, когда я занят, и не предлагает занятое время.
- Создаёт встречу в обоих календарях, со ссылкой на видеозвонок.
- Шлёт письмо-подтверждение, которое оба участника могут добавить в календарь.
Всё. Те самые 5%, которыми я пользовался. Логика маршрутизации, оплата, round-robin на команду, брендированные страницы: ничего из этого. Я написал ровно четыре вещи.
1. Страница и date picker
Один роут, /book: сетка месяца на react-day-picker, колонка слотов рядом, форма с именем и почтой, экран подтверждения.
Я описал Claude флоу обычным текстом. Он выдал компонент. Пара итераций: «выбранный день инверсией, не обводкой», «слоты плотнее». Всё встало как надо.
2. Доступность: конфиг плюс запрос free/busy
Вот что отличает это от игрушки. Какие слоты существуют решают два входа.
Первое: конфиг-файл. Мой: таймзона Europe/Moscow, рабочие часы 07:00–23:00, слоты по 30 минут, буфер 15 минут между звонками, 4 часа лид-тайма до самого раннего доступного слота, горизонт 30 дней, список праздников.
Второе: мой настоящий календарь. Сервер дёргает freebusy API Google Calendar на видимое окно, получает занятые интервалы и вычитает их из сетки по конфигу. Что осталось, то и предлагается.
freebusy здесь правильный выбор, не полный список событий. Он возвращает только занятые интервалы: без названий, без участников. Страница бронирования узнаёт «занято 14:00–15:00» и больше ничего.
3. Бронирование: сначала валидация, потом событие
Сабмит уходит в один API-роут. zod проверяет payload: настоящая почта, имя в пределах длины, строка слота, которая парсится. Мусор отсекается до того, как запустится что-либо ещё.
Потом роут перепроверяет free/busy (слот могли занять, пока форма была открыта), создаёт событие в моём Google Calendar через googleapis и даёт Google прицепить ссылку на Meet. Посетитель добавляется участником: встреча попадает и в его календарь.
4. Подтверждение: письмо с настоящим .ics
Письмо уходит через Resend. В нём нормальное вложение .ics, собранное вручную: «Добавить в календарь» работает в Apple Mail, Outlook, где угодно. Плюс ссылка на Google Calendar в один клик для всех остальных.
Вот что упускают, когда говорят, что сделать страницу бронирования просто. Страница: просто. А совместимость с календарями (таймзоны, поля .ics, ссылка на Meet, список праздников) раньше съедала весь вечер. С Claude ушло минут двадцать.
Одна занудная часть: Google OAuth
Чтобы читать и писать мой календарь, серверу нужен refresh-токен Google OAuth. Для этого надо зарегистрировать приложение в Google Cloud Console, настроить consent screen, выбрать scopes, прогнать одноразовый обмен кода на токен. Claude написал роут /api/oauth/start для обмена. Кликанье по консоли Google на тебе: её интерфейс меняется достаточно часто, чтобы ни одна модель не помнила его точно.
Заложи полчаса занудства. Как только refresh-токен в переменных окружения, ты к нему больше не возвращаешься.
Что на самом деле значил «вайб-кодинг»
Не «Claude написал приложение, а я смотрел». Скорее так:
- Я знал четыре вещи, которые система должна делать. Конфиг (буферы, лид-тайм, праздники) я продумал до первого промпта. Это мышление не аутсорсится.
- Claude написал по сути весь код: математику слотов, слияние free/busy, сборщик .ics, React-компонент, шаблоны писем.
- Я читал каждый дифф. Баг с таймзоной (слоты уезжали на час у границы перехода на летнее время) я поймал чтением, не запуском.
- Продуктовые решения принимал я. Должен ли удержанный слот протухать? Класть ли в письмо ссылку на перенос? Это моё.
Модель: быстрый, начитанный напарник, который собрал сотню страниц бронирования. Не продакт-оунер.
SaaS продавал мне скучные 95%. Claude теперь делает их бесплатно, и я плачу только за те 5%, которые всегда были моими.
Стоило ли оно того
Командный тариф Calendly: $15–30 в месяц. Страница заняла вечер: часа три, большую часть из которых съели занудство с OAuth и доводка экрана подтверждения. Аргумент «сделай за вечер». Буквально.
Окупается за пару месяцев. Настоящий выигрыш в другом. Флоу бронирования теперь четыре маленьких файла, которые я могу прочитать, в репозитории, который я уже деплою, с данными в моём собственном календаре. Когда хочу изменить поведение, меняю код, а не ищу нужный тумблер в чужой панели настроек.
Доказательство живое. Запишись на звонок на той самой странице, о которой весь этот пост.