index.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. 'use client'
  2. import { useTranslation } from 'react-i18next'
  3. import { useEffect, useRef, useState } from 'react'
  4. import {
  5. RiAccountCircleFill,
  6. RiAccountCircleLine,
  7. RiApps2AddFill,
  8. RiApps2AddLine,
  9. RiBox3Fill,
  10. RiBox3Line,
  11. RiCloseLine,
  12. RiColorFilterFill,
  13. RiColorFilterLine,
  14. RiDatabase2Fill,
  15. RiDatabase2Line,
  16. RiGroup2Fill,
  17. RiGroup2Line,
  18. RiMoneyDollarCircleFill,
  19. RiMoneyDollarCircleLine,
  20. RiPuzzle2Fill,
  21. RiPuzzle2Line,
  22. RiTranslate2,
  23. } from '@remixicon/react'
  24. import AccountPage from './account-page'
  25. import MembersPage from './members-page'
  26. import IntegrationsPage from './Integrations-page'
  27. import LanguagePage from './language-page'
  28. import ApiBasedExtensionPage from './api-based-extension-page'
  29. import DataSourcePage from './data-source-page'
  30. import ModelProviderPage from './model-provider-page'
  31. import s from './index.module.css'
  32. import cn from '@/utils/classnames'
  33. import BillingPage from '@/app/components/billing/billing-page'
  34. import CustomPage from '@/app/components/custom/custom-page'
  35. import Modal from '@/app/components/base/modal'
  36. import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
  37. import { useProviderContext } from '@/context/provider-context'
  38. import { useAppContext } from '@/context/app-context'
  39. const iconClassName = `
  40. w-4 h-4 ml-3 mr-2
  41. `
  42. const scrolledClassName = `
  43. border-b shadow-xs bg-white/[.98]
  44. `
  45. type IAccountSettingProps = {
  46. onCancel: () => void
  47. activeTab?: string
  48. }
  49. type GroupItem = {
  50. key: string
  51. name: string
  52. description?: string
  53. icon: JSX.Element
  54. activeIcon: JSX.Element
  55. }
  56. export default function AccountSetting({
  57. onCancel,
  58. activeTab = 'account',
  59. }: IAccountSettingProps) {
  60. const [activeMenu, setActiveMenu] = useState(activeTab)
  61. const { t } = useTranslation()
  62. const { enableBilling, enableReplaceWebAppLogo } = useProviderContext()
  63. const { isCurrentWorkspaceDatasetOperator } = useAppContext()
  64. const workplaceGroupItems = (() => {
  65. if (isCurrentWorkspaceDatasetOperator)
  66. return []
  67. return [
  68. {
  69. key: 'provider',
  70. name: t('common.settings.provider'),
  71. icon: <RiBox3Line className={iconClassName} />,
  72. activeIcon: <RiBox3Fill className={iconClassName} />,
  73. },
  74. {
  75. key: 'members',
  76. name: t('common.settings.members'),
  77. icon: <RiGroup2Line className={iconClassName} />,
  78. activeIcon: <RiGroup2Fill className={iconClassName} />,
  79. },
  80. {
  81. // Use key false to hide this item
  82. key: enableBilling ? 'billing' : false,
  83. name: t('common.settings.billing'),
  84. description: t('billing.plansCommon.receiptInfo'),
  85. icon: <RiMoneyDollarCircleLine className={iconClassName} />,
  86. activeIcon: <RiMoneyDollarCircleFill className={iconClassName} />,
  87. },
  88. {
  89. key: 'data-source',
  90. name: t('common.settings.dataSource'),
  91. icon: <RiDatabase2Line className={iconClassName} />,
  92. activeIcon: <RiDatabase2Fill className={iconClassName} />,
  93. },
  94. {
  95. key: 'api-based-extension',
  96. name: t('common.settings.apiBasedExtension'),
  97. icon: <RiPuzzle2Line className={iconClassName} />,
  98. activeIcon: <RiPuzzle2Fill className={iconClassName} />,
  99. },
  100. {
  101. key: (enableReplaceWebAppLogo || enableBilling) ? 'custom' : false,
  102. name: t('custom.custom'),
  103. icon: <RiColorFilterLine className={iconClassName} />,
  104. activeIcon: <RiColorFilterFill className={iconClassName} />,
  105. },
  106. ].filter(item => !!item.key) as GroupItem[]
  107. })()
  108. const media = useBreakpoints()
  109. const isMobile = media === MediaType.mobile
  110. const menuItems = [
  111. {
  112. key: 'workspace-group',
  113. name: t('common.settings.workplaceGroup'),
  114. items: workplaceGroupItems,
  115. },
  116. {
  117. key: 'account-group',
  118. name: t('common.settings.accountGroup'),
  119. items: [
  120. {
  121. key: 'account',
  122. name: t('common.settings.account'),
  123. icon: <RiAccountCircleLine className={iconClassName} />,
  124. activeIcon: <RiAccountCircleFill className={iconClassName} />,
  125. },
  126. {
  127. key: 'integrations',
  128. name: t('common.settings.integrations'),
  129. icon: <RiApps2AddLine className={iconClassName} />,
  130. activeIcon: <RiApps2AddFill className={iconClassName} />,
  131. },
  132. {
  133. key: 'language',
  134. name: t('common.settings.language'),
  135. icon: <RiTranslate2 className={iconClassName} />,
  136. activeIcon: <RiTranslate2 className={iconClassName} />,
  137. },
  138. ],
  139. },
  140. ]
  141. const scrollRef = useRef<HTMLDivElement>(null)
  142. const [scrolled, setScrolled] = useState(false)
  143. useEffect(() => {
  144. const targetElement = scrollRef.current
  145. const scrollHandle = (e: Event) => {
  146. const userScrolled = (e.target as HTMLDivElement).scrollTop > 0
  147. setScrolled(userScrolled)
  148. }
  149. targetElement?.addEventListener('scroll', scrollHandle)
  150. return () => {
  151. targetElement?.removeEventListener('scroll', scrollHandle)
  152. }
  153. }, [])
  154. const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)
  155. return (
  156. <Modal
  157. isShow
  158. onClose={() => { }}
  159. className={s.modal}
  160. wrapperClassName='pt-[60px]'
  161. >
  162. <div className='flex'>
  163. <div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-gray-100 shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
  164. <div className='mb-8 ml-0 sm:ml-2 text-sm sm:text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
  165. <div className='w-full'>
  166. {
  167. menuItems.map(menuItem => (
  168. <div key={menuItem.key} className='mb-4'>
  169. {!isCurrentWorkspaceDatasetOperator && (
  170. <div className='px-2 mb-[6px] text-[10px] sm:text-xs font-medium text-gray-500'>{menuItem.name}</div>
  171. )}
  172. <div>
  173. {
  174. menuItem.items.map(item => (
  175. <div
  176. key={item.key}
  177. className={`
  178. flex items-center h-[37px] mb-[2px] text-sm cursor-pointer rounded-lg
  179. ${activeMenu === item.key ? 'font-semibold text-primary-600 bg-primary-50' : 'font-light text-gray-700'}
  180. `}
  181. title={item.name}
  182. onClick={() => setActiveMenu(item.key)}
  183. >
  184. {activeMenu === item.key ? item.activeIcon : item.icon}
  185. {!isMobile && <div className='truncate'>{item.name}</div>}
  186. </div>
  187. ))
  188. }
  189. </div>
  190. </div>
  191. ))
  192. }
  193. </div>
  194. </div>
  195. <div ref={scrollRef} className='relative w-[824px] h-[720px] pb-4 overflow-y-auto'>
  196. <div className={cn('sticky top-0 px-6 py-4 flex items-center h-14 mb-4 bg-white text-base font-medium text-gray-900 z-20', scrolled && scrolledClassName)}>
  197. <div className='shrink-0'>{activeItem?.name}</div>
  198. {
  199. activeItem?.description && (
  200. <div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
  201. )
  202. }
  203. <div className='grow flex justify-end'>
  204. <div className='flex items-center justify-center -mr-4 w-6 h-6 cursor-pointer' onClick={onCancel}>
  205. <RiCloseLine className='w-4 h-4 text-gray-400' />
  206. </div>
  207. </div>
  208. </div>
  209. <div className='px-4 sm:px-8 pt-2'>
  210. {activeMenu === 'account' && <AccountPage />}
  211. {activeMenu === 'members' && <MembersPage />}
  212. {activeMenu === 'billing' && <BillingPage />}
  213. {activeMenu === 'integrations' && <IntegrationsPage />}
  214. {activeMenu === 'language' && <LanguagePage />}
  215. {activeMenu === 'provider' && <ModelProviderPage />}
  216. {activeMenu === 'data-source' && <DataSourcePage />}
  217. {activeMenu === 'api-based-extension' && <ApiBasedExtensionPage />}
  218. {activeMenu === 'custom' && <CustomPage />}
  219. </div>
  220. </div>
  221. </div>
  222. </Modal>
  223. )
  224. }