You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

add-oauth-button.tsx 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import {
  2. memo,
  3. useCallback,
  4. useMemo,
  5. useState,
  6. } from 'react'
  7. import { useTranslation } from 'react-i18next'
  8. import {
  9. RiClipboardLine,
  10. RiEqualizer2Line,
  11. RiInformation2Fill,
  12. } from '@remixicon/react'
  13. import Button from '@/app/components/base/button'
  14. import type { ButtonProps } from '@/app/components/base/button'
  15. import OAuthClientSettings from './oauth-client-settings'
  16. import cn from '@/utils/classnames'
  17. import type { PluginPayload } from '../types'
  18. import { openOAuthPopup } from '@/hooks/use-oauth'
  19. import Badge from '@/app/components/base/badge'
  20. import {
  21. useGetPluginOAuthClientSchemaHook,
  22. useGetPluginOAuthUrlHook,
  23. } from '../hooks/use-credential'
  24. import type { FormSchema } from '@/app/components/base/form/types'
  25. import { FormTypeEnum } from '@/app/components/base/form/types'
  26. import ActionButton from '@/app/components/base/action-button'
  27. import { useRenderI18nObject } from '@/hooks/use-i18n'
  28. export type AddOAuthButtonProps = {
  29. pluginPayload: PluginPayload
  30. buttonVariant?: ButtonProps['variant']
  31. buttonText?: string
  32. className?: string
  33. buttonLeftClassName?: string
  34. buttonRightClassName?: string
  35. dividerClassName?: string
  36. disabled?: boolean
  37. onUpdate?: () => void
  38. }
  39. const AddOAuthButton = ({
  40. pluginPayload,
  41. buttonVariant = 'primary',
  42. buttonText = 'use oauth',
  43. className,
  44. buttonLeftClassName,
  45. buttonRightClassName,
  46. dividerClassName,
  47. disabled,
  48. onUpdate,
  49. }: AddOAuthButtonProps) => {
  50. const { t } = useTranslation()
  51. const renderI18nObject = useRenderI18nObject()
  52. const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
  53. const { mutateAsync: getPluginOAuthUrl } = useGetPluginOAuthUrlHook(pluginPayload)
  54. const { data, isLoading } = useGetPluginOAuthClientSchemaHook(pluginPayload)
  55. const {
  56. schema = [],
  57. is_oauth_custom_client_enabled,
  58. is_system_oauth_params_exists,
  59. client_params,
  60. redirect_uri,
  61. } = data || {}
  62. const isConfigured = is_system_oauth_params_exists || is_oauth_custom_client_enabled
  63. const handleOAuth = useCallback(async () => {
  64. const { authorization_url } = await getPluginOAuthUrl()
  65. if (authorization_url) {
  66. openOAuthPopup(
  67. authorization_url,
  68. () => onUpdate?.(),
  69. )
  70. }
  71. }, [getPluginOAuthUrl, onUpdate])
  72. const renderCustomLabel = useCallback((item: FormSchema) => {
  73. return (
  74. <div className='w-full'>
  75. <div className='mb-4 flex rounded-xl bg-background-section-burn p-4'>
  76. <div className='mr-3 flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg'>
  77. <RiInformation2Fill className='h-5 w-5 text-text-accent' />
  78. </div>
  79. <div className='w-0 grow'>
  80. <div className='system-sm-regular mb-1.5'>
  81. {t('plugin.auth.clientInfo')}
  82. </div>
  83. {
  84. redirect_uri && (
  85. <div className='system-sm-medium flex w-full py-0.5'>
  86. <div className='w-0 grow break-words'>{redirect_uri}</div>
  87. <ActionButton
  88. className='shrink-0'
  89. onClick={() => {
  90. navigator.clipboard.writeText(redirect_uri || '')
  91. }}
  92. >
  93. <RiClipboardLine className='h-4 w-4' />
  94. </ActionButton>
  95. </div>
  96. )
  97. }
  98. </div>
  99. </div>
  100. <div className='system-sm-medium flex h-6 items-center text-text-secondary'>
  101. {renderI18nObject(item.label as Record<string, string>)}
  102. {
  103. item.required && (
  104. <span className='ml-1 text-text-destructive-secondary'>*</span>
  105. )
  106. }
  107. </div>
  108. </div>
  109. )
  110. }, [t, redirect_uri, renderI18nObject])
  111. const memorizedSchemas = useMemo(() => {
  112. const result: FormSchema[] = schema.map((item, index) => {
  113. return {
  114. ...item,
  115. label: index === 0 ? renderCustomLabel(item) : item.label,
  116. labelClassName: index === 0 ? 'h-auto' : undefined,
  117. }
  118. })
  119. if (is_system_oauth_params_exists) {
  120. result.unshift({
  121. name: '__oauth_client__',
  122. label: t('plugin.auth.oauthClient'),
  123. type: FormTypeEnum.radio,
  124. options: [
  125. {
  126. label: t('plugin.auth.default'),
  127. value: 'default',
  128. },
  129. {
  130. label: t('plugin.auth.custom'),
  131. value: 'custom',
  132. },
  133. ],
  134. required: false,
  135. default: is_oauth_custom_client_enabled ? 'custom' : 'default',
  136. } as FormSchema)
  137. result.forEach((item, index) => {
  138. if (index > 0) {
  139. item.show_on = [
  140. {
  141. variable: '__oauth_client__',
  142. value: 'custom',
  143. },
  144. ]
  145. if (client_params)
  146. item.default = client_params[item.name] || item.default
  147. }
  148. })
  149. }
  150. return result
  151. }, [schema, renderCustomLabel, t, is_system_oauth_params_exists, is_oauth_custom_client_enabled, client_params])
  152. const __auth_client__ = useMemo(() => {
  153. if (isConfigured) {
  154. if (is_oauth_custom_client_enabled)
  155. return 'custom'
  156. return 'default'
  157. }
  158. else {
  159. if (is_system_oauth_params_exists)
  160. return 'default'
  161. return 'custom'
  162. }
  163. }, [isConfigured, is_oauth_custom_client_enabled, is_system_oauth_params_exists])
  164. return (
  165. <>
  166. {
  167. isConfigured && (
  168. <Button
  169. variant={buttonVariant}
  170. className={cn(
  171. 'w-full px-0 py-0 hover:bg-components-button-primary-bg',
  172. className,
  173. )}
  174. disabled={disabled}
  175. onClick={handleOAuth}
  176. >
  177. <div className={cn(
  178. 'flex h-full w-0 grow items-center justify-center rounded-l-lg pl-0.5 hover:bg-components-button-primary-bg-hover',
  179. buttonLeftClassName,
  180. )}>
  181. <div
  182. className='truncate'
  183. title={buttonText}
  184. >
  185. {buttonText}
  186. </div>
  187. {
  188. is_oauth_custom_client_enabled && (
  189. <Badge
  190. className={cn(
  191. 'ml-1 mr-0.5',
  192. buttonVariant === 'primary' && 'border-text-primary-on-surface bg-components-badge-bg-dimm text-text-primary-on-surface',
  193. )}
  194. >
  195. {t('plugin.auth.custom')}
  196. </Badge>
  197. )
  198. }
  199. </div>
  200. <div className={cn(
  201. 'h-4 w-[1px] shrink-0 bg-text-primary-on-surface opacity-[0.15]',
  202. dividerClassName,
  203. )}></div>
  204. <div
  205. className={cn(
  206. 'flex h-full w-8 shrink-0 items-center justify-center rounded-r-lg hover:bg-components-button-primary-bg-hover',
  207. buttonRightClassName,
  208. )}
  209. onClick={(e) => {
  210. e.stopPropagation()
  211. setIsOAuthSettingsOpen(true)
  212. }}
  213. >
  214. <RiEqualizer2Line className='h-4 w-4' />
  215. </div>
  216. </Button>
  217. )
  218. }
  219. {
  220. !isConfigured && (
  221. <Button
  222. variant={buttonVariant}
  223. onClick={() => setIsOAuthSettingsOpen(true)}
  224. disabled={disabled}
  225. className='w-full'
  226. >
  227. <RiEqualizer2Line className='mr-0.5 h-4 w-4' />
  228. {t('plugin.auth.setupOAuth')}
  229. </Button>
  230. )
  231. }
  232. {
  233. isOAuthSettingsOpen && (
  234. <OAuthClientSettings
  235. pluginPayload={pluginPayload}
  236. onClose={() => setIsOAuthSettingsOpen(false)}
  237. disabled={disabled || isLoading}
  238. schemas={memorizedSchemas}
  239. onAuth={handleOAuth}
  240. editValues={{
  241. ...client_params,
  242. __oauth_client__: __auth_client__,
  243. }}
  244. hasOriginalClientParams={Object.keys(client_params || {}).length > 0}
  245. onUpdate={onUpdate}
  246. />
  247. )
  248. }
  249. </>
  250. )
  251. }
  252. export default memo(AddOAuthButton)