Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. 'use client'
  2. import type { MouseEventHandler } from 'react'
  3. import { useMemo, useRef, useState } from 'react'
  4. import { useRouter } from 'next/navigation'
  5. import { useContext } from 'use-context-selector'
  6. import { useTranslation } from 'react-i18next'
  7. import { RiCloseLine } from '@remixicon/react'
  8. import Uploader from './uploader'
  9. import Button from '@/app/components/base/button'
  10. import Input from '@/app/components/base/input'
  11. import Modal from '@/app/components/base/modal'
  12. import { ToastContext } from '@/app/components/base/toast'
  13. import {
  14. importApp,
  15. importAppFromUrl,
  16. } from '@/service/apps'
  17. import { useAppContext } from '@/context/app-context'
  18. import { useProviderContext } from '@/context/provider-context'
  19. import AppsFull from '@/app/components/billing/apps-full-in-dialog'
  20. import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
  21. import { getRedirection } from '@/utils/app-redirection'
  22. import cn from '@/utils/classnames'
  23. type CreateFromDSLModalProps = {
  24. show: boolean
  25. onSuccess?: () => void
  26. onClose: () => void
  27. activeTab?: string
  28. dslUrl?: string
  29. }
  30. export enum CreateFromDSLModalTab {
  31. FROM_FILE = 'from-file',
  32. FROM_URL = 'from-url',
  33. }
  34. const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDSLModalTab.FROM_FILE, dslUrl = '' }: CreateFromDSLModalProps) => {
  35. const { push } = useRouter()
  36. const { t } = useTranslation()
  37. const { notify } = useContext(ToastContext)
  38. const [currentFile, setDSLFile] = useState<File>()
  39. const [fileContent, setFileContent] = useState<string>()
  40. const [currentTab, setCurrentTab] = useState(activeTab)
  41. const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
  42. const readFile = (file: File) => {
  43. const reader = new FileReader()
  44. reader.onload = function (event) {
  45. const content = event.target?.result
  46. setFileContent(content as string)
  47. }
  48. reader.readAsText(file)
  49. }
  50. const handleFile = (file?: File) => {
  51. setDSLFile(file)
  52. if (file)
  53. readFile(file)
  54. if (!file)
  55. setFileContent('')
  56. }
  57. const { isCurrentWorkspaceEditor } = useAppContext()
  58. const { plan, enableBilling } = useProviderContext()
  59. const isAppsFull = (enableBilling && plan.usage.buildApps >= plan.total.buildApps)
  60. const isCreatingRef = useRef(false)
  61. const onCreate: MouseEventHandler = async () => {
  62. if (currentTab === CreateFromDSLModalTab.FROM_FILE && !currentFile)
  63. return
  64. if (currentTab === CreateFromDSLModalTab.FROM_URL && !dslUrlValue)
  65. return
  66. if (isCreatingRef.current)
  67. return
  68. isCreatingRef.current = true
  69. try {
  70. let app
  71. if (currentTab === CreateFromDSLModalTab.FROM_FILE) {
  72. app = await importApp({
  73. data: fileContent || '',
  74. })
  75. }
  76. if (currentTab === CreateFromDSLModalTab.FROM_URL) {
  77. app = await importAppFromUrl({
  78. url: dslUrlValue || '',
  79. })
  80. }
  81. if (onSuccess)
  82. onSuccess()
  83. if (onClose)
  84. onClose()
  85. notify({ type: 'success', message: t('app.newApp.appCreated') })
  86. localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1')
  87. getRedirection(isCurrentWorkspaceEditor, app, push)
  88. }
  89. catch (e) {
  90. notify({ type: 'error', message: t('app.newApp.appCreateFailed') })
  91. }
  92. isCreatingRef.current = false
  93. }
  94. const tabs = [
  95. {
  96. key: CreateFromDSLModalTab.FROM_FILE,
  97. label: t('app.importFromDSLFile'),
  98. },
  99. {
  100. key: CreateFromDSLModalTab.FROM_URL,
  101. label: t('app.importFromDSLUrl'),
  102. },
  103. ]
  104. const buttonDisabled = useMemo(() => {
  105. if (isAppsFull)
  106. return true
  107. if (currentTab === CreateFromDSLModalTab.FROM_FILE)
  108. return !currentFile
  109. if (currentTab === CreateFromDSLModalTab.FROM_URL)
  110. return !dslUrlValue
  111. return false
  112. }, [isAppsFull, currentTab, currentFile, dslUrlValue])
  113. return (
  114. <Modal
  115. className='p-0 w-[520px] rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
  116. isShow={show}
  117. onClose={() => { }}
  118. >
  119. <div className='flex items-center justify-between pt-6 pl-6 pr-5 pb-3 text-text-primary title-2xl-semi-bold'>
  120. {t('app.importFromDSL')}
  121. <div
  122. className='flex items-center w-8 h-8 cursor-pointer'
  123. onClick={() => onClose()}
  124. >
  125. <RiCloseLine className='w-5 h-5 text-text-tertiary' />
  126. </div>
  127. </div>
  128. <div className='flex items-center px-6 h-9 space-x-6 system-md-semibold text-text-tertiary border-b border-divider-subtle'>
  129. {
  130. tabs.map(tab => (
  131. <div
  132. key={tab.key}
  133. className={cn(
  134. 'relative flex items-center h-full cursor-pointer',
  135. currentTab === tab.key && 'text-text-primary',
  136. )}
  137. onClick={() => setCurrentTab(tab.key)}
  138. >
  139. {tab.label}
  140. {
  141. currentTab === tab.key && (
  142. <div className='absolute bottom-0 w-full h-[2px] bg-util-colors-blue-brand-blue-brand-600'></div>
  143. )
  144. }
  145. </div>
  146. ))
  147. }
  148. </div>
  149. <div className='px-6 py-4'>
  150. {
  151. currentTab === CreateFromDSLModalTab.FROM_FILE && (
  152. <Uploader
  153. className='mt-0'
  154. file={currentFile}
  155. updateFile={handleFile}
  156. />
  157. )
  158. }
  159. {
  160. currentTab === CreateFromDSLModalTab.FROM_URL && (
  161. <div>
  162. <div className='mb-1 system-md-semibold leading6'>DSL URL</div>
  163. <Input
  164. placeholder={t('app.importFromDSLUrlPlaceholder') || ''}
  165. value={dslUrlValue}
  166. onChange={e => setDslUrlValue(e.target.value)}
  167. />
  168. </div>
  169. )
  170. }
  171. </div>
  172. {isAppsFull && (
  173. <div className='px-6'>
  174. <AppsFull className='mt-0' loc='app-create-dsl' />
  175. </div>
  176. )}
  177. <div className='flex justify-end px-6 py-5'>
  178. <Button className='mr-2' onClick={onClose}>{t('app.newApp.Cancel')}</Button>
  179. <Button disabled={buttonDisabled} variant="primary" onClick={onCreate}>{t('app.newApp.Create')}</Button>
  180. </div>
  181. </Modal>
  182. )
  183. }
  184. export default CreateFromDSLModal