Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

index.tsx 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. 'use client'
  2. import { useCallback, useEffect, useState } from 'react'
  3. import { useContext } from 'use-context-selector'
  4. import { RiCloseLine } from '@remixicon/react'
  5. import { useTranslation } from 'react-i18next'
  6. import { ReactMultiEmail } from 'react-multi-email'
  7. import { RiErrorWarningFill } from '@remixicon/react'
  8. import RoleSelector from './role-selector'
  9. import s from './index.module.css'
  10. import cn from '@/utils/classnames'
  11. import Modal from '@/app/components/base/modal'
  12. import Button from '@/app/components/base/button'
  13. import { inviteMember } from '@/service/common'
  14. import { emailRegex } from '@/config'
  15. import { ToastContext } from '@/app/components/base/toast'
  16. import type { InvitationResult } from '@/models/common'
  17. import I18n from '@/context/i18n'
  18. import 'react-multi-email/dist/style.css'
  19. import { noop } from 'lodash-es'
  20. import { useProviderContextSelector } from '@/context/provider-context'
  21. type IInviteModalProps = {
  22. isEmailSetup: boolean
  23. onCancel: () => void
  24. onSend: (invitationResults: InvitationResult[]) => void
  25. }
  26. const InviteModal = ({
  27. isEmailSetup,
  28. onCancel,
  29. onSend,
  30. }: IInviteModalProps) => {
  31. const { t } = useTranslation()
  32. const licenseLimit = useProviderContextSelector(s => s.licenseLimit)
  33. const refreshLicenseLimit = useProviderContextSelector(s => s.refreshLicenseLimit)
  34. const [emails, setEmails] = useState<string[]>([])
  35. const { notify } = useContext(ToastContext)
  36. const [isLimited, setIsLimited] = useState(false)
  37. const [isLimitExceeded, setIsLimitExceeded] = useState(false)
  38. const [usedSize, setUsedSize] = useState(licenseLimit.workspace_members.size ?? 0)
  39. useEffect(() => {
  40. const limited = licenseLimit.workspace_members.limit > 0
  41. const used = emails.length + licenseLimit.workspace_members.size
  42. setIsLimited(limited)
  43. setUsedSize(used)
  44. setIsLimitExceeded(limited && (used > licenseLimit.workspace_members.limit))
  45. }, [licenseLimit, emails])
  46. const { locale } = useContext(I18n)
  47. const [role, setRole] = useState<string>('normal')
  48. const handleSend = useCallback(async () => {
  49. if (isLimitExceeded)
  50. return
  51. if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) {
  52. try {
  53. const { result, invitation_results } = await inviteMember({
  54. url: '/workspaces/current/members/invite-email',
  55. body: { emails, role, language: locale },
  56. })
  57. if (result === 'success') {
  58. refreshLicenseLimit()
  59. onCancel()
  60. onSend(invitation_results)
  61. }
  62. }
  63. catch { }
  64. }
  65. else {
  66. notify({ type: 'error', message: t('common.members.emailInvalid') })
  67. }
  68. }, [isLimitExceeded, emails, role, locale, onCancel, onSend, notify, t])
  69. return (
  70. <div className={cn(s.wrap)}>
  71. <Modal overflowVisible isShow onClose={noop} className={cn(s.modal)}>
  72. <div className='mb-2 flex justify-between'>
  73. <div className='text-xl font-semibold text-text-primary'>{t('common.members.inviteTeamMember')}</div>
  74. <RiCloseLine className='h-4 w-4 cursor-pointer text-text-tertiary' onClick={onCancel} />
  75. </div>
  76. <div className='mb-3 text-[13px] text-text-tertiary'>{t('common.members.inviteTeamMemberTip')}</div>
  77. {!isEmailSetup && (
  78. <div className='grow basis-0 overflow-y-auto pb-4'>
  79. <div className='relative mb-1 rounded-xl border border-components-panel-border p-2 shadow-xs'>
  80. <div className='absolute left-0 top-0 h-full w-full rounded-xl opacity-40' style={{ background: 'linear-gradient(92deg, rgba(255, 171, 0, 0.25) 18.12%, rgba(255, 255, 255, 0.00) 167.31%)' }}></div>
  81. <div className='relative flex h-full w-full items-start'>
  82. <div className='mr-0.5 shrink-0 p-0.5'>
  83. <RiErrorWarningFill className='h-5 w-5 text-text-warning' />
  84. </div>
  85. <div className='system-xs-medium text-text-primary'>
  86. <span>{t('common.members.emailNotSetup')}</span>
  87. </div>
  88. </div>
  89. </div>
  90. </div>
  91. )}
  92. <div>
  93. <div className='mb-2 text-sm font-medium text-text-primary'>{t('common.members.email')}</div>
  94. <div className='mb-8 flex h-36 flex-col items-stretch'>
  95. <ReactMultiEmail
  96. className={cn('w-full border-components-input-border-active !bg-components-input-bg-normal px-3 pt-2 outline-none',
  97. 'appearance-none overflow-y-auto rounded-lg text-sm !text-text-primary',
  98. )}
  99. autoFocus
  100. emails={emails}
  101. inputClassName='bg-transparent'
  102. onChange={setEmails}
  103. getLabel={(email, index, removeEmail) =>
  104. <div data-tag key={index} className={cn('bg-components-button-secondary-bg')}>
  105. <div data-tag-item>{email}</div>
  106. <span data-tag-handle onClick={() => removeEmail(index)}>
  107. ×
  108. </span>
  109. </div>
  110. }
  111. placeholder={t('common.members.emailPlaceholder') || ''}
  112. />
  113. <div className={
  114. cn('system-xs-regular flex items-center justify-end text-text-tertiary',
  115. (isLimited && usedSize > licenseLimit.workspace_members.limit) ? 'text-text-destructive' : '')}
  116. >
  117. <span>{usedSize}</span>
  118. <span>/</span>
  119. <span>{isLimited ? licenseLimit.workspace_members.limit : t('common.license.unlimited')}</span>
  120. </div>
  121. </div>
  122. <div className='mb-6'>
  123. <RoleSelector value={role} onChange={setRole} />
  124. </div>
  125. <Button
  126. tabIndex={0}
  127. className='w-full'
  128. onClick={handleSend}
  129. disabled={!emails.length || isLimitExceeded}
  130. variant='primary'
  131. >
  132. {t('common.members.sendInvite')}
  133. </Button>
  134. </div>
  135. </Modal>
  136. </div>
  137. )
  138. }
  139. export default InviteModal