Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/cursor/src/actions/track-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { revalidatePath } from "next/cache";
import { z } from "zod";
import { installGlobalLimit, installPerPluginLimit } from "@/lib/rate-limit";
import { createClient as createAdminClient } from "@/utils/supabase/admin-client";
import { ActionError, actionClient } from "./safe-action";
import { actionClient } from "./safe-action";

export type TrackInstallResult =
| { tracked: true }
| { tracked: false; rateLimited: true };

export const trackInstallAction = actionClient
.metadata({
Expand All @@ -23,7 +27,7 @@ export const trackInstallAction = actionClient
]);

if (!global.success || !perPlugin.success) {
throw new ActionError("Rate limit exceeded. Please try again later.");
return { tracked: false, rateLimited: true } satisfies TrackInstallResult;
}

const admin = await createAdminClient();
Expand All @@ -34,4 +38,6 @@ export const trackInstallAction = actionClient

revalidatePath("/");
revalidatePath(`/plugins/${slug}`);

return { tracked: true } satisfies TrackInstallResult;
});
16 changes: 13 additions & 3 deletions apps/cursor/src/app/c/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { getCompanyProfile } from "@/data/queries";
import { createOGResponse, OG, OGLayout } from "@/lib/og";
import {
createOGResponse,
OG,
OGLayout,
resolveOgImageUrl,
} from "@/lib/og";

