diff --git a/src/components/Inbox/DialogAvatar.module.scss b/src/components/Inbox/DialogAvatar.module.scss new file mode 100644 index 00000000..0bf16d19 --- /dev/null +++ b/src/components/Inbox/DialogAvatar.module.scss @@ -0,0 +1,35 @@ +.DialogAvatar { + width: 40px; + height: 40px; + border-radius: 100%; + display: flex; + align-items: center; + justify-content: center; + position: relative; + + &.online::before { + content: ''; + position: absolute; + background: #2bb452; + width: 8px; + height: 8px; + top: -2px; + right: -2px; + border-radius: 50%; + border: 3px solid #fff; + } + + > img, + > .letter { + display: block; + border-radius: 100%; + } + + > .letter { + margin-bottom: -2px; + font-weight: 500; + font-size: 18px; + line-height: 10px; + color: #fff; + } +} diff --git a/src/components/Inbox/DialogAvatar.tsx b/src/components/Inbox/DialogAvatar.tsx new file mode 100644 index 00000000..7d3788d8 --- /dev/null +++ b/src/components/Inbox/DialogAvatar.tsx @@ -0,0 +1,48 @@ +import { Show, createMemo } from 'solid-js' +import './DialogCard.module.scss' +import styles from './DialogAvatar.module.scss' +import { clsx } from 'clsx' + +type Props = { + url: string + name: string + online?: boolean +} + +const colors = [ + '#001219', + '#005f73', + '#0a9396', + '#94d2bd', + '#ee9b00', + '#ca6702', + '#ae2012', + '#9b2226', + '#668CFF', + '#C34CFE', + '#E699FF', + '#6633FF' +] + +const getById = (letter: string) => + colors[Math.abs(Number(BigInt(letter.toLowerCase().charCodeAt(0) - 97) % BigInt(colors.length)))] + +const DialogAvatar = (props: Props) => { + const nameFirstLetter = props.name.substring(0, 1) + const randomBg = createMemo(() => { + return getById(nameFirstLetter) + }) + + return ( +
+
{nameFirstLetter}
}> + {props.name} +
+
+ ) +} + +export default DialogAvatar diff --git a/src/components/Inbox/DialogCard.module.scss b/src/components/Inbox/DialogCard.module.scss new file mode 100644 index 00000000..c66470f5 --- /dev/null +++ b/src/components/Inbox/DialogCard.module.scss @@ -0,0 +1,67 @@ +.DialogCard { + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + font-size: 14px; + padding: 12px; + transition: background 0.3s ease-in-out; + cursor: pointer; + + &:hover { + background: #f7f7f7; + } + + .avatar { + flex-basis: 40px; + margin-right: 12px; + } + + .row { + flex-basis: 0; + flex-grow: 1; + min-width: 0; + + .name, + .message { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .name { + color: #141414; + font-weight: 500; + } + .message { + color: #9fa1a7; + } + } + + .activity { + font-size: 12px; + margin-left: 12px; + .time { + text-align: right; + color: #ccc; + } + + .counter { + display: flex; + margin-left: auto; + align-items: center; + justify-content: center; + border-radius: 12px; + padding: 0 8px; + background: #d00820; + font-weight: 400; + color: #fff; + width: 22px; + height: 22px; + line-height: 6px; + + > span { + margin-bottom: -2px; + } + } + } +} diff --git a/src/components/Inbox/DialogCard.tsx b/src/components/Inbox/DialogCard.tsx new file mode 100644 index 00000000..dea0d61d --- /dev/null +++ b/src/components/Inbox/DialogCard.tsx @@ -0,0 +1,34 @@ +import './DialogCard.module.scss' +import styles from './DialogCard.module.scss' +import DialogAvatar from './DialogAvatar' +import type { Author } from '../../graphql/types.gen' + +type Props = { + online?: boolean + message?: string + counter?: number +} & Author + +const DialogCard = (props: Props) => { + return ( +
+
+ +
+
+
{props.name}
+
+ Указать предпочтительные языки для результатов поиска можно в разделе +
+
+
+
22:22
+
+ 12 +
+
+
+ ) +} + +export default DialogCard diff --git a/src/components/Inbox/Search.module.scss b/src/components/Inbox/Search.module.scss new file mode 100644 index 00000000..2808aba0 --- /dev/null +++ b/src/components/Inbox/Search.module.scss @@ -0,0 +1,44 @@ +.Search { + .field { + position: relative; + background: #ffffff; + border: 2px solid #e8e8e8; + border-radius: 2px; + overflow: hidden; + + input { + display: block; + height: 40px; + border: none; + box-shadow: none; + padding: 10px 36px 10px 12px; + width: 100%; + font-family: Muller, Arial, Helvetica, sans-serif; + font-style: normal; + font-weight: 400; + font-size: 15px; + + &::placeholder { + color: #858585; + font-family: inherit; + } + &:focus { + outline: none; + & + .icon { + opacity: 0; + right: -30px; + } + } + } + + .icon { + transition: 0.3s ease-in-out; + position: absolute; + width: 16px; + height: 16px; + top: 12px; + right: 12px; + opacity: 0.5; + } + } +} diff --git a/src/components/Inbox/Search.tsx b/src/components/Inbox/Search.tsx new file mode 100644 index 00000000..d180331a --- /dev/null +++ b/src/components/Inbox/Search.tsx @@ -0,0 +1,27 @@ +import styles from './Search.module.scss' +import { createSignal } from 'solid-js' +import { Icon } from '../Nav/Icon' + +type Props = { + placeholder: string + onChange: (value: () => string) => void +} + +const Search = (props: Props) => { + const [value, setValue] = createSignal('') + const search = (event) => { + event.preventDefault() + setValue(event.target.value) + props.onChange(value) + } + return ( +
+
+ + +
+
+ ) +} + +export default Search diff --git a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx index f19a89f4..0dc12583 100644 --- a/src/components/Nav/AuthModal/ForgotPasswordForm.tsx +++ b/src/components/Nav/AuthModal/ForgotPasswordForm.tsx @@ -22,7 +22,7 @@ export const ForgotPasswordForm = () => { setValidationErrors(({ email: _notNeeded, ...rest }) => rest) setEmail(newEmail) } - + const [sended, setSended] = createSignal(false) const [submitError, setSubmitError] = createSignal('') const [isSubmitting, setIsSubmitting] = createSignal(false) const [validationErrors, setValidationErrors] = createSignal({}) @@ -53,6 +53,7 @@ export const ForgotPasswordForm = () => { try { const result = await signSendLink({ email: email(), lang: locale() }) if (result.error) setSubmitError(result.error) + else setSended(true) } catch (error) { setSubmitError(error.message) } finally { @@ -63,7 +64,12 @@ export const ForgotPasswordForm = () => { return (

{t('Forgot password?')}

-
{t('Everything is ok, please give us your email address')}
+ {t('Link sent, check your email')}} + > +
{t('Everything is ok, please give us your email address')}
+
    diff --git a/src/components/Views/Inbox.tsx b/src/components/Views/Inbox.tsx index 47b67d91..747f2163 100644 --- a/src/components/Views/Inbox.tsx +++ b/src/components/Views/Inbox.tsx @@ -1,59 +1,140 @@ +import { For, createSignal, Show, onMount, createEffect } from 'solid-js' import type { Author } from '../../graphql/types.gen' import { AuthorCard } from '../Author/Card' import { Icon } from '../Nav/Icon' +import { Loading } from '../Loading' +import DialogCard from '../Inbox/DialogCard' +import Search from '../Inbox/Search' +import { useAuthorsStore } from '../../stores/zine/authors' + import '../../styles/Inbox.scss' +// Для моков +import { createClient } from '@urql/core' +import { findAndLoadGraphQLConfig } from '@graphql-codegen/cli' + +const OWNER_ID = '501' +const client = createClient({ + url: 'https://graphqlzero.almansi.me/api' +}) // interface InboxProps { // chats?: Chat[] // messages?: Message[] // } +const messageQuery = ` +query Comments ($options: PageQueryOptions) { + comments(options: $options) { + data { + id + body + email + } + } +} +` +const newMessageQuery = ` +mutation postComment($messageBody: String!) { + createComment( + input: { body: $messageBody, email: "test@test.com", name: "User" } + ) { + id + body + name + email + } +} +` + +const userSearch = (array: Author[], keyword: string) => { + const searchTerm = keyword.toLowerCase() + return array.filter((value) => { + return value.name.toLowerCase().match(new RegExp(searchTerm, 'g')) + }) +} + export const InboxView = () => { + const [messages, setMessages] = createSignal([]) + const [authors, setAuthors] = createSignal([]) + const [postMessageText, setPostMessageText] = createSignal('') + const [loading, setLoading] = createSignal(false) + const { sortedAuthors } = useAuthorsStore() + + createEffect(() => { + setAuthors(sortedAuthors()) + }) + + // Поиск по диалогам + const getQuery = (query) => { + if (query().length >= 2) { + const match = userSearch(authors(), query()) + console.log('!!! match:', match) + setAuthors(match) + } else { + setAuthors(sortedAuthors()) + } + } + + const fetchMessages = async (query) => { + const response = await client + .query(query, { + options: { slice: { start: 0, end: 3 } } + }) + .toPromise() + if (response.error) console.debug('getMessages', response.error) + setMessages(response.data.comments.data) + } + const postMessage = async (msg: string) => { + const response = await client.mutation(newMessageQuery, { messageBody: msg }).toPromise() + return response.data.createComment + } + + let chatWindow + onMount(async () => { + setLoading(true) + await fetchMessages(messageQuery) + .then(() => { + setLoading(false) + chatWindow.scrollTop = chatWindow.scrollHeight + }) + .catch(() => setLoading(false)) + }) + + const handleSubmit = async () => { + postMessage(postMessageText()) + .then((result) => { + setMessages((prev) => [...prev, result]) + }) + .then(() => { + setPostMessageText('') + chatWindow.scrollTop = chatWindow.scrollHeight + }) + } + const handleChangeMessage = (event) => { + setPostMessageText(event.target.value) + } + // TODO: get user session return (
    - - - - - +
    - -
    -
      -
    • - -
      12:15
      -
      - Lorem ipsum dolor sit amet, consectetur adipisicing elit. -
      -
    • -
    • - -
      19:48
      -
      - Assumenda delectus deleniti dolores doloribus ducimus, et expedita facere iste laborum, - nihil similique suscipit, ut voluptatem. Accusantium consequuntur doloremque ex molestiae - nemo. -
      -
    • -
    +
    +
    + + {(author) => } + +
    @@ -64,59 +145,50 @@ export const InboxView = () => {
    -
    -
    -
    - Круто, беру в оборот! -
    - +
    + + + + + {(comment: { body: string; id: string; email: string }) => ( +
    +
    + {comment.body} +
    + + {comment.email} id: {comment.id} +
    + +
    - -
    -
    + )} + -
    -
    - Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut beatae earum iste itaque - libero perspiciatis possimus quod! Accusamus, aliquam amet consequuntur debitis dolorum - esse laudantium magni omnis rerum voluptatem voluptates! -
    - - Отредактировано -
    - -
    -
    - -
    - -
    - -
    -
    - Нужна грамотная инфраструктура для сообщений, если ожидается нагрузка - надо опираться на - это. Но в целом это несложно сделать. -
    - -
    - -
    -
    + {/*
    */} + {/* */} + {/*
    */}
    -
    - -