mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-26 03:59:53 +01:00
feat: patreon exempt
This commit is contained in:
1 parent
9bc745bc0e
commit
4f5a8c9a37
5 files changed
+110
-12
No files matched your search
@@ -1,12 +1,17 @@
|
|||||||
import { Box, type BoxProps } from '@radix-ui/themes';
|
import { Box, type BoxProps } from '@radix-ui/themes';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useAdExempt } from '../hooks/useAdExempt';
|
||||||
|
|
||||||
type AdProps = BoxProps & {
|
export type AdProps = BoxProps & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Ad({ id, ...props }: AdProps) {
|
export function Ad({ id, ...props }: AdProps) {
|
||||||
|
const exempt = useAdExempt();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (exempt) return;
|
||||||
|
|
||||||
(window as any).nitroAds.createAd(id, {
|
(window as any).nitroAds.createAd(id, {
|
||||||
refreshTime: 30,
|
refreshTime: 30,
|
||||||
demo: import.meta.env.MODE === 'development',
|
demo: import.meta.env.MODE === 'development',
|
||||||
@@ -19,7 +24,9 @@ export function Ad({ id, ...props }: AdProps) {
|
|||||||
position: 'top-right',
|
position: 'top-right',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, []);
|
}, [exempt, id]);
|
||||||
|
|
||||||
|
if (exempt) return null;
|
||||||
|
|
||||||
return <Box id={id} width="100%" {...props} />;
|
return <Box id={id} width="100%" {...props} />;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { App } from '../stores/app';
|
||||||
|
import { Ad, type AdProps } from './Ad';
|
||||||
|
|
||||||
|
export function AdWithContext(props: AdProps) {
|
||||||
|
return (
|
||||||
|
<App.Provider>
|
||||||
|
<Ad {...props} />
|
||||||
|
</App.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ let cache: Record<string, boolean | Promise<boolean>> = {};
|
|||||||
|
|
||||||
export function useAdExempt() {
|
export function useAdExempt() {
|
||||||
const patreon = App.use((state) => state.logins.patreon);
|
const patreon = App.use((state) => state.logins.patreon);
|
||||||
const [exempt, setExempt] = useState(false);
|
const [exempt, setExempt] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -13,14 +13,17 @@ export function useAdExempt() {
|
|||||||
|
|
||||||
if (!(patreon.token in cache)) {
|
if (!(patreon.token in cache)) {
|
||||||
cache[patreon.token] = new Promise<boolean>(async (resolve) => {
|
cache[patreon.token] = new Promise<boolean>(async (resolve) => {
|
||||||
const response = await fetch(
|
const response = await fetch(`/api/patreon/membership`, {
|
||||||
`/api/patreon/membership/${patreon.token}`,
|
cache: 'default',
|
||||||
{ cache: 'force-cache' },
|
headers: { token: patreon.token },
|
||||||
);
|
});
|
||||||
|
|
||||||
if (!response.ok) return resolve(false);
|
if (!response.ok) return resolve(false);
|
||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
|
console.log(json);
|
||||||
|
|
||||||
const exempt = typeof json === 'boolean' && json;
|
const exempt = typeof json === 'boolean' && json;
|
||||||
|
|
||||||
cache[patreon.token] = exempt;
|
cache[patreon.token] = exempt;
|
||||||
|
|||||||
@@ -1,7 +1,85 @@
|
|||||||
|
import { assertSecret } from '@blitzkit/core';
|
||||||
import type { APIContext } from 'astro';
|
import type { APIContext } from 'astro';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
export async function GET({}: APIContext) {
|
interface PatreonMembershipResponse {
|
||||||
return Response.json({ hello: Math.random() });
|
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;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
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 HomeHero from '../components/HomeHero.astro';
|
||||||
import { Plugs } from '../components/Plugs.tsx';
|
import { Plugs } from '../components/Plugs.tsx';
|
||||||
import ToolCard from '../components/ToolCard.astro';
|
import ToolCard from '../components/ToolCard.astro';
|
||||||
@@ -24,7 +24,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
|||||||
<Flex maxWidth="64rem" flexGrow="1" gap="4" direction="column">
|
<Flex maxWidth="64rem" flexGrow="1" gap="4" direction="column">
|
||||||
<ToolCard tool={tankopediaTool} />
|
<ToolCard tool={tankopediaTool} />
|
||||||
|
|
||||||
<Ad client:only="react" id="homepage-ad-1" />
|
<AdWithContext client:only="react" id="homepage-ad-1" />
|
||||||
|
|
||||||
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
|
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
|
||||||
{/* <ToolCard tool={playerStatsTool} /> */}
|
{/* <ToolCard tool={playerStatsTool} /> */}
|
||||||
@@ -39,7 +39,7 @@ import BaseLayout from '../layouts/BaseLayout.astro';
|
|||||||
<ToolCard tool={discordTool} />
|
<ToolCard tool={discordTool} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Ad client:only="react" id="homepage-ad-2" />
|
<AdWithContext client:only="react" id="homepage-ad-2" />
|
||||||
|
|
||||||
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
|
<Flex gap="4" direction={{ initial: 'column', sm: 'row' }}>
|
||||||
<ToolCard tool={moreTool} />
|
<ToolCard tool={moreTool} />
|
||||||
|
|||||||
Reference in new issue
Block a user