@luxfi/core 4.4.10 → 4.4.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 -71
  2. package/components/auth/auth-listener.tsx +23 -41
  3. package/components/auth/auth-token/clear-auth-token.tsx +12 -0
  4. package/components/auth/auth-token/set-auth-token.tsx +16 -0
  5. package/components/auth/common-auth-domains.ts +16 -15
  6. package/components/auth/login-panel.tsx +104 -117
  7. package/components/chat-widget.tsx +77 -77
  8. package/components/commerce/bag-button.tsx +67 -67
  9. package/components/commerce/checkout-panel/close-button.tsx +26 -26
  10. package/components/commerce/checkout-panel/dt-bag-carousel.tsx +36 -36
  11. package/components/commerce/checkout-panel/dt-checkout-panel.tsx +66 -66
  12. package/components/commerce/checkout-panel/index.tsx +123 -123
  13. package/components/commerce/checkout-panel/links-row.tsx +21 -21
  14. package/components/commerce/checkout-panel/mb-checkout-panel.tsx +55 -55
  15. package/components/commerce/checkout-panel/steps-indicator.tsx +39 -39
  16. package/components/commerce/checkout-panel/thank-you.tsx +18 -18
  17. package/components/commerce/desktop-bag-popup.tsx +78 -78
  18. package/components/commerce/mobile-bag-drawer.tsx +51 -51
  19. package/components/commerce/mobile-menu-toggle-button.tsx +35 -35
  20. package/components/commerce/mobile-nav-menu.tsx +64 -64
  21. package/components/contact-dialog/contact-form.tsx +112 -112
  22. package/components/contact-dialog/disclaimer.tsx +13 -13
  23. package/components/contact-dialog/index.tsx +64 -64
  24. package/components/copyright.tsx +21 -21
  25. package/components/footer.tsx +77 -77
  26. package/components/header/desktop.tsx +54 -54
  27. package/components/header/index.tsx +26 -26
  28. package/components/header/mobile.tsx +161 -161
  29. package/components/header/theme-toggle.tsx +26 -26
  30. package/components/icons/bag-icon.tsx +10 -10
  31. package/components/icons/github.tsx +14 -14
  32. package/components/icons/index.tsx +35 -35
  33. package/components/icons/lux-logo.tsx +10 -10
  34. package/components/icons/secure-delivery.tsx +13 -13
  35. package/components/icons/social-icon.tsx +35 -35
  36. package/components/icons/social-svg.css +3 -3
  37. package/components/icons/youtube-logo.tsx +59 -59
  38. package/components/index.ts +27 -27
  39. package/components/logo.tsx +81 -81
  40. package/components/mini-chart/index.tsx +7 -7
  41. package/components/mini-chart/mini-chart-props.ts +43 -43
  42. package/components/mini-chart/mini-chart.tsx +85 -85
  43. package/components/mini-chart/wrapper.tsx +23 -23
  44. package/components/not-found/index.tsx +27 -27
  45. package/components/not-found/not-found-content.mdx +5 -5
  46. package/components/root-layout.tsx +71 -71
  47. package/components/scripts.tsx +23 -23
  48. package/conf/index.ts +50 -50
  49. package/environment.d.ts +5 -5
  50. package/next/analytics/fpixel.ts +15 -15
  51. package/next/analytics/google-analytics.ts +13 -13
  52. package/next/analytics/index.ts +3 -3
  53. package/next/analytics/pixel-analytics.tsx +54 -54
  54. package/next/determine-device-mw.ts +16 -16
  55. package/next/font/get-app-router-font-classes.ts +12 -12
  56. package/next/font/load-and-return-lux-next-fonts-on-import.ts +68 -68
  57. package/next/font/next-font-desc.ts +27 -27
  58. package/next/font/pages-router-font-vars.tsx +18 -18
  59. package/next/head-metadata/from-next/metadata-types.ts +158 -158
  60. package/next/head-metadata/from-next/opengraph-types.ts +267 -267
  61. package/next/head-metadata/from-next/twitter-types.ts +92 -92
  62. package/next/head-metadata/index.tsx +208 -208
  63. package/package.json +73 -73
  64. package/server-actions/firebase-app.ts +14 -14
  65. package/server-actions/index.ts +5 -5
  66. package/server-actions/store-contact.ts +51 -51
  67. package/site-def/footer/community.tsx +67 -67
  68. package/site-def/footer/company.ts +37 -37
  69. package/site-def/footer/ecosystem.ts +37 -37
  70. package/site-def/footer/index.tsx +26 -26
  71. package/site-def/footer/legal.ts +28 -28
  72. package/site-def/footer/network.ts +45 -45
  73. package/site-def/footer/svg/warpcast-logo.svg +11 -11
  74. package/site-def/index.ts +2 -2
  75. package/site-def/main-nav.ts +35 -35
  76. package/site-def/site-def.ts +36 -36
  77. package/style/lux-colors.css +85 -85
  78. package/style/lux-global.css +30 -30
  79. package/tailwind/fontFamily.tailwind.lux.ts +18 -18
  80. package/tailwind/index.ts +2 -2
  81. package/tailwind/lux-tw-fonts.ts +39 -39
  82. package/tailwind/tailwind.config.lux-preset.ts +10 -10
  83. package/tsconfig.json +10 -10
  84. package/types/contact-info.ts +10 -10
