index.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { ChevronRightIcon } from '@heroicons/react/20/solid'
  5. import Link from 'next/link'
  6. import { Trans, useTranslation } from 'react-i18next'
  7. import s from './style.module.css'
  8. import Modal from '@/app/components/base/modal'
  9. import Button from '@/app/components/base/button'
  10. import AppIcon from '@/app/components/base/app-icon'
  11. import { SimpleSelect } from '@/app/components/base/select'
  12. import type { AppDetailResponse } from '@/models/app'
  13. import type { Language } from '@/types/app'
  14. import EmojiPicker from '@/app/components/base/emoji-picker'
  15. export type ISettingsModalProps = {
  16. appInfo: AppDetailResponse
  17. isShow: boolean
  18. defaultValue?: string
  19. onClose: () => void
  20. onSave: (params: ConfigParams) => Promise<any>
  21. }
  22. export type ConfigParams = {
  23. title: string
  24. description: string
  25. default_language: string
  26. prompt_public: boolean
  27. }
  28. const LANGUAGE_MAP: Record<Language, string> = {
  29. 'en-US': 'English(United States)',
  30. 'zh-Hans': '简体中文',
  31. }
  32. const prefixSettings = 'appOverview.overview.appInfo.settings'
  33. const SettingsModal: FC<ISettingsModalProps> = ({
  34. appInfo,
  35. isShow = false,
  36. onClose,
  37. onSave,
  38. }) => {
  39. const [isShowMore, setIsShowMore] = useState(false)
  40. const { title, description, copyright, privacy_policy, default_language, icon, icon_background } = appInfo.site
  41. const [inputInfo, setInputInfo] = useState({ title, desc: description, copyright, privacyPolicy: privacy_policy })
  42. const [language, setLanguage] = useState(default_language)
  43. const [saveLoading, setSaveLoading] = useState(false)
  44. const { t } = useTranslation()
  45. // Emoji Picker
  46. const [showEmojiPicker, setShowEmojiPicker] = useState(false)
  47. const [emoji, setEmoji] = useState({ icon, icon_background })
  48. const onHide = () => {
  49. onClose()
  50. setTimeout(() => {
  51. setIsShowMore(false)
  52. }, 200)
  53. }
  54. const onClickSave = async () => {
  55. setSaveLoading(true)
  56. const params = {
  57. title: inputInfo.title,
  58. description: inputInfo.desc,
  59. default_language: language,
  60. prompt_public: false,
  61. copyright: inputInfo.copyright,
  62. privacy_policy: inputInfo.privacyPolicy,
  63. icon: emoji.icon,
  64. icon_background: emoji.icon_background,
  65. }
  66. await onSave(params)
  67. setSaveLoading(false)
  68. onHide()
  69. }
  70. const onChange = (field: string) => {
  71. return (e: any) => {
  72. setInputInfo(item => ({ ...item, [field]: e.target.value }))
  73. }
  74. }
  75. return (
  76. <>
  77. {showEmojiPicker && <EmojiPicker
  78. onSelect={(icon, icon_background) => {
  79. console.log(icon, icon_background)
  80. setEmoji({ icon, icon_background })
  81. setShowEmojiPicker(false)
  82. }}
  83. onClose={() => {
  84. setEmoji({ icon: '🤖', icon_background: '#FFEAD5' })
  85. setShowEmojiPicker(false)
  86. }}
  87. />}
  88. <Modal
  89. title={t(`${prefixSettings}.title`)}
  90. isShow={isShow}
  91. onClose={onHide}
  92. className={`${s.settingsModal}`}
  93. >
  94. <div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.webName`)}</div>
  95. <div className='flex mt-2'>
  96. <AppIcon size='large'
  97. onClick={() => { setShowEmojiPicker(true) }}
  98. className='cursor-pointer !mr-3 self-center'
  99. icon={emoji.icon}
  100. background={emoji.icon_background}
  101. />
  102. <input className={`flex-grow rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
  103. value={inputInfo.title}
  104. onChange={onChange('title')} />
  105. </div>
  106. <div className={`mt-6 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.webDesc`)}</div>
  107. <p className={`mt-1 ${s.settingsTip} text-gray-500`}>{t(`${prefixSettings}.webDescTip`)}</p>
  108. <textarea
  109. rows={3}
  110. className={`mt-2 pt-2 pb-2 px-3 rounded-lg bg-gray-100 w-full ${s.settingsTip} text-gray-900`}
  111. value={inputInfo.desc}
  112. onChange={onChange('desc')}
  113. placeholder={t(`${prefixSettings}.webDescPlaceholder`) as string}
  114. />
  115. <div className={`mt-6 mb-2 font-medium ${s.settingTitle} text-gray-900 `}>{t(`${prefixSettings}.language`)}</div>
  116. <SimpleSelect
  117. items={Object.keys(LANGUAGE_MAP).map(lang => ({ name: LANGUAGE_MAP[lang as Language], value: lang }))}
  118. defaultValue={language}
  119. onSelect={item => setLanguage(item.value as Language)}
  120. />
  121. {!isShowMore && <div className='w-full cursor-pointer mt-8' onClick={() => setIsShowMore(true)}>
  122. <div className='flex justify-between'>
  123. <div className={`font-medium ${s.settingTitle} flex-grow text-gray-900`}>{t(`${prefixSettings}.more.entry`)}</div>
  124. <div className='flex-shrink-0 w-4 h-4 text-gray-500'>
  125. <ChevronRightIcon />
  126. </div>
  127. </div>
  128. <p className={`mt-1 ${s.policy} text-gray-500`}>{t(`${prefixSettings}.more.copyright`)} & {t(`${prefixSettings}.more.privacyPolicy`)}</p>
  129. </div>}
  130. {isShowMore && <>
  131. <hr className='w-full mt-6' />
  132. <div className={`mt-6 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.copyright`)}</div>
  133. <input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
  134. value={inputInfo.copyright}
  135. onChange={onChange('copyright')}
  136. placeholder={t(`${prefixSettings}.more.copyRightPlaceholder`) as string}
  137. />
  138. <div className={`mt-8 font-medium ${s.settingTitle} text-gray-900`}>{t(`${prefixSettings}.more.privacyPolicy`)}</div>
  139. <p className={`mt-1 ${s.settingsTip} text-gray-500`}>
  140. <Trans
  141. i18nKey={`${prefixSettings}.more.privacyPolicyTip`}
  142. components={{ privacyPolicyLink: <Link href={'https://langgenius.ai/privacy-policy'} target='_blank' className='text-primary-600' /> }}
  143. />
  144. </p>
  145. <input className={`w-full mt-2 rounded-lg h-10 box-border px-3 ${s.projectName} bg-gray-100`}
  146. value={inputInfo.privacyPolicy}
  147. onChange={onChange('privacyPolicy')}
  148. placeholder={t(`${prefixSettings}.more.privacyPolicyPlaceholder`) as string}
  149. />
  150. </>}
  151. <div className='mt-10 flex justify-end'>
  152. <Button className='mr-2 flex-shrink-0' onClick={onHide}>{t('common.operation.cancel')}</Button>
  153. <Button type='primary' className='flex-shrink-0' onClick={onClickSave} loading={saveLoading}>{t('common.operation.save')}</Button>
  154. </div>
  155. </Modal >
  156. </>
  157. )
  158. }
  159. export default React.memo(SettingsModal)