api: add subscription API (#1491)

This commit is contained in:
Huang Xin
2025-06-29 20:38:04 +08:00
committed by GitHub
parent 9f343992b2
commit d5d0a9abd3
19 changed files with 623 additions and 98 deletions
+3
View File
@@ -7,3 +7,6 @@ NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64="cGhjX2ViNXowbVRxWm8yZm5YYnZGNmE3bFh5TThp
NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64="aHR0cHM6Ly9yZWFkZXN0LnN1cGFiYXNlLmNv"
NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64="ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKemRYQmhZbUZ6WlNJc0luSmxaaUk2SW5aaWMzbDRablZ6YW1weFpIaHJhbkZzZVhOaklpd2ljbTlzWlNJNkltRnViMjRpTENKcFlYUWlPakUzTXpReE1qTTJOekVzSW1WNGNDSTZNakEwT1RZNU9UWTNNWDAuM1U1VXFhb3VfMVNnclZlMWVvOXJBcGMwdUtqcWhwUWRVWGh2d1VIbVVmZw=="
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_DEV_BASE64="cGtfdGVzdF81MVJmQmdLRTdSWW5pTWsxc0tDV2RUd2hMZzcySzk4eDRWcjlIdDdsRFBONngzcnpZYmhydGtNQnpDdzZKbHFaRVVITVp5eVNjVXhCZXVkcGppWTk0WXNHcDAweFlRRnRRaUU="
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_BASE64="cGtfbGl2ZV81MVFYN3dRRU5ndjJFOUxQRHpZUlE5TlJJeTNjd09EZ1AzSkNFRHRPWlFtdFJWc3Brd053ZE1NNUpIVnVPTmJWcjZ3VGFCMUNZR1pJMmRPVWppTkY0bHJvVjAwalE4TkpkdWk="
+4 -1
View File
@@ -8,11 +8,14 @@ if (isDev) {
initOpenNextCloudflareForDev();
}
const exportOutput = appPlatform !== 'web' && !isDev;
/** @type {import('next').NextConfig} */
const nextConfig = {
// Ensure Next.js uses SSG instead of SSR
// https://nextjs.org/docs/pages/building-your-application/deploying/static-exports
output: appPlatform === 'web' || isDev ? undefined : 'export',
output: exportOutput ? 'export' : undefined,
pageExtensions: exportOutput ? ['jsx', 'tsx'] : ['js', 'jsx', 'ts', 'tsx'],
// Note: This feature is required to use the Next.js Image component in SSG mode.
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
images: {
+2
View File
@@ -43,6 +43,7 @@
"@ducanh2912/next-pwa": "^10.2.9",
"@fabianlars/tauri-plugin-oauth": "2",
"@opennextjs/cloudflare": "^1.3.1",
"@stripe/stripe-js": "^7.4.0",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/supabase-js": "^2.49.10",
@@ -83,6 +84,7 @@
"react-responsive": "^10.0.0",
"react-window": "^1.8.11",
"semver": "^7.7.1",
"stripe": "^18.2.1",
"tinycolor2": "^1.6.0",
"zustand": "5.0.1"
},
@@ -0,0 +1,36 @@
import Stripe from 'stripe';
import { NextResponse } from 'next/server';
import { getStripe } from '@/libs/stripe/server';
import { createSubscription } from '@/utils/stripe';
import { validateUserAndToken } from '@/utils/access';
export async function POST(request: Request) {
const { sessionId } = await request.json();
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
}
try {
const stripe = getStripe();
const session = await stripe.checkout.sessions.retrieve(sessionId);
if (session.payment_status === 'paid') {
const customerId = session.customer as string;
const subscriptionId = session.subscription as string;
await createSubscription(user.id, customerId, subscriptionId);
}
return NextResponse.json({ session });
} catch (error) {
if (error instanceof Stripe.errors.StripeError) {
console.error('Stripe error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 },
);
}
}
@@ -0,0 +1,65 @@
import { NextRequest, NextResponse } from 'next/server';
import { getStripe } from '@/libs/stripe/server';
import { validateUserAndToken } from '@/utils/access';
import { supabase } from '@/utils/supabase';
export async function POST(request: NextRequest) {
const { priceId, metadata = {} } = await request.json();
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
}
const enhancedMetadata = {
...metadata,
userId: user.id,
};
try {
const { data: customerData } = await supabase
.from('customers')
.select('stripe_customer_id')
.eq('user_id', user.id)
.single();
let customerId;
if (!customerData?.stripe_customer_id) {
const stripe = getStripe();
const customer = await stripe.customers.create({
email: user.email,
metadata: {
userId: user.id,
},
});
customerId = customer.id;
await supabase.from('customers').insert({
user_id: user.id,
stripe_customer_id: customerId,
});
} else {
customerId = customerData.stripe_customer_id;
}
const stripe = getStripe();
const session = await stripe.checkout.sessions.create({
customer: customerId,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: priceId,
quantity: 1,
},
],
metadata: enhancedMetadata,
success_url: `${request.headers.get('origin')}/user/subscription/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${request.headers.get('origin')}/user`,
});
return NextResponse.json({ sessionId: session.id, url: session.url });
} catch (error) {
console.error(error);
return NextResponse.json({ error: 'Error creating checkout session' }, { status: 500 });
}
}
@@ -0,0 +1,39 @@
import Stripe from 'stripe';
import { NextResponse } from 'next/server';
import { getStripe } from '@/libs/stripe/server';
import { UserPlan } from '@/types/user';
export async function GET() {
try {
const stripe = getStripe();
const prices = await stripe.prices.list({
expand: ['data.product'],
active: true,
type: 'recurring',
});
const plans = prices.data
.filter((price) => {
const product = price.product as Stripe.Product;
return product.active === true;
})
.map((price) => {
const product = price.product as Stripe.Product & {
metadata: { plan: UserPlan };
};
return {
plan: product.metadata.plan,
price_id: price.id,
price: price.unit_amount,
currency: price.currency,
interval: price.recurring?.interval,
product: price.product,
};
});
return NextResponse.json(plans);
} catch (error) {
console.error(error);
return NextResponse.json({ error: 'Error fetching subscription plans' }, { status: 500 });
}
}
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from 'next/server';
import { getStripe } from '@/libs/stripe/server';
import { validateUserAndToken } from '@/utils/access';
import { supabase } from '@/utils/supabase';
export async function POST(request: NextRequest) {
const { user, token } = await validateUserAndToken(request.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
}
try {
const { data: customerData } = await supabase
.from('customers')
.select('stripe_customer_id')
.eq('user_id', user.id)
.single();
if (!customerData?.stripe_customer_id) {
throw new Error('Customer not found');
}
const stripe = getStripe();
const session = await stripe.billingPortal.sessions.create({
customer: customerData.stripe_customer_id,
return_url: `${request.headers.get('origin')}/user`,
});
return NextResponse.json({ url: session.url });
} catch (error) {
console.error(error);
return NextResponse.json({ error: 'Error creating portal session' }, { status: 500 });
}
}
@@ -0,0 +1,196 @@
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
import { getStripe } from '@/libs/stripe/server';
import { createSubscription } from '@/utils/stripe';
import { createSupabaseAdminClient } from '@/utils/supabase';
export async function POST(request: NextRequest) {
try {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
if (!signature) {
return NextResponse.json({ error: 'Missing Stripe signature' }, { status: 401 });
}
const stripe = getStripe();
let event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env['STRIPE_WEBHOOK_SECRET']!,
);
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
console.error(`Webhook signature verification failed: ${message}`);
return NextResponse.json(
{
error: `Webhook signature verification failed: ${message}`,
},
{ status: 400 },
);
}
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
const userId = session.metadata?.['userId'];
if (userId) {
if (session.mode === 'subscription') {
await handleSuccessfulSubscription(session, userId);
} else {
await handleSuccessfulPayment(session, userId);
}
}
break;
case 'invoice.paid':
await handleSuccessfulInvoice(event.data.object);
break;
case 'invoice.payment_failed':
await handleFailedInvoice(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionCancelled(event.data.object);
break;
}
return NextResponse.json({ received: true });
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Webhook error:', message);
return NextResponse.json({ error: message }, { status: 500 });
}
}
async function handleSuccessfulPayment(session: Stripe.Checkout.Session, userId: string) {
const supabase = createSupabaseAdminClient();
await supabase.from('payments').insert({
user_id: userId,
stripe_checkout_id: session.id,
amount: session.amount_total,
currency: session.currency,
status: 'completed',
payment_intent: session.payment_intent,
payment_method: session.payment_method_types?.[0] || 'unknown',
created_at: new Date().toISOString(),
});
}
async function handleSuccessfulSubscription(session: Stripe.Checkout.Session, userId: string) {
const customerId = session.customer as string;
const subscriptionId = session.subscription as string;
await createSubscription(userId, customerId, subscriptionId);
}
async function handleSuccessfulInvoice(invoice: Stripe.Invoice) {
const customerId = invoice.customer;
const subscriptionId = invoice.parent?.subscription_details?.subscription;
if (!subscriptionId) {
return;
}
const supabase = createSupabaseAdminClient();
const { data: customerData } = await supabase
.from('customers')
.select('user_id')
.eq('stripe_customer_id', customerId)
.single();
if (!customerData?.user_id) {
console.error('Customer not found:', customerId);
return;
}
await supabase
.from('subscriptions')
.update({
status: 'active',
current_period_end: new Date(invoice.lines.data[0]!.period.end * 1000).toISOString(),
})
.eq('stripe_subscription_id', subscriptionId);
await supabase
.from('plans')
.update({
status: 'active',
})
.eq('user_id', customerData.user_id);
}
async function handleFailedInvoice(invoice: Stripe.Invoice) {
const customerId = invoice.customer;
const subscriptionId = invoice.parent?.subscription_details?.subscription;
if (!subscriptionId) {
return;
}
const supabase = createSupabaseAdminClient();
const { data: customerData } = await supabase
.from('customers')
.select('user_id')
.eq('stripe_customer_id', customerId)
.single();
if (!customerData?.user_id) {
console.error('Customer not found:', customerId);
return;
}
await supabase
.from('subscriptions')
.update({
status: 'past_due',
})
.eq('stripe_subscription_id', subscriptionId);
await supabase
.from('plans')
.update({
status: 'past_due',
})
.eq('id', customerData.user_id);
}
async function handleSubscriptionCancelled(subscription: Stripe.Subscription) {
const subscriptionId = subscription.id;
const supabase = createSupabaseAdminClient();
await supabase
.from('subscriptions')
.update({
status: 'cancelled',
cancelled_at: new Date().toISOString(),
})
.eq('stripe_subscription_id', subscriptionId);
const { data: subscriptionData } = await supabase
.from('subscriptions')
.select('user_id')
.eq('stripe_subscription_id', subscriptionId)
.single();
if (subscriptionData?.user_id) {
await supabase
.from('plans')
.update({
plan: 'free',
status: 'cancelled',
})
.eq('id', subscriptionData.user_id);
}
}
// This is needed to parse the body as a stream for the webhook signature verification
export const config = {
api: {
bodyParser: false,
},
};
@@ -0,0 +1,14 @@
import { loadStripe, Stripe } from '@stripe/stripe-js';
let stripePromise: Promise<Stripe | null>;
export const getStripe = () => {
if (!stripePromise) {
const publishableKey =
process.env.NODE_ENV === 'production'
? process.env['NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_BASE64']
: process.env['NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_DEV_BASE64'];
stripePromise = loadStripe(atob(publishableKey!));
}
return stripePromise;
};
@@ -0,0 +1,16 @@
import Stripe from 'stripe';
let stripe: Stripe | null;
export const getStripe = () => {
if (!stripe) {
const stripeSecretKey =
process.env.NODE_ENV === 'production'
? process.env['STRIPE_SECRET_KEY']
: process.env['STRIPE_SECRET_KEY_DEV'];
stripe = new Stripe(stripeSecretKey!, {
httpClient: Stripe.createFetchHttpClient(),
});
}
return stripe;
};
+47
View File
@@ -0,0 +1,47 @@
import { NextRequest, NextResponse } from 'next/server';
const allowedOrigins = [
'https://web.readest.com',
'https://tauri.localhost',
'http://tauri.localhost',
'tauri://localhost',
'http://localhost:3000',
'http://localhost:3001',
];
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
};
export function middleware(request: NextRequest) {
const origin = request.headers.get('origin') ?? '';
const isAllowedOrigin = allowedOrigins.includes(origin);
if (request.method === 'OPTIONS') {
const preflightHeaders = new Headers({
...corsOptions,
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
});
return new NextResponse(null, {
status: 200,
headers: preflightHeaders,
});
}
const response = NextResponse.next();
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin);
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
}
export const config = {
matcher: ['/api/stripe/:path*'],
};
@@ -1,9 +1,8 @@
import crypto from 'crypto';
import { supabase } from '@/utils/supabase';
import { NextApiRequest, NextApiResponse } from 'next';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import { getDailyTranslationPlanData, getUserPlan } from '@/utils/access';
import { getDailyTranslationPlanData, getUserPlan, validateUserAndToken } from '@/utils/access';
import { ErrorCodes } from '@/services/translators';
const DEFAULT_DEEPL_FREE_API = 'https://api-free.deepl.com/v2/translate';
@@ -19,19 +18,6 @@ interface CloudflareEnv {
TRANSLATIONS_KV?: KVNamespace;
}
const getUserAndToken = async (authHeader: string | undefined) => {
if (!authHeader) return {};
const token = authHeader.replace('Bearer ', '');
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error || !user) return {};
return { user, token };
};
const LANG_V2_V1_MAP: Record<string, string> = {
'ZH-HANS': 'ZH',
'ZH-HANT': 'ZH-TW',
@@ -58,7 +44,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const env = (getCloudflareContext().env || {}) as CloudflareEnv;
const hasKVCache = !!env['TRANSLATIONS_KV'];
const { user, token } = await getUserAndToken(req.headers['authorization']);
const { user, token } = await validateUserAndToken(req.headers['authorization']);
const { DEEPL_PRO_API, DEEPL_FREE_API } = process.env;
const deepFreeApiUrl = DEEPL_FREE_API || DEFAULT_DEEPL_FREE_API;
const deeplProApiUrl = DEEPL_PRO_API || DEFAULT_DEEPL_PRO_API;
@@ -1,20 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase, createSupabaseClient } from '@/utils/supabase';
import { createSupabaseClient } from '@/utils/supabase';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { getDownloadSignedUrl } from '@/utils/object';
const getUserAndToken = async (authHeader: string | undefined) => {
if (!authHeader) return {};
const token = authHeader.replace('Bearer ', '');
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error || !user) return {};
return { user, token };
};
import { validateUserAndToken } from '@/utils/access';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await runMiddleware(req, res, corsAllMethods);
@@ -24,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
try {
const { user, token } = await getUserAndToken(req.headers['authorization']);
const { user, token } = await validateUserAndToken(req.headers['authorization']);
if (!user || !token) {
return res.status(403).json({ error: 'Not authenticated' });
}
@@ -1,22 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase, createSupabaseClient } from '@/utils/supabase';
import { createSupabaseClient } from '@/utils/supabase';
import { corsAllMethods, runMiddleware } from '@/utils/cors';
import { getStoragePlanData } from '@/utils/access';
import { getStoragePlanData, validateUserAndToken } from '@/utils/access';
import { getUploadSignedUrl } from '@/utils/object';
const getUserAndToken = async (authHeader: string | undefined) => {
if (!authHeader) return {};
const token = authHeader.replace('Bearer ', '');
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error || !user) return {};
return { user, token };
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await runMiddleware(req, res, corsAllMethods);
@@ -25,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
try {
const { user, token } = await getUserAndToken(req.headers['authorization']);
const { user, token } = await validateUserAndToken(req.headers['authorization']);
if (!user || !token) {
return res.status(403).json({ error: 'Not authenticated' });
}
@@ -74,11 +61,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
try {
const uploadUrl = await getUploadSignedUrl(
fileKey,
objSize,
1800,
);
const uploadUrl = await getUploadSignedUrl(fileKey, objSize, 1800);
res.status(200).json({
uploadUrl,
+8 -28
View File
@@ -1,13 +1,14 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextRequest, NextResponse } from 'next/server';
import { PostgrestError } from '@supabase/supabase-js';
import { supabase, createSupabaseClient } from '@/utils/supabase';
import { createSupabaseClient } from '@/utils/supabase';
import { BookDataRecord } from '@/types/book';
import { transformBookConfigToDB } from '@/utils/transform';
import { transformBookNoteToDB } from '@/utils/transform';
import { transformBookToDB } from '@/utils/transform';
import { runMiddleware, corsAllMethods } from '@/utils/cors';
import { SyncData, SyncResult, SyncType } from '@/libs/sync';
import { validateUserAndToken } from '@/utils/access';
const transformsToDB = {
books: transformBookToDB,
@@ -25,31 +26,10 @@ type TableName = keyof typeof transformsToDB;
type DBError = { table: TableName; error: PostgrestError };
const getUserAndToken = async (req: NextRequest) => {
const authHeader = req.headers.get('authorization');
if (!authHeader) return {};
const token = authHeader.replace('Bearer ', '');
try {
const {
data: { user },
error,
} = await supabase.auth.getUser(token);
if (error?.message === 'fetch failed') {
return { error: 'Network error' };
} else if (error || !user) {
return { error: 'Not authenticated' };
}
return { user, token };
} catch {
return { error: 'Network error' };
}
};
export async function GET(req: NextRequest) {
const { user, token, error } = await getUserAndToken(req);
if (!user || !token || error) {
return NextResponse.json({ error: error || 'Unknown error' }, { status: 401 });
const { user, token } = await validateUserAndToken(req.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
}
const supabase = createSupabaseClient(token);
@@ -121,9 +101,9 @@ export async function GET(req: NextRequest) {
}
export async function POST(req: NextRequest) {
const { user, token, error } = await getUserAndToken(req);
if (!user || !token || error) {
return NextResponse.json({ error: error || 'Unknown error' }, { status: 401 });
const { user, token } = await validateUserAndToken(req.headers.get('authorization'));
if (!user || !token) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 403 });
}
const supabase = createSupabaseClient(token);
+3 -3
View File
@@ -555,9 +555,9 @@ export const DEFAULT_STORAGE_QUOTA: UserStorageQuota = {
};
export const DEFAULT_DAILY_TRANSLATION_QUOTA: UserDailyTranslationQuota = {
free: 50 * 1024,
plus: 500 * 1024,
pro: 1024 * 1024,
free: 10 * 1024,
plus: 50 * 1024,
pro: 200 * 1024,
};
export const DOUBLE_CLICK_INTERVAL_THRESHOLD_MS = 250;
+1 -1
View File
@@ -76,7 +76,7 @@ export const getUserID = async (): Promise<string | null> => {
return data?.session?.user?.id ?? null;
};
export const validateUserAndToken = async (authHeader: string | undefined) => {
export const validateUserAndToken = async (authHeader: string | null | undefined) => {
if (!authHeader) return {};
const token = authHeader.replace('Bearer ', '');
+65
View File
@@ -0,0 +1,65 @@
import Stripe from 'stripe';
import { getStripe } from '@/libs/stripe/server';
import { createSupabaseAdminClient } from './supabase';
import { UserPlan } from '@/types/user';
export const createSubscription = async (
userId: string,
customerId: string,
subscriptionId: string,
) => {
const stripe = getStripe();
const supabase = createSupabaseAdminClient();
const subscription = await stripe.subscriptions.retrieve(subscriptionId, {
expand: ['items.data.price.product'],
});
const subscriptionItem = subscription.items.data[0]!;
const priceId = subscriptionItem.price.id;
const product = subscriptionItem.price.product as Stripe.Product & {
metadata: { plan: UserPlan };
};
const plan = product.metadata['plan'] || 'free';
try {
const { data: existingSubscription } = await supabase
.from('subscriptions')
.select('*')
.eq('stripe_subscription_id', subscriptionId)
.single();
const period_start = new Date(subscriptionItem.current_period_start * 1000).toISOString();
const period_end = new Date(subscriptionItem.current_period_end * 1000).toISOString();
if (existingSubscription) {
await supabase
.from('subscriptions')
.update({
status: subscription.status,
current_period_start: period_start,
current_period_end: period_end,
})
.eq('id', existingSubscription.id);
} else {
await supabase.from('subscriptions').insert({
user_id: userId,
stripe_customer_id: customerId,
stripe_subscription_id: subscriptionId,
stripe_price_id: priceId,
status: subscription.status,
current_period_start: period_start,
current_period_end: period_end,
created_at: new Date().toISOString(),
});
}
} catch (error) {
console.error('Error checking existing subscription:', error);
}
await supabase
.from('plans')
.update({
plan: plan,
status: subscription.status,
})
.eq('id', userId);
};
+81 -13
View File
@@ -53,6 +53,9 @@ importers:
'@opennextjs/cloudflare':
specifier: ^1.3.1
version: 1.3.1(wrangler@4.21.0)
'@stripe/stripe-js':
specifier: ^7.4.0
version: 7.4.0
'@supabase/auth-ui-react':
specifier: ^0.4.7
version: 0.4.7(@supabase/supabase-js@2.49.10)
@@ -173,6 +176,9 @@ importers:
semver:
specifier: ^7.7.1
version: 7.7.2
stripe:
specifier: ^18.2.1
version: 18.2.1(@types/node@22.15.31)
tinycolor2:
specifier: ^1.6.0
version: 1.6.0
@@ -329,24 +335,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@ast-grep/napi-linux-arm64-musl@0.35.0':
resolution: {integrity: sha512-1EcvHPwyWpCL/96LuItBYGfeI5FaMTRvL+dHbO/hL5q1npqbb5qn+ppJwtNOjTPz8tayvgggxVk9T4C2O7taYA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@ast-grep/napi-linux-x64-gnu@0.35.0':
resolution: {integrity: sha512-FDzNdlqmQnsiWXhnLxusw5AOfEcEM+5xtmrnAf3SBRFr86JyWD9qsynnFYC2pnP9hlMfifNH2TTmMpyGJW49Xw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@ast-grep/napi-linux-x64-musl@0.35.0':
resolution: {integrity: sha512-wlmndjfBafT8u5p4DBnoRQyoCSGNuVSz7rT3TqhvlHcPzUouRWMn95epU9B1LNLyjXvr9xHeRjSktyCN28w57Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@ast-grep/napi-win32-arm64-msvc@0.35.0':
resolution: {integrity: sha512-gkhJeYc4rrZLX2icLxalPikTLMR57DuIYLwLr9g+StHYXIsGHrbfrE6Nnbdd8Izfs34ArFCrcwdaMrGlvOPSeg==}
@@ -1551,138 +1561,163 @@ packages:
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm64@1.1.0':
resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.1.0':
resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.1.0':
resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.1.0':
resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.1.0':
resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-arm64@1.1.0':
resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.1.0':
resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm64@0.34.2':
resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.2':
resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.2':
resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.2':
resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-arm64@0.34.2':
resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.2':
resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
@@ -1781,30 +1816,35 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-arm64-musl@0.1.70':
resolution: {integrity: sha512-wBTOllEYNfJCHOdZj9v8gLzZ4oY3oyPX8MSRvaxPm/s7RfEXxCyZ8OhJ5xAyicsDdbE5YBZqdmaaeP5+xKxvtg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.70':
resolution: {integrity: sha512-GVUUPC8TuuFqHip0rxHkUqArQnlzmlXmTEBuXAWdgCv85zTCFH8nOHk/YCF5yo0Z2eOm8nOi90aWs0leJ4OE5Q==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-gnu@0.1.70':
resolution: {integrity: sha512-/kvUa2lZRwGNyfznSn5t1ShWJnr/m5acSlhTV3eXECafObjl0VBuA1HJw0QrilLpb4Fe0VLywkpD1NsMoVDROQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@napi-rs/canvas-linux-x64-musl@0.1.70':
resolution: {integrity: sha512-aqlv8MLpycoMKRmds7JWCfVwNf1fiZxaU7JwJs9/ExjTD8lX2KjsO7CTeAj5Cl4aEuzxUWbJPUUE2Qu9cZ1vfg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@napi-rs/canvas-win32-x64-msvc@0.1.70':
resolution: {integrity: sha512-Q9QU3WIpwBTVHk4cPfBjGHGU4U0llQYRXgJtFtYqqGNEOKVN4OT6PQ+ve63xwIPODMpZ0HHyj/KLGc9CWc3EtQ==}
@@ -1839,24 +1879,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@next/swc-linux-arm64-musl@15.3.3':
resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@next/swc-linux-x64-gnu@15.3.3':
resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@next/swc-linux-x64-musl@15.3.3':
resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@next/swc-win32-arm64-msvc@15.3.3':
resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==}
@@ -2007,46 +2051,55 @@ packages:
resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.28.0':
resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.28.0':
resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.28.0':
resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.28.0':
resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.28.0':
resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.28.0':
resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.28.0':
resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.28.0':
resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.28.0':
resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==}
@@ -2492,6 +2545,10 @@ packages:
'@stitches/core@1.2.8':
resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==}
'@stripe/stripe-js@7.4.0':
resolution: {integrity: sha512-lQHQPfXPTBeh0XFjq6PqSBAyR7umwcJbvJhXV77uGCUDD6ymXJU/f2164ydLMLCCceNuPlbV9b+1smx98efwWQ==}
engines: {node: '>=12.16'}
'@supabase/auth-js@2.69.1':
resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==}
@@ -2564,30 +2621,35 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.6.0':
resolution: {integrity: sha512-lAEzj40VGrn/dPtly9E2dfng82t1xuEdoq0fwjEk6J4oeoKJwD/Kss7vysQHSHq36nKGFoO7gpKa5mxisBGFog==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tauri-apps/cli-linux-riscv64-gnu@2.6.0':
resolution: {integrity: sha512-IWaMX0tQQHFzOudJ0OyC3wamFUr/Qje3qwPSu2okSiHmP1+5enSN3WZZJV5vZ979n/Mak2TFQ8+ivPeSW2fn6g==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-gnu@2.6.0':
resolution: {integrity: sha512-w/zlc2H3c9D4GEjAQOOh/DT701SRT9L8IvuhEMwdWWG6BQEH/+u5tgYR3sVJNDv9WXGNxTCiFV0bd/6pgreC1Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.6.0':
resolution: {integrity: sha512-XSeuj7AWuP6Abg532wvzcgIXLlkZLhFXNDRISp7Zeb/ZNHrYTd4BcO88IPb5LVw5vVmvV5r6iTxDd9oVw/FrmA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.6.0':
resolution: {integrity: sha512-gvmpQkj2vsU3fvJNgSzrX2mw/0DRnkfrg+1GH3wHsCoZq0/xVXG1mXZAxDg4r6e4yCeVuM6Xv2OHpF+Az9+cXA==}
@@ -5312,10 +5374,6 @@ packages:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
@@ -5448,6 +5506,15 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
stripe@18.2.1:
resolution: {integrity: sha512-GwB1B7WSwEBzW4dilgyJruUYhbGMscrwuyHsPUmSRKrGHZ5poSh2oU9XKdii5BFVJzXHn35geRvGJ6R8bYcp8w==}
engines: {node: '>=12.*'}
peerDependencies:
'@types/node': '>=12.x.x'
peerDependenciesMeta:
'@types/node':
optional: true
strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
@@ -9314,6 +9381,8 @@ snapshots:
'@stitches/core@1.2.8': {}
'@stripe/stripe-js@7.4.0': {}
'@supabase/auth-js@2.69.1':
dependencies:
'@supabase/node-fetch': 2.6.15
@@ -11205,7 +11274,7 @@ snapshots:
dependencies:
es-errors: 1.3.0
hasown: 2.0.2
side-channel: 1.0.6
side-channel: 1.1.0
ipaddr.js@1.9.1: {}
@@ -12359,13 +12428,6 @@ snapshots:
object-inspect: 1.13.3
side-channel-map: 1.0.1
side-channel@1.0.6:
dependencies:
call-bind: 1.0.7
es-errors: 1.3.0
get-intrinsic: 1.2.4
object-inspect: 1.13.3
side-channel@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -12459,7 +12521,7 @@ snapshots:
internal-slot: 1.0.7
regexp.prototype.flags: 1.5.3
set-function-name: 2.0.2
side-channel: 1.0.6
side-channel: 1.1.0
string.prototype.repeat@1.0.0:
dependencies:
@@ -12515,6 +12577,12 @@ snapshots:
strip-json-comments@3.1.1: {}
stripe@18.2.1(@types/node@22.15.31):
dependencies:
qs: 6.14.0
optionalDependencies:
'@types/node': 22.15.31
strnum@1.0.5: {}
styled-jsx@5.1.6(@babel/core@7.26.7)(react@19.0.0):