diff --git a/packages/website/src/components/Ad.tsx b/packages/website/src/components/Ad.tsx
index c17c4ae59..2082dd33a 100644
--- a/packages/website/src/components/Ad.tsx
+++ b/packages/website/src/components/Ad.tsx
@@ -1,12 +1,17 @@
import { Box, type BoxProps } from '@radix-ui/themes';
import { useEffect } from 'react';
+import { useAdExempt } from '../hooks/useAdExempt';
-type AdProps = BoxProps & {
+export type AdProps = BoxProps & {
id: string;
};
export function Ad({ id, ...props }: AdProps) {
+ const exempt = useAdExempt();
+
useEffect(() => {
+ if (exempt) return;
+
(window as any).nitroAds.createAd(id, {
refreshTime: 30,
demo: import.meta.env.MODE === 'development',
@@ -19,7 +24,9 @@ export function Ad({ id, ...props }: AdProps) {
position: 'top-right',
},
});
- }, []);
+ }, [exempt, id]);
+
+ if (exempt) return null;
return ;
}
diff --git a/packages/website/src/components/AdWithContext.tsx b/packages/website/src/components/AdWithContext.tsx
new file mode 100644
index 000000000..85639762a
--- /dev/null
+++ b/packages/website/src/components/AdWithContext.tsx
@@ -0,0 +1,10 @@
+import { App } from '../stores/app';
+import { Ad, type AdProps } from './Ad';
+
+export function AdWithContext(props: AdProps) {
+ return (
+
+
+
+ );
+}
diff --git a/packages/website/src/hooks/useAdExempt.ts b/packages/website/src/hooks/useAdExempt.ts
index f2f7ce1a9..825820b3d 100644
--- a/packages/website/src/hooks/useAdExempt.ts
+++ b/packages/website/src/hooks/useAdExempt.ts
@@ -5,7 +5,7 @@ let cache: Record> = {};
export function useAdExempt() {
const patreon = App.use((state) => state.logins.patreon);
- const [exempt, setExempt] = useState(false);
+ const [exempt, setExempt] = useState(true);
useEffect(() => {
(async () => {
@@ -13,14 +13,17 @@ export function useAdExempt() {
if (!(patreon.token in cache)) {
cache[patreon.token] = new Promise(async (resolve) => {
- const response = await fetch(
- `/api/patreon/membership/${patreon.token}`,
- { cache: 'force-cache' },
- );
+ const response = await fetch(`/api/patreon/membership`, {
+ cache: 'default',
+ headers: { token: patreon.token },
+ });
if (!response.ok) return resolve(false);
const json = await response.json();
+
+ console.log(json);
+
const exempt = typeof json === 'boolean' && json;
cache[patreon.token] = exempt;
diff --git a/packages/website/src/pages/api/patreon/membership/index.ts b/packages/website/src/pages/api/patreon/membership/index.ts
index 347c713e7..8e1cf61e9 100644
--- a/packages/website/src/pages/api/patreon/membership/index.ts
+++ b/packages/website/src/pages/api/patreon/membership/index.ts
@@ -1,7 +1,85 @@
+import { assertSecret } from '@blitzkit/core';
import type { APIContext } from 'astro';
export const prerender = false;
-export async function GET({}: APIContext) {
- return Response.json({ hello: Math.random() });
+interface PatreonMembershipResponse {
+ data: {
+ attributes: unknown;
+ id: `${number}`;
+ relationships: {
+ memberships: {
+ data: {
+ id: string; // uuid
+ type: 'member';
+ }[];
+ };
+ };
+ type: 'user';
+ };
+ included?: (
+ | {
+ attributes: {
+ patron_status:
+ | 'active_patron'
+ | 'declined_patron'
+ | 'former_patron'
+ | null; // null = never a paid member
+ };
+ id: string; // uuid
+ relationships: {
+ campaign: {
+ data: {
+ id: `${number}`; // id of my campaign to check against
+ type: 'campaign';
+ };
+ links: {
+ related: string; // url
+ };
+ };
+ };
+ type: 'member';
+ }
+ | {
+ attributes: {};
+ id: `${number}`;
+ type: 'campaign'; // ignore this discriminator
+ }
+ )[];
+ links: {
+ self: 'https://www.patreon.com/api/oauth2/v2/user/124201389';
+ };
+}
+
+export async function GET({ request }: APIContext) {
+ const token = request.headers.get('token');
+
+ if (!token) return Response.json(false);
+
+ console.log('doing it');
+
+ const patreonResponse = await fetch(
+ encodeURI(
+ 'https://www.patreon.com/api/oauth2/v2/identity?include=memberships.campaign&fields[member]=patron_status',
+ ),
+ { headers: { Authorization: `Bearer ${token}` } },
+ );
+
+ if (!patreonResponse.ok) return Response.json(false);
+
+ const json = (await patreonResponse.json()) as PatreonMembershipResponse;
+
+ const response = Response.json(
+ json.included?.some(
+ (item) =>
+ item.type === 'member' &&
+ item.relationships.campaign.data.id ===
+ assertSecret(process.env.PATREON_CAMPAIGN_ID) &&
+ item.attributes.patron_status === 'active_patron',
+ ) ?? false,
+ );
+
+ response.headers.set('Cache-Control', 'max-age=86400'); // a day
+
+ return response;
}
diff --git a/packages/website/src/pages/index.astro b/packages/website/src/pages/index.astro
index 3d0ba976a..a9ad4a090 100644
--- a/packages/website/src/pages/index.astro
+++ b/packages/website/src/pages/index.astro
@@ -1,6 +1,6 @@
---
import { Box, Flex, Heading } from '@radix-ui/themes';
-import { Ad } from '../components/Ad';
+import { AdWithContext } from '../components/AdWithContext';
import HomeHero from '../components/HomeHero.astro';
import { Plugs } from '../components/Plugs.tsx';
import ToolCard from '../components/ToolCard.astro';
@@ -24,7 +24,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
-
+
{/* */}
@@ -39,7 +39,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
-
+