Complete admin panel for content management: - SQLite database with better-sqlite3, seed script from content.ts - Simple password auth with HMAC-signed cookies (Edge + Node compatible) - 9 section editors: meta, hero, about, team, classes, schedule, pricing, FAQ, contact - Team CRUD with image upload and drag reorder - Schedule editor with Google Calendar-style visual timeline (colored blocks, overlap detection, click-to-add) - All public components refactored to accept data props from DB (with fallback to static content) - Middleware protecting /admin/* and /api/admin/* routes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
import { MapPin, Phone, Clock, Instagram } from "lucide-react";
|
|
import { BRAND } from "@/lib/constants";
|
|
import { SectionHeading } from "@/components/ui/SectionHeading";
|
|
import { Reveal } from "@/components/ui/Reveal";
|
|
import { IconBadge } from "@/components/ui/IconBadge";
|
|
import type { ContactInfo } from "@/types/content";
|
|
|
|
interface ContactProps {
|
|
data: ContactInfo;
|
|
}
|
|
|
|
export function Contact({ data: contact }: ContactProps) {
|
|
|
|
return (
|
|
<section id="contact" className="relative section-padding bg-neutral-50 dark:bg-[#050505]">
|
|
<div className="section-divider absolute top-0 left-0 right-0" />
|
|
<div className="section-container grid items-center gap-12 lg:grid-cols-2">
|
|
<Reveal>
|
|
<SectionHeading>{contact.title}</SectionHeading>
|
|
|
|
<div className="mt-10 space-y-5">
|
|
{contact.addresses.map((address, i) => (
|
|
<div key={i} className="group flex items-center gap-4">
|
|
<IconBadge><MapPin size={18} /></IconBadge>
|
|
<p className="body-text">{address}</p>
|
|
</div>
|
|
))}
|
|
|
|
<div className="group flex items-center gap-4">
|
|
<IconBadge><Phone size={18} /></IconBadge>
|
|
<a
|
|
href={`tel:${contact.phone}`}
|
|
className="text-neutral-600 transition-colors hover:text-gold-dark dark:text-neutral-300 dark:hover:text-gold-light"
|
|
>
|
|
{contact.phone}
|
|
</a>
|
|
</div>
|
|
|
|
<div className="group flex items-center gap-4">
|
|
<IconBadge><Clock size={18} /></IconBadge>
|
|
<p className="body-text">{contact.workingHours}</p>
|
|
</div>
|
|
|
|
<div className="border-t border-neutral-200 pt-5 dark:border-white/[0.08]">
|
|
<div className="group flex items-center gap-4">
|
|
<IconBadge><Instagram size={18} /></IconBadge>
|
|
<a
|
|
href={contact.instagram}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-neutral-600 transition-colors hover:text-gold-dark dark:text-neutral-300 dark:hover:text-gold-light"
|
|
>
|
|
{BRAND.instagramHandle}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Reveal>
|
|
|
|
<Reveal>
|
|
<div className="overflow-hidden rounded-2xl border border-neutral-200 shadow-sm dark:border-white/[0.08] dark:shadow-[0_0_30px_rgba(201,169,110,0.05)]">
|
|
<iframe
|
|
src={contact.mapEmbedUrl}
|
|
width="100%"
|
|
height="380"
|
|
style={{ border: 0 }}
|
|
allowFullScreen
|
|
loading="lazy"
|
|
title="Карта"
|
|
className="dark:invert dark:hue-rotate-180 dark:brightness-95 dark:contrast-90"
|
|
/>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|