@pradip1995/theme-impulse 1.1.4

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 (47) hide show
  1. package/README.md +31 -0
  2. package/assets/hero-desktop.svg +10 -0
  3. package/assets/hero-mobile.svg +9 -0
  4. package/assets/logo.svg +3 -0
  5. package/package.json +60 -0
  6. package/src/blocks/home/Features/index.tsx +37 -0
  7. package/src/blocks/home/Hero/index.tsx +102 -0
  8. package/src/blocks/home/LovedByMoms/index.tsx +59 -0
  9. package/src/blocks/home/NewArrivals/index.tsx +57 -0
  10. package/src/blocks/home/ShopByAge/index.tsx +82 -0
  11. package/src/blocks/home/ShopByCategory/index.tsx +92 -0
  12. package/src/blocks/home/Testimonials/index.tsx +130 -0
  13. package/src/blocks/home/WhyChooseUs/index.tsx +46 -0
  14. package/src/layouts/MainLayoutShell.tsx +14 -0
  15. package/src/primitives/Button.tsx +31 -0
  16. package/src/primitives/Card.tsx +32 -0
  17. package/src/primitives/index.ts +2 -0
  18. package/src/slots/account/ForgotPassword/index.tsx +1 -0
  19. package/src/slots/account/Login/index.tsx +1 -0
  20. package/src/slots/account/LoginTemplate/index.tsx +44 -0
  21. package/src/slots/account/Register/index.tsx +1 -0
  22. package/src/slots/cart/CartItem/index.tsx +11 -0
  23. package/src/slots/cart/CartSummary/index.tsx +13 -0
  24. package/src/slots/checkout/CheckoutForm/index.tsx +1 -0
  25. package/src/slots/checkout/CheckoutSummary/index.tsx +1 -0
  26. package/src/slots/layout/Footer/index.tsx +103 -0
  27. package/src/slots/layout/Nav/index.tsx +97 -0
  28. package/src/slots/layout/PromoBar/index.tsx +19 -0
  29. package/src/slots/layout/PromoBar/promo-bar-content.tsx +174 -0
  30. package/src/slots/order/OrderDetails/index.tsx +12 -0
  31. package/src/slots/product/ProductActions/ProductCTASection.tsx +191 -0
  32. package/src/slots/product/ProductActions/ProductDetailsSection.tsx +137 -0
  33. package/src/slots/product/ProductActions/ProductFeaturePanel.tsx +245 -0
  34. package/src/slots/product/ProductActions/ProductHighlightsSection.tsx +99 -0
  35. package/src/slots/product/ProductActions/ProductOptionsSection.tsx +234 -0
  36. package/src/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
  37. package/src/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
  38. package/src/slots/product/ProductActions/index.tsx +161 -0
  39. package/src/slots/product/ProductCard/index.tsx +132 -0
  40. package/src/slots/product/ProductInfo/index.tsx +40 -0
  41. package/src/templates/StorePage/index.tsx +154 -0
  42. package/src/tokens/colors.js +16 -0
  43. package/src/tokens/colors.ts +21 -0
  44. package/src/tokens/fonts.ts +13 -0
  45. package/src/tokens/index.ts +3 -0
  46. package/src/tokens/spacing.ts +9 -0
  47. package/src/tokens/theme.css +89 -0
