@omnikit-js/ui 0.5.5 → 0.6.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/README.md +100 -255
- package/dist/components/client/index.d.mts +7 -0
- package/dist/components/client/index.mjs +6 -0
- package/dist/components/client/index.mjs.map +1 -0
- package/dist/components/server/index.d.mts +2 -0
- package/dist/components/server/index.mjs +201 -0
- package/dist/components/server/index.mjs.map +1 -0
- package/dist/index-D-5etDTV.d.mts +80 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.mjs +204 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -77
- package/LICENSE +0 -21
- package/dist/styles.css +0 -2506
- package/dist/styles.isolated.css +0 -2643
- package/dist/styles.prefixed.css +0 -248
- package/src/components/BillingClient.tsx +0 -686
- package/src/components/BillingServer.tsx +0 -129
- package/src/components/PaymentMethodForm.tsx +0 -125
- package/src/components/SubscriptionConfirmModal.tsx +0 -213
- package/src/components/theme-provider.tsx +0 -11
- package/src/components/ui/alert.tsx +0 -59
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/card-brand-icon.tsx +0 -88
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/chart.tsx +0 -353
- package/src/components/ui/dialog.tsx +0 -122
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/navigation-menu.tsx +0 -168
- package/src/components/ui/progress.tsx +0 -31
- package/src/components/ui/radio-group.tsx +0 -44
- package/src/components/ui/select.tsx +0 -185
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/tabs.tsx +0 -66
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/tooltip.tsx +0 -30
- package/src/index.ts +0 -13
- package/src/lib/utils.ts +0 -6
- package/src/styles/globals.css +0 -1
- package/src/styles/isolated.css +0 -316
- package/src/styles/main.css +0 -122
- package/src/styles/prefixed-main.css +0 -95
- package/src/styles/prefixed.css +0 -1
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import BillingClient from './BillingClient';
|
|
2
|
-
|
|
3
|
-
// Import types from the target implementation
|
|
4
|
-
interface BillingProps {
|
|
5
|
-
userId: string;
|
|
6
|
-
apiUrl?: string;
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
projectId?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Server actions - these would typically be in a separate file
|
|
12
|
-
async function getCustomerData(userId: string, apiUrl: string, apiKey: string) {
|
|
13
|
-
try {
|
|
14
|
-
const response = await fetch(`${apiUrl}/billing/customers/${userId}`, {
|
|
15
|
-
headers: {
|
|
16
|
-
'x-api-key': apiKey,
|
|
17
|
-
'Content-Type': 'application/json'
|
|
18
|
-
},
|
|
19
|
-
cache: 'no-store'
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
if (!response.ok) {
|
|
23
|
-
throw new Error('Failed to fetch customer data');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const data = await response.json();
|
|
27
|
-
return data.customer;
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error('Error fetching customer data:', error);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function getPricingData(projectId: string, apiUrl: string, apiKey: string) {
|
|
35
|
-
try {
|
|
36
|
-
const response = await fetch(`${apiUrl}/billing/prices?project_id=${projectId}`, {
|
|
37
|
-
headers: {
|
|
38
|
-
'x-api-key': apiKey,
|
|
39
|
-
'Content-Type': 'application/json'
|
|
40
|
-
},
|
|
41
|
-
cache: 'no-store'
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (!response.ok) {
|
|
45
|
-
throw new Error('Failed to fetch pricing data');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return await response.json();
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Error fetching pricing data:', error);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function getPaymentMethods(userId: string, apiUrl: string, apiKey: string) {
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(`${apiUrl}/billing/payment-methods?user_id=${userId}`, {
|
|
58
|
-
headers: {
|
|
59
|
-
'x-api-key': apiKey,
|
|
60
|
-
'Content-Type': 'application/json'
|
|
61
|
-
},
|
|
62
|
-
cache: 'no-store'
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new Error('Failed to fetch payment methods');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return await response.json();
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error('Error fetching payment methods:', error);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export default async function BillingServer({
|
|
77
|
-
userId,
|
|
78
|
-
apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001',
|
|
79
|
-
apiKey = process.env.API_KEY || '',
|
|
80
|
-
projectId = process.env.NEXT_PUBLIC_API_PROJECT || ''
|
|
81
|
-
}: BillingProps) {
|
|
82
|
-
if (!userId) {
|
|
83
|
-
console.error('Billing component: userId is required');
|
|
84
|
-
return <div>Error: User ID is required</div>;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
let customerData = null;
|
|
88
|
-
let pricingData = null;
|
|
89
|
-
let paymentMethods = null;
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// Fetch all billing data in parallel
|
|
93
|
-
const [customer, pricing, methods] = await Promise.all([
|
|
94
|
-
getCustomerData(userId, apiUrl, apiKey),
|
|
95
|
-
getPricingData(projectId, apiUrl, apiKey),
|
|
96
|
-
getPaymentMethods(userId, apiUrl, apiKey)
|
|
97
|
-
]);
|
|
98
|
-
|
|
99
|
-
customerData = customer;
|
|
100
|
-
pricingData = pricing;
|
|
101
|
-
|
|
102
|
-
// Use payment methods from customer data if available, otherwise use the separate endpoint
|
|
103
|
-
if (customer?.payment_methods) {
|
|
104
|
-
paymentMethods = { paymentMethods: customer.payment_methods };
|
|
105
|
-
} else {
|
|
106
|
-
paymentMethods = methods;
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error('Error fetching billing data:', error);
|
|
110
|
-
// Return a fallback UI or error state
|
|
111
|
-
return (
|
|
112
|
-
<div className="p-6 text-center">
|
|
113
|
-
<p className="text-red-600">Failed to load billing information. Please try again later.</p>
|
|
114
|
-
</div>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div className="omnikit-billing-component">
|
|
120
|
-
<BillingClient
|
|
121
|
-
customerData={customerData}
|
|
122
|
-
pricingData={pricingData}
|
|
123
|
-
paymentMethods={paymentMethods}
|
|
124
|
-
apiUrl={apiUrl}
|
|
125
|
-
apiKey={apiKey}
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
5
|
-
import { Button } from './ui/button';
|
|
6
|
-
import { Alert, AlertDescription } from './ui/alert';
|
|
7
|
-
import { Loader2, CheckCircle } from 'lucide-react';
|
|
8
|
-
|
|
9
|
-
interface PaymentMethodFormProps {
|
|
10
|
-
userId: string;
|
|
11
|
-
onSuccess?: () => void;
|
|
12
|
-
onCancel?: () => void;
|
|
13
|
-
createSetupIntent: (userId: string) => Promise<{ client_secret: string }>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default function PaymentMethodForm({ userId, onSuccess, onCancel, createSetupIntent }: PaymentMethodFormProps) {
|
|
17
|
-
const stripe = useStripe();
|
|
18
|
-
const elements = useElements();
|
|
19
|
-
const [loading, setLoading] = useState(false);
|
|
20
|
-
const [error, setError] = useState<string | null>(null);
|
|
21
|
-
const [success, setSuccess] = useState(false);
|
|
22
|
-
|
|
23
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
24
|
-
e.preventDefault();
|
|
25
|
-
|
|
26
|
-
if (!stripe || !elements) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setLoading(true);
|
|
31
|
-
setError(null);
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
// Create setup intent
|
|
35
|
-
const { client_secret } = await createSetupIntent(userId);
|
|
36
|
-
|
|
37
|
-
if (!client_secret) {
|
|
38
|
-
throw new Error('Failed to create setup intent');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Confirm card setup
|
|
42
|
-
const result = await stripe.confirmCardSetup(client_secret, {
|
|
43
|
-
payment_method: {
|
|
44
|
-
card: elements.getElement(CardElement)!,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (result.error) {
|
|
49
|
-
setError(result.error.message || 'An error occurred');
|
|
50
|
-
} else {
|
|
51
|
-
setSuccess(true);
|
|
52
|
-
|
|
53
|
-
// Close modal after 2 seconds
|
|
54
|
-
setTimeout(() => {
|
|
55
|
-
onSuccess && onSuccess();
|
|
56
|
-
}, 2000);
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
60
|
-
} finally {
|
|
61
|
-
setLoading(false);
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if (success) {
|
|
66
|
-
return (
|
|
67
|
-
<div className="text-center py-8">
|
|
68
|
-
<CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
|
|
69
|
-
<p className="text-lg font-medium">Payment method added successfully!</p>
|
|
70
|
-
<p className="text-sm text-muted-foreground mt-2">Redirecting...</p>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<form onSubmit={handleSubmit} className="space-y-6">
|
|
77
|
-
<div>
|
|
78
|
-
<label className="block text-sm font-medium mb-2">
|
|
79
|
-
Card Details
|
|
80
|
-
</label>
|
|
81
|
-
<div className="border rounded-md p-3 bg-background">
|
|
82
|
-
<CardElement
|
|
83
|
-
options={{
|
|
84
|
-
style: {
|
|
85
|
-
base: {
|
|
86
|
-
fontSize: '16px',
|
|
87
|
-
color: '#fff',
|
|
88
|
-
'::placeholder': {
|
|
89
|
-
color: '#999',
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
}}
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
{error && (
|
|
99
|
-
<Alert variant="destructive">
|
|
100
|
-
<AlertDescription>{error}</AlertDescription>
|
|
101
|
-
</Alert>
|
|
102
|
-
)}
|
|
103
|
-
|
|
104
|
-
<div className="flex gap-2">
|
|
105
|
-
<Button
|
|
106
|
-
type="button"
|
|
107
|
-
variant="outline"
|
|
108
|
-
onClick={onCancel}
|
|
109
|
-
disabled={loading}
|
|
110
|
-
className="flex-1"
|
|
111
|
-
>
|
|
112
|
-
Cancel
|
|
113
|
-
</Button>
|
|
114
|
-
<Button
|
|
115
|
-
type="submit"
|
|
116
|
-
disabled={!stripe || loading}
|
|
117
|
-
className="flex-1"
|
|
118
|
-
>
|
|
119
|
-
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
120
|
-
{loading ? 'Processing...' : 'Add Payment Method'}
|
|
121
|
-
</Button>
|
|
122
|
-
</div>
|
|
123
|
-
</form>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from './ui/dialog';
|
|
5
|
-
import { Button } from './ui/button';
|
|
6
|
-
import { Card } from './ui/card';
|
|
7
|
-
import { Check, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
|
|
8
|
-
import { Alert, AlertDescription } from './ui/alert';
|
|
9
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip';
|
|
10
|
-
import { RadioGroup, RadioGroupItem } from './ui/radio-group';
|
|
11
|
-
import { Label } from './ui/label';
|
|
12
|
-
import { CardBrandIcon } from './ui/card-brand-icon';
|
|
13
|
-
|
|
14
|
-
interface SubscriptionConfirmModalProps {
|
|
15
|
-
isOpen: boolean;
|
|
16
|
-
onClose: () => void;
|
|
17
|
-
plan: any;
|
|
18
|
-
currentPlan?: any;
|
|
19
|
-
paymentMethods: any[];
|
|
20
|
-
userId: string;
|
|
21
|
-
onSuccess: () => void;
|
|
22
|
-
createSubscription: (userId: string, priceId: string, paymentMethodId: string) => Promise<{success: boolean; error?: string}>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function SubscriptionConfirmModal({
|
|
26
|
-
isOpen,
|
|
27
|
-
onClose,
|
|
28
|
-
plan,
|
|
29
|
-
currentPlan,
|
|
30
|
-
paymentMethods,
|
|
31
|
-
userId,
|
|
32
|
-
onSuccess,
|
|
33
|
-
createSubscription
|
|
34
|
-
}: SubscriptionConfirmModalProps) {
|
|
35
|
-
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(
|
|
36
|
-
paymentMethods?.find(m => m.is_default)?.stripe_payment_method_id || paymentMethods?.[0]?.stripe_payment_method_id || ''
|
|
37
|
-
);
|
|
38
|
-
const [isSubscribing, setIsSubscribing] = useState(false);
|
|
39
|
-
const [error, setError] = useState<string | null>(null);
|
|
40
|
-
const [success, setSuccess] = useState(false);
|
|
41
|
-
|
|
42
|
-
const formatCurrency = (amount: number) => {
|
|
43
|
-
return new Intl.NumberFormat('en-US', {
|
|
44
|
-
style: 'currency',
|
|
45
|
-
currency: 'usd',
|
|
46
|
-
}).format(amount / 100);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const formatFeatureValue = (value: any) => {
|
|
50
|
-
// Handle string boolean values
|
|
51
|
-
if (value === 'true' || value === true) {
|
|
52
|
-
return '✓'
|
|
53
|
-
}
|
|
54
|
-
if (value === 'false' || value === false) {
|
|
55
|
-
return '✗'
|
|
56
|
-
}
|
|
57
|
-
// Handle numeric values (both string and number)
|
|
58
|
-
const numValue = typeof value === 'string' ? parseFloat(value) : value
|
|
59
|
-
if (!isNaN(numValue) && numValue >= 1000) {
|
|
60
|
-
return new Intl.NumberFormat('en-US').format(numValue)
|
|
61
|
-
}
|
|
62
|
-
return value
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const handleConfirm = async () => {
|
|
66
|
-
setIsSubscribing(true);
|
|
67
|
-
setError(null);
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const priceId = plan.prices[0].stripe_price_id;
|
|
71
|
-
const isFree = parseFloat(plan.prices[0].unit_amount) === 0;
|
|
72
|
-
|
|
73
|
-
const result = await createSubscription(
|
|
74
|
-
userId,
|
|
75
|
-
priceId,
|
|
76
|
-
isFree ? '' : selectedPaymentMethod
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
if (result.success) {
|
|
80
|
-
setSuccess(true);
|
|
81
|
-
setTimeout(() => {
|
|
82
|
-
onSuccess();
|
|
83
|
-
onClose();
|
|
84
|
-
}, 2000);
|
|
85
|
-
} else {
|
|
86
|
-
setError(result.error || 'Failed to create subscription');
|
|
87
|
-
}
|
|
88
|
-
} catch (err) {
|
|
89
|
-
setError(err instanceof Error ? err.message : 'An error occurred');
|
|
90
|
-
} finally {
|
|
91
|
-
setIsSubscribing(false);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
if (!plan) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const price = plan?.prices?.[0];
|
|
100
|
-
const isFree = price && parseFloat(price.unit_amount) === 0;
|
|
101
|
-
const isDowngrade = currentPlan && price && parseFloat(price.unit_amount) < parseFloat(currentPlan.prices[0].unit_amount);
|
|
102
|
-
|
|
103
|
-
if (success) {
|
|
104
|
-
return (
|
|
105
|
-
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
106
|
-
<DialogContent>
|
|
107
|
-
<div className="text-center py-8">
|
|
108
|
-
<CheckCircle className="h-12 w-12 text-green-500 mx-auto mb-4" />
|
|
109
|
-
<h3 className="text-lg font-medium">Subscription Successful!</h3>
|
|
110
|
-
<p className="text-sm text-muted-foreground mt-2">
|
|
111
|
-
You have been successfully subscribed to {plan.name}
|
|
112
|
-
</p>
|
|
113
|
-
</div>
|
|
114
|
-
</DialogContent>
|
|
115
|
-
</Dialog>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
121
|
-
<DialogContent className="sm:max-w-md">
|
|
122
|
-
<DialogHeader>
|
|
123
|
-
<DialogTitle>Confirm Subscription</DialogTitle>
|
|
124
|
-
<DialogDescription>
|
|
125
|
-
{currentPlan ? 'You are about to change your subscription plan' : 'You are about to subscribe to a new plan'}
|
|
126
|
-
</DialogDescription>
|
|
127
|
-
</DialogHeader>
|
|
128
|
-
|
|
129
|
-
<div className="space-y-4">
|
|
130
|
-
<Card className="p-4">
|
|
131
|
-
<h4 className="font-medium mb-2">{plan.name}</h4>
|
|
132
|
-
<p className="text-2xl font-bold mb-2">
|
|
133
|
-
{isFree ? 'Free' : formatCurrency(parseFloat(price.unit_amount) * 100)}
|
|
134
|
-
{!isFree && <span className="text-sm font-normal text-muted-foreground">/month</span>}
|
|
135
|
-
</p>
|
|
136
|
-
<ul className="space-y-1">
|
|
137
|
-
{plan.features?.map((feature: any) => (
|
|
138
|
-
<li key={feature.id} className="flex items-start text-sm">
|
|
139
|
-
<Check className="h-4 w-4 mr-2 text-primary mt-0.5 shrink-0" />
|
|
140
|
-
<TooltipProvider>
|
|
141
|
-
<Tooltip>
|
|
142
|
-
<TooltipTrigger asChild>
|
|
143
|
-
<span className="cursor-help">
|
|
144
|
-
{formatFeatureValue(feature.value)} <span className="text-sm text-muted-foreground font-normal">{feature.units}</span> - {feature.name || feature.description}
|
|
145
|
-
</span>
|
|
146
|
-
</TooltipTrigger>
|
|
147
|
-
<TooltipContent>
|
|
148
|
-
<p>{feature.description}</p>
|
|
149
|
-
</TooltipContent>
|
|
150
|
-
</Tooltip>
|
|
151
|
-
</TooltipProvider>
|
|
152
|
-
</li>
|
|
153
|
-
))}
|
|
154
|
-
</ul>
|
|
155
|
-
</Card>
|
|
156
|
-
|
|
157
|
-
{!isFree && paymentMethods?.length > 0 && (
|
|
158
|
-
<div className="space-y-3">
|
|
159
|
-
<Label>Payment Method</Label>
|
|
160
|
-
<RadioGroup value={selectedPaymentMethod} onValueChange={setSelectedPaymentMethod}>
|
|
161
|
-
{paymentMethods.map((method) => (
|
|
162
|
-
<div key={method.id} className="flex items-center space-x-3">
|
|
163
|
-
<RadioGroupItem value={method.stripe_payment_method_id} id={method.id.toString()} />
|
|
164
|
-
<Label htmlFor={method.id.toString()} className="flex items-center gap-3 cursor-pointer flex-1">
|
|
165
|
-
<CardBrandIcon brand={method.brand} className="w-12 h-8" />
|
|
166
|
-
<span className="font-medium">•••• {method.last4}</span>
|
|
167
|
-
{method.is_default && (
|
|
168
|
-
<span className="text-xs text-muted-foreground">(Default)</span>
|
|
169
|
-
)}
|
|
170
|
-
</Label>
|
|
171
|
-
</div>
|
|
172
|
-
))}
|
|
173
|
-
</RadioGroup>
|
|
174
|
-
</div>
|
|
175
|
-
)}
|
|
176
|
-
|
|
177
|
-
{!isFree && (
|
|
178
|
-
<Alert>
|
|
179
|
-
<AlertCircle className="h-4 w-4" />
|
|
180
|
-
<AlertDescription>
|
|
181
|
-
You will be charged {formatCurrency(parseFloat(price.unit_amount) * 100)} immediately upon confirmation.
|
|
182
|
-
{isDowngrade && ' Refunds for downgrades will be prorated and applied to your next invoice.'}
|
|
183
|
-
</AlertDescription>
|
|
184
|
-
</Alert>
|
|
185
|
-
)}
|
|
186
|
-
|
|
187
|
-
{error && (
|
|
188
|
-
<Alert variant="destructive">
|
|
189
|
-
<AlertDescription>{error}</AlertDescription>
|
|
190
|
-
</Alert>
|
|
191
|
-
)}
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<DialogFooter className="gap-2">
|
|
195
|
-
<Button
|
|
196
|
-
variant="outline"
|
|
197
|
-
onClick={onClose}
|
|
198
|
-
disabled={isSubscribing}
|
|
199
|
-
>
|
|
200
|
-
Cancel
|
|
201
|
-
</Button>
|
|
202
|
-
<Button
|
|
203
|
-
onClick={handleConfirm}
|
|
204
|
-
disabled={isSubscribing || (!isFree && !selectedPaymentMethod)}
|
|
205
|
-
>
|
|
206
|
-
{isSubscribing && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
207
|
-
{isSubscribing ? 'Processing...' : 'Confirm Subscription'}
|
|
208
|
-
</Button>
|
|
209
|
-
</DialogFooter>
|
|
210
|
-
</DialogContent>
|
|
211
|
-
</Dialog>
|
|
212
|
-
);
|
|
213
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
|
5
|
-
|
|
6
|
-
export function ThemeProvider({
|
|
7
|
-
children,
|
|
8
|
-
...props
|
|
9
|
-
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
10
|
-
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
|
11
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
-
|
|
4
|
-
import { cn } from "../../lib/utils"
|
|
5
|
-
|
|
6
|
-
const alertVariants = cva(
|
|
7
|
-
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-background text-foreground border-border",
|
|
12
|
-
destructive:
|
|
13
|
-
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
defaultVariants: {
|
|
17
|
-
variant: "default",
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
const Alert = React.forwardRef<
|
|
23
|
-
HTMLDivElement,
|
|
24
|
-
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
25
|
-
>(({ className, variant, ...props }, ref) => (
|
|
26
|
-
<div
|
|
27
|
-
ref={ref}
|
|
28
|
-
role="alert"
|
|
29
|
-
className={cn(alertVariants({ variant }), className)}
|
|
30
|
-
{...props}
|
|
31
|
-
/>
|
|
32
|
-
))
|
|
33
|
-
Alert.displayName = "Alert"
|
|
34
|
-
|
|
35
|
-
const AlertTitle = React.forwardRef<
|
|
36
|
-
HTMLParagraphElement,
|
|
37
|
-
React.HTMLAttributes<HTMLHeadingElement>
|
|
38
|
-
>(({ className, ...props }, ref) => (
|
|
39
|
-
<h5
|
|
40
|
-
ref={ref}
|
|
41
|
-
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
|
42
|
-
{...props}
|
|
43
|
-
/>
|
|
44
|
-
))
|
|
45
|
-
AlertTitle.displayName = "AlertTitle"
|
|
46
|
-
|
|
47
|
-
const AlertDescription = React.forwardRef<
|
|
48
|
-
HTMLParagraphElement,
|
|
49
|
-
React.HTMLAttributes<HTMLParagraphElement>
|
|
50
|
-
>(({ className, ...props }, ref) => (
|
|
51
|
-
<div
|
|
52
|
-
ref={ref}
|
|
53
|
-
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
|
54
|
-
{...props}
|
|
55
|
-
/>
|
|
56
|
-
))
|
|
57
|
-
AlertDescription.displayName = "AlertDescription"
|
|
58
|
-
|
|
59
|
-
export { Alert, AlertTitle, AlertDescription }
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import * as React from "react"
|
|
4
|
-
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
|
5
|
-
|
|
6
|
-
import { cn } from "../../lib/utils"
|
|
7
|
-
|
|
8
|
-
function Avatar({
|
|
9
|
-
className,
|
|
10
|
-
...props
|
|
11
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
|
12
|
-
return (
|
|
13
|
-
<AvatarPrimitive.Root
|
|
14
|
-
data-slot="avatar"
|
|
15
|
-
className={cn(
|
|
16
|
-
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
|
17
|
-
className
|
|
18
|
-
)}
|
|
19
|
-
{...props}
|
|
20
|
-
/>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function AvatarImage({
|
|
25
|
-
className,
|
|
26
|
-
...props
|
|
27
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
28
|
-
return (
|
|
29
|
-
<AvatarPrimitive.Image
|
|
30
|
-
data-slot="avatar-image"
|
|
31
|
-
className={cn("aspect-square size-full", className)}
|
|
32
|
-
{...props}
|
|
33
|
-
/>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function AvatarFallback({
|
|
38
|
-
className,
|
|
39
|
-
...props
|
|
40
|
-
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
41
|
-
return (
|
|
42
|
-
<AvatarPrimitive.Fallback
|
|
43
|
-
data-slot="avatar-fallback"
|
|
44
|
-
className={cn(
|
|
45
|
-
"bg-muted flex size-full items-center justify-center rounded-full",
|
|
46
|
-
className
|
|
47
|
-
)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export { Avatar, AvatarImage, AvatarFallback }
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils"
|
|
6
|
-
|
|
7
|
-
const badgeVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default:
|
|
13
|
-
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
14
|
-
secondary:
|
|
15
|
-
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
16
|
-
destructive:
|
|
17
|
-
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
18
|
-
outline:
|
|
19
|
-
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
defaultVariants: {
|
|
23
|
-
variant: "default",
|
|
24
|
-
},
|
|
25
|
-
}
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
function Badge({
|
|
29
|
-
className,
|
|
30
|
-
variant,
|
|
31
|
-
asChild = false,
|
|
32
|
-
...props
|
|
33
|
-
}: React.ComponentProps<"span"> &
|
|
34
|
-
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
35
|
-
const Comp = asChild ? Slot : "span"
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<Comp
|
|
39
|
-
data-slot="badge"
|
|
40
|
-
className={cn(badgeVariants({ variant }), className)}
|
|
41
|
-
{...props}
|
|
42
|
-
/>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export { Badge, badgeVariants }
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
-
|
|
5
|
-
import { cn } from "../../lib/utils"
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default:
|
|
13
|
-
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
14
|
-
destructive:
|
|
15
|
-
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
16
|
-
outline:
|
|
17
|
-
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
18
|
-
secondary:
|
|
19
|
-
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
|
-
ghost:
|
|
21
|
-
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
22
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
-
},
|
|
24
|
-
size: {
|
|
25
|
-
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
26
|
-
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
27
|
-
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
28
|
-
icon: "size-9",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
defaultVariants: {
|
|
32
|
-
variant: "default",
|
|
33
|
-
size: "default",
|
|
34
|
-
},
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
function Button({
|
|
39
|
-
className,
|
|
40
|
-
variant,
|
|
41
|
-
size,
|
|
42
|
-
asChild = false,
|
|
43
|
-
...props
|
|
44
|
-
}: React.ComponentProps<"button"> &
|
|
45
|
-
VariantProps<typeof buttonVariants> & {
|
|
46
|
-
asChild?: boolean
|
|
47
|
-
}) {
|
|
48
|
-
const Comp = asChild ? Slot : "button"
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<Comp
|
|
52
|
-
data-slot="button"
|
|
53
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
54
|
-
{...props}
|
|
55
|
-
/>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export { Button, buttonVariants }
|