myoperator-ui 0.0.165 → 0.0.167-beta.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.
Files changed (2) hide show
  1. package/dist/index.js +1471 -129
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8566,158 +8566,1380 @@ import {
8566
8566
  DialogTitle,
8567
8567
  } from "../dialog";
8568
8568
  import { PaymentOptionCard } from "./payment-option-card";
8569
- import type { PaymentOptionCardModalProps } from "./types";
8569
+ import type { PaymentOptionCardProps } from "./types";
8570
+
8571
+ interface PaymentOptionCardModalProps
8572
+ extends Omit<PaymentOptionCardProps, "onClose"> {
8573
+ open: boolean;
8574
+ onOpenChange: (open: boolean) => void;
8575
+ }
8576
+
8577
+ /**
8578
+ * PaymentOptionCardModal wraps the PaymentOptionCard in a centered Dialog overlay.
8579
+ * Use this when you want to show payment method selection as a modal popup rather
8580
+ * than an inline card.
8581
+ *
8582
+ * @example
8583
+ * \`\`\`tsx
8584
+ * const [open, setOpen] = useState(false);
8585
+ *
8586
+ * <PaymentOptionCardModal
8587
+ * open={open}
8588
+ * onOpenChange={setOpen}
8589
+ * options={paymentOptions}
8590
+ * onOptionSelect={(id) => console.log(id)}
8591
+ * onCtaClick={() => { handlePayment(); setOpen(false); }}
8592
+ * />
8593
+ * \`\`\`
8594
+ */
8595
+ export const PaymentOptionCardModal = React.forwardRef<
8596
+ HTMLDivElement,
8597
+ PaymentOptionCardModalProps
8598
+ >(
8599
+ (
8600
+ {
8601
+ open,
8602
+ onOpenChange,
8603
+ title,
8604
+ subtitle,
8605
+ options,
8606
+ selectedOptionId,
8607
+ defaultSelectedOptionId,
8608
+ onOptionSelect,
8609
+ ctaText,
8610
+ onCtaClick,
8611
+ loading,
8612
+ disabled,
8613
+ className,
8614
+ },
8615
+ ref
8616
+ ) => {
8617
+ const handleClose = () => {
8618
+ onOpenChange(false);
8619
+ };
8620
+
8621
+ return (
8622
+ <Dialog open={open} onOpenChange={onOpenChange}>
8623
+ <DialogContent
8624
+ ref={ref}
8625
+ size="sm"
8626
+ hideCloseButton
8627
+ className="p-0 border-0 bg-transparent shadow-none"
8628
+ >
8629
+ {/* Visually hidden title for accessibility */}
8630
+ <DialogTitle className="sr-only">
8631
+ {title || "Select payment method"}
8632
+ </DialogTitle>
8633
+ <PaymentOptionCard
8634
+ title={title}
8635
+ subtitle={subtitle}
8636
+ options={options}
8637
+ selectedOptionId={selectedOptionId}
8638
+ defaultSelectedOptionId={defaultSelectedOptionId}
8639
+ onOptionSelect={onOptionSelect}
8640
+ ctaText={ctaText}
8641
+ onCtaClick={onCtaClick}
8642
+ onClose={handleClose}
8643
+ loading={loading}
8644
+ disabled={disabled}
8645
+ className={className}
8646
+ />
8647
+ </DialogContent>
8648
+ </Dialog>
8649
+ );
8650
+ }
8651
+ );
8652
+
8653
+ PaymentOptionCardModal.displayName = "PaymentOptionCardModal";
8654
+ `, prefix)
8655
+ },
8656
+ {
8657
+ name: "types.ts",
8658
+ content: prefixTailwindClasses(`import * as React from "react";
8659
+
8660
+ /**
8661
+ * A single payment option entry.
8662
+ */
8663
+ export interface PaymentOption {
8664
+ /** Unique identifier for this option */
8665
+ id: string;
8666
+ /** Icon rendered inside a rounded container (e.g. an SVG or Lucide icon) */
8667
+ icon: React.ReactNode;
8668
+ /** Primary label (e.g. "Net banking") */
8669
+ title: string;
8670
+ /** Secondary description (e.g. "Pay securely through your bank") */
8671
+ description: string;
8672
+ }
8673
+
8674
+ /**
8675
+ * Props for the PaymentOptionCard component.
8676
+ */
8677
+ export interface PaymentOptionCardProps {
8678
+ /** Header title */
8679
+ title?: string;
8680
+ /** Header subtitle */
8681
+ subtitle?: string;
8682
+ /** List of selectable payment options */
8683
+ options: PaymentOption[];
8684
+ /** Currently selected option id */
8685
+ selectedOptionId?: string;
8686
+ /** Default selected option id (uncontrolled mode) */
8687
+ defaultSelectedOptionId?: string;
8688
+ /** Callback fired when an option is selected */
8689
+ onOptionSelect?: (optionId: string) => void;
8690
+ /** CTA button text */
8691
+ ctaText?: string;
8692
+ /** Callback fired when CTA button is clicked */
8693
+ onCtaClick?: () => void;
8694
+ /** Callback fired when close button is clicked */
8695
+ onClose?: () => void;
8696
+ /** Whether the CTA button shows loading state */
8697
+ loading?: boolean;
8698
+ /** Whether the CTA button is disabled */
8699
+ disabled?: boolean;
8700
+ /** Additional className for the root element */
8701
+ className?: string;
8702
+ }
8703
+ `, prefix)
8704
+ },
8705
+ {
8706
+ name: "index.ts",
8707
+ content: prefixTailwindClasses(`export { PaymentOptionCard } from "./payment-option-card";
8708
+ export { PaymentOptionCardModal } from "./payment-option-card-modal";
8709
+ export type { PaymentOptionCardProps, PaymentOption } from "./types";
8710
+ `, prefix)
8711
+ }
8712
+ ]
8713
+ },
8714
+ "let-us-drive-card": {
8715
+ name: "let-us-drive-card",
8716
+ description: "A managed service card with pricing, billing badge, 'Show details' link, and CTA for the full-service management section",
8717
+ category: "custom",
8718
+ dependencies: [
8719
+ "clsx",
8720
+ "tailwind-merge"
8721
+ ],
8722
+ internalDependencies: [
8723
+ "button",
8724
+ "badge"
8725
+ ],
8726
+ isMultiFile: true,
8727
+ directory: "let-us-drive-card",
8728
+ mainFile: "let-us-drive-card.tsx",
8729
+ files: [
8730
+ {
8731
+ name: "let-us-drive-card.tsx",
8732
+ content: prefixTailwindClasses(`import * as React from "react";
8733
+ import { cn } from "../../../lib/utils";
8734
+ import { Button } from "../button";
8735
+ import { Badge } from "../badge";
8736
+ import type { LetUsDriveCardProps } from "./types";
8737
+
8738
+ /**
8739
+ * LetUsDriveCard displays a managed service offering with pricing, billing
8740
+ * frequency badge, and a CTA. Used in the "Let us drive \u2014 Full-service
8741
+ * management" section of the pricing page.
8742
+ *
8743
+ * Supports a "free/discount" state where the original price is shown with
8744
+ * strikethrough and a green label (e.g., "FREE") replaces it.
8745
+ *
8746
+ * @example
8747
+ * \`\`\`tsx
8748
+ * <LetUsDriveCard
8749
+ * title="Account Manager"
8750
+ * price="15,000"
8751
+ * period="/month"
8752
+ * billingBadge="Annually"
8753
+ * description="One expert who knows your business. And moves it forward."
8754
+ * onShowDetails={() => console.log("details")}
8755
+ * onCtaClick={() => console.log("talk")}
8756
+ * />
8757
+ * \`\`\`
8758
+ */
8759
+ const LetUsDriveCard = React.forwardRef<HTMLDivElement, LetUsDriveCardProps>(
8760
+ (
8761
+ {
8762
+ title,
8763
+ price,
8764
+ period,
8765
+ startsAt = false,
8766
+ billingBadge,
8767
+ description,
8768
+ freeLabel,
8769
+ showDetailsLabel = "Show details",
8770
+ ctaLabel = "Talk to us",
8771
+ onShowDetails,
8772
+ onCtaClick,
8773
+ className,
8774
+ ...props
8775
+ },
8776
+ ref
8777
+ ) => {
8778
+ return (
8779
+ <div
8780
+ ref={ref}
8781
+ className={cn(
8782
+ "flex flex-col gap-6 rounded-[14px] border border-semantic-border-layout bg-card p-5",
8783
+ className
8784
+ )}
8785
+ {...props}
8786
+ >
8787
+ {/* Header: title + optional billing badge */}
8788
+ <div className="flex items-center justify-between">
8789
+ <h3 className="text-base font-semibold text-semantic-text-primary m-0">
8790
+ {title}
8791
+ </h3>
8792
+ {billingBadge && (
8793
+ <Badge
8794
+ size="sm"
8795
+ className="bg-semantic-info-surface text-semantic-info-primary font-normal"
8796
+ >
8797
+ {billingBadge}
8798
+ </Badge>
8799
+ )}
8800
+ </div>
8801
+
8802
+ {/* Price section */}
8803
+ <div className="flex flex-col gap-2.5">
8804
+ {startsAt && (
8805
+ <span className="text-xs text-semantic-text-muted tracking-[0.048px]">
8806
+ Starts at
8807
+ </span>
8808
+ )}
8809
+ <div className="flex gap-1 items-end">
8810
+ {freeLabel ? (
8811
+ <span className="text-[28px] font-semibold leading-[36px]">
8812
+ <span className="line-through text-semantic-text-muted">
8813
+ \u20B9{price}
8814
+ </span>{" "}
8815
+ <span className="text-semantic-success-primary">
8816
+ {freeLabel}
8817
+ </span>
8818
+ </span>
8819
+ ) : (
8820
+ <span className="text-[28px] font-semibold leading-[36px] text-semantic-text-primary">
8821
+ \u20B9{price}
8822
+ </span>
8823
+ )}
8824
+ {period && (
8825
+ <span className="text-sm text-semantic-text-muted tracking-[0.035px]">
8826
+ {period}
8827
+ </span>
8828
+ )}
8829
+ </div>
8830
+
8831
+ {/* Description */}
8832
+ <p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0">
8833
+ {description}
8834
+ </p>
8835
+ </div>
8836
+
8837
+ {/* Actions: Show details link + CTA button */}
8838
+ <div className="flex flex-col gap-3 w-full">
8839
+ {onShowDetails && (
8840
+ <Button
8841
+ variant="link"
8842
+ className="text-semantic-text-link p-0 h-auto min-w-0 justify-start"
8843
+ onClick={onShowDetails}
8844
+ >
8845
+ {showDetailsLabel}
8846
+ </Button>
8847
+ )}
8848
+ <Button variant="outline" className="w-full" onClick={onCtaClick}>
8849
+ {ctaLabel}
8850
+ </Button>
8851
+ </div>
8852
+ </div>
8853
+ );
8854
+ }
8855
+ );
8856
+
8857
+ LetUsDriveCard.displayName = "LetUsDriveCard";
8858
+
8859
+ export { LetUsDriveCard };
8860
+ `, prefix)
8861
+ },
8862
+ {
8863
+ name: "types.ts",
8864
+ content: prefixTailwindClasses(`import * as React from "react";
8865
+
8866
+ /**
8867
+ * Props for the LetUsDriveCard component.
8868
+ */
8869
+ export interface LetUsDriveCardProps
8870
+ extends React.HTMLAttributes<HTMLDivElement> {
8871
+ /** Service title (e.g., "Dedicated Onboarding", "Account Manager") */
8872
+ title: string;
8873
+ /** Price amount as formatted string (e.g., "20,000", "15,000") */
8874
+ price: string;
8875
+ /** Billing period label (e.g., "/one-time fee", "/month") */
8876
+ period?: string;
8877
+ /** Show "Starts at" prefix above the price */
8878
+ startsAt?: boolean;
8879
+ /** Billing frequency badge text (e.g., "Annually", "Quarterly") */
8880
+ billingBadge?: string;
8881
+ /** Service description text */
8882
+ description: string;
8883
+ /** When provided, price is shown with strikethrough and this label (e.g., "FREE") is displayed in green */
8884
+ freeLabel?: string;
8885
+ /** Text for the details link (default: "Show details") */
8886
+ showDetailsLabel?: string;
8887
+ /** CTA button text (default: "Talk to us") */
8888
+ ctaLabel?: string;
8889
+ /** Callback when "Show details" link is clicked */
8890
+ onShowDetails?: () => void;
8891
+ /** Callback when CTA button is clicked */
8892
+ onCtaClick?: () => void;
8893
+ }
8894
+ `, prefix)
8895
+ },
8896
+ {
8897
+ name: "index.ts",
8898
+ content: prefixTailwindClasses(`export { LetUsDriveCard } from "./let-us-drive-card";
8899
+ export type { LetUsDriveCardProps } from "./types";
8900
+ `, prefix)
8901
+ }
8902
+ ]
8903
+ },
8904
+ "power-up-card": {
8905
+ name: "power-up-card",
8906
+ description: "An add-on service card with icon, title, pricing, description, and CTA button for the power-ups section",
8907
+ category: "custom",
8908
+ dependencies: [
8909
+ "clsx",
8910
+ "tailwind-merge"
8911
+ ],
8912
+ internalDependencies: [
8913
+ "button"
8914
+ ],
8915
+ isMultiFile: true,
8916
+ directory: "power-up-card",
8917
+ mainFile: "power-up-card.tsx",
8918
+ files: [
8919
+ {
8920
+ name: "power-up-card.tsx",
8921
+ content: prefixTailwindClasses(`import * as React from "react";
8922
+ import { cn } from "../../../lib/utils";
8923
+ import { Button } from "../button";
8924
+ import type { PowerUpCardProps } from "./types";
8925
+
8926
+ /**
8927
+ * PowerUpCard displays an add-on service with icon, pricing, description,
8928
+ * and a CTA button. Used in the "Power-ups and charges" section of
8929
+ * the pricing page.
8930
+ *
8931
+ * @example
8932
+ * \`\`\`tsx
8933
+ * <PowerUpCard
8934
+ * icon={<PhoneCall className="size-6" />}
8935
+ * title="Auto-Dialer"
8936
+ * price="Starts @ \u20B9700/user/month"
8937
+ * description="Available for SUV & Enterprise plans as an add-on per user."
8938
+ * onCtaClick={() => console.log("clicked")}
8939
+ * />
8940
+ * \`\`\`
8941
+ */
8942
+ const PowerUpCard = React.forwardRef<HTMLDivElement, PowerUpCardProps>(
8943
+ (
8944
+ {
8945
+ icon,
8946
+ title,
8947
+ price,
8948
+ description,
8949
+ ctaLabel = "Talk to us",
8950
+ onCtaClick,
8951
+ className,
8952
+ ...props
8953
+ },
8954
+ ref
8955
+ ) => {
8956
+ return (
8957
+ <div
8958
+ ref={ref}
8959
+ className={cn(
8960
+ "flex flex-col justify-between gap-8 rounded-md border border-semantic-border-layout bg-card p-5",
8961
+ className
8962
+ )}
8963
+ {...props}
8964
+ >
8965
+ {/* Content */}
8966
+ <div className="flex flex-col gap-4">
8967
+ {/* Icon + title/price row */}
8968
+ <div className="flex gap-4 items-start">
8969
+ {icon && (
8970
+ <div className="flex items-center justify-center size-[47px] rounded bg-[var(--color-info-25)] shrink-0">
8971
+ {icon}
8972
+ </div>
8973
+ )}
8974
+ <div className="flex flex-col gap-2 min-w-0">
8975
+ <h3 className="text-base font-semibold text-semantic-text-primary m-0 leading-normal">
8976
+ {title}
8977
+ </h3>
8978
+ <p className="text-sm text-semantic-text-primary tracking-[0.035px] m-0 leading-normal">
8979
+ {price}
8980
+ </p>
8981
+ </div>
8982
+ </div>
8983
+
8984
+ {/* Description */}
8985
+ <p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0 leading-normal">
8986
+ {description}
8987
+ </p>
8988
+ </div>
8989
+
8990
+ {/* CTA */}
8991
+ <Button variant="outline" className="w-full" onClick={onCtaClick}>
8992
+ {ctaLabel}
8993
+ </Button>
8994
+ </div>
8995
+ );
8996
+ }
8997
+ );
8998
+
8999
+ PowerUpCard.displayName = "PowerUpCard";
9000
+
9001
+ export { PowerUpCard };
9002
+ `, prefix)
9003
+ },
9004
+ {
9005
+ name: "types.ts",
9006
+ content: prefixTailwindClasses(`import * as React from "react";
9007
+
9008
+ /**
9009
+ * Props for the PowerUpCard component.
9010
+ */
9011
+ export interface PowerUpCardProps
9012
+ extends React.HTMLAttributes<HTMLDivElement> {
9013
+ /** Icon or illustration displayed in the tinted container */
9014
+ icon?: React.ReactNode;
9015
+ /** Service title (e.g., "Truecaller business") */
9016
+ title: string;
9017
+ /** Pricing text (e.g., "Starts @ \u20B930,000/month") */
9018
+ price: string;
9019
+ /** Description explaining the service value */
9020
+ description: string;
9021
+ /** CTA button label (default: "Talk to us") */
9022
+ ctaLabel?: string;
9023
+ /** Callback when CTA button is clicked */
9024
+ onCtaClick?: () => void;
9025
+ }
9026
+ `, prefix)
9027
+ },
9028
+ {
9029
+ name: "index.ts",
9030
+ content: prefixTailwindClasses(`export { PowerUpCard } from "./power-up-card";
9031
+ export type { PowerUpCardProps } from "./types";
9032
+ `, prefix)
9033
+ }
9034
+ ]
9035
+ },
9036
+ "pricing-card": {
9037
+ name: "pricing-card",
9038
+ description: "A pricing tier card with plan name, pricing, feature checklist, CTA button, and optional popularity badge and addon footer",
9039
+ category: "custom",
9040
+ dependencies: [
9041
+ "clsx",
9042
+ "tailwind-merge",
9043
+ "lucide-react"
9044
+ ],
9045
+ internalDependencies: [
9046
+ "button",
9047
+ "badge"
9048
+ ],
9049
+ isMultiFile: true,
9050
+ directory: "pricing-card",
9051
+ mainFile: "pricing-card.tsx",
9052
+ files: [
9053
+ {
9054
+ name: "pricing-card.tsx",
9055
+ content: prefixTailwindClasses(`import * as React from "react";
9056
+ import { cn } from "../../../lib/utils";
9057
+ import { Button } from "../button";
9058
+ import { Badge } from "../badge";
9059
+ import { CircleCheck } from "lucide-react";
9060
+ import type { PricingCardProps } from "./types";
9061
+
9062
+ /**
9063
+ * PricingCard displays a plan tier with pricing, features, and a CTA button.
9064
+ * Supports current-plan state (outlined button), popularity badge, and an
9065
+ * optional add-on footer.
9066
+ *
9067
+ * @example
9068
+ * \`\`\`tsx
9069
+ * <PricingCard
9070
+ * planName="Compact"
9071
+ * price="2,5000"
9072
+ * planDetails="3 Users | 12 Month plan"
9073
+ * description="For small teams that need a WhatsApp-first plan"
9074
+ * headerBgColor="#d7eae9"
9075
+ * features={["WhatsApp Campaigns", "Missed Call Tracking"]}
9076
+ * onCtaClick={() => console.log("selected")}
9077
+ * onFeatureDetails={() => console.log("details")}
9078
+ * />
9079
+ * \`\`\`
9080
+ */
9081
+ const PricingCard = React.forwardRef<HTMLDivElement, PricingCardProps>(
9082
+ (
9083
+ {
9084
+ planName,
9085
+ price,
9086
+ period = "/Month",
9087
+ planDetails,
9088
+ planIcon,
9089
+ description,
9090
+ headerBgColor,
9091
+ features = [],
9092
+ isCurrentPlan = false,
9093
+ showPopularBadge = false,
9094
+ badgeText = "MOST POPULAR",
9095
+ ctaText,
9096
+ onCtaClick,
9097
+ onFeatureDetails,
9098
+ addon,
9099
+ usageDetails,
9100
+ className,
9101
+ ...props
9102
+ },
9103
+ ref
9104
+ ) => {
9105
+ const buttonText =
9106
+ ctaText || (isCurrentPlan ? "Current plan" : "Select plan");
9107
+
9108
+ return (
9109
+ <div
9110
+ ref={ref}
9111
+ className={cn(
9112
+ "flex flex-col gap-6 rounded-t-xl rounded-b-lg border border-semantic-border-layout p-4",
9113
+ className
9114
+ )}
9115
+ {...props}
9116
+ >
9117
+ {/* Header */}
9118
+ <div
9119
+ className="flex flex-col gap-4 rounded-t-xl rounded-b-lg p-4"
9120
+ style={
9121
+ headerBgColor ? { backgroundColor: headerBgColor } : undefined
9122
+ }
9123
+ >
9124
+ {/* Plan name + badge */}
9125
+ <div className="flex items-center gap-4">
9126
+ <h3 className="text-xl font-semibold text-semantic-text-primary m-0">
9127
+ {planName}
9128
+ </h3>
9129
+ {showPopularBadge && (
9130
+ <Badge
9131
+ size="sm"
9132
+ className="bg-[#e3fdfe] text-[#119ba8] uppercase tracking-wider font-semibold"
9133
+ >
9134
+ {badgeText}
9135
+ </Badge>
9136
+ )}
9137
+ </div>
9138
+
9139
+ {/* Price */}
9140
+ <div className="flex flex-col gap-2.5">
9141
+ <div className="flex items-end gap-1">
9142
+ <span className="text-4xl leading-[44px] text-semantic-text-primary">
9143
+ \u20B9{price}
9144
+ </span>
9145
+ <span className="text-sm text-semantic-text-muted tracking-[0.035px]">
9146
+ {period}
9147
+ </span>
9148
+ </div>
9149
+ {planDetails && (
9150
+ <p className="text-sm tracking-[0.035px] text-semantic-text-primary m-0">
9151
+ {planDetails}
9152
+ </p>
9153
+ )}
9154
+ </div>
9155
+
9156
+ {/* Plan icon */}
9157
+ {planIcon && <div className="size-[30px]">{planIcon}</div>}
9158
+
9159
+ {/* Description */}
9160
+ {description && (
9161
+ <p className="text-sm text-semantic-text-secondary tracking-[0.035px] m-0">
9162
+ {description}
9163
+ </p>
9164
+ )}
9165
+
9166
+ {/* Feature details link + CTA */}
9167
+ <div className="flex flex-col gap-3.5 w-full">
9168
+ {onFeatureDetails && (
9169
+ <div className="flex justify-center">
9170
+ <Button
9171
+ variant="link"
9172
+ className="text-semantic-text-link p-0 h-auto min-w-0"
9173
+ onClick={onFeatureDetails}
9174
+ >
9175
+ Feature details
9176
+ </Button>
9177
+ </div>
9178
+ )}
9179
+ <Button
9180
+ variant={isCurrentPlan ? "outline" : "default"}
9181
+ className="w-full"
9182
+ onClick={onCtaClick}
9183
+ >
9184
+ {buttonText}
9185
+ </Button>
9186
+ </div>
9187
+ </div>
9188
+
9189
+ {/* Features */}
9190
+ {features.length > 0 && (
9191
+ <div className="flex flex-col gap-4">
9192
+ <p className="text-sm font-semibold text-semantic-text-primary tracking-[0.014px] uppercase m-0">
9193
+ Includes
9194
+ </p>
9195
+ <div className="flex flex-col gap-4">
9196
+ {features.map((feature, index) => {
9197
+ const text =
9198
+ typeof feature === "string" ? feature : feature.text;
9199
+ const isBold =
9200
+ typeof feature !== "string" && feature.bold;
9201
+ return (
9202
+ <div key={index} className="flex items-start gap-2">
9203
+ <CircleCheck className="size-[18px] text-semantic-text-secondary shrink-0 mt-0.5" />
9204
+ <span
9205
+ className={cn(
9206
+ "text-sm text-semantic-text-secondary tracking-[0.035px]",
9207
+ isBold && "font-semibold"
9208
+ )}
9209
+ >
9210
+ {text}
9211
+ </span>
9212
+ </div>
9213
+ );
9214
+ })}
9215
+ </div>
9216
+ </div>
9217
+ )}
9218
+
9219
+ {/* Addon */}
9220
+ {addon && (
9221
+ <div className="flex items-center gap-2.5 rounded-md bg-[var(--color-info-25)] border border-[#f3f5f6] pl-4 py-2.5">
9222
+ {addon.icon && (
9223
+ <div className="size-5 shrink-0">{addon.icon}</div>
9224
+ )}
9225
+ <span className="text-sm text-semantic-text-primary tracking-[0.035px]">
9226
+ {addon.text}
9227
+ </span>
9228
+ </div>
9229
+ )}
9230
+
9231
+ {/* Usage Details */}
9232
+ {usageDetails && usageDetails.length > 0 && (
9233
+ <div className="flex flex-col gap-2.5 rounded-md bg-[var(--color-info-25)] border border-[#f3f5f6] px-4 py-2.5">
9234
+ {usageDetails.map((detail, index) => (
9235
+ <div key={index} className="flex items-start gap-2">
9236
+ <span className="size-1.5 rounded-full bg-semantic-primary shrink-0 mt-[7px]" />
9237
+ <span className="text-sm text-semantic-text-primary tracking-[0.035px]">
9238
+ <strong>{detail.label}:</strong> {detail.value}
9239
+ </span>
9240
+ </div>
9241
+ ))}
9242
+ </div>
9243
+ )}
9244
+ </div>
9245
+ );
9246
+ }
9247
+ );
9248
+
9249
+ PricingCard.displayName = "PricingCard";
9250
+
9251
+ export { PricingCard };
9252
+ `, prefix)
9253
+ },
9254
+ {
9255
+ name: "plan-icons.tsx",
9256
+ content: prefixTailwindClasses(`import * as React from "react";
9257
+
9258
+ interface PlanIconProps extends React.SVGAttributes<SVGElement> {
9259
+ className?: string;
9260
+ }
9261
+
9262
+ const CompactCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
9263
+ ({ className, ...props }, ref) => (
9264
+ <svg
9265
+ ref={ref}
9266
+ className={className}
9267
+ viewBox="0 0 30 19"
9268
+ fill="none"
9269
+ xmlns="http://www.w3.org/2000/svg"
9270
+ {...props}
9271
+ >
9272
+ <ellipse cx="25.2" cy="14.72" rx="3.33" ry="3.03" fill="white" />
9273
+ <path
9274
+ d="M25.12 11.21c-1.95 0-3.5 1.56-3.5 3.5 0 1.95 1.55 3.5 3.5 3.5 1.94 0 3.5-1.55 3.5-3.5 0-1.94-1.56-3.5-3.5-3.5zm0 5.45c-1.09 0-2.02-.93-2.02-2.02s.93-2.02 2.02-2.02 2.02.93 2.02 2.02-.93 2.02-2.02 2.02z"
9275
+ stroke="#2BBAC8"
9276
+ strokeLinejoin="round"
9277
+ />
9278
+ <ellipse cx="4.09" cy="14.72" rx="3.33" ry="3.03" fill="white" />
9279
+ <path
9280
+ d="M4.26 11.21c-1.95 0-3.5 1.56-3.5 3.5 0 1.95 1.55 3.5 3.5 3.5 1.94 0 3.5-1.55 3.5-3.5 0-1.94-1.56-3.5-3.5-3.5zm0 5.45c-1.09 0-2.02-.93-2.02-2.02s.93-2.02 2.02-2.02 2.02.93 2.02 2.02-.93 2.02-2.02 2.02z"
9281
+ stroke="#2BBAC8"
9282
+ strokeLinejoin="round"
9283
+ />
9284
+ <path
9285
+ d="M28.85 12.38c-.08-.16-.31-.31-.39-.47-.16-.39-.16-1.09-.23-1.48-.31-1.17-1.17-2.02-2.02-2.72-1.64-1.25-3.66-2.57-5.45-3.74C18 2.11 15.85.78 12.35.63c-1.79-.08-4.51 0-6.23.23-.15 0-1.4.31-1.24.62 1.25.23.55.93.24 1.63-.31.62-1.09 2.49-1.64 2.8-.15 0-.23.08-.31 0-.23-.31.16-1.4.31-1.71.16-.47.86-1.4.93-1.79 0-.31 0-.7-.39-.62L2.62 4.75c-.62 1.17-.62 2.18-.78 3.42-.15 1.56-1.09 2.88-1.24 4.36 0 .16 0 .39 0 .54.08.31.23.31.47.08.54-1.17 1.71-2.02 3.11-2.02 1.4 0 3.5 1.56 3.5 3.5s-.08 1.86-.23 2.25l.85-.08h11.75c.78-.08 1.4-.47 1.56-1.24 0-1.79 1.56-3.35 3.5-3.35 1.95 0 3.5 1.56 3.5 3.5 0 1.95-.16.93 0 1.09 1.09-.54.86-2.57.23-3.35v-.08z"
9286
+ fill="white"
9287
+ stroke="currentColor"
9288
+ strokeWidth="1.2"
9289
+ strokeLinejoin="round"
9290
+ />
9291
+ <path
9292
+ d="M10.02 1.41c3.81-.23 8.56 1.4 11.44 3.89 2.88 2.49 1.79 1.64.16 1.79-3.58-.31-7.16-.62-10.74-.93-.86 0-2.65 0-3.27-.47-.62-.47-.54-1.87-.23-2.72.54-1.25 1.4-1.48 2.64-1.56z"
9293
+ stroke="currentColor"
9294
+ strokeWidth="1.2"
9295
+ strokeLinejoin="round"
9296
+ />
9297
+ </svg>
9298
+ )
9299
+ );
9300
+ CompactCarIcon.displayName = "CompactCarIcon";
9301
+
9302
+ const SedanCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
9303
+ ({ className, ...props }, ref) => (
9304
+ <svg
9305
+ ref={ref}
9306
+ className={className}
9307
+ viewBox="0 0 31 13"
9308
+ fill="none"
9309
+ xmlns="http://www.w3.org/2000/svg"
9310
+ {...props}
9311
+ >
9312
+ <ellipse cx="24.98" cy="9.51" rx="2.19" ry="2.56" fill="white" />
9313
+ <path
9314
+ d="M24.8 7.16c-1.33 0-2.38 1.09-2.38 2.45 0 1.37 1.05 2.46 2.38 2.46 1.33 0 2.38-1.09 2.38-2.46 0-1.36-1.05-2.45-2.38-2.45zm0 3.82c-.74 0-1.38-.66-1.38-1.42 0-.76.64-1.42 1.38-1.42.74 0 1.38.66 1.38 1.42 0 .76-.64 1.42-1.38 1.42z"
9315
+ stroke="#2BBAC8"
9316
+ strokeLinejoin="round"
9317
+ />
9318
+ <ellipse cx="6.33" cy="9.51" rx="2.19" ry="2.56" fill="white" />
9319
+ <path
9320
+ d="M6.32 7.16c-1.32 0-2.38 1.09-2.38 2.45 0 1.37 1.06 2.46 2.38 2.46 1.33 0 2.39-1.09 2.39-2.46 0-1.36-1.06-2.45-2.39-2.45zm0 3.82c-.74 0-1.38-.66-1.38-1.42 0-.76.64-1.42 1.38-1.42.74 0 1.38.66 1.38 1.42 0 .76-.64 1.42-1.38 1.42z"
9321
+ stroke="#2BBAC8"
9322
+ strokeLinejoin="round"
9323
+ />
9324
+ <path
9325
+ d="M29.7 7.79l-.24.81c0 .08-.16.4-.23.49-.24.16-1.97.57-2.05.49.24-1.22-.47-2.6-1.57-3 -2.05-.81-4.09 1.05-3.54 3.24H8.99v-.81c0-.32-.39-1.05-.55-1.3C7.03 5.6 4.27 6.33 3.8 8.6c-.47 2.27 0 .49 0 .49l-2.28-.41C.81 8.27.49 7.14.73 6.33c.23-.81.39-.57.39-.73.08-.49-.16-1.62.16-2.03.31-.4 1.97-.4 2.44-.57 1.42-.4 2.76-1.22 4.17-1.62 2.91-.89 6.61-1.05 9.53 0 2.91 1.05 3.7 1.95 5.51 2.51 1.81.57 4.09.65 5.83 1.62 1.73.97.62 1.05.93 1.78v.57z"
9326
+ fill="white"
9327
+ stroke="currentColor"
9328
+ strokeWidth="1.3"
9329
+ strokeLinejoin="round"
9330
+ />
9331
+ <path
9332
+ d="M13.48 1.38l.63 2.6 4.8.16c0-.32 0-.64.32-.89-1.58-1.38-3.78-1.78-5.83-1.87h.08z"
9333
+ stroke="currentColor"
9334
+ strokeWidth="1.3"
9335
+ strokeLinejoin="round"
9336
+ />
9337
+ <path
9338
+ d="M8.99 1.87s-.63.97-.63 1.05c0 .16.16.65.24.81l4.41.16-.39-2.51c-.87 0-1.81 0-2.68.16-.87.16-.87.16-.95.24v.09z"
9339
+ stroke="currentColor"
9340
+ strokeWidth="1.3"
9341
+ strokeLinejoin="round"
9342
+ />
9343
+ <path
9344
+ d="M6.08 3.81h1.18l1.26-1.78c-.47.32-2.2.81-2.36 1.3-.16.49 0 .32 0 .49h-.08z"
9345
+ stroke="currentColor"
9346
+ strokeWidth="1.3"
9347
+ strokeLinejoin="round"
9348
+ />
9349
+ </svg>
9350
+ )
9351
+ );
9352
+ SedanCarIcon.displayName = "SedanCarIcon";
9353
+
9354
+ const SuvCarIcon = React.forwardRef<SVGSVGElement, PlanIconProps>(
9355
+ ({ className, ...props }, ref) => (
9356
+ <svg
9357
+ ref={ref}
9358
+ className={className}
9359
+ viewBox="0 0 32 15"
9360
+ fill="none"
9361
+ xmlns="http://www.w3.org/2000/svg"
9362
+ {...props}
9363
+ >
9364
+ <ellipse cx="25.57" cy="11.14" rx="2.65" ry="2.78" fill="white" />
9365
+ <ellipse cx="9.12" cy="11.14" rx="2.89" ry="2.78" fill="white" />
9366
+ <path
9367
+ d="M25.32 8.18c-1.61 0-2.9 1.3-2.9 2.94 0 1.63 1.29 2.93 2.9 2.93 1.62 0 2.9-1.3 2.9-2.93 0-1.64-1.28-2.94-2.9-2.94zm0 4.57c-.9 0-1.68-.78-1.68-1.7 0-.91.78-1.69 1.68-1.69.9 0 1.68.78 1.68 1.7 0 .91-.78 1.69-1.68 1.69z"
9368
+ stroke="#2BBAC8"
9369
+ strokeWidth="1.2"
9370
+ strokeLinejoin="round"
9371
+ />
9372
+ <ellipse cx="9.14" cy="11.09" rx="1.4" ry="1.37" fill="white" />
9373
+ <path
9374
+ d="M8.96 8.18c-1.61 0-2.9 1.3-2.9 2.94 0 1.63 1.29 2.93 2.9 2.93 1.61 0 2.9-1.3 2.9-2.93 0-1.64-1.29-2.94-2.9-2.94zm0 4.57c-.9 0-1.68-.78-1.68-1.7 0-.91.78-1.69 1.68-1.69.9 0 1.68.78 1.68 1.7 0 .91-.78 1.69-1.68 1.69z"
9375
+ stroke="#2BBAC8"
9376
+ strokeWidth="1.2"
9377
+ strokeLinejoin="round"
9378
+ />
9379
+ <path
9380
+ d="M30.6 10.78l-.26.99c-.3 1-.36.56-1.09.64.48-3.15-2.66-5.79-5.13-3.7-.43.37-1.18 1.52-1.18 2.1v1.5H12.06c.33-2.62-1.84-5.01-4.24-4.06-1.53.61-2.13 2.39-1.98 4.06-1.61-.14-3.18.68-3.39-1.7-.05-.6.07-1.21-.04-1.8-.65-.34-1.63.37-1.75-.77C.57 7.13.59 4.97.67 4.03c.03-.33.06-.79.43-.87.28-.06 1.83-.08 1.83.26v1.49l.29-.06c.67-1.75.59-3.97 2.76-4.15 3.76-.3 7.87.23 11.67.02 1.75.22 4.02 3.02 5.39 4.18 1.24.15 2.5.24 3.73.44.5.09 1.95.3 2.31.56.7.49.57 2.79.67 2.91.02.03.37.04.56.26.19.23.12.47.28.67v1.03z"
9381
+ fill="white"
9382
+ stroke="currentColor"
9383
+ strokeWidth="1.2"
9384
+ strokeLinejoin="round"
9385
+ />
9386
+ <path
9387
+ d="M14.32 1.53c.25 1.41.16 2.98.61 4.32h6.22l.1-.21c-.48-1.34-1.41-2.72-2.51-3.53-.15-.11-.86-.59-.98-.59h-3.44z"
9388
+ stroke="currentColor"
9389
+ strokeWidth="1.2"
9390
+ strokeLinejoin="round"
9391
+ />
9392
+ <path
9393
+ d="M9.71 1.53l-.19 4.32h4.33c-.17-1.3-.17-2.78-.38-4.06-.02-.12-.04-.2-.14-.26H9.71z"
9394
+ stroke="currentColor"
9395
+ strokeWidth="1.2"
9396
+ strokeLinejoin="round"
9397
+ />
9398
+ <path
9399
+ d="M8.58 5.84l.29-4.07-.09-.31c-1.1.07-2.89-.32-3.46.95-.23.51-.74 2.67-.74 3.2 0 .12.01.13.1.21h3.91v-.01z"
9400
+ stroke="currentColor"
9401
+ strokeWidth="1.2"
9402
+ strokeLinejoin="round"
9403
+ />
9404
+ </svg>
9405
+ )
9406
+ );
9407
+ SuvCarIcon.displayName = "SuvCarIcon";
9408
+
9409
+ export { CompactCarIcon, SedanCarIcon, SuvCarIcon };
9410
+ `, prefix)
9411
+ },
9412
+ {
9413
+ name: "types.ts",
9414
+ content: prefixTailwindClasses(`import * as React from "react";
9415
+
9416
+ /**
9417
+ * Add-on info displayed at the bottom of the pricing card.
9418
+ */
9419
+ export interface PricingCardAddon {
9420
+ /** Icon rendered in the addon section */
9421
+ icon?: React.ReactNode;
9422
+ /** Addon description text */
9423
+ text: string;
9424
+ }
9425
+
9426
+ /**
9427
+ * A single usage detail item (e.g., "Usage: Includes 2,000 AI conversations/month").
9428
+ */
9429
+ export interface UsageDetail {
9430
+ /** Bold label (e.g., "Usage") */
9431
+ label: string;
9432
+ /** Value text (e.g., "Includes 2,000 AI conversations/month") */
9433
+ value: string;
9434
+ }
9435
+
9436
+ /**
9437
+ * A feature can be a plain string or an object with bold styling.
9438
+ */
9439
+ export type PricingCardFeature = string | { text: string; bold?: boolean };
9440
+
9441
+ /**
9442
+ * Props for the PricingCard component.
9443
+ */
9444
+ export interface PricingCardProps
9445
+ extends React.HTMLAttributes<HTMLDivElement> {
9446
+ /** Plan name displayed in the header (e.g., "Compact", "Sedan", "SUV") */
9447
+ planName: string;
9448
+ /** Price amount as formatted string (e.g., "2,5000") */
9449
+ price: string;
9450
+ /** Billing period label (default: "/Month") */
9451
+ period?: string;
9452
+ /** Plan detail line (e.g., "3 Users | 12 Month plan") */
9453
+ planDetails?: React.ReactNode;
9454
+ /** Plan icon or illustration */
9455
+ planIcon?: React.ReactNode;
9456
+ /** Plan description text */
9457
+ description?: string;
9458
+ /** Background color for the header section */
9459
+ headerBgColor?: string;
9460
+ /** List of included features shown with checkmarks. Supports bold items via object form. */
9461
+ features?: PricingCardFeature[];
9462
+ /** Whether this is the currently active plan (shows outlined button) */
9463
+ isCurrentPlan?: boolean;
9464
+ /** Show a popularity badge next to the plan name */
9465
+ showPopularBadge?: boolean;
9466
+ /** Custom badge text (defaults to "MOST POPULAR") */
9467
+ badgeText?: string;
9468
+ /** Custom CTA button text (overrides default "Select plan" / "Current plan") */
9469
+ ctaText?: string;
9470
+ /** Callback when CTA button is clicked */
9471
+ onCtaClick?: () => void;
9472
+ /** Callback when "Feature details" link is clicked */
9473
+ onFeatureDetails?: () => void;
9474
+ /** Add-on info displayed at the bottom of the card */
9475
+ addon?: PricingCardAddon;
9476
+ /** Usage details displayed in a bulleted list at the bottom (e.g., AIO plan) */
9477
+ usageDetails?: UsageDetail[];
9478
+ }
9479
+ `, prefix)
9480
+ },
9481
+ {
9482
+ name: "index.ts",
9483
+ content: prefixTailwindClasses(`export { PricingCard } from "./pricing-card";
9484
+ export { CompactCarIcon, SedanCarIcon, SuvCarIcon } from "./plan-icons";
9485
+ export type {
9486
+ PricingCardProps,
9487
+ PricingCardAddon,
9488
+ PricingCardFeature,
9489
+ UsageDetail,
9490
+ } from "./types";
9491
+ `, prefix)
9492
+ }
9493
+ ]
9494
+ },
9495
+ "pricing-page": {
9496
+ name: "pricing-page",
9497
+ description: "A full pricing page layout composing plan-type tabs, billing toggle, pricing cards grid, power-ups section, and let-us-drive managed services section",
9498
+ category: "custom",
9499
+ dependencies: [
9500
+ "clsx",
9501
+ "tailwind-merge",
9502
+ "lucide-react"
9503
+ ],
9504
+ internalDependencies: [
9505
+ "button",
9506
+ "page-header",
9507
+ "pricing-toggle",
9508
+ "pricing-card",
9509
+ "power-up-card",
9510
+ "let-us-drive-card"
9511
+ ],
9512
+ isMultiFile: true,
9513
+ directory: "pricing-page",
9514
+ mainFile: "pricing-page.tsx",
9515
+ files: [
9516
+ {
9517
+ name: "pricing-page.tsx",
9518
+ content: prefixTailwindClasses(`import * as React from "react";
9519
+ import { cn } from "../../../lib/utils";
9520
+ import { PageHeader } from "../page-header";
9521
+ import { Button } from "../button";
9522
+ import { PricingToggle } from "../pricing-toggle/pricing-toggle";
9523
+ import { PricingCard } from "../pricing-card/pricing-card";
9524
+ import { PowerUpCard } from "../power-up-card/power-up-card";
9525
+ import { LetUsDriveCard } from "../let-us-drive-card/let-us-drive-card";
9526
+ import { ExternalLink } from "lucide-react";
9527
+ import type { PricingPageProps } from "./types";
8570
9528
 
8571
9529
  /**
8572
- * PaymentOptionCardModal wraps the PaymentOptionCard in a centered Dialog overlay.
8573
- * Use this when you want to show payment method selection as a modal popup rather
8574
- * than an inline card.
9530
+ * PricingPage composes all plan-selection sub-components into a full
9531
+ * page layout: header, plan-type tabs with billing toggle, pricing
9532
+ * cards grid, power-ups section, and let-us-drive section.
9533
+ *
9534
+ * Supports controlled or uncontrolled tab / billing state.
8575
9535
  *
8576
9536
  * @example
8577
9537
  * \`\`\`tsx
8578
- * const [open, setOpen] = useState(false);
8579
- *
8580
- * <PaymentOptionCardModal
8581
- * open={open}
8582
- * onOpenChange={setOpen}
8583
- * options={paymentOptions}
8584
- * onOptionSelect={(id) => console.log(id)}
8585
- * onCtaClick={() => { handlePayment(); setOpen(false); }}
9538
+ * <PricingPage
9539
+ * tabs={[
9540
+ * { label: "Team-Led Plans", value: "team" },
9541
+ * { label: "Go-AI First", value: "ai" },
9542
+ * ]}
9543
+ * planCards={compactCard, sedanCard, suvCard}
9544
+ * powerUpCards={[truecaller, tollFree, autoDialer]}
9545
+ * letUsDriveCards={[onboarding, accountMgr, managed]}
8586
9546
  * />
8587
9547
  * \`\`\`
8588
9548
  */
8589
- export const PaymentOptionCardModal = React.forwardRef<
8590
- HTMLDivElement,
8591
- PaymentOptionCardModalProps
8592
- >(
9549
+ const PricingPage = React.forwardRef<HTMLDivElement, PricingPageProps>(
8593
9550
  (
8594
9551
  {
8595
- open,
8596
- onOpenChange,
8597
- title,
8598
- subtitle,
8599
- options,
8600
- selectedOptionId,
8601
- defaultSelectedOptionId,
8602
- onOptionSelect,
8603
- ctaText,
8604
- onCtaClick,
8605
- loading,
8606
- disabled,
9552
+ title = "Select business plan",
9553
+ headerActions,
9554
+ tabs = [],
9555
+ activeTab: controlledTab,
9556
+ onTabChange,
9557
+ showBillingToggle = false,
9558
+ billingPeriod: controlledBilling,
9559
+ onBillingPeriodChange,
9560
+ planCards = [],
9561
+ powerUpCards = [],
9562
+ powerUpsTitle = "Power-ups and charges",
9563
+ featureComparisonText = "See full feature comparison",
9564
+ onFeatureComparisonClick,
9565
+ letUsDriveCards = [],
9566
+ letUsDriveTitle = "Let us drive \u2014 Full-service management",
8607
9567
  className,
9568
+ ...props
8608
9569
  },
8609
9570
  ref
8610
9571
  ) => {
8611
- const handleClose = () => {
8612
- onOpenChange(false);
9572
+ // Internal state for uncontrolled mode
9573
+ const [internalTab, setInternalTab] = React.useState(
9574
+ tabs[0]?.value ?? ""
9575
+ );
9576
+ const [internalBilling, setInternalBilling] = React.useState<
9577
+ "monthly" | "yearly"
9578
+ >("monthly");
9579
+
9580
+ const currentTab = controlledTab ?? internalTab;
9581
+ const currentBilling = controlledBilling ?? internalBilling;
9582
+
9583
+ const handleTabChange = (value: string) => {
9584
+ if (!controlledTab) setInternalTab(value);
9585
+ onTabChange?.(value);
8613
9586
  };
8614
9587
 
9588
+ const handleBillingChange = (period: "monthly" | "yearly") => {
9589
+ if (!controlledBilling) setInternalBilling(period);
9590
+ onBillingPeriodChange?.(period);
9591
+ };
9592
+
9593
+ const hasPowerUps = powerUpCards.length > 0;
9594
+ const hasLetUsDrive = letUsDriveCards.length > 0;
9595
+
8615
9596
  return (
8616
- <Dialog open={open} onOpenChange={onOpenChange}>
8617
- <DialogContent
8618
- ref={ref}
8619
- size="sm"
8620
- hideCloseButton
8621
- className="p-0 border-0 bg-transparent shadow-none"
8622
- >
8623
- {/* Visually hidden title for accessibility */}
8624
- <DialogTitle className="sr-only">
8625
- {title || "Select payment method"}
8626
- </DialogTitle>
8627
- <PaymentOptionCard
8628
- title={title}
8629
- subtitle={subtitle}
8630
- options={options}
8631
- selectedOptionId={selectedOptionId}
8632
- defaultSelectedOptionId={defaultSelectedOptionId}
8633
- onOptionSelect={onOptionSelect}
8634
- ctaText={ctaText}
8635
- onCtaClick={onCtaClick}
8636
- onClose={handleClose}
8637
- loading={loading}
8638
- disabled={disabled}
8639
- className={className}
8640
- />
8641
- </DialogContent>
8642
- </Dialog>
9597
+ <div
9598
+ ref={ref}
9599
+ className={cn("flex flex-col bg-card", className)}
9600
+ {...props}
9601
+ >
9602
+ {/* \u2500\u2500\u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500 */}
9603
+ <PageHeader
9604
+ title={title}
9605
+ actions={headerActions}
9606
+ layout="horizontal"
9607
+ />
9608
+
9609
+ {/* \u2500\u2500\u2500\u2500\u2500 Plan Selection Area \u2500\u2500\u2500\u2500\u2500 */}
9610
+ <div className="flex flex-col gap-6 px-6 py-6">
9611
+ {/* Tabs + billing toggle */}
9612
+ {tabs.length > 0 && (
9613
+ <PricingToggle
9614
+ tabs={tabs}
9615
+ activeTab={currentTab}
9616
+ onTabChange={handleTabChange}
9617
+ showBillingToggle={showBillingToggle}
9618
+ billingPeriod={currentBilling}
9619
+ onBillingPeriodChange={handleBillingChange}
9620
+ />
9621
+ )}
9622
+
9623
+ {/* Plan cards grid */}
9624
+ {planCards.length > 0 && (
9625
+ <div
9626
+ className={cn(
9627
+ "grid gap-6 justify-center",
9628
+ planCards.length <= 2
9629
+ ? "grid-cols-1 md:grid-cols-2 max-w-[960px] mx-auto"
9630
+ : "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
9631
+ )}
9632
+ >
9633
+ {planCards.map((cardProps, index) => (
9634
+ <PricingCard key={index} {...cardProps} />
9635
+ ))}
9636
+ </div>
9637
+ )}
9638
+ </div>
9639
+
9640
+ {/* \u2500\u2500\u2500\u2500\u2500 Power-ups Section \u2500\u2500\u2500\u2500\u2500 */}
9641
+ {hasPowerUps && (
9642
+ <div className="bg-semantic-bg-ui px-6 py-[60px]">
9643
+ <div className="flex flex-col gap-4">
9644
+ {/* Section header */}
9645
+ <div className="flex items-center justify-between">
9646
+ <h2 className="text-lg font-semibold text-semantic-text-primary m-0">
9647
+ {powerUpsTitle}
9648
+ </h2>
9649
+ {onFeatureComparisonClick && (
9650
+ <Button
9651
+ variant="link"
9652
+ className="text-semantic-text-link p-0 h-auto min-w-0 gap-1"
9653
+ onClick={onFeatureComparisonClick}
9654
+ >
9655
+ {featureComparisonText}
9656
+ <ExternalLink className="size-3.5" />
9657
+ </Button>
9658
+ )}
9659
+ </div>
9660
+
9661
+ {/* Power-up cards */}
9662
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
9663
+ {powerUpCards.map((cardProps, index) => (
9664
+ <PowerUpCard key={index} {...cardProps} />
9665
+ ))}
9666
+ </div>
9667
+ </div>
9668
+ </div>
9669
+ )}
9670
+
9671
+ {/* \u2500\u2500\u2500\u2500\u2500 Let Us Drive Section \u2500\u2500\u2500\u2500\u2500 */}
9672
+ {hasLetUsDrive && (
9673
+ <div className="bg-card px-6 py-[60px]">
9674
+ <div className="flex flex-col gap-4">
9675
+ {/* Section header */}
9676
+ <h2 className="text-lg font-semibold text-semantic-text-primary m-0">
9677
+ {letUsDriveTitle}
9678
+ </h2>
9679
+
9680
+ {/* Service cards */}
9681
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
9682
+ {letUsDriveCards.map((cardProps, index) => (
9683
+ <LetUsDriveCard key={index} {...cardProps} />
9684
+ ))}
9685
+ </div>
9686
+ </div>
9687
+ </div>
9688
+ )}
9689
+ </div>
8643
9690
  );
8644
9691
  }
8645
9692
  );
8646
9693
 
8647
- PaymentOptionCardModal.displayName = "PaymentOptionCardModal";
9694
+ PricingPage.displayName = "PricingPage";
9695
+
9696
+ export { PricingPage };
8648
9697
  `, prefix)
8649
9698
  },
8650
9699
  {
8651
9700
  name: "types.ts",
8652
9701
  content: prefixTailwindClasses(`import * as React from "react";
9702
+ import type { PricingCardProps } from "../pricing-card/types";
9703
+ import type { PowerUpCardProps } from "../power-up-card/types";
9704
+ import type { LetUsDriveCardProps } from "../let-us-drive-card/types";
9705
+ import type { PricingToggleTab } from "../pricing-toggle/types";
8653
9706
 
8654
- /**
8655
- * A single payment option entry.
8656
- */
8657
- export interface PaymentOption {
8658
- /** Unique identifier for this option */
8659
- id: string;
8660
- /** Icon rendered inside a rounded container (e.g. an SVG or Lucide icon) */
8661
- icon: React.ReactNode;
8662
- /** Primary label (e.g. "Net banking") */
8663
- title: string;
8664
- /** Secondary description (e.g. "Pay securely through your bank") */
8665
- description: string;
8666
- }
9707
+ export type { PricingToggleTab };
8667
9708
 
8668
9709
  /**
8669
- * Props for the PaymentOptionCard component.
9710
+ * Props for the PricingPage component.
9711
+ *
9712
+ * PricingPage is a layout compositor that orchestrates PricingToggle,
9713
+ * PricingCard, PowerUpCard, LetUsDriveCard, and PageHeader into
9714
+ * the full plan selection page.
8670
9715
  */
8671
- export interface PaymentOptionCardProps {
8672
- /** Header title */
9716
+ export interface PricingPageProps
9717
+ extends React.HTMLAttributes<HTMLDivElement> {
9718
+ /* \u2500\u2500\u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500 */
9719
+
9720
+ /** Page title (default: "Select business plan") */
8673
9721
  title?: string;
8674
- /** Header subtitle */
8675
- subtitle?: string;
8676
- /** List of selectable payment options */
8677
- options: PaymentOption[];
8678
- /** Currently selected option id */
8679
- selectedOptionId?: string;
8680
- /** Default selected option id (uncontrolled mode) */
8681
- defaultSelectedOptionId?: string;
8682
- /** Callback fired when an option is selected */
8683
- onOptionSelect?: (optionId: string) => void;
8684
- /** CTA button text */
8685
- ctaText?: string;
8686
- /** Callback fired when CTA button is clicked */
8687
- onCtaClick?: () => void;
8688
- /** Callback fired when close button is clicked */
8689
- onClose?: () => void;
8690
- /** Whether the CTA button shows loading state */
8691
- loading?: boolean;
8692
- /** Whether the CTA button is disabled */
8693
- disabled?: boolean;
8694
- /** Additional className for the root element */
8695
- className?: string;
9722
+ /** Actions rendered on the right side of the header (e.g., number-type dropdown) */
9723
+ headerActions?: React.ReactNode;
9724
+
9725
+ /* \u2500\u2500\u2500\u2500\u2500 Tabs & Billing \u2500\u2500\u2500\u2500\u2500 */
9726
+
9727
+ /** Plan type tabs shown in the pill selector */
9728
+ tabs?: PricingToggleTab[];
9729
+ /** Currently active tab value (controlled). Falls back to first tab when unset. */
9730
+ activeTab?: string;
9731
+ /** Callback when the active tab changes */
9732
+ onTabChange?: (value: string) => void;
9733
+ /** Whether to show the monthly/yearly billing toggle */
9734
+ showBillingToggle?: boolean;
9735
+ /** Current billing period (controlled) */
9736
+ billingPeriod?: "monthly" | "yearly";
9737
+ /** Callback when the billing period changes */
9738
+ onBillingPeriodChange?: (period: "monthly" | "yearly") => void;
9739
+
9740
+ /* \u2500\u2500\u2500\u2500\u2500 Plan Cards \u2500\u2500\u2500\u2500\u2500 */
9741
+
9742
+ /** Array of plan card props to render in the main pricing grid */
9743
+ planCards?: PricingCardProps[];
9744
+
9745
+ /* \u2500\u2500\u2500\u2500\u2500 Power-ups Section \u2500\u2500\u2500\u2500\u2500 */
9746
+
9747
+ /** Array of power-up card props */
9748
+ powerUpCards?: PowerUpCardProps[];
9749
+ /** Power-ups section heading (default: "Power-ups and charges") */
9750
+ powerUpsTitle?: string;
9751
+ /** Feature comparison link text (default: "See full feature comparison") */
9752
+ featureComparisonText?: string;
9753
+ /** Callback when the feature comparison link is clicked */
9754
+ onFeatureComparisonClick?: () => void;
9755
+
9756
+ /* \u2500\u2500\u2500\u2500\u2500 Let Us Drive Section \u2500\u2500\u2500\u2500\u2500 */
9757
+
9758
+ /** Array of let-us-drive card props */
9759
+ letUsDriveCards?: LetUsDriveCardProps[];
9760
+ /** Let-us-drive section heading (default: "Let us drive \u2014 Full-service management") */
9761
+ letUsDriveTitle?: string;
8696
9762
  }
9763
+ `, prefix)
9764
+ },
9765
+ {
9766
+ name: "index.ts",
9767
+ content: prefixTailwindClasses(`export { PricingPage } from "./pricing-page";
9768
+ export type { PricingPageProps, PricingToggleTab } from "./types";
9769
+ `, prefix)
9770
+ }
9771
+ ]
9772
+ },
9773
+ "pricing-toggle": {
9774
+ name: "pricing-toggle",
9775
+ description: "A plan type tab selector with billing period toggle for pricing pages. Pill-shaped tabs switch plan categories, and an optional switch toggles between monthly/yearly billing.",
9776
+ category: "custom",
9777
+ dependencies: [
9778
+ "clsx",
9779
+ "tailwind-merge",
9780
+ "@radix-ui/react-switch@^1.2.6"
9781
+ ],
9782
+ internalDependencies: [
9783
+ "switch"
9784
+ ],
9785
+ isMultiFile: true,
9786
+ directory: "pricing-toggle",
9787
+ mainFile: "pricing-toggle.tsx",
9788
+ files: [
9789
+ {
9790
+ name: "pricing-toggle.tsx",
9791
+ content: prefixTailwindClasses(`import * as React from "react";
9792
+ import { cn } from "../../../lib/utils";
9793
+ import { Switch } from "../switch";
9794
+ import type { PricingToggleProps } from "./types";
8697
9795
 
8698
9796
  /**
8699
- * Props for the PaymentOptionCardModal component.
8700
- * Extends the card props with Dialog open/close control, omitting \`onClose\`
8701
- * which is handled internally by the modal.
9797
+ * PricingToggle provides a plan type tab selector with an optional
9798
+ * billing period toggle. The pill-shaped tabs switch between plan
9799
+ * categories (e.g. "Team-Led Plans" vs "Go-AI First"), and the
9800
+ * billing toggle switches between monthly/yearly pricing.
9801
+ *
9802
+ * @example
9803
+ * \`\`\`tsx
9804
+ * <PricingToggle
9805
+ * tabs={[
9806
+ * { label: "Team-Led Plans", value: "team" },
9807
+ * { label: "Go-AI First", value: "ai" },
9808
+ * ]}
9809
+ * activeTab="team"
9810
+ * onTabChange={(value) => setActiveTab(value)}
9811
+ * showBillingToggle
9812
+ * billingPeriod="monthly"
9813
+ * onBillingPeriodChange={(period) => setBillingPeriod(period)}
9814
+ * />
9815
+ * \`\`\`
8702
9816
  */
8703
- export interface PaymentOptionCardModalProps
8704
- extends Omit<PaymentOptionCardProps, "onClose"> {
8705
- /** Whether the modal is open */
8706
- open: boolean;
8707
- /** Callback when modal should open or close */
8708
- onOpenChange: (open: boolean) => void;
9817
+ const PricingToggle = React.forwardRef<HTMLDivElement, PricingToggleProps>(
9818
+ (
9819
+ {
9820
+ tabs,
9821
+ activeTab,
9822
+ onTabChange,
9823
+ showBillingToggle = false,
9824
+ billingPeriod = "monthly",
9825
+ onBillingPeriodChange,
9826
+ monthlyLabel = "Monthly",
9827
+ yearlyLabel = "Yearly (Save 20%)",
9828
+ className,
9829
+ ...props
9830
+ },
9831
+ ref
9832
+ ) => {
9833
+ const isYearly = billingPeriod === "yearly";
9834
+
9835
+ return (
9836
+ <div
9837
+ ref={ref}
9838
+ className={cn("flex flex-col items-center gap-4", className)}
9839
+ {...props}
9840
+ >
9841
+ {/* Plan type tabs */}
9842
+ <div className="inline-flex items-start gap-1 rounded-full bg-semantic-bg-ui p-1">
9843
+ {tabs.map((tab) => {
9844
+ const isActive = tab.value === activeTab;
9845
+ return (
9846
+ <button
9847
+ key={tab.value}
9848
+ type="button"
9849
+ role="tab"
9850
+ aria-selected={isActive}
9851
+ className={cn(
9852
+ "h-10 shrink-0 rounded-full px-4 py-1 text-base transition-colors",
9853
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-brand focus-visible:ring-offset-2",
9854
+ isActive
9855
+ ? "bg-semantic-brand font-semibold text-white shadow-sm"
9856
+ : "font-normal text-semantic-text-primary"
9857
+ )}
9858
+ onClick={() => onTabChange(tab.value)}
9859
+ >
9860
+ {tab.label}
9861
+ </button>
9862
+ );
9863
+ })}
9864
+ </div>
9865
+
9866
+ {/* Billing period toggle */}
9867
+ {showBillingToggle && (
9868
+ <div className="flex items-center gap-4">
9869
+ <span
9870
+ className={cn(
9871
+ "text-sm font-semibold tracking-[0.014px]",
9872
+ !isYearly
9873
+ ? "text-semantic-text-secondary"
9874
+ : "text-semantic-text-muted"
9875
+ )}
9876
+ >
9877
+ {monthlyLabel}
9878
+ </span>
9879
+ <Switch
9880
+ size="sm"
9881
+ checked={isYearly}
9882
+ onCheckedChange={(checked) =>
9883
+ onBillingPeriodChange?.(checked ? "yearly" : "monthly")
9884
+ }
9885
+ />
9886
+ <span
9887
+ className={cn(
9888
+ "text-sm font-semibold tracking-[0.014px]",
9889
+ isYearly
9890
+ ? "text-semantic-text-secondary"
9891
+ : "text-semantic-text-muted"
9892
+ )}
9893
+ >
9894
+ {yearlyLabel}
9895
+ </span>
9896
+ </div>
9897
+ )}
9898
+ </div>
9899
+ );
9900
+ }
9901
+ );
9902
+
9903
+ PricingToggle.displayName = "PricingToggle";
9904
+
9905
+ export { PricingToggle };
9906
+ `, prefix)
9907
+ },
9908
+ {
9909
+ name: "types.ts",
9910
+ content: prefixTailwindClasses(`/** A single tab option in the plan tab selector */
9911
+ export interface PricingToggleTab {
9912
+ /** Display label for the tab */
9913
+ label: string;
9914
+ /** Unique value identifier for the tab */
9915
+ value: string;
9916
+ }
9917
+
9918
+ export interface PricingToggleProps
9919
+ extends React.HTMLAttributes<HTMLDivElement> {
9920
+ /** Array of tab options for the plan type selector */
9921
+ tabs: PricingToggleTab[];
9922
+ /** Currently active tab value (controlled) */
9923
+ activeTab: string;
9924
+ /** Callback when the active tab changes */
9925
+ onTabChange: (value: string) => void;
9926
+ /** Whether to show the billing period toggle below the tabs */
9927
+ showBillingToggle?: boolean;
9928
+ /** Current billing period \u2014 "monthly" or "yearly" (controlled) */
9929
+ billingPeriod?: "monthly" | "yearly";
9930
+ /** Callback when the billing period changes */
9931
+ onBillingPeriodChange?: (period: "monthly" | "yearly") => void;
9932
+ /** Left label for the billing toggle (default: "Monthly") */
9933
+ monthlyLabel?: string;
9934
+ /** Right label for the billing toggle (default: "Yearly (Save 20%)") */
9935
+ yearlyLabel?: string;
8709
9936
  }
8710
9937
  `, prefix)
8711
9938
  },
8712
9939
  {
8713
9940
  name: "index.ts",
8714
- content: prefixTailwindClasses(`export { PaymentOptionCard } from "./payment-option-card";
8715
- export { PaymentOptionCardModal } from "./payment-option-card-modal";
8716
- export type {
8717
- PaymentOptionCardProps,
8718
- PaymentOptionCardModalProps,
8719
- PaymentOption,
8720
- } from "./types";
9941
+ content: prefixTailwindClasses(`export { PricingToggle } from "./pricing-toggle";
9942
+ export type { PricingToggleProps, PricingToggleTab } from "./types";
8721
9943
  `, prefix)
8722
9944
  }
8723
9945
  ]
@@ -8743,7 +9965,7 @@ export type {
8743
9965
  {
8744
9966
  name: "wallet-topup.tsx",
8745
9967
  content: prefixTailwindClasses(`import * as React from "react";
8746
- import { Check, Ticket } from "lucide-react";
9968
+ import { Ticket } from "lucide-react";
8747
9969
  import { cn } from "../../../lib/utils";
8748
9970
  import { Button } from "../button";
8749
9971
  import { Input } from "../input";
@@ -8766,7 +9988,11 @@ function normalizeAmountOption(option: number | AmountOption): AmountOption {
8766
9988
  * Format currency amount with symbol
8767
9989
  */
8768
9990
  function formatCurrency(amount: number, symbol: string = "\u20B9"): string {
8769
- return \`\${symbol}\${amount.toLocaleString("en-IN")}\`;
9991
+ const hasDecimals = amount % 1 !== 0;
9992
+ return \`\${symbol}\${amount.toLocaleString("en-IN", {
9993
+ minimumFractionDigits: hasDecimals ? 2 : 0,
9994
+ maximumFractionDigits: hasDecimals ? 2 : 0,
9995
+ })}\`;
8770
9996
  }
8771
9997
 
8772
9998
  /**
@@ -8798,6 +10024,13 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
8798
10024
  customAmountPlaceholder = "Enter amount",
8799
10025
  customAmountLabel = "Custom Amount",
8800
10026
  currencySymbol = "\u20B9",
10027
+ taxAmount: taxAmountProp,
10028
+ taxCalculator,
10029
+ taxLabel = "Taxes (GST)",
10030
+ rechargeAmountLabel = "Recharge amount",
10031
+ outstandingAmount,
10032
+ outstandingLabel = "Outstanding",
10033
+ topupLabel = "Top-up",
8801
10034
  showVoucherLink = true,
8802
10035
  voucherLinkText = "Have an offline code or voucher?",
8803
10036
  voucherIcon = <Ticket className="size-4" />,
@@ -8894,6 +10127,10 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
8894
10127
  };
8895
10128
 
8896
10129
  const normalizedAmounts = amounts.map(normalizeAmountOption);
10130
+ const displayAmounts =
10131
+ outstandingAmount && outstandingAmount > 0
10132
+ ? [{ value: 0 } as AmountOption, ...normalizedAmounts]
10133
+ : normalizedAmounts;
8897
10134
 
8898
10135
  const handleAmountSelect = (value: number) => {
8899
10136
  const newValue = selectedValue === value ? null : value;
@@ -8925,19 +10162,39 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
8925
10162
  };
8926
10163
 
8927
10164
  // Determine the effective pay amount
8928
- const payAmount =
8929
- selectedValue ?? (customValue ? Number(customValue) : 0);
10165
+ const baseSelection =
10166
+ selectedValue ?? (customValue ? Number(customValue) : null);
10167
+
10168
+ // Effective recharge amount (includes outstanding if present)
10169
+ const effectiveRechargeAmount =
10170
+ baseSelection !== null
10171
+ ? outstandingAmount
10172
+ ? outstandingAmount + baseSelection
10173
+ : baseSelection
10174
+ : 0;
10175
+
10176
+ // Tax computation
10177
+ const hasTax = taxCalculator !== undefined || taxAmountProp !== undefined;
10178
+ const computedTax =
10179
+ effectiveRechargeAmount > 0
10180
+ ? taxCalculator
10181
+ ? taxCalculator(effectiveRechargeAmount)
10182
+ : (taxAmountProp ?? 0)
10183
+ : 0;
10184
+
10185
+ // Total payable (recharge + tax)
10186
+ const totalPayable = effectiveRechargeAmount + computedTax;
8930
10187
 
8931
10188
  const handlePay = () => {
8932
- if (payAmount > 0) {
8933
- onPay?.(payAmount);
10189
+ if (totalPayable > 0) {
10190
+ onPay?.(totalPayable);
8934
10191
  }
8935
10192
  };
8936
10193
 
8937
10194
  const buttonText =
8938
10195
  ctaText ||
8939
- (payAmount > 0
8940
- ? \`Pay \${formatCurrency(payAmount, currencySymbol)} now\`
10196
+ (totalPayable > 0
10197
+ ? \`Pay \${formatCurrency(totalPayable, currencySymbol)} now\`
8941
10198
  : "Select an amount");
8942
10199
 
8943
10200
  return (
@@ -8974,8 +10231,15 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
8974
10231
  {amountSectionLabel}
8975
10232
  </label>
8976
10233
  <div className="grid grid-cols-2 gap-4">
8977
- {normalizedAmounts.map((option) => {
10234
+ {displayAmounts.map((option) => {
8978
10235
  const isSelected = selectedValue === option.value;
10236
+ const hasOutstanding =
10237
+ outstandingAmount !== undefined &&
10238
+ outstandingAmount > 0;
10239
+ const totalForOption = hasOutstanding
10240
+ ? outstandingAmount + option.value
10241
+ : option.value;
10242
+
8979
10243
  return (
8980
10244
  <button
8981
10245
  key={option.value}
@@ -8984,18 +10248,53 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
8984
10248
  aria-checked={isSelected}
8985
10249
  onClick={() => handleAmountSelect(option.value)}
8986
10250
  className={cn(
8987
- "flex items-center justify-between h-10 px-4 py-2.5 rounded text-sm text-semantic-text-primary transition-all cursor-pointer",
10251
+ "flex px-4 rounded text-sm transition-all cursor-pointer",
10252
+ hasOutstanding
10253
+ ? "flex-col items-start gap-0.5 h-auto py-3"
10254
+ : "items-center h-10 py-2.5",
8988
10255
  isSelected
8989
- ? "border border-semantic-primary shadow-sm"
10256
+ ? "border border-[var(--semantic-brand)] shadow-sm"
8990
10257
  : "border border-semantic-border-input hover:border-semantic-text-muted"
8991
10258
  )}
8992
10259
  >
8993
- <span>
8994
- {option.label ||
8995
- formatCurrency(option.value, currencySymbol)}
10260
+ <span
10261
+ className={cn(
10262
+ isSelected
10263
+ ? "text-semantic-primary"
10264
+ : "text-semantic-text-primary",
10265
+ hasOutstanding && "font-medium"
10266
+ )}
10267
+ >
10268
+ {hasOutstanding
10269
+ ? formatCurrency(
10270
+ totalForOption,
10271
+ currencySymbol
10272
+ )
10273
+ : option.label ||
10274
+ formatCurrency(
10275
+ option.value,
10276
+ currencySymbol
10277
+ )}
8996
10278
  </span>
8997
- {isSelected && (
8998
- <Check className="size-5 text-semantic-primary" />
10279
+ {hasOutstanding && (
10280
+ <>
10281
+ <span className="text-xs text-semantic-text-muted">
10282
+ {outstandingLabel}:{" "}
10283
+ {formatCurrency(
10284
+ outstandingAmount,
10285
+ currencySymbol
10286
+ )}
10287
+ </span>
10288
+ <span className="text-xs text-semantic-text-muted">
10289
+ {topupLabel}:{" "}
10290
+ {option.value > 0
10291
+ ? formatCurrency(
10292
+ option.value,
10293
+ currencySymbol
10294
+ )
10295
+ : "-"}
10296
+ </span>
10297
+ </>
8999
10298
  )}
9000
10299
  </button>
9001
10300
  );
@@ -9016,6 +10315,31 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
9016
10315
  />
9017
10316
  </div>
9018
10317
 
10318
+ {/* Recharge Summary */}
10319
+ {hasTax && effectiveRechargeAmount > 0 && (
10320
+ <div className="flex flex-col gap-2 rounded-lg bg-[var(--semantic-warning-surface)] px-4 py-3">
10321
+ <div className="flex items-center justify-between text-sm">
10322
+ <span className="text-semantic-text-primary">
10323
+ {rechargeAmountLabel}
10324
+ </span>
10325
+ <span className="text-semantic-text-primary font-medium">
10326
+ {formatCurrency(
10327
+ effectiveRechargeAmount,
10328
+ currencySymbol
10329
+ )}
10330
+ </span>
10331
+ </div>
10332
+ <div className="flex items-center justify-between text-sm">
10333
+ <span className="text-semantic-text-muted">
10334
+ {taxLabel}
10335
+ </span>
10336
+ <span className="text-semantic-text-muted">
10337
+ {formatCurrency(computedTax, currencySymbol)}
10338
+ </span>
10339
+ </div>
10340
+ </div>
10341
+ )}
10342
+
9019
10343
  {/* Voucher Link or Voucher Code Input */}
9020
10344
  {showVoucherLink && !showVoucherInput && (
9021
10345
  <button
@@ -9067,7 +10391,7 @@ export const WalletTopup = React.forwardRef<HTMLDivElement, WalletTopupProps>(
9067
10391
  className="w-full"
9068
10392
  onClick={handlePay}
9069
10393
  loading={loading}
9070
- disabled={disabled || payAmount <= 0}
10394
+ disabled={disabled || totalPayable <= 0}
9071
10395
  >
9072
10396
  {buttonText}
9073
10397
  </Button>
@@ -9136,6 +10460,24 @@ export interface WalletTopupProps {
9136
10460
  /** Currency symbol (default: "\u20B9") */
9137
10461
  currencySymbol?: string;
9138
10462
 
10463
+ // Tax / Summary
10464
+ /** Static tax amount to display in the summary section */
10465
+ taxAmount?: number;
10466
+ /** Function to dynamically compute tax from the recharge amount. Takes priority over taxAmount. */
10467
+ taxCalculator?: (amount: number) => number;
10468
+ /** Label for the tax line in the summary (default: "Taxes (GST)") */
10469
+ taxLabel?: string;
10470
+ /** Label for the recharge amount line in the summary (default: "Recharge amount") */
10471
+ rechargeAmountLabel?: string;
10472
+
10473
+ // Outstanding balance
10474
+ /** Outstanding balance. When set, auto-prepends an outstanding-only option and shows breakdowns in each amount button. */
10475
+ outstandingAmount?: number;
10476
+ /** Label for the outstanding breakdown in amount buttons (default: "Outstanding") */
10477
+ outstandingLabel?: string;
10478
+ /** Label for the topup breakdown in amount buttons (default: "Top-up") */
10479
+ topupLabel?: string;
10480
+
9139
10481
  // Voucher link
9140
10482
  /** Whether to show the voucher/code link */
9141
10483
  showVoucherLink?: boolean;