@luxfi/ui 5.3.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.
Files changed (187) hide show
  1. package/commerce/ui/conf.ts +13 -0
  2. package/commerce/ui/context.tsx +123 -0
  3. package/commerce/ui/store.ts +290 -0
  4. package/components/access-code-input.tsx +71 -0
  5. package/components/auth/auth-listener.tsx +29 -0
  6. package/components/auth/auth-token/clear-auth-token.tsx +12 -0
  7. package/components/auth/auth-token/set-auth-token.tsx +16 -0
  8. package/components/auth/common-auth-domains.ts +16 -0
  9. package/components/auth/login-panel.tsx +107 -0
  10. package/components/back-button.tsx +49 -0
  11. package/components/chat-widget.tsx +85 -0
  12. package/components/commerce/_to_deprecate_checkout-widget/const.ts +13 -0
  13. package/components/commerce/_to_deprecate_checkout-widget/index.tsx_ +188 -0
  14. package/components/commerce/_to_deprecate_checkout-widget/obs-string-set.ts +48 -0
  15. package/components/commerce/_to_deprecate_checkout-widget/use-anim-clx-set.ts +59 -0
  16. package/components/commerce/bag-button.tsx +98 -0
  17. package/components/commerce/buy-button.tsx +34 -0
  18. package/components/commerce/checkout-button.tsx +129 -0
  19. package/components/commerce/checkout-panel/cart-accordian.tsx +66 -0
  20. package/components/commerce/checkout-panel/checkout-panel-props.ts +10 -0
  21. package/components/commerce/checkout-panel/desktop-bag-carousel.tsx +36 -0
  22. package/components/commerce/checkout-panel/desktop-cp.tsx +83 -0
  23. package/components/commerce/checkout-panel/index.tsx +126 -0
  24. package/components/commerce/checkout-panel/mobile-cp.tsx +67 -0
  25. package/components/commerce/checkout-panel/policy-links.tsx +29 -0
  26. package/components/commerce/checkout-panel/steps-indicator.tsx +39 -0
  27. package/components/commerce/checkout-panel/thank-you.tsx +18 -0
  28. package/components/commerce/desktop-bag-popup.tsx +78 -0
  29. package/components/commerce/desktop-nav-menu.tsx +194 -0
  30. package/components/commerce/drawer/index.tsx +88 -0
  31. package/components/commerce/drawer/micro.tsx +145 -0
  32. package/components/commerce/drawer/shell.tsx +85 -0
  33. package/components/commerce/mobile-bag-drawer.tsx +51 -0
  34. package/components/commerce/mobile-login-button.tsx +108 -0
  35. package/components/commerce/mobile-menu-toggle-button.tsx +35 -0
  36. package/components/commerce/mobile-nav-menu-ai.tsx +51 -0
  37. package/components/commerce/mobile-nav-menu-item.tsx +50 -0
  38. package/components/commerce/mobile-nav-menu.tsx +102 -0
  39. package/components/contact-dialog/contact-form.tsx +113 -0
  40. package/components/contact-dialog/disclaimer.tsx +13 -0
  41. package/components/contact-dialog/index.tsx +64 -0
  42. package/components/copyright.tsx +21 -0
  43. package/components/drawer-margin.tsx +28 -0
  44. package/components/footer.tsx +78 -0
  45. package/components/header/desktop.tsx +51 -0
  46. package/components/header/index.tsx +50 -0
  47. package/components/header/mobile.tsx +163 -0
  48. package/components/header/theme-toggle.tsx +26 -0
  49. package/components/icons/24k-gold-card.tsx +43 -0
  50. package/components/icons/ai-chat-act.tsx +47 -0
  51. package/components/icons/ai-chat.tsx +29 -0
  52. package/components/icons/anodized-titanium.tsx +45 -0
  53. package/components/icons/avatar.tsx +11 -0
  54. package/components/icons/bag-icon.tsx +10 -0
  55. package/components/icons/blog-act.tsx +14 -0
  56. package/components/icons/blog.tsx +20 -0
  57. package/components/icons/bridge-act.tsx +18 -0
  58. package/components/icons/bridge.tsx +68 -0
  59. package/components/icons/changelog-act.tsx +15 -0
  60. package/components/icons/changelog.tsx +21 -0
  61. package/components/icons/chrome.tsx +45 -0
  62. package/components/icons/coins-act.tsx +29 -0
  63. package/components/icons/coins.tsx +20 -0
  64. package/components/icons/compare-cards-act.tsx +30 -0
  65. package/components/icons/compare-cards.tsx +21 -0
  66. package/components/icons/credit-act.tsx +29 -0
  67. package/components/icons/credit.tsx +20 -0
  68. package/components/icons/customer-support-act.tsx +27 -0
  69. package/components/icons/customer-support.tsx +21 -0
  70. package/components/icons/customers-act.tsx +65 -0
  71. package/components/icons/customers.tsx +33 -0
  72. package/components/icons/developer-docs-act.tsx +26 -0
  73. package/components/icons/developer-docs.tsx +20 -0
  74. package/components/icons/exchange-act.tsx +27 -0
  75. package/components/icons/exchange.tsx +21 -0
  76. package/components/icons/explorer-act.tsx +27 -0
  77. package/components/icons/explorer.tsx +22 -0
  78. package/components/icons/faqs-act.tsx +27 -0
  79. package/components/icons/faqs.tsx +21 -0
  80. package/components/icons/github.tsx +14 -0
  81. package/components/icons/guides-act.tsx +26 -0
  82. package/components/icons/guides.tsx +21 -0
  83. package/components/icons/gun-metal.tsx +44 -0
  84. package/components/icons/index.tsx +43 -0
  85. package/components/icons/integrations-act.tsx +41 -0
  86. package/components/icons/integrations.tsx +25 -0
  87. package/components/icons/irradescent.tsx +41 -0
  88. package/components/icons/launch-subnet.tsx +21 -0
  89. package/components/icons/launchsubnet-act.tsx +29 -0
  90. package/components/icons/left-arrow.tsx +11 -0
  91. package/components/icons/lux-finance-act.tsx +34 -0
  92. package/components/icons/lux-finance.tsx +23 -0
  93. package/components/icons/lux-logo.tsx +10 -0
  94. package/components/icons/lux-pass-act.tsx +41 -0
  95. package/components/icons/lux-pass.tsx +25 -0
  96. package/components/icons/lux-quests-act.tsx +15 -0
  97. package/components/icons/lux-quests.tsx +21 -0
  98. package/components/icons/market-act.tsx +39 -0
  99. package/components/icons/market.tsx +24 -0
  100. package/components/icons/mirrored-titanium.tsx +46 -0
  101. package/components/icons/more-benefits-act.tsx +29 -0
  102. package/components/icons/more-benefits.tsx +21 -0
  103. package/components/icons/open-source-act.tsx +41 -0
  104. package/components/icons/open-source.tsx +26 -0
  105. package/components/icons/right-arrow.tsx +10 -0
  106. package/components/icons/safe-act.tsx +77 -0
  107. package/components/icons/safe.tsx +37 -0
  108. package/components/icons/search.tsx +12 -0
  109. package/components/icons/secure-delivery.tsx +13 -0
  110. package/components/icons/shop-act.tsx +29 -0
  111. package/components/icons/shop.tsx +20 -0
  112. package/components/icons/social-icon.tsx +35 -0
  113. package/components/icons/social-svg.css +3 -0
  114. package/components/icons/sterling-silver-card.tsx +44 -0
  115. package/components/icons/templates-act.tsx +29 -0
  116. package/components/icons/templates.tsx +21 -0
  117. package/components/icons/validators-act.tsx +42 -0
  118. package/components/icons/validators.tsx +41 -0
  119. package/components/icons/view-all-card-act.tsx +28 -0
  120. package/components/icons/view-all-card.tsx +20 -0
  121. package/components/icons/wallet-act.tsx +29 -0
  122. package/components/icons/wallet.tsx +20 -0
  123. package/components/icons/warpcast.tsx +58 -0
  124. package/components/icons/youtube-logo.tsx +59 -0
  125. package/components/index.ts +25 -0
  126. package/components/logo.tsx +89 -0
  127. package/components/main.tsx +27 -0
  128. package/components/mini-chart/index.tsx +8 -0
  129. package/components/mini-chart/mini-chart-props.ts +44 -0
  130. package/components/mini-chart/mini-chart.tsx +85 -0
  131. package/components/mini-chart/wrapper.tsx +23 -0
  132. package/components/not-found/index.tsx +28 -0
  133. package/components/not-found/not-found-content.mdx +5 -0
  134. package/components/scripts.tsx +24 -0
  135. package/components/tooltip.tsx +31 -0
  136. package/environment.d.ts +6 -0
  137. package/next/analytics/fpixel.ts +16 -0
  138. package/next/analytics/google-analytics.ts +14 -0
  139. package/next/analytics/index.ts +3 -0
  140. package/next/analytics/pixel-analytics.tsx +55 -0
  141. package/next/font/get-app-router-font-classes.ts +12 -0
  142. package/next/font/load-and-return-lux-next-fonts-on-import.ts +68 -0
  143. package/next/font/local/Druk-Wide-Bold.ttf +0 -0
  144. package/next/font/local/Druk-Wide-Medium.ttf +0 -0
  145. package/next/font/local/InterVariable-Italic.ttf +0 -0
  146. package/next/font/local/InterVariable-Italic.woff2 +0 -0
  147. package/next/font/local/InterVariable.ttf +0 -0
  148. package/next/font/local/InterVariable.woff2 +0 -0
  149. package/next/font/next-font-desc.ts +28 -0
  150. package/next/font/pages-router-font-vars.tsx +18 -0
  151. package/next/head-metadata/from-next/metadata-types.ts +158 -0
  152. package/next/head-metadata/from-next/opengraph-types.ts +267 -0
  153. package/next/head-metadata/from-next/twitter-types.ts +92 -0
  154. package/next/head-metadata/index.tsx +208 -0
  155. package/next/index.ts +1 -0
  156. package/next/middleware/determine-device-mw.ts +16 -0
  157. package/package.json +80 -0
  158. package/root-layout/WHY_THIS_IS_SEPARATE.txt +2 -0
  159. package/root-layout/index.tsx +112 -0
  160. package/server-actions/TO-DO.txt +1 -0
  161. package/server-actions/firebase-app.ts +14 -0
  162. package/server-actions/index.ts +5 -0
  163. package/server-actions/store-contact.ts +51 -0
  164. package/site-def/footer/community.tsx +67 -0
  165. package/site-def/footer/company.ts +37 -0
  166. package/site-def/footer/ecosystem.ts +37 -0
  167. package/site-def/footer/index.tsx +26 -0
  168. package/site-def/footer/legal.ts +28 -0
  169. package/site-def/footer/network.ts +45 -0
  170. package/site-def/footer/svg/warpcast-logo.svg +12 -0
  171. package/site-def/index.ts +3 -0
  172. package/site-def/main-nav.tsx +458 -0
  173. package/style/cart-animation.css +29 -0
  174. package/style/checkout-animation.css +23 -0
  175. package/style/drawer-handle-overrides.css +160 -0
  176. package/style/lux-colors.css +85 -0
  177. package/style/lux-global.css +51 -0
  178. package/tailwind/fontFamily.tailwind.lux.ts +18 -0
  179. package/tailwind/index.ts +2 -0
  180. package/tailwind/lux-tw-fonts.ts +40 -0
  181. package/tailwind/tailwind.config.lux-preset.ts +10 -0
  182. package/tsconfig.json +15 -0
  183. package/types/chatbot-config.ts +7 -0
  184. package/types/chatbot-suggested-question.ts +7 -0
  185. package/types/contact-info.ts +11 -0
  186. package/types/index.ts +4 -0
  187. package/types/site-def.ts +43 -0