@@ -1,71 +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
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
@@ -1,41 +1,23 @@
1
- 'use client'
2
-
3
- import { useEffect } from 'react'
4
- import { useAuth } from '@hanzo/auth/service'
5
-
6
- const AuthListener = () => {
7
- const auth = useAuth()
8
-
9
- const requestAuthToken = () => {
10
- const childFrame = document.getElementById('login') as HTMLIFrameElement
11
- childFrame?.contentWindow?.postMessage(true, process.env.NEXT_PUBLIC_LOGIN_SITE_URL ?? '')
12
- }
13
-
14
- useEffect(() => {
15
- const handleMessage = (event: any) => {
16
- if (event.origin === process.env.NEXT_PUBLIC_LOGIN_SITE_URL) {
17
- const token = event.data
18
- auth.loginWithCustomToken(token)
19
- }
20
- }
21
-
22
- if (typeof window !== 'undefined') {
23
- window.addEventListener('message', handleMessage)
24
-
25
- return () => {
26
- window.removeEventListener('message', handleMessage)
27
- }
28
- }
29
- }, [])
30
-
31
- return (
32
- <iframe
33
- id='login'
34
- onLoad={requestAuthToken}
35
- src={`${process.env.NEXT_PUBLIC_LOGIN_SITE_URL}/login`}
36
- className='hidden'
37
- />
38
- )
39
- }
40
-
41
- export default AuthListener
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+ import { useAuth } from '@hanzo/auth/service'
5
+ import { getCookie } from 'cookies-next'
6
+
7
+ const AuthListener = () => {
8
+ const auth = useAuth()
9
+
10
+ useEffect(() => {
11
+ const authToken = getCookie('auth-token')
12
+ if (!!authToken) {
13
+ auth.loginWithCustomToken(authToken)
14
+ }
15
+ else {
16
+ auth.logout()
17
+ }
18
+ }, [])
19
+
20
+ return ( <></> )
21
+ }
22
+
23
+ export default AuthListener
@@ -0,0 +1,12 @@
1
+ import domains from '../common-auth-domains'
2
+
3
+ const ClearAuthToken = () => {
4
+ return (<>
5
+ {domains.map(({url}) => (
6
+ /* Clear auth-token cookie across all Lux domains */
7
+ <img src={`${url}/api/auth/clear-auth-token`} className='absolute hidden'/>
8
+ ))}
9
+ </>)
10
+ }
11
+
12
+ export default ClearAuthToken
@@ -0,0 +1,16 @@
1
+ import domains from '../common-auth-domains'
2
+
3
+ const SetAuthToken: React.FC<{
4
+ authToken: string
5
+ }> = ({
6
+ authToken,
7
+ }) => {
8
+ return (<>
9
+ {!!authToken && domains.map(({url}) => (
10
+ /* Set auth-token cookie across all Lux domains */
11
+ <img src={`${url}/api/auth/set-auth-token?token=${authToken}`} className='absolute hidden'/>
12
+ ))}
13
+ </>)
14
+ }
15
+
16
+ export default SetAuthToken
@@ -1,15 +1,16 @@
1
- const domains = [
2
- { id: 'lux.market', url: 'https://lux.market' },
3
- { id: 'lux.shop', url: 'https://lux.shop' },
4
- { id: 'lux.credit', url: 'https://lux.credit' },
5
- { id: 'lux.network', url: 'https://lux.network' },
6
- { id: 'wallet.lux.network', url: 'https://wallet.lux.network' },
7
- { id: 'safe.lux.network', url: 'https://safe.lux.network' },
8
- { id: 'lux.finance', url: 'https://lux.finance' },
9
- { id: 'lux.exchange', url: 'https://lux.exchange' },
10
- { id: 'lux.quest', url: 'https://lux.quest' },
11
- ]
12
-
13
- export {
14
- domains as default
15
- }
1
+ const domains = [
2
+ { id: 'lux.market', url: 'https://lux.market' },
3
+ { id: 'lux.shop', url: 'https://lux.shop' },
4
+ { id: 'lux.credit', url: 'https://lux.credit' },
5
+ { id: 'lux.network', url: 'https://lux.network' },
6
+ { id: 'wallet.lux.network', url: 'https://wallet.lux.network' },
7
+ { id: 'safe.lux.network', url: 'https://safe.lux.network' },
8
+ { id: 'lux.finance', url: 'https://lux.finance' },
9
+ { id: 'lux.exchange', url: 'https://lux.exchange' },
10
+ { id: 'lux.quest', url: 'https://lux.quest' },
11
+ { id: 'lux.services', url: 'https://lux.services' },
12
+ ]
13
+
14
+ export {
15
+ domains as default
16
+ }
@@ -1,117 +1,104 @@
1
- 'use client'
2
-
3
- import { useEffect } from 'react'
4
- import { useRouter } from 'next/navigation'
5
- import Link from 'next/link'
6
- import Autoplay from 'embla-carousel-autoplay'
7
- import { getCookie , setCookie } from 'cookies-next'
8
-
9
- import { cn } from '@hanzo/ui/util'
10
- import { Button, Carousel, CarouselContent, CarouselItem } from '@hanzo/ui/primitives'
11
- import { LoginPanel as Login } from '@hanzo/auth/components'
12
-
13
- import { Logo } from '..'
14
- import LuxLogo from '../icons/lux-logo'
15
- import { legal } from '../../site-def/footer'
16
- import domains from './common-auth-domains'
17
-
18
- const LoginPanel: React.FC<{
19
- close: () => void
20
- getStartedUrl?: string
21
- redirectUrl?: string
22
- className?: string
23
- reviews: { text: string, author: string, href: string }[]
24
- }> = ({
25
- close,
26
- getStartedUrl='/',
27
- redirectUrl,
28
- className='',
29
- reviews
30
- }) => {
31
- const router = useRouter()
32
-
33
- const termsOfServiceUrl = legal.find(({title}) => title === 'Terms and Conditions')?.href || ''
34
- const privacyPolicyUrl = legal.find(({title}) => title === 'Privacy Policy')?.href || ''
35
-
36
- const onLogin = (token: string) => {
37
- setCookie('auth-token', token, { sameSite: 'none', secure: true })
38
- for (const { url } of domains) {
39
- parent?.contentWindow?.postMessage(token, url)
40
- }
41
- redirectUrl && router.replace(redirectUrl)
42
- }
43
-
44
- useEffect(() => {
45
- const handleMessage = (event: any) => {
46
- if (domains.includes(event.origin)) {
47
- const token = getCookie('auth-token')
48
- parent?.contentWindow?.postMessage(token, event.origin)
49
- }
50
- }
51
-
52
- if (typeof window !== 'undefined') {
53
- window.addEventListener('message', handleMessage)
54
-
55
- return () => {
56
- window.removeEventListener('message', handleMessage)
57
- }
58
- }
59
- }, [])
60
-
61
- return (
62
- <div className={cn('grid grid-cols-1 md:grid-cols-2', className)}>
63
- <div className='hidden md:flex w-full h-full bg-level-1 flex-row items-end justify-end overflow-y-auto min-h-screen'>
64
- <div className='h-full w-full max-w-[750px] px-8 pt-0'>
65
- <div className='h-full w-full max-w-[550px] mx-auto flex flex-col justify-between min-h-screen py-10'>
66
- <Button
67
- variant='ghost'
68
- onClick={close}
69
- className='w-fit !min-w-0 p-2'
70
- >
71
- <Logo size='md' spanClassName='!cursor-pointer' layout='text-only'/>
72
- </Button>
73
- <Carousel
74
- options={{ align: 'center', loop: true }}
75
- className='w-full'
76
- plugins={[Autoplay({ delay: 5000, stopOnInteraction: true })]}
77
- >
78
- <CarouselContent>
79
- {reviews.map(({text, author, href}, index) => (
80
- <CarouselItem key={index}>
81
- <Link href={href} className='flex flex-col gap-3 cursor-pointer'>
82
- <p>“{text}“</p>
83
- <p className='text-sm'>{author}</p>
84
- </Link>
85
- </CarouselItem>
86
- ))}
87
- </CarouselContent>
88
- </Carousel>
89
- </div>
90
- </div>
91
- </div>
92
- <div className='w-full h-full bg-background flex flex-row items-center'>
93
- <div className='w-full max-w-[750px] relative flex flex-col items-center px-8 pt-0 text-center'>
94
- <div className='relative h-full w-full max-w-[400px] mx-auto flex flex-col gap-4 items-center py-10'>
95
- <Button
96
- variant='ghost'
97
- onClick={close}
98
- className='block md:hidden absolute rounded-full p-2 left-0 h-auto hover:bg-background'
99
- >
100
- <LuxLogo className='w-5 h-5'/>
101
- </Button>
102
- <Login
103
- getStartedUrl={getStartedUrl}
104
- redirectUrl={redirectUrl}
105
- className='w-full max-w-sm'
106
- termsOfServiceUrl={termsOfServiceUrl}
107
- privacyPolicyUrl={privacyPolicyUrl}
108
- onLoginChanged={onLogin}
109
- />
110
- </div>
111
- </div>
112
- </div>
113
- </div>
114
- )
115
- }
116
-
117
- export default LoginPanel
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import Link from 'next/link'
6
+ import Autoplay from 'embla-carousel-autoplay'
7
+ import { getCookie } from 'cookies-next'
8
+
9
+ import { cn } from '@hanzo/ui/util'
10
+ import { Button, Carousel, CarouselContent, CarouselItem } from '@hanzo/ui/primitives'
11
+ import { LoginPanel as Login } from '@hanzo/auth/components'
12
+
13
+ import { Logo } from '..'
14
+ import LuxLogo from '../icons/lux-logo'
15
+ import { legal } from '../../site-def/footer'
16
+ import domains from './common-auth-domains'
17
+ import SetAuthToken from './auth-token/set-auth-token'
18
+ import ClearAuthToken from './auth-token/clear-auth-token'
19
+
20
+ const LoginPanel: React.FC<{
21
+ close: () => void
22
+ getStartedUrl?: string
23
+ redirectUrl?: string
24
+ className?: string
25
+ reviews: { text: string, author: string, href: string }[]
26
+ }> = ({
27
+ close,
28
+ getStartedUrl='/',
29
+ redirectUrl,
30
+ className='',
31
+ reviews
32
+ }) => {
33
+ const router = useRouter()
34
+
35
+ const [authToken, setAuthToken] = useState<string>(getCookie('auth-token') ?? '')
36
+
37
+ const termsOfServiceUrl = legal.find(({title}) => title === 'Terms and Conditions')?.href || ''
38
+ const privacyPolicyUrl = legal.find(({title}) => title === 'Privacy Policy')?.href || ''
39
+
40
+ const onLogin = (token: string) => {
41
+ setAuthToken(token)
42
+ redirectUrl && setTimeout(() => router.replace(redirectUrl))
43
+ }
44
+
45
+ return (<>
46
+ {!!authToken && <SetAuthToken authToken={authToken} />}
47
+ {!authToken && <ClearAuthToken />}
48
+
49
+ <div className={cn('grid grid-cols-1 md:grid-cols-2', className)}>
50
+ <div className='hidden md:flex w-full h-full bg-level-1 flex-row items-end justify-end overflow-y-auto min-h-screen'>
51
+ <div className='h-full w-full max-w-[750px] px-8 pt-0'>
52
+ <div className='h-full w-full max-w-[550px] mx-auto flex flex-col justify-between min-h-screen py-10'>
53
+ <Button
54
+ variant='ghost'
55
+ onClick={close}
56
+ className='w-fit !min-w-0 p-2'
57
+ >
58
+ <Logo size='md' spanClassName='!cursor-pointer' layout='text-only'/>
59
+ </Button>
60
+ <Carousel
61
+ options={{ align: 'center', loop: true }}
62
+ className='w-full'
63
+ plugins={[Autoplay({ delay: 5000, stopOnInteraction: true })]}
64
+ >
65
+ <CarouselContent>
66
+ {reviews.map(({text, author, href}, index) => (
67
+ <CarouselItem key={index}>
68
+ <Link href={href} className='flex flex-col gap-3 cursor-pointer'>
69
+ <p>“{text}“</p>
70
+ <p className='text-sm'>{author}</p>
71
+ </Link>
72
+ </CarouselItem>
73
+ ))}
74
+ </CarouselContent>
75
+ </Carousel>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <div className='w-full h-full bg-background flex flex-row items-center'>
80
+ <div className='w-full max-w-[750px] relative flex flex-col items-center px-8 pt-0 text-center'>
81
+ <div className='relative h-full w-full max-w-[400px] mx-auto flex flex-col gap-4 items-center py-10'>
82
+ <Button
83
+ variant='ghost'
84
+ onClick={close}
85
+ className='block md:hidden absolute rounded-full p-2 left-0 h-auto hover:bg-background'
86
+ >
87
+ <LuxLogo className='w-5 h-5'/>
88
+ </Button>
89
+ <Login
90
+ getStartedUrl={getStartedUrl}
91
+ redirectUrl={redirectUrl}
92
+ className='w-full max-w-sm'
93
+ termsOfServiceUrl={termsOfServiceUrl}
94
+ privacyPolicyUrl={privacyPolicyUrl}
95
+ onLoginChanged={onLogin}
96
+ />
97
+ </div>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </>)
102
+ }
103
+
104
+ export default LoginPanel
@@ -1,77 +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
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