import React, { useState, useCallback, useEffect, useRef } from 'react'; import { BaseBoxShapeUtil, HTMLContainer, RecordProps, T, TLBaseShape, } from 'tldraw'; import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; // Declare global variable from vite config declare const __STRIPE_PUBLISHABLE_KEY__: string; // Define the shape type inline to avoid import conflicts export type StripePaymentShape = TLBaseShape< 'stripe-payment', { w: number; h: number; } >; // Define subscription plans const SUBSCRIPTION_PLANS = [ { id: 'price_1RdDMgKFe1dC1xn7p319KRDU', // Basic Support Stream name: 'Basic Support Stream', price: 500, // $5.00 CAD interval: 'month', description: 'I like what you\'re doing', features: ['Yay support'] }, { id: 'price_1RdDMwKFe1dC1xn7kDSgE95J', // Mid-range support stream name: 'Mid-range support stream', price: 2500, // $25.00 CAD interval: 'month', description: 'Wait this stuff could actually be helpful', features: ['Even Yayer'] }, { id: 'price_1RdDNAKFe1dC1xn7x2n0FUI5', // Comrades & Collaborators name: 'Comrades & Collaborators', price: 5000, // $50.00 CAD interval: 'month', description: 'We are the ones we\'ve been waiting for', features: ['The yayest of them all'] } ]; // Stripe Payment Form Component function StripePaymentForm({ clientSecret }: { clientSecret: string }) { const stripe = useStripe(); const elements = useElements(); const [error, setError] = useState(null); const [processing, setProcessing] = useState(false); const mountedRef = useRef(true); useEffect(() => { return () => { mountedRef.current = false; }; }, []); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!stripe || !elements || !mountedRef.current) return; setProcessing(true); setError(null); try { const { error } = await stripe.confirmPayment({ elements, confirmParams: { return_url: window.location.href }, }); if (error && mountedRef.current) { setError(error.message || 'Payment failed'); } } catch (err) { if (mountedRef.current) { setError('An unexpected error occurred'); } } finally { if (mountedRef.current) { setProcessing(false); } } }; if (typeof __STRIPE_PUBLISHABLE_KEY__ === 'undefined' || !__STRIPE_PUBLISHABLE_KEY__) { return
Stripe configuration error. Please check your setup.
; } const stripePromise = loadStripe(__STRIPE_PUBLISHABLE_KEY__); return (

Complete Your Subscription

Enter your payment details to start your subscription

{error && (
{error}
)}
); } // Stripe Payment Popup Component export function StripePaymentPopup({ onClose }: { onClose: () => void }) { const [selectedPlanId, setSelectedPlanId] = useState('price_1RdDMgKFe1dC1xn7p319KRDU'); const [customerEmail, setCustomerEmail] = useState(''); const [clientSecret, setClientSecret] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); const mountedRef = useRef(true); const abortControllerRef = useRef(null); useEffect(() => { return () => { mountedRef.current = false; if (abortControllerRef.current) { abortControllerRef.current.abort(); } }; }, []); const initializeSubscription = useCallback(async () => { // Only proceed if we have a valid email and aren't already loading if (isLoading || clientSecret || !customerEmail.trim() || !mountedRef.current) return; try { setIsLoading(true); setError(null); // Create new abort controller for this request abortControllerRef.current = new AbortController(); const selectedPlan = SUBSCRIPTION_PLANS.find(plan => plan.id === selectedPlanId) || SUBSCRIPTION_PLANS[0]; const response = await fetch('/api/stripe/create-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ priceId: selectedPlan.id, customerEmail: customerEmail.trim(), metadata: { planId: selectedPlan.id }, }), signal: abortControllerRef.current.signal, }); if (!mountedRef.current) return; if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json() as { client_secret: string; payment_intent_id?: string; customer_id?: string; price_id?: string }; if (!data.client_secret) { throw new Error('No client secret received from server'); } if (mountedRef.current) { setClientSecret(data.client_secret); } } catch (err) { if (mountedRef.current) { console.error('Stripe: Subscription initialization error:', err); setError('Failed to initialize subscription. Please try again.'); } } finally { if (mountedRef.current) { setIsLoading(false); } } }, [selectedPlanId, customerEmail, isLoading, clientSecret]); return (
{!clientSecret ? (

Choose Your Plan

{SUBSCRIPTION_PLANS.map((plan) => (
setSelectedPlanId(plan.id)} >

{plan.name}

${(plan.price / 100).toFixed(2)}/{plan.interval}

{plan.description}

    {plan.features.map((feature, index) => (
  • {feature}
  • ))}
))}
setCustomerEmail(e.target.value)} placeholder="Enter your email" style={{ width: '100%', padding: '10px 12px', border: '2px solid #ddd', borderRadius: '6px', fontSize: '14px', boxSizing: 'border-box', }} />
) : ( )} {error && (
⚠️
{error}
)}
); } // Main shape utility class export class StripePaymentShapeUtil extends BaseBoxShapeUtil { static type = 'stripe-payment' as const; getDefaultProps(): StripePaymentShape['props'] { return { w: 400, h: 200, }; } override canEdit() { return true; } override canResize() { return false; } override onResize(shape: StripePaymentShape) { return shape; } component(shape: StripePaymentShape) { const [showPopup, setShowPopup] = useState(false); return (
💳

Stripe Payment

Click the button below to start your subscription

{showPopup && setShowPopup(false)} />}
); } indicator(shape: StripePaymentShape) { return ( ); } }