@@ -0,0 +1,129 @@
1
+ 'use client'
2
+ import React, { useEffect, useRef } from 'react'
3
+ import { observable, type IObservableValue, reaction, runInAction } from 'mobx'
4
+ import { observer } from 'mobx-react-lite'
5
+
6
+ import { Button, type ButtonProps } from '@hanzo/ui/primitives'
7
+ import { cn } from '@hanzo/ui/util'
8
+ import { useCommerce } from '@hanzo/commerce'
9
+
10
+ import * as Icons from '../icons'
11
+
12
+ const IconAndQuantity: React.FC<{
13
+ animateOnQuantityChange?: boolean
14
+ showArrow?: boolean
15
+ showQuantity?: boolean
16
+ clx?: string
17
+ iconClx?: string
18
+ digitClx?: string
19
+ }> = observer(({
20
+ animateOnQuantityChange=false,
21
+ showArrow=true,
22
+ showQuantity=true,
23
+ clx='',
24
+ iconClx='',
25
+ digitClx=''
26
+ }) => {
27
+
28
+ const cmmc = useCommerce()
29
+ const wiggleRef = useRef<IObservableValue<'more' | 'less' | 'none'>>(observable.box('none'))
30
+
31
+ useEffect(() => (
32
+ // return IReactionDisposer
33
+ animateOnQuantityChange ? reaction(
34
+ () => (cmmc.cartQuantity),
35
+ (curr, prev) => {
36
+ if (curr > prev) {
37
+ runInAction(() => {wiggleRef.current.set('more') })
38
+ }
39
+ else {
40
+ runInAction(() => {wiggleRef.current.set('less') })
41
+ }
42
+ setTimeout(() => {
43
+ // Note that this doesn't actually stop the animation
44
+ // just resets the styles
45
+ runInAction(() => {wiggleRef.current.set('none') })
46
+ }, 800)
47
+ }
48
+ ) : undefined
49
+ ), [])
50
+
51
+ return (
52
+ <div className={cn('flex items-center justify-center', clx)}>
53
+ {showQuantity && (
54
+ <div className={cn(
55
+ 'relative flex items-center justify-center mr-1',
56
+ ((wiggleRef.current.get() === 'more') ?
57
+ 'item-added-to-cart-animation'
58
+ :
59
+ (wiggleRef.current.get() === 'less') ? 'item-removed-from-cart-animation' : ''),
60
+ )} >
61
+ {cmmc.cartQuantity > 0 && (
62
+ <div className={cn(
63
+ 'z-above-content flex flex-col justify-center items-center',
64
+ 'absolute left-0 right-0 top-0 bottom-0',
65
+ digitClx
66
+ )}>
67
+ <div style={{/* color: 'white' tailwind bug? ,*/ fontSize: '11px', position: 'relative', top: '1px' }}>{cmmc.cartQuantity}</div>
68
+ </div>
69
+ )}
70
+ <Icons.bag width='19' height='24' className={cn('relative -top-[3px] opacity-70' , iconClx)} aria-hidden="true" />
71
+ </div>
72
+ )}
73
+ {showArrow && (<span style={{fontSize: '17px',}}>&rsaquo;</span>)}
74
+ </div>
75
+ )
76
+ })
77
+
78
+ const CheckoutButton: React.FC<ButtonProps & {
79
+ handleCheckout: () => void
80
+ showQuantity?: boolean
81
+ showArrow?: boolean
82
+ animateOnQuantityChange?: boolean
83
+ centerText?: boolean
84
+ }> = ({
85
+ handleCheckout,
86
+ variant='primary',
87
+ rounded='lg',
88
+ className,
89
+ showQuantity=true,
90
+ showArrow=true,
91
+ animateOnQuantityChange=true,
92
+ centerText=true,
93
+ children,
94
+ ...rest
95
+ }) => {
96
+
97
+ return (
98
+ <Button
99
+ {...rest}
100
+ onClick={handleCheckout}
101
+ variant={variant}
102
+ rounded={rounded}
103
+ className={cn(
104
+ 'flex justify-between items-stretch group',
105
+ showQuantity ? (centerText ? 'px-1.5' : 'pl-2.5 pr-1.5') : '',
106
+ className,
107
+ )}
108
+ >
109
+ {centerText && ( // must scale this one too, as it effects layout
110
+ <IconAndQuantity
111
+ showArrow={showArrow}
112
+ showQuantity={showQuantity}
113
+ clx='invisible group-hover:scale-105 transition-scale transition-duration-300'
114
+ />
115
+ )}
116
+ {children ?? (<div className='flex justify-center items-center'>Checkout</div>)}
117
+ <IconAndQuantity
118
+ clx='group-hover:scale-105 transition-scale transition-duration-300'
119
+ animateOnQuantityChange={animateOnQuantityChange}
120
+ showArrow={showArrow}
121
+ showQuantity={showQuantity}
122
+ iconClx='fill-background'
123
+ digitClx='text-foreground group-hover:opacity-80 leading-none font-bold font-sans'
124
+ />
125
+ </Button>
126
+ )
127
+ }
128
+
129
+ export default CheckoutButton
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { ChevronRight } from 'lucide-react'
6
+
7
+ import {
8
+ Accordion,
9
+ AccordionContent,
10
+ AccordionItem,
11
+ AccordionTrigger,
12
+ } from '@hanzo/ui/primitives'
13
+ import { cn } from '@hanzo/ui/util'
14
+
15
+ import { useCommerce, CartPanel, formatCurrencyValue } from '@hanzo/commerce'
16
+
17
+ const CartAccordian: React.FC<{
18
+ icon?: React.ReactNode
19
+ clx?: string
20
+ triggerClx?: string
21
+ panelClx?: string
22
+ scrollAfter: number
23
+ scrollHeightClx: string
24
+ }> = observer(({
25
+ icon,
26
+ clx='',
27
+ panelClx='',
28
+ triggerClx='',
29
+ scrollAfter,
30
+ scrollHeightClx
31
+ }) => {
32
+ const cmmc = useCommerce()
33
+ return (
34
+ <Accordion type="single" collapsible className={clx}>
35
+ <AccordionItem value="cart" className='w-full border-b-0'>
36
+ <AccordionTrigger className={'!no-underline group flex justify-between '} headerClx={ triggerClx}>
37
+ <div className='flex gap-0 items-center'>
38
+ {icon}
39
+ <h5 className='text-sm sm:text-xl grow'>
40
+ <span className='group-data-[state=open]:hidden' >Order Total:</span>
41
+ <span className='group-data-[state=closed]:hidden' >Your Order</span>
42
+ </h5>
43
+ </div>
44
+ <div className='flex gap-1 items-center'>
45
+ <h5 className='text-sm sm:text-xl grow truncate'>{formatCurrencyValue(cmmc.promoAppliedCartTotal)}</h5>
46
+ <ChevronRight className="h-5 w-5 -mr-2 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-90" />
47
+ </div>
48
+ </AccordionTrigger>
49
+ <AccordionContent className='data-[state=open]:mb-4'>
50
+ <CartPanel
51
+ className={cn('w-full', panelClx)}
52
+ scrollAfter={scrollAfter}
53
+ scrollHeightClx={scrollHeightClx}
54
+ listClx='mt-0'
55
+ itemClx='mt-0.5 mb-0'
56
+ totalClx='sticky px-1 pr-2 border-t -bottom-[1px] bg-background'
57
+ showShipping
58
+ showPromoCode
59
+ />
60
+ </AccordionContent>
61
+ </AccordionItem>
62
+ </Accordion>
63
+ )
64
+ })
65
+
66
+ export default CartAccordian
@@ -0,0 +1,10 @@
1
+ interface CheckoutPanelProps {
2
+ step: number
3
+ stepNames: string[]
4
+ onLeave:() => void
5
+ clx?: string
6
+ }
7
+
8
+ export {
9
+ type CheckoutPanelProps as default
10
+ }
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import {
6
+ useCommerce,
7
+ CarouselItemSelector,
8
+ type CarouselItemSelectorPropsExt
9
+ } from '@hanzo/commerce'
10
+
11
+ const DesktopBagCarousel: React.FC<{
12
+ constrainTo: {w: number, h: number}
13
+ className?: string
14
+ }> = observer(({
15
+ constrainTo,
16
+ className=''
17
+
18
+ }) => {
19
+ const cmmc = useCommerce()
20
+ return (
21
+ <CarouselItemSelector
22
+ items={cmmc.cartItems}
23
+ selectedItemRef={cmmc}
24
+ scrollable={false} // ignored
25
+ selectSku={cmmc.setCurrentItem.bind(cmmc)}
26
+ clx={className}
27
+ ext={{
28
+ options: {loop: true},
29
+ constrainTo,
30
+ imageOnly: true
31
+ } satisfies CarouselItemSelectorPropsExt}
32
+ />
33
+ )
34
+ })
35
+
36
+ export default DesktopBagCarousel
@@ -0,0 +1,83 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren } from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { ScrollArea, StepIndicator } from '@hanzo/ui/primitives'
6
+ import { AuthWidget } from '@hanzo/auth/components'
7
+ import { CartPanel, useCommerce } from '@hanzo/commerce'
8
+ import { cn } from '@hanzo/ui/util'
9
+
10
+ import { BackButton, Logo, Tooltip } from '../..'
11
+ import DesktopBagCarousel from './desktop-bag-carousel'
12
+ import LinksRow from './policy-links'
13
+ import type CheckoutPanelProps from './checkout-panel-props'
14
+
15
+ const DesktopCheckoutPanel: React.FC<PropsWithChildren & CheckoutPanelProps> = observer(({
16
+ step,
17
+ stepNames,
18
+ onLeave,
19
+ clx='',
20
+ children
21
+ }) => {
22
+
23
+ const cmmc = useCommerce()
24
+ return (
25
+ <div /* id='CHECKOUT_PANEL' */ className={cn('grid grid-cols-2', clx)}>
26
+ <div key={1} className='w-full h-full bg-background flex flex-row items-start justify-end'>
27
+ <div className='w-full h-full max-w-[750px] relative flex flex-col items-stretch justify-start px-8 pb-8'>
28
+ <div key={1} className='h-[80px] grow-0 flex flex-row items-center z-10' >
29
+ <Logo size='md' href='/' onClick={onLeave} variant='text-only' outerClx='logo-outer-tooltip-class' />
30
+ <Tooltip select='.logo-outer-tooltip-class' text='home' position='right' offset={6}/>
31
+ </div>
32
+ <BackButton
33
+ size='sm'
34
+ clx={
35
+ 'z-10 absolute top-14 left-6 !px-0 aspect-square ' +
36
+ 'rounded-full hover:!bg-level-3 ' +
37
+ 'back-button-tooltip-class '
38
+ }
39
+ onBack={onLeave}
40
+ />
41
+ <Tooltip select='.back-button-tooltip-class' text='back' position='right' offset={5}/>
42
+ <div key={2} className={cn(
43
+ 'w-full grow min-h-0 max-w-[550px] mx-auto flex flex-col gap-3',
44
+ (cmmc.cartItems.length > 4 ? 'justify-between' : 'justify-start gap-10 pt-10')
45
+ )}>
46
+ <DesktopBagCarousel className='grow-0 h-[260px] w-[360px] lg:w-[420px] mx-auto -mt-8' constrainTo={{w: 250, h: 250}}/>
47
+ <CartPanel
48
+ className='w-full border-none p-0'
49
+ itemClx='mb-2'
50
+ totalClx='sticky bottom-0 bg-background'
51
+ listClx='pr-3'
52
+ scrollAfter={4}
53
+ scrollHeightClx='min-h-[50vh] grow'
54
+ showPromoCode
55
+ showShipping
56
+ selectItems
57
+ />
58
+ </div>
59
+ </div>
60
+ </div>
61
+ <div key={2} className='w-full h-full flex flex-col bg-level-1 min-h-screen justify-between'>
62
+ <ScrollArea className='w-full flex flex-row items-start justify-start overflow-y-auto'>
63
+ <div className='h-full w-full max-w-[750px] relative flex flex-col items-center px-8 pt-0'>
64
+ <div key={1} className='bg-level-1 sticky h-[80px] bg-[#aaaaff] w-full top-0 flex justify-center items-end'>
65
+ <AuthWidget noLogin className='hidden md:flex absolute top-4 right-4 '/>
66
+ <StepIndicator dotSizeRem={1.35} steps={stepNames} currentStep={step} className='gap-2 text-base w-pr-70' />
67
+ </div>
68
+ <div key={2} className='w-full max-w-[550px] mx-auto py-8'>
69
+ {children}
70
+ </div>
71
+ </div>
72
+ </ScrollArea>
73
+ <div className='w-full max-w-[750px] relative flex flex-col items-center px-8 pt-0'>
74
+ <div className='w-full max-w-[550px] mx-auto flex flex-col items-center'>
75
+ <LinksRow clx='w-full' />
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ )
81
+ })
82
+
83
+ export default DesktopCheckoutPanel
@@ -0,0 +1,126 @@
1
+ 'use client'
2
+ import React, { useLayoutEffect, useRef, useState } from 'react'
3
+
4
+ import { capitalize, cn } from '@hanzo/ui/util'
5
+
6
+ import { useCommerce, ShippingStepForm, PaymentStepForm } from '@hanzo/commerce'
7
+ import type { CheckoutStep } from '@hanzo/commerce/types'
8
+
9
+ import ThankYou from './thank-you'
10
+
11
+ const STEPS = [
12
+ {
13
+ name: 'payment',
14
+ Comp: PaymentStepForm
15
+ },
16
+ {
17
+ name: 'delivery',
18
+ Comp: ShippingStepForm
19
+ },
20
+ {
21
+ name: 'done',
22
+ label: 'Done!',
23
+ Comp: ThankYou
24
+ }
25
+ ] satisfies CheckoutStep[]
26
+
27
+ const STEP_NAMES = STEPS.map((s) => (s.label ? s.label : capitalize(s.name)))
28
+
29
+ import DesktopCP from './desktop-cp'
30
+ import MobileCP from './mobile-cp'
31
+
32
+ const CheckoutPanel: React.FC<{
33
+ clx?: string
34
+ }> = ({
35
+ clx=''
36
+ }) => {
37
+
38
+ const cmmc = useCommerce()
39
+
40
+ // For sites that don't initialize cmmc
41
+ if (!cmmc) {
42
+ console.log("CHECKOUT PANEL: cmmc svc undefined!")
43
+ return <></>
44
+ }
45
+
46
+ const [stepIndex, setStepIndex] = useState<number>(0)
47
+ const [orderId, setOrderId] = useState<string | undefined>(undefined)
48
+
49
+ // Step.name or 'first' or 'next' or 'last'
50
+ const setStep = (name: string): void => {
51
+
52
+ if (name === 'first') {
53
+ setStepIndex(0)
54
+ }
55
+ else if (name === 'last') {
56
+ setStepIndex(STEPS.length - 1)
57
+ }
58
+ else if (name === 'next') {
59
+ if (stepIndex <= STEPS.length - 2) {
60
+ setStepIndex(stepIndex + 1)
61
+ }
62
+ else {
63
+ throw new Error('CheckoutPanel.setStep(): Attempting to advance past last step!')
64
+ }
65
+ }
66
+ else {
67
+ const indexFound = STEPS.findIndex((el) => (el.name === name))
68
+ if (indexFound !== -1) {
69
+ setStepIndex(indexFound)
70
+ }
71
+ else {
72
+ throw new Error('CheckoutPanel.setStep(): Step named ' + name + ' not found!')
73
+ }
74
+ }
75
+ }
76
+
77
+ const _close = () => {
78
+ setStep('first')
79
+ }
80
+
81
+ // Determine if mobile or desktop layout based on visibility of desktopElement
82
+ // This prevents issues with multiple instances of 3rd party e-commerce widgets
83
+ // from ever being in the DOM.
84
+ // https://stackoverflow.com/a/21696585/11378853
85
+ const desktopElement = useRef<HTMLDivElement | null>(null)
86
+ const [layout, setLayout] = useState<'mobile' | 'desktop' | undefined>(undefined)
87
+
88
+ useLayoutEffect(() => {
89
+ const checkLayout = () => {
90
+ setLayout(!!desktopElement.current?.offsetParent ? 'desktop' : 'mobile')
91
+ }
92
+
93
+ // initial layout check
94
+ checkLayout()
95
+
96
+ window.addEventListener('resize', checkLayout)
97
+ return () => {
98
+ window.removeEventListener('resize', checkLayout)
99
+ }
100
+ }, [])
101
+
102
+ const StepToRender = STEPS[stepIndex].Comp
103
+
104
+ return (<>
105
+ <DesktopCP
106
+ clx={cn('h-full', clx, 'hidden md:flex')}
107
+ onLeave={_close}
108
+ step={stepIndex}
109
+ stepNames={STEP_NAMES}
110
+ >
111
+ {/* Element required to determine if DesktopCP is visible. See above. */}
112
+ <div ref={desktopElement}/>
113
+ {layout === 'desktop' && <StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>}
114
+ </DesktopCP>
115
+ <MobileCP
116
+ clx={cn('w-full h-full overflow-y-auto', clx, 'md:hidden' )}
117
+ onLeave={_close}
118
+ step={stepIndex}
119
+ stepNames={STEP_NAMES}
120
+ >
121
+ {layout === 'mobile' && <StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>}
122
+ </MobileCP>
123
+ </>)
124
+ }
125
+
126
+ export default CheckoutPanel
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren } from 'react'
3
+
4
+ import { StepIndicator } from '@hanzo/ui/primitives'
5
+ import { cn } from '@hanzo/ui/util'
6
+ import { AuthWidget } from '@hanzo/auth/components'
7
+
8
+ import { BackButton, Logo } from '../..'
9
+ import BagButton from '../bag-button'
10
+ import PolicyLinks from './policy-links'
11
+ import CartAccordian from './cart-accordian'
12
+ import type CheckoutPanelProps from './checkout-panel-props'
13
+
14
+
15
+ const MobileCheckoutPanel: React.FC<PropsWithChildren & CheckoutPanelProps> = ({
16
+ step,
17
+ stepNames,
18
+ onLeave,
19
+ clx='',
20
+ children
21
+ }) => (
22
+
23
+ <div /* id='MOBILE_GRID' */ className={cn('bg-background flex flex-col justify-start px-4 pt-[101px]', clx)}>
24
+ <div className='fixed z-11 top-0 h-[45px] w-full flex justify-between items-stretch bg-background'>
25
+ <div className='flex items-stretch gap-1 grow-0'>
26
+ <BackButton
27
+ size='sm'
28
+ clx={
29
+ '-ml-5 !px-0 aspect-square h-full ' +
30
+ 'rounded-full active:!bg-level-3 '
31
+ }
32
+ onBack={onLeave}
33
+ />
34
+ <Logo size='xs' variant='text-only' href='/' onClick={onLeave} outerClx='-ml-2'/>
35
+ </div>
36
+ <StepIndicator
37
+ dotSizeRem={1}
38
+ steps={stepNames}
39
+ currentStep={step}
40
+ className='relative grow mx-2 top-[14px] text-xs font-semibold w-full'
41
+ />
42
+
43
+ {/* 72px by observation (for centering). Need wrapper div since 'noLogin' returns null if no logged in user */}
44
+ <div className='w-[72px] grow-0 shrink-0 flex items-center justify-center'><AuthWidget noLogin className=''/></div>
45
+ </div>
46
+ <CartAccordian
47
+ icon={
48
+ <BagButton
49
+ animateOnHover={false}
50
+ showIfEmpty
51
+ size='sm'
52
+ className='mr-1 relative w-5 h-6 sm:w-6 sm:h-7'
53
+ iconClx='fill-foreground'
54
+ />
55
+ }
56
+ clx='flex items-center justify-center w-full'
57
+ triggerClx='bg-background fixed z-11 top-[45px] left-0 right-0 !m-0 px-4'
58
+ panelClx='!py-0'
59
+ scrollAfter={3}
60
+ scrollHeightClx='h-[385px]'
61
+ />
62
+ {children}
63
+ <PolicyLinks clx='mt-auto mb-3 pt-2' />
64
+ </div>
65
+ )
66
+
67
+ export default MobileCheckoutPanel
@@ -0,0 +1,29 @@
1
+ import React from 'react'
2
+ import Link from 'next/link'
3
+
4
+ import { Separator, buttonVariants } from '@hanzo/ui/primitives'
5
+ import { cn } from '@hanzo/ui/util'
6
+
7
+
8
+ const linkClx = buttonVariants({
9
+ variant: 'linkMuted',
10
+ size: 'link',
11
+ rounded: 'none',
12
+ })
13
+
14
+ const PolicyLinks: React.FC<{
15
+ clx?: string
16
+ }> = ({
17
+ clx=''
18
+ }) => (
19
+ <div className={cn('flex flex-col items-center', clx)}>
20
+ <Separator/>
21
+ <div className='flex gap-4 py-2 text-sm'>
22
+ {/* TODO: add Refund policy and Privacy policy links */}
23
+ <Link className={linkClx} href=''>refund policy</Link>
24
+ <Link className={linkClx} href=''>privacy policy</Link>
25
+ </div>
26
+ </div>
27
+ )
28
+
29
+ export default PolicyLinks
@@ -0,0 +1,39 @@
1
+ import {
2
+ Breadcrumb,
3
+ BreadcrumbItem,
4
+ BreadcrumbLink,
5
+ BreadcrumbList,
6
+ BreadcrumbSeparator
7
+ } from '@hanzo/ui/primitives'
8
+ import { cn } from '@hanzo/ui/util'
9
+
10
+ const StepsIndicator: React.FC<{
11
+ currentStep: number
12
+ stepNames: string[]
13
+ className?: string
14
+ }> = ({
15
+ currentStep,
16
+ stepNames,
17
+ className=''
18
+ }) => (
19
+ <Breadcrumb className={className}>
20
+ <BreadcrumbList>
21
+ {stepNames.map((name, i) => (
22
+ <>
23
+ <BreadcrumbItem key={`item-${i}`}>
24
+ <BreadcrumbLink className={cn(
25
+ currentStep >= i ? '!text-foreground hover:text-foreground' : 'hover:text-muted-2',
26
+ 'text-xxs sm:text-sm'
27
+ )}
28
+ >
29
+ {name}
30
+ </BreadcrumbLink>
31
+ </BreadcrumbItem>
32
+ {i !== stepNames.length - 1 && <BreadcrumbSeparator key={`sep-${i}`}/>}
33
+ </>
34
+ ))}
35
+ </BreadcrumbList>
36
+ </Breadcrumb>
37
+ )
38
+
39
+ export default StepsIndicator
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+ import Link from 'next/link'
3
+
4
+ import { ApplyTypography } from '@hanzo/ui/primitives'
5
+
6
+ import type { CheckoutStepComponentProps } from '@hanzo/commerce/types'
7
+
8
+ const ThankYou: React.FC<CheckoutStepComponentProps> = ({}) => (
9
+ <ApplyTypography className='flex flex-col gap-4 text-center mt-10'>
10
+ <h3>Thank you for your order!</h3>
11
+ <h6>Once your payment has been confirmed, you'll recieve an email with additional information.</h6>
12
+ <p>
13
+ While you wait, we cordially invite you to join the <Link href='https://warpcast.com/~/channel/lux'>Lux Channel</Link> on <Link href='https://warpcast.com/~/invite-page/227706?id=fbc9ca91'>Warpcast</Link>.
14
+ </p>
15
+ </ApplyTypography>
16
+ )
17
+
18
+ export default ThankYou
@@ -0,0 +1,78 @@
1
+ 'use client'
2
+ import React, { useState, useEffect } from 'react'
3
+ import { useRouter } from 'next/navigation'
4
+
5
+ import { X } from 'lucide-react'
6
+
7
+ import {
8
+ Popover,
9
+ PopoverContent,
10
+ PopoverTrigger,
11
+ PopoverClose,
12
+ } from "@hanzo/ui/primitives"
13
+
14
+ import { cn } from '@hanzo/ui/util'
15
+ import { CartPanel, useCommerce } from '@hanzo/commerce'
16
+
17
+ import * as Icons from '../icons'
18
+ import sendGAEvent from '../../next/analytics/google-analytics'
19
+
20
+ const DesktopBagPopup: React.FC<{
21
+ triggerClx?: string
22
+ popupClx?: string
23
+ trigger: React.ReactNode
24
+ }> = ({
25
+ triggerClx='',
26
+ popupClx='',
27
+ trigger
28
+ }) => {
29
+ const cmmc = useCommerce()
30
+
31
+ const [bagOpen, setBagOpen] = useState<boolean>(false)
32
+ const router = useRouter()
33
+
34
+ useEffect(() => {
35
+ if (bagOpen) {
36
+ sendGAEvent('view_cart', {
37
+ items: cmmc.cartItems.map((item) => ({
38
+ item_id: item.sku,
39
+ item_name: item.title,
40
+ item_category: item.familyId,
41
+ price: item.price,
42
+ quantity: item.quantity
43
+ })),
44
+ value: cmmc.cartTotal,
45
+ currency: 'USD',
46
+ })
47
+ }
48
+ }, [bagOpen])
49
+
50
+ return (
51
+ <Popover open={bagOpen} onOpenChange={setBagOpen}>
52
+ <PopoverTrigger className={triggerClx}>
53
+ {trigger}
54
+ </PopoverTrigger>
55
+ <PopoverContent sideOffset={28} className={cn('relative flex flex-col p-0 px-4 pb-4 pt-2', popupClx)}>
56
+ <PopoverClose className='absolute z-above-content right-2 top-2 self-end hover:bg-level-3 text-muted hover:text-accent p-1 rounded-full'><X className='w-5 h-5'/></PopoverClose>
57
+ <CartPanel
58
+ handleCheckout={() => {router.push('/checkout')}}
59
+ className='mt-4 mb-4 border-none py-0 px-4'
60
+ listClx='rounded-sm pr-3'
61
+ scrollAfter={5}
62
+ scrollHeightClx='h-[70vh]'
63
+ itemClx='mt-3'
64
+ totalClx='sticky px-1 pr-2 -bottom-[1px] bg-level-1'
65
+ buttonClx='max-w-[220px] flex-none'
66
+ >
67
+ <div className='flex flex-row items-center flex-none justify-center '>
68
+ <Icons.bag className='mr-2 relative w-6 h-7 fill-foreground ' />
69
+ <p className='font-heading text-foreground text-default'>Your Bag</p>
70
+ </div>
71
+ <div className='h-[1px] w-pr-80 bg-muted-3 mx-auto mt-1.5 flex-none'/>
72
+ </CartPanel>
73
+ </PopoverContent>
74
+ </Popover>
75
+ )
76
+ }
77
+
78
+ export default DesktopBagPopup