Skip to content

feat: revamp Ko-fi support prompts in header and export flow#2042

Merged
andymai merged 3 commits into
mainfrom
feat/kofi-support-ux
Jun 6, 2026
Merged

feat: revamp Ko-fi support prompts in header and export flow#2042
andymai merged 3 commits into
mainfrom
feat/kofi-support-ux

Conversation

@andymai
Copy link
Copy Markdown
Owner

@andymai andymai commented Jun 6, 2026

What & why

Moves the Ko-fi support ask to the surfaces that actually convert, and retires the one that didn't.

Header

  • The Ko-fi entry is now a prominent accent-colored "Support me on Ko-fi" pill — a native reproduction of the official Ko-fi widget (the real cup logo + its wiggle animation, light label), tinted with the user's chosen accent color instead of a hardcoded blue. (The official <script> widget can't run here — CSP blocks third-party scripts — and can't use the accent color, so it's reproduced natively.)
  • Trimmed the verbose "Press ? for help" hint to a compact "Help" to make room.

Export flow (bin designer + baseplate)

  • After a successful export, the dialog shows an inline success view: a download confirmation, a low-pressure support ask, and a free "star on GitHub" fallback for people who won't tip.
  • Gated for good UX: never shown on a user's first export (too early — they haven't printed it yet), shown from the 2nd export on, and at most once per 30 days so frequent exporters are never nagged. Everyone else just gets a quick "Export complete" toast + auto-close.

Nudges

  • Removed the timer-based Ko-fi nudge toast — it converted poorly and fired on an arbitrary timer rather than at a value moment. The feedback nudge is unchanged; its copy was tightened across all 10 locales.

Measurement

The export CTA is tagged kofi_clicked with source: bin_designer_export / baseplate_export, so its conversion can be compared head-to-head against the header pill (source: header).

Notes

  • New exportSupportGate (returning-maker + cooldown logic) lives in shared/ with its own storage key — no cross-feature import into the engagement slice.
  • Mobile Ko-fi links left as-is intentionally (compact accent "Tip" links suit the narrow title bar).

Testing

  • Typecheck, ESLint, module-boundary, and all 4 i18n checks pass.
  • Unit tests for the gate, the support prompt, and the nudge hook (489 tests across touched areas).
  • Manually verified end-to-end: first-export fast path vs. second-export support view (desktop + mobile), and the accent pill across multiple accent colors.

Move the Ko-fi ask to the surfaces that actually convert and retire the
one that didn't.

- Header: accent-colored "Support me on Ko-fi" pill (native reproduction
  of the official widget — animated cup, white label) using the user's
  accent color; trimmed the verbose "Press ? for help" hint to reclaim room.
- Export dialog: inline post-export success view with a low-pressure
  support ask + a free "star on GitHub" fallback, in both the bin designer
  and baseplate generator. Gated to returning makers (2nd+ export) and
  capped at once per 30 days, so first-timers and power users aren't nagged.
- Removed the timer-based Ko-fi nudge toast (it converted at ~0.7%);
  tightened the remaining feedback copy across all 10 locales.
- Tagged the export CTA with kofi_clicked source=bin_designer_export /
  baseplate_export to compare against the header pill.
Copilot AI review requested due to automatic review settings June 6, 2026 16:38
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gridfinity-layout-tool Ready Ready Preview, Comment Jun 6, 2026 5:07pm

Request Review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR repositions Ko-fi support prompts to higher-intent UI moments (header + post-export success view), removes the timer-based Ko-fi nudge, and adds a shared gating mechanism so the export support ask is shown only to returning users on a cooldown.

