@ezcoder.dev/sdk 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DatabaseProvider-DalP-KHC.d.ts → DatabaseProvider-DaBP5XUs.d.ts} +16 -2
- package/dist/analytics/index.js +7 -5
- package/dist/analytics/index.js.map +1 -1
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +25 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/chunk-CQKYANAW.js +44 -0
- package/dist/chunk-CQKYANAW.js.map +1 -0
- package/dist/{chunk-GPF4AYNG.js → chunk-HJ2EIZ4S.js} +2 -2
- package/dist/{chunk-2WG4O4J2.js → chunk-I2YGB7Z6.js} +14 -50
- package/dist/chunk-I2YGB7Z6.js.map +1 -0
- package/dist/chunk-LIUE7M7K.js +72 -0
- package/dist/chunk-LIUE7M7K.js.map +1 -0
- package/dist/{chunk-AWU47M6N.js → chunk-QHB7LGCA.js} +91 -8
- package/dist/chunk-QHB7LGCA.js.map +1 -0
- package/dist/{chunk-7VGYFCQC.js → chunk-TQC4ROTL.js} +110 -7
- package/dist/chunk-TQC4ROTL.js.map +1 -0
- package/dist/cms/index.js +4 -2
- package/dist/cms/index.js.map +1 -1
- package/dist/cron/index.d.ts +17 -0
- package/dist/cron/index.js +26 -12
- package/dist/cron/index.js.map +1 -1
- package/dist/database/index.d.ts +1 -1
- package/dist/database/index.js +4 -3
- package/dist/email/index.d.ts +18 -0
- package/dist/email/index.js +27 -12
- package/dist/email/index.js.map +1 -1
- package/dist/index.d.ts +168 -2
- package/dist/index.js +263 -7
- package/dist/index.js.map +1 -1
- package/dist/notifications/index.d.ts +27 -2
- package/dist/notifications/index.js +53 -18
- package/dist/notifications/index.js.map +1 -1
- package/dist/payments/index.js +6 -4
- package/dist/payments/index.js.map +1 -1
- package/dist/roles/index.js +6 -4
- package/dist/roles/index.js.map +1 -1
- package/dist/storage/index.js +7 -5
- package/dist/storage/index.js.map +1 -1
- package/package.json +148 -120
- package/dist/chunk-2WG4O4J2.js.map +0 -1
- package/dist/chunk-7VGYFCQC.js.map +0 -1
- package/dist/chunk-AWU47M6N.js.map +0 -1
- /package/dist/{chunk-GPF4AYNG.js.map → chunk-HJ2EIZ4S.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/payments/useSubscription.ts","../../src/payments/useCustomerAccess.ts","../../src/payments/BuyButton.tsx","../../src/payments/PricingTable.tsx","../../src/payments/ManageSubscriptionButton.tsx","../../src/payments/SubscriptionManager.tsx","../../src/payments/ProtectedContent.tsx","../../src/payments/InvoiceHistory.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport type { SubscriptionTier, SubscriptionStatus } from '../core/types';\r\n\r\ninterface SubscriptionState {\r\n customerId: string | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n currentPeriodEnd: string | null;\r\n}\r\n\r\ninterface UseSubscriptionReturn {\r\n subscription: SubscriptionState | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n isActive: boolean;\r\n isPro: boolean;\r\n isBusiness: boolean;\r\n isEnterprise: boolean;\r\n canAccess: (requiredTier: SubscriptionTier) => boolean;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n user: unknown;\r\n profile: unknown;\r\n isAuthenticated: boolean;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nconst TIER_LEVELS: Record<string, number> = {\r\n free: 0, starter: 1, creator: 2, pro: 3, business: 4, enterprise: 5,\r\n};\r\n\r\nexport function useSubscription(): UseSubscriptionReturn {\r\n const auth = useContext(AuthContext);\r\n const [subscription, setSubscription] = useState<SubscriptionState | null>(null);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const fetchSubscription = useCallback(async () => {\r\n if (!auth?.profile) {\r\n setSubscription(null);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const sub: SubscriptionState = {\r\n customerId: auth.profile.stripe_customer_id || null,\r\n tier: (auth.profile.subscription_tier as SubscriptionTier) || 'free',\r\n status: (auth.profile.subscription_status as SubscriptionStatus) || null,\r\n currentPeriodEnd: auth.profile.subscription_period_end || null,\r\n };\r\n\r\n setSubscription(sub);\r\n setLoading(false);\r\n }, [auth?.profile]);\r\n\r\n useEffect(() => {\r\n fetchSubscription();\r\n }, [fetchSubscription]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !auth?.user?.id) return;\r\n\r\n const channel = supabase\r\n .channel(`subscription_${auth.user.id}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'UPDATE',\r\n schema: 'public',\r\n table: 'user_profiles',\r\n filter: `id=eq.${auth.user.id}`,\r\n },\r\n () => {\r\n auth.refetchProfile(auth.user!.id);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [auth?.user?.id, auth]);\r\n\r\n const tier = subscription?.tier || 'free';\r\n const status = subscription?.status || null;\r\n const isActive = !status || status === 'active' || status === 'trialing';\r\n const tierLevel = TIER_LEVELS[tier] ?? 0;\r\n\r\n return {\r\n subscription,\r\n tier,\r\n status,\r\n isActive,\r\n isPro: tierLevel >= TIER_LEVELS.pro,\r\n isBusiness: tierLevel >= TIER_LEVELS.business,\r\n isEnterprise: tierLevel >= TIER_LEVELS.enterprise,\r\n canAccess: (requiredTier: SubscriptionTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),\r\n loading: loading || (auth?.loading ?? false),\r\n isConfigured: features.payments,\r\n user: auth?.user ?? null,\r\n profile: auth?.profile ?? null,\r\n isAuthenticated: Boolean(auth?.user),\r\n refetch: fetchSubscription,\r\n };\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface CustomerAccessReturn {\r\n loading: boolean;\r\n hasAccess: boolean;\r\n customer: Record<string, unknown> | null;\r\n subscription: Record<string, unknown> | null;\r\n error: string | null;\r\n refresh: () => Promise<void>;\r\n logout: () => void;\r\n}\r\n\r\nexport function useCustomerAccess(): CustomerAccessReturn {\r\n const [loading, setLoading] = useState(true);\r\n const [hasAccess, setHasAccess] = useState(false);\r\n const [customer, setCustomer] = useState<Record<string, unknown> | null>(null);\r\n const [subscription, setSubscription] = useState<Record<string, unknown> | null>(null);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const checkAccess = useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const customerId = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('stripeCustomerId')\r\n : null;\r\n const email = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('customerEmail')\r\n : null;\r\n\r\n if (!customerId && !email) {\r\n setHasAccess(false);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || undefined, email: email || undefined });\r\n\r\n if (result.success) {\r\n setHasAccess(Boolean(result.hasAccess));\r\n setCustomer((result.customer as Record<string, unknown>) || null);\r\n setSubscription((result.subscription as Record<string, unknown>) || null);\r\n } else {\r\n setHasAccess(false);\r\n setError(result.error || 'Failed to check access');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n setHasAccess(false);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n checkAccess();\r\n }, [checkAccess]);\r\n\r\n const logout = useCallback(() => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.removeItem('stripeCustomerId');\r\n localStorage.removeItem('customerEmail');\r\n }\r\n setHasAccess(false);\r\n setCustomer(null);\r\n setSubscription(null);\r\n }, []);\r\n\r\n return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface BuyButtonProps {\r\n priceId: string;\r\n productName?: string;\r\n className?: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n customerEmail?: string;\r\n quantity?: number;\r\n successUrl?: string;\r\n cancelUrl?: string;\r\n}\r\n\r\nexport function BuyButton({\r\n priceId,\r\n productName,\r\n className = '',\r\n children,\r\n disabled = false,\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n}: BuyButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleClick = async () => {\r\n setError(null);\r\n setLoading(true);\r\n\r\n try {\r\n const result = await ezcoder.stripe.createCheckout(priceId, {\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n redirect: true,\r\n });\r\n\r\n if (!result.success) {\r\n setError(result.error || 'Checkout failed');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Checkout failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button\r\n onClick={handleClick}\r\n disabled={disabled || loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '10px 20px',\r\n backgroundColor: disabled || loading ? '#9ca3af' : '#3b82f6',\r\n color: 'white',\r\n border: 'none',\r\n borderRadius: '6px',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n cursor: disabled || loading ? 'not-allowed' : 'pointer',\r\n } : undefined}\r\n >\r\n {loading ? 'Processing...' : children || `Buy ${productName || 'Now'}`}\r\n </button>\r\n {error && (\r\n <p style={{ color: '#dc2626', fontSize: '12px', marginTop: '4px' }}>{error}</p>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { BuyButton } from './BuyButton';\r\n\r\ninterface PricingPlan {\r\n name: string;\r\n description: string;\r\n monthlyPriceId?: string;\r\n yearlyPriceId?: string;\r\n monthlyPrice: number;\r\n yearlyPrice?: number;\r\n features: string[];\r\n highlighted?: boolean;\r\n cta?: string;\r\n}\r\n\r\ninterface PricingTableProps {\r\n plans?: PricingPlan[];\r\n className?: string;\r\n customerEmail?: string;\r\n}\r\n\r\nconst DEFAULT_PLANS: PricingPlan[] = [\r\n {\r\n name: 'Starter',\r\n description: 'For individuals getting started',\r\n monthlyPrice: 0,\r\n features: ['Basic features', 'Community support'],\r\n cta: 'Get Started',\r\n },\r\n {\r\n name: 'Creator',\r\n description: 'For growing projects',\r\n monthlyPrice: 25,\r\n yearlyPrice: 270,\r\n features: ['All Starter features', 'Priority support', 'Advanced analytics'],\r\n highlighted: true,\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Business',\r\n description: 'For teams and businesses',\r\n monthlyPrice: 50,\r\n yearlyPrice: 540,\r\n features: ['All Creator features', 'Team collaboration', 'Custom integrations'],\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Enterprise',\r\n description: 'For large organizations',\r\n monthlyPrice: -1,\r\n features: ['All Business features', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],\r\n cta: 'Contact Sales',\r\n },\r\n];\r\n\r\nexport function PricingTable({ plans = DEFAULT_PLANS, className = '', customerEmail }: PricingTableProps) {\r\n const [yearly, setYearly] = useState(false);\r\n\r\n return (\r\n <div className={className}>\r\n <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '32px', gap: '8px', alignItems: 'center' }}>\r\n <span style={{ fontSize: '14px', color: !yearly ? '#111' : '#6b7280', fontWeight: !yearly ? 600 : 400 }}>Monthly</span>\r\n <button\r\n onClick={() => setYearly(!yearly)}\r\n style={{\r\n width: '44px', height: '24px', borderRadius: '12px',\r\n backgroundColor: yearly ? '#3b82f6' : '#d1d5db',\r\n border: 'none', cursor: 'pointer', position: 'relative',\r\n }}\r\n >\r\n <span style={{\r\n width: '18px', height: '18px', borderRadius: '50%', backgroundColor: 'white',\r\n position: 'absolute', top: '3px', left: yearly ? '23px' : '3px',\r\n transition: 'left 0.2s',\r\n }} />\r\n </button>\r\n <span style={{ fontSize: '14px', color: yearly ? '#111' : '#6b7280', fontWeight: yearly ? 600 : 400 }}>\r\n Yearly <span style={{ color: '#059669', fontSize: '12px' }}>Save 10%</span>\r\n </span>\r\n </div>\r\n\r\n <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: '24px', maxWidth: '1200px', margin: '0 auto' }}>\r\n {plans.map((plan) => {\r\n const price = yearly && plan.yearlyPrice !== undefined ? plan.yearlyPrice / 12 : plan.monthlyPrice;\r\n const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;\r\n\r\n return (\r\n <div\r\n key={plan.name}\r\n style={{\r\n border: plan.highlighted ? '2px solid #3b82f6' : '1px solid #e5e7eb',\r\n borderRadius: '12px', padding: '24px',\r\n backgroundColor: plan.highlighted ? '#eff6ff' : 'white',\r\n }}\r\n >\r\n <h3 style={{ fontSize: '1.25rem', fontWeight: 600, marginBottom: '4px' }}>{plan.name}</h3>\r\n <p style={{ color: '#6b7280', fontSize: '14px', marginBottom: '16px' }}>{plan.description}</p>\r\n\r\n <div style={{ marginBottom: '24px' }}>\r\n {plan.monthlyPrice === 0 ? (\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>Free</span>\r\n ) : plan.monthlyPrice === -1 ? (\r\n <span style={{ fontSize: '1.5rem', fontWeight: 600 }}>Custom</span>\r\n ) : (\r\n <>\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>${Math.round(price)}</span>\r\n <span style={{ color: '#6b7280', fontSize: '14px' }}>/mo</span>\r\n </>\r\n )}\r\n </div>\r\n\r\n <ul style={{ listStyle: 'none', padding: 0, marginBottom: '24px' }}>\r\n {plan.features.map((feature) => (\r\n <li key={feature} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', fontSize: '14px' }}>\r\n <span style={{ color: '#059669' }}>✓</span> {feature}\r\n </li>\r\n ))}\r\n </ul>\r\n\r\n {priceId ? (\r\n <BuyButton priceId={priceId} productName={plan.name} customerEmail={customerEmail}>\r\n {plan.cta || 'Subscribe'}\r\n </BuyButton>\r\n ) : plan.monthlyPrice === 0 ? (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Get Started'}\r\n </button>\r\n ) : (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Contact Sales'}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface ManageSubscriptionButtonProps {\r\n children?: React.ReactNode;\r\n className?: string;\r\n customerId?: string;\r\n}\r\n\r\nexport function ManageSubscriptionButton({ children, className = '', customerId }: ManageSubscriptionButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) return;\r\n\r\n setLoading(true);\r\n await ezcoder.stripe.createPortalSession(id, { redirect: true });\r\n setLoading(false);\r\n };\r\n\r\n return (\r\n <button\r\n onClick={handleClick}\r\n disabled={loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '8px 16px',\r\n border: '1px solid #d1d5db',\r\n borderRadius: '6px',\r\n background: 'white',\r\n cursor: loading ? 'not-allowed' : 'pointer',\r\n fontSize: '14px',\r\n } : undefined}\r\n >\r\n {loading ? 'Loading...' : children || 'Manage Subscription'}\r\n </button>\r\n );\r\n}\r\n","import { useSubscription } from './useSubscription';\r\nimport { ManageSubscriptionButton } from './ManageSubscriptionButton';\r\n\r\ninterface SubscriptionManagerProps {\r\n className?: string;\r\n}\r\n\r\nconst STATUS_COLORS: Record<string, string> = {\r\n active: '#059669',\r\n trialing: '#3b82f6',\r\n past_due: '#d97706',\r\n canceled: '#dc2626',\r\n unpaid: '#dc2626',\r\n};\r\n\r\nexport function SubscriptionManager({ className = '' }: SubscriptionManagerProps) {\r\n const { subscription, tier, status, isActive, loading } = useSubscription();\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading subscription...</div>;\r\n }\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', padding: '24px' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '16px' }}>Subscription</h3>\r\n\r\n <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Plan</span>\r\n <span style={{ fontWeight: 500, textTransform: 'capitalize' }}>{tier}</span>\r\n </div>\r\n\r\n {status && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Status</span>\r\n <span style={{\r\n fontWeight: 500,\r\n color: STATUS_COLORS[status] || '#6b7280',\r\n textTransform: 'capitalize',\r\n }}>\r\n {status === 'past_due' ? 'Past Due' : status}\r\n </span>\r\n </div>\r\n )}\r\n\r\n {subscription?.currentPeriodEnd && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>{isActive ? 'Renews' : 'Expires'}</span>\r\n <span>{new Date(subscription.currentPeriodEnd).toLocaleDateString()}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {subscription?.customerId && (\r\n <ManageSubscriptionButton customerId={subscription.customerId} />\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCustomerAccess } from './useCustomerAccess';\r\nimport { useSubscription } from './useSubscription';\r\nimport type { SubscriptionTier } from '../core/types';\r\n\r\ninterface ProtectedContentProps {\r\n children: React.ReactNode;\r\n fallback?: React.ReactNode;\r\n loadingComponent?: React.ReactNode;\r\n requiredTier?: SubscriptionTier;\r\n}\r\n\r\nexport function ProtectedContent({ children, fallback, loadingComponent, requiredTier }: ProtectedContentProps) {\r\n const customerAccess = useCustomerAccess();\r\n const subscription = useSubscription();\r\n\r\n const loading = requiredTier ? subscription.loading : customerAccess.loading;\r\n\r\n if (loading) {\r\n return <>{loadingComponent || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>Loading...</div>}</>;\r\n }\r\n\r\n const hasAccess = requiredTier\r\n ? subscription.isActive && subscription.canAccess(requiredTier)\r\n : customerAccess.hasAccess;\r\n\r\n if (!hasAccess) {\r\n return <>{fallback || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>\r\n {requiredTier\r\n ? `This content requires a ${requiredTier} subscription or higher.`\r\n : 'This content requires a subscription.'}\r\n </div>}</>;\r\n }\r\n\r\n return <>{children}</>;\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface Invoice {\r\n id: string;\r\n number: string | null;\r\n amount: number;\r\n currency: string;\r\n status: string;\r\n created: number;\r\n hostedUrl: string | null;\r\n pdfUrl: string | null;\r\n}\r\n\r\ninterface InvoiceHistoryProps {\r\n customerId?: string;\r\n limit?: number;\r\n className?: string;\r\n}\r\n\r\nexport function InvoiceHistory({ customerId, limit = 10, className = '' }: InvoiceHistoryProps) {\r\n const [invoices, setInvoices] = useState<Invoice[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchInvoices = useCallback(async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const result = await ezcoder.stripe.getInvoices(id, { limit });\r\n if (result.success && result.invoices) {\r\n setInvoices(result.invoices as Invoice[]);\r\n } else {\r\n setError(result.error || 'Failed to load invoices');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [customerId, limit]);\r\n\r\n useEffect(() => {\r\n fetchInvoices();\r\n }, [fetchInvoices]);\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading invoices...</div>;\r\n }\r\n\r\n if (error) {\r\n return <div style={{ padding: '20px', color: '#dc2626' }}>{error}</div>;\r\n }\r\n\r\n if (invoices.length === 0) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>No invoices found.</div>;\r\n }\r\n\r\n const formatAmount = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount / 100);\r\n };\r\n\r\n const STATUS_COLORS: Record<string, string> = {\r\n paid: '#059669',\r\n open: '#3b82f6',\r\n draft: '#6b7280',\r\n void: '#9ca3af',\r\n uncollectible: '#dc2626',\r\n };\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', overflow: 'hidden' }}>\r\n <div style={{ padding: '16px 24px', borderBottom: '1px solid #e5e7eb' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, margin: 0 }}>Invoice History</h3>\r\n </div>\r\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px' }}>\r\n <thead>\r\n <tr style={{ borderBottom: '1px solid #e5e7eb', background: '#f9fafb' }}>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Invoice</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Date</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}>Amount</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'center', fontWeight: 500, color: '#6b7280' }}>Status</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}></th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {invoices.map((invoice) => (\r\n <tr key={invoice.id} style={{ borderBottom: '1px solid #f3f4f6' }}>\r\n <td style={{ padding: '12px 16px' }}>{invoice.number || invoice.id.slice(0, 12)}</td>\r\n <td style={{ padding: '12px 16px', color: '#6b7280' }}>\r\n {new Date(invoice.created * 1000).toLocaleDateString()}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 500 }}>\r\n {formatAmount(invoice.amount, invoice.currency)}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'center' }}>\r\n <span style={{\r\n padding: '2px 8px',\r\n borderRadius: '9999px',\r\n fontSize: '12px',\r\n fontWeight: 500,\r\n color: STATUS_COLORS[invoice.status] || '#6b7280',\r\n background: `${STATUS_COLORS[invoice.status] || '#6b7280'}15`,\r\n textTransform: 'capitalize',\r\n }}>\r\n {invoice.status}\r\n </span>\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right' }}>\r\n {invoice.pdfUrl && (\r\n <a\r\n href={invoice.pdfUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n style={{ color: '#3b82f6', textDecoration: 'none', fontSize: '13px' }}\r\n >\r\n PDF\r\n </a>\r\n )}\r\n </td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useState","useState","jsx","jsxs","useState","useState","jsx","useState","jsx","jsxs","Fragment","jsx","useState","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","STATUS_COLORS"]}
|
|
1
|
+
{"version":3,"sources":["../../src/payments/useSubscription.ts","../../src/payments/useCustomerAccess.ts","../../src/payments/BuyButton.tsx","../../src/payments/PricingTable.tsx","../../src/payments/ManageSubscriptionButton.tsx","../../src/payments/SubscriptionManager.tsx","../../src/payments/ProtectedContent.tsx","../../src/payments/InvoiceHistory.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport type { SubscriptionTier, SubscriptionStatus } from '../core/types';\r\n\r\ninterface SubscriptionState {\r\n customerId: string | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n currentPeriodEnd: string | null;\r\n}\r\n\r\ninterface UseSubscriptionReturn {\r\n subscription: SubscriptionState | null;\r\n tier: SubscriptionTier;\r\n status: SubscriptionStatus | null;\r\n isActive: boolean;\r\n isPro: boolean;\r\n isBusiness: boolean;\r\n isEnterprise: boolean;\r\n canAccess: (requiredTier: SubscriptionTier) => boolean;\r\n loading: boolean;\r\n isConfigured: boolean;\r\n user: unknown;\r\n profile: unknown;\r\n isAuthenticated: boolean;\r\n refetch: () => Promise<void>;\r\n}\r\n\r\nconst TIER_LEVELS: Record<string, number> = {\r\n free: 0, starter: 1, creator: 2, pro: 3, business: 4, enterprise: 5,\r\n};\r\n\r\nexport function useSubscription(): UseSubscriptionReturn {\r\n const auth = useContext(AuthContext);\r\n const [subscription, setSubscription] = useState<SubscriptionState | null>(null);\r\n const [loading, setLoading] = useState(true);\r\n\r\n const fetchSubscription = useCallback(async () => {\r\n if (!auth?.profile) {\r\n setSubscription(null);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const sub: SubscriptionState = {\r\n customerId: auth.profile.stripe_customer_id || null,\r\n tier: (auth.profile.subscription_tier as SubscriptionTier) || 'free',\r\n status: (auth.profile.subscription_status as SubscriptionStatus) || null,\r\n currentPeriodEnd: auth.profile.subscription_period_end || null,\r\n };\r\n\r\n setSubscription(sub);\r\n setLoading(false);\r\n }, [auth?.profile]);\r\n\r\n useEffect(() => {\r\n fetchSubscription();\r\n }, [fetchSubscription]);\r\n\r\n useEffect(() => {\r\n if (!features.auth || !auth?.user?.id) return;\r\n\r\n const channel = supabase\r\n .channel(`subscription_${auth.user.id}`)\r\n .on(\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n 'postgres_changes' as any,\r\n {\r\n event: 'UPDATE',\r\n schema: 'public',\r\n table: 'user_profiles',\r\n filter: `id=eq.${auth.user.id}`,\r\n },\r\n () => {\r\n auth.refetchProfile(auth.user!.id);\r\n }\r\n )\r\n .subscribe();\r\n\r\n return () => {\r\n supabase.removeChannel(channel);\r\n };\r\n }, [auth?.user?.id, auth]);\r\n\r\n const tier = subscription?.tier || 'free';\r\n const status = subscription?.status || null;\r\n const isActive = !status || status === 'active' || status === 'trialing';\r\n const tierLevel = TIER_LEVELS[tier] ?? 0;\r\n\r\n return {\r\n subscription,\r\n tier,\r\n status,\r\n isActive,\r\n isPro: tierLevel >= TIER_LEVELS.pro,\r\n isBusiness: tierLevel >= TIER_LEVELS.business,\r\n isEnterprise: tierLevel >= TIER_LEVELS.enterprise,\r\n canAccess: (requiredTier: SubscriptionTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),\r\n loading: loading || (auth?.loading ?? false),\r\n isConfigured: features.payments,\r\n user: auth?.user ?? null,\r\n profile: auth?.profile ?? null,\r\n isAuthenticated: Boolean(auth?.user),\r\n refetch: fetchSubscription,\r\n };\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface CustomerAccessReturn {\r\n loading: boolean;\r\n hasAccess: boolean;\r\n customer: Record<string, unknown> | null;\r\n subscription: Record<string, unknown> | null;\r\n error: string | null;\r\n refresh: () => Promise<void>;\r\n logout: () => void;\r\n}\r\n\r\nexport function useCustomerAccess(): CustomerAccessReturn {\r\n const [loading, setLoading] = useState(true);\r\n const [hasAccess, setHasAccess] = useState(false);\r\n const [customer, setCustomer] = useState<Record<string, unknown> | null>(null);\r\n const [subscription, setSubscription] = useState<Record<string, unknown> | null>(null);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const checkAccess = useCallback(async () => {\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const customerId = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('stripeCustomerId')\r\n : null;\r\n const email = typeof localStorage !== 'undefined'\r\n ? localStorage.getItem('customerEmail')\r\n : null;\r\n\r\n if (!customerId && !email) {\r\n setHasAccess(false);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || undefined, email: email || undefined });\r\n\r\n if (result.success) {\r\n setHasAccess(Boolean(result.hasAccess));\r\n setCustomer((result.customer as Record<string, unknown>) || null);\r\n setSubscription((result.subscription as Record<string, unknown>) || null);\r\n } else {\r\n setHasAccess(false);\r\n setError(result.error || 'Failed to check access');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n setHasAccess(false);\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, []);\r\n\r\n useEffect(() => {\r\n checkAccess();\r\n }, [checkAccess]);\r\n\r\n const logout = useCallback(() => {\r\n if (typeof localStorage !== 'undefined') {\r\n localStorage.removeItem('stripeCustomerId');\r\n localStorage.removeItem('customerEmail');\r\n }\r\n setHasAccess(false);\r\n setCustomer(null);\r\n setSubscription(null);\r\n }, []);\r\n\r\n return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface BuyButtonProps {\r\n priceId: string;\r\n productName?: string;\r\n className?: string;\r\n children?: React.ReactNode;\r\n disabled?: boolean;\r\n customerEmail?: string;\r\n quantity?: number;\r\n successUrl?: string;\r\n cancelUrl?: string;\r\n}\r\n\r\nexport function BuyButton({\r\n priceId,\r\n productName,\r\n className = '',\r\n children,\r\n disabled = false,\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n}: BuyButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const handleClick = async () => {\r\n setError(null);\r\n setLoading(true);\r\n\r\n try {\r\n const result = await ezcoder.stripe.createCheckout(priceId, {\r\n customerEmail,\r\n quantity,\r\n successUrl,\r\n cancelUrl,\r\n redirect: true,\r\n });\r\n\r\n if (!result.success) {\r\n setError(result.error || 'Checkout failed');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Checkout failed');\r\n } finally {\r\n setLoading(false);\r\n }\r\n };\r\n\r\n return (\r\n <div>\r\n <button\r\n onClick={handleClick}\r\n disabled={disabled || loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '10px 20px',\r\n backgroundColor: disabled || loading ? '#9ca3af' : '#3b82f6',\r\n color: 'white',\r\n border: 'none',\r\n borderRadius: '6px',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n cursor: disabled || loading ? 'not-allowed' : 'pointer',\r\n } : undefined}\r\n >\r\n {loading ? 'Processing...' : children || `Buy ${productName || 'Now'}`}\r\n </button>\r\n {error && (\r\n <p style={{ color: '#dc2626', fontSize: '12px', marginTop: '4px' }}>{error}</p>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { BuyButton } from './BuyButton';\r\n\r\ninterface PricingPlan {\r\n name: string;\r\n description: string;\r\n monthlyPriceId?: string;\r\n yearlyPriceId?: string;\r\n monthlyPrice: number;\r\n yearlyPrice?: number;\r\n features: string[];\r\n highlighted?: boolean;\r\n cta?: string;\r\n}\r\n\r\ninterface PricingTableProps {\r\n plans?: PricingPlan[];\r\n className?: string;\r\n customerEmail?: string;\r\n}\r\n\r\nconst DEFAULT_PLANS: PricingPlan[] = [\r\n {\r\n name: 'Starter',\r\n description: 'For individuals getting started',\r\n monthlyPrice: 0,\r\n features: ['Basic features', 'Community support'],\r\n cta: 'Get Started',\r\n },\r\n {\r\n name: 'Creator',\r\n description: 'For growing projects',\r\n monthlyPrice: 25,\r\n yearlyPrice: 270,\r\n features: ['All Starter features', 'Priority support', 'Advanced analytics'],\r\n highlighted: true,\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Business',\r\n description: 'For teams and businesses',\r\n monthlyPrice: 50,\r\n yearlyPrice: 540,\r\n features: ['All Creator features', 'Team collaboration', 'Custom integrations'],\r\n cta: 'Subscribe',\r\n },\r\n {\r\n name: 'Enterprise',\r\n description: 'For large organizations',\r\n monthlyPrice: -1,\r\n features: ['All Business features', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],\r\n cta: 'Contact Sales',\r\n },\r\n];\r\n\r\nexport function PricingTable({ plans = DEFAULT_PLANS, className = '', customerEmail }: PricingTableProps) {\r\n const [yearly, setYearly] = useState(false);\r\n\r\n return (\r\n <div className={className}>\r\n <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '32px', gap: '8px', alignItems: 'center' }}>\r\n <span style={{ fontSize: '14px', color: !yearly ? '#111' : '#6b7280', fontWeight: !yearly ? 600 : 400 }}>Monthly</span>\r\n <button\r\n onClick={() => setYearly(!yearly)}\r\n style={{\r\n width: '44px', height: '24px', borderRadius: '12px',\r\n backgroundColor: yearly ? '#3b82f6' : '#d1d5db',\r\n border: 'none', cursor: 'pointer', position: 'relative',\r\n }}\r\n >\r\n <span style={{\r\n width: '18px', height: '18px', borderRadius: '50%', backgroundColor: 'white',\r\n position: 'absolute', top: '3px', left: yearly ? '23px' : '3px',\r\n transition: 'left 0.2s',\r\n }} />\r\n </button>\r\n <span style={{ fontSize: '14px', color: yearly ? '#111' : '#6b7280', fontWeight: yearly ? 600 : 400 }}>\r\n Yearly <span style={{ color: '#059669', fontSize: '12px' }}>Save 10%</span>\r\n </span>\r\n </div>\r\n\r\n <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: '24px', maxWidth: '1200px', margin: '0 auto' }}>\r\n {plans.map((plan) => {\r\n const price = yearly && plan.yearlyPrice !== undefined ? plan.yearlyPrice / 12 : plan.monthlyPrice;\r\n const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;\r\n\r\n return (\r\n <div\r\n key={plan.name}\r\n style={{\r\n border: plan.highlighted ? '2px solid #3b82f6' : '1px solid #e5e7eb',\r\n borderRadius: '12px', padding: '24px',\r\n backgroundColor: plan.highlighted ? '#eff6ff' : 'white',\r\n }}\r\n >\r\n <h3 style={{ fontSize: '1.25rem', fontWeight: 600, marginBottom: '4px' }}>{plan.name}</h3>\r\n <p style={{ color: '#6b7280', fontSize: '14px', marginBottom: '16px' }}>{plan.description}</p>\r\n\r\n <div style={{ marginBottom: '24px' }}>\r\n {plan.monthlyPrice === 0 ? (\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>Free</span>\r\n ) : plan.monthlyPrice === -1 ? (\r\n <span style={{ fontSize: '1.5rem', fontWeight: 600 }}>Custom</span>\r\n ) : (\r\n <>\r\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>${Math.round(price)}</span>\r\n <span style={{ color: '#6b7280', fontSize: '14px' }}>/mo</span>\r\n </>\r\n )}\r\n </div>\r\n\r\n <ul style={{ listStyle: 'none', padding: 0, marginBottom: '24px' }}>\r\n {plan.features.map((feature) => (\r\n <li key={feature} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', fontSize: '14px' }}>\r\n <span style={{ color: '#059669' }}>✓</span> {feature}\r\n </li>\r\n ))}\r\n </ul>\r\n\r\n {priceId ? (\r\n <BuyButton priceId={priceId} productName={plan.name} customerEmail={customerEmail}>\r\n {plan.cta || 'Subscribe'}\r\n </BuyButton>\r\n ) : plan.monthlyPrice === 0 ? (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Get Started'}\r\n </button>\r\n ) : (\r\n <button style={{\r\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\r\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\r\n }}>\r\n {plan.cta || 'Contact Sales'}\r\n </button>\r\n )}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import { useState } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface ManageSubscriptionButtonProps {\r\n children?: React.ReactNode;\r\n className?: string;\r\n customerId?: string;\r\n}\r\n\r\nexport function ManageSubscriptionButton({ children, className = '', customerId }: ManageSubscriptionButtonProps) {\r\n const [loading, setLoading] = useState(false);\r\n\r\n const handleClick = async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) return;\r\n\r\n setLoading(true);\r\n await ezcoder.stripe.createPortalSession(id, { redirect: true });\r\n setLoading(false);\r\n };\r\n\r\n return (\r\n <button\r\n onClick={handleClick}\r\n disabled={loading}\r\n className={className}\r\n style={!className ? {\r\n padding: '8px 16px',\r\n border: '1px solid #d1d5db',\r\n borderRadius: '6px',\r\n background: 'white',\r\n cursor: loading ? 'not-allowed' : 'pointer',\r\n fontSize: '14px',\r\n } : undefined}\r\n >\r\n {loading ? 'Loading...' : children || 'Manage Subscription'}\r\n </button>\r\n );\r\n}\r\n","import { useSubscription } from './useSubscription';\r\nimport { ManageSubscriptionButton } from './ManageSubscriptionButton';\r\n\r\ninterface SubscriptionManagerProps {\r\n className?: string;\r\n}\r\n\r\nconst STATUS_COLORS: Record<string, string> = {\r\n active: '#059669',\r\n trialing: '#3b82f6',\r\n past_due: '#d97706',\r\n canceled: '#dc2626',\r\n unpaid: '#dc2626',\r\n};\r\n\r\nexport function SubscriptionManager({ className = '' }: SubscriptionManagerProps) {\r\n const { subscription, tier, status, isActive, loading } = useSubscription();\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading subscription...</div>;\r\n }\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', padding: '24px' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '16px' }}>Subscription</h3>\r\n\r\n <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Plan</span>\r\n <span style={{ fontWeight: 500, textTransform: 'capitalize' }}>{tier}</span>\r\n </div>\r\n\r\n {status && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>Status</span>\r\n <span style={{\r\n fontWeight: 500,\r\n color: STATUS_COLORS[status] || '#6b7280',\r\n textTransform: 'capitalize',\r\n }}>\r\n {status === 'past_due' ? 'Past Due' : status}\r\n </span>\r\n </div>\r\n )}\r\n\r\n {subscription?.currentPeriodEnd && (\r\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\r\n <span style={{ color: '#6b7280' }}>{isActive ? 'Renews' : 'Expires'}</span>\r\n <span>{new Date(subscription.currentPeriodEnd).toLocaleDateString()}</span>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {subscription?.customerId && (\r\n <ManageSubscriptionButton customerId={subscription.customerId} />\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useCustomerAccess } from './useCustomerAccess';\r\nimport { useSubscription } from './useSubscription';\r\nimport type { SubscriptionTier } from '../core/types';\r\n\r\ninterface ProtectedContentProps {\r\n children: React.ReactNode;\r\n fallback?: React.ReactNode;\r\n loadingComponent?: React.ReactNode;\r\n requiredTier?: SubscriptionTier;\r\n}\r\n\r\nexport function ProtectedContent({ children, fallback, loadingComponent, requiredTier }: ProtectedContentProps) {\r\n const customerAccess = useCustomerAccess();\r\n const subscription = useSubscription();\r\n\r\n const loading = requiredTier ? subscription.loading : customerAccess.loading;\r\n\r\n if (loading) {\r\n return <>{loadingComponent || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>Loading...</div>}</>;\r\n }\r\n\r\n const hasAccess = requiredTier\r\n ? subscription.isActive && subscription.canAccess(requiredTier)\r\n : customerAccess.hasAccess;\r\n\r\n if (!hasAccess) {\r\n return <>{fallback || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>\r\n {requiredTier\r\n ? `This content requires a ${requiredTier} subscription or higher.`\r\n : 'This content requires a subscription.'}\r\n </div>}</>;\r\n }\r\n\r\n return <>{children}</>;\r\n}\r\n","import { useState, useEffect, useCallback } from 'react';\r\nimport { ezcoder } from '../core/platform';\r\n\r\ninterface Invoice {\r\n id: string;\r\n number: string | null;\r\n amount: number;\r\n currency: string;\r\n status: string;\r\n created: number;\r\n hostedUrl: string | null;\r\n pdfUrl: string | null;\r\n}\r\n\r\ninterface InvoiceHistoryProps {\r\n customerId?: string;\r\n limit?: number;\r\n className?: string;\r\n}\r\n\r\nexport function InvoiceHistory({ customerId, limit = 10, className = '' }: InvoiceHistoryProps) {\r\n const [invoices, setInvoices] = useState<Invoice[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchInvoices = useCallback(async () => {\r\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\r\n if (!id) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n try {\r\n const result = await ezcoder.stripe.getInvoices(id, { limit });\r\n if (result.success && result.invoices) {\r\n setInvoices(result.invoices as Invoice[]);\r\n } else {\r\n setError(result.error || 'Failed to load invoices');\r\n }\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Unknown error');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [customerId, limit]);\r\n\r\n useEffect(() => {\r\n fetchInvoices();\r\n }, [fetchInvoices]);\r\n\r\n if (loading) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading invoices...</div>;\r\n }\r\n\r\n if (error) {\r\n return <div style={{ padding: '20px', color: '#dc2626' }}>{error}</div>;\r\n }\r\n\r\n if (invoices.length === 0) {\r\n return <div style={{ padding: '20px', color: '#6b7280' }}>No invoices found.</div>;\r\n }\r\n\r\n const formatAmount = (amount: number, currency: string) => {\r\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount / 100);\r\n };\r\n\r\n const STATUS_COLORS: Record<string, string> = {\r\n paid: '#059669',\r\n open: '#3b82f6',\r\n draft: '#6b7280',\r\n void: '#9ca3af',\r\n uncollectible: '#dc2626',\r\n };\r\n\r\n return (\r\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', overflow: 'hidden' }}>\r\n <div style={{ padding: '16px 24px', borderBottom: '1px solid #e5e7eb' }}>\r\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, margin: 0 }}>Invoice History</h3>\r\n </div>\r\n <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '14px' }}>\r\n <thead>\r\n <tr style={{ borderBottom: '1px solid #e5e7eb', background: '#f9fafb' }}>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Invoice</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'left', fontWeight: 500, color: '#6b7280' }}>Date</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}>Amount</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'center', fontWeight: 500, color: '#6b7280' }}>Status</th>\r\n <th style={{ padding: '10px 16px', textAlign: 'right', fontWeight: 500, color: '#6b7280' }}></th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n {invoices.map((invoice) => (\r\n <tr key={invoice.id} style={{ borderBottom: '1px solid #f3f4f6' }}>\r\n <td style={{ padding: '12px 16px' }}>{invoice.number || invoice.id.slice(0, 12)}</td>\r\n <td style={{ padding: '12px 16px', color: '#6b7280' }}>\r\n {new Date(invoice.created * 1000).toLocaleDateString()}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right', fontWeight: 500 }}>\r\n {formatAmount(invoice.amount, invoice.currency)}\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'center' }}>\r\n <span style={{\r\n padding: '2px 8px',\r\n borderRadius: '9999px',\r\n fontSize: '12px',\r\n fontWeight: 500,\r\n color: STATUS_COLORS[invoice.status] || '#6b7280',\r\n background: `${STATUS_COLORS[invoice.status] || '#6b7280'}15`,\r\n textTransform: 'capitalize',\r\n }}>\r\n {invoice.status}\r\n </span>\r\n </td>\r\n <td style={{ padding: '12px 16px', textAlign: 'right' }}>\r\n {invoice.pdfUrl && (\r\n <a\r\n href={invoice.pdfUrl}\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n style={{ color: '#3b82f6', textDecoration: 'none', fontSize: '13px' }}\r\n >\r\n PDF\r\n </a>\r\n )}\r\n </td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useState","useState","jsx","jsxs","useState","useState","jsx","useState","jsx","jsxs","Fragment","jsx","useState","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","STATUS_COLORS"]}
|
package/dist/roles/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-QHB7LGCA.js";
|
|
4
|
+
import "../chunk-HJ2EIZ4S.js";
|
|
5
5
|
import {
|
|
6
|
-
features,
|
|
7
6
|
supabase
|
|
8
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
8
|
+
import {
|
|
9
|
+
features
|
|
10
|
+
} from "../chunk-LIUE7M7K.js";
|
|
9
11
|
|
|
10
12
|
// src/roles/useRoles.ts
|
|
11
13
|
import { useState, useEffect, useCallback, useContext } from "react";
|
package/dist/roles/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/roles/useRoles.ts","../../src/roles/RoleGate.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface RoleData {\r\n role_name: string;\r\n display_name: string;\r\n permissions: string[];\r\n can_access_routes: string[];\r\n}\r\n\r\ninterface UseRolesReturn {\r\n roles: string[];\r\n permissions: string[];\r\n accessibleRoutes: string[];\r\n loading: boolean;\r\n error: string | null;\r\n hasRole: (name: string) => boolean;\r\n hasAnyRole: (names: string[]) => boolean;\r\n hasAllRoles: (names: string[]) => boolean;\r\n hasPermission: (perm: string) => boolean;\r\n hasAnyPermission: (perms: string[]) => boolean;\r\n hasAllPermissions: (perms: string[]) => boolean;\r\n canAccessRoute: (path: string) => boolean;\r\n refreshRoles: () => Promise<void>;\r\n}\r\n\r\nexport function useRoles(): UseRolesReturn {\r\n const auth = useContext(AuthContext);\r\n const [roles, setRoles] = useState<string[]>([]);\r\n const [permissions, setPermissions] = useState<string[]>([]);\r\n const [accessibleRoutes, setAccessibleRoutes] = useState<string[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchRoles = useCallback(async () => {\r\n if (!auth?.user?.id || !features.auth) {\r\n setRoles([]);\r\n setPermissions([]);\r\n setAccessibleRoutes([]);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error: rpcError } = await supabase.rpc('get_user_roles', {\r\n user_uuid: auth.user.id,\r\n });\r\n\r\n if (rpcError) {\r\n setError(rpcError.message);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const roleData = (data || []) as RoleData[];\r\n const allRoles = roleData.map((r) => r.role_name);\r\n const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];\r\n const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];\r\n\r\n setRoles(allRoles);\r\n setPermissions(allPerms);\r\n setAccessibleRoutes(allRoutes);\r\n setError(null);\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Failed to fetch roles');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [auth?.user?.id]);\r\n\r\n useEffect(() => {\r\n fetchRoles();\r\n }, [fetchRoles]);\r\n\r\n const hasRole = useCallback((name: string) => roles.includes(name), [roles]);\r\n const hasAnyRole = useCallback((names: string[]) => names.some((n) => roles.includes(n)), [roles]);\r\n const hasAllRoles = useCallback((names: string[]) => names.every((n) => roles.includes(n)), [roles]);\r\n const hasPermission = useCallback((perm: string) => permissions.includes(perm), [permissions]);\r\n const hasAnyPermission = useCallback((perms: string[]) => perms.some((p) => permissions.includes(p)), [permissions]);\r\n const hasAllPermissions = useCallback((perms: string[]) => perms.every((p) => permissions.includes(p)), [permissions]);\r\n\r\n const canAccessRoute = useCallback((path: string) => {\r\n return accessibleRoutes.some((route) => {\r\n if (route.endsWith('/*')) {\r\n return path.startsWith(route.slice(0, -2));\r\n }\r\n return route === path;\r\n });\r\n }, [accessibleRoutes]);\r\n\r\n return {\r\n roles, permissions, accessibleRoutes, loading, error,\r\n hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions,\r\n canAccessRoute, refreshRoles: fetchRoles,\r\n };\r\n}\r\n","import { useRoles } from './useRoles';\r\n\r\ninterface RoleGateProps {\r\n children: React.ReactNode;\r\n roles?: string[];\r\n permissions?: string[];\r\n requireAll?: boolean;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\ninterface RouteGateProps {\r\n children: React.ReactNode;\r\n route: string;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\nexport function RoleGate({\r\n children,\r\n roles: requiredRoles,\r\n permissions: requiredPerms,\r\n requireAll = false,\r\n fallback = null,\r\n loadingFallback = null,\r\n}: RoleGateProps) {\r\n const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n let hasAccess = true;\r\n\r\n if (requiredRoles?.length) {\r\n hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);\r\n }\r\n\r\n if (hasAccess && requiredPerms?.length) {\r\n hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);\r\n }\r\n\r\n if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {\r\n hasAccess = true;\r\n }\r\n\r\n // Use individual checks to satisfy linter\r\n void hasRole;\r\n void hasPermission;\r\n\r\n return hasAccess ? <>{children}</> : <>{fallback}</>;\r\n}\r\n\r\nexport function RouteGate({ children, route, fallback = null, loadingFallback = null }: RouteGateProps) {\r\n const { canAccessRoute, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n return canAccessRoute(route) ? <>{children}</> : <>{fallback}</>;\r\n}\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/roles/useRoles.ts","../../src/roles/RoleGate.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\n\r\ninterface RoleData {\r\n role_name: string;\r\n display_name: string;\r\n permissions: string[];\r\n can_access_routes: string[];\r\n}\r\n\r\ninterface UseRolesReturn {\r\n roles: string[];\r\n permissions: string[];\r\n accessibleRoutes: string[];\r\n loading: boolean;\r\n error: string | null;\r\n hasRole: (name: string) => boolean;\r\n hasAnyRole: (names: string[]) => boolean;\r\n hasAllRoles: (names: string[]) => boolean;\r\n hasPermission: (perm: string) => boolean;\r\n hasAnyPermission: (perms: string[]) => boolean;\r\n hasAllPermissions: (perms: string[]) => boolean;\r\n canAccessRoute: (path: string) => boolean;\r\n refreshRoles: () => Promise<void>;\r\n}\r\n\r\nexport function useRoles(): UseRolesReturn {\r\n const auth = useContext(AuthContext);\r\n const [roles, setRoles] = useState<string[]>([]);\r\n const [permissions, setPermissions] = useState<string[]>([]);\r\n const [accessibleRoutes, setAccessibleRoutes] = useState<string[]>([]);\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n const fetchRoles = useCallback(async () => {\r\n if (!auth?.user?.id || !features.auth) {\r\n setRoles([]);\r\n setPermissions([]);\r\n setAccessibleRoutes([]);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n try {\r\n const { data, error: rpcError } = await supabase.rpc('get_user_roles', {\r\n user_uuid: auth.user.id,\r\n });\r\n\r\n if (rpcError) {\r\n setError(rpcError.message);\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n const roleData = (data || []) as RoleData[];\r\n const allRoles = roleData.map((r) => r.role_name);\r\n const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];\r\n const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];\r\n\r\n setRoles(allRoles);\r\n setPermissions(allPerms);\r\n setAccessibleRoutes(allRoutes);\r\n setError(null);\r\n } catch (err: unknown) {\r\n setError(err instanceof Error ? err.message : 'Failed to fetch roles');\r\n } finally {\r\n setLoading(false);\r\n }\r\n }, [auth?.user?.id]);\r\n\r\n useEffect(() => {\r\n fetchRoles();\r\n }, [fetchRoles]);\r\n\r\n const hasRole = useCallback((name: string) => roles.includes(name), [roles]);\r\n const hasAnyRole = useCallback((names: string[]) => names.some((n) => roles.includes(n)), [roles]);\r\n const hasAllRoles = useCallback((names: string[]) => names.every((n) => roles.includes(n)), [roles]);\r\n const hasPermission = useCallback((perm: string) => permissions.includes(perm), [permissions]);\r\n const hasAnyPermission = useCallback((perms: string[]) => perms.some((p) => permissions.includes(p)), [permissions]);\r\n const hasAllPermissions = useCallback((perms: string[]) => perms.every((p) => permissions.includes(p)), [permissions]);\r\n\r\n const canAccessRoute = useCallback((path: string) => {\r\n return accessibleRoutes.some((route) => {\r\n if (route.endsWith('/*')) {\r\n return path.startsWith(route.slice(0, -2));\r\n }\r\n return route === path;\r\n });\r\n }, [accessibleRoutes]);\r\n\r\n return {\r\n roles, permissions, accessibleRoutes, loading, error,\r\n hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions,\r\n canAccessRoute, refreshRoles: fetchRoles,\r\n };\r\n}\r\n","import { useRoles } from './useRoles';\r\n\r\ninterface RoleGateProps {\r\n children: React.ReactNode;\r\n roles?: string[];\r\n permissions?: string[];\r\n requireAll?: boolean;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\ninterface RouteGateProps {\r\n children: React.ReactNode;\r\n route: string;\r\n fallback?: React.ReactNode;\r\n loadingFallback?: React.ReactNode;\r\n}\r\n\r\nexport function RoleGate({\r\n children,\r\n roles: requiredRoles,\r\n permissions: requiredPerms,\r\n requireAll = false,\r\n fallback = null,\r\n loadingFallback = null,\r\n}: RoleGateProps) {\r\n const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n let hasAccess = true;\r\n\r\n if (requiredRoles?.length) {\r\n hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);\r\n }\r\n\r\n if (hasAccess && requiredPerms?.length) {\r\n hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);\r\n }\r\n\r\n if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {\r\n hasAccess = true;\r\n }\r\n\r\n // Use individual checks to satisfy linter\r\n void hasRole;\r\n void hasPermission;\r\n\r\n return hasAccess ? <>{children}</> : <>{fallback}</>;\r\n}\r\n\r\nexport function RouteGate({ children, route, fallback = null, loadingFallback = null }: RouteGateProps) {\r\n const { canAccessRoute, loading } = useRoles();\r\n\r\n if (loading) return <>{loadingFallback}</>;\r\n\r\n return canAccessRoute(route) ? <>{children}</> : <>{fallback}</>;\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/storage/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AuthContext
|
|
3
|
-
} from "../chunk-
|
|
4
|
-
import "../chunk-
|
|
3
|
+
} from "../chunk-QHB7LGCA.js";
|
|
4
|
+
import "../chunk-HJ2EIZ4S.js";
|
|
5
5
|
import {
|
|
6
|
-
env,
|
|
7
|
-
features,
|
|
8
6
|
supabase
|
|
9
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-I2YGB7Z6.js";
|
|
8
|
+
import {
|
|
9
|
+
env,
|
|
10
|
+
features
|
|
11
|
+
} from "../chunk-LIUE7M7K.js";
|
|
10
12
|
|
|
11
13
|
// src/storage/useStorage.ts
|
|
12
14
|
import { useState, useCallback, useContext } from "react";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/storage/useStorage.ts","../../src/storage/FileUpload.tsx","../../src/storage/ImagePreview.tsx"],"sourcesContent":["import { useState, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features, env } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport type { StorageResult, StorageFile } from '../core/types';\r\n\r\ntype StorageProvider = 'supabase' | 'stub';\r\n\r\nconst TIER_RETENTION_DAYS: Record<string, number> = {\r\n free: 7,\r\n starter: 30,\r\n pro: 90,\r\n creator: 90,\r\n business: 365,\r\n enterprise: -1,\r\n};\r\n\r\ninterface UploadOptions {\r\n path?: string;\r\n public?: boolean;\r\n bucket?: string;\r\n}\r\n\r\ninterface UrlOptions {\r\n signed?: boolean;\r\n expiresIn?: number;\r\n}\r\n\r\ninterface UseStorageReturn {\r\n upload: (file: File, options?: UploadOptions) => Promise<StorageResult>;\r\n download: (path: string, bucket?: string) => Promise<Blob>;\r\n getUrl: (path: string, options?: UrlOptions & { bucket?: string }) => Promise<string>;\r\n list: (prefix?: string, bucket?: string) => Promise<StorageFile[]>;\r\n remove: (path: string, bucket?: string) => Promise<void>;\r\n isConfigured: boolean;\r\n provider: StorageProvider;\r\n retentionDays: number;\r\n}\r\n\r\nfunction getDefaultBucket(isPublic = false): string {\r\n if (isPublic && env.STORAGE_BUCKET_PUBLIC) return env.STORAGE_BUCKET_PUBLIC;\r\n if (!isPublic && env.STORAGE_BUCKET_PRIVATE) return env.STORAGE_BUCKET_PRIVATE;\r\n if (env.STORAGE_BUCKET_PRIVATE) return env.STORAGE_BUCKET_PRIVATE;\r\n return 'uploads';\r\n}\r\n\r\nexport function useStorage(): UseStorageReturn {\r\n const auth = useContext(AuthContext);\r\n const [provider] = useState<StorageProvider>(features.storage ? 'supabase' : 'stub');\r\n const tier = (auth?.profile as unknown as Record<string, unknown>)?.subscription_tier as string || 'free';\r\n const retentionDays = TIER_RETENTION_DAYS[tier] ?? 7;\r\n\r\n const getUserPath = useCallback((path?: string): string => {\r\n const userId = auth?.user?.id;\r\n const basePath = userId ? `user/${userId}` : 'public';\r\n return path ? `${basePath}/${path}` : basePath;\r\n }, [auth?.user?.id]);\r\n\r\n const upload = useCallback(async (file: File, options: UploadOptions = {}): Promise<StorageResult> => {\r\n if (!features.storage) {\r\n throw new Error('Storage is not configured. Set SUPABASE_URL to enable file storage.');\r\n }\r\n\r\n const bucket = options.bucket || getDefaultBucket(options.public);\r\n const filePath = getUserPath(options.path || `${Date.now()}_${file.name}`);\r\n\r\n const { data, error } = await supabase.storage\r\n .from(bucket)\r\n .upload(filePath, file, {\r\n cacheControl: '3600',\r\n upsert: false,\r\n });\r\n\r\n if (error) throw error;\r\n\r\n let url: string;\r\n if (options.public) {\r\n const { data: urlData } = supabase.storage.from(bucket).getPublicUrl(filePath);\r\n url = urlData.publicUrl;\r\n } else {\r\n const { data: signedData, error: signedError } = await supabase.storage\r\n .from(bucket)\r\n .createSignedUrl(filePath, 3600);\r\n if (signedError) throw signedError;\r\n url = signedData.signedUrl;\r\n }\r\n\r\n return {\r\n path: data.path,\r\n url,\r\n size: file.size,\r\n type: file.type,\r\n };\r\n }, [getUserPath]);\r\n\r\n const download = useCallback(async (path: string, bucket = getDefaultBucket()): Promise<Blob> => {\r\n if (!features.storage) throw new Error('Storage is not configured');\r\n\r\n const { data, error } = await supabase.storage.from(bucket).download(path);\r\n if (error) throw error;\r\n return data;\r\n }, []);\r\n\r\n const getUrl = useCallback(async (path: string, options: UrlOptions & { bucket?: string } = {}): Promise<string> => {\r\n if (!features.storage) return '';\r\n\r\n const bucket = options.bucket || getDefaultBucket();\r\n\r\n if (options.signed !== false) {\r\n const { data, error } = await supabase.storage\r\n .from(bucket)\r\n .createSignedUrl(path, options.expiresIn || 3600);\r\n if (error) throw error;\r\n return data.signedUrl;\r\n }\r\n\r\n const { data } = supabase.storage.from(bucket).getPublicUrl(path);\r\n return data.publicUrl;\r\n }, []);\r\n\r\n const list = useCallback(async (prefix?: string, bucket = getDefaultBucket()): Promise<StorageFile[]> => {\r\n if (!features.storage) return [];\r\n\r\n const searchPath = prefix || getUserPath();\r\n const { data, error } = await supabase.storage.from(bucket).list(searchPath);\r\n if (error) throw error;\r\n\r\n return (data || []).map((item) => ({\r\n name: item.name,\r\n path: `${searchPath}/${item.name}`,\r\n size: (item.metadata as Record<string, unknown>)?.size as number || 0,\r\n type: (item.metadata as Record<string, unknown>)?.mimetype as string || '',\r\n created_at: item.created_at || '',\r\n updated_at: item.updated_at || item.created_at || '',\r\n }));\r\n }, [getUserPath]);\r\n\r\n const remove = useCallback(async (path: string, bucket = getDefaultBucket()): Promise<void> => {\r\n if (!features.storage) throw new Error('Storage is not configured');\r\n\r\n const { error } = await supabase.storage.from(bucket).remove([path]);\r\n if (error) throw error;\r\n }, []);\r\n\r\n return { upload, download, getUrl, list, remove, isConfigured: features.storage, provider, retentionDays };\r\n}\r\n","import { useState, useRef, useCallback } from 'react';\r\nimport { useStorage } from './useStorage';\r\nimport type { StorageResult } from '../core/types';\r\n\r\ninterface FileUploadProps {\r\n onUpload?: (result: StorageResult) => void;\r\n onError?: (error: Error) => void;\r\n accept?: string;\r\n maxSize?: number;\r\n multiple?: boolean;\r\n children?: React.ReactNode;\r\n className?: string;\r\n bucket?: string;\r\n public?: boolean;\r\n}\r\n\r\nexport function FileUpload({\r\n onUpload,\r\n onError,\r\n accept,\r\n maxSize = 10 * 1024 * 1024,\r\n multiple = false,\r\n children,\r\n className = '',\r\n bucket,\r\n public: isPublic = false,\r\n}: FileUploadProps) {\r\n const { upload, isConfigured } = useStorage();\r\n const [uploading, setUploading] = useState(false);\r\n const [dragOver, setDragOver] = useState(false);\r\n const fileInputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleFiles = useCallback(async (files: FileList | null) => {\r\n if (!files || files.length === 0) return;\r\n\r\n setUploading(true);\r\n\r\n for (const file of Array.from(files)) {\r\n if (file.size > maxSize) {\r\n onError?.(new Error(`File ${file.name} exceeds maximum size of ${Math.round(maxSize / 1024 / 1024)}MB`));\r\n continue;\r\n }\r\n\r\n try {\r\n const result = await upload(file, { bucket, public: isPublic });\r\n onUpload?.(result);\r\n } catch (err: unknown) {\r\n onError?.(err instanceof Error ? err : new Error('Upload failed'));\r\n }\r\n }\r\n\r\n setUploading(false);\r\n }, [upload, maxSize, onUpload, onError, bucket, isPublic]);\r\n\r\n const handleDrop = useCallback((e: React.DragEvent) => {\r\n e.preventDefault();\r\n setDragOver(false);\r\n handleFiles(e.dataTransfer.files);\r\n }, [handleFiles]);\r\n\r\n if (!isConfigured) {\r\n return (\r\n <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280', border: '1px dashed #d1d5db', borderRadius: '8px' }}>\r\n Storage is not configured\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={className}\r\n onDrop={handleDrop}\r\n onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}\r\n onDragLeave={() => setDragOver(false)}\r\n onClick={() => fileInputRef.current?.click()}\r\n style={{\r\n border: `2px dashed ${dragOver ? '#3b82f6' : '#d1d5db'}`,\r\n borderRadius: '8px',\r\n padding: '24px',\r\n textAlign: 'center',\r\n cursor: 'pointer',\r\n backgroundColor: dragOver ? '#eff6ff' : 'transparent',\r\n transition: 'all 0.2s',\r\n }}\r\n >\r\n <input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept={accept}\r\n multiple={multiple}\r\n onChange={(e) => handleFiles(e.target.files)}\r\n style={{ display: 'none' }}\r\n />\r\n\r\n {children || (\r\n <div>\r\n {uploading ? (\r\n <p style={{ color: '#3b82f6' }}>Uploading...</p>\r\n ) : (\r\n <>\r\n <p style={{ fontWeight: 500, marginBottom: '4px' }}>Drop files here or click to upload</p>\r\n <p style={{ color: '#9ca3af', fontSize: '14px' }}>\r\n Max size: {Math.round(maxSize / 1024 / 1024)}MB\r\n </p>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useEffect } from 'react';\r\nimport { useStorage } from './useStorage';\r\n\r\ninterface ImagePreviewProps {\r\n path: string;\r\n alt?: string;\r\n className?: string;\r\n width?: number | string;\r\n height?: number | string;\r\n bucket?: string;\r\n signed?: boolean;\r\n}\r\n\r\nexport function ImagePreview({ path, alt = '', className = '', width, height, bucket, signed = true }: ImagePreviewProps) {\r\n const { getUrl, isConfigured } = useStorage();\r\n const [url, setUrl] = useState<string>('');\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState(false);\r\n\r\n useEffect(() => {\r\n if (!isConfigured || !path) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n getUrl(path, { signed, bucket })\r\n .then(setUrl)\r\n .catch(() => setError(true))\r\n .finally(() => setLoading(false));\r\n }, [path, signed, bucket, getUrl, isConfigured]);\r\n\r\n if (loading) {\r\n return (\r\n <div style={{ width: width || '100%', height: height || '200px', backgroundColor: '#f3f4f6', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <span style={{ color: '#9ca3af' }}>Loading...</span>\r\n </div>\r\n );\r\n }\r\n\r\n if (error || !url) {\r\n return (\r\n <div style={{ width: width || '100%', height: height || '200px', backgroundColor: '#f3f4f6', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <span style={{ color: '#9ca3af' }}>Image not available</span>\r\n </div>\r\n );\r\n }\r\n\r\n return <img src={url} alt={alt} className={className} style={{ width, height, objectFit: 'cover', borderRadius: '8px' }} />;\r\n}\r\n"],"mappings":"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","names":["data","useState","useCallback","useState","useCallback","useState","jsx","useState"]}
|
|
1
|
+
{"version":3,"sources":["../../src/storage/useStorage.ts","../../src/storage/FileUpload.tsx","../../src/storage/ImagePreview.tsx"],"sourcesContent":["import { useState, useCallback, useContext } from 'react';\r\nimport { supabase } from '../core/supabase';\r\nimport { features, env } from '../core/config';\r\nimport { AuthContext } from '../auth/AuthProvider';\r\nimport type { StorageResult, StorageFile } from '../core/types';\r\n\r\ntype StorageProvider = 'supabase' | 'stub';\r\n\r\nconst TIER_RETENTION_DAYS: Record<string, number> = {\r\n free: 7,\r\n starter: 30,\r\n pro: 90,\r\n creator: 90,\r\n business: 365,\r\n enterprise: -1,\r\n};\r\n\r\ninterface UploadOptions {\r\n path?: string;\r\n public?: boolean;\r\n bucket?: string;\r\n}\r\n\r\ninterface UrlOptions {\r\n signed?: boolean;\r\n expiresIn?: number;\r\n}\r\n\r\ninterface UseStorageReturn {\r\n upload: (file: File, options?: UploadOptions) => Promise<StorageResult>;\r\n download: (path: string, bucket?: string) => Promise<Blob>;\r\n getUrl: (path: string, options?: UrlOptions & { bucket?: string }) => Promise<string>;\r\n list: (prefix?: string, bucket?: string) => Promise<StorageFile[]>;\r\n remove: (path: string, bucket?: string) => Promise<void>;\r\n isConfigured: boolean;\r\n provider: StorageProvider;\r\n retentionDays: number;\r\n}\r\n\r\nfunction getDefaultBucket(isPublic = false): string {\r\n if (isPublic && env.STORAGE_BUCKET_PUBLIC) return env.STORAGE_BUCKET_PUBLIC;\r\n if (!isPublic && env.STORAGE_BUCKET_PRIVATE) return env.STORAGE_BUCKET_PRIVATE;\r\n if (env.STORAGE_BUCKET_PRIVATE) return env.STORAGE_BUCKET_PRIVATE;\r\n return 'uploads';\r\n}\r\n\r\nexport function useStorage(): UseStorageReturn {\r\n const auth = useContext(AuthContext);\r\n const [provider] = useState<StorageProvider>(features.storage ? 'supabase' : 'stub');\r\n const tier = (auth?.profile as unknown as Record<string, unknown>)?.subscription_tier as string || 'free';\r\n const retentionDays = TIER_RETENTION_DAYS[tier] ?? 7;\r\n\r\n const getUserPath = useCallback((path?: string): string => {\r\n const userId = auth?.user?.id;\r\n const basePath = userId ? `user/${userId}` : 'public';\r\n return path ? `${basePath}/${path}` : basePath;\r\n }, [auth?.user?.id]);\r\n\r\n const upload = useCallback(async (file: File, options: UploadOptions = {}): Promise<StorageResult> => {\r\n if (!features.storage) {\r\n throw new Error('Storage is not configured. Set SUPABASE_URL to enable file storage.');\r\n }\r\n\r\n const bucket = options.bucket || getDefaultBucket(options.public);\r\n const filePath = getUserPath(options.path || `${Date.now()}_${file.name}`);\r\n\r\n const { data, error } = await supabase.storage\r\n .from(bucket)\r\n .upload(filePath, file, {\r\n cacheControl: '3600',\r\n upsert: false,\r\n });\r\n\r\n if (error) throw error;\r\n\r\n let url: string;\r\n if (options.public) {\r\n const { data: urlData } = supabase.storage.from(bucket).getPublicUrl(filePath);\r\n url = urlData.publicUrl;\r\n } else {\r\n const { data: signedData, error: signedError } = await supabase.storage\r\n .from(bucket)\r\n .createSignedUrl(filePath, 3600);\r\n if (signedError) throw signedError;\r\n url = signedData.signedUrl;\r\n }\r\n\r\n return {\r\n path: data.path,\r\n url,\r\n size: file.size,\r\n type: file.type,\r\n };\r\n }, [getUserPath]);\r\n\r\n const download = useCallback(async (path: string, bucket = getDefaultBucket()): Promise<Blob> => {\r\n if (!features.storage) throw new Error('Storage is not configured');\r\n\r\n const { data, error } = await supabase.storage.from(bucket).download(path);\r\n if (error) throw error;\r\n return data;\r\n }, []);\r\n\r\n const getUrl = useCallback(async (path: string, options: UrlOptions & { bucket?: string } = {}): Promise<string> => {\r\n if (!features.storage) return '';\r\n\r\n const bucket = options.bucket || getDefaultBucket();\r\n\r\n if (options.signed !== false) {\r\n const { data, error } = await supabase.storage\r\n .from(bucket)\r\n .createSignedUrl(path, options.expiresIn || 3600);\r\n if (error) throw error;\r\n return data.signedUrl;\r\n }\r\n\r\n const { data } = supabase.storage.from(bucket).getPublicUrl(path);\r\n return data.publicUrl;\r\n }, []);\r\n\r\n const list = useCallback(async (prefix?: string, bucket = getDefaultBucket()): Promise<StorageFile[]> => {\r\n if (!features.storage) return [];\r\n\r\n const searchPath = prefix || getUserPath();\r\n const { data, error } = await supabase.storage.from(bucket).list(searchPath);\r\n if (error) throw error;\r\n\r\n return (data || []).map((item) => ({\r\n name: item.name,\r\n path: `${searchPath}/${item.name}`,\r\n size: (item.metadata as Record<string, unknown>)?.size as number || 0,\r\n type: (item.metadata as Record<string, unknown>)?.mimetype as string || '',\r\n created_at: item.created_at || '',\r\n updated_at: item.updated_at || item.created_at || '',\r\n }));\r\n }, [getUserPath]);\r\n\r\n const remove = useCallback(async (path: string, bucket = getDefaultBucket()): Promise<void> => {\r\n if (!features.storage) throw new Error('Storage is not configured');\r\n\r\n const { error } = await supabase.storage.from(bucket).remove([path]);\r\n if (error) throw error;\r\n }, []);\r\n\r\n return { upload, download, getUrl, list, remove, isConfigured: features.storage, provider, retentionDays };\r\n}\r\n","import { useState, useRef, useCallback } from 'react';\r\nimport { useStorage } from './useStorage';\r\nimport type { StorageResult } from '../core/types';\r\n\r\ninterface FileUploadProps {\r\n onUpload?: (result: StorageResult) => void;\r\n onError?: (error: Error) => void;\r\n accept?: string;\r\n maxSize?: number;\r\n multiple?: boolean;\r\n children?: React.ReactNode;\r\n className?: string;\r\n bucket?: string;\r\n public?: boolean;\r\n}\r\n\r\nexport function FileUpload({\r\n onUpload,\r\n onError,\r\n accept,\r\n maxSize = 10 * 1024 * 1024,\r\n multiple = false,\r\n children,\r\n className = '',\r\n bucket,\r\n public: isPublic = false,\r\n}: FileUploadProps) {\r\n const { upload, isConfigured } = useStorage();\r\n const [uploading, setUploading] = useState(false);\r\n const [dragOver, setDragOver] = useState(false);\r\n const fileInputRef = useRef<HTMLInputElement>(null);\r\n\r\n const handleFiles = useCallback(async (files: FileList | null) => {\r\n if (!files || files.length === 0) return;\r\n\r\n setUploading(true);\r\n\r\n for (const file of Array.from(files)) {\r\n if (file.size > maxSize) {\r\n onError?.(new Error(`File ${file.name} exceeds maximum size of ${Math.round(maxSize / 1024 / 1024)}MB`));\r\n continue;\r\n }\r\n\r\n try {\r\n const result = await upload(file, { bucket, public: isPublic });\r\n onUpload?.(result);\r\n } catch (err: unknown) {\r\n onError?.(err instanceof Error ? err : new Error('Upload failed'));\r\n }\r\n }\r\n\r\n setUploading(false);\r\n }, [upload, maxSize, onUpload, onError, bucket, isPublic]);\r\n\r\n const handleDrop = useCallback((e: React.DragEvent) => {\r\n e.preventDefault();\r\n setDragOver(false);\r\n handleFiles(e.dataTransfer.files);\r\n }, [handleFiles]);\r\n\r\n if (!isConfigured) {\r\n return (\r\n <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280', border: '1px dashed #d1d5db', borderRadius: '8px' }}>\r\n Storage is not configured\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div\r\n className={className}\r\n onDrop={handleDrop}\r\n onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}\r\n onDragLeave={() => setDragOver(false)}\r\n onClick={() => fileInputRef.current?.click()}\r\n style={{\r\n border: `2px dashed ${dragOver ? '#3b82f6' : '#d1d5db'}`,\r\n borderRadius: '8px',\r\n padding: '24px',\r\n textAlign: 'center',\r\n cursor: 'pointer',\r\n backgroundColor: dragOver ? '#eff6ff' : 'transparent',\r\n transition: 'all 0.2s',\r\n }}\r\n >\r\n <input\r\n ref={fileInputRef}\r\n type=\"file\"\r\n accept={accept}\r\n multiple={multiple}\r\n onChange={(e) => handleFiles(e.target.files)}\r\n style={{ display: 'none' }}\r\n />\r\n\r\n {children || (\r\n <div>\r\n {uploading ? (\r\n <p style={{ color: '#3b82f6' }}>Uploading...</p>\r\n ) : (\r\n <>\r\n <p style={{ fontWeight: 500, marginBottom: '4px' }}>Drop files here or click to upload</p>\r\n <p style={{ color: '#9ca3af', fontSize: '14px' }}>\r\n Max size: {Math.round(maxSize / 1024 / 1024)}MB\r\n </p>\r\n </>\r\n )}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useState, useEffect } from 'react';\r\nimport { useStorage } from './useStorage';\r\n\r\ninterface ImagePreviewProps {\r\n path: string;\r\n alt?: string;\r\n className?: string;\r\n width?: number | string;\r\n height?: number | string;\r\n bucket?: string;\r\n signed?: boolean;\r\n}\r\n\r\nexport function ImagePreview({ path, alt = '', className = '', width, height, bucket, signed = true }: ImagePreviewProps) {\r\n const { getUrl, isConfigured } = useStorage();\r\n const [url, setUrl] = useState<string>('');\r\n const [loading, setLoading] = useState(true);\r\n const [error, setError] = useState(false);\r\n\r\n useEffect(() => {\r\n if (!isConfigured || !path) {\r\n setLoading(false);\r\n return;\r\n }\r\n\r\n getUrl(path, { signed, bucket })\r\n .then(setUrl)\r\n .catch(() => setError(true))\r\n .finally(() => setLoading(false));\r\n }, [path, signed, bucket, getUrl, isConfigured]);\r\n\r\n if (loading) {\r\n return (\r\n <div style={{ width: width || '100%', height: height || '200px', backgroundColor: '#f3f4f6', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <span style={{ color: '#9ca3af' }}>Loading...</span>\r\n </div>\r\n );\r\n }\r\n\r\n if (error || !url) {\r\n return (\r\n <div style={{ width: width || '100%', height: height || '200px', backgroundColor: '#f3f4f6', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>\r\n <span style={{ color: '#9ca3af' }}>Image not available</span>\r\n </div>\r\n );\r\n }\r\n\r\n return <img src={url} alt={alt} className={className} style={{ width, height, objectFit: 'cover', borderRadius: '8px' }} />;\r\n}\r\n"],"mappings":"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","names":["data","useState","useCallback","useState","useCallback","useState","jsx","useState"]}
|