feat: add Записаться button to group cards with pre-filled Instagram DM

- BookingModal now accepts optional groupInfo for pre-filled message
- Trainer profile: each group card has Записаться button
- Schedule group view: each group card has Записаться button
- Message includes class type, trainer, days, and time

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 16:45:27 +03:00
parent 4f92057411
commit 6981376171
4 changed files with 37 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useState, useMemo, useCallback } from "react"; import { useState, useMemo, useCallback } from "react";
import { BookingModal } from "@/components/ui/BookingModal";
import { CalendarDays, Users, LayoutGrid } from "lucide-react"; import { CalendarDays, Users, LayoutGrid } from "lucide-react";
import { SectionHeading } from "@/components/ui/SectionHeading"; import { SectionHeading } from "@/components/ui/SectionHeading";
import { Reveal } from "@/components/ui/Reveal"; import { Reveal } from "@/components/ui/Reveal";
@@ -28,6 +29,7 @@ export function Schedule({ data: schedule, classItems }: ScheduleProps) {
const [filterStatus, setFilterStatus] = useState<StatusFilter>("all"); const [filterStatus, setFilterStatus] = useState<StatusFilter>("all");
const [filterTime, setFilterTime] = useState<TimeFilter>("all"); const [filterTime, setFilterTime] = useState<TimeFilter>("all");
const [filterDaySet, setFilterDaySet] = useState<Set<string>>(new Set()); const [filterDaySet, setFilterDaySet] = useState<Set<string>>(new Set());
const [bookingGroup, setBookingGroup] = useState<string | null>(null);
const isAllMode = locationMode === "all"; const isAllMode = locationMode === "all";
@@ -329,9 +331,15 @@ export function Schedule({ data: schedule, classItems }: ScheduleProps) {
filterTrainer={filterTrainer} filterTrainer={filterTrainer}
setFilterTrainer={setFilterTrainerFromCard} setFilterTrainer={setFilterTrainerFromCard}
showLocation={isAllMode} showLocation={isAllMode}
onBook={setBookingGroup}
/> />
</Reveal> </Reveal>
)} )}
<BookingModal
open={bookingGroup !== null}
onClose={() => setBookingGroup(null)}
groupInfo={bookingGroup ?? undefined}
/>
</section> </section>
); );
} }

View File

@@ -55,6 +55,7 @@ interface GroupViewProps {
filterTrainer: string | null; filterTrainer: string | null;
setFilterTrainer: (trainer: string | null) => void; setFilterTrainer: (trainer: string | null) => void;
showLocation?: boolean; showLocation?: boolean;
onBook?: (groupInfo: string) => void;
} }
export function GroupView({ export function GroupView({
@@ -65,6 +66,7 @@ export function GroupView({
filterTrainer, filterTrainer,
setFilterTrainer, setFilterTrainer,
showLocation, showLocation,
onBook,
}: GroupViewProps) { }: GroupViewProps) {
const groups = buildGroups(filteredDays); const groups = buildGroups(filteredDays);
@@ -177,6 +179,14 @@ export function GroupView({
</div> </div>
))} ))}
</div> </div>
{onBook && (
<button
onClick={() => onBook(`${group.type}, ${group.trainer}, ${group.slots.map(s => s.dayShort).join("/")} ${group.slots[0]?.time ?? ""}`)}
className="w-full mt-3 rounded-xl bg-gold/15 border border-gold/25 py-2 text-xs font-semibold text-gold-dark dark:text-gold hover:bg-gold/25 transition-colors cursor-pointer"
>
Записаться
</button>
)}
</div> </div>
</div> </div>
); );

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
import Image from "next/image"; import Image from "next/image";
import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Award, Scale, Clock, MapPin } from "lucide-react"; import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Award, Scale, Clock, MapPin } from "lucide-react";
import type { TeamMember, RichListItem, VictoryItem, ScheduleLocation } from "@/types/content"; import type { TeamMember, RichListItem, VictoryItem, ScheduleLocation } from "@/types/content";
import { BookingModal } from "@/components/ui/BookingModal";
interface TeamProfileProps { interface TeamProfileProps {
member: TeamMember; member: TeamMember;
@@ -11,6 +12,7 @@ interface TeamProfileProps {
export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) { export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
const [lightbox, setLightbox] = useState<string | null>(null); const [lightbox, setLightbox] = useState<string | null>(null);
const [bookingGroup, setBookingGroup] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
@@ -177,7 +179,7 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
</span> </span>
<ScrollRow> <ScrollRow>
{uniqueGroups.map((g, i) => ( {uniqueGroups.map((g, i) => (
<div key={i} className="w-44 shrink-0 rounded-xl border border-white/[0.08] bg-white/[0.03] p-3 space-y-1.5"> <div key={i} className="w-48 shrink-0 rounded-xl border border-white/[0.08] bg-white/[0.03] p-3 space-y-1.5">
<p className="text-xs font-semibold uppercase tracking-wider text-white/80">{g.type}</p> <p className="text-xs font-semibold uppercase tracking-wider text-white/80">{g.type}</p>
<div className="flex items-center gap-1.5 text-xs text-white/50"> <div className="flex items-center gap-1.5 text-xs text-white/50">
<Clock size={11} /> <Clock size={11} />
@@ -195,6 +197,12 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
Набор открыт Набор открыт
</span> </span>
)} )}
<button
onClick={() => setBookingGroup(`${g.type}, ${g.days.join("/")} ${g.time}`)}
className="w-full mt-1 rounded-lg bg-gold/15 border border-gold/25 py-1.5 text-[11px] font-semibold text-gold hover:bg-gold/25 transition-colors cursor-pointer"
>
Записаться
</button>
</div> </div>
))} ))}
</ScrollRow> </ScrollRow>
@@ -274,6 +282,12 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
</div> </div>
</div> </div>
)} )}
<BookingModal
open={bookingGroup !== null}
onClose={() => setBookingGroup(null)}
groupInfo={bookingGroup ?? undefined}
/>
</div> </div>
); );
} }

View File

@@ -8,9 +8,10 @@ import { siteContent } from "@/data/content";
interface BookingModalProps { interface BookingModalProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
groupInfo?: string;
} }
export function BookingModal({ open, onClose }: BookingModalProps) { export function BookingModal({ open, onClose, groupInfo }: BookingModalProps) {
const { contact } = siteContent; const { contact } = siteContent;
const [name, setName] = useState(""); const [name, setName] = useState("");
const [phone, setPhone] = useState("+375 "); const [phone, setPhone] = useState("+375 ");
@@ -65,7 +66,8 @@ export function BookingModal({ open, onClose }: BookingModalProps) {
(e: React.FormEvent) => { (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
// Build Instagram DM message with pre-filled text // Build Instagram DM message with pre-filled text
const message = `Здравствуйте! Меня зовут ${name}, хочу записаться на занятие. Мой телефон: ${phone}`; const groupText = groupInfo ? ` (${groupInfo})` : "";
const message = `Здравствуйте! Меня зовут ${name}, хочу записаться на занятие${groupText}. Мой телефон: ${phone}`;
const instagramUrl = `https://ig.me/m/blackheartdancehouse?text=${encodeURIComponent(message)}`; const instagramUrl = `https://ig.me/m/blackheartdancehouse?text=${encodeURIComponent(message)}`;
window.open(instagramUrl, "_blank"); window.open(instagramUrl, "_blank");
setSubmitted(true); setSubmitted(true);