webapp/src/components/_shared/InviteMembers/InviteMembers.tsx

182 lines
5.8 KiB
TypeScript
Raw Normal View History

import { createInfiniteScroll } from '@solid-primitives/pagination'
import { clsx } from 'clsx'
2024-02-04 11:25:21 +00:00
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { useAuthors } from '~/context/authors'
import { useInbox } from '~/context/inbox'
import { useLocalize } from '~/context/localize'
2024-06-24 17:50:27 +00:00
import { useUI } from '~/context/ui'
import { Author } from '~/graphql/schema/core.gen'
import { AuthorBadge } from '../../Author/AuthorBadge'
import { Button } from '../Button'
import { DropdownSelect } from '../DropdownSelect'
2024-07-21 02:17:42 +00:00
import { InlineLoader } from '../InlineLoader'
import styles from './InviteMembers.module.scss'
type InviteAuthor = Author & { selected: boolean }
type Props = {
title?: string
variant?: 'coauthors' | 'recipients'
}
const PAGE_SIZE = 50
export const InviteMembers = (props: Props) => {
const { t } = useLocalize()
2024-06-24 17:50:27 +00:00
const { hideModal } = useUI()
const roles = [
{
title: t('Editor'),
2024-06-26 08:22:05 +00:00
description: t('Can write and edit text directly, and accept or reject suggestions from others')
},
{
title: t('Co-author'),
2024-06-26 08:22:05 +00:00
description: t('Can make any changes, accept or reject suggestions, and share access with others')
},
{
title: t('Commentator'),
2024-06-26 08:22:05 +00:00
description: t('Can offer edits and comments, but cannot edit the post or share access with others')
}
]
2024-06-24 17:50:27 +00:00
const { authorsSorted } = useAuthors()
2024-02-04 17:40:15 +00:00
const { loadChats, createChat } = useInbox()
const [authorsToInvite, setAuthorsToInvite] = createSignal<InviteAuthor[]>()
const [searchResultAuthors, setSearchResultAuthors] = createSignal<Author[]>()
const [collectionToInvite, setCollectionToInvite] = createSignal<number[]>([])
const fetcher = async (page: number) => {
await new Promise((resolve, reject) => {
const checkDataLoaded = () => {
2024-07-05 19:40:54 +00:00
if ((authorsSorted?.().length || 0) > 0) {
resolve(true)
} else {
setTimeout(checkDataLoaded, 100)
}
}
setTimeout(() => reject(new Error('Timeout waiting for sortedAuthors')), 10000)
checkDataLoaded()
})
const start = page * PAGE_SIZE
const end = start + PAGE_SIZE
2024-01-25 19:16:38 +00:00
const authors = authorsToInvite()?.map((author) => ({ ...author, selected: false }))
2024-06-24 17:50:27 +00:00
return authors?.slice(start, end) || []
}
const [pages, setEl, { end }] = createInfiniteScroll(fetcher)
createEffect(
on(
2024-06-24 17:50:27 +00:00
authorsSorted,
(currentAuthors) => {
setAuthorsToInvite(currentAuthors.map((author) => ({ ...author, selected: false })))
},
2024-06-26 08:22:05 +00:00
{ defer: true }
)
)
const handleInputChange = async (value: string) => {
if (value.length > 1) {
2024-02-04 06:38:45 +00:00
const match = authorsToInvite()?.filter((author) =>
2024-06-26 08:22:05 +00:00
author.name?.toLowerCase().includes(value.toLowerCase())
)
setSearchResultAuthors(match)
} else {
setSearchResultAuthors()
}
}
2024-06-24 17:50:27 +00:00
const handleInvite = (id: number) => {
setCollectionToInvite((prev) => [...prev, id])
}
const handleCloseModal = () => {
setSearchResultAuthors()
2024-06-24 17:50:27 +00:00
setCollectionToInvite([])
hideModal()
}
const handleCreate = async () => {
try {
2024-01-27 06:21:48 +00:00
const initChat = await createChat(collectionToInvite(), 'chat Title')
console.debug('[components.Inbox] create chat result:', initChat)
hideModal()
2024-01-27 06:21:48 +00:00
await loadChats()
} catch (error) {
console.error('handleCreate chat', error)
}
}
return (
<>
<h2>{props.title || t('Invite collaborators')}</h2>
<div class={clsx(styles.InviteMembers)}>
<div class={styles.searchHeader}>
<div class={styles.field}>
<input
class={styles.input}
type="text"
placeholder={t('Write your colleagues name or email')}
onChange={(e) => {
if (props.variant === 'recipients') return
handleInputChange(e.target.value)
}}
onInput={(e) => {
if (props.variant === 'coauthors') return
handleInputChange(e.target.value)
}}
/>
<Show when={props.variant === 'coauthors'}>
<DropdownSelect selectItems={roles} />
</Show>
</div>
<Show when={props.variant === 'coauthors'}>
<Button class={styles.searchButton} variant={'bordered'} size={'M'} value={t('Search')} />
</Show>
</div>
<Show when={props.variant === 'coauthors'}>
<div class={styles.teaser}>
<h3>{t('Coming soon')}</h3>
<p>
{t(
2024-06-26 08:22:05 +00:00
'We are working on collaborative editing of articles and in the near future you will have an amazing opportunity - to create together with your colleagues'
)}
</p>
</div>
</Show>
<Show when={props.variant === 'recipients'}>
<div class={styles.authors}>
<For each={searchResultAuthors() ?? pages()}>
{(author) => (
<div class={styles.author}>
<AuthorBadge
author={author}
nameOnly={true}
inviteView={true}
onInvite={(id) => handleInvite(id)}
/>
</div>
)}
</For>
<Show when={!end()}>
2024-06-24 17:50:27 +00:00
<div ref={(el: HTMLDivElement) => setEl(el, () => true)}>
<InlineLoader />
</div>
</Show>
</div>
</Show>
<div class={styles.actions}>
<Button variant={'bordered'} size={'M'} value={t('Cancel')} onClick={handleCloseModal} />
<Button
variant={'primary'}
size={'M'}
disabled={collectionToInvite().length === 0}
value={t('Start dialog')}
onClick={handleCreate}
/>
</div>
</div>
</>
)
}