diff --git a/src/app/api/auth/patreon/[code]/route.ts b/src/app/api/patreon/auth/[code]/route.ts similarity index 100% rename from src/app/api/auth/patreon/[code]/route.ts rename to src/app/api/patreon/auth/[code]/route.ts diff --git a/src/app/api/patreon/membership/[token]/route.ts b/src/app/api/patreon/membership/[token]/route.ts new file mode 100644 index 000000000..8c1a42e15 --- /dev/null +++ b/src/app/api/patreon/membership/[token]/route.ts @@ -0,0 +1,75 @@ +import { NextResponse } from 'next/server'; + +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: Request, + { params }: { params: { token: string } }, +) { + const response = await fetch( + encodeURI( + 'https://www.patreon.com/api/oauth2/v2/identity?include=memberships.campaign&fields[member]=patron_status', + ), + { headers: { Authorization: `Bearer ${params.token}` } }, + ); + + if (!response.ok) return NextResponse.json(false); + + const json = (await response.json()) as PatreonMembershipResponse; + + return NextResponse.json( + json.included.some( + (item) => + item.type === 'member' && + item.relationships.campaign.data.id === + process.env.PATREON_CAMPAIGN_ID && + item.attributes.patron_status === 'active_patron', + ), + ); +} diff --git a/src/app/api/auth/patreon/refresh/[token]/route.ts b/src/app/api/patreon/refresh/[token]/route.ts similarity index 100% rename from src/app/api/auth/patreon/refresh/[token]/route.ts rename to src/app/api/patreon/refresh/[token]/route.ts diff --git a/src/app/auth/[provider]/page.tsx b/src/app/auth/[provider]/page.tsx index 43323a52b..4c63ad7dc 100644 --- a/src/app/auth/[provider]/page.tsx +++ b/src/app/auth/[provider]/page.tsx @@ -51,7 +51,7 @@ export default function Page({ if (!searchParams.code) break; const data = (await fetch( - `/api/auth/patreon/${searchParams.code}`, + `/api/patreon/auth/${searchParams.code}`, ).then((response) => response.json())) as PatreonAuthResponse; mutateApp((draft) => { diff --git a/src/components/Ad.tsx b/src/components/Ad.tsx index b785a6df6..5e7a973ad 100644 --- a/src/components/Ad.tsx +++ b/src/components/Ad.tsx @@ -1,10 +1,11 @@ 'use client'; -import { Box, BoxProps, Flex, Text } from '@radix-ui/themes'; +import { Box, BoxProps, Text } from '@radix-ui/themes'; import { uniqueId } from 'lodash'; import { useEffect, useRef } from 'react'; import { Vector2Tuple } from 'three'; import { imgur } from '../core/blitzkit/imgur'; +import { useAdExempt } from '../hooks/useAdExempt'; export enum AdType { MediumRectangleHorizontalPurple = 738182777, @@ -28,6 +29,8 @@ export function Ad({ type, style, ...props }: AdProps) { const id = useRef(uniqueId()); const dimensions = AD_DIMENSIONS[type]; + useAdExempt(); + useEffect(() => { (window as any).msAdsQueue.push(function () { (window as any).mmnow.render({ @@ -51,19 +54,18 @@ export function Ad({ type, style, ...props }: AdProps) { }} {...props} > - - Advertisement - - {dimensions[0]}x{dimensions[1]} - - + Advertisement + state.logins); @@ -191,7 +194,7 @@ export default function Navbar() { {!logins.patreon && (