From b53f83947c2627a62a0e8a90102e6673d44509f0 Mon Sep 17 00:00:00 2001 From: Untone Date: Fri, 2 Aug 2024 00:32:52 +0300 Subject: [PATCH] inbox-route --- README.md | 6 +- codegen.yml | 3 - .../Author/AuthorBadge/AuthorBadge.tsx | 9 +- .../Author/AuthorCard/AuthorCard.tsx | 38 ++- src/components/Inbox/CreateModalContent.tsx | 4 +- src/components/Views/Inbox/Inbox.tsx | 221 +++++++++--------- .../_shared/InviteMembers/InviteMembers.tsx | 4 +- src/routes/author/(all-authors).tsx | 2 +- src/routes/inbox/(chats).tsx | 28 +++ src/routes/inbox/[chat].tsx | 53 +++++ 10 files changed, 207 insertions(+), 161 deletions(-) create mode 100644 src/routes/inbox/(chats).tsx create mode 100644 src/routes/inbox/[chat].tsx diff --git a/README.md b/README.md index 8f3f4157..81e3a1fe 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ bun run typecheck bun run fix ``` - ## End-to-End (E2E) тесты End-to-end тесты написаны с использованием [Playwright](https://playwright.dev/). @@ -47,10 +46,7 @@ End-to-end тесты написаны с использованием [Playwrig ### 🚀 Тесты в режиме CI -Тесты выполняются в рамках GitHub workflow. Мы организуем наши тесты в две основные директории: - -- `tests`: Содержит тесты, которые не требуют аутентификации. -- `tests-with-auth`: Содержит тесты, которые взаимодействуют с аутентифицированными частями приложения. +Тесты выполняются в рамках GitHub workflow из папки `tests` 🔧 **Конфигурация:** diff --git a/codegen.yml b/codegen.yml index eb4811ba..31d62db2 100644 --- a/codegen.yml +++ b/codegen.yml @@ -25,6 +25,3 @@ generates: useTypeImports: true outputPath: './src/graphql/types/core.gen.ts' # namingConvention: change-case#CamelCase # for generated types -hooks: - afterAllFileWrite: - - prettier --ignore-path .gitignore --write --plugin-search-dir=. src/graphql/schema/*.gen.ts diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx index 8531f3f5..a6e31708 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.tsx +++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx @@ -1,7 +1,6 @@ +import { useNavigate } from '@solidjs/router' import { clsx } from 'clsx' import { Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js' - -import { useNavigate, useSearchParams } from '@solidjs/router' import { Button } from '~/components/_shared/Button' import { CheckButton } from '~/components/_shared/CheckButton' import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' @@ -49,17 +48,13 @@ export const AuthorBadge = (props: Props) => { ) ) - const [, changeSearchParams] = useSearchParams() const navigate = useNavigate() const { t, formatDate, lang } = useLocalize() const initChat = () => { // eslint-disable-next-line solid/reactivity requireAuthentication(() => { - navigate('/inbox') - changeSearchParams({ - initChat: props.author?.id.toString() - }) + props.author?.id && navigate(`/inbox/${props.author?.id}`, { replace: true }) }, 'discussions') } diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx index 405749a9..251627e1 100644 --- a/src/components/Author/AuthorCard/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -1,6 +1,6 @@ -import { redirect, useNavigate, useSearchParams } from '@solidjs/router' +import { redirect, useNavigate } from '@solidjs/router' import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, onMount } from 'solid-js' +import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js' import { Button } from '~/components/_shared/Button' import stylesButton from '~/components/_shared/Button/Button.module.scss' import { FollowingCounters } from '~/components/_shared/FollowingCounters/FollowingCounters' @@ -34,11 +34,7 @@ export const AuthorCard = (props: Props) => { const [followsFilter, setFollowsFilter] = createSignal('all') const [isFollowed, setIsFollowed] = createSignal() const isProfileOwner = createMemo(() => author()?.slug === props.author.slug) - const { follow, unfollow, follows, following } = useFollowing() - - onMount(() => { - setAuthorSubs(props.flatFollows || []) - }) + const { follow, unfollow, follows, following } = useFollowing() // viewer's followings createEffect(() => { if (!(follows && props.author)) return @@ -56,30 +52,22 @@ export const AuthorCard = (props: Props) => { return props.author.name }) - const [, changeSearchParams] = useSearchParams() const initChat = () => { // eslint-disable-next-line solid/reactivity requireAuthentication(() => { - navigate('/inbox') - changeSearchParams({ - initChat: props.author?.id.toString() - }) + props.author?.id && navigate(`/inbox/${props.author?.id}`, { replace: true }) }, 'discussions') } - createEffect(() => { - if (props.flatFollows) { - if (followsFilter() === 'authors') { - setAuthorSubs(props.flatFollows.filter((s) => 'name' in s)) - } else if (followsFilter() === 'topics') { - setAuthorSubs(props.flatFollows.filter((s) => 'title' in s)) - } else if (followsFilter() === 'communities') { - setAuthorSubs(props.flatFollows.filter((s) => 'title' in s)) - } else { - setAuthorSubs(props.flatFollows) - } - } - }) + createEffect( + on(followsFilter, (f = 'all') => { + const subs = + f !== 'all' + ? follows[f as keyof typeof follows] + : [...(follows.topics || []), ...(follows.authors || [])] + setAuthorSubs(subs || []) + }) + ) const handleFollowClick = () => { requireAuthentication(() => { diff --git a/src/components/Inbox/CreateModalContent.tsx b/src/components/Inbox/CreateModalContent.tsx index ad0bc77e..fcecd440 100644 --- a/src/components/Inbox/CreateModalContent.tsx +++ b/src/components/Inbox/CreateModalContent.tsx @@ -57,8 +57,8 @@ const CreateModalContent = (props: Props) => { const handleCreate = async () => { try { - const initChat = await createChat(usersId(), chatTitle()) - console.debug('[components.Inbox] create chat result:', initChat) + const result = await createChat(usersId(), chatTitle()) + console.debug('[components.Inbox] create chat result:', result) hideModal() await loadChats() } catch (error) { diff --git a/src/components/Views/Inbox/Inbox.tsx b/src/components/Views/Inbox/Inbox.tsx index 6f89cff0..5ff38b8b 100644 --- a/src/components/Views/Inbox/Inbox.tsx +++ b/src/components/Views/Inbox/Inbox.tsx @@ -1,6 +1,7 @@ +import { useNavigate } from '@solidjs/router' import { clsx } from 'clsx' import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' - +import QuotedMessage from '~/components/Inbox/QuotedMessage' import { Icon } from '~/components/_shared/Icon' import { InviteMembers } from '~/components/_shared/InviteMembers' import { Popover } from '~/components/_shared/Popover' @@ -15,6 +16,7 @@ import type { MutationCreate_MessageArgs } from '~/graphql/schema/chat.gen' import type { Author } from '~/graphql/schema/core.gen' +import { getShortDate } from '~/utils/date' import SimplifiedEditor from '../../Editor/SimplifiedEditor' import DialogCard from '../../Inbox/DialogCard' import DialogHeader from '../../Inbox/DialogHeader' @@ -22,28 +24,16 @@ import { Message } from '../../Inbox/Message' import MessagesFallback from '../../Inbox/MessagesFallback' import Search from '../../Inbox/Search' import { Modal } from '../../_shared/Modal' - -import { useSearchParams } from '@solidjs/router' import styles from './Inbox.module.scss' -type InboxSearchParams = { - by?: string - initChat: string - chat: string -} const userSearch = (array: Author[], keyword: string) => { return array.filter((value) => new RegExp(keyword.trim(), 'gi').test(value.name || '')) } -type Props = { - authors: Author[] - isLoaded: boolean -} - -export const InboxView = (props: Props) => { +export const InboxView = (props: { authors: Author[]; chat?: Chat }) => { const { t } = useLocalize() - const { chats, messages, setMessages, loadChats, getMessages, sendMessage, createChat } = useInbox() + const { chats, messages, setMessages, loadChats, getMessages, sendMessage } = useInbox() const [recipients, setRecipients] = createSignal(props.authors) const [sortByGroup, setSortByGroup] = createSignal(false) const [sortByPerToPer, setSortByPerToPer] = createSignal(false) @@ -53,7 +43,6 @@ export const InboxView = (props: Props) => { const [isScrollToNewVisible, setIsScrollToNewVisible] = createSignal(false) const { session } = useSession() const authorId = createMemo(() => session()?.user?.app_data?.profile?.id || 0) - const [searchParams, changeSearchParams] = useSearchParams() const { showModal } = useUI() const handleOpenInviteModal = () => showModal('inviteMembers') let messagesContainerRef: HTMLDivElement | null @@ -64,12 +53,10 @@ export const InboxView = (props: Props) => { setRecipients(match) } } - + const navigate = useNavigate() const handleOpenChat = async (chat: Chat) => { setCurrentDialog(chat) - changeSearchParams({ - chat: chat.id - }) + navigate(`/inbox/${chat.id}`) try { const mmm = await getMessages?.(chat.id) if (mmm) { @@ -98,28 +85,11 @@ export const InboxView = (props: Props) => { setClear(false) } - createEffect(async () => { - if (searchParams?.chat) { - const chatToOpen = chats()?.find((chat) => chat.id.toString() === searchParams?.chat) - if (!chatToOpen) return - await handleOpenChat(chatToOpen) - return - } - if (searchParams?.initChat) { - try { - const newChat = await createChat([Number(searchParams?.initChat)], '') - await loadChats() - changeSearchParams({ - initChat: undefined, - chat: newChat.chat.id - }) - const chatToOpen = chats().find((chat) => chat.id === newChat.chat.id) - await handleOpenChat(chatToOpen as Chat) - } catch (error) { - console.error(error) - } - } - }) + createEffect( + on([() => props.chat, currentDialog], ([c, current]) => { + c?.id !== current?.id && handleOpenChat(c as Chat) + }) + ) const chatsToShow = () => { if (!chats()) return @@ -173,9 +143,85 @@ export const InboxView = (props: Props) => { } onMount(async () => { + props.chat && setCurrentDialog(props.chat) await loadChats() }) + const InboxNav = () => ( +
+
+ + +
+ + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ + {(chat) => ( + handleOpenChat(chat)} + isOpened={chat.id === currentDialog()?.id} + members={chat?.members as ChatMember[]} + ownId={authorId()} + lastUpdate={chat.updated_at || Date.now()} + counter={chat.unread || 0} + message={chat.messages?.pop()?.body || ''} + /> + )} + +
+
+
+ ) + return (
@@ -183,66 +229,7 @@ export const InboxView = (props: Props) => { {/**/}
-
-
- - -
- - -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- - {(chat) => ( - handleOpenChat(chat)} - isOpened={chat.id === currentDialog()?.id} - members={chat?.members as ChatMember[]} - ownId={authorId()} - lastUpdate={chat.updated_at || Date.now()} - counter={chat.unread || 0} - message={chat.messages?.pop()?.body || ''} - /> - )} - -
-
-
+
{ /> )} - {/*
*/} - {/* */} - {/*
*/} + + + + +
- -

FIXME: messageToReply

- {/* member.id === Number(messageToReply().author))*/} - {/* .name*/} - {/* }*/} - {/* body={messageToReply().body}*/} - {/* cancel={() => setMessageToReply(null)}*/} - {/*/>*/} + + member?.id === Number(messageToReply()?.created_by) + )?.name + } + body={messageToReply()?.body || ''} + cancel={() => setMessageToReply(null)} + />
{ const handleCreate = async () => { try { - const initChat = await createChat(collectionToInvite(), 'chat Title') - console.debug('[components.Inbox] create chat result:', initChat) + const result = await createChat(collectionToInvite(), 'chat Title') + console.debug('[components.Inbox] create chat result:', result) hideModal() await loadChats() } catch (error) { diff --git a/src/routes/author/(all-authors).tsx b/src/routes/author/(all-authors).tsx index 931d77b5..92cd7130 100644 --- a/src/routes/author/(all-authors).tsx +++ b/src/routes/author/(all-authors).tsx @@ -15,7 +15,7 @@ const fetchAuthorsWithStat = async (offset = 0, order?: string) => { return await authorsFetcher() } -const fetchAllAuthors = async () => { +export const fetchAllAuthors = async () => { const authorsAllFetcher = loadAuthorsAll() return await authorsAllFetcher() } diff --git a/src/routes/inbox/(chats).tsx b/src/routes/inbox/(chats).tsx new file mode 100644 index 00000000..100ad8ec --- /dev/null +++ b/src/routes/inbox/(chats).tsx @@ -0,0 +1,28 @@ +import { RouteDefinition, RouteSectionProps, createAsync } from '@solidjs/router' +import { InboxView } from '~/components/Views/Inbox/Inbox' +import { PageLayout } from '~/components/_shared/PageLayout' +import { ShowOnlyOnClient } from '~/components/_shared/ShowOnlyOnClient' +import { useLocalize } from '~/context/localize' +import { Author } from '~/graphql/schema/core.gen' +import { fetchAllAuthors } from '../author/(all-authors)' + +export const route = { + load: async () => { + return { + authors: await fetchAllAuthors() + } + } +} satisfies RouteDefinition + +export const InboxPage = (props: RouteSectionProps<{ authors: Author[] }>) => { + const { t } = useLocalize() + const authors = createAsync(async () => props.data.authors || (await fetchAllAuthors())) + + return ( + + + + + + ) +} diff --git a/src/routes/inbox/[chat].tsx b/src/routes/inbox/[chat].tsx new file mode 100644 index 00000000..521fd3ae --- /dev/null +++ b/src/routes/inbox/[chat].tsx @@ -0,0 +1,53 @@ +import { RouteDefinition, RouteSectionProps, createAsync, useParams } from '@solidjs/router' +import { createSignal, onMount } from 'solid-js' +import { InboxView } from '~/components/Views/Inbox/Inbox' +import { PageLayout } from '~/components/_shared/PageLayout' +import { ShowOnlyOnClient } from '~/components/_shared/ShowOnlyOnClient' +import { useInbox } from '~/context/inbox' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { Chat } from '~/graphql/schema/chat.gen' +import { Author } from '~/graphql/schema/core.gen' +import { fetchAllAuthors } from '../author/(all-authors)' + +export const route = { + load: async () => { + return { + authors: await fetchAllAuthors() + } + } +} satisfies RouteDefinition + +export const ChatPage = (props: RouteSectionProps<{ authors: Author[] }>) => { + const { t } = useLocalize() + const params = useParams() + const { createChat, chats } = useInbox() + const [chat, setChat] = createSignal() + const { session } = useSession() + const authors = createAsync(async () => props.data.authors || (await fetchAllAuthors())) + + onMount(async () => { + if (params.id.includes('-')) { + // real chat id contains - + setChat((_) => chats().find((x: Chat) => x.id === params.id)) + } else { + try { + // handle if params.id is an author's id + const me = session()?.user?.app_data?.profile.id as number + const author = Number.parseInt(params.chat) + const result = await createChat([author, me], '') + // result.chat.id && redirect(`/inbox/${result.chat.id}`) + result.chat && setChat(result.chat) + } catch (e) { + console.warn(e) + } + } + }) + return ( + + + + + + ) +}