Commit graph

78 commits

Author SHA1 Message Date
17a90e5821 TinaCMS content update
Some checks failed
Deploy to Server / deploy (push) Has been cancelled
2026-03-19 12:25:27 +00:00
d9743818b3 TinaCMS content update 2026-03-19 12:18:16 +00:00
Vadym Samoilenko
15eb481b35 Update TinaCMS to v3.6.3 and @tinacms/cli to v2.1.11
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 12:12:01 +00:00
Vadym Samoilenko
464f91370d Fix TinaCloud schema: add pages collection to tina-lock.json
The pages collection was added to tina/config.ts but tina-lock.json was
not regenerated — TinaCloud indexed the old 3-collection schema, causing
GraphQL Schema Mismatch and GetCollection failed errors in the admin.

Rebuilt tina-lock.json from _schema.json + _lookup.json + _graphql.json
which already contained the correct 4-collection schema including pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 11:58:22 +00:00
Vadym Samoilenko
b0de69b1d9 Fix TinaCloud schema sync: force re-index before build
The root cause of the schema mismatch was skipIfSchemaCurrent=true in
TinaCMS's syncProject() call, which caused TinaCloud to skip re-indexing
when it thought the schema SHA hadn't changed. This left TinaCloud with
a stale GraphQL schema missing the pages collection (PagesSeo type).

