fix(web): remove turbostarter CTA popup + ship claudemesh OG image
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled
Two visible launch-polish issues: 1. BuyCtaDialog popup was firing on an exponential backoff schedule (15s, 30s, 60s, …) pushing users toward turbostarter.dev/#pricing + Discord. Wrong product, wrong audience. Fully removed: mount point in [locale]/layout.tsx + the component file + localStorage keys will self-prune on next visit. 2. WhatsApp/Slack/Twitter link previews were pulling the TurboStarter boilerplate opengraph-image.png (from Jan 8). Replaced with a 1200×630 claudemesh OG: "CLAUDEMESH" pixel wordmark left side, hero mesh composition (6 Claude Code terminals + pixel-crab hub + orange energy lattice + vaporwave grid floor) right side, "peer mesh for Claude Code sessions" tagline in mono beneath wordmark. 3. Default metadata description swapped from the dangling `common:product.description` i18n key (which rendered as the key itself because the key doesn't exist in our trimmed translations) to a hardcoded claudemesh description. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import { Providers } from "~/lib/providers/providers";
|
|||||||
import { ImpersonatingBanner } from "~/modules/admin/users/user/impersonating-banner";
|
import { ImpersonatingBanner } from "~/modules/admin/users/user/impersonating-banner";
|
||||||
import { BaseLayout } from "~/modules/common/layout/base";
|
import { BaseLayout } from "~/modules/common/layout/base";
|
||||||
import { Toaster } from "~/modules/common/toast";
|
import { Toaster } from "~/modules/common/toast";
|
||||||
import { BuyCtaDialog } from "~/modules/marketing/layout/buy-cta-dialog";
|
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return config.locales.map((locale) => ({ locale }));
|
return config.locales.map((locale) => ({ locale }));
|
||||||
@@ -33,7 +32,6 @@ export default async function RootLayout({
|
|||||||
<Providers locale={locale}>
|
<Providers locale={locale}>
|
||||||
<ImpersonatingBanner />
|
<ImpersonatingBanner />
|
||||||
{children}
|
{children}
|
||||||
<BuyCtaDialog />
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</Providers>
|
</Providers>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 947 KiB |
@@ -49,7 +49,7 @@ export const getMetadata =
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
title,
|
title,
|
||||||
description = "common:product.description",
|
description = "Connect your Claude Code sessions to each other. Zero config. End-to-end encrypted. Peer mesh for Claude Code teams.",
|
||||||
url,
|
url,
|
||||||
canonical,
|
canonical,
|
||||||
images = [DEFAULT_IMAGE],
|
images = [DEFAULT_IMAGE],
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { useTranslation } from "@turbostarter/i18n";
|
|
||||||
import { cn } from "@turbostarter/ui";
|
|
||||||
import { buttonVariants } from "@turbostarter/ui-web/button";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from "@turbostarter/ui-web/dialog";
|
|
||||||
import { Icons } from "@turbostarter/ui-web/icons";
|
|
||||||
|
|
||||||
const MIN_DELAY_MS = 15_000;
|
|
||||||
const STORAGE_LAST_SHOWN_AT = "buyCtaDialog:lastShownAt";
|
|
||||||
const STORAGE_PREV_DELAY_MS = "buyCtaDialog:prevDelayMs";
|
|
||||||
|
|
||||||
export const BuyCtaDialog = () => {
|
|
||||||
const { t } = useTranslation(["common", "marketing"]);
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const timeoutIdRef = useRef<number | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const scheduleNext = () => {
|
|
||||||
const now = Date.now();
|
|
||||||
const storedLastShown = Number(
|
|
||||||
window.localStorage.getItem(STORAGE_LAST_SHOWN_AT) ?? "0",
|
|
||||||
);
|
|
||||||
const prevDelayMs = Number(
|
|
||||||
window.localStorage.getItem(STORAGE_PREV_DELAY_MS) ?? "0",
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextDelay = Math.max(
|
|
||||||
MIN_DELAY_MS,
|
|
||||||
prevDelayMs ? prevDelayMs * 2 : MIN_DELAY_MS,
|
|
||||||
);
|
|
||||||
|
|
||||||
const baseNextShow = storedLastShown
|
|
||||||
? storedLastShown + nextDelay
|
|
||||||
: now + nextDelay;
|
|
||||||
|
|
||||||
const delayFromNow = Math.max(MIN_DELAY_MS, baseNextShow - now);
|
|
||||||
|
|
||||||
if (timeoutIdRef.current) {
|
|
||||||
window.clearTimeout(timeoutIdRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeoutIdRef.current = window.setTimeout(() => {
|
|
||||||
setOpen(true);
|
|
||||||
|
|
||||||
const shownAt = Date.now();
|
|
||||||
window.localStorage.setItem(STORAGE_LAST_SHOWN_AT, String(shownAt));
|
|
||||||
window.localStorage.setItem(STORAGE_PREV_DELAY_MS, String(nextDelay));
|
|
||||||
|
|
||||||
scheduleNext();
|
|
||||||
}, delayFromNow);
|
|
||||||
};
|
|
||||||
|
|
||||||
scheduleNext();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (timeoutIdRef.current) {
|
|
||||||
window.clearTimeout(timeoutIdRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
|
||||||
<DialogContent className="max-w-md">
|
|
||||||
<DialogHeader className="space-y-3">
|
|
||||||
<DialogTitle>{t("cta.buy.question")}</DialogTitle>
|
|
||||||
<DialogDescription className="text-foreground text-base">
|
|
||||||
{t("cta.buy.description")}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://turbostarter.dev/#pricing"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className={cn(buttonVariants(), "gap-2")}
|
|
||||||
>
|
|
||||||
<Icons.Code className="size-4" />
|
|
||||||
{t("cta.buy.button")}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div className="bg-border relative -mx-6 my-3 h-px">
|
|
||||||
<span className="bg-background text-muted-foreground absolute left-1/2 -translate-x-1/2 -translate-y-1/2 px-3 text-sm">
|
|
||||||
{t("or")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<p>{t("cta.buy.join.description")}</p>
|
|
||||||
|
|
||||||
<a
|
|
||||||
className={cn(
|
|
||||||
buttonVariants(),
|
|
||||||
"gap-2 bg-[#5865F2] px-7 no-underline hover:bg-[#5865F2]/95",
|
|
||||||
)}
|
|
||||||
href="https://discord.gg/KjpK2uk3JP"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<Icons.Discord className="size-[1.35rem] text-white" />
|
|
||||||
<span className="font-semibold text-white">
|
|
||||||
{t("cta.buy.join.button")}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user