@luxfi/core 4.3.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. package/components/access-code-input.tsx +71 -0
  2. package/components/auth/login-panel.tsx +80 -0
  3. package/components/chat-widget.tsx +77 -0
  4. package/components/commerce/bag-button.tsx +67 -0
  5. package/components/commerce/checkout-panel/close-button.tsx +23 -0
  6. package/components/commerce/checkout-panel/dt-bag-carousel.tsx +36 -0
  7. package/components/commerce/checkout-panel/dt-checkout-panel.tsx +68 -0
  8. package/components/commerce/checkout-panel/index.tsx +124 -0
  9. package/components/commerce/checkout-panel/links-row.tsx +21 -0
  10. package/components/commerce/checkout-panel/mb-checkout-panel.tsx +51 -0
  11. package/components/commerce/checkout-panel/steps-indicator.tsx +39 -0
  12. package/components/commerce/checkout-panel/thank-you.tsx +18 -0
  13. package/components/commerce/desktop-bag-popup.tsx +78 -0
  14. package/components/commerce/mobile-bag-drawer.tsx +51 -0
  15. package/components/commerce/mobile-menu-toggle-button.tsx +35 -0
  16. package/components/commerce/mobile-nav-menu.tsx +64 -0
  17. package/components/contact-dialog/contact-form.tsx +112 -0
  18. package/components/contact-dialog/disclaimer.tsx +13 -0
  19. package/components/contact-dialog/index.tsx +64 -0
  20. package/components/copyright.tsx +21 -0
  21. package/components/footer.tsx +78 -0
  22. package/components/header/desktop.tsx +54 -0
  23. package/components/header/index.tsx +26 -0
  24. package/components/header/mobile.tsx +161 -0
  25. package/components/header/theme-toggle.tsx +26 -0
  26. package/components/icons/bag-icon.tsx +10 -0
  27. package/components/icons/github.tsx +14 -0
  28. package/components/icons/index.tsx +35 -0
  29. package/components/icons/lux-logo.tsx +10 -0
  30. package/components/icons/secure-delivery.tsx +13 -0
  31. package/components/icons/social-icon.tsx +35 -0
  32. package/components/icons/social-svg.css +3 -0
  33. package/components/icons/youtube-logo.tsx +59 -0
  34. package/components/index.ts +26 -0
  35. package/components/logo.tsx +81 -0
  36. package/components/mini-chart/index.tsx +8 -0
  37. package/components/mini-chart/mini-chart-props.ts +44 -0
  38. package/components/mini-chart/mini-chart.tsx +85 -0
  39. package/components/mini-chart/wrapper.tsx +23 -0
  40. package/components/not-found/index.tsx +27 -0
  41. package/components/not-found/not-found-content.mdx +5 -0
  42. package/components/root-layout.tsx +71 -0
  43. package/components/scripts.tsx +23 -0
  44. package/conf/index.ts +50 -0
  45. package/environment.d.ts +6 -0
  46. package/next/analytics/fpixel.ts +16 -0
  47. package/next/analytics/google-analytics.ts +14 -0
  48. package/next/analytics/index.ts +3 -0
  49. package/next/analytics/pixel-analytics.tsx +55 -0
  50. package/next/determine-device-mw.ts +16 -0
  51. package/next/font/get-app-router-font-classes.ts +12 -0
  52. package/next/font/load-and-return-lux-next-fonts-on-import.ts +67 -0
  53. package/next/font/local/Druk-Wide-Bold.ttf +0 -0
  54. package/next/font/local/Druk-Wide-Medium.ttf +0 -0
  55. package/next/font/next-font-desc.ts +28 -0
  56. package/next/font/pages-router-font-vars.tsx +18 -0
  57. package/next/head-metadata/from-next/metadata-types.ts +158 -0
  58. package/next/head-metadata/from-next/opengraph-types.ts +267 -0
  59. package/next/head-metadata/from-next/twitter-types.ts +92 -0
  60. package/next/head-metadata/index.tsx +208 -0
  61. package/next/index.ts +1 -0
  62. package/package.json +72 -0
  63. package/server-actions/firebase-app.ts +14 -0
  64. package/server-actions/index.ts +5 -0
  65. package/server-actions/store-contact.ts +51 -0
  66. package/site-def/footer/community.tsx +67 -0
  67. package/site-def/footer/company.ts +37 -0
  68. package/site-def/footer/ecosystem.ts +37 -0
  69. package/site-def/footer/index.tsx +26 -0
  70. package/site-def/footer/legal.ts +28 -0
  71. package/site-def/footer/network.ts +45 -0
  72. package/site-def/footer/svg/warpcast-logo.svg +12 -0
  73. package/site-def/index.ts +3 -0
  74. package/site-def/main-nav.ts +35 -0
  75. package/site-def/site-def.ts +37 -0
  76. package/style/lux-colors.css +85 -0
  77. package/style/lux-global.css +19 -0
  78. package/tailwind/fontFamily.tailwind.lux.ts +18 -0
  79. package/tailwind/index.ts +2 -0
  80. package/tailwind/lux-tw-fonts.ts +40 -0
  81. package/tailwind/tailwind.config.lux-preset.ts +10 -0
  82. package/tsconfig.json +10 -0
  83. package/types/contact-info.ts +11 -0
  84. package/types/index.ts +1 -0
