feat(editor): follow-up GitHub star prompt for past dismissers
The star banner used a single localStorage flag (velxio_star_prompted) set identically whether the user clicked through to the repo or just closed it, so once dismissed it never showed again and clickers and closers were indistinguishable. Now track three flags: velxio_star_prompted - dismissed the first ask velxio_star_prompted_v2 - dismissed the follow-up (stop forever) velxio_star_clicked - clicked through to the repo (stop forever) Anyone who dismissed the first ask without clicking through gets ONE follow-up (round 2) with a stronger message; clicking the repo link at any time opts them out permanently. Capped at two asks total. Adds starBanner.title2/body2 copy in all 9 locales.
This commit is contained in:
parent
c81e5b839d
commit
e51d26ce33
|
|
@ -6,10 +6,17 @@ const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio';
|
|||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
// Fired when the user clicks through to the repo (distinct from a plain
|
||||
// dismiss) — lets the caller never prompt that user again.
|
||||
onStarClick: () => void;
|
||||
// 1 = first ask; 2 = follow-up ask for someone who dismissed the first one.
|
||||
round?: 1 | 2;
|
||||
}
|
||||
|
||||
export const GitHubStarBanner: React.FC<Props> = ({ onClose }) => {
|
||||
export const GitHubStarBanner: React.FC<Props> = ({ onClose, onStarClick, round = 1 }) => {
|
||||
const { t } = useTranslation();
|
||||
const titleKey = round === 2 ? 'starBanner.title2' : 'starBanner.title';
|
||||
const bodyKey = round === 2 ? 'starBanner.body2' : 'starBanner.body';
|
||||
return (
|
||||
<div className="gh-star-banner" role="dialog" aria-label={t('starBanner.ariaLabel')}>
|
||||
<div className="gh-star-banner__icon">
|
||||
|
|
@ -19,14 +26,14 @@ export const GitHubStarBanner: React.FC<Props> = ({ onClose }) => {
|
|||
</div>
|
||||
|
||||
<div className="gh-star-banner__body">
|
||||
<strong>{t('starBanner.title')}</strong>
|
||||
<p>{t('starBanner.body')}</p>
|
||||
<strong>{t(titleKey)}</strong>
|
||||
<p>{t(bodyKey)}</p>
|
||||
<a
|
||||
href={GITHUB_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="gh-star-banner__btn"
|
||||
onClick={onClose}
|
||||
onClick={onStarClick}
|
||||
>
|
||||
⭐ {t('starBanner.cta')}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Velxio auf GitHub mit Stern markieren",
|
||||
"title": "Gefällt Ihnen Velxio?",
|
||||
"body": "Ein ⭐ auf GitHub hilft mehr Entwicklern, das Projekt zu entdecken – es dauert nur 2 Sekunden!",
|
||||
"title2": "Ein kurzer Gefallen?",
|
||||
"body2": "Velxio ist kostenlos und Open Source. Ein Stern auf GitHub ist der einfachste Weg, das Projekt zu unterstützen und mehr Menschen zu helfen, es zu finden.",
|
||||
"cta": "Auf GitHub mit Stern markieren",
|
||||
"dismiss": "Schließen"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Star Velxio on GitHub",
|
||||
"title": "Enjoying Velxio?",
|
||||
"body": "A ⭐ on GitHub helps more developers discover the project — it only takes 2 seconds!",
|
||||
"title2": "One quick favor?",
|
||||
"body2": "Velxio is free and open-source. A star on GitHub is the simplest way to support it and help more people find the project.",
|
||||
"cta": "Star on GitHub",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Marcar Velxio con estrella en GitHub",
|
||||
"title": "¿Disfrutando de Velxio?",
|
||||
"body": "¡Una ⭐ en GitHub ayuda a que más desarrolladores descubran el proyecto — solo toma 2 segundos!",
|
||||
"title2": "¿Un favor rápido?",
|
||||
"body2": "Velxio es gratis y de código abierto. Una estrella en GitHub es la forma más simple de apoyarlo y ayudar a que más gente lo descubra.",
|
||||
"cta": "Marcar con estrella en GitHub",
|
||||
"dismiss": "Descartar"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Mettre une étoile à Velxio sur GitHub",
|
||||
"title": "Vous appréciez Velxio ?",
|
||||
"body": "Une ⭐ sur GitHub aide plus de développeurs à découvrir le projet — cela ne prend que 2 secondes !",
|
||||
"title2": "Un petit service ?",
|
||||
"body2": "Velxio est gratuit et open source. Une étoile sur GitHub est le moyen le plus simple de le soutenir et d'aider davantage de personnes à le découvrir.",
|
||||
"cta": "Mettre une étoile sur GitHub",
|
||||
"dismiss": "Ignorer"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Metti una stella a Velxio su GitHub",
|
||||
"title": "Ti piace Velxio?",
|
||||
"body": "Una ⭐ su GitHub aiuta più sviluppatori a scoprire il progetto — ci vogliono solo 2 secondi!",
|
||||
"title2": "Un piccolo favore?",
|
||||
"body2": "Velxio è gratuito e open source. Una stella su GitHub è il modo più semplice per sostenerlo e aiutare più persone a scoprire il progetto.",
|
||||
"cta": "Metti stella su GitHub",
|
||||
"dismiss": "Ignora"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "GitHubでVelxioにスターを付ける",
|
||||
"title": "Velxioを気に入っていますか?",
|
||||
"body": "GitHubで⭐を付けると、より多くの開発者がこのプロジェクトを発見できます—たった2秒で完了します!",
|
||||
"title2": "ちょっとしたお願いです",
|
||||
"body2": "Velxio は無料のオープンソースです。GitHub でスターを付けることが、プロジェクトを応援し、より多くの人に見つけてもらう一番簡単な方法です。",
|
||||
"cta": "GitHubでスターを付ける",
|
||||
"dismiss": "閉じる"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Favoritar Velxio no GitHub",
|
||||
"title": "Gostando do Velxio?",
|
||||
"body": "Uma ⭐ no GitHub ajuda mais desenvolvedores a descobrirem o projeto — leva apenas 2 segundos!",
|
||||
"title2": "Um favor rápido?",
|
||||
"body2": "O Velxio é gratuito e de código aberto. Uma estrela no GitHub é a forma mais simples de apoiá-lo e ajudar mais pessoas a descobrirem o projeto.",
|
||||
"cta": "Favoritar no GitHub",
|
||||
"dismiss": "Dispensar"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "Поставьте звезду Velxio на GitHub",
|
||||
"title": "Нравится Velxio?",
|
||||
"body": "Звезда на GitHub помогает большему числу разработчиков узнать о проекте — это занимает всего 2 секунды!",
|
||||
"title2": "Небольшая просьба",
|
||||
"body2": "Velxio — бесплатный проект с открытым исходным кодом. Звезда на GitHub — самый простой способ поддержать его и помочь большему числу людей узнать о проекте.",
|
||||
"cta": "Поставить звезду на GitHub",
|
||||
"dismiss": "Закрыть"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -373,6 +373,8 @@
|
|||
"ariaLabel": "在 GitHub 上为 Velxio 加星",
|
||||
"title": "喜欢 Velxio 吗?",
|
||||
"body": "在 GitHub 上点个 ⭐ 可以帮助更多开发者发现这个项目——只需 2 秒钟!",
|
||||
"title2": "帮个小忙?",
|
||||
"body2": "Velxio 是免费的开源项目。在 GitHub 上点个 star 是支持它、帮助更多人发现它最简单的方式。",
|
||||
"cta": "在 GitHub 上加星",
|
||||
"dismiss": "忽略"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ export const EditorPage: React.FC = () => {
|
|||
const setCompileLogs = useCompileLogsStore((s) => s.setLogs);
|
||||
const [bottomPanelHeight, setBottomPanelHeight] = useState(BOTTOM_PANEL_DEFAULT);
|
||||
const [showStarBanner, setShowStarBanner] = useState(false);
|
||||
const [starRound, setStarRound] = useState<1 | 2>(1);
|
||||
|
||||
// ── Electrical simulation (one-time mount) ────────────────────────────────
|
||||
// `startSimulation()` is the single entry point: it constructs the
|
||||
|
|
@ -100,14 +101,29 @@ export const EditorPage: React.FC = () => {
|
|||
return startSimulation();
|
||||
}, []);
|
||||
|
||||
// ── GitHub star prompt (show once: 2nd visit OR after 3 min) ──────────────
|
||||
// ── GitHub star prompt (show twice at most: 2nd visit OR after 3 min) ──────
|
||||
// Three localStorage flags drive this:
|
||||
// velxio_star_prompted → dismissed the first ask
|
||||
// velxio_star_prompted_v2 → dismissed the follow-up ask (stop forever)
|
||||
// velxio_star_clicked → clicked through to the repo (stop forever)
|
||||
// Anyone who dismissed the first ask WITHOUT clicking through gets one
|
||||
// follow-up (round 2) with a stronger message; clicking the repo link at
|
||||
// any time opts them out permanently.
|
||||
useEffect(() => {
|
||||
const STAR_KEY = 'velxio_star_prompted';
|
||||
const STAR_KEY_V2 = 'velxio_star_prompted_v2';
|
||||
const STAR_CLICKED_KEY = 'velxio_star_clicked';
|
||||
const VISITS_KEY = 'velxio_editor_visits';
|
||||
const FIRST_VISIT_KEY = 'velxio_editor_first_visit';
|
||||
const THREE_MIN = 3 * 60 * 1000;
|
||||
|
||||
if (localStorage.getItem(STAR_KEY)) return;
|
||||
// Never bother people who already starred or already saw the follow-up.
|
||||
if (localStorage.getItem(STAR_CLICKED_KEY)) return;
|
||||
if (localStorage.getItem(STAR_KEY_V2)) return;
|
||||
|
||||
// Round 2 = they dismissed the first ask (without clicking through).
|
||||
const round = localStorage.getItem(STAR_KEY) ? 2 : 1;
|
||||
setStarRound(round);
|
||||
|
||||
// Increment visit counter
|
||||
const visits = parseInt(localStorage.getItem(VISITS_KEY) ?? '0', 10) + 1;
|
||||
|
|
@ -129,13 +145,26 @@ export const EditorPage: React.FC = () => {
|
|||
const elapsed = Date.now() - firstVisit;
|
||||
const delay = Math.max(0, THREE_MIN - elapsed);
|
||||
const timer = setTimeout(() => {
|
||||
if (!localStorage.getItem(STAR_KEY)) setShowStarBanner(true);
|
||||
if (!localStorage.getItem(STAR_CLICKED_KEY) && !localStorage.getItem(STAR_KEY_V2)) {
|
||||
setShowStarBanner(true);
|
||||
}
|
||||
}, delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const handleDismissStarBanner = () => {
|
||||
localStorage.setItem('velxio_star_prompted', '1');
|
||||
// First dismiss → mark round 1; second dismiss → mark round 2 (stop forever).
|
||||
if (localStorage.getItem('velxio_star_prompted')) {
|
||||
localStorage.setItem('velxio_star_prompted_v2', '1');
|
||||
} else {
|
||||
localStorage.setItem('velxio_star_prompted', '1');
|
||||
}
|
||||
setShowStarBanner(false);
|
||||
};
|
||||
|
||||
const handleStarClick = () => {
|
||||
// They went to the repo — opt them out of any further prompts.
|
||||
localStorage.setItem('velxio_star_clicked', '1');
|
||||
setShowStarBanner(false);
|
||||
};
|
||||
const [explorerOpen, setExplorerOpen] = useState(true);
|
||||
|
|
@ -642,7 +671,13 @@ export const EditorPage: React.FC = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{showStarBanner && <GitHubStarBanner onClose={handleDismissStarBanner} />}
|
||||
{showStarBanner && (
|
||||
<GitHubStarBanner
|
||||
onClose={handleDismissStarBanner}
|
||||
onStarClick={handleStarClick}
|
||||
round={starRound}
|
||||
/>
|
||||
)}
|
||||
{/* Slot reserved for the private pro overlay (e.g. agent chat panel).
|
||||
Self-hosted builds without an overlay see nothing here. */}
|
||||
<div data-velxio-slot="agent-chat" />
|
||||
|
|
|
|||
Loading…
Reference in New Issue