Skip to content

feat(courses): other courses under every course and post, footer advert, utm on shares, sign-in after Telegram - #1634

Merged
gHashTag merged 1 commit into
mainfrom
feat/courses-cross-sell-utm
Oct 13, 2026
Merged

gHashTag merged 1 commit into
mainfrom
feat/courses-cross-sell-utm

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

What

Owner, 2026-10-13: other courses at the foot of every course page, a course advert in the footer and on every blog post, utm tags to track traffic, a way to sign in on the site after joining the club in Telegram, and three courses free with no sign-up.

  • More courses shelf under every course page and every blog post (lazy chunk)
  • Footer advert linking to the catalog
  • ?from=<placement> on internal links (not utm: an internal utm overwrites the real channel); placement + arrival source go with the checkout order
  • utm_source/medium/campaign/content on all share links; static course pages forward arrival utm
  • Catalog takes ?unlock=<key> from the bot; Sign in via Telegram links
  • Words: three courses free, no sign-up

Bot side (deeplink after paying): gHashTag/999-multibots-telegraf, separate PR.

{
  "version": 1,
  "head_sha": "bcf926f4ebf36f1d4c71cbce2ef519e9a4d7d748",
  "summary": "Sell courses where readers already are: a More courses shelf under every course page and blog post, a footer advert, utm tags on share links, placements carried to the order, and sign-in on the site for readers who joined the club in Telegram. The words now say plainly that three courses are free with no sign-up.",
  "changes": [
    "src/components/CourseShelf.tsx and src/lib/useLadderView.ts: course cards and a More courses shelf, unopened courses first",
    "src/pages/Course.tsx and src/pages/Blog.tsx: the shelf at the foot of every course page and (lazy-loaded) every blog post",
    "src/components/Footer.tsx: a course advert in the footer, off on course pages",
    "src/lib/courseLadder.ts: ?from=<placement> on internal links; first placement and arrival utm_source sent with the checkout order",
    "src/lib/shareTags.ts: utm tags on course and blog share links; scripts/course-pages.mjs forwards arrival utm_* into the app link",
    "src/pages/Courses.tsx: the catalog accepts ?unlock=<key> (sign-in after joining in the bot) and links to sign in via Telegram",
    "specs/ladder/course-ladder.t27 + i18n/course-ladder.ru.json: six new words, and no sign-up said plainly"
  ],
  "tests": [
    {
      "command": "npm run check:course-ladder && npm run test:course-ladder",
      "result": "49 words in 2 languages, up to date; tests pass",
      "status": "passed",
      "evidence": "local run of both npm scripts in apps/website on head bcf926f4e"
    },
    {
      "command": "npm run check:course && npm run test:course && npm run check:course-pages",
      "result": "pass; 504 static course pages regenerated",
      "status": "passed",
      "evidence": "local run of the three npm scripts in apps/website on head bcf926f4e"
    },
    {
      "command": "npx vite build",
      "result": "built in 14.47s; CourseShelf is its own lazy chunk",
      "status": "passed",
      "evidence": "local vite build in apps/website; dist written with no errors"
    },
    {
      "command": "browser on vite dev: clean localStorage, open #/courses then a course",
      "result": "course opens with one press, no key and no email; shelf shown; from=catalog kept in sessionStorage",
      "status": "passed",
      "evidence": "BrowserOS neo on the vite dev server, localStorage cleared before the check"
    }
  ],
  "limitations": [
    "Static blog pages at t27.ai/blog/<slug>/ are generated outside this repo and load no app JS; their share utm counts only once a reader reaches the app.",
    "The on-site rouble checkout needs gHashTag/999-multibots-telegraf#4001 deployed with Robokassa env set; until then the gate keeps the bot button."
  ],
  "tags": [
    "courses",
    "sales",
    "utm",
    "ux"
  ],
  "blog": {
    "title": "Selling courses where readers already are",
    "summary": "Every course page and blog post now ends with other courses, the footer carries an advert, shares are utm-tagged, and a club member signs in on the site with one bot button.",
    "outline": [
      "A reader who finishes a lesson used to reach the end of the page and find nothing next, so the foot of every course page now shows three other courses, unopened first.",
      "Blog posts bring most of the traffic but sold nothing, so every post now ends with the same shelf, loaded lazily so the blog stays fast for readers.",
      "Internal links carry a from placement instead of utm tags, because an internal utm tag would overwrite the channel the reader really arrived from.",
      "The first placement of a session and the arrival source ride on to the checkout order, so each payment names the shelf that sold it and the channel.",
      "A member who paid for the club in Telegram had no way back to the site; the catalog now accepts the bot key and a sign in via Telegram link sits on it."
    ]
  }
}