export const alt = "Company Profile";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image({
params,
Expand All @@ -30,10 +36,12 @@ export default async function Image({
);
}

const logoUrl = resolveOgImageUrl(data.image);

return createOGResponse(
<OGLayout>
<div style={{ display: "flex", alignItems: "center", gap: 40 }}>
{data.image && (
{logoUrl && (
<div
style={{
display: "flex",
Expand All @@ -48,7 +56,7 @@ export default async function Image({
}}
>
<img
src={data.image}
src={logoUrl}
width={104}
height={104}
style={{ borderRadius: 14, objectFit: "contain" }}
Expand All @@ -66,6 +74,7 @@ export default async function Image({
>
<div
style={{
display: "flex",
fontSize: 52,
fontWeight: 700,
color: OG.text,
Expand Down Expand Up @@ -106,6 +115,7 @@ export default async function Image({
{data.bio && (
<div
style={{
display: "flex",
fontSize: 22,
color: OG.textTertiary,
lineHeight: 1.4,
Expand Down
1 change: 1 addition & 0 deletions apps/cursor/src/app/companies/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createListingOG, OG } from "@/lib/og";
export const alt = "Companies";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createListingOG("Companies", "Companies using Cursor");
Expand Down
1 change: 1 addition & 0 deletions apps/cursor/src/app/login/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createListingOG, OG } from "@/lib/og";
export const alt = "Sign In";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createListingOG("Sign In", "Sign in to Cursor Directory");
Expand Down
1 change: 1 addition & 0 deletions apps/cursor/src/app/members/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createListingOG, OG } from "@/lib/og";
export const alt = "Members";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createListingOG(
Expand Down
10 changes: 9 additions & 1 deletion apps/cursor/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { CursorIcon, createOGResponse, OG, OGLayout } from "@/lib/og";
import {
CursorIcon,
createOGResponse,
OG,
OGLayout,
} from "@/lib/og";

export const alt = "Cursor Directory";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createOGResponse(
Expand All @@ -20,6 +26,7 @@ export default async function Image() {
<CursorIcon size={80} />
<div
style={{
display: "flex",
fontSize: 56,
fontWeight: 700,
color: OG.text,
Expand All @@ -31,6 +38,7 @@ export default async function Image() {
</div>
<div
style={{
display: "flex",
fontSize: 26,
color: OG.textSecondary,
lineHeight: 1.4,
Expand Down
28 changes: 22 additions & 6 deletions apps/cursor/src/app/plugins/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { getPluginBySlug } from "@/data/queries";
import {
CursorIcon,
createOGResponse,
formatCount,
OG,
OGLayout,
resolveOgImageUrl,
} from "@/lib/og";

export const alt = "Plugin";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
// Must be a literal — Next.js segment config does not accept imported values.
export const revalidate = 86400;

export default async function Image({
params,
Expand All @@ -36,6 +38,8 @@ export default async function Image({
);
}

const logoUrl = resolveOgImageUrl(data.logo);

const components = data.plugin_components ?? [];
const typeCounts: Record<string, number> = {};
for (const c of components) {
Expand All @@ -62,16 +66,17 @@ export default async function Image({
padding: 6,
}}
>
{data.logo ? (
{logoUrl ? (
<img
src={data.logo}
src={logoUrl}
width={60}
height={60}
style={{ borderRadius: 10, objectFit: "contain" }}
/>
) : (
<span
style={{
display: "flex",
fontSize: 32,
fontWeight: 700,
color: OG.textSecondary,
Expand All @@ -90,6 +95,7 @@ export default async function Image({
>
<div
style={{
display: "flex",
fontSize: 48,
fontWeight: 700,
color: OG.text,
Expand All @@ -100,7 +106,13 @@ export default async function Image({
{data.name}
</div>
{data.author_name && (
<div style={{ fontSize: 22, color: OG.textSecondary }}>
<div
style={{
display: "flex",
fontSize: 22,
color: OG.textSecondary,
}}
>
by {data.author_name}
</div>
)}
Expand All @@ -110,6 +122,7 @@ export default async function Image({
{data.description && (
<div
style={{
display: "flex",
fontSize: 24,
color: OG.textSecondary,
lineHeight: 1.4,
Expand Down Expand Up @@ -146,13 +159,15 @@ export default async function Image({
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
<span style={{ fontWeight: 700 }}>
<span style={{ display: "flex", fontWeight: 700 }}>
{formatCount(data.install_count)}
</span>
</div>

{componentSummary && (
<div style={{ fontSize: 20, color: OG.textTertiary }}>
<div
style={{ display: "flex", fontSize: 20, color: OG.textTertiary }}
>
{componentSummary}
</div>
)}
Expand All @@ -164,6 +179,7 @@ export default async function Image({
<div
key={kw}
style={{
display: "flex",
fontSize: 16,
color: OG.textSecondary,
padding: "6px 14px",
Expand Down
1 change: 1 addition & 0 deletions apps/cursor/src/app/plugins/new/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createListingOG, OG } from "@/lib/og";
export const alt = "Submit a Plugin";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createListingOG(
Expand Down
1 change: 1 addition & 0 deletions apps/cursor/src/app/plugins/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createListingOG, OG } from "@/lib/og";
export const alt = "Plugins";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image() {
return createListingOG(
Expand Down
17 changes: 14 additions & 3 deletions apps/cursor/src/app/u/[slug]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { getUserProfile } from "@/data/queries";
import { createOGResponse, OG, OGLayout } from "@/lib/og";
import {
createOGResponse,
OG,
OGLayout,
resolveOgImageUrl,
} from "@/lib/og";

export const alt = "User Profile";
export const size = { width: OG.width, height: OG.height };
export const contentType = "image/png";
export const revalidate = 86400;

export default async function Image({
params,
Expand All @@ -30,12 +36,14 @@ export default async function Image({
);
}

const avatarUrl = resolveOgImageUrl(data.image);

return createOGResponse(
<OGLayout>
<div style={{ display: "flex", alignItems: "center", gap: 40 }}>
{data.image && (
{avatarUrl && (
<img
src={data.image}
src={avatarUrl}
width={140}
height={140}
style={{ borderRadius: "50%", border: `1px solid ${OG.border}` }}
Expand All @@ -52,6 +60,7 @@ export default async function Image({
>
<div
style={{
display: "flex",
fontSize: 52,
fontWeight: 700,
color: OG.text,
Expand All @@ -65,6 +74,7 @@ export default async function Image({
{data.work && (
<div
style={{
display: "flex",
fontSize: 24,
color: OG.textSecondary,
lineHeight: 1.3,
Expand All @@ -77,6 +87,7 @@ export default async function Image({
{data.bio && (
<div
style={{
display: "flex",
fontSize: 22,
color: OG.textTertiary,
lineHeight: 1.4,
Expand Down
12 changes: 10 additions & 2 deletions apps/cursor/src/components/plugins/plugin-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Image from "next/image";
import Link from "next/link";
import { useAction } from "next-safe-action/hooks";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { trackInstallAction } from "@/actions/track-install";
import { CursorDeepLink } from "@/components/cursor-deeplink";
import { Card, CardContent } from "@/components/ui/card";
Expand Down Expand Up @@ -211,10 +212,17 @@ export function PluginDetailView({ plugin }: { plugin: PluginRow }) {
});
}, [plugin.owner_id]);

const { execute: trackInstall } = useAction(trackInstallAction);
const { execute: trackInstall } = useAction(trackInstallAction, {
onSuccess: ({ data }) => {
if (data?.tracked) {
setInstallCount((c) => c + 1);
} else if (data?.rateLimited) {
toast("Too many installs right now. Please try again in a bit.");
}
},
});

const handleInstall = useCallback(() => {
setInstallCount((c) => c + 1);
trackInstall({ pluginId: plugin.id, slug: plugin.slug });
}, [plugin.id, plugin.slug, trackInstall]);

Expand Down
Loading