@omnikit-js/ui 0.3.2 → 0.3.3

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 CHANGED
@@ -19,6 +19,31 @@ A comprehensive SaaS billing component for Next.js applications with Stripe inte
19
19
  npm install @omnikit-js/ui
20
20
  ```
21
21
 
22
+ **Important**: The component requires Tailwind CSS to be configured in your project.
23
+
24
+ ### Setup Tailwind CSS
25
+
26
+ 1. Install Tailwind CSS if you haven't already:
27
+ ```bash
28
+ npm install -D tailwindcss postcss autoprefixer
29
+ npx tailwindcss init -p
30
+ ```
31
+
32
+ 2. Import the component's CSS in your main CSS file or `layout.js`:
33
+ ```css
34
+ @import '@omnikit-js/ui/styles.css';
35
+ ```
36
+
37
+ Or in your `app/globals.css`:
38
+ ```css
39
+ @tailwind base;
40
+ @tailwind components;
41
+ @tailwind utilities;
42
+
43
+ /* Import the component styles */
44
+ @import '@omnikit-js/ui/styles.css';
45
+ ```
46
+
22
47
  ## Prerequisites
23
48
 
24
49
  1. A Stripe account with API keys
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnikit-js/ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A SaaS billing component for Next.js with Stripe integration",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -13,7 +13,8 @@
13
13
  }
14
14
  },
15
15
  "files": [
16
- "src"
16
+ "src",
17
+ "styles.css"
17
18
  ],
18
19
  "scripts": {
19
20
  "build": "rollup -c",
@@ -49,7 +49,8 @@ async function setDefaultPaymentMethod(paymentMethodId: string, apiUrl: string,
49
49
  headers: {
50
50
  'x-api-key': apiKey,
51
51
  'Content-Type': 'application/json'
52
- }
52
+ },
53
+ body: JSON.stringify({})
53
54
  });
54
55
 
55
56
  if (!response.ok) {
@@ -65,7 +66,8 @@ async function deletePaymentMethod(paymentMethodId: string, apiUrl: string, apiK
65
66
  headers: {
66
67
  'x-api-key': apiKey,
67
68
  'Content-Type': 'application/json'
68
- }
69
+ },
70
+ body: JSON.stringify({})
69
71
  });
70
72
 
71
73
  if (!response.ok) {
@@ -145,16 +147,24 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
145
147
  const [isLoading, setIsLoading] = useState(false);
146
148
  const [addPaymentModalOpen, setAddPaymentModalOpen] = useState(false);
147
149
  const [subscribeModalOpen, setSubscribeModalOpen] = useState(false);
150
+ const [deletePaymentModalOpen, setDeletePaymentModalOpen] = useState(false);
148
151
  const [selectedPlan, setSelectedPlan] = useState<any>(null);
152
+ const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);
149
153
  const [activeTab, setActiveTab] = useState('overview');
150
154
  const router = useRouter();
151
155
 
152
156
  const formatFeatureValue = (value: any) => {
153
- if (typeof value === 'boolean') {
154
- return value ? '' : '✗'
157
+ // Handle string boolean values
158
+ if (value === 'true' || value === true) {
159
+ return '✓'
160
+ }
161
+ if (value === 'false' || value === false) {
162
+ return '✗'
155
163
  }
156
- if (typeof value === 'number' && value >= 1000) {
157
- return new Intl.NumberFormat('en-US').format(value)
164
+ // Handle numeric values (both string and number)
165
+ const numValue = typeof value === 'string' ? parseFloat(value) : value
166
+ if (!isNaN(numValue) && numValue >= 1000) {
167
+ return new Intl.NumberFormat('en-US').format(numValue)
158
168
  }
159
169
  return value
160
170
  }
@@ -217,19 +227,24 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
217
227
  }
218
228
  };
219
229
 
220
- const handleDeletePayment = async (paymentMethodId: string) => {
221
- if (!confirm('Are you sure you want to delete this payment method?')) {
222
- return;
223
- }
224
-
225
- setIsLoading(true);
230
+ const handleDeletePayment = (paymentMethodId: string) => {
231
+ setSelectedPaymentMethodId(paymentMethodId);
232
+ setDeletePaymentModalOpen(true);
233
+ };
234
+
235
+ const confirmDeletePayment = async () => {
236
+ if (!selectedPaymentMethodId) return;
237
+
226
238
  try {
227
- await deletePaymentMethod(paymentMethodId, apiUrl, apiKey);
239
+ setIsLoading(true);
240
+ await deletePaymentMethod(selectedPaymentMethodId, apiUrl, apiKey);
228
241
  router.refresh();
229
242
  } catch (error) {
230
- console.error('Failed to delete payment method:', error);
243
+ console.error('Error deleting payment method:', error);
231
244
  } finally {
232
245
  setIsLoading(false);
246
+ setDeletePaymentModalOpen(false);
247
+ setSelectedPaymentMethodId(null);
233
248
  }
234
249
  };
235
250
 
@@ -404,7 +419,7 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
404
419
  <CardDescription>Your resource usage for the current billing period</CardDescription>
405
420
  </CardHeader>
406
421
  <CardContent className="space-y-6">
407
- {usage?.data?.map((item: any) => {
422
+ {usage?.data?.filter((item: any) => typeof item.limit === 'number' && item.limit > 0).map((item: any) => {
408
423
  const percentage = typeof item.limit === 'number'
409
424
  ? Math.min((item.current_usage / item.limit) * 100, 100)
410
425
  : 0;
@@ -504,7 +519,7 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
504
519
  };
505
520
 
506
521
  return (
507
- <Card key={product.id} className={`relative overflow-hidden ${isCurrentPlan ? "border-primary shadow-md" : "border-muted"} ${product.metadata?.popular === 'true' && !isCurrentPlan ? "border-primary/50" : ""}`}>
522
+ <Card key={product.id} className={`relative overflow-hidden h-full flex flex-col ${isCurrentPlan ? "border-primary shadow-md" : "border-muted"} ${product.metadata?.popular === 'true' && !isCurrentPlan ? "border-primary/50" : ""}`}>
508
523
  {product.metadata?.popular === 'true' && !isCurrentPlan && (
509
524
  <div className="absolute top-0 right-0 bg-primary text-primary-foreground text-xs font-medium px-3 py-1 rounded-bl-lg">
510
525
  POPULAR
@@ -514,30 +529,32 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
514
529
  <CardTitle className="text-lg font-semibold">{product.name}</CardTitle>
515
530
  <CardDescription className="mt-1 text-sm">{product.description}</CardDescription>
516
531
  </CardHeader>
517
- <CardContent className="text-center pb-4">
532
+ <CardContent className="text-center pb-4 flex-1">
518
533
  <div className="mb-4">
519
534
  <div className="flex items-baseline justify-center">
520
535
  <span className="text-3xl font-bold tracking-tight">
521
- {parseFloat(price?.unit_amount) === 0 ? 'Free' : `$${parseFloat(price?.unit_amount).toFixed(0)}`}
536
+ {parseFloat(price?.unit_amount) === 0 ? 'Free' : `$${parseFloat(price?.unit_amount).toFixed(2).replace(/\.00$/, '')}`}
522
537
  </span>
523
538
  {parseFloat(price?.unit_amount) > 0 && (
524
539
  <span className="ml-1 text-sm text-muted-foreground">/month</span>
525
540
  )}
526
541
  </div>
527
- {isCurrentPlan && (
542
+ {isCurrentPlan ? (
528
543
  <Badge variant="secondary" className="mt-2">Current Plan</Badge>
544
+ ) : (
545
+ <div className="mt-2 h-6"></div>
529
546
  )}
530
547
  </div>
531
- <div className="space-y-2">
548
+ <div className="space-y-3 flex-1 flex flex-col justify-between min-h-[300px]">
532
549
  {features.map((feature: any) => (
533
- <div key={feature.id} className="border-t pt-2">
550
+ <div key={feature.id} className="border-t pt-3 first:border-t-0 first:pt-0 flex-1 flex flex-col justify-center">
534
551
  <div className="text-lg font-semibold text-foreground">
535
- {formatFeatureValue(feature.value)} {feature.units}
552
+ {formatFeatureValue(feature.value)} <span className="text-sm text-gray-500 font-normal">{feature.units}</span>
536
553
  </div>
537
554
  <TooltipProvider>
538
555
  <Tooltip>
539
556
  <TooltipTrigger asChild>
540
- <div className="text-xs text-muted-foreground mt-1 cursor-help">
557
+ <div className="text-xs text-gray-500 mt-1 cursor-help">
541
558
  {feature.name || feature.description}
542
559
  </div>
543
560
  </TooltipTrigger>
@@ -644,6 +661,26 @@ export default function BillingClient({ customerData, pricingData, paymentMethod
644
661
  onSuccess={handleSubscriptionCreated}
645
662
  createSubscription={handleCreateSubscription}
646
663
  />
664
+
665
+ <Dialog open={deletePaymentModalOpen} onOpenChange={setDeletePaymentModalOpen}>
666
+ <DialogContent>
667
+ <DialogHeader>
668
+ <DialogTitle>Delete Payment Method</DialogTitle>
669
+ <DialogDescription>
670
+ Are you sure you want to delete this payment method? This action cannot be undone.
671
+ </DialogDescription>
672
+ </DialogHeader>
673
+ <div className="flex justify-end space-x-2 pt-4">
674
+ <Button variant="outline" onClick={() => setDeletePaymentModalOpen(false)}>
675
+ Cancel
676
+ </Button>
677
+ <Button variant="destructive" onClick={confirmDeletePayment} disabled={isLoading}>
678
+ {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
679
+ Delete
680
+ </Button>
681
+ </div>
682
+ </DialogContent>
683
+ </Dialog>
647
684
  </>
648
685
  );
649
686
  }
@@ -47,11 +47,17 @@ export default function SubscriptionConfirmModal({
47
47
  };
48
48
 
49
49
  const formatFeatureValue = (value: any) => {
50
- if (typeof value === 'boolean') {
51
- return value ? '' : '✗'
50
+ // Handle string boolean values
51
+ if (value === 'true' || value === true) {
52
+ return '✓'
52
53
  }
53
- if (typeof value === 'number' && value >= 1000) {
54
- return new Intl.NumberFormat('en-US').format(value)
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)
55
61
  }
56
62
  return value
57
63
  };
@@ -135,7 +141,7 @@ export default function SubscriptionConfirmModal({
135
141
  <Tooltip>
136
142
  <TooltipTrigger asChild>
137
143
  <span className="cursor-help">
138
- {formatFeatureValue(feature.value)} {feature.units} - {feature.name || feature.description}
144
+ {formatFeatureValue(feature.value)} <span className="text-sm text-muted-foreground font-normal">{feature.units}</span> - {feature.name || feature.description}
139
145
  </span>
140
146
  </TooltipTrigger>
141
147
  <TooltipContent>
package/styles.css ADDED
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;