Fix: add an explicit curl step before the build that calls TinaCloud's
reset endpoint WITHOUT skipIfSchemaCurrent, forcing a full re-index.
Keep --skip-cloud-checks in the build command to avoid the circular
dependency (build failing because TinaCloud is still indexing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 23:01:15 +00:00
Vadym Samoilenko
9146ebfbf0 Remove --skip-cloud-checks to fix TinaCloud schema sync
--skip-cloud-checks was also skipping syncProject() which is the API call
that tells TinaCloud to refresh its schema. Without it, TinaCloud never
indexed the pages collection, causing 'Expected to find collection named pages'
errors in the admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:48:57 +00:00
Vadym Samoilenko
16fcf793d7 Fix TypeScript unused import errors blocking CI build
Remove unused 'motion' import from BlockDivider and unused 'PageBlock' type from DynamicPage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:41:52 +00:00
Vadym Samoilenko
d1ff9c7ad1 Fix TinaCloud CI: add --skip-cloud-checks to tinacms build
TinaCloud schema validation creates circular dependency in CI when
new types are added. Schema files are committed to git, so TinaCloud
re-indexes via GitHub webhook independently. Skip the build-time check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:38:12 +00:00
Vadym Samoilenko
d5da487b4b Retry: wait for TinaCloud schema re-indexing 2026-03-18 22:36:45 +00:00
Vadym Samoilenko
c3bcf83212 Regenerate TinaCMS schema for pages collection
Updates _graphql.json, _schema.json, _lookup.json, config.prebuild.jsx
to include new Pages collection types. Required for TinaCloud re-indexing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:34:04 +00:00
Vadym Samoilenko
6cd63f1bdf Add Page Builder: 15 block types + publish/unpublish via TinaCMS
- New TinaCMS collection 'pages' with 15 block templates:
  Hero, TextBlock, TwoColumn, Features, Stats, Testimonials,
  Team, FAQ, CTABanner, Video, Gallery, Pricing, Timeline,
  Divider, ContactForm
- Each page has published toggle (unpublished → 404)
- Route /p/:slug renders dynamic pages from content/pages/*.json
- scripts/copy-pages.mjs copies content/pages → public/pages at build
- prerender.mjs extended to prerender published pages
- All blocks styled with design tokens + Framer Motion animations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:29:51 +00:00
Vadym Samoilenko
1f1c7508b4 Deploy nginx.conf to server in CI/CD pipeline
server/nginx.conf was never synced to the server — CI/CD only deployed
dist/. Add rsync step to copy nginx.conf before container restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:08:39 +00:00
Vadym Samoilenko
7e67369600 Fix CSP violations: allow PostHog, TinaCMS visual editor framing
- script-src: add us-assets.i.posthog.com (TinaCMS bundles PostHog)
- connect-src: add us.i.posthog.com, us-assets.i.posthog.com
- frame-src: add ai-impress.com (TinaCMS visual editor iframes the site)
- font-src: add data: (TinaCMS font loading)
- X-Frame-Options: DENY → SAMEORIGIN (required for TinaCMS admin preview)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:03:33 +00:00
Vadym Samoilenko
f5423d202c Increase gap between rotating text ring and sphere in Banner2
Text was overlapping the globe. Scale the SVG text ring to 1.45x on
desktop and 1.6x on mobile so the text orbit sits clearly outside
the sphere boundary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:51:57 +00:00
Vadym Samoilenko
bffaa1663d Update package-lock.json with puppeteer and mime-types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:44:30 +00:00
Vadym Samoilenko
0ffb58ca74 Add TinaCMS domains to CSP connect-src
content.tinajs.io and *.tinajs.io were blocked by CSP, preventing
the visual editor from connecting to TinaCloud.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:43:03 +00:00
Vadym Samoilenko
f7a010fc93 Fix nginx /blog/ 403: use try_files \$uri /index.html without \$uri/
When \$uri ends with / and the directory exists (dist/blog/), nginx
considers it "found" and returns 403 (no autoindex, no index.html).
Removing \$uri/ ensures SPA fallback always works for route paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:42:41 +00:00
Vadym Samoilenko
4fc85ef99e Enable SPA prerendering in build pipeline
- Add puppeteer + mime-types to devDependencies
- Integrate prerender.mjs into build script (runs after vite build)
- Add Linux/Docker-safe Chrome flags (--disable-setuid-sandbox, --disable-dev-shm-usage, --disable-gpu)
- Fix static server to strip query strings from URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:39:01 +00:00
Vadym Samoilenko
272839d7a9 Add post-build SPA prerendering script with Puppeteer
Manual script (node scripts/prerender.mjs) that starts a local static
server, visits each route with Puppeteer, waits 3s for React to render,
and saves full HTML to dist/. Covers all static routes + blog posts.

Run after vite build when prerendering is needed. Requires puppeteer
installed (npm i -D puppeteer mime-types).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:37:34 +00:00
Vadym Samoilenko
b6a6a55e05 Generate dynamic sitemap with blog posts and lastmod dates
New script reads public/blog/posts.json at build time and outputs
public/sitemap.xml with all static routes + /blog/:slug entries,
each with lastmod from post dates or current date.

Build pipeline: sync-blog → generate-sitemap → tsc → vite build

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:37:29 +00:00
Vadym Samoilenko
d42ab963bc Add hreflang tags for en/uk language switching
Single URL for both languages is correct for client-side i18n — signals
to search engines that both language versions exist at the same URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:37:25 +00:00
Vadym Samoilenko
a3040d9852 Add JSON-LD schemas: FAQ, HowTo, BlogPosting, Person, AggregateRating
- PricingPage: FAQPage schema from existing FAQ array
- HomePage: HowTo schema with 5-step process (Challenge Briefing → Scaling)
- BlogPostPage: BlogPosting schema with headline, dates, author, publisher
- AboutPage: Person schema for Vadym Samoilenko (CEO & Founder)
- index.html: LocalBusiness + AggregateRating schema (5.0/5, 5 reviews)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:37:21 +00:00
Vadym Samoilenko
2fb3fce608 Fix blog 403: add try_files to /blog/ nginx location
Nginx was intercepting /blog/ before the SPA fallback, causing 403 when
it found the blog/ directory without autoindex. Adding try_files ensures
SPA routing handles all /blog/* paths correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 21:37:15 +00:00
Vadym Samoilenko
2ab22e5efb Fix cookie banner disappearing when TinaCloud data loads
Refactored LanguageProvider to avoid remounting children when switching
from static to live translations. Previously, the switch from
LanguageContext.Provider to TinaConnectedProvider caused all children
to unmount/remount, resetting CookieConsent state and hiding the banner
if cookie_consent was already set in localStorage.

New approach: TinaLiveSync is a null-rendering component that calls useTina
and syncs live data back to LanguageProvider via a stable callback, while
LanguageContext.Provider remains the stable root wrapper — children never remount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:36:18 +00:00
Vadym Samoilenko
09943e9f5e Re-enable visual editor preview for Site Content collections
Add router: () => '/' back to translationsEn and translationsUk collections
so TinaCloud shows the homepage preview alongside the editing form.
The EOF error that previously blocked this is fixed (TinaConnectedProvider
defers useTina calls until both queries are loaded).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:31:12 +00:00
Vadym Samoilenko
5d0aaab339 Migrate 5 server blog posts to TinaCMS-managed content/blog/
Converts existing server-side JSON blog posts to Markdown format with
YAML frontmatter so they appear in TinaCloud admin and are managed via git.
Also fixes sync-blog.mjs parseFrontmatter to support multi-line YAML lists
(TinaCMS writes hashtags as multi-line list items).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:28:11 +00:00
Vadym Samoilenko
8d5b499c16 Set mediaRoot to public root so all site assets show in Media Manager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:19:33 +00:00
Vadym Samoilenko
5fa337d97a Preserve server blog content: exclude blog/ from rsync --delete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:14:39 +00:00
Vadym Samoilenko
f7f1376568 Add search, design tokens with color picker, beforeSubmit for blog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:07:40 +00:00
Vadym Samoilenko
3958080de8 Remove router from global translation collections (fixes EOF GraphQL error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:04:08 +00:00
Vadym Samoilenko
af2f8a08ac Add public/uploads directory for TinaCloud media storage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:02:41 +00:00
Vadym Samoilenko
9484ce0587 Fix useTina empty query error: defer TinaCloud connection until data loaded
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:02:10 +00:00
356338e3de TinaCMS content update 2026-03-12 22:01:24 +00:00
Vadym Samoilenko
ee4deec1f7 Regenerate tina-lock.json after config changes (global, router)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:53:09 +00:00
Vadym Samoilenko
f53b64760a Add visual editing via useTina + configure tina collections for SPA
Configured TinaCloud for visual editing of translation files with useTina hook integration. Updated .gitignore to exclude generated runtime files while preserving schema files needed by TinaCloud for branch indexing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:42:05 +00:00
Vadym Samoilenko
1cca59dbdd Add visual editing via useTina + configure tina collections for SPA
- Integrate useTina hook for live translation editing in LanguageContext
- Configure translationsEn/translationsUk with global=true and router
- Add blog post router and auto-slugify filename generator

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:40:15 +00:00
Vadym Samoilenko
0fa1a9ab9a Update TinaCloud project: new client ID, add tina-lock.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 21:06:57 +00:00
Vadym Samoilenko
2f3e4d3dd8 Add tina/__generated__ for TinaCloud branch indexing
TinaCloud requires _schema.json and _graphql.json to index branches.
Generated by running tinacms dev locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:50:38 +00:00
Vadym Samoilenko
fdf598422c Fix CI: remove TINA_TOKEN from build to skip cloud branch check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:40:18 +00:00
Vadym Samoilenko
985d21b53d Add TinaCMS Cloud integration (Phase 1)
- Add tina/config.ts with full schema for all site sections
- Convert i18n from TypeScript to nested JSON (content/translations/)
- Update LanguageContext to import JSON with flattenObject utility
- Update dev/build scripts to run tinacms build
- Add sync-blog.mjs support for content/blog/*.md (TinaCMS posts)
- Update CI/CD with Tina env vars, remove blog rsync exclusion
- Add tina/__generated__/ to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 20:34:35 +00:00
Vadym Samoilenko
de292da095 Reduce Banner2 rotating text circle size further
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:43:04 +00:00
Vadym Samoilenko
01eef07e90 Fix Banner2 rotating text circle overflowing frame
Reduced .banner2-text-overlay width at all breakpoints to keep the
circular "Get Your Free Consultation" text within the banner bounds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:41:26 +00:00
Vadym Samoilenko
6e932d76e4 Add multi-language support (EN/UK) across entire site
Custom i18n system with typed translation dictionaries (~570 keys),
LanguageProvider context, and useTranslation hook. All 31 components
and pages wired with t() calls. Chatbot backend passes language hint
to Claude for Ukrainian responses. Language preference persists via
localStorage. SEO meta tags and html lang attribute update dynamically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:32:04 +00:00
Vadym Samoilenko
bc80f7667c Use bodyV2 markdown for CRM notes and transcripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:45:31 +00:00
Vadym Samoilenko
93288aa981 Fix CRM enrichment: correct API filters, note linking, add tasks & transcripts
- Fix Twenty CRM filter syntax (dot notation for composite fields)
- Fix noteTargets/taskTargets to use targetPerson instead of personId
- Handle duplicate person creation gracefully (find existing on 400)
- Add task creation for new leads (follow-up TODO)
- Save conversation transcript to CRM on escalation and rate limit
- Strengthen system prompt to make Claude call update_lead proactively
- Tell Claude that form-submitted leads are already in CRM

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:42:56 +00:00
Vadym Samoilenko
43b95c84df Add markdown rendering in chat widget + opportunities for new leads
- Chat messages now render bold, italic, links, and line breaks
- Bare URLs auto-linked, XSS-safe via HTML escaping before markdown
- New leads create Opportunity with stage NEW in Twenty CRM
- Applied to both chatbot-api and email-api (contact + quote forms)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:30:49 +00:00
Vadym Samoilenko
99e6c37827 Add sales escalation to chatbot + CRM integration for all forms
- Bot now acts as sales consultant: identifies needs, proposes services, pushes for booking
- escalate_to_human tool: triggers on user request, bot stuck, or hot lead
- Escalation notifies RC with reason + conversation summary
- Contact form and quote form now create leads in Twenty CRM
- Fix RC webhook to use correct payload format (visitor.token as session_id)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:22:11 +00:00
Vadym Samoilenko
8e73d77abc Fix chatbot: lead persistence, CRM creation, RC delivery, mobile UI
- Store lead info in Redis session meta so bot remembers name across messages
- Create Twenty CRM lead immediately from form data (bypass tool-call flow)
- Store room→session reverse mapping in Redis for RC webhook delivery
- Update system prompt: don't re-ask for form-provided info
- Fix "Most Popular" badge clipped on mobile (overflow: visible)
- Fix contact form inputs overflowing on small screens (box-sizing)
- Reduce chat tooltip size on mobile to avoid overlapping content

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:18:29 +00:00
Vadym Samoilenko
e78d6dc1c6 Add Twenty CRM integration + lead enrichment + pulsating chat bubble
- New twenty_crm.py: full CRUD for people, companies, notes via Twenty REST API
- Lead capture now creates person + company in Twenty CRM automatically
- New update_lead tool: enriches CRM profile as conversation progresses
  (job title, phone, city, budget, requirements)
- Session meta stored in Redis to track Twenty person ID across messages
- Docker-compose updated with TWENTY_CRM env vars
- Chat bubble: pulsating ring animation with gradient background

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:13:26 +00:00
Vadym Samoilenko
a8e8d8a71b Add lead collection form before chat + fix RC bot message delivery
- New ChatLeadForm component: collects name, email, company before chat starts
- GDPR consent checkbox with Privacy Policy link
- Lead info passed to backend and injected as LLM context
- Visitor name from form used in Rocket.Chat room
- RC bot messages: added logging + fallback to livechat/message endpoint
- RC room caching to avoid repeated API calls

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:02:08 +00:00