@@ -0,0 +1,46 @@
1
+ import Image from "next/image"
2
+ import type { WhyChooseUsBlockData } from "@core/types/home"
3
+
4
+ const WhyChooseUs = ({ title, features }: WhyChooseUsBlockData) => {
5
+ if (features.length === 0) {
6
+ return null
7
+ }
8
+
9
+ return (
10
+ <section className="w-full py-10 md:py-14 bg-page-bg">
11
+ <div
12
+ className="mx-auto px-4 sm:px-6 lg:px-8"
13
+ style={{ maxWidth: "var(--container-max)" }}
14
+ >
15
+ <h2 className="font-display text-2xl md:text-3xl text-heading text-center mb-8 md:mb-12">
16
+ {title}
17
+ </h2>
18
+
19
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-10">
20
+ {features.map((feature, index) => (
21
+ <div
22
+ key={`${feature.name}-${index}`}
23
+ className="flex flex-col items-center text-center"
24
+ >
25
+ <div className="relative w-14 h-14 md:w-16 md:h-16 mb-3 md:mb-4">
26
+ <Image
27
+ src={feature.icon}
28
+ alt={feature.name}
29
+ width={64}
30
+ height={64}
31
+ className="w-full h-full object-contain opacity-80"
32
+ unoptimized
33
+ />
34
+ </div>
35
+ <h3 className="text-[11px] md:text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-heading leading-snug">
36
+ {feature.name}
37
+ </h3>
38
+ </div>
39
+ ))}
40
+ </div>
41
+ </div>
42
+ </section>
43
+ )
44
+ }
45
+
46
+ export default WhyChooseUs
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from "react"
2
+ import { colorClasses } from "@theme/tokens"
3
+
4
+ type MainLayoutShellProps = {
5
+ children: ReactNode
6
+ }
7
+
8
+ export function MainLayoutShell({ children }: MainLayoutShellProps) {
9
+ return (
10
+ <main className={`${colorClasses.pageBg} min-h-screen w-full`}>
11
+ {children}
12
+ </main>
13
+ )
14
+ }
@@ -0,0 +1,31 @@
1
+ import { Button as MedusaButton } from "@medusajs/ui"
2
+ import { clx } from "@medusajs/ui"
3
+ import type { ComponentProps } from "react"
4
+
5
+ type ButtonVariant = "primary" | "secondary" | "ghost"
6
+
7
+ type ButtonProps = Omit<ComponentProps<typeof MedusaButton>, "variant"> & {
8
+ variant?: ButtonVariant
9
+ }
10
+
11
+ const variantClasses: Record<ButtonVariant, string> = {
12
+ primary:
13
+ "bg-brand-accent hover:bg-brand-accent-hover text-white rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs",
14
+ secondary:
15
+ "bg-transparent border border-brand-accent text-brand-accent rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs hover:bg-brand-accent hover:text-white",
16
+ ghost:
17
+ "bg-transparent text-heading hover:opacity-70 rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs",
18
+ }
19
+
20
+ export function Button({
21
+ variant = "primary",
22
+ className,
23
+ children,
24
+ ...props
25
+ }: ButtonProps) {
26
+ return (
27
+ <MedusaButton className={clx(variantClasses[variant], className)} {...props}>
28
+ {children}
29
+ </MedusaButton>
30
+ )
31
+ }
@@ -0,0 +1,32 @@
1
+ import { clx } from "@medusajs/ui"
2
+ import type { HTMLAttributes } from "react"
3
+
4
+ type CardProps = HTMLAttributes<HTMLDivElement> & {
5
+ padding?: "sm" | "md" | "lg"
6
+ }
7
+
8
+ const paddingClasses = {
9
+ sm: "p-4",
10
+ md: "p-6",
11
+ lg: "p-8",
12
+ }
13
+
14
+ export function Card({
15
+ padding = "md",
16
+ className,
17
+ children,
18
+ ...props
19
+ }: CardProps) {
20
+ return (
21
+ <div
22
+ className={clx(
23
+ "rounded-xl bg-white border border-gray-100 shadow-sm",
24
+ paddingClasses[padding],
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ </div>
31
+ )
32
+ }
@@ -0,0 +1,2 @@
1
+ export { Button } from "./Button"
2
+ export { Card } from "./Card"
@@ -0,0 +1 @@
1
+ export { default } from "@pradip1995/commerce-auth/components/forgot-password"
@@ -0,0 +1 @@
1
+ export { default } from "@pradip1995/commerce-auth/components/login"
@@ -0,0 +1,44 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+
5
+ import { LOGIN_VIEW } from "@core/types/account"
6
+ import type { AccountPageData } from "@core/types/account"
7
+ import Login from "@theme/slots/account/Login"
8
+ import Register from "@theme/slots/account/Register"
9
+ import ForgotPassword from "@theme/slots/account/ForgotPassword"
10
+ import { colorClasses } from "@theme/tokens"
11
+
12
+ type LoginTemplateProps = Pick<AccountPageData, "countryCode">
13
+
14
+ export default function LoginTemplate(_props: LoginTemplateProps) {
15
+ const [currentView, setCurrentView] = useState(LOGIN_VIEW.SIGN_IN)
16
+
17
+ const renderView = () => {
18
+ switch (currentView) {
19
+ case LOGIN_VIEW.REGISTER:
20
+ return <Register setCurrentView={setCurrentView} />
21
+ case LOGIN_VIEW.FORGOT_PASSWORD:
22
+ return <ForgotPassword setCurrentView={setCurrentView} />
23
+ default:
24
+ return <Login setCurrentView={setCurrentView} />
25
+ }
26
+ }
27
+
28
+ return (
29
+ <div className={`min-h-screen w-full flex flex-col ${colorClasses.pageBg}`}>
30
+ <div className="flex-1 flex flex-col items-center justify-start px-2 min-[340px]:px-3 min-[550px]:px-4 sm:px-6 md:px-8 pt-2 min-[340px]:pt-3 min-[550px]:pt-4 sm:pt-4 md:pt-6 pb-6 min-[340px]:pb-8 min-[550px]:pb-8 sm:pb-8 md:pb-8">
31
+ <h1 className="text-2xl min-[340px]:text-3xl min-[550px]:text-3xl sm:text-4xl md:text-4xl font-bold text-heading mb-4 min-[340px]:mb-6 min-[550px]:mb-8 sm:mb-8 md:mb-8 text-center">
32
+ My Account
33
+ </h1>
34
+
35
+ <div
36
+ className={`w-full ${colorClasses.pageBg} rounded-2xl shadow-lg p-4 min-[340px]:p-5 min-[550px]:p-6 sm:p-7 md:p-8`}
37
+ style={{ maxWidth: "560px" }}
38
+ >
39
+ {renderView()}
40
+ </div>
41
+ </div>
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@pradip1995/commerce-auth/components/register"
@@ -0,0 +1,11 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import CartItemCard from "@modules/cart/components/cart-item-card"
3
+
4
+ export type CartItemSlotProps = {
5
+ item: HttpTypes.StoreCartLineItem
6
+ currencyCode: string
7
+ }
8
+
9
+ export default function CartItem({ item, currencyCode }: CartItemSlotProps) {
10
+ return <CartItemCard item={item} currencyCode={currencyCode} />
11
+ }
@@ -0,0 +1,13 @@
1
+ import { HttpTypes } from "@medusajs/types"
2
+ import Summary from "@modules/cart/templates/summary"
3
+
4
+ export type CartSummarySlotProps = {
5
+ cart: HttpTypes.StoreCart & {
6
+ promotions: HttpTypes.StorePromotion[]
7
+ }
8
+ customer: HttpTypes.StoreCustomer | null
9
+ }
10
+
11
+ export default function CartSummary({ cart, customer }: CartSummarySlotProps) {
12
+ return <Summary cart={cart} customer={customer} />
13
+ }
@@ -0,0 +1 @@
1
+ export { default } from "@modules/checkout/templates/checkout-form"
@@ -0,0 +1 @@
1
+ export { default } from "@modules/checkout/templates/checkout-summary"
@@ -0,0 +1,103 @@
1
+ import type { FooterSlotData } from "@core/types/layout"
2
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
3
+ import FooterNewsletter from "@modules/layout/components/footer-newsletter"
4
+
5
+ const COMPANY_LINKS = [
6
+ { href: "/", label: "Home" },
7
+ { href: "/store", label: "Shop" },
8
+ { href: "/contact", label: "Contact" },
9
+ { href: "/help", label: "Help" },
10
+ { href: "/privacy-policy", label: "Privacy Policy" },
11
+ { href: "/terms-of-use", label: "Terms of Use" },
12
+ ]
13
+
14
+ export default function Footer({ categories }: FooterSlotData) {
15
+ return (
16
+ <footer className="bg-brand-footer text-[var(--color-text-inverse)] mt-auto">
17
+ <div
18
+ className="mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16"
19
+ style={{ maxWidth: "var(--container-max)" }}
20
+ >
21
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 lg:gap-12">
22
+ <div className="sm:col-span-2 lg:col-span-1">
23
+ <p className="font-display text-xl text-white mb-4">
24
+ Your Store
25
+ </p>
26
+ <p className="text-sm text-white/65 leading-relaxed max-w-xs">
27
+ Flexible, fashionable design with powerful promotions. Shop the
28
+ latest collections with fast delivery and easy returns.
29
+ </p>
30
+ </div>
31
+
32
+ <div>
33
+ <h3 className="text-[10px] font-semibold uppercase tracking-[var(--letter-spacing-nav)] mb-4 text-white/90">
34
+ Shop
35
+ </h3>
36
+ <ul className="space-y-2.5">
37
+ {(categories?.slice(0, 6) ?? []).map((cat) => (
38
+ <li key={cat.id}>
39
+ <LocalizedClientLink
40
+ href={`/store?category=${cat.id}`}
41
+ className="text-sm text-white/65 hover:text-white transition-colors"
42
+ >
43
+ {cat.name}
44
+ </LocalizedClientLink>
45
+ </li>
46
+ ))}
47
+ {!categories?.length && (
48
+ <li>
49
+ <LocalizedClientLink
50
+ href="/store"
51
+ className="text-sm text-white/65 hover:text-white transition-colors"
52
+ >
53
+ All products
54
+ </LocalizedClientLink>
55
+ </li>
56
+ )}
57
+ </ul>
58
+ </div>
59
+
60
+ <div>
61
+ <h3 className="text-[10px] font-semibold uppercase tracking-[var(--letter-spacing-nav)] mb-4 text-white/90">
62
+ Company
63
+ </h3>
64
+ <ul className="space-y-2.5">
65
+ {COMPANY_LINKS.map((link) => (
66
+ <li key={link.href}>
67
+ <LocalizedClientLink
68
+ href={link.href}
69
+ className="text-sm text-white/65 hover:text-white transition-colors"
70
+ >
71
+ {link.label}
72
+ </LocalizedClientLink>
73
+ </li>
74
+ ))}
75
+ </ul>
76
+ </div>
77
+
78
+ <div>
79
+ <h3 className="text-[10px] font-semibold uppercase tracking-[var(--letter-spacing-nav)] mb-4 text-white/90">
80
+ Newsletter
81
+ </h3>
82
+ <p className="text-sm text-white/65 mb-4">
83
+ Subscribe for exclusive offers and new arrivals.
84
+ </p>
85
+ <FooterNewsletter />
86
+ </div>
87
+ </div>
88
+
89
+ <div className="mt-12 pt-8 border-t border-white/10 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-white/45">
90
+ <p>© {new Date().getFullYear()} Your Store. All rights reserved.</p>
91
+ <div className="flex gap-6">
92
+ <LocalizedClientLink href="/privacy-policy" className="hover:text-white">
93
+ Privacy
94
+ </LocalizedClientLink>
95
+ <LocalizedClientLink href="/terms-of-use" className="hover:text-white">
96
+ Terms
97
+ </LocalizedClientLink>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </footer>
102
+ )
103
+ }
@@ -0,0 +1,97 @@
1
+ import { Suspense } from "react"
2
+ import { HttpTypes } from "@medusajs/types"
3
+ import type { NavSlotData } from "@core/types/layout"
4
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
5
+ import CartButton from "@modules/layout/components/cart-button"
6
+ import DynamicLogo from "@modules/layout/components/dynamic-logo"
7
+ import MobileMenu from "@modules/layout/components/mobile-menu"
8
+ import SearchPanel from "@modules/layout/components/search-panel"
9
+ import DesktopSearch, {
10
+ DesktopSearchIcon,
11
+ } from "@modules/layout/components/desktop-search"
12
+ import AccountDropdown from "@modules/layout/components/account-dropdown"
13
+
14
+ const NAV_LINKS = [
15
+ { href: "/", label: "Home" },
16
+ { href: "/store", label: "Shop" },
17
+ { href: "/contact", label: "Contact" },
18
+ { href: "/help", label: "Help" },
19
+ ]
20
+
21
+ export default function Nav({ currentLocale, customer }: NavSlotData) {
22
+ return (
23
+ <div className="sticky top-0 inset-x-0 z-50">
24
+ <header className="bg-[var(--color-header-bg)] border-b border-[var(--color-header-border)]">
25
+ <div
26
+ className="mx-auto px-4 sm:px-6 flex items-center justify-between gap-4"
27
+ style={{ maxWidth: "var(--container-max)", height: "var(--header-height)" }}
28
+ >
29
+ <div className="flex items-center gap-3 lg:hidden flex-shrink-0">
30
+ <MobileMenu
31
+ customer={customer}
32
+ countryCode={currentLocale || "in"}
33
+ />
34
+ </div>
35
+
36
+ <div className="flex-shrink-0 lg:flex-1 lg:basis-0">
37
+ <DynamicLogo />
38
+ </div>
39
+
40
+ <nav
41
+ className="hidden lg:flex items-center justify-center gap-8 xl:gap-10 flex-1"
42
+ aria-label="Main"
43
+ >
44
+ {NAV_LINKS.map((link) => (
45
+ <LocalizedClientLink
46
+ key={link.href}
47
+ href={link.href}
48
+ className="text-[11px] xl:text-xs font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-heading hover:text-brand-accent transition-colors"
49
+ >
50
+ {link.label}
51
+ </LocalizedClientLink>
52
+ ))}
53
+ </nav>
54
+
55
+ <DesktopSearch countryCode={currentLocale || "in"} hideNavLinks>
56
+ <div className="flex items-center justify-end gap-3 sm:gap-5 flex-shrink-0 lg:flex-1 lg:basis-0">
57
+ <div className="hidden lg:block">
58
+ <DesktopSearchIcon />
59
+ </div>
60
+ <div className="lg:hidden">
61
+ <SearchPanel countryCode={currentLocale || "in"} />
62
+ </div>
63
+
64
+ {!customer ? (
65
+ <LocalizedClientLink
66
+ href="/account"
67
+ className="hidden sm:inline-flex text-[11px] font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-heading hover:opacity-70 transition-opacity"
68
+ data-testid="nav-login-link"
69
+ >
70
+ Account
71
+ </LocalizedClientLink>
72
+ ) : (
73
+ <AccountDropdown customer={customer as HttpTypes.StoreCustomer} />
74
+ )}
75
+
76
+ <Suspense
77
+ fallback={
78
+ <LocalizedClientLink
79
+ href="/cart"
80
+ className="text-heading hover:opacity-70"
81
+ data-testid="nav-cart-link"
82
+ >
83
+ <span className="text-[11px] font-semibold uppercase tracking-[var(--letter-spacing-nav)]">
84
+ Bag
85
+ </span>
86
+ </LocalizedClientLink>
87
+ }
88
+ >
89
+ <CartButton />
90
+ </Suspense>
91
+ </div>
92
+ </DesktopSearch>
93
+ </div>
94
+ </header>
95
+ </div>
96
+ )
97
+ }
@@ -0,0 +1,19 @@
1
+ import type { PromoBarSlotData } from "@core/types/layout"
2
+ import PromoBarContent from "./promo-bar-content"
3
+
4
+ export default function PromoBar({
5
+ text,
6
+ code,
7
+ value,
8
+ active,
9
+ }: PromoBarSlotData) {
10
+ if (!active || !text) {
11
+ return null
12
+ }
13
+
14
+ return (
15
+ <div className="relative bg-brand-accent text-[var(--color-text-inverse)] text-center py-2.5 px-4 text-xs sm:text-sm font-medium tracking-[var(--letter-spacing-nav)] uppercase">
16
+ <PromoBarContent text={text} code={code} value={value} />
17
+ </div>
18
+ )
19
+ }
@@ -0,0 +1,174 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import Link from "next/link"
5
+ import { Copy, Check } from "lucide-react"
6
+
7
+ export default function PromoBarContent({ text, code: explicitCode, value: explicitValue }: { text: string; code: string | null; value: string | null }) {
8
+ const [copied, setCopied] = useState(false)
9
+
10
+ // Regex for code and value
11
+ const codeRegex = /\b[A-Z0-9]{4,}\b/;
12
+ const valueRegex = /(\d+%\s?(OFF)?)|([₹$]\d+)|(\d+\s?(Rs|₹|%|OFF))/gi;
13
+
14
+ let currentText = text;
15
+ let codeToCopy = explicitCode || "";
16
+
17
+ // Find code part
18
+ let codeMatch = explicitCode && currentText.includes(explicitCode)
19
+ ? { text: explicitCode, index: currentText.indexOf(explicitCode) }
20
+ : null;
21
+
22
+ if (!codeMatch) {
23
+ const regMatch = currentText.match(codeRegex);
24
+ if (regMatch) {
25
+ codeMatch = { text: regMatch[0], index: regMatch.index! };
26
+ if (!explicitCode) codeToCopy = regMatch[0];
27
+ }
28
+ }
29
+
30
+ // Find value part
31
+ let valueMatch = explicitValue && currentText.includes(explicitValue)
32
+ ? { text: explicitValue, index: currentText.indexOf(explicitValue) }
33
+ : null;
34
+
35
+ if (!valueMatch) {
36
+ const regMatch = Array.from(currentText.matchAll(valueRegex));
37
+ if (regMatch.length > 0) {
38
+ const firstValue = regMatch[0];
39
+ valueMatch = { text: firstValue[0], index: firstValue.index! };
40
+ }
41
+ }
42
+
43
+ // Handle priority: if explicitCode/explicitValue exist but not found in text,
44
+ // we replace the detected ones or append if none detected?
45
+ // User said "replace" essentially by saying priority.
46
+
47
+ // To render precisely, we need to collect all segments.
48
+ const segments: Array<{ text: string; type: 'text' | 'code' | 'value' }> = [];
49
+ let lastIndex = 0;
50
+
51
+ const matches = [
52
+ ...(codeMatch ? [{ ...codeMatch, type: 'code' as const }] : []),
53
+ ...(valueMatch ? [{ ...valueMatch, type: 'value' as const }] : [])
54
+ ].sort((a, b) => a.index - b.index);
55
+
56
+ // Avoid overlaps
57
+ let filteredMatches: typeof matches = [];
58
+ let currentPos = 0;
59
+ for (const match of matches) {
60
+ if (match.index >= currentPos) {
61
+ filteredMatches.push(match);
62
+ currentPos = match.index + match.text.length;
63
+ }
64
+ }
65
+
66
+ filteredMatches.forEach(match => {
67
+ if (match.index > lastIndex) {
68
+ segments.push({ text: currentText.substring(lastIndex, match.index), type: 'text' });
69
+ }
70
+
71
+ // Use explicit values if they exist, even if the text match was different (Priority logic)
72
+ const displayText = match.type === 'code' ? (explicitCode || match.text) : (explicitValue || match.text);
73
+ segments.push({ text: displayText, type: match.type });
74
+ lastIndex = match.index + match.text.length;
75
+ });
76
+
77
+ if (lastIndex < currentText.length) {
78
+ segments.push({ text: currentText.substring(lastIndex), type: 'text' });
79
+ }
80
+
81
+ // Fallback for cases where no match was found but explicit values exist:
82
+ // (Optional: we could append them, but usually text should contain them)
83
+
84
+ const handleCopy = () => {
85
+ navigator.clipboard.writeText(codeToCopy || text)
86
+ setCopied(true)
87
+ setTimeout(() => setCopied(false), 2000)
88
+ }
89
+
90
+ return (
91
+ <div className="max-w-[1440px] mx-auto w-full relative z-10 flex flex-row items-center justify-start lg:justify-center gap-x-4 text-center font-medium overflow-hidden whitespace-nowrap py-0.5">
92
+ {/* Visual Feedback Overlay for Copy Success - Mobile Only */}
93
+ {copied && (
94
+ <div className="absolute inset-0 z-50 flex sm:hidden items-center justify-center bg-promo-gradient animate-in fade-in zoom-in duration-200">
95
+ <span className="text-yellow-300 text-[11px] sm:text-[13px] font-extrabold tracking-wider uppercase flex items-center gap-2 drop-shadow-md">
96
+ PROMOCODE COPIED! APPLY AT CHECKOUT 🎁
97
+ </span>
98
+ </div>
99
+ )}
100
+
101
+ <div className="flex items-center lg:animate-none animate-marquee whitespace-nowrap min-w-full lg:min-w-0">
102
+ <button
103
+ onClick={handleCopy}
104
+ className="group/btn flex items-center justify-center gap-1.5 focus:outline-none flex-shrink-0"
105
+ title={codeToCopy ? `Click to copy promo code: ${codeToCopy}` : "Click to copy"}
106
+ >
107
+ {/* Main Content Segment */}
108
+ <div className="flex items-center gap-1.5 px-2 sm:px-0">
109
+ <span className="text-[11px] sm:text-[12.5px] font-bold tracking-[0.05em] uppercase drop-shadow-sm group-hover/btn:text-yellow-100 transition-colors flex items-center">
110
+ {segments.length > 0 ? segments.map((s, i) => (
111
+ s.type === 'text' ? (
112
+ <span key={i}>{s.text}</span>
113
+ ) : (
114
+ <span key={i} className="text-yellow-300 text-[12px] sm:text-[13.5px] mx-1 font-extrabold shadow-sm group-hover/btn:text-yellow-200 transition-colors drop-shadow-md">
115
+ "{s.text}"
116
+ </span>
117
+ )
118
+ )) : text}
119
+ </span>
120
+ <span className="bg-white/20 p-1 rounded-md group-hover/btn:bg-white/30 transition-colors flex items-center justify-center flex-shrink-0 ml-1">
121
+ {copied ? (
122
+ <Check size={13} className="text-green-300" strokeWidth={3} />
123
+ ) : (
124
+ <Copy size={13} className="text-white/80 group-hover/btn:text-white" strokeWidth={2.5} />
125
+ )}
126
+ </span>
127
+ </div>
128
+ </button>
129
+
130
+ <div className="mx-4 h-3 w-px bg-white/30 flex-shrink-0" />
131
+
132
+ <Link
133
+ href="/store"
134
+ className="text-[11px] sm:text-[12.5px] font-bold tracking-[0.02em] uppercase text-yellow-300 hover:text-white underline underline-offset-4 decoration-white/40 hover:decoration-white transition-all flex items-center group/link flex-shrink-0 mr-8 sm:mr-0"
135
+ >
136
+ SHOP NOW
137
+ <svg
138
+ className="ml-1 w-3 h-3 transform group-hover/link:translate-x-1 transition-transform"
139
+ fill="none"
140
+ viewBox="0 0 24 24"
141
+ stroke="currentColor"
142
+ >
143
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 5l7 7-7 7" />
144
+ </svg>
145
+ </Link>
146
+
147
+ {/* Duplicated Content for Seamless Loop on Mobile and Tablet */}
148
+ <div className="flex lg:hidden items-center gap-x-4">
149
+ <div className="mx-4 h-3 w-px bg-white/30 flex-shrink-0" />
150
+ <div className="flex items-center gap-1.5 px-2">
151
+ <span className="text-[11px] font-bold tracking-[0.05em] uppercase drop-shadow-sm flex items-center">
152
+ {segments.length > 0 ? segments.map((s, i) => (
153
+ s.type === 'text' ? (
154
+ <span key={i}>{s.text}</span>
155
+ ) : (
156
+ <span key={i} className="text-yellow-300 text-[12px] mx-1 font-extrabold">
157
+ "{s.text}"
158
+ </span>
159
+ )
160
+ )) : text}
161
+ </span>
162
+ <span className="bg-white/20 p-1 rounded-md flex items-center justify-center flex-shrink-0 ml-1">
163
+ <Copy size={13} className="text-white/80" strokeWidth={2.5} />
164
+ </span>
165
+ </div>
166
+ <div className="mx-4 h-3 w-px bg-white/30 flex-shrink-0" />
167
+ <span className="text-[11px] font-bold tracking-[0.02em] uppercase text-yellow-300 flex items-center mr-8">
168
+ SHOP NOW
169
+ </span>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ )
174
+ }
@@ -0,0 +1,12 @@
1
+ "use client"
2
+
3
+ import OrderDetailsTemplate from "@modules/order/templates/order-details-template"
4
+ import type { HttpTypes } from "@medusajs/types"
5
+
6
+ type OrderDetailsProps = {
7
+ order: HttpTypes.StoreOrder
8
+ }
9
+
10
+ export default function OrderDetails({ order }: OrderDetailsProps) {
11
+ return <OrderDetailsTemplate order={order} />
12
+ }