@@ -0,0 +1,71 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@hanzo/ui/primitives'
5
+ import { cn } from '@hanzo/ui/util'
6
+
7
+ const AccessCodeInput: React.FC<{
8
+ onSuccess?: () => void
9
+ onFail?: () => void
10
+ validCodes?: string[]
11
+ className?: string
12
+ }> = ({
13
+ onSuccess,
14
+ onFail,
15
+ validCodes,
16
+ className
17
+ }) => {
18
+ const [status, setStatus] = useState<'valid' | 'invalid' | 'checking' | undefined>()
19
+
20
+ const checkAccessCode = (code: string) => {
21
+ setStatus(undefined)
22
+ if (code.length === 6) {
23
+ setStatus('checking')
24
+ setTimeout(() => {
25
+ if (validCodes?.includes(code) && onSuccess) {
26
+ setStatus('valid')
27
+ onSuccess()
28
+ }
29
+ else {
30
+ setStatus('invalid')
31
+ onFail && onFail()
32
+ }
33
+ }, 1000)
34
+ }
35
+ }
36
+
37
+ return (
38
+ <div className={cn('flex flex-col gap-2 mx-auto w-full text-center items-center', className)}>
39
+ <InputOTP
40
+ className='mx-auto'
41
+ maxLength={6}
42
+ onInput={(event) => checkAccessCode((event.target as HTMLInputElement).value)}
43
+ render={({ slots }) => (
44
+ <>
45
+ <InputOTPGroup>
46
+ {slots.slice(0, 3).map((slot, index) => (
47
+ <InputOTPSlot key={index} {...slot} />
48
+ ))}{" "}
49
+ </InputOTPGroup>
50
+ <InputOTPSeparator />
51
+ <InputOTPGroup>
52
+ {slots.slice(3).map((slot, index) => (
53
+ <InputOTPSlot key={index + 3} {...slot} />
54
+ ))}
55
+ </InputOTPGroup>
56
+ </>
57
+ )}
58
+ />
59
+ <p className='h-[3rem]'>
60
+ {
61
+ status === 'checking' ? 'Checking access code...' :
62
+ status === 'invalid' ? <span className='text-destructive'>Invalid access code!</span> :
63
+ status === 'valid' ? <span>Access code is valid! Redirecting...</span> :
64
+ null
65
+ }
66
+ </p>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ export default AccessCodeInput
@@ -0,0 +1,80 @@
1
+ import Link from 'next/link'
2
+ import Autoplay from 'embla-carousel-autoplay'
3
+
4
+ import { cn } from '@hanzo/ui/util'
5
+ import { Button, Carousel, CarouselContent, CarouselItem } from '@hanzo/ui/primitives'
6
+ import { LoginPanel as Login } from '@hanzo/auth/components'
7
+
8
+ import { Logo } from '..'
9
+ import LuxLogo from '../icons/lux-logo'
10
+
11
+ const LoginPanel: React.FC<{
12
+ close: () => void
13
+ getStartedUrl?: string
14
+ redirectUrl?: string
15
+ className?: string
16
+ reviews: { text: string, author: string, href: string }[]
17
+ }> = ({
18
+ close,
19
+ getStartedUrl='/',
20
+ redirectUrl,
21
+ className='',
22
+ reviews
23
+ }) => {
24
+ return (
25
+ <div className={cn('grid grid-cols-1 md:grid-cols-2', className)}>
26
+ <div className='hidden md:flex w-full h-full bg-level-1 flex-row items-end justify-end overflow-y-auto min-h-screen'>
27
+ <div className='h-full w-full max-w-[750px] px-8 pt-0'>
28
+ <div className='h-full w-full max-w-[550px] mx-auto flex flex-col justify-between min-h-screen py-10'>
29
+ <Button
30
+ variant='ghost'
31
+ onClick={close}
32
+ className='w-fit !min-w-0 p-2'
33
+ >
34
+ <Logo size='md' spanClassName='!cursor-pointer' layout='text-only'/>
35
+ </Button>
36
+ <Carousel
37
+ options={{ align: 'center', loop: true }}
38
+ className='w-full'
39
+ plugins={[Autoplay({ delay: 5000, stopOnInteraction: true })]}
40
+ >
41
+ <CarouselContent>
42
+ {reviews.map(({text, author, href}, index) => (
43
+ <CarouselItem key={index}>
44
+ <Link href={href} className='flex flex-col gap-3 cursor-pointer'>
45
+ <p>“{text}“</p>
46
+ <p className='text-sm'>{author}</p>
47
+ </Link>
48
+ </CarouselItem>
49
+ ))}
50
+ </CarouselContent>
51
+ </Carousel>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ <div className='w-full h-full bg-background flex flex-row items-center'>
56
+ <div className='w-full max-w-[750px] relative flex flex-col items-center px-8 pt-0 text-center'>
57
+ <div className='relative h-full w-full max-w-[400px] mx-auto flex flex-col gap-4 items-center py-10'>
58
+ <Button
59
+ variant='ghost'
60
+ onClick={close}
61
+ className='block md:hidden absolute rounded-full p-2 left-0 h-auto hover:bg-background'
62
+ >
63
+ <LuxLogo className='w-5 h-5'/>
64
+ </Button>
65
+ {/* TODO: add Terms of Service and Privacy Policy links */}
66
+ <Login
67
+ getStartedUrl={getStartedUrl}
68
+ redirectUrl={redirectUrl}
69
+ className='w-full max-w-sm'
70
+ termsOfServiceUrl=''
71
+ privacyPolicyUrl=''
72
+ />
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ )
78
+ }
79
+
80
+ export default LoginPanel
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+ import React from 'react'
3
+
4
+ import { Button, Card } from '@hanzo/ui/primitives'
5
+
6
+ import LuxLogo from './icons/lux-logo'
7
+ import { cn } from '@hanzo/ui/util'
8
+
9
+ const ChatWidget: React.FC<{
10
+ title: string,
11
+ chatbotUrl: string,
12
+ subtitle?: string,
13
+ question?: string,
14
+ /*
15
+ Currently supports these icons from remix icons (https://remixicon.com/):
16
+ GlobalLineIcon,
17
+ ShieldFlashLineIcon,
18
+ BankCardLineIcon,
19
+ GroupLineIcon,
20
+ QuestionnaireLineIcon
21
+ */
22
+ suggestedQuestions?: { heading: string, message: string, icon?: string }[]
23
+ }> = ({
24
+ title,
25
+ chatbotUrl,
26
+ subtitle,
27
+ question,
28
+ suggestedQuestions
29
+ }) => {
30
+
31
+ const [showChatbot, setShowChatbot] = React.useState<boolean>(false)
32
+
33
+ const onClick = () => { setShowChatbot(!showChatbot) }
34
+
35
+ const searchParams = new URLSearchParams()
36
+ if (question) {
37
+ searchParams.append('question', question)
38
+ }
39
+ if (suggestedQuestions) {
40
+ searchParams.append('sQuestions', suggestedQuestions.map(({ message }) => message).join(','))
41
+ searchParams.append('sHeadings', suggestedQuestions.map(({ heading }) => heading).join(','))
42
+ searchParams.append('sIcons', suggestedQuestions.map(({ icon }) => icon).join(','))
43
+ }
44
+
45
+ const iframeSrc = `${chatbotUrl}?${searchParams.toString()}`
46
+
47
+ return (<>
48
+ <div className={
49
+ 'fixed bottom-0 sm:bottom-16 right-0 w-full h-full ' +
50
+ 'sm:max-w-[400px] sm:max-h-[550px] sm:px-4 z-above-floating ' +
51
+ (showChatbot ? 'flex' : 'hidden')
52
+ }>
53
+ <Card className='flex flex-col h-full w-full'>
54
+ <div className='flex px-4 py-2 h-12 bg-level-0 items-center justify-between'>
55
+ <h3 className='font-semibold font-heading'>{title} <span className='opacity-60'>{subtitle}</span></h3>
56
+ <Button onClick={onClick} variant='link' size='icon' className='w-fit sm:hidden'>
57
+ <LuxLogo width={24} height={24}/>
58
+ </Button>
59
+ </div>
60
+ <iframe src={iframeSrc} className='h-full' />
61
+ </Card>
62
+ </div>
63
+
64
+ <LuxLogo
65
+ width={28}
66
+ height={28}
67
+ onClick={onClick}
68
+ className={cn(
69
+ 'fixed bottom-5 right-5 z-floating transition-all cursor-pointer hover:drop-shadow-[0_2px_6px_rgba(255,255,255,1)]',
70
+ showChatbot ? 'rotate-180' : ''
71
+ )}
72
+ strokeWidth={1}
73
+ />
74
+ </>)
75
+ }
76
+
77
+ export default ChatWidget
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import { observer } from 'mobx-react-lite'
4
+
5
+ import { buttonVariants, type ButtonSizes } from '@hanzo/ui/primitives'
6
+ import { cn } from '@hanzo/ui/util'
7
+ import { useCommerce } from '@hanzo/commerce'
8
+
9
+ import * as Icons from '../icons'
10
+
11
+ const BagButton: React.FC<{
12
+ showIfEmpty?: boolean
13
+ noHoverEffects?: boolean
14
+ size?: ButtonSizes
15
+ className?: string
16
+ iconClx?: string
17
+ onClick?: () => void
18
+ }> = observer(({
19
+ showIfEmpty=false,
20
+ noHoverEffects=false,
21
+ size='default',
22
+ className='',
23
+ iconClx='',
24
+ onClick
25
+ }) => {
26
+
27
+ const c = useCommerce()
28
+
29
+ // undefined means context is not installed, ie commerce functions are not in use
30
+ if (!c || (!showIfEmpty && c.cartEmpty)) {
31
+ return <div /> // trigger code needs non-null
32
+ }
33
+
34
+ return (
35
+ <div
36
+ aria-label="Bag"
37
+ role='button'
38
+ onClick={onClick}
39
+ className={cn(
40
+ buttonVariants({ variant: 'ghost', size, rounded: 'md' }),
41
+ // Overides the bg change on hover --not a "hover effect"
42
+ 'relative group p-0 aspect-square hover:bg-background',
43
+ className
44
+ )}
45
+ >
46
+ {!c.cartEmpty && (
47
+ <div className={
48
+ 'z-above-content flex flex-col justify-center items-center ' +
49
+ 'absolute left-0 right-0 top-0 bottom-0 ' +
50
+ 'leading-none font-sans font-bold text-primary-fg text-accent text-xs '
51
+ }>
52
+ <div className='h-[3px] w-full' />
53
+ <div>{c.cartQuantity}</div>
54
+ </div>
55
+ )}
56
+ <Icons.bag className={cn(
57
+ 'relative -top-[3px] fill-primary w-6 h-7 ',
58
+ iconClx,
59
+ (noHoverEffects ? '' : (
60
+ 'group-hover:fill-primary-hover group-hover:scale-105 transition-scale transition-duration-300'
61
+ ))
62
+ )} aria-hidden="true" />
63
+ </div>
64
+ )
65
+ })
66
+
67
+ export default BagButton
@@ -0,0 +1,23 @@
1
+ 'use client'
2
+ import React from 'react'
3
+
4
+ import { cn } from '@hanzo/ui/util'
5
+
6
+ import Logo from '../../logo'
7
+
8
+ const CloseButton: React.FC<{
9
+ close: () => void
10
+ className?: string
11
+ }> = ({
12
+ close,
13
+ className=''
14
+ }) => (
15
+ <div
16
+ onClick={close}
17
+ className={cn('md:self-start', className)}
18
+ >
19
+ <Logo layout='text-only' href='/'/>
20
+ </div>
21
+ )
22
+
23
+ export default CloseButton
@@ -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,68 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren } from 'react'
3
+
4
+ import { ScrollArea } from '@hanzo/ui/primitives'
5
+ import { cn } from '@hanzo/ui/util'
6
+ import { AuthWidget } from '@hanzo/auth/components'
7
+ import { CartPanel } from '@hanzo/commerce'
8
+
9
+ import * as Icons from '../../icons'
10
+ import DesktopBagCarousel from './dt-bag-carousel'
11
+ import CloseButton from './close-button'
12
+ import LinksRow from './links-row'
13
+ import StepsIndicator from './steps-indicator'
14
+
15
+ const DesktopCheckoutPanel: React.FC<PropsWithChildren & {
16
+ index: number
17
+ stepNames: string[]
18
+ close:() => void
19
+ className?: string
20
+ }> = ({
21
+ index,
22
+ stepNames,
23
+ close,
24
+ className='',
25
+ children
26
+ }) => (
27
+
28
+ <div /* id='CHECKOUT_PANEL' */ className={cn('grid grid-cols-2', className)}>
29
+ <ScrollArea className='w-full h-full bg-level-1 flex flex-row items-start overflow-y-auto min-h-screen'>
30
+ <div className='h-full w-full flex justify-end'>
31
+ <div className='h-full w-full max-w-[750px] px-8 pt-0'>
32
+ <div className='h-full w-full max-w-[550px] mx-auto flex flex-col gap-3 justify-end min-h-screen'>
33
+ <div className='flex flex-col gap-3 h-30 justify-center'>
34
+ <CloseButton close={close} />
35
+ <StepsIndicator currentStep={index} stepNames={stepNames}/>
36
+ </div>
37
+ {children}
38
+ <LinksRow className='mt-auto mb-3' />
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </ScrollArea>
43
+ <div className='w-full h-full bg-background flex flex-row items-start justify-start'>
44
+ <div className='w-full max-w-[750px] relative flex flex-col items-center justify-start px-8 pt-0'>
45
+ <AuthWidget noLogin className='hidden md:flex absolute top-4 right-4 '/>
46
+ <div className='flex items-center justify-center h-30'>
47
+ <Icons.bag className='fill-foreground mr-2 relative -top-1 w-6 h-7'/>
48
+ <p className='font-heading text-default'>Order Summary</p>
49
+ </div>
50
+ <div className='w-full max-w-[550px] mx-auto'>
51
+ <DesktopBagCarousel className='h-[260px] w-[360px] lg:w-[420px] mx-auto -mt-8' constrainTo={{w: 250, h: 250}}/>
52
+ <CartPanel
53
+ className='w-full border-none p-0 pr-3'
54
+ itemClx='mb-3'
55
+ totalClx='sticky bottom-0 p-1 bg-background'
56
+ scrollAfter={5}
57
+ scrollHeightClx='h-[50vh]'
58
+ selectItems
59
+ showPromoCode
60
+ showShipping
61
+ />
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ )
67
+
68
+ export default DesktopCheckoutPanel
@@ -0,0 +1,124 @@
1
+ 'use client'
2
+ import React, { useEffect, 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 './dt-checkout-panel'
30
+ import MobileCP from './mb-checkout-panel'
31
+
32
+ const CheckoutPanel: React.FC<{
33
+ close: () => void
34
+ className?: string
35
+ }> = ({
36
+ close,
37
+ className=''
38
+ }) => {
39
+
40
+ const cmmc = useCommerce()
41
+
42
+ // For sites that don't initialize cmmc
43
+ if (!cmmc) {
44
+ return <></>
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
+ close()
80
+ }
81
+
82
+ // Determine if mobile or desktop based on visibility of desktopElement
83
+ // https://stackoverflow.com/a/21696585/11378853
84
+ const desktopElement = useRef<HTMLDivElement | null>(null)
85
+ const [layout, setLayout] = useState<'mobile' | 'desktop' | undefined>()
86
+ useEffect(() => {
87
+ const checkLayout = () => {
88
+ setLayout(!!desktopElement.current?.offsetParent ? 'desktop' : 'mobile')
89
+ }
90
+
91
+ // initial layout check
92
+ checkLayout()
93
+
94
+ window.addEventListener('resize', checkLayout)
95
+ return () => {
96
+ window.removeEventListener('resize', checkLayout)
97
+ }
98
+ }, [])
99
+
100
+ const StepToRender = STEPS[stepIndex].Comp
101
+
102
+ return (<>
103
+ <DesktopCP
104
+ className={cn('h-full', className, 'hidden md:flex')}
105
+ close={_close}
106
+ index={stepIndex}
107
+ stepNames={STEP_NAMES}
108
+ >
109
+ {/* Element required to determine if DesktopCP is visible */}
110
+ <div ref={desktopElement}/>
111
+ {layout === 'desktop' && <StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>}
112
+ </DesktopCP>
113
+ <MobileCP
114
+ className={cn('h-full overflow-y-auto', className, 'md:hidden' )}
115
+ close={_close}
116
+ index={stepIndex}
117
+ stepNames={STEP_NAMES}
118
+ >
119
+ {layout === 'mobile' && <StepToRender onDone={() => {setStep('next')}} orderId={orderId} setOrderId={setOrderId}/>}
120
+ </MobileCP>
121
+ </>)
122
+ }
123
+
124
+ export default CheckoutPanel
@@ -0,0 +1,21 @@
1
+ import Link from 'next/link'
2
+
3
+ import { Separator } from '@hanzo/ui/primitives'
4
+ import { cn } from '@hanzo/ui/util'
5
+
6
+ const LinksRow: React.FC<{
7
+ className?: string
8
+ }> = ({
9
+ className=''
10
+ }) => (
11
+ <div className={cn('flex flex-col', className)}>
12
+ <Separator className='my-1'/>
13
+ <div className='flex gap-4 text-sm'>
14
+ {/* TODO: add Refund policy and Privacy policy links */}
15
+ <Link href=''>Refund policy</Link>
16
+ <Link href=''>Privacy policy</Link>
17
+ </div>
18
+ </div>
19
+ )
20
+
21
+ export default LinksRow
@@ -0,0 +1,51 @@
1
+ 'use client'
2
+ import React, { type PropsWithChildren } from 'react'
3
+
4
+ import { cn } from '@hanzo/ui/util'
5
+ import { AuthWidget } from '@hanzo/auth/components'
6
+ import { CartAccordian } from '@hanzo/commerce'
7
+
8
+ import CloseButton from './close-button'
9
+ import BagButton from '../bag-button'
10
+ import LinksRow from './links-row'
11
+ import StepsIndicator from './steps-indicator'
12
+
13
+ const MobileCheckoutPanel: React.FC<PropsWithChildren & {
14
+ index: number
15
+ stepNames: string[]
16
+ close:() => void
17
+ className?: string
18
+ }> = ({
19
+ index,
20
+ stepNames,
21
+ close,
22
+ className='',
23
+ children
24
+ }) => (
25
+
26
+ <div /* id='MOBILE_GRID' */ className={cn('bg-background flex flex-col justify-start px-4', className)}>
27
+ <div className='sticky top-0 w-full flex flex-row justify-between items-center bg-background'>
28
+ <CloseButton close={close} />
29
+ <StepsIndicator currentStep={index} stepNames={stepNames}/>
30
+ {/* Need wrapper div since 'noLogin' returns null if no logged in user */}
31
+ <div className='w-10 h-10 flex items-center justify-center'><AuthWidget noLogin className=''/></div>
32
+ </div>
33
+ <CartAccordian
34
+ icon={
35
+ <BagButton
36
+ noHoverEffects
37
+ showIfEmpty
38
+ size='sm'
39
+ className=
40
+ 'mr-1 relative w-5 h-6 sm:w-6 sm:h-7 '
41
+ iconClx='fill-foreground '
42
+ />
43
+ }
44
+ className='flex items-center justify-center py-2 w-full'
45
+ />
46
+ {children}
47
+ <LinksRow className='mt-auto mb-3 pt-2' />
48
+ </div>
49
+ )
50
+
51
+ export default MobileCheckoutPanel
@@ -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