🤖 Generated with Claude Code

…rt, utm on shares, sign-in after joining in Telegram

- More courses shelf at the foot of every course page and (lazy) every blog post;
  a course advert in the site footer.
- Internal shelf links carry ?from=<placement>; the first placement of a session and
  the arrival utm_source ride on to the checkout order.
- Share links (course + blog) carry utm_source/medium/campaign/content; static course
  pages forward an arrival's utm_* into the app link.
- The catalog accepts ?unlock=<key>, so a reader who joined the club in the bot signs in
  on the site; a "Sign in via Telegram" link on the catalog and on the paid gate.
- The words say it plainly: three courses free with no sign-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gHashTag gHashTag added the owner-approved-foreign Owner approved hand-written non-t27 code in this PR label Oct 13, 2026
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 13, 2026
@tri-claw

tri-claw Bot commented Oct 13, 2026

Copy link
Copy Markdown

Summary by t27-bees

This PR adds a "More courses" shelf under course pages and blog posts, a footer advert, UTM tracking on share links, placement tracking (?from=) carried to checkout, and a sign-in flow for Telegram club members. Static course pages now forward arrival UTM params to the app.

Worth a look

  • apps/website/public/learn/*.html (90+ files): Each static course page now injects a script that rewrites all /#/ links to include the full query string when utm_source is present. This runs on every page load and mutates href attributes in-place. If a page has many internal links, this could cause a visible flicker or race with other scripts. The selector a[href^="/#/"] is broad—any link matching that pattern gets rewritten, including ones added dynamically after this script runs (they won't be rewritten).

  • apps/website/public/learn/*.html: The script prepends the query string with a leading / ('/' + q + ...). If the original href is /#/course/slug, the result becomes /?utm_source=.../#/course/slug—a valid hash route but an unusual URL shape. Verify the router handles /?utm_source=.../#/... correctly (it should, since the hash is preserved), but it's worth a quick smoke test.

  • apps/website/i18n/course-ladder.ru.json: New keys SAY_MORE_TITLE, SAY_PROMO_TITLE, SAY_PROMO_LINE, SAY_PROMO_PRESS, SAY_SIGN_IN, SAY_SIGNED_IN are added only in Russian. The English source file (course-ladder.t27 or equivalent) isn't shown in the diff—if those keys are missing there, the i18n check will fail or the English UI will show raw keys.

  • apps/website/src/lib/courseLadder.ts (not shown but referenced): The description says ?from=<placement> is added to internal links and the first placement + arrival utm_source are sent with the checkout order. If sessionStorage is used to persist the first placement (as the test mentions), clearing storage or opening in a new tab could lose attribution. Also, if a user navigates from a blog post (from=blog) to the catalog (from=catalog), the second write might overwrite the first unless guarded.

  • apps/website/src/pages/Courses.tsx (not shown): Accepts ?unlock=<key> for sign-in after joining via Telegram bot. If the key is invalid/expired, the UI should surface a clear error rather than silently failing. The diff doesn't show the error handling path.


Read: 300 files, +308 -3, the whole diff up to 60000 characters (the rest was not read). Head bcf926f. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

@gHashTag
gHashTag merged commit d69b016 into main Oct 13, 2026
19 of 23 checks passed
@github-actions github-actions Bot added status:completed Done and removed status:in-progress 🔵 Agent working labels Oct 13, 2026
github-actions Bot added a commit that referenced this pull request Oct 13, 2026
Merge pull request #1634 from gHashTag/feat/courses-cross-sell-utm

feat(courses): other courses under every course and post, footer advert, utm on shares, sign-in after Telegram
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

owner-approved-foreign Owner approved hand-written non-t27 code in this PR status:completed Done

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant