@devxcrew/react-ui 0.0.0-stage → 0.1.7
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 +85 -2
- package/package.json +105 -4
- package/src/blocks/agent-chat-workspace/agent-chat-workspace.tsx +162 -0
- package/src/blocks/agent-chat-workspace/index.ts +2 -0
- package/src/blocks/agent-provider-settings/agent-provider-settings.tsx +183 -0
- package/src/blocks/agent-provider-settings/index.ts +2 -0
- package/src/blocks/agent-task-workspace/agent-task-workspace.tsx +48 -0
- package/src/blocks/agent-task-workspace/index.ts +2 -0
- package/src/blocks/analysis-context/analysis-context.tsx +6 -0
- package/src/blocks/analysis-context/index.ts +1 -0
- package/src/blocks/app-header/app-header.tsx +159 -0
- package/src/blocks/app-header/index.ts +2 -0
- package/src/blocks/archived-chat-workspace/archived-chat-workspace.tsx +29 -0
- package/src/blocks/archived-chat-workspace/index.ts +2 -0
- package/src/blocks/auth/README.md +32 -0
- package/src/blocks/auth/admin-login-page.tsx +15 -0
- package/src/blocks/auth/admin-password-forgot-page.tsx +5 -0
- package/src/blocks/auth/auth-page-layout.tsx +90 -0
- package/src/blocks/auth/client-login-page.tsx +20 -0
- package/src/blocks/auth/client-password-forgot-page.tsx +5 -0
- package/src/blocks/auth/identity-management-desk.tsx +530 -0
- package/src/blocks/auth/index.ts +12 -0
- package/src/blocks/auth/login-page.tsx +206 -0
- package/src/blocks/auth/password-forgot-page.tsx +60 -0
- package/src/blocks/auth/portal-pages.tsx +32 -0
- package/src/blocks/auth/privileged-desk.tsx +55 -0
- package/src/blocks/auth/register-page.tsx +117 -0
- package/src/blocks/auth/session-boundary.tsx +296 -0
- package/src/blocks/auth/super-admin-login-page.tsx +15 -0
- package/src/blocks/auth/super-admin-password-forgot-page.tsx +5 -0
- package/src/blocks/blog/blog-card.tsx +156 -0
- package/src/blocks/blog/blog-reader.tsx +175 -0
- package/src/blocks/blog/blog-types.ts +47 -0
- package/src/blocks/blog/index.ts +3 -0
- package/src/blocks/chat-composer/chat-composer.tsx +140 -0
- package/src/blocks/chat-composer/index.ts +1 -0
- package/src/blocks/chat-history/chat-history.tsx +59 -0
- package/src/blocks/chat-history/index.ts +1 -0
- package/src/blocks/chat-response-progress/chat-response-progress.tsx +34 -0
- package/src/blocks/chat-response-progress/index.ts +2 -0
- package/src/blocks/chat-runtime-controls/chat-runtime-controls.tsx +56 -0
- package/src/blocks/chat-runtime-controls/index.ts +2 -0
- package/src/blocks/chat-runtime-trace/chat-runtime-trace.tsx +71 -0
- package/src/blocks/chat-runtime-trace/index.ts +2 -0
- package/src/blocks/codex-connection-settings/codex-connection-settings.tsx +42 -0
- package/src/blocks/codex-connection-settings/index.ts +1 -0
- package/src/blocks/dropzone/dropzone-file-item.tsx +113 -0
- package/src/blocks/dropzone/dropzone-types.ts +32 -0
- package/src/blocks/dropzone/dropzone.tsx +216 -0
- package/src/blocks/dropzone/index.ts +3 -0
- package/src/blocks/ecommerce/cart/cart-drawer.tsx +228 -0
- package/src/blocks/ecommerce/cart/cart-types.ts +27 -0
- package/src/blocks/ecommerce/cart/index.ts +2 -0
- package/src/blocks/ecommerce/categories/categories-types.ts +23 -0
- package/src/blocks/ecommerce/categories/category-showcase.tsx +125 -0
- package/src/blocks/ecommerce/categories/index.ts +2 -0
- package/src/blocks/ecommerce/checkout/checkout-types.ts +48 -0
- package/src/blocks/ecommerce/checkout/checkout-wizard.tsx +468 -0
- package/src/blocks/ecommerce/checkout/index.ts +2 -0
- package/src/blocks/ecommerce/comparison/comparison-types.ts +28 -0
- package/src/blocks/ecommerce/comparison/index.ts +2 -0
- package/src/blocks/ecommerce/comparison/product-comparison.tsx +174 -0
- package/src/blocks/ecommerce/coupon-wallet/coupon-wallet-types.ts +21 -0
- package/src/blocks/ecommerce/coupon-wallet/coupon-wallet.tsx +154 -0
- package/src/blocks/ecommerce/coupon-wallet/index.ts +2 -0
- package/src/blocks/ecommerce/delivery-tracker/delivery-tracker-types.ts +20 -0
- package/src/blocks/ecommerce/delivery-tracker/delivery-tracker.tsx +169 -0
- package/src/blocks/ecommerce/delivery-tracker/index.ts +2 -0
- package/src/blocks/ecommerce/index.ts +12 -0
- package/src/blocks/ecommerce/payment-methods/index.ts +2 -0
- package/src/blocks/ecommerce/payment-methods/payment-method-selector.tsx +224 -0
- package/src/blocks/ecommerce/payment-methods/payment-methods-types.ts +21 -0
- package/src/blocks/ecommerce/price-history/index.ts +2 -0
- package/src/blocks/ecommerce/price-history/price-history-chart.tsx +217 -0
- package/src/blocks/ecommerce/price-history/price-history-types.ts +16 -0
- package/src/blocks/ecommerce/reviews/index.ts +2 -0
- package/src/blocks/ecommerce/reviews/reviews-section.tsx +309 -0
- package/src/blocks/ecommerce/reviews/reviews-types.ts +26 -0
- package/src/blocks/ecommerce/wishlist/index.ts +2 -0
- package/src/blocks/ecommerce/wishlist/wishlist-grid.tsx +149 -0
- package/src/blocks/ecommerce/wishlist/wishlist-types.ts +19 -0
- package/src/blocks/execution-status/README.md +25 -0
- package/src/blocks/execution-status/execution-status.tsx +135 -0
- package/src/blocks/execution-status/index.ts +2 -0
- package/src/blocks/file-tree/file-icons.tsx +84 -0
- package/src/blocks/file-tree/file-tree-node.tsx +158 -0
- package/src/blocks/file-tree/file-tree-types.ts +33 -0
- package/src/blocks/file-tree/file-tree.tsx +163 -0
- package/src/blocks/file-tree/index.ts +5 -0
- package/src/blocks/filter-builder/filter-builder.tsx +169 -0
- package/src/blocks/filter-builder/filter-row.tsx +164 -0
- package/src/blocks/filter-builder/filter-types.ts +61 -0
- package/src/blocks/filter-builder/index.ts +12 -0
- package/src/blocks/footer/footer-types.ts +35 -0
- package/src/blocks/footer/index.ts +2 -0
- package/src/blocks/footer/site-footer.tsx +212 -0
- package/src/blocks/form/form-block.tsx +113 -0
- package/src/blocks/form/form-lookup-field.tsx +60 -0
- package/src/blocks/form/index.ts +4 -0
- package/src/blocks/handover-stack/handover-stack.tsx +41 -0
- package/src/blocks/handover-stack/index.ts +1 -0
- package/src/blocks/idea-handover/idea-handover-readiness.ts +41 -0
- package/src/blocks/idea-handover/idea-handover-workspace.tsx +150 -0
- package/src/blocks/idea-handover/index.ts +4 -0
- package/src/blocks/kanban/index.ts +11 -0
- package/src/blocks/kanban/kanban-board.tsx +146 -0
- package/src/blocks/kanban/kanban-card.tsx +149 -0
- package/src/blocks/kanban/kanban-column.tsx +102 -0
- package/src/blocks/kanban/kanban-types.ts +38 -0
- package/src/blocks/loader/global-loader.tsx +90 -0
- package/src/blocks/loader/index.ts +1 -0
- package/src/blocks/mascot/README.md +21 -0
- package/src/blocks/mascot/index.ts +2 -0
- package/src/blocks/mascot/mascot.tsx +202 -0
- package/src/blocks/master-list/README.md +21 -0
- package/src/blocks/master-list/index.ts +12 -0
- package/src/blocks/master-list/master-form.tsx +157 -0
- package/src/blocks/master-list/master-list-desk.tsx +370 -0
- package/src/blocks/master-list/master-list.tsx +189 -0
- package/src/blocks/master-list/types.ts +18 -0
- package/src/blocks/mermaid-preview/index.ts +1 -0
- package/src/blocks/mermaid-preview/mermaid-preview.tsx +164 -0
- package/src/blocks/notifications/README.md +7 -0
- package/src/blocks/notifications/index.ts +1 -0
- package/src/blocks/notifications/notification-center-page.tsx +73 -0
- package/src/blocks/pricing/index.ts +7 -0
- package/src/blocks/pricing/pricing-table.tsx +173 -0
- package/src/blocks/pricing/pricing-types.ts +34 -0
- package/src/blocks/product-card/index.ts +2 -0
- package/src/blocks/product-card/product-card-types.ts +35 -0
- package/src/blocks/product-card/product-card.tsx +183 -0
- package/src/blocks/table/data-table-block.tsx +313 -0
- package/src/blocks/table/data-table-filter-menu.tsx +48 -0
- package/src/blocks/table/data-table-icon-menu-trigger.tsx +44 -0
- package/src/blocks/table/data-table-menu-content.tsx +32 -0
- package/src/blocks/table/data-table-pagination.tsx +149 -0
- package/src/blocks/table/data-table-row-actions.tsx +53 -0
- package/src/blocks/table/data-table-status.tsx +42 -0
- package/src/blocks/table/data-table-toolbar.tsx +124 -0
- package/src/blocks/table/data-table-totals.tsx +31 -0
- package/src/blocks/table/index.ts +13 -0
- package/src/blocks/workspace/index.ts +19 -0
- package/src/blocks/workspace/workspace-action-card.tsx +73 -0
- package/src/blocks/workspace/workspace-application-header.tsx +67 -0
- package/src/blocks/workspace/workspace-entity-card.tsx +48 -0
- package/src/blocks/workspace/workspace-health-summary.tsx +15 -0
- package/src/blocks/workspace/workspace-metric-card.tsx +97 -0
- package/src/blocks/workspace/workspace-page-header.tsx +43 -0
- package/src/blocks/workspace/workspace-publish-status.tsx +15 -0
- package/src/blocks/workspace/workspace-route-checklist.tsx +28 -0
- package/src/blocks/workspace/workspace-runtime-status.tsx +48 -0
- package/src/blocks/workspace/workspace-section-card.tsx +42 -0
- package/src/components/accordion.tsx +65 -0
- package/src/components/alert-dialog.tsx +160 -0
- package/src/components/alert.tsx +68 -0
- package/src/components/aspect-ratio.tsx +22 -0
- package/src/components/attachment.tsx +195 -0
- package/src/components/avatar.tsx +90 -0
- package/src/components/badge.tsx +55 -0
- package/src/components/breadcrumb.tsx +100 -0
- package/src/components/bubble.tsx +123 -0
- package/src/components/button-group.tsx +79 -0
- package/src/components/button.tsx +71 -0
- package/src/components/calendar.tsx +185 -0
- package/src/components/card.tsx +100 -0
- package/src/components/carousel.tsx +229 -0
- package/src/components/chart.tsx +338 -0
- package/src/components/checkbox.tsx +27 -0
- package/src/components/collapsible.tsx +15 -0
- package/src/components/combobox.tsx +269 -0
- package/src/components/command.tsx +181 -0
- package/src/components/compact-model-switcher.tsx +128 -0
- package/src/components/context-menu.tsx +244 -0
- package/src/components/dialog.tsx +138 -0
- package/src/components/direction.tsx +1 -0
- package/src/components/drawer.tsx +208 -0
- package/src/components/dropdown-menu.tsx +257 -0
- package/src/components/empty.tsx +93 -0
- package/src/components/field.tsx +232 -0
- package/src/components/hover-card.tsx +45 -0
- package/src/components/input-group.tsx +146 -0
- package/src/components/input-otp.tsx +83 -0
- package/src/components/input.tsx +19 -0
- package/src/components/item.tsx +188 -0
- package/src/components/kbd.tsx +26 -0
- package/src/components/label.tsx +17 -0
- package/src/components/markdown-content.tsx +113 -0
- package/src/components/marker.tsx +67 -0
- package/src/components/menubar.tsx +267 -0
- package/src/components/message-scroller.tsx +91 -0
- package/src/components/message.tsx +84 -0
- package/src/components/native-select.tsx +54 -0
- package/src/components/navigation-menu.tsx +160 -0
- package/src/components/pagination.tsx +118 -0
- package/src/components/popover.tsx +74 -0
- package/src/components/progress.tsx +82 -0
- package/src/components/questionnaire.tsx +150 -0
- package/src/components/radio-group.tsx +35 -0
- package/src/components/resizable.tsx +41 -0
- package/src/components/rich-text-editor.tsx +604 -0
- package/src/components/scroll-area.tsx +47 -0
- package/src/components/select.tsx +189 -0
- package/src/components/separator.tsx +18 -0
- package/src/components/sheet.tsx +125 -0
- package/src/components/sidebar.tsx +693 -0
- package/src/components/skeleton.tsx +13 -0
- package/src/components/slider.tsx +51 -0
- package/src/components/sonner.tsx +45 -0
- package/src/components/sparkline.tsx +231 -0
- package/src/components/spinner.tsx +20 -0
- package/src/components/status-badge.tsx +114 -0
- package/src/components/switch.tsx +31 -0
- package/src/components/table.tsx +91 -0
- package/src/components/tabs.tsx +72 -0
- package/src/components/textarea.tsx +17 -0
- package/src/components/toast.tsx +222 -0
- package/src/components/toggle-group.tsx +85 -0
- package/src/components/toggle.tsx +42 -0
- package/src/components/tooltip.tsx +53 -0
- package/src/design-system/README.md +55 -0
- package/src/design-system/action-intents.ts +21 -0
- package/src/design-system/agent-manifest.ts +83 -0
- package/src/design-system/block-registry.ts +272 -0
- package/src/design-system/component-registry.ts +142 -0
- package/src/design-system/contracts.ts +69 -0
- package/src/design-system/defaults.ts +3 -0
- package/src/design-system/index.ts +29 -0
- package/src/design-system/mcp-server.ts +156 -0
- package/src/design-system/page-registry.ts +74 -0
- package/src/design-system/selection.ts +67 -0
- package/src/design-system/template-registry.ts +33 -0
- package/src/features/interface-topology/index.ts +14 -0
- package/src/features/interface-topology/interface-topology.types.ts +40 -0
- package/src/features/interface-topology/topology-control.tsx +18 -0
- package/src/features/interface-topology/topology-inspector.tsx +228 -0
- package/src/features/interface-topology/topology-marker.tsx +47 -0
- package/src/features/interface-topology/topology-region.tsx +44 -0
- package/src/features/interface-topology/use-interface-topology.ts +178 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-scroll-follow.ts +22 -0
- package/src/index.ts +11 -0
- package/src/layouts/README.md +65 -0
- package/src/layouts/agent-workspace/agent-activity-rail.tsx +101 -0
- package/src/layouts/agent-workspace/agent-workspace.tsx +27 -0
- package/src/layouts/agent-workspace/agent-workspace.types.ts +28 -0
- package/src/layouts/agent-workspace/index.ts +7 -0
- package/src/layouts/blog-header/blog-header-types.ts +25 -0
- package/src/layouts/blog-header/blog-header.tsx +172 -0
- package/src/layouts/blog-header/index.ts +2 -0
- package/src/layouts/documentation-workspace/README.md +8 -0
- package/src/layouts/documentation-workspace/documentation-workspace.tsx +34 -0
- package/src/layouts/documentation-workspace/index.ts +1 -0
- package/src/layouts/ecommerce-header/ecommerce-actions.tsx +89 -0
- package/src/layouts/ecommerce-header/ecommerce-announcement.tsx +43 -0
- package/src/layouts/ecommerce-header/ecommerce-drawer.tsx +97 -0
- package/src/layouts/ecommerce-header/ecommerce-header-types.ts +59 -0
- package/src/layouts/ecommerce-header/ecommerce-header.tsx +47 -0
- package/src/layouts/ecommerce-header/ecommerce-navigation.tsx +98 -0
- package/src/layouts/ecommerce-header/ecommerce-search.tsx +86 -0
- package/src/layouts/ecommerce-header/index.ts +2 -0
- package/src/layouts/main-workspace/index.ts +28 -0
- package/src/layouts/mdi/index.ts +1 -0
- package/src/layouts/mdi/mdi.tsx +20 -0
- package/src/layouts/mdi-main/index.ts +27 -0
- package/src/layouts/mdi-main/mdi-app-catalog.generated.ts +159 -0
- package/src/layouts/mdi-main/mdi-app-catalog.ts +76 -0
- package/src/layouts/mdi-main/mdi-app-switcher.tsx +107 -0
- package/src/layouts/mdi-main/mdi-feature-settings.tsx +135 -0
- package/src/layouts/mdi-main/mdi-global-search.tsx +192 -0
- package/src/layouts/mdi-main/mdi-main.tsx +241 -0
- package/src/layouts/mdi-main/mdi-notifications-menu.tsx +197 -0
- package/src/layouts/mdi-main/mdi-profile-menu.tsx +79 -0
- package/src/layouts/mdi-main/mdi-sidebar-navigation.tsx +226 -0
- package/src/layouts/mdi-main/mdi-sidebar.tsx +110 -0
- package/src/layouts/mdi-main/mdi-status-bar.tsx +26 -0
- package/src/layouts/mdi-main/mdi-top-menu-control.ts +4 -0
- package/src/layouts/mdi-main/mdi-top-menu.tsx +117 -0
- package/src/layouts/mdi-main/mdi-topology.tsx +297 -0
- package/src/layouts/mdi-main/mdi-tweak-panel.tsx +104 -0
- package/src/layouts/mdi-main/mdi-types.ts +131 -0
- package/src/layouts/mdi-main/use-mdi-features.ts +36 -0
- package/src/layouts/mdi-main/use-mdi-sidebar-state.ts +59 -0
- package/src/layouts/site-header/announcement-bar.tsx +55 -0
- package/src/layouts/site-header/category-strip.tsx +41 -0
- package/src/layouts/site-header/index.ts +16 -0
- package/src/layouts/site-header/main-navbar.tsx +179 -0
- package/src/layouts/site-header/mobile-drawer.tsx +140 -0
- package/src/layouts/site-header/site-header-types.ts +63 -0
- package/src/layouts/site-header/site-header.tsx +58 -0
- package/src/lib/random-id.ts +19 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/pdf-provider.ts +137 -0
- package/src/styles/globals.css +374 -0
- package/src/templates/README.md +12 -0
- package/src/templates/dashboard-01/components/app-sidebar.tsx +177 -0
- package/src/templates/dashboard-01/components/chart-area-interactive.tsx +276 -0
- package/src/templates/dashboard-01/components/data-table-cell-viewer.tsx +213 -0
- package/src/templates/dashboard-01/components/data-table.tsx +577 -0
- package/src/templates/dashboard-01/components/nav-documents.tsx +79 -0
- package/src/templates/dashboard-01/components/nav-main.tsx +55 -0
- package/src/templates/dashboard-01/components/nav-secondary.tsx +39 -0
- package/src/templates/dashboard-01/components/nav-user.tsx +97 -0
- package/src/templates/dashboard-01/components/section-cards.tsx +99 -0
- package/src/templates/dashboard-01/components/site-header.tsx +14 -0
- package/src/templates/dashboard-01/data.json +614 -0
- package/src/templates/dashboard-01/index.ts +7 -0
- package/src/templates/documentation-sidebar/app-sidebar.tsx +95 -0
- package/src/templates/documentation-sidebar/index.ts +2 -0
- package/src/templates/sidebar-07/components/app-sidebar.tsx +175 -0
- package/src/templates/sidebar-07/components/nav-main.tsx +64 -0
- package/src/templates/sidebar-07/components/nav-projects.tsx +79 -0
- package/src/templates/sidebar-07/components/nav-user.tsx +105 -0
- package/src/templates/sidebar-07/components/team-switcher.tsx +94 -0
- package/src/templates/sidebar-07/index.ts +1 -0
- package/src/templates/ui-page/index.ts +7 -0
- package/src/templates/ui-page/ui-browser-frame.tsx +27 -0
- package/src/templates/ui-page/ui-template-code.tsx +48 -0
- package/src/templates/ui-page/ui-template-header.tsx +87 -0
- package/src/templates/ui-page/ui-template-navigation.tsx +60 -0
- package/src/templates/ui-page/ui-template-page.tsx +97 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-config.ts +30 -0
- package/src/theme/theme-provider.tsx +142 -0
- package/src/theme/theme-selector.tsx +75 -0
- package/src/tokens/index.ts +15 -0
- package/src/vite-env.d.ts +3 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type StoreCoupon = {
|
|
2
|
+
readonly applicableCategory?: string
|
|
3
|
+
readonly code: string
|
|
4
|
+
readonly description: string
|
|
5
|
+
readonly discountText: string
|
|
6
|
+
readonly expiresAt: string
|
|
7
|
+
readonly id: string
|
|
8
|
+
readonly isApplied?: boolean
|
|
9
|
+
readonly minimumOrderValue?: number
|
|
10
|
+
readonly terms?: string
|
|
11
|
+
readonly title: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type CouponWalletProps = {
|
|
15
|
+
readonly appliedCouponId?: string
|
|
16
|
+
readonly className?: string
|
|
17
|
+
readonly coupons: readonly StoreCoupon[]
|
|
18
|
+
readonly onApplyCoupon?: (code: string) => void
|
|
19
|
+
readonly onCopyCode?: (code: string) => void
|
|
20
|
+
readonly onRemoveCoupon?: () => void
|
|
21
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { CheckIcon, CopyIcon, SparklesIcon, TagIcon, TicketIcon } from 'lucide-react'
|
|
3
|
+
import { cn } from '../../../lib/utils'
|
|
4
|
+
import { Button } from '../../../components/button'
|
|
5
|
+
import { Badge } from '../../../components/badge'
|
|
6
|
+
import type { CouponWalletProps } from './coupon-wallet-types'
|
|
7
|
+
|
|
8
|
+
export function CouponWallet({
|
|
9
|
+
appliedCouponId,
|
|
10
|
+
className,
|
|
11
|
+
coupons,
|
|
12
|
+
onApplyCoupon,
|
|
13
|
+
onCopyCode,
|
|
14
|
+
onRemoveCoupon,
|
|
15
|
+
}: CouponWalletProps) {
|
|
16
|
+
const [copiedCode, setCopiedCode] = useState<string | null>(null)
|
|
17
|
+
const [customCode, setCustomCode] = useState('')
|
|
18
|
+
|
|
19
|
+
const handleCopy = (code: string) => {
|
|
20
|
+
navigator.clipboard?.writeText(code)
|
|
21
|
+
setCopiedCode(code)
|
|
22
|
+
onCopyCode?.(code)
|
|
23
|
+
setTimeout(() => setCopiedCode(null), 2000)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={cn('space-y-6', className)}>
|
|
28
|
+
{/* Top Input for manual code entry */}
|
|
29
|
+
<div className="flex flex-col sm:flex-row gap-2 rounded-2xl border border-border/80 bg-card p-4 shadow-xs">
|
|
30
|
+
<div className="relative flex-1">
|
|
31
|
+
<TagIcon className="size-4 text-muted-foreground absolute left-3 top-1/2 -translate-y-1/2" />
|
|
32
|
+
<input
|
|
33
|
+
type="text"
|
|
34
|
+
value={customCode}
|
|
35
|
+
onChange={(e) => setCustomCode(e.target.value.toUpperCase())}
|
|
36
|
+
placeholder="Have a promo code? Enter here..."
|
|
37
|
+
className="w-full rounded-xl border border-input bg-muted/20 pl-9 pr-3 py-2 text-xs font-mono uppercase placeholder:font-sans placeholder:normal-case focus:outline-none focus:ring-1 focus:ring-primary"
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
<Button
|
|
41
|
+
size="sm"
|
|
42
|
+
disabled={!customCode.trim()}
|
|
43
|
+
onClick={() => {
|
|
44
|
+
onApplyCoupon?.(customCode)
|
|
45
|
+
setCustomCode('')
|
|
46
|
+
}}
|
|
47
|
+
className="text-xs"
|
|
48
|
+
>
|
|
49
|
+
Apply Promo
|
|
50
|
+
</Button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Available Coupons Grid */}
|
|
54
|
+
<div className="space-y-3">
|
|
55
|
+
<div className="flex items-center justify-between">
|
|
56
|
+
<div className="flex items-center gap-2">
|
|
57
|
+
<TicketIcon className="size-4 text-primary" />
|
|
58
|
+
<h4 className="text-sm font-bold text-foreground">Available Coupons & Vouchers</h4>
|
|
59
|
+
</div>
|
|
60
|
+
<span className="text-xs text-muted-foreground">{coupons.length} vouchers</span>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
64
|
+
{coupons.map((coupon) => {
|
|
65
|
+
const isApplied = appliedCouponId === coupon.id
|
|
66
|
+
const isCopied = copiedCode === coupon.code
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
key={coupon.id}
|
|
71
|
+
className={cn(
|
|
72
|
+
'relative flex flex-col justify-between overflow-hidden rounded-2xl border p-4 shadow-xs transition-all',
|
|
73
|
+
isApplied
|
|
74
|
+
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
|
75
|
+
: 'border-border/80 bg-card hover:border-border',
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{/* Coupon Scallop Cutouts */}
|
|
79
|
+
<div className="absolute -left-2 top-1/2 -translate-y-1/2 size-4 rounded-full bg-background border-r border-border" />
|
|
80
|
+
<div className="absolute -right-2 top-1/2 -translate-y-1/2 size-4 rounded-full bg-background border-l border-border" />
|
|
81
|
+
|
|
82
|
+
<div className="space-y-2">
|
|
83
|
+
<div className="flex items-start justify-between gap-2">
|
|
84
|
+
<div>
|
|
85
|
+
<span className="text-base font-black text-primary tracking-tight">
|
|
86
|
+
{coupon.discountText}
|
|
87
|
+
</span>
|
|
88
|
+
<h5 className="text-xs font-bold text-foreground mt-0.5">{coupon.title}</h5>
|
|
89
|
+
</div>
|
|
90
|
+
{isApplied ? (
|
|
91
|
+
<Badge variant="default" className="gap-1 text-[10px] font-bold">
|
|
92
|
+
<SparklesIcon className="size-3" />
|
|
93
|
+
<span>Applied</span>
|
|
94
|
+
</Badge>
|
|
95
|
+
) : (
|
|
96
|
+
<span className="text-[10px] text-muted-foreground font-semibold">
|
|
97
|
+
Expires {coupon.expiresAt}
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<p className="text-xs text-muted-foreground">{coupon.description}</p>
|
|
103
|
+
|
|
104
|
+
{coupon.minimumOrderValue && (
|
|
105
|
+
<div className="text-[11px] text-muted-foreground/90">
|
|
106
|
+
Min. spend: <strong>${coupon.minimumOrderValue}</strong>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Bottom Actions */}
|
|
112
|
+
<div className="mt-4 pt-3 border-t border-border/60 border-dashed flex items-center justify-between gap-2">
|
|
113
|
+
{/* Code pill */}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => handleCopy(coupon.code)}
|
|
117
|
+
className="flex items-center gap-1.5 rounded-lg border border-border/80 bg-muted/40 px-2.5 py-1 text-xs font-mono font-bold text-foreground hover:bg-muted transition-colors"
|
|
118
|
+
>
|
|
119
|
+
<span>{coupon.code}</span>
|
|
120
|
+
{isCopied ? (
|
|
121
|
+
<CheckIcon className="size-3 text-emerald-600" />
|
|
122
|
+
) : (
|
|
123
|
+
<CopyIcon className="size-3 text-muted-foreground" />
|
|
124
|
+
)}
|
|
125
|
+
</button>
|
|
126
|
+
|
|
127
|
+
{isApplied ? (
|
|
128
|
+
<Button
|
|
129
|
+
size="sm"
|
|
130
|
+
variant="ghost"
|
|
131
|
+
className="h-7 text-xs text-destructive hover:bg-destructive/10"
|
|
132
|
+
onClick={onRemoveCoupon}
|
|
133
|
+
>
|
|
134
|
+
Remove
|
|
135
|
+
</Button>
|
|
136
|
+
) : (
|
|
137
|
+
<Button
|
|
138
|
+
size="sm"
|
|
139
|
+
variant="outline"
|
|
140
|
+
className="h-7 text-xs"
|
|
141
|
+
onClick={() => onApplyCoupon?.(coupon.code)}
|
|
142
|
+
>
|
|
143
|
+
Apply Code
|
|
144
|
+
</Button>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
})}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type DeliveryMilestone = {
|
|
2
|
+
readonly date?: string
|
|
3
|
+
readonly description?: string
|
|
4
|
+
readonly id: string
|
|
5
|
+
readonly location?: string
|
|
6
|
+
readonly status: 'completed' | 'current' | 'pending'
|
|
7
|
+
readonly time?: string
|
|
8
|
+
readonly title: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type DeliveryTrackerProps = {
|
|
12
|
+
readonly carrierName?: string
|
|
13
|
+
readonly className?: string
|
|
14
|
+
readonly deliveryAddress?: string
|
|
15
|
+
readonly estimatedDelivery: string
|
|
16
|
+
readonly milestones: readonly DeliveryMilestone[]
|
|
17
|
+
readonly orderId: string
|
|
18
|
+
readonly onTrackCarrier?: () => void
|
|
19
|
+
readonly trackingNumber: string
|
|
20
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { CheckCircle2Icon, ClockIcon, ExternalLinkIcon, MapPinIcon, TruckIcon } from 'lucide-react'
|
|
2
|
+
import { cn } from '../../../lib/utils'
|
|
3
|
+
import { Button } from '../../../components/button'
|
|
4
|
+
import { Badge } from '../../../components/badge'
|
|
5
|
+
import type { DeliveryTrackerProps } from './delivery-tracker-types'
|
|
6
|
+
|
|
7
|
+
export function DeliveryTracker({
|
|
8
|
+
carrierName = 'FedEx Express',
|
|
9
|
+
className,
|
|
10
|
+
deliveryAddress,
|
|
11
|
+
estimatedDelivery,
|
|
12
|
+
milestones,
|
|
13
|
+
onTrackCarrier,
|
|
14
|
+
orderId,
|
|
15
|
+
trackingNumber,
|
|
16
|
+
}: DeliveryTrackerProps) {
|
|
17
|
+
const currentMilestone =
|
|
18
|
+
milestones.find((m) => m.status === 'current') ?? milestones[milestones.length - 1]
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={cn(
|
|
23
|
+
'space-y-6 rounded-2xl border border-border/80 bg-card p-6 shadow-xs',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
{/* Header Info */}
|
|
28
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-border/60 pb-5">
|
|
29
|
+
<div>
|
|
30
|
+
<div className="flex items-center gap-2">
|
|
31
|
+
<span className="text-xs font-semibold text-muted-foreground">Order #{orderId}</span>
|
|
32
|
+
<Badge variant="secondary" className="text-[10px] font-bold">
|
|
33
|
+
{carrierName}
|
|
34
|
+
</Badge>
|
|
35
|
+
</div>
|
|
36
|
+
<h3 className="text-lg font-bold text-foreground mt-0.5">
|
|
37
|
+
Estimated Delivery: <span className="text-primary">{estimatedDelivery}</span>
|
|
38
|
+
</h3>
|
|
39
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
40
|
+
Tracking Number:{' '}
|
|
41
|
+
<span className="font-mono font-semibold text-foreground">{trackingNumber}</span>
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{onTrackCarrier && (
|
|
46
|
+
<Button
|
|
47
|
+
size="sm"
|
|
48
|
+
variant="outline"
|
|
49
|
+
className="gap-1.5 text-xs self-start sm:self-auto"
|
|
50
|
+
onClick={onTrackCarrier}
|
|
51
|
+
>
|
|
52
|
+
<span>Track on {carrierName}</span>
|
|
53
|
+
<ExternalLinkIcon className="size-3.5" />
|
|
54
|
+
</Button>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{/* Horizontal Milestone Tracker */}
|
|
59
|
+
<div className="py-2">
|
|
60
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
61
|
+
{milestones.map((step, idx) => {
|
|
62
|
+
const isCompleted = step.status === 'completed'
|
|
63
|
+
const isCurrent = step.status === 'current'
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div key={step.id} className="relative flex flex-col items-start space-y-2">
|
|
67
|
+
{/* Step indicator circle */}
|
|
68
|
+
<div className="flex items-center w-full">
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
'flex size-8 shrink-0 items-center justify-center rounded-full text-xs font-bold transition-all',
|
|
72
|
+
isCompleted && 'bg-emerald-600 text-white',
|
|
73
|
+
isCurrent &&
|
|
74
|
+
'bg-primary text-primary-foreground ring-4 ring-primary/20 animate-pulse',
|
|
75
|
+
!isCompleted && !isCurrent && 'bg-muted text-muted-foreground',
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{isCompleted ? (
|
|
79
|
+
<CheckCircle2Icon className="size-4" />
|
|
80
|
+
) : isCurrent ? (
|
|
81
|
+
<TruckIcon className="size-4" />
|
|
82
|
+
) : (
|
|
83
|
+
<span>{idx + 1}</span>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
{idx < milestones.length - 1 && (
|
|
87
|
+
<div
|
|
88
|
+
className={cn(
|
|
89
|
+
'h-1 w-full ml-2 rounded-full hidden sm:block',
|
|
90
|
+
isCompleted ? 'bg-emerald-600' : 'bg-border/60',
|
|
91
|
+
)}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="text-left space-y-0.5">
|
|
97
|
+
<h5
|
|
98
|
+
className={cn(
|
|
99
|
+
'text-xs font-bold',
|
|
100
|
+
isCurrent ? 'text-primary' : 'text-foreground',
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{step.title}
|
|
104
|
+
</h5>
|
|
105
|
+
{step.date && <p className="text-[11px] text-muted-foreground">{step.date}</p>}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
})}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Status Highlights & Destination */}
|
|
114
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 rounded-xl border border-border/60 bg-muted/20 p-4 text-xs">
|
|
115
|
+
<div className="space-y-1">
|
|
116
|
+
<div className="flex items-center gap-1.5 font-bold text-foreground">
|
|
117
|
+
<ClockIcon className="size-3.5 text-primary" />
|
|
118
|
+
<span>Latest Update</span>
|
|
119
|
+
</div>
|
|
120
|
+
<p className="text-muted-foreground">{currentMilestone?.description ?? 'In transit'}</p>
|
|
121
|
+
{currentMilestone?.location && (
|
|
122
|
+
<p className="text-[11px] text-muted-foreground/80 font-medium">
|
|
123
|
+
Location: {currentMilestone.location}
|
|
124
|
+
</p>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
{deliveryAddress && (
|
|
129
|
+
<div className="space-y-1 md:border-l md:border-border/60 md:pl-4">
|
|
130
|
+
<div className="flex items-center gap-1.5 font-bold text-foreground">
|
|
131
|
+
<MapPinIcon className="size-3.5 text-primary" />
|
|
132
|
+
<span>Destination Address</span>
|
|
133
|
+
</div>
|
|
134
|
+
<p className="text-muted-foreground">{deliveryAddress}</p>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Detailed Tracking Events List */}
|
|
140
|
+
<div className="space-y-3 pt-2">
|
|
141
|
+
<h5 className="text-xs font-bold text-foreground uppercase tracking-wider">
|
|
142
|
+
Tracking History
|
|
143
|
+
</h5>
|
|
144
|
+
<div className="space-y-3 border-l-2 border-border/80 pl-4 ml-1">
|
|
145
|
+
{milestones.map((m) => (
|
|
146
|
+
<div key={m.id} className="relative space-y-0.5 text-xs">
|
|
147
|
+
<div
|
|
148
|
+
className={cn(
|
|
149
|
+
'absolute -left-[21px] top-1 size-2 rounded-full',
|
|
150
|
+
m.status === 'completed'
|
|
151
|
+
? 'bg-emerald-600'
|
|
152
|
+
: m.status === 'current'
|
|
153
|
+
? 'bg-primary ring-2 ring-primary/30'
|
|
154
|
+
: 'bg-muted-foreground/40',
|
|
155
|
+
)}
|
|
156
|
+
/>
|
|
157
|
+
<div className="flex items-baseline justify-between gap-2">
|
|
158
|
+
<span className="font-semibold text-foreground">{m.title}</span>
|
|
159
|
+
<span className="text-[11px] text-muted-foreground">{m.time ?? m.date}</span>
|
|
160
|
+
</div>
|
|
161
|
+
{m.description && <p className="text-muted-foreground">{m.description}</p>}
|
|
162
|
+
{m.location && <p className="text-[10px] text-muted-foreground/70">{m.location}</p>}
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './cart'
|
|
2
|
+
export * from './categories'
|
|
3
|
+
export * from './checkout'
|
|
4
|
+
export * from './comparison'
|
|
5
|
+
export * from './coupon-wallet'
|
|
6
|
+
export * from './delivery-tracker'
|
|
7
|
+
export * from './payment-methods'
|
|
8
|
+
export * from './price-history'
|
|
9
|
+
export * from './reviews'
|
|
10
|
+
export * from './wishlist'
|
|
11
|
+
export * from '../product-card'
|
|
12
|
+
export * from '../pricing'
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
BanknoteIcon,
|
|
4
|
+
CreditCardIcon,
|
|
5
|
+
PlusIcon,
|
|
6
|
+
QrCodeIcon,
|
|
7
|
+
ShieldCheckIcon,
|
|
8
|
+
WalletIcon,
|
|
9
|
+
} from 'lucide-react'
|
|
10
|
+
import { cn } from '../../../lib/utils'
|
|
11
|
+
import { Button } from '../../../components/button'
|
|
12
|
+
import { Badge } from '../../../components/badge'
|
|
13
|
+
import type { PaymentMethodSelectorProps } from './payment-methods-types'
|
|
14
|
+
|
|
15
|
+
export function PaymentMethodSelector({
|
|
16
|
+
allowCOD = true,
|
|
17
|
+
className,
|
|
18
|
+
onAddNewCard,
|
|
19
|
+
onSelectMethod,
|
|
20
|
+
savedCards,
|
|
21
|
+
selectedMethod: initialMethod = 'card',
|
|
22
|
+
}: PaymentMethodSelectorProps) {
|
|
23
|
+
const [method, setMethod] = useState<'card' | 'upi' | 'wallet' | 'cod'>(initialMethod)
|
|
24
|
+
const [selectedCardId, setSelectedCardId] = useState<string>(
|
|
25
|
+
savedCards.find((c) => c.isDefault)?.id ?? savedCards[0]?.id ?? '',
|
|
26
|
+
)
|
|
27
|
+
const [cvv, setCvv] = useState('')
|
|
28
|
+
const [upiId, setUpiId] = useState('')
|
|
29
|
+
|
|
30
|
+
const handleMethodChange = (newMethod: 'card' | 'upi' | 'wallet' | 'cod') => {
|
|
31
|
+
setMethod(newMethod)
|
|
32
|
+
onSelectMethod?.(newMethod, { cardId: selectedCardId, upiId })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cn(
|
|
38
|
+
'space-y-6 rounded-2xl border border-border/80 bg-card p-6 shadow-xs',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<div className="flex items-center justify-between border-b border-border/60 pb-4">
|
|
43
|
+
<div>
|
|
44
|
+
<h3 className="text-base font-bold text-foreground">Payment Options</h3>
|
|
45
|
+
<p className="text-xs text-muted-foreground">
|
|
46
|
+
Select how you would like to complete your purchase.
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="flex items-center gap-1.5 text-xs text-emerald-600 font-semibold">
|
|
50
|
+
<ShieldCheckIcon className="size-4" />
|
|
51
|
+
<span>PCI-DSS Compliant</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{/* Main Method Selector Tabs */}
|
|
56
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
|
57
|
+
{[
|
|
58
|
+
{ id: 'card', label: 'Cards', icon: CreditCardIcon },
|
|
59
|
+
{ id: 'upi', label: 'UPI / QR', icon: QrCodeIcon },
|
|
60
|
+
{ id: 'wallet', label: 'Wallets', icon: WalletIcon },
|
|
61
|
+
...(allowCOD ? [{ id: 'cod', label: 'Cash on Delivery', icon: BanknoteIcon }] : []),
|
|
62
|
+
].map((m) => {
|
|
63
|
+
const Icon = m.icon
|
|
64
|
+
const isSelected = method === m.id
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<button
|
|
68
|
+
key={m.id}
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={() => handleMethodChange(m.id as 'card' | 'upi' | 'wallet' | 'cod')}
|
|
71
|
+
className={cn(
|
|
72
|
+
'flex flex-col items-center justify-center p-3 rounded-xl border text-xs font-bold transition-all gap-2',
|
|
73
|
+
isSelected
|
|
74
|
+
? 'border-primary bg-primary/10 text-primary ring-1 ring-primary shadow-xs'
|
|
75
|
+
: 'border-border/80 bg-muted/20 text-muted-foreground hover:border-border hover:text-foreground',
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
<Icon className="size-5" />
|
|
79
|
+
<span>{m.label}</span>
|
|
80
|
+
</button>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* 1. Credit / Debit Cards Mode */}
|
|
86
|
+
{method === 'card' && (
|
|
87
|
+
<div className="space-y-4 pt-2">
|
|
88
|
+
<div className="flex items-center justify-between">
|
|
89
|
+
<span className="text-xs font-bold text-foreground">Your Saved Cards</span>
|
|
90
|
+
{onAddNewCard && (
|
|
91
|
+
<Button
|
|
92
|
+
size="sm"
|
|
93
|
+
variant="ghost"
|
|
94
|
+
className="h-7 text-xs gap-1"
|
|
95
|
+
onClick={onAddNewCard}
|
|
96
|
+
>
|
|
97
|
+
<PlusIcon className="size-3.5" />
|
|
98
|
+
<span>Add Card</span>
|
|
99
|
+
</Button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div className="space-y-2.5">
|
|
104
|
+
{savedCards.map((card) => {
|
|
105
|
+
const isSelected = selectedCardId === card.id
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
key={card.id}
|
|
110
|
+
onClick={() => {
|
|
111
|
+
setSelectedCardId(card.id)
|
|
112
|
+
onSelectMethod?.('card', { cardId: card.id })
|
|
113
|
+
}}
|
|
114
|
+
className={cn(
|
|
115
|
+
'flex flex-col sm:flex-row sm:items-center justify-between gap-3 p-3.5 rounded-xl border cursor-pointer transition-all text-xs',
|
|
116
|
+
isSelected
|
|
117
|
+
? 'border-primary bg-primary/5 ring-1 ring-primary'
|
|
118
|
+
: 'border-border/80 bg-muted/10 hover:border-border',
|
|
119
|
+
)}
|
|
120
|
+
>
|
|
121
|
+
<div className="flex items-center gap-3">
|
|
122
|
+
<div className="flex size-9 items-center justify-center rounded-lg bg-background border border-border/80 font-mono font-bold uppercase text-[10px]">
|
|
123
|
+
{card.brand}
|
|
124
|
+
</div>
|
|
125
|
+
<div>
|
|
126
|
+
<div className="flex items-center gap-2 font-bold text-foreground">
|
|
127
|
+
<span>•••• •••• •••• {card.last4}</span>
|
|
128
|
+
{card.isDefault && (
|
|
129
|
+
<Badge variant="secondary" className="text-[10px] py-0 font-semibold">
|
|
130
|
+
Default
|
|
131
|
+
</Badge>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
<div className="text-[11px] text-muted-foreground mt-0.5">
|
|
135
|
+
{card.cardholderName} • Expires {card.expiryMonth}/{card.expiryYear}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{isSelected && (
|
|
141
|
+
<div
|
|
142
|
+
className="flex items-center gap-2 self-end sm:self-center"
|
|
143
|
+
onClick={(e) => e.stopPropagation()}
|
|
144
|
+
>
|
|
145
|
+
<span className="text-[11px] font-semibold text-foreground">CVV:</span>
|
|
146
|
+
<input
|
|
147
|
+
type="password"
|
|
148
|
+
maxLength={4}
|
|
149
|
+
placeholder="•••"
|
|
150
|
+
value={cvv}
|
|
151
|
+
onChange={(e) => setCvv(e.target.value)}
|
|
152
|
+
className="w-14 rounded border border-input bg-background px-2 py-1 text-center font-mono text-xs focus:outline-none focus:ring-1 focus:ring-primary"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
})}
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* 2. UPI / QR Mode */}
|
|
164
|
+
{method === 'upi' && (
|
|
165
|
+
<div className="space-y-3 rounded-xl border border-border/80 bg-muted/20 p-4 text-xs">
|
|
166
|
+
<div className="flex items-center gap-2 font-bold text-foreground">
|
|
167
|
+
<QrCodeIcon className="size-4 text-primary" />
|
|
168
|
+
<span>Instant UPI Payment</span>
|
|
169
|
+
</div>
|
|
170
|
+
<p className="text-muted-foreground">
|
|
171
|
+
Pay directly from Google Pay, PhonePe, Paytm, or BHIM.
|
|
172
|
+
</p>
|
|
173
|
+
<div className="flex gap-2 pt-1">
|
|
174
|
+
<input
|
|
175
|
+
type="text"
|
|
176
|
+
placeholder="e.g. mobileNumber@upi or user@okhdfcbank"
|
|
177
|
+
value={upiId}
|
|
178
|
+
onChange={(e) => setUpiId(e.target.value)}
|
|
179
|
+
className="flex-1 rounded-lg border border-input bg-background px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
|
180
|
+
/>
|
|
181
|
+
<Button
|
|
182
|
+
size="sm"
|
|
183
|
+
disabled={!upiId.includes('@')}
|
|
184
|
+
onClick={() => onSelectMethod?.('upi', { upiId })}
|
|
185
|
+
>
|
|
186
|
+
Verify UPI ID
|
|
187
|
+
</Button>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{/* 3. Wallets Mode */}
|
|
193
|
+
{method === 'wallet' && (
|
|
194
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 pt-1">
|
|
195
|
+
{['Apple Pay', 'Google Pay', 'PayPal'].map((wallet) => (
|
|
196
|
+
<button
|
|
197
|
+
key={wallet}
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={() => onSelectMethod?.('wallet')}
|
|
200
|
+
className="flex items-center justify-center gap-2 p-4 rounded-xl border border-border/80 bg-muted/20 hover:border-primary hover:bg-primary/5 transition-all text-xs font-bold text-foreground"
|
|
201
|
+
>
|
|
202
|
+
<WalletIcon className="size-4 text-primary" />
|
|
203
|
+
<span>{wallet}</span>
|
|
204
|
+
</button>
|
|
205
|
+
))}
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* 4. Cash on Delivery Mode */}
|
|
210
|
+
{method === 'cod' && (
|
|
211
|
+
<div className="rounded-xl border border-border/80 bg-muted/20 p-4 text-xs space-y-2">
|
|
212
|
+
<div className="flex items-center gap-2 font-bold text-foreground">
|
|
213
|
+
<BanknoteIcon className="size-4 text-primary" />
|
|
214
|
+
<span>Pay with Cash Upon Delivery</span>
|
|
215
|
+
</div>
|
|
216
|
+
<p className="text-muted-foreground">
|
|
217
|
+
You can pay the courier agent via cash or mobile QR scan at your doorstep. Please keep
|
|
218
|
+
exact change ready.
|
|
219
|
+
</p>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type SavedCard = {
|
|
2
|
+
readonly brand: 'visa' | 'mastercard' | 'amex' | 'discover'
|
|
3
|
+
readonly cardholderName: string
|
|
4
|
+
readonly expiryMonth: string
|
|
5
|
+
readonly expiryYear: string
|
|
6
|
+
readonly id: string
|
|
7
|
+
readonly isDefault?: boolean
|
|
8
|
+
readonly last4: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type PaymentMethodSelectorProps = {
|
|
12
|
+
readonly allowCOD?: boolean
|
|
13
|
+
readonly className?: string
|
|
14
|
+
readonly onAddNewCard?: () => void
|
|
15
|
+
readonly onSelectMethod?: (
|
|
16
|
+
method: 'card' | 'upi' | 'wallet' | 'cod',
|
|
17
|
+
details?: { cardId?: string; upiId?: string },
|
|
18
|
+
) => void
|
|
19
|
+
readonly savedCards: readonly SavedCard[]
|
|
20
|
+
readonly selectedMethod?: 'card' | 'upi' | 'wallet' | 'cod'
|
|
21
|
+
}
|