feat: remove patreon

This commit is contained in:
TrèsAbhi committed 2024-10-18 10:29:58 -05:00
1 parent da8025a0ad
commit 0df58d2d2c
13 files changed
+21 -326

No files matched your search

@@ -3,7 +3,6 @@
import { idToRegion } from '@blitzkit/core';
import { Button, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { useEffect, useState } from 'react';
import type { PatreonAuthResponse } from '../pages/auth/[provider]/_index';
import { App } from '../stores/app';
import { CURRENT_POLICIES_AGREEMENT_INDEX } from '../stores/app/constants';
@@ -30,7 +29,6 @@ export function Checks() {
/**
* Wargaming: 14 (refresh: 7)
* Patreon: 31 (refresh: 15)
*/
function Content() {
const logins = App.use((state) => state.logins);
@@ -103,33 +101,6 @@ function Content() {
});
}
}
if (logins.patreon) {
const expiresIn = logins.patreon.expires - Date.now();
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
if (expiresInDays < 0) {
mutateApp((draft) => {
draft.logins.patreon = undefined;
});
} else if (expiresInDays < 15) {
const { patreon } = appStore.getState().logins;
if (!patreon) return;
fetch(`/api/patreon/refresh/${patreon.refreshToken}`)
.then((response) => response.json() as Promise<PatreonAuthResponse>)
.then((data) => {
mutateApp((draft) => {
draft.logins.patreon = {
token: data.access_token,
refreshToken: data.refresh_token,
expires: Date.now() + data.expires_in * 1000,
};
});
});
}
}
});
if (!showPoliciesAgreement) return null;
@@ -84,8 +84,6 @@ const gameDefinitions = await fetchGameDefinitions();
GitHub
</Link>
<Link href="/docs/patches" color="gray"> Patches </Link>
{/* patreon supporters? */}
</Flex>
</Flex>
@@ -19,7 +19,6 @@ import {
} from '@radix-ui/themes';
import { resolveBranchName } from '../core/blitzkit/branches';
import { homeTool, TOOLS } from '../constants/tools';
import { PatreonIcon } from './PatreonIcon';
import { NavbarAccount } from './NavbarAccount';
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
@@ -176,15 +175,6 @@ const isBranchNamed = branchName !== undefined;
>
<DiscordLogoIcon />
</Link>
<Link
style={{ display: 'flex', alignContent: 'center' }}
color="gray"
href="https://www.patreon.com/tresabhi"
underline="none"
target="_blank"
>
<PatreonIcon width="0.75em" height="0.75em" />
</Link>
<Link
style={{ display: 'flex', alignContent: 'center' }}
color="gray"
@@ -1,20 +1,15 @@
import { patreonLoginUrl } from '@blitzkit/core';
import { PersonIcon } from '@radix-ui/react-icons';
import {
Button,
Flex,
IconButton,
Inset,
Link,
Popover,
Separator,
Skeleton,
Text,
} from '@radix-ui/themes';
import { Suspense } from 'react';
import { App } from '../../stores/app';
import { BlitzKitTheme } from '../BlitzKitTheme';
import { PatreonIcon } from '../PatreonIcon';
import { WargamingAccountName } from '../WargamingAccountName';
import { WargamingIcon } from '../WargamingIcon';
import { WargamingLoginButton } from '../WargamingLoginButton';
@@ -46,30 +41,12 @@ function Content() {
width="320px"
onOpenAutoFocus={(event) => event.preventDefault()}
>
{(logins.patreon || logins.wargaming) && (
{logins.wargaming && (
<Flex direction="column" gap="2">
<Text align="center" color="gray">
Logged in accounts
</Text>
{logins.patreon && (
<Flex align="center" gap="2" justify="center">
<PatreonIcon width={15} height={15} />
<Text>Patreon</Text>
<Button
color="red"
variant="ghost"
onClick={() => {
mutateApp((draft) => {
draft.logins.patreon = undefined;
});
}}
>
Logout
</Button>
</Flex>
)}
{logins.wargaming && (
<Flex align="center" gap="2" justify="center">
<WargamingIcon width={15} height={15} />
@@ -96,26 +73,18 @@ function Content() {
</Flex>
)}
{!logins.patreon !== !logins.wargaming && (
{/* {!logins. !== !logins.wargaming && (
<Inset side="x">
<Separator my="4" size="4" />
</Inset>
)}
)} */}
{(!logins.patreon || !logins.wargaming) && (
{!logins.wargaming && (
<Flex direction="column" gap="2">
<Text align="center" color="gray">
Log in with...
</Text>
{!logins.patreon && (
<Link style={{ width: '100%' }} href={patreonLoginUrl()}>
<Button style={{ width: '100%' }} color="tomato">
<PatreonIcon width={15} height={15} /> Patreon
</Button>
</Link>
)}
{!logins.wargaming && <WargamingLoginButton />}
</Flex>
)}
@@ -1,13 +0,0 @@
import type { ComponentProps } from 'react';
export function PatreonIcon(props: ComponentProps<'svg'>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436 476" {...props}>
<path
fill="currentColor"
data-fill="1"
d="M436 143c-.084-60.778-47.57-110.591-103.285-128.565C263.528-7.884 172.279-4.649 106.214 26.424 26.142 64.089.988 146.596.051 228.883c-.77 67.653 6.004 245.841 106.83 247.11 74.917.948 86.072-95.279 120.737-141.623 24.662-32.972 56.417-42.285 95.507-51.929C390.309 265.865 436.097 213.011 436 143Z"
></path>
</svg>
);
}
+16 -49
View File
@@ -1,9 +1,7 @@
import { assertSecret, patreonLoginUrl } from '@blitzkit/core';
import { assertSecret } from '@blitzkit/core';
import { ArrowRightIcon } from '@radix-ui/react-icons';
import { Button, Code, Flex, Link, Separator, Text } from '@radix-ui/themes';
import { useAdExempt } from '../hooks/useAdExempt';
import { Button, Code, Flex, Link, Text } from '@radix-ui/themes';
import { App } from '../stores/app';
import { PatreonIcon } from './PatreonIcon';
export function Plugs() {
return (
@@ -14,9 +12,6 @@ export function Plugs() {
}
function Content() {
const exempt = useAdExempt();
const hasPatreon = App.use((state) => state.logins.patreon !== undefined);
return (
<Flex
align="center"
@@ -28,50 +23,22 @@ function Content() {
>
{assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === 'true' &&
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH) !== 'opentest' && (
<>
<Flex direction="column" gap="3" align="center">
<Text size="5">
BlitzKit{' '}
<Code variant="outline" size="4" color="gray" highContrast>
opentest
</Code>{' '}
is available now{' '}
</Text>
<Flex direction="column" gap="3" align="center">
<Text size="5">
BlitzKit{' '}
<Code variant="outline" size="4" color="gray" highContrast>
opentest
</Code>{' '}
is available now{' '}
</Text>
<Link href="https://opentest.blitzkit.app/" target="_blank">
<Button color="green">
BlitzKit opentest <ArrowRightIcon />
</Button>
</Link>
</Flex>
<Separator size="3" />
</>
)}
<Flex direction="column" gap="3" align="center">
<Flex gap="1" align="center">
<Text size="4" align="center">
{exempt
? 'You are supporting BlitzKit, thank you!'
: 'Active Patreon supports go ad-free'}
</Text>
</Flex>
<Flex gap="2" wrap="wrap" justify="center">
<Link href="https://www.patreon.com/tresabhi" target="_blank">
<Button>
<PatreonIcon width={15} height={15} />{' '}
{exempt ? 'Manage membership' : 'Support on Patreon'}
</Button>
</Link>
{!hasPatreon && !exempt && (
<Link href={patreonLoginUrl()}>
<Button variant="outline">Log in with Patreon</Button>
<Link href="https://opentest.blitzkit.app/" target="_blank">
<Button color="green">
BlitzKit opentest <ArrowRightIcon />
</Button>
</Link>
)}
</Flex>
</Flex>
</Flex>
)}
</Flex>
);
}
@@ -16,7 +16,6 @@ import { times } from 'lodash-es';
import { memo, useEffect, useMemo, useState } from 'react';
import { filterTank } from '../../core/blitzkit/filterTank';
import { resolveReload } from '../../core/blitzkit/resolveReload';
import { useAdExempt } from '../../hooks/useAdExempt';
import { $tankFilters } from '../../stores/tankFilters';
import {
SORT_NAMES,
@@ -49,7 +48,6 @@ const tankNames = await fetchTankNames();
export const TankSearch = memo<TankSearchProps>(
({ compact, onSelect, onSelectAll, ...props }) => {
const awaitedTanksDefinitionsArray = Object.values(tankDefinitions.tanks);
const exempt = useAdExempt();
const tankFilters = useStore($tankFilters);
const tankopediaSort = useStore($tankopediaSort);
@@ -26,7 +26,6 @@ import { Fragment } from 'react/jsx-runtime';
import { lerp } from 'three/src/math/MathUtils.js';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
import { useAdExempt } from '../../../../../hooks/useAdExempt';
import { useEquipment } from '../../../../../hooks/useEquipment';
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
import { Duel } from '../../../../../stores/duel';
@@ -39,7 +38,6 @@ const provisionDefinitions = await fetchProvisionDefinitions();
export function Characteristics() {
const mutateDuel = Duel.useMutation();
const exempt = useAdExempt();
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
const penetrationDistanceInput = useRef<HTMLInputElement>(null);
const hasImprovedVentilation = useEquipment(102);
-36
View File
@@ -1,36 +0,0 @@
import { useEffect, useState } from 'react';
import { App } from '../stores/app';
let cache: Record<string, boolean | Promise<boolean>> = {};
export function useAdExempt() {
const patreon = App.use((state) => state.logins.patreon);
const [exempt, setExempt] = useState(false);
useEffect(() => {
(async () => {
if (!patreon) return setExempt(false);
if (!(patreon.token in cache)) {
cache[patreon.token] = new Promise<boolean>(async (resolve) => {
const response = await fetch(`/api/patreon/membership`, {
cache: 'default',
headers: { token: patreon.token },
});
if (!response.ok) return resolve(false);
const json = await response.json();
const exempt = typeof json === 'boolean' && json;
cache[patreon.token] = exempt;
resolve(exempt);
});
}
setExempt(await cache[patreon.token]);
})();
}, []);
return exempt;
}
@@ -1,34 +0,0 @@
import { assertSecret, type BlitzkitResponse } from '@blitzkit/core';
import type { APIContext } from 'astro';
export const prerender = false;
export async function GET({ request, params }: APIContext) {
const code = request.headers.get('code');
if (!code) {
return Response.json({
status: 'error',
error: 'UNDEFINED_CODE',
} satisfies BlitzkitResponse);
}
const response = await fetch(
`https://www.patreon.com/api/oauth2/token?code=${
code
}&grant_type=authorization_code&client_id=${assertSecret(
import.meta.env.PUBLIC_PATREON_CLIENT_ID,
)}&client_secret=${assertSecret(
import.meta.env.PATREON_CLIENT_SECRET,
)}&redirect_uri=${assertSecret(
import.meta.env.PUBLIC_PATREON_REDIRECT_URI,
)}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
},
);
const json = await response.json();
return Response.json(json);
}
@@ -1,83 +0,0 @@
import { assertSecret } from '@blitzkit/core';
import type { APIContext } from 'astro';
export const prerender = false;
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);
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,24 +1,14 @@
import { useEffect } from 'react';
import { App } from '../../../stores/app';
export const AUTH_PROVIDERS = ['wargaming', 'patreon'] as const;
export const AUTH_PROVIDERS = ['wargaming'] as const;
type AuthProvider = (typeof AUTH_PROVIDERS)[number];
export const AUTH_PROVIDER_NAMES: Record<AuthProvider, string> = {
patreon: 'Patreon',
wargaming: 'Wargaming',
};
export interface PatreonAuthResponse {
access_token: string;
expires_in: number;
token_type: 'Bearer';
scope: string;
refresh_token: string;
version: string;
}
interface AuthorizeProps {
provider: AuthProvider;
}
@@ -59,25 +49,6 @@ function Content({ provider }: AuthorizeProps) {
break;
}
case 'patreon': {
if (!searchParams.get('code')) break;
const response = await fetch('/api/patreon/auth', {
headers: { code: searchParams.get('code')! },
});
const data = (await response.json()) as PatreonAuthResponse;
mutateApp((draft) => {
draft.logins.patreon = {
token: data.access_token,
refreshToken: data.refresh_token,
expires: Date.now() + data.expires_in * 1000,
};
});
break;
}
default:
throw new Error(`Unknown provider: ${provider}`);
}
-1
View File
@@ -8,7 +8,6 @@ interface AppStore {
policiesAgreementIndex: number;
logins: {
wargaming?: { token: string; id: number; expires: number };
patreon?: { token: string; refreshToken: string; expires: number };
};
}