Changes:

  • Revamps the header Ko-fi affordance into an accent-colored “Support me on Ko-fi” pill (with native cup wiggle animation) and simplifies the help affordance.
  • Adds a post-export inline success view (ExportSupportPrompt) gated by exportSupportGate (2nd+ export; max once per 30 days), wired into both bin designer and baseplate export flows.
  • Removes the timer-based Ko-fi engagement nudge and updates i18n strings across supported locales.

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/shared/components/HeaderSupportLinks/HeaderSupportLinks.tsx Updates Help UI + replaces Ko-fi action with a prominent btn-primary pill using the animated cup asset.
src/shared/components/HeaderSupportLinks/HeaderSupportLinks.test.tsx Updates header tests to reflect the new Ko-fi label/ARIA key.
src/shared/components/ExportDialog/index.ts Re-exports ExportSupportPrompt from the shared ExportDialog module.
src/shared/components/ExportDialog/ExportSupportPrompt.tsx Adds the post-export success/support view with Ko-fi + GitHub actions and analytics.
src/shared/components/ExportDialog/ExportSupportPrompt.test.tsx Adds unit tests for the support prompt (copy, click tracking, window.open, done callback).
src/shared/components/ExportDialog/exportSupportGate.ts Introduces the localStorage-backed gating logic (returning user + cooldown).
src/shared/components/ExportDialog/exportSupportGate.test.ts Adds unit tests for the gate behavior (1st vs 2nd export; cooldown).
src/shared/components/ExportDialog/ExportDialog.tsx Extends the shared dialog to optionally render a success view instead of the export form.
src/index.css Adds the Ko-fi cup “wiggle” keyframes/class and relies on reduced-motion blanket rules to disable it.
src/i18n/locales/en.ts Removes old header/help/tip keys; adds header.supportOnKofi + export support keys; removes timer-nudge key.
src/i18n/locales/en.json Same key updates as en.ts for JSON locale.
src/i18n/locales/de.json Updates German strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/es.json Updates Spanish strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/fr.json Updates French strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/ja.json Updates Japanese strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/nb.json Updates Norwegian strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/nl.json Updates Dutch strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/pt-BR.json Updates Brazilian Portuguese strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/sv.json Updates Swedish strings: removes old keys; adds export support keys + new Ko-fi label.
src/i18n/locales/uk.json Updates Ukrainian strings: removes old keys; adds export support keys + new Ko-fi label.
src/features/engagement/useEngagementNudges.ts Removes Ko-fi from the periodic engagement nudge polling; keeps feedback nudge only.
src/features/engagement/useEngagementNudges.test.ts Adds tests ensuring only feedback nudge is considered/shown on the timer.
src/features/bin-designer/components/ExportDialog/ExportDialog.tsx Integrates the new gated success view into the bin designer export flow.
src/features/baseplate/hooks/useBaseplateExport.ts Changes baseplate export to return success boolean and removes single-file success toast (handled by dialog view).
src/features/baseplate/components/BaseplatePage/BaseplatePage.tsx Integrates the new gated success view + export-complete toast/close behavior into baseplate export.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 118 to +123
<button
onClick={handleKofiClick}
className="btn btn-ghost px-2.5 py-1.5 text-sm leading-none text-content-secondary flex items-center gap-1.5"
title={t('header.tip')}
aria-label={t('header.tip')}
className="btn btn-primary px-3 py-1.5 text-sm leading-none flex items-center gap-1.5"
style={{ color: '#fff', textShadow: '0 1px 1px rgba(34, 34, 34, 0.15)' }}
title={t('header.supportOnKofi')}
aria-label={t('header.supportOnKofi')}
Comment on lines +60 to +64
<button
onClick={handleKofi}
className="btn btn-primary flex w-full items-center justify-center gap-2 px-4 py-2.5 text-sm leading-none"
style={{ color: '#fff', textShadow: '0 1px 1px rgba(34, 34, 34, 0.15)' }}
>
Comment on lines +22 to +44
function load(): ExportSupportState {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) return JSON.parse(raw) as ExportSupportState;
} catch {
/* ignore malformed / unavailable storage */
}
return { exportCount: 0 };
}

function save(state: ExportSupportState): void {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {
/* storage may be full or unavailable */
}
}

function cooledDown(lastShown: string | undefined): boolean {
if (!lastShown) return true;
const elapsed = Date.now() - new Date(lastShown).getTime();
return elapsed > COOLDOWN_DAYS * 24 * 60 * 60 * 1000;
}
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR revamps Ko-fi support prompts across three surfaces: the header pill is redesigned as an accent-colored "Support me on Ko-fi" button, a new post-export success view (ExportSupportPrompt) is injected into both the bin-designer and baseplate export dialogs, and the timer-based Ko-fi nudge toast is removed from useEngagementNudges. The export prompt is rate-limited by exportSupportGate — suppressed on a user's first export, shown from the second onward, with a 30-day cooldown between shows.

  • exportSupportGate.ts introduces a standalone localStorage counter/cooldown, deliberately isolated from the engagement slice so both export paths can share it without cross-feature coupling.
  • downloadBaseplate return type changes from Promise<void> to Promise<boolean>, delegating dialog-close and toast responsibility to the calling component rather than the hook.
  • All 10 locale files receive the four new export.support.* / export.complete / header.supportOnKofi keys; removed keys (baseplate.export.success, binDesigner.exportSuccess, engagement.kofiNudge, header.tip) are cleaned up everywhere.

Confidence Score: 5/5

Safe to merge — the changes are additive UI improvements with no mutations to core layout data, storage, or geometry logic.

The export gate is well-isolated in its own localStorage key with defensive parsing and a tested cooldown; the boolean return from downloadBaseplate cleanly delegates dialog-close responsibility to the component; all 10 locale files carry the new keys; and the removed Ko-fi nudge path is fully deleted rather than left dormant. No cross-feature imports, no changes to coordinate math, no store schema changes.

No files require special attention.

Important Files Changed

Filename Overview
src/shared/components/ExportDialog/exportSupportGate.ts New self-contained gate: increments an export counter in localStorage and stamps a 30-day cooldown; defensively handles malformed/missing storage. Logic and tests are thorough.
src/shared/components/ExportDialog/ExportSupportPrompt.tsx New post-export success view: download confirmation + Ko-fi CTA + GitHub star fallback + Done button. Tracks events with the caller-supplied source tag.
src/features/baseplate/components/BaseplatePage/BaseplatePage.tsx handleDownload now awaits downloadBaseplate's boolean return, calls the export gate, and conditionally shows the support view or closes the dialog; re-open resets justExported via a useEffect/useRef guard.
src/features/baseplate/hooks/useBaseplateExport.ts Return type changed from Promise to Promise; removed auto-close and single-file success toast — both delegated to the calling component. Split-export dedup/piece-count toasts remain inside the hook.
src/features/bin-designer/components/ExportDialog/ExportDialog.tsx Split-export toast still fires unconditionally, then the support gate is checked; dialog stays open with the success view when gate triggers, otherwise closes. justExported is reset on re-open.
src/shared/components/ExportDialog/index.ts Barrel now exports ExportSupportPrompt and recordExportAndShouldPromptSupport so callers import through the public API rather than internal paths.
src/shared/components/HeaderSupportLinks/HeaderSupportLinks.tsx Ko-fi button changed from ghost-style heart icon to accent-colored pill with animated cup image; verbose "Press ? for help" hint collapsed to compact "Help".
src/features/engagement/useEngagementNudges.ts Ko-fi nudge branch removed; hook now checks only feedback_rating in the periodic timer loop.
src/index.css Adds kofi-cup-wiggle keyframe animation (copied from official Ko-fi widget); placed above the existing prefers-reduced-motion block which blanket-disables it for users who prefer reduced motion.
src/shared/constants/links.ts New file centralising KOFI_URL, GITHUB_REPO_URL and GITHUB_ISSUES_URL; previously duplicated inline in HeaderSupportLinks.

Reviews (3): Last reviewed commit: "refactor: centralize Ko-fi/GitHub URLs i..." | Re-trigger Greptile

Comment thread src/shared/components/ExportDialog/index.ts
Address PR review:
- exportSupportGate: validate the persisted shape so a malformed payload
  (non-numeric count or unparseable timestamp) can't poison the counter with
  NaN and permanently suppress the prompt; treat an invalid lastShown as
  cooled down and make the cooldown boundary inclusive.
- Export recordExportAndShouldPromptSupport through the ExportDialog barrel
  and import it there from both export wrappers (public-API contract).
@andymai
Copy link
Copy Markdown
Owner Author

andymai commented Jun 6, 2026

Thanks for the review — addressed in 91d1e37:

Fixed

  • Gate robustness (Copilot, exportSupportGate.ts)load() now validates the persisted shape, so a malformed payload (non-numeric exportCount, or an unparseable lastShown) can no longer poison the counter with NaN and permanently suppress the prompt. Invalid timestamps are treated as "cooled down", and the cooldown boundary is now inclusive (>=). Added two regression tests.
  • Barrel export (Greptile P2, index.ts)recordExportAndShouldPromptSupport is now exported from the ExportDialog barrel, and both wrappers import it from there instead of the internal path.

Intentional — keeping as-is

  • Hardcoded #fff on the Ko-fi buttons (Copilot, header + export prompt) — this is a deliberate product decision: the ask is styled to match the official Ko-fi widget, which always uses light text on the brand color. Using --text-on-accent would render black text on the light accents (e.g. the default amber), which isn't the intended look. A text-shadow is already applied to preserve legibility. So the theming bypass here is by design, not an oversight.

The support URLs were duplicated as per-file constants in HeaderSupportLinks
and ExportSupportPrompt. Add src/shared/constants/links.ts as the single
source of truth and import from it (issues URL derives from the repo URL).
Mobile/sidebar inline usages are left untouched (out of this PR's scope).
@andymai andymai enabled auto-merge (squash) June 6, 2026 17:07
@andymai andymai merged commit 02ed517 into main Jun 6, 2026
21 checks passed
@andymai andymai deleted the feat/kofi-support-ux branch